Add Force Smoothly

I am trying (and failing) to apply a force smoothing instead of just a big jump. However, it is not cooperating! The math just isn’t working out. Any help would be greatly appreciated (I have made it all the way through to weapon hitbox at this point, just going back and doing some tweaking) :

The theory:
Apply the force over a variable amount of time (lets say, 0.4 seconds)
Apply 10 force over 0.4 seconds
This sounds easy!

No, not easy …

The code:

 //---------------- FROM BASE STATE
        protected void MoveWithForces(float deltaTime)
        {
            MoveWithForces(Vector3.zero, deltaTime);
        }

        protected void MoveWithForces(Vector3 motion, float deltaTime)
        {
            stateMachine.CharacterController.Move((motion + stateMachine.ForceReceiver.Movement) * Time.deltaTime);
        }

//------------------- FROM ATTACK STATE
        public override void Tick(float deltaTime)
        {
            MoveWithForces(deltaTime);

            FaceTarget();
            
            float normalizedTime = GetNormalizedTimeThroughAnimation();

            if (attack.HasForce && normalizedTime >= attack.ForceTime)
            {
                TryApplyForce();
            }

            if (normalizedTime > 1)
            {
                if (stateMachine.TargetingController.CurrentTarget != null)
                {
                    stateMachine.SwitchState(new PlayerTargetingState(stateMachine));
                }
                else
                {
                    stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));    
                }
            }
            
            previousFrameTime = normalizedTime;
        }

        private float startTime;
        
        private void TryApplyForce()
        {
            if (hasFinishedApplyingForce) return;

            if (!hasAppliedForce)
                startTime = Time.time;
            
            //Apply the force over a set amount of time
            float overTime = attack.ForceSmoothing;
            float forceToApply = attack.Force;

            float forceSteps = (forceToApply / overTime);
            Debug.Log(forceSteps);
            if (Time.time - startTime > overTime)
            {
                Debug.Log("Time!");
                hasFinishedApplyingForce = true;
                return;
            }
            
            //When we call the move with forces, this force is multiplied by time.deltaTime
            stateMachine.ForceReceiver.AddForce(stateMachine.transform.forward * forceSteps);
            hasAppliedForce = true;
        }

The thought process (whining included):

When you go to apply the force for the first time, capture the start time.
If our Time.time - start time < total time, add some force
How much force? well, the (force/time) to add … right? RIGHT?! wrong… so very very wrong… in all ways wrong

the theory is, if I want to add 15 force over 2 seconds, I should be adding 7.5 force per second * time.deltaTime (this is done in the Move method on the base state) to account frame time instead of actual seconds

So, we run it through, saying add 10 force over the course of 0.4 seconds, and ZOOM the player takes off like a rocketship …

So we be stubborn and think math is solid, and change it to 0.5 force over 0.4 seconds, and it feels and looks betters … BUT, we run the maths, and find out that it ran 87 times, each time applying 0.125 force… mathy no add up, that means we added 10.875 total? and its just a teeeeny little step (not the same distance as when we didnt do all fancy math and just plopped 10 in there ) …

We learn that math no good, bad at math, beg for helps … (note: even though this feels good there is obviously some fundamental lack of understanding that is bothering me. I can keep it like this and work around it, but then I can’t say to my designer “yeah, itll add X force over Y seconds” because… it doesnt. I would just have to say “play with it till it feels good!” which is not great).

Part of the problem is that this is not at all how jumping or any ballistic type motion in the real world works (unless you’re Billy Campbell, and are wearing a Jet Pack).

Let’s take a look at ballistic force from the perspective of a football (my favorite sport). The QB arches his arm back and propels the football forward. While the football is in the hands of the QB, it is gathering kinetic energy. The faster and harder the QB throws, the more kinetic energy is applied to the ball.

Now once that ball is released, it has aquired all of the kinetic energy that it will get until the ball reaches the apex of the throw. Up until that point, it is actually losing kinetic energy due to the effect of gravity, and the friction of the air that the ball is thrown (and the rotation of the ball around it’s axis, etc).

Once the QB has let go of the ball, it’s not going to pick up applied force (besides Gravity after it reaches the apex).

Now the same is true with jumping. You generate the kinetic energy needed for the jump, usually a nice moving start. Once your feet leave the ground, only the afore mentioned Billy Campbell, or a Kryptonian will continue to gain kinetic energy. You, the jumper, will begin to lose kinetic energy from the moment your foot loses contact with the ground.

Now in our game, we’re ignoring many of the factors involved in a jump. Our original code pre-supposes that the forward momentum you had when you made the jump carries over through the whole jump without friction.

It looks like you’re specifically adding to the forward thrust, not dealing with the y element of the thrust at all in your adding of forces. Is this correct?

At issue is the loss of momentum from applied forces. This simulates the dastardly work of air and ground resistance against a body in motion. That’s represented in the ForceReceiver here:

        impact = Vector3.SmoothDamp(impact, Vector3.zero, ref dampingVelocity, drag);

        if (agent != null)
        {
            if (impact.sqrMagnitude < 0.2f * 0.2f)
            {
                impact = Vector3.zero;
                agent.enabled = true;
            }

Now if your drag is as Nathan wrote it (.3f/s) you’re going to need more force to break the drag. Of course, if you do that, you’re going to find yourself thrown across the terrain the first time you get hit.

All I can suggest is that you’ve established the “I barely moved” floor and the “I’m Superman!” ceiling, I would start increasing the force over time until you find a force that looks right. You might also have to experiment with the y force to increase the hang time.

Hello, I greatly appreciate your detailed answer in how/why the forces work out for jump force and hang.

That makes sense as far as jumping goes, but this is the forward movement of a thrust during a weapon attack where we are gathering our momentum and then “thrusting” forward … as it stands, the character moves 3 “meters” in 7 frames (using the settings from the video)… the 3 meters is great, but I would love to stretch that out so it doesnt happen in 7 frames . It feels choppy and not smooth, character is standing at A, does the animation, mid-animation character appears to ‘jump’ 3 meters forward (there is a small amount of drag that smooths it out at the end).

We apply, essentially, an impulse force of 30 to achieve that (3 meters in 7 frames). What the code I have applies it more smoothly over time, but is “wrong” in it’s intention.

I guess the part I am missing is determining the appropriate fraction of the force to achieve a total force over time .

Since this is being triggered from a Tick, which is ultimately an Update, what could/would you use to calculate “10 force over 0.4 seconds, accounting for the fact this is running every frame” … if our framerate was locked to 30 frames per second, we would be looking at 33 miliseconds (ish) per frame, with 0.4 equaling approx 400 milliseconds, which means we want 10 force over 400 milliseconds. That gives us 12.12 (ish) frames, so we would need to apply 1.2 force per frame.

That math works here, on paper, with numbers I can control ( (1000 ms * 0.4 desired length) / 33 ms per frame / 10 desired total force) . But we aren’t locked to 30 FPS , framerates vary (and can even vary between frames), and that seems like an awful amount of math for smoothing the movement even if we COULD both determine a close-enough accuracy on FPS and that FPS was unrealistically stable across all players.

Currently, I swapped to using:

        private void TryApplyForce()
        {
            if (hasFinishedApplyingForce) return;

            if (!hasAppliedForce)
                startTime = Time.time;

            float overTime = attack.ForceSmoothing;
            float test = (1000 * attack.ForceSmoothing) * (Time.deltaTime) / attack.Force;
            totalForce += test;
            if (Time.time - startTime > overTime)
            {
                Debug.Log("Time! " + (Time.time-startTime) + " total force " + totalForce);
                hasFinishedApplyingForce = true;
                return;
            }
            
            //When we call the move with forces, this force is multiplied by time.deltaTime
            stateMachine.ForceReceiver.AddForce(stateMachine.transform.forward * test);
            hasAppliedForce = true;
        }

It is still pretty non-deterministic (± 1 testing with framerates between 30 and 300) and inaccurate (only applies a total of 8ish force accounting for drag when set to apply 30)

As long as we’re tied to Update(), we’re held hostage by the variable frame rate.

Now for a dirty little secret (it’s not a secret):

In Unity, all physics is processed (collisions, the actual CharacterController movement, Rigidbody moves) takes place in a different Update, called FixedUpdate, which Unity makes every effort imaginable to ensure happens at the same rate no matter what the actual FPS is.

So in my own version of the project (and this will likely find it’s way into an RPG Core Combat rewrite) I add another method to the State class,

void FixedTick(float fixedTime);

And in my StateMachine, in FixedTick, I call

currentState?.FixedTick(Time.fixedDeltaTime);

Then I calculate movement (poll the InputReader) in Update and get a movement value, but wait until FixedUpdate to call Move().

So FixedUpdate() => FixedTick(float) may be the best place to put your applied force.

That was the ticket! Ended up with:

        private float elapsedTime = 0f;
        private float forcePerFixedUpdate;

        private void TryApplyForce(float fixedDeltaTime)
        {
            if (hasFinishedApplyingForce) return;

            if (!hasAppliedForce)
            {
                hasAppliedForce = true;
                forcePerFixedUpdate = attack.TotalForceToApply / (attack.ForceDuration / fixedDeltaTime);
            }

            if (elapsedTime < attack.ForceDuration)
            {
                stateMachine.ForceReceiver.AddForce(stateMachine.transform.forward * forcePerFixedUpdate);
                elapsedTime += fixedDeltaTime;
            }
            else
            {
                hasFinishedApplyingForce = true;
            }
        }

Which is being triggered from the FixedTick (from FixedUpdate) as you suggested … its beautiful!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms