I want to move a rigidBody2D for a fixed amount of time with a fixed amount of velocity, when the player presses a button. I am calling the method in FixedUpdate() and the results are not consistent. Sometimes the rigidBody will travel more and sometimes less (Here is a relevant part of my code how i went about do this)
void Update()
{
GetPlayerInput();
}
private void FixedUpdate()
{
GroundCheck();
ApplayNormalPlayerMovement();
ApplyMove(); // This is the method of interest - I tried calling this in Update() with Time.DeltaTime - still inconsistent results.
MoveCooldownCounter(); // I tried calling this in Update() - inconsistent results also
}
IEnumerator MoveRB()
{
savedVelocityX = rb.velocity.x;
rb.gravityScale = 0;
savedXinput = xInput;
while (moveTimer >= 0)
{
if (xInput != 0)
xInput = 0;
cc.sharedMaterial = noFriction;
if (facingRight)
{
rb.velocity = new Vector2(moveSpeed, .0f); // I tried multiplying the moveSpeed by Time.DeltaTime and FixedDeltaTime - still inconsistent results.
} else
{
rb.velocity = new Vector2(-moveSpeed, .0f);
}
moveTimer -= Time.fixedDeltaTime;
yield return null;
}
moveTimer= moveDuration;
rb.gravityScale = gravity;
rb.velocity = new Vector2 (savedVelocityX, .0f);
xInput = savedXinput;
moveCooldownInternal -= Time.deltaTime; // I tried changing this to a specific float - still inconsistent results in physics..
}
private void MoveCooldownCounter()
{
if (moveCooldownInternal != moveCooldown)
{
moveCooldownInternal -= Time.deltaTime;
if (moveCooldownInternal <= 0)
{
moveCooldownInternal = moveCooldown;
}
if (moveCooldownInternal == moveCooldown && isGrounded)
canMove = true;
}
}
private void ApplyMove()
{
if (b_Fire3 && canMove)
{
StartCoroutine("MoveRB");
canMove= false;
}
}
Side note: right now i experience player input loss on occasions because i call this ApplyMove() in FixedUpdate() (will have to find a workaround for that as well - naturally if I can call the ApplyMove in Update() and get consistent results than this issue would be fixed).
Pleas excise my newbiness, i am trying to learn :)
Thank you in advance!
Add comment
Unity is a game engine. All game engines are based on the game loop - however Unity somewhat hides if from you, behind the 3 Update functions.
Not all parts are equally important. Drawing is generally skipped when the GPU is not ready or the FPS are capped. And Drawing and physics are generally calculated independantly of one another (unless you really messed up the development).
With Physics and Animations, you generally want to stuff to be consistent to the Game Tick Counter, not real time. All movement of game pieces and all animation should count in game ticks. Real Time is a nice figure and all, but ticks can have wildly different processing times. Especially since the CPU side drawing work also has to run in the same thread.
Just figure out your maximum ticks/second. And if you want to run something to run for "roughly" 10 ingame seconds, you multiply that value. It does not mater if the game on this machine needs 2 seconds for 200 ticks or 2 minutes for 200 ticks - with the number of ticks, the results will be consistent (unless the math itself is inprecise, like floating point math).
If it's not multiplayer game, it does not have to be hackproof and thus easiest fix is to capture the first frame of the button pressed and apply the force only on that frame, because of that we have to capture input on the Update function and also there immadietly apply the force even though it's suggested to do physics stuff in fixed update, but if you use delta time in update function for calculation it should be fine!
Now I think you know how to do that but I'll write it anyway we can either use apply force or set the velocity,
void Update()
{
if(Input.GetKeyDown("w"))
rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y + 5f*Time.deltaTime);
}
Note: I didn't test it but it should be allright since GetKeyDown returns true only on the first frame when the button was pressed,
If it was just GetKey and in case of jump we would check the grounding of player, we could be during jump and still be grounded on the second frame even if we jumped because of not really precise ground checking.
PS. Use fixedDeltaTime in fixedUpdate method!
Related
I have a player on XY position. After player's swipe position must be increased or decreased on 1 for x-area. Tryed to do this by addForce and velocity, but these ways are increasing x-area pos to infinity.
So - How i can stop increasing after then position will be (1,0) after (0,0)? or mb anything else method can help me?
void FixedUpdate()
{
if (Input.touchCount > 0)
{
theTouch = Input.GetTouch(0);
if (theTouch.phase == TouchPhase.Began)
{
theTouchStartPos = theTouch.position;
}
if (theTouch.phase == TouchPhase.Ended)
{
theTouchEndPos = theTouch.position;
if (theTouchEndPos.x <= theTouchStartPos.x)
{
Debug.Log("swipe left");
Move(Vector2.left);
}
if (theTouchEndPos.x >= theTouchStartPos.x)
{
Debug.Log("swipe right");
Move(Vector2.right);
}
}
}
}
void Move(Vector2 direction)
{
//playerRb.velocity = new Vector2(direction.x, direction.y) * speed;
float step = speed * Time.deltaTime;
playerPos = Vector2.Lerp(playerPos, playerPos + direction, step);
}
Do not take single frame inputs such as TouchPhase.Began or GetKeyDown in FixedUpdate(). It may work on your machine but it will not register the input every time on a faster machine.
"FixedUpdate is often called more frequently than Update. It can be called multiple times per frame, if the frame rate is low and it may not be called between frames at all if the frame rate is high." - https://docs.unity3d.com/Manual/ExecutionOrder.html
What this means is that FixedUpdate is not guaranteed to run on the frame when you pressed down, so inputs that only last for one frame such as TouchPhase.Began or GetKeyDown may not register if the framerate is high enough.
It's surprising to me how noone else noticed this.
So it sounds like what you are trying to achieve is: Each swipe moves your player one "step" to left or right on the X-axis but smoothly.
In your current approach you call the Lerp only in one single frame when the touch ends so the object will barely move at all.
I would rather set the target x-axis position and let the playeroventowards that with the given speed in every physics update call.
As SpicyCatGames pointed out correctly, you should always separate the handling of User Input (Update) from the physics (FixedUpdate).
private float targetX;
private void Awake ()
{
targetX = playerRb.position.x;
}
void FixedUpdate()
{
// Move the player towards the target position but only on X
// -> keep the Y velocity
var position = playerRb.position;
position.x = Mathf.MoveTowards(position.x, targetX, speed * Time.deltaTime);
playerRb.MovePosition(position);
}
private void Update ()
{
if (Input.touchCount > 0)
{
theTouch = Input.GetTouch(0);
if (theTouch.phase == TouchPhase.Began)
{
theTouchStartPos = theTouch.position;
}
else if (theTouch.phase == TouchPhase.Ended)
{
theTouchEndPos = theTouch.position;
if (theTouchEndPos.x < theTouchStartPos.x)
{
Debug.Log("swipe left");
Move(-1);
}
else if (theTouchEndPos.x > theTouchStartPos.x)
{
Debug.Log("swipe right");
Move(1);
}
}
}
}
void Move(float direction)
{
targetX += direction;
}
This seems to get discussed a lot.
FixedUpdate is for physics "calculations".
If you are animating something - that is to, say, you want to draw something each time the scene is drawn (perhaps changing the position) - you very much use Update, not FixedUpdate.
It's both pointless (and inaccurate) to change the position of on object on the FixedUpdate calls.
Say this represents time and the F are fixed update events and the D (draw) are update events
F F F F F F F F F F F F F F F F F
D D D D D D
You (of course, obviously) only need to set the position of the object when you are about to draw it.
And indeed, when you're about to draw it: you (obviously) want the position it should be in at that actual time.
(The script given here shows "F" and "D" running .. https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html )
As it explains in the documentation, FixedUpdate is for physics >>calculations<<. So, if you are extending the physics engine, simulating something, applying the precise rules of a bullet game, or whatever. (I've rarely had to use it.) Note that interestingly, if for some reason you were calculating a planet or something and doing that every FixedUpdate, you'd still have to actually set the position of the planet (or whatever it is) at the exact moment when you're about to draw it! (ie in Update.)
It's another good example of the famously touchy Unity doco. In dozens of places they show something being moved (ie, using Update, when the draw happens). On that FixedUpdate page it very confusingly just mentions physics "calculations" which is very vague. If they had added a couple more words ("for behind the scenes physics calculations where you are not actually getting the position to draw something that's about to be drawn") it would all be clearer!
I'm new to Unity and C# in general, so excuse my terminology(or lack thereof)...
I have succeeded -with the help of a friend, to credit where its due- in making a platform go up a certain amount after one frame of collision with the player! The only problem is that, now, I can't seem to get it moving down again... Said friend challenged me to make it go up, stay airborne for a certain amount of time, then go back down.
Here is a snippet of my working code that makes it go up.
bool HaveCollided = false; //Collision checker boolean
void Update()
{
Vector3 up = lifter * Time.deltaTime;
if (HaveCollided != true || transform.position.y >= 5)
{
return;
}
}
void OnCollisionEnter(Collision collision) //Makes HaveCollided true at collision event
{
if (collision.gameObject.GetComponent<Playertag>() != null) //don't use GetComponent in Update()
{
HaveCollided = true;
}
So if my logic is right, I'd need to nest another if statement inside the one where the condition is: HaveCollided != true || transform.position.y >= 5 which should be:
if (newTime == Time.deltaTime * CertainAmount && transform.position.y >= 1) //make platform go down until y position is equal to 1
{
//make platform go down
Debug.Log("reached");
transform.position = transform.position - up;
}
But it's not working. It doesn't even seem to reach the part that would make the platform descend. I literally do not know where to go from here...
Based on your comment I made a few revisions to make the platform movement a bit smoother. I also made it so the same function can be used for both the upward and downward motion.
using UnityEngine;
using System.Collections;
[SerializeField] private float timeToMovePlatform = 0.0f; // time it takes the platform to get from 1 point to another
[SerializeField] private float timeToRestAtPeak = 0.0f; // time that the platform rests at the top of its movement
[SerializeField] private float platformYOffset = 0.0f; // offset the platform moves from its origin point to float upward
private Coroutine movingPlatform = null; // determines if the platform is currently moving or not
private void OnCollisionEnter(Collision col)
{
// add a tag to your player object as checking a tag at runtime vs. a GetComponent is faster
if(col.gameObject.tag == "Player")
{
// only trigger when we are not currently moving
if(movingPlatform == null)
{
// start our coroutine so the platform can move
movingPlatform = StartCoroutine(MovePlatform(true));
}
}
}
/// <summary>
/// Moves this object up or down depending on the parameter passed in
/// </summary>
/// <param name="moveUpward">Determines if this object is currently moving upward or not</param>
private IEnumerator MovePlatform(bool moveUpward)
{
// store our start position
Vector3 startPos = transform.position;
// build our goal position based on which direction we are moving
Vector3 goalPos = new Vector3(startPos.x, startPos + (moveUpward ? platformYOffset : -platformYOffset), startPos.z);
// set our current time
float currentTime = 0.0f;
while(currentTime <= timeToMovePlatform)
{
// lerp the position over the current time compared to our total duration
transform.position = Vector3.Lerp(startPos, goalPos, currentTime / timeToMovePlatform);
// update our timer
currentTime += Time.deltaTime;
// need to yield out of our coroutine so it does not get stuck here
yield return null;
}
// just in case there are any floating point issues, set our position directly
transform.position = goalPosition;
// when we are moving upward, make sure to stop at the peak for the set duration
if(moveUpwards)
{
yield return WaitForSeconds(timeToRestAtPeak);
// once we are done waiting, we need to move back downward
StartCoroutine(MovePlatform(false));
}
else
{
// we finished moving downward, so set our coroutine reference to false
// so the player can set the platform to move again
movingPlatform = null;
}
}
Fair warning, I did not test this code it is more a direction I would take for your situation. Before doing anything, add a tag to your player. If you are unsure what tags are, check out the docs. Be sure to give the player the tag Player so that the code will work as expected.
You will notice the first three variables are Serialized but are private. By doing this, it allows for Unity to show them in the inspector but not allow other scripts to touch them.
To learn more about Coroutines, check out the docs and for Lerps, I enjoy this blog post. Let me know if this works for you and/or you have any questions.
Your best bet is to add colliders and use isTrigger. Seed a time and if it's not triggered within that time period raise an event.
So, my jumping is inconsistent, basically, every time i press space the character jumps different heihgts, even when standing still (not moving left, and right), why is that?
here is my code:
public bool onGround = false;
public float JumpHeight;
public Rigidbody2D gravidade;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (onGround == true)
{
if (Input.GetKey(KeyCode.Space))
{
gravidade.AddForce(new Vector2(0, JumpHeight * Time.deltaTime), ForceMode2D.Impulse);
}
}
}
void OnCollisionEnter2D(Collision2D outro)
{
if (outro.gameObject.CompareTag("ground"))
{
onGround = true;
}
}
void OnCollisionExit2D(Collision2D outro)
{
if (outro.gameObject.CompareTag("ground"))
{
onGround = false;
}
}
You shouldn't multiply by delta time when applying a one-time force.
Time.deltaTime is the time completion time in seconds since the last frame. Check the documetation
This time is not constant. MonoBehaviour.FixedUpdate uses fixedDeltaTime instead of deltaTime, this fixedDeltaTime and the FixedUpdate is usually used for physics and it is quite an advanced topic.
Use a constant value instead of the Time.deltaTime to have always the same jump height.
It is different becuase it's calculated off of deltaTime, which is incosistent (time between two rendered frames if i'm not wrong), and it yields a slightly different value every time, which combined with multiplication has a bigger deviation from an average.
Hope i helped to clarify this, cheers!
I have this piece of code right here that should make the block object move between the startPos and endPos objects, but something is wrong about it and I don't know what.
void FixedUpdate()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
if(block.transform.position == endPos.transform.position)
{
check = true;
}
if (check == false)
{
block.transform.position = Vector3.Lerp(block.transform.position, endPos.transform.position, .03f);
}
if (check == true)
{
block.transform.position = Vector3.Lerp(block.transform.position, startPos.transform.position, .03f);
}
}
At some point the block will reach endPos, and then on its way back to startPos it will stop, because the functions will be executed simultaneously. But how is this possible because my if's right there should not allow this to happen?
In general you should always be using
Update → called every frame
instead of
FixedUpdate → called in certain realtime intervals
except you are dealing with Physics somehow (which doesn't seem to be the case here). Also see the Update and FixedUpdate Tutorial
The issue with Vector3.Lerp is that it doesn't behave as you expect.
I guess you like that it starts fast and then becomes "smooth" ... but actually this might be your problem.
It never reaches the target position really. It just gets closer and closer and slower and slower ...
... until at some moment the == with a precision of 0.00001f eventually becomes true.
So it might seem that it stopped but actually it might still be moving just really really slow.
For the following two alternatives you have to decide a bit what you want to control:
Option: The speed
If you want to have a linear velocity for the object you should rather use
// adjust via the Inspector
[SerializeField] private float moveSpeedInUnityUnitPerSecond = 1f;
// you should use Update here in general
void Update()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
// always use else in cases where only on condition can be
// true at the same time anyway
else if(block.transform.position == endPos.transform.position)
{
check = true;
}
block.transform.position = Vector3.MoveTowards(block.transform.position, check ? startPos.transform.position : endPos.transform.position, Time.deltaTime * moveSpeed);
}
Option: The duration
If you rather want a smooth movement but control the duration it takes to reach the target you should use a Lerp but with a factor depending on the time like
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
private float passedTime;
// you should use Update here in general
void Update()
{
// prevent overshooting
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
if(passedTime >= moveDurationInSeconds)
{
check = !check;
passedTime = 0;
}
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
var fromPosition = check ? endPos.transform.position : startPos.transform.position;
var toPosition = check ? startPos.transform.position : endPos.transform.position;
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
}
For this you could also use a Coroutine which usually is a bit easier to interpret and maintain:
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
// yes you see correctly one can directly use the Start
// as a Coroutine
private IEnumerator Start()
{
var fromPosition = startPos.transform.position;
var toPosition = endPos.transform.position;
// looks strange but as long as you yield somewhere inside
// the loop it simply means repeat the sequence forever
// just like the Update method
while(true)
{
var passedTime = 0f;
while(passedTime < moveDurationInSeconds)
{
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
// reads like: "pause" here, render this frame and continue
// from here in the next frame
yield return null;
}
// once reached flip the positions
var temp = fromPosition;
fromPosition = toPosition;
toPosition = temp;
}
}
in both cases you could still add more flexibility and instead of simply using the moveDurationInSeconds use
var fixedDuration = moveDurationInSeconds * Vector3.Distance(fromPosition, toPosition);
this way the movement takes shorter if the positions are closer together and longer if they are further apart. This comes pretty close to the Lerp you used before regarding to the smoothness of motion but you can control very good how long the movement will take.
The third argument of Vector3.Lerp is a percentage of the distance between the first two arguments. When you pass in 0.03f, you're getting 3% closer to your end position but never actually getting exactly there (You can prove this by logging the block's position and the target's position and you'll see they're never perfectly equal).
Instead of checking if your block's position with the == operator (which works to an accuracy of 0.00001f) to the target position, you could simply check if it's close enough with Vector3.Distance:
if(Vector3.Distance(block.transform.position, endPos.transform.position) < 0.1f) {
You can make your "close enough threshold" (0.1f in the example) a named variable for easy adjustment until it feels right.
For starters, Update() loops every frame. FixedUpdate() depends on the number of frames per second set in the time setting. So, put your code into Void Update() instead.
If you refer to Vector3.MoveTowards documentation, you may find a much better approach to your problem.
The script below should do the trick. The target variable should be set to the position of your end point. Speed is how fast your object should move.
Inside Update(), the step variable is used to determine how far the object should move based off its speed and the time elapsed since it last moved. Finally, the last line changes the position of the object and records the new position.
public Transform target;
public float speed;
void Update() {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
}
I am trying to simulate gravity in my first xna 2d game. I have the following
//Used for Jumping
double elapsedAirTime = 0.0;
double maxAirTime = 8.35;
//End Jumping
So I am trying to move the sprite up by a certain amount while the elapsedAirTime < maxAirTime
However, there is some issue where my code only seems to move the sprite up once and not multiple times during this segment of time. here is the code in my "player.cs" class, or the update method of the class.
if (newState.IsKeyDown(Keys.Space))
{
if(oldState.IsKeyUp(Keys.Space))
{
//if we are standing or jumping then change the velocity
if (playerState == PlayerStates.Standing)
{
playerState = PlayerStates.Jumping;
this.position.Y -= (float)(30.0 + ((1.2)*elapsedAirTime)*elapsedAirTime);
}
}
}
//if we are jumping give it some time
if (playerState == PlayerStates.Jumping)
{
if ((elapsedAirTime < maxAirTime) && position.Y < 3)
{
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
elapsedAirTime += gameTime.ElapsedGameTime.TotalSeconds;
}
//otherwise time to fall
else
{
playerState = PlayerStates.Falling;
}
}
//add gravity to falling objects
if (playerState == PlayerStates.Falling || playerState == PlayerStates.Standing)
{
//if we are above the ground
if (this.position.Y < windowBot - 110)
{
//chnage state to falling
playerState = PlayerStates.Falling;
this.position.Y += 3.0f + ((float)(gameTime.ElapsedGameTime.TotalSeconds));
}
else
{
playerState = PlayerStates.Standing;
elapsedAirTime = 0.0f;
}
}
Any help is much appreciated, please and thank you!
To give your sprite the feel of gravity, you should add velocity and acceleration to your Sprite class. Then, create an Update method for the Sprite, and have acceleration be added to your velocity every update, and velocity added to position every update. Position should not be based on the amount of elapsed air time. You can set the acceleration to a constant gravitational value, and then add to the velocity of the Sprite whenever you jump. This will create a flowing parabolic jump that looks nice. If you want to include timing, you can pass the GameTime into the Sprite's Update method, and use it as a modifier on the velocity. Here is an example Update method:
void Update(GameTime gt)
{
int updateTime = gt.ElapsedGameTime.TotalMilliseconds - oldgt.ElapsedGameTime.TotalMilliseconds;
float timeScalar = updateTime / AVG_FRAME_TIME;
this.velocity += this.acceleration * timeScalar;
this.position += this.velocity;
oldgt = gt;
}
If you use timing, this method is a little complicated. You have to keep track of how much time the update took, then divide it by the average amount of time an update or frame should take to get the amount you should adjust your velocity by. Without timing, the method is very simple:
void Update()
{
this.velocity += this.acceleration;
this.position += this.velocity;
}
I would suggest using the simpler method until you understand exactly how timing works and why you need to implement it.
It looks like this line is at fault:
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
I think you will find that this updates the sprites position quicker than you imagine, the sprite will move 330 pixels up the screen in 10 updates (assuming Game.IsFixedTimeStep == true) that is 1 tenth of a second realtime
It is likely that this is just updating so quickly that you don't get a change to see it rise before the && position.Y < 3 condition kicks in and changes the playerState.
It looks like you are trying to say - jump at a rate of x pixels per second for upto 8.5 seconds so long as space is held.
What you need for that is to change the calculation to this.position.y -= (float) (30 * gameTime.ElapsedGameTime.TotalSeconds), this will give a very liner movement to the jump action but it will mean that the sprite jumps at exactly 30 pixels per second.
If Game.IsFixedTimeStep == true - which is the default - the update gets called 60 times per second so gameTime.ElapsedGameTime.TotalSeconds is going to be about 0.1 every update. If something happens to cause an update to skip (rendering issues for example) then update will get delayed and gameTime.ElapsedGameTime.TotalSeconds may be 0.3 (the 2 updates skipped) but the formular still works out the correct jump rate.