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!
Related
Overview
I'm making an endless runner game. In this game, I have 5 lines, I want to player smoothly switch lines something like this Ref Link
In my case, I have everything the same but instead of a car, I have a player with PlayerController attached to it.
I'm changing the player line on Button click and also on IPointerDownHandler & IPointerUpHandler
Code
Full Code
[SerializeField] private List<Vector3> lines; // 5 lines in my case. Vector3 (0,0,0) and so on ...
private int flag;
Vector3 currLine;
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
//I used two approaches to moving but both are not working as indented
// 1 _ using DoTween
// 2 _ using Vector3.Lerp ()
private void MoveRight ()
{
// some input delay for Ipointers
if (inputDelay > 0) return;
if (currLine == lines [lines.Count - 1]) return; // can't move right anymore
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f); // rotate player toward target
transform.DoMoveX (currLine.X, 0.3f) // 0.3f is coming from inspector
.SetEase (Ease.Linear) // i almost tried all Ease
.OnComplete ( ()=> DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f));
// using Lerp
LookAt (new Vector3 (currLine.x,Y,Z));
transform.position = Vector3.Lerp(transform.position, new Vector3(currLine.x, ..,..), lineChangeCurve
.Evaluate(Time.deltaTime * lineChangeSpeed));
}
private void MoveLeft ()
{
// same code as MoveRight
}
Problem
The code I wrote is prettier much working. the player is changing lines and also rotating towards the line but I'm unable to figure out what should i need to do to make this effect look like a reference.
Can you tell me how can I achieve the same smoother effect as the reference for my player?
Here is the link that I made so far
3D Assets link
Player lines distance :
new Vector3 (-8, 0,0)
new Vector3 (-4, 0,0)
new Vector3 (0, 0,0)
new Vector3 (4, 0,0)
new Vector3 (8, 0,0)
Thanks in Advance
You seem to be mixing two different animation techniques.
You do
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
...
which starts a tween animation
and then you also do Evaluate on the lerp which seems to be a bit redundant.
Your issue with the Lerp and Evaluate is
a) that you pass in
Time.deltaTime * lineChangeSpeed
which basically means
lineChangeSpeed / frame rate (e.g. 60)
=> This is a way to small value and will most probably basically mean you don't move at all (depending on your used curve)
You want to call this every frame and pass in a value increasing from 0 to 1 here (e.g. in a Coroutine)
b) you call it only exactly once. This will not result in any movement at all .. or at least only in a single step in the first frame => which probably causes a little "hiccup"
=> get rid of the lerp line all together. At best it interferes with the tween animation and is probably the cause of it looking somewhat off
To the question about keeping the button pressed.
You have nothing to prevent this in
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
First of all the flag seems quite redundant. And then you would
Either change to GetKeyDown so only the first frame where the button goes down is handled
Or add a flag that ignores all input while an animation is already running
Or even both (depending on your desired UX)
So e.g.
private bool alreadyAnimating;
private void ChangeLines ()
{
if(alreadyAnimating) return;
if (Input.GetKey(KeyCode.LeftArrow))
// Or as said maybe even better
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
MoveLeft();
}
else if (Input.GetKey(KeyCode.RightArrow))
// same here
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
MoveRight();
}
}
and then block and reset the flag when done with moving
alreadyAnimating = true;
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f);
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
.OnComplete (()=>
{
DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f)
.OnComplete(() => { alreadyAnimating = false; })
});
I used Lerp() for these kind of conditions and it worked well all the time. As I saw your character correctly turns but it look like a snap turn. I couldn't spot an obvious error in your code. Play a little more with the rotation time and you will get your desired result. Sorry this must be a comment but dont have the reps.
The idea of the animation for switching lines is pretty simple. All you need is an animation of turning the main character to the side and back. Let's say the line change animation takes 1 second. Then you need that when moving to the left lane, the main character makes a turn to the left in 0.5 seconds and the next 0.5 seconds turns straight again. While the gameobject moves into the adjacent line, the main character will make a small smooth turn and the animation will look better. This is how it is done in the example you showed in the question. In your video, instead of this animation, the main character abruptly turns to a certain angle and returns to its original position abruptly back when it reaches the required line.
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.
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!
Trying to create simple endless moving platform with 3 cubes of scale 70 on z(Player will not move forward, will just move left/right). The RepositionPlatform script is attached to each platform/cube which is responsible for movement and checks the z position of each platform and if it is <= -100.0f, then position is changed to (0,0,200.0f).
Problem is sometimes there is a little gap between the platforms(cubes) or there is a little overlap which I don't want.
Platforms should be placed one after each other without any gap or overlap!!!
Can anyone help find the issue looking at the script or suggest any other better way ?
The script below is attached to 3 platform game objects!!!
public class RepositionPlatform : MonoBehaviour
{
private GameObject platformGO;
[SerializeField]
private float speed;
// Start is called before the first frame update
void Start()
{
platformGO = this.gameObject;
Debug.Log("In RepositionPlatform Start method - "+ platformGO.name);
}
// Update is called once per frame
void Update()
{
Debug.Log("In RepositionPlatform Update Method- " + platformGO.name);
platformGO.transform.Translate(Vector3.back * Time.deltaTime * speed);
Transform platformTransform = platformGO.transform;
if(platformTransform.position.z <= -100.0f)
{
platformTransform.position = new Vector3(0,0,200.0f);
}
}
}
Probably because speed is a floating point value. You should read up on them if you haven't already.
Long story short, you aren't accounting for how far below -100 the value might have gone, you're just resetting it.
If you translate it instead, you will preserve any extra distance beyond -100 that the transform might have gone.
Try this instead:
If (transform.position.z < -100){
transform.Translate(new Vector3(0,0,200));
}
Edit
Should be Z value, not X
I have trouble with PhysicMaterial2D for the ground and my character. When the character doesn't move, it works as I need (both materials have friction = 1.0). But due to high friction value the character can't move.
Of course, I could use more force (AddForce()) to move the character but then it will move too quickly on other grounds that uses other materials.
How is this problem resolved in other games (i.e. many platformers)? Should I set a friction of the character's material to zero when it moves (I haven't another ideas)? Or has it more pretty solution?
P.S. I tested with higher value of the friction also but it led to the character begun to rotate on the moving.
ADDED
I tried to set boxCollider.sharedMaterial.friction = 0.0f on the movement and return back to 1.0f after the user released button BUT the character continues to move as with zero friction although in the logger I can see it's 1.0f.
private void Update()
{
if (!paused)
{
if (Input.GetMouseButtonDown(0) && !IsOverPauseButton())
{
isPressed = true;
endMovePoint = Input.mousePosition;
isWalk = GetTouchDistance() > jumpTriggerRadius;
if (!(isWalk || isJump))
{
StartCoroutine(Jump());
}
}
else if (Input.GetMouseButtonUp(0) && isPressed)
{
boxCollider.sharedMaterial.friction = 1.0f;
isPressed = false;
isWalk = false;
canFlip = true;
}
if (isWalk)
{
if (!isJump)
{
boxCollider.sharedMaterial.friction = 0.0f;
}
Walk();
}
animator.SetBool(WalkingFlag, isWalk && !isJump);
}
if (isWalk && !isJump)
{
FixSpeed();
}
Debug.Log("Friction=" + boxCollider.sharedMaterial.friction);
}
Rather than using friction, you can dampen the player's movement.
Say your player has a velocity of 10, you want the player to stop moving of it's own accord because that's what's natural.
What you would want to do is subtract an amount from your player's velocity every frame e.g.
player.velocity.x *= 0.9f;
This would set the player's x velocity to 90% of it's original velocity. Do this every frame and soon your player will stop all by itself.
If the above method does not produce the effect you desire, you can always just subtract an arbitrary value from the player's velocity (of course you must account for if the player's velocity is negative):
player.vel.x -= 1;
Both of the methods above can factor in friction if need be, simply multiply the percentage or arbitrary value by the friction coefficient plus or minus a bit to suit your needs.