How to handle FixedUpdate() and Input in Unity? - c#

As far as I know, in Unity, physics related things should be done in FixedUpdate() method.
And FixedUpdate() is called at fix timestamp and Update() is not. Let's asuume I have a gameobject with RigidBody2D component attached and get the user input in Update(). User can move the object with left and right arrow. See the fllowing figure,
user input happen at frame 2 and 3 and FixedUpdate() is not called. During frame 2
user pressed right arrow and say it move the object 1 unit and object is currently located at
(0, 0). The object position should be now at (1, 0). But nothing happen because FixedUpdate()
wasn't called. And again in frame 3 FixedUpdate() is not called and user press the left arrow.
So, it move the object 1 unit left and now the position is (0, 0). When we reached frame 4
FixedUpdate() is called and if I put the input data in a variable at previous frame, frame 3,
current object position will be (0, 0). So, in this scenario our gameobject never reached
the position (1, 0) but it's supposed to be. Probably it's not the only problem. There might
exist many scenario like called FixedUpdate() 2 times within one frame and called FixedUpdate()
only every 3 or 4 frames and so on. So, should I ignore such case because I think it's not too obivious to user. If I shouldn't, how can I handle it? Do I need to queue all the input data and do all the movement when the FixedUpdate() is called? What I tested is store movement data in a variable and if movement occurred then put true to a flag. If the flag is true the object will be moved inside FixedUpdate(). But the problem is it can handle only one input. If FixedUpdate() is not called the next movement will never be happen.
void FixedUpdate()
{
if(moved)
{
Vector2 movePos = rb2d.position + new Vector2(x, 0);
rb2d.MovePosition(movePos);
moved = false;
}
}
void Update()
{
if(!moved)
{
x = Input.GetAxisRaw("Horizontal");
if (x != 0) moved = true;
}
}

FixedUpdate isn't guaranteed to run on a fixed schedule because Unity is designed to deal with lag. Instead, whenever Update is called, Unity checks if any FixedUpdates should have happened between the previous Update and this one. If so, multiple FixedUpdates may run in sequence. If not, no FixedUpdates will run. So the FixedUpdate timing is actually linked to the Update timing, but not very predictably.
Generally, I recommend staying away from FixedUpdate and instead handling movement indirectly through the physics system in Update, as anything that needs to happen in FixedUpdate will get aggregated before it needs to be run. For example, if you change a rigidbody's velocity multiple times in Update, then FixedUpdate should still work as expected.

For single event inputs (GetKeyDown) it might be sometimes important to split the behavior and get the Input within Update but apply it in FixedUpdate (again this depends a lot on your use case still).
However, for continues input like GetKey or your GetAxisRaw it doesn't matter where you handle it (again depending on your exact use case of course). MovePosition is recommended to be called in FixedUpdate so in your case you could simply do
private void FixedUpdate()
{
rb2d.MovePosition(rb2d.position + Vector2.right * Input.GetAxisRaw("Horizontal");
}
What I mean by "it depends on your use case" is that you could achieve the same thing using rather the velocity in which case it wouldn't matter at all whether you do it within Update or FixedUpdate:
//private void Update ()
private void FixedUpdate()
{
// This is even better since you keep the Y velocity unchanged
// => Gravity will still work while it wouldn't using MovePosition!
var velocity = rb2d.velocity;
velocity.x = Input.GetAxisRaw("Horizontal");
rb2d.velocity = velocity;
}
for both Update or FixedUpdate the movement will be the same. If you use Update some frames might just have no effect as the velocity is applied in the next FixedUpdate call and in the meantime interpolated using the settings on your Rigidbody.

Related

Game does not take input from keyboard consistantly

I am new to Game dev and started with the 7-hour free course on youtube. I followed the course very carefully and stumbled against this problem. As i have learn so far this is how to get the 2D game object to jump when i press Space:
void PlayerJump()
{
if (Input.GetButtonDown("Jump") && isGrounded)
{
MyBody.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
}
}
But when i compiled and run my game, the game object did not jump right away when i pressed Space. It took about 5,6 times to make it jump once, and about the same amount each time later. I'm not sure if this is an Input problem. I swear I did not miss anything in the course until this.
I looked for mistake i might have in my code and it's clean. I expect to make it jump right when i press Space
First I would like to demonstrate that the code from the tutorial does indeed miss valid space bar presses.
// Update is called once per frame
void Update()
{
PlayerMoveKeyboard();
AnimatePlayer();
if (Input.GetButtonDown("Jump"))
{
Debug.Log("pressedUpdate" + isGrounded);
}
}
private void FixedUpdate()
{
PlayerJump();
}
void PlayerJump()
{
if (Input.GetButtonDown("Jump"))
{
Debug.Log("pressedFixed" + isGrounded);
}
if (Input.GetButtonDown("Jump") && isGrounded)
{
Debug.Log("jump");
isGrounded = false;
myBody.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
}
}
If you compile this and play around for a bit you can get the following output in the log:
As you can see the update loop has detected the button press 6 times and only on the last one the fixed update loop detected the input and executed the jump.
Something you need to know is that Input.GetButtonDown is only true for the frame that it is pressed down. By default Unity runs in 60 fps and therefore the input system is checked approximately every 0.017 seconds. When you press the space bar the input system learns about that on the next frame. For the next 0.017 seconds the button is considered to be pressed down.
Parallel to this FixedUpdate is called every 0.02 seconds (default time step), which is a little bit more than the duration of a frame.
Consider the following scenario:
Unity checks the input in the FixedUpdate and detects that space wasn't pressed yet
runs the frame related code and the input system detects that space is pressed down
0.017 seconds pass and it's time for the next frame where the input system decides that the button is no longer pressed down, but rather held down
since 0.02 seconds have passed now Unity quickly goes back to check on the FixedUpdate, queries the input system once more, which reports that the button wasn't pressed down
Depending on your timing pressing the button this can happen a number of times in a row, or not happen at all.
The lazy solution would be so simply decrease the physics time step to be smaller than the frame rate step. You can go to the Project Settings in the Time menu and set the Fixed Timestep to something like 0.01. Problem with this approach is that you make physics more expensive and don't actually address the issue, since it will crawl back if you run the game in 120 fps for example.
Another easy solution is to simply move the the PlayerJump function into the Update loop. As the guy in the tutorial explained it is technically not correct to do this, since you should keep all physics related stuff in FixedUpdate, but you won't notice the collision related issues in such a simple game.
Finlay there is the "proper" way to resolve your problem by implementing a lenience system with a bit more advanced code. Instead of the jump button only counting as pressed down for one frame you can make it so it is considered pressed down for an arbitrary long amount of time.
private bool jump;
[SerializeField] private float lenience = 0.2f;
// Update is called once per frame
void Update()
{
PlayerMoveKeyboard();
AnimatePlayer();
JumpInput();
}
private void FixedUpdate()
{
PlayerJump();
}
void JumpInput()
{
if (Input.GetButtonDown("Jump"))
{
CancelInvoke("Lenience");
jump = true;
Invoke("Lenience", lenience);
}
}
private void Lenience()
{
jump = false;
}
private void CancelLenience()
{
CancelInvoke("Lenience");
jump = false;
}
void PlayerJump()
{
if (jump && isGrounded)
{
CancelLenience();
isGrounded = false;
myBody.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
}
}

Unity3D C# - speed issues with `Input.GetMouseButton(0)` [SOLVED]

I've been struggling with the movement of my player since its movement was based on updating its world position with time frames. Using Update or FixedUpdate didn't change anything.
Since, I setted up another movement which involved a lot of changes in my code, this time based on speed (* Time.delaTime) and Update make my player moving faster when the mouse button is kept down, but FixedUpdate fix the problem and whatever if I just clic or keep the mouse down my player keep the same speed.
Code:
void FixedUpdate()
{
if(Input.GetMouseButton(0))
{
Vector3 target = GetMouseWorldPosition();
transform.position = Vector3.MoveTowards(transform.position, target , speed * Time.deltaTime);
Good luck and have fun :)
Issue 1
As you are already using Coroutine, you can use yield return new WaitForSeconds (1) instead of yield return null. you can adjust the number to set the wait duration between steps. Don't purposely slow down your CPU to achieve anything about timing. And don't mess up with Fixed Update time it's for physics simulation.
Issue 2
change it to yield return new WaitForSeconds (Input.GetMouseButton(0)?.5f:1);, so it waits shorter time if mouse button is held.
Issue 3
if(pathfinding==null || grid=null){
...(do random movement)
}else{
...(do pathfinding movement)
}
EDIT 1
if you don't want teleporting, you need to write transition yourself.
Vector3 oldPos = transform.position;
Vector3 newPos = nodeWaypoint;
float time = 0;
while(time<1){
yield return null;
time +=Time.deltaTime;
transform.position = Vector3.Lerp(oldPos,newPos,time);
}
Or just use DoTween
but TBH, your coding is quite problematic. it will keep doing pathfinding and calling UpdatePosition when the player holding click.

RigidBody2D AddForce Not Adding Acceleration

I'm trying to add acceleration to an object with a RigidBody2D component using AddForce, but all it seems to do is add a fixed velocity instead.
Here are the RigidBody2D settings:
For testing I've modified the Update function to allow me to add forces and report the object's velocity periodically:
void Update()
{
time += Time.deltaTime;
if (time > 0.5)
{
Debug.Log("Velocity magnitude = " + rigidbody2D.velocity.magnitude);
time = 0;
}
if (Input.GetKeyDown(KeyCode.Q))
{
rigidbody2D.AddForce(Vector2.right);
}
}
What I see is that every time I press Q, the velocity magnitude increases by a fixed 0.02.
I.e. first it doesn't move at all and the velocity is 0, then after pressing Q the velocity magnitude changes to 0.02 and stays at 0.02 while the object trudges slowly to the right. Pressing Q again increases it to 0.04, then pressing once more increases it to 0.06 and so on.
These are results from two days ago. I did the experiment again today and got different velocity increments but the same behavior otherwise.
The velocity changes only when I add a force.
If I added a force of magnitude 1 to an object at rest with a mass of 1 then its velocity would increase by 1 in magnitude every single unit of time (which I think here is seconds).
Instead it's staying fixed.
Using ForceMode2D.Impulse increases the velocity increments from 0.02 to 1, but the behavior stays the same - no acceleration, only one-time increments in speed.
What am I doing wrong?
I've tried looking for similar posts and for the problem in general but the only things I found were either irrelevant or asking the opposite (how to stop acceleration from occurring).
Any help would be appreciated.
The Unity version is 2019.1.1f1.
TL;DR
What you are doing wrong is that you are expecting the wrong physics behavior from the apply force function. That function does what it says, it applies force. It changes the object velocity instantaneously adding to it. The force mode for 2d bodies only tells the engine to consider the object's mass or not.
The problem is the naming Unity uses, AddForce doesn't "add" the force, it just applies the force for a single frame. In the following frames, all forces are zero again. If you want to keep the force, just create your own vector in your script, modify it, and apply it to the object every frame using AddForce.
how are you?
I have created a sample project to reproduce your issue but I couldn't reproduce the velocity staying fixed as you said. I'm not sure how you set up your scene so I made some assumptions. You probably have disabled either gravity on your object or friction, since you are reporting that the velocity is increased.
Acceleration, on the other hand, is a constant force being applied, think of it. If your car is still, and you push it for a short amount of time, the car will move then stop. If you keep pushing on the other hand, adding more and more force, the car will move faster and faster. Now think about your environment, you have a frictionless object. Every time you press Q you add a small force to it. It moves to the right a bit faster but with a constant speed (there is no force to change its velocity).
Here is what you'd want to do. Instead of adding the force only in the frame you pressed Q, add while you are pressing Q. This is what you want to do:
private void FixedUpdate()
{
time += Time.deltaTime;
if (time > 0.5)
{
Debug.Log("Velocity magnitude = " + rigidbody2D.velocity.magnitude);
time = 0;
}
if (Input.GetKey(KeyCode.Q))
{
rigidbody2D.AddForce(speedMultiplier * Time.fixedTime * Vector2.right, ForceMode2D.Force);
}
}
Another problem is the naming Unity uses, AddForce doesn't "add" the force, it just applies the force for a single frame. In the following frames all forces are zero again. If you want to keep the force, just create your own vector in your script, modify it, and apply it to the object every frame using AddForce.
Also, see that I changed Update to FixedUpdate? In Unity, you are recommended to apply all physics changes in that method instead. Update runs as much as it can (once every frame if possible), fixed update instead runs only after the physics thread is done. This means you are not spending CPU time telling the object to move every frame, and instead you are waiting for the physics to be synchronized.
I don't think that's going to add enough force to the object to get it into motion. Normally you need to multiply your direction by a "force" multiple. Try something like rigidbody2D.AddForce(Vector2.right * 1000);. That is, intentianally, probably way too much force but at least you'll know it's moving.

Unity - Run a loop like sonic without falling down

I am making this 2D platform game for a freelance, and I have to do a sonic mechanic, I have to make the character run through a full loop without falling down, I realised that to do this first I would have to rotate the character conform he runs through the loop, but the second part is where I am stuck.
So, basically, how can I make the character run the loop without falling down.
private void OnCollisionEnter2D(Collision2D coll)
{
Vector3 collRotation = coll.transform.rotation.eulerAngles;
if (coll.gameObject.tag == "Ground")
{
//in this part i rotate the player as he runs through the loop
transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, collRotation.z);
//this is the part that i am stuck, trying to figure out how to make the character stay in the loop without falling, i tried collision detection,
//i tried raycasting but nothing seems to work
if (IsGrounded)
{
GetComponent<Rigidbody2D>().AddForce(Vector2.down, ForceMode2D.Impulse);
}
}
}
The trick that is most commonly used for remakes and similar is getting the speed of the player character on entry.
// Feel free to adjust this to whatever works for your project.
const float minimumAttachSpeed = 2f;
// This should be your characters current movement speed
float currentSpeed;
// You need a Rigidbody in this example, or you can just disable
// any custom gravity solution you may have created
Rigidbody2D rb;
If the speed of the character is excelling the minimum attachment speed, you can then allow them to follow a predefined path at that speed.
bool LoopAttachmentCheck()
{
return minimumAttachSpeed <= currentSpeed;
}
And now you can check to see if you're moving fast enough! I assume for this example you are using a Rigidbody2D...
(this check should only be run when you're either entering or currently on the loop)
void Update()
{
if( LoopAttachmentCheck() )
{
// We enable this to prevent the character from falling
rb.isKinematic = true;
// Here you write the code that allows the character to move
// in a circle, e.g. via a bezier curve, at the currentSpeed.
}
else
{
rb.isKinematic = false;
}
}
It's up to you to implement the actual rotational behaviour. If I were you, a good way to do this would (assuming it is a perfect circle) use RotateAround from the center of the circle.
If you have more complex shapes, you can use waypoints for movement, and just iterate through them at your speed.
The moment you fail to keep up speed, your character will fall off (so if a player decided to stop running for example) and the Rigibody2D will become kinematic.
Hope this helps!

how do i do something every second after Collision

I am currently working on a football game(American) and im working on a tackle mechanic. Now just having 2 things bumping into each-other did not work because the character would continue to move after being forced sideways on the ground. To make the character stop after making contact with the other rigid body, I thought the best way would be to use Time.Timescale = 0; . However the problem with this is the fact that the 2 rigid body's then just go through each-other.to solve this I think the best way would be to set time scale to 0 after 1 second of collision. How can I do this?
Feedback is always appreciated ;)
to invoke a method delayed within unity presuming it is a monobehaviour.
the Invoke method apears to be what you are after
http://docs.unity3d.com/ScriptReference/MonoBehaviour.Invoke.html
however please be aware that Time.Timescale effects everything and is not local and would more than likely not get the effect you are after. setting the Velocity of the gameObject to zero should get the desired outcome.
var rb = GetComponent<Rigidbody>();
rb.velocity = Vector3.zero;
Time.Timescale would effect your whole game.
Actually Time.Timsescale is
The scale at which the time is passing. This can be used for slow motion effects.
When timeScale is 1.0 the time is passing as fast as realtime. When timeScale is 0.5 the time is passing 2x slower than realtime.
When timeScale is set to zero the game is basically paused if all your functions are frame rate independent.
From Unity Documentation
Well, what you can do is,
In script attached to Rigidbody's GameObject you can implement OnCollisionEnter.
Rigidbody _rb;
void Start()
{
_rb = GetComponent<Rigidbody>();
}
void OnCollisionEnter(Collision col){
_rb.velocity = Vector3.zero;
}
It will stop your player even after collision with ground. :)
So you can further modify collision condition like if body strikes to some specific object, you can detect it by tag or some other properties.
void OnCollisionEnter(Collision col){
if (col.gameObject.tag == "TAG_OF_SPECIFIC_OBJECT")
_rb.velocity = Vector3.zero;
}

Categories