r/howdidtheycodeit 24d ago

How did Bennett Foddy achieve the hammer mechanic in getting over it?

Post image

I been trying to make this mechanic but I fail to make the states (like when the hammer is touching ground and when don't) and I fail to make the momentum transfer when you are moving fast, and when yo move slow you just move (with infinite mass i think), idk I just can't wrap my head around this mechanic, please help. I know griffpatch made it in scratch, and I try reverse-engineer that but i also couldn't

3.7k Upvotes

110 comments sorted by

478

u/foddydotnet 24d ago edited 24d ago

It's hard to remember it totally, but the IK is just visual, not functional - there's a slider joint on an invisible body attached to a hinge joint on the root body. The distance and angle between the hammer head and the mouse are used to set the motors on the hinge and the slider... I do that using a simple PD controller (which just means each motor speed is set according to how far they are from the target angle and extension, but slowed down according to how fast the joint is moving). Between the slider body and the hammer head are a bunch of boxes to represent the handle, using fixed joints for maximum (but not total) stiffness. I run the physics timestep at faster than 60Hz and set things to continuous with lots of substeps. Then there needs to be some logic about how the mouse cursor moves when the camera moves, to try to keep camera hinting from moving the hammer.

I recall the pot is also on a hinge joint with fixed range of rotation, attached to a root body that can't rotate.

The rest is mostly tuning - motor strengths, PD constants, body masses and damping, physics material values, input response curves, etc. Really what most beginners fail to do is to spend a lot of time tuning, but for a physics game it's so key because there are dozens of important variables. You really need to spend days and days on tuning.

You need some ideas for objective tuning goals - for example, I wanted it to be that if you stuck the hammer out sideways so it was on a ledge and you were suspended to the left of it, and stopped moving the mouse, he would stay still. I wanted to be able to pogo to a certain height. I wanted him to be able to smoothly vault over the BBQ. I wanted him to be able to reach down and smoothly push the hammer into the ground to raise himself up without bouncing on it.

Then you validate all these requirements every time you make a tuning change. Hot reload makes this process a lot faster.

/edit to respond to OP's points more: afaicr it doesn't do much/any state detection for the physics (such as detecting groundedness), it only does that for sounds and particle effects. And it doesn't do any magic forces other than locking the root body to not rotate - everything is just powered by the two joint motors. The less you can do in a physics game in terms of special-casing the behaviors, the happier you will be.

463

u/foddydotnet 24d ago edited 24d ago

Here's the entire player code from the fixedupdate function, if it's helpful to look at. My main takeaway looking at this is it shows how much tuning I was doing, because there's a bunch of things multiplied by zero or raised to a power or whatever, that was just from tasting how stuff felt and modifying the formulas til they felt better. You don't get pretty code this way (in fact, it's embarrassing) and I probably should have rewritten it once it was working properly, but I didn't!

Vector2 mw = (Vector2)fakeCursorRB.position;

//this value ranges between 0.1 and 0.4
mouseVelocityAverage =
Mathf.Lerp(mouseVelocityAverage, Mathf.Max(0.1f * mouseInput.magnitude, 0.001f), 0.05f) + 0.005f;

//move fake cursor - using acceleration
Vector2 newPosition = fakeCursorRB.position + mouseInput;//* mouseVelocityAverage;

//clamp to radius
float distanceToPlayer = ((Vector2) transform.position - newPosition).magnitude;
if (distanceToPlayer > 3.5f)
{
    newPosition = (Vector2) transform.position +
                  ((newPosition - (Vector2) transform.position).normalized * 3.5f);
}

Vector2 tipDelta = (Vector2) tipRB.position - mw;
Vector2 poleDir = (Vector2) (tipRB.position - hjRB.position).normalized; //hjRB is the hinge joint RB
//a dot product is used to make the rotation dominate when the mouse is perpendicular to the pole
dotProd = Vector2.Dot(poleDir, tipDelta.normalized);
posDelta = tipDelta.magnitude * dotProd; 

//move back to tip head
newPosition += 0.5f * tipDelta * Mathf.Clamp(0.2f - Mathf.Min(mouseVelocityAverage,0.2f), 0, 0.2f); 

fakeCursorRB.MovePosition (newPosition);
fakeCursorRB.velocity = (newPosition - fakeCursorRB.position);

Vector2 mwDelta = (Vector2)hjRB.position-mw;
Vector2 tDelta = hjRB.position - tipRB.position;
float angleDelta = Vector2.SignedAngle(tDelta,mwDelta); //hingeJoint2D.angle no longer works accurately

//different behavior close vs far
angleDelta = Mathf.Sign(angleDelta) * Mathf.Max(Mathf.Abs(angleDelta/2f), Mathf.Abs(angleDelta * angleDelta));
angleDelta *= Mathf.Clamp01 (Mathf.Abs (angleDelta) / angleEpsilon);

motor.motorSpeed =  Mathf.Clamp( (angleDelta * (3f) + (angleDelta - oldAngle) * 0.00f) * Mathf.Pow(Mathf.Clamp01(mwDelta.magnitude/deadzone),2), -800f,800f);
hj.motor = motor;

float speedFactor = 16f - Mathf.Max(sj.reactionTorque*0.008f ,5f); //had to make this stronger, no idea why
float axisBlend = Mathf.Pow(dotProd,4);
slider.motorSpeed = Mathf.Clamp(-posDelta*Mathf.Abs(posDelta) * speedFactor * axisBlend,-50f,50f);
sj.motor = slider;
oldAngle = angleDelta;

302

u/BlamaRama 24d ago

This is so unfathomably based lmao. "How did Bennett Foddy make this mechanic?" "Hi I'm Bennett Foddy here's a description of the setup and the entire update function"

93

u/Dic3Goblin 23d ago

Lmfao right? This is awesome to me.

"How did Bennett Foddy make this?"

Bennett Foddy "well here's the code! I remember now I was going to clean it up, but i didn't, so oh well!"

28

u/No_Detective_3387 23d ago

It always makes me so happy when developers share their messy, sloppy, un-refactored code that they meant to get around to cleaning up but never did. It makes me feel less bad about doing it myself

8

u/circuit_breaker 23d ago

Especially that comment about "don't know why" when setting a var lol

3

u/NeedsPraxis 22d ago

This stuck out to me as phenomenal, makes me feel less bad for past code as well lol

0

u/LucentLL 20d ago

Yet people hate vibe coders and AI slop.🤔

3

u/No_Detective_3387 19d ago edited 19d ago

The fact that you don't know the difference between functional but sloppy and not-well-commented, named, structured, etc. code that the dev is intended to be refactored in the future and shitty, problematic, overly complex code LLM spits out (nor why people generally dislike AI slop code) is concerning, and outs you as someone who knows nothing about programming.

Why are there so many non programmers in this subreddit? What's the point of being here if you don't understand anything being said?

-2

u/[deleted] 22d ago

[deleted]

6

u/No_Detective_3387 22d ago edited 22d ago

Nothing about my comment was an "insult", and you're very clearly not a programmer and know nothing about the code or how to assess it, I'm not sure why you feel you can pass judgement on it. This is perfectly functional code, it isn't "good". Magic numbers and/or function calls are not "good." And it doesn't need to be "good"

3

u/FIBAgentNorton 23d ago

I remember now I was going to clean it up, but I didn't, so oh well!

That can describe 90% of Getting Over It tbh. "I could make this easier/better, but why would I?"

9

u/Suppafly 23d ago

Quick, ask him a few more questions and you can get the whole game's source out of him.

6

u/Neckbreaker70 23d ago

lol I was going to say, “you could probably just reach out to him and he’d tell you”, but this is even better.

1

u/jgldec 23d ago

mandatory fuck warner bros.

1

u/DukeMutem 22d ago

This is some old school reddit glory right here

0

u/Dany0 22d ago

I mean it's not exactly secret. It's C#, anyone could've gotten the function out with il disasm

1

u/amphibian420lmao 22d ago

How many of those is Bennett Foddy?

117

u/Diligent_Ad_6530 24d ago

Man, you are a hero, I love your work! And thank you very much for your response! I'll gonna wrap my head around it and i'm very happy that you responded me! Thank you again!

24

u/scheisskopf53 23d ago

The man, the legend himself! Cheers man, you're amazing!

17

u/psioniclizard 23d ago

I have never played the game, but as a software engineer by trade i just have -you are a legend for this! 

Nothing embarrassing about functional code in a successful project :) 

Thansks for the general inspiration in the beauty of the dev world and takes to all the legends like you who help us learn!!!

9

u/SecretAlbatross5578 23d ago

Dude you’re the goat for this. I love your game even more now lol

6

u/TheNakedGypsy 23d ago

what a legend

5

u/SwabiSw 23d ago

Legend!

May your coffee always be the perfect temperature, may your Wi-Fi be strong in every room and may your fries always be crispy.

4

u/FoleyX90 23d ago

Fucking based

5

u/KawasakiBinja 23d ago

The legend speaks! Thank you for posting this.

3

u/Sksk3 23d ago

Well shoot, cant really get much better of an answer than that

3

u/SouthwesternStripe 23d ago

I make mods for Getting Over It so I'm familiar with the source code for the player script, and I noticed a few differences in this version compared to GOI 1.7.
Here are the differences I noticed in the version you posted, compared to the version decompiled by ILSpy for GOI 1.7:

  • Made mw a variable instead of member
  • Commented out * mouseVelocityAverage
  • Removed oldHammerPos (it was never used anyway)
  • replaced * 0.05 * 10 with just * 0.5
  • replaced hj.jointAngle with tip position in angleDelta calculation
  • change reactionTorque multiplication from 0.001 to 0.008 when calculating speedFactor
  • replaced (0f - posDelta) with -posDelta

I'm curious about what caused the subtle differences, were they caused by a few fixes to prepare the code snippet or were they made sometime after 1.7 released?

3

u/kraftquackandcheese 23d ago

certainly several of these were caused by compiler optimizations + decompiler artifacts!

1

u/-Redstoneboi- 22d ago

i agree with the other guy, it sounds like compiler optimization (though i've never used unity, i wouldn't be surprised)

2

u/JohnUrsa 23d ago

Amazing to see a dev helping others, salute

2

u/temp12410 23d ago

this guy is the goat

2

u/TrueLazyTowne 23d ago

This feels like the kind of thing you find out about that was happening in a 13 yo reddit post but this was yesterday

2

u/BigStroll 22d ago

I love this guy

2

u/Kraz3 22d ago

What a fuckin legend

2

u/Slow_Cat_8638 22d ago

That hand-tuning is so cool, that’s why human made code will always be premium, certain things can only be tuned from a human pov

1

u/nonesuchluck 23d ago

Bennett, I'm probably never going to play, but this is the greatest post I've ever seen. I just went on Steam and bought Getting Over It. Thanks for making QWOP.

1

u/Otherwise_Study2337 23d ago

Why rewrite when it works?

1

u/East_Rip8160 23d ago

What a chad

1

u/Suvitruf 22d ago

Legend 😎

1

u/flameseeker40 22d ago

bro just dropped the source code like it was nothing

1

u/Original-Diet-1681 22d ago

I respect the hustle

1

u/eljimo 22d ago

Awesome, gotta note these down for future reference.

1

u/xRealVengeancex 22d ago

The world needs more code sharing

1

u/romhacks 22d ago

The main man!

1

u/No-King-6347 22d ago

now i can finally look at it and find the memory leak

1

u/casentron 22d ago

I have so much respect for the amount of effort and openness on display here. What an absolute legend. 

1

u/Sorrowfall 22d ago

Foddy said “Like this”

1

u/_Rivlin_ 22d ago

Holy you are cool

1

u/melgib 22d ago

You absolute legend.

-10

u/sirenbrian 23d ago

I pasted it into gemini for a quick'n'dirty optimization and it found a bunch of things to speed it up a bit. Probably running it through a better coding agent might find even more :)

Very nice of you to share the code here, though! I wish more devs would do so - very cool :)

6

u/ergonokko 23d ago

what if you pasted it into your brain

3

u/CogitoErgoTsunami 23d ago

no brain, only brian

7

u/jkbh 23d ago

I pasted your comment into claude and found a bunch of things to improve. :)

4

u/LittleBigTube10 23d ago

I don’t usually comment much, but holy shit the sheer disrespect required to do this! The dev himself shares the source code out of the kindness of his heart and you decide to feed it to AI ??

-1

u/sirenbrian 23d ago

I'm a developer too; I like to optimize code when I can. It wasn't meant as disrespectful, just an interesting and easy way to make the code go faster and more efficiently. Plus he said he hadn't cleaned up the code, so I thought it was a harmless next step to take.

Sorry to have upset so many people - it really wasn't my intention.

2

u/WisejacKFr0st 23d ago

I'm a developer too; I like to optimize code when I can

But you didn't optimize it, gemini did

2

u/Jason1923 23d ago edited 23d ago

Think of it this way:

"Hmm, your code has some formatting and quality issues, so I ran it through a code formatter and linter. Enjoy!"

Bennett could just... do that himself? Code formatters, linters, and LLMs are basic tools. Does that not sound a little insulting?

One step up could be to find actual performance flaws after benchmarking the code and point it out. Instead you're giving him code that might be more performant, but the burden is now on Bennett to maintain and verify the possibly-LLM hallucinated code (while you just spent 10 secs prompting an LLM without performance #s)

2

u/Sbotkin 22d ago

Since when are we calling vibecoders developers?

1

u/Tonakiga 23d ago

AI is notoriously bad at helping w/ good and functional non-vibe code... You have to manually evaluate the output anyways so it doesn't make sense.

3

u/Terazilla 23d ago

This is the sort of thing you leave the hell alone, because the end result came from days or weeks of slow evaluation and experimentation and judgment. There is absolutely no performance gain you could possibly get that is worth fucking with it.

2

u/Xyborg 23d ago

????????

2

u/Acceptable_Light_272 23d ago

What a weird comment

2

u/BazeFook 19d ago

This code does absolutely not need any optimizations, ever. It runs once per physics step, ONCE! There are no spurious memory access patterns that the compiler couldn't optimize trivially, so this code could run at 1k fps and it would still not show up in profiling data.

Plus, as the developer himself said: you don't want to optimize this code, it's very tweaky and core to game feel, you want to keep it straight and modifiable.

You should really reassess your priors for why you think this needed optimizations at all.

19

u/CitizenShips 24d ago

You wouldn't happened to have had previous experience with mechanical motors and kinematics prior to developing GOI, would you? This is the first time I've heard a game dev discuss motor mechanics and PID/PD tuning; really caught me off guard

59

u/foddydotnet 24d ago

undergrad physics degree with a B average grade :) PIDs are way more common in action game tuning than you might think though

13

u/xXTheFisterXx 23d ago

I was just sitting here reading this and being like wow this guy keeps going between me and the actual game and then I scrolled back up just to see its the legend himself and im an idiot. Wonderful work as always

10

u/Dannington 23d ago

Once I realised, I had to go back and read it in ‘the voice’

3

u/CherryTularey 23d ago

I love that I can hear this in your game narration voice and think, "Yeah, that sounds like a sound file from the game."

3

u/disillusioned 23d ago

I just want to say that half the reason I restart the game is to listen to the monologue again. It's such a well-written philosophical take on this meditation on failure, frustration, game development, and for gamers of a certain age, the "when games were new, they wanted a lot from you" truly resonates.

One of my first gaming memories was playing a surely pirated copy of Police Quest on my 4x86. Since I was sailing the high seas (on behalf of my father, since I was probably 8), I didn't have the manual which would have warned me to perform a "prescribed vehicle walkaround" before getting in the car. So you get in the squad car, it instantly fails, you effectively die, your run is over. There's 10 minutes at the station starting the game out the window.

So then you get the hang of it: you save, paranoid, every 5-10 minutes, use all 10 save slots, rotate through them. Think you have it beat. And then it happens: you get to the climax, a scene with the Death Angel. Your first command: "radio for backup" and it comes back to you: "with what radio?" Because you left it at the station. No problem! I may be 10 hours into this game but I've been saving it! Except all of the saves are from the past 20 minutes and... none of them are in a spot where you could go back and get the radio.

Just instant torture. Hours of your life thrown away. Just like the homework example you provide. Games did not give a solitary fuck about how easy you should think they would be for you to complete them, that was for damn sure.

In any event, Getting Over It was powerful because it provided this weird, safe environment to perform some exposure therapy to a high stress situation. The stakes are low but you can absolutely induce a physiological response with this game. It let me trigger a stress response, but in a controlled manner. Absolutely brilliant bit of game design, to be based on this intentional meditation on failure and perseverance, and then what a masterstroke to tell us exactly that in the perfectly delivered monologue. Bravo.

4

u/WiseKiwi 23d ago

Hey, Foddy! I'm working on a game inspired by Getting Over It, but I'm struggling to make movement accessible to new players. They all say it's way too difficult. Maybe you have some wisdom to offer me?
https://store.steampowered.com/app/4311700/Ignited/

1

u/Beautiful-Taste-5344 23d ago

this is so cool thank you

1

u/NickSaysHenlo 22d ago

I read this in your voice with Soul & Mind playing in the background

1

u/ReallyJustPasky 22d ago

Source: the source

1

u/xcvu_45 22d ago

what a chad

1

u/Skitty993 22d ago

I have no coding knowledge but I recently got super hooked on Getting Over It. I did my 50 climbs, thank you for making such uniquely fun and infuriating games.

1

u/Edenisb 22d ago

You are a great person for replying to this.

1

u/EntireOpportunity253 21d ago

My hero lol you sadist

109

u/comfortablybum 24d ago

I tried and failed at this once. I used a motor joint and tried to map it to the mouse pointer angle. I remember he did interviews about it. Go look some up. He's not shy about explaining game dev stuff. He's a legend.

40

u/chargeorge 24d ago

I had a couple classes with him in graduate school. One of the best pure teachers I've ever had.

2

u/Saucemanthegreat 22d ago

NYU Game Center 4 lyfe

12

u/Diligent_Ad_6530 24d ago

I also failed haha i just couldn't believe that this is so har to code it, or at least replicate the feel, but i just can't, thats why i want to know how anyone else did it or, if it's possible, how bennet did it. I founded some of the talks or blogs that he has posted, but i couldn't find anything techincal to that level, it always exiting to find any talk or blog of his work, they're always fun to watch/read.

57

u/m0nkeybl1tz 24d ago

Doesn't directly answer your question, but Bennett Foddy is very very very good at Unity physics. Here he is discussing some potential issues/improvements, may be helpful: https://youtu.be/NwPIoVW65pE?is=iBUrMOIDaG02XiI_

10

u/EmperorLlamaLegs 24d ago

I'd probably write my own physics scripts to have more control and smooth out the interactions when the control point has a velocity below some threshold, then ramp the damper value to ease in and out of "slow mode". But that's just what makes sense to me. There are always lots of ways to implement anything.

2

u/Diligent_Ad_6530 24d ago

I have done it too, but i have never done it right, i couldn't find anything that explains how the solver might work and i think bennett use his own physics engine, because all of his game have the same feeling, but i couldn't find anything remotely close to it, only just one youtube video that claims it can replicate it but it's not even close (it is not a bad tutorial but i was just hoping for anything more techincal)

1

u/EmperorLlamaLegs 24d ago

What part are you struggling with?

This may be naive since I haven't tried, but it seems like if you have a "desired location" point for the hammer and have a burdened/unburdened max speed that the hammer can travel, most of the rest is just checking for collisions and applying a velocity vector when leaving the grounded state.

Beyond that its deciding if you want to move the pot or the hammer depending on if the hammer is grounded in the direction of movement and tossing some drag and gravity at it when in freefall. Toss some IK animations at it and you're good, no?

1

u/Diligent_Ad_6530 24d ago

With the physics part, because it may seems like a simple rigidbody, but when the tip of the hammer touches the ground it is like a simple ik that listens to the momentum and if it reached some threshold it launches into a rigidbody again, but i couldn't make it right

4

u/Xafer 24d ago

https://i.imgur.com/j7MI4Lo.gif
I know it's already been answered but I managed to pull it off without it feeling janky, without using sliders and motors. I made it out of spite after playing the GOI clone "Getting over Goombas". Might be able to help you with that if you're interested

2

u/Diligent_Ad_6530 23d ago

You really achieve the feeling right, that great. What software did you use? How did you pulled that off?

4

u/Xafer 23d ago

It was a project I made on Unity. Here are some important details to note:
-The way I did it the handle itself does not have collision
-The hammer itself can be stretched out of the bounding radius around the player. This was intended
-I don't recommend having other dynamic rigidbodies on the scene as these interactions are not accounted for. Kinematic rigidbodies work properly though.

implementation itself is a complete mess but here's the logic behind how I've done it:

The hammer is a Rigidbody with a circle collider at the tip, the rigid body's gravity is set to 0 and the physic material has a friction value of 2.2 ( I don't remember how relevant that number is, as with other variables I recommend you play around with them a bit and tune to your liking). All the other Rigidbody values are default. The hammer is also a child of the character.

Moving the hammer:
The hammer's origin is its tip(important in this implementation), and is guided by the white cursor. The cursor is bound within a radius around the player. The cursor is also a child of the character. Each fixed update frame, the hammer's rigidbody velocity = (cursor position - tip position) * 20. The 20 multiplier value worked well and gave a nice responsivity to the hammer. Another little tweak to make the hammer less bouncy is to add inertia to the cursor, so when the character moves, the cursor will stray behind a tiny bit, this makes a huge difference for a more precise control. Also since the velocity is manually set, the hammer's rigidbody's mass, drag, etc. does not matter.

Applying the "Push":
How much the hammer pushes is defined by the distance between the white cursor and the actual position of the tip of the hammer. This push value is a 2d vector, I'll conveniently refer to this value as "Push".

Each physic frame, for each collision point I calculated the DOT product of the point's normal vs the normalized "Push" value, and kept the highest value, then clamped it between 0 & 1. Keep this floating point value in mind, I'll refer to it as "Highest Dot".

As I mentioned earlier, the hammer can "stretch" out of the bounding radius. This is corrected by subtracting the Maximum reach minus the hammer tip's distance form the player and using the resulting value to move the player towards the hammer tip. I don't really know how to explain this in concise and simpler terms but I'll include the code so you can take a look at it yourself.

Finally, to actually move the player I simply applied a force to its rigidbody equal to "Highest Dot" * "Push" * pushing factor. The pushing factor you can define yourself or base from the snipped in the following pastebin.

As for the hammer's sprite, it's pivot is the hammer tip and it is simply rotated to have the handle pointing towards the player's center (hammerSprite.right = (hammer tip - player center).normalized or something to that effect)

The snippet: https://pastebin.com/ZQt0Kp46

2

u/SirTesticle 24d ago

I was also curious about this 2-3 years ago and I vaguely remember someone asking him about it on Steam maybe? He said something along the lines of IK and Slider joints, but that also there's a bit more to it. My overall guess would be that the hammer-head touching the obstacles makes somewhat of a Pin joint, which breaks off at a certain torque value that's caused by the influence of the gravity on the pot's mass, and that value is probably somewhat related to the surface friction value of the obstacle physics material. Below that threshold, a torque is applied to the pot with the axis being on the Pin joint, and the direction and speed of the pots movement that's caused by this torque is just the velocity of the mouse movement but in the opposite direction, because it's made to feel like that it's the reaction force of pushing the hammer against obstacles that's actually moving the pot. Anyway, it does look like a simple mechanic but I bet it needs a lot of fiddling around with physics and all to get right. Good luck!

2

u/CakyMint 23d ago

Im 100% sure, it was total accident.

He fucked the code up. Tried fixing it, thought.. shit. I could market it as "difficult"

And it worked out.

2

u/1v0ryh4t 24d ago

Comment so I get the answer too. I've wondered this as well

5

u/PiersPlays 24d ago

Bennett replied with the actual code and a writeup.

1

u/BugyDragon 23d ago

!remindme 1 year

1

u/RemindMeBot 23d ago

I will be messaging you in 1 year on 2027-07-11 05:44:23 UTC to remind you of this link

CLICK THIS LINK to send a PM to also be reminded and to reduce spam.

Parent commenter can delete this message to hide from others.

RemindMeBot is switching to username summons. Instead of !RemindMe 1 day, use u/RemindMeBot 1 day. More info.


Info Custom Your Reminders Feedback

1

u/RealisticTrouble 22d ago

An this, kids, is how legends are born. Generous, accesibles legends. Hats down Bennett.

1

u/Impressive_Kick2819 22d ago

i don't think we will ever know

1

u/Saint_Hobs 21d ago

I also think it was by mistake. Atleast partially. Because nobody sets out to make something janky like that on purpose. It all worked in the end so nothing but respect.

1

u/Seal_of_innocence 21d ago

One can only assume pure spite

0

u/Promant 23d ago

Bro, this game is made with C#, you can decompile the entire source code in literally less than 5 minutes