Shaking GameObject, moving on x-axis in endless runner style game
Hi all,
I've got an issue with a character in an Endless Runner style game, from a tutorial I found on YouTube. (Tutorial here: https://www.youtube.com/playlist?list=PLLH3mUGkfFCXQcNBz_FZDpqJfQlupTznd )
The player 'jitters' (see video) when it's not moved into the correct lane absolutely, it's immediately clear on iOS but I have noticed it occur on Android.
I've updated the Player Position into UI text objects to show the desired lane move changes, (x,y and z respectively).
The player (with a character controller attached) shakes when the player moves and doesn't reach the perfect x-axis values. -2.5, 0 or 2.5 for left, middle or right lanes.
Video (filmed on an iPhone 6, apologies for quality.) Note the X values changing rapidly when the player is shaking.
https://www.youtube.com/watch?time_continue=33&v=jrcnZTAF-CI
The jitter does stop and it often helped by moving along the Y-axis ( Jumping / Sliding in the context of the game).
Has anyone else experienced this issue? Found an elegant solution for it? I've taken a look at the following issues, but nothing I've tried has worked. https://forum.unity.com/threads/how-to-smooth-character-movement-that-is-jittery.322417/ https://forum.unity.com/threads/charactercontroller-movement-jittery.40196/
I've seen that some people experience jitters far from the origin, but this occurs close to Zero, so I've discounted that idea. I've tried updating the Updates to LateUpdate / FixedUpdate. I have changed the "Min Move Distance" on the Character Controller to no avail.
I think the key is fixing the issue is either reaching the desired lane value with a different type of Move on the Character Controller or giving it a margin or error, any ideas on how to achieve this would be great.
I've also attached my code for the playerMotor below.
Unity Version: 2019.1.0f2
PlayerMotor.cs:
using UnityEngine;
using UnityEngine.UI;
public class PlayerMotor : MonoBehaviour
{
// PARAMETERS
private const float LANE_DISTANCE = 2.5f;
private const float TURN_SPEED = 0.05f;
// BOOL
private bool isRunning = false;
// ANIMATION
private Animator anim;
// MOVEMENT
private CharacterController controller;
private float jumpForce = 4.0f;
private float gravity = 12.0f;
private float verticalVelocity;
// SPEED
private float originalSpeed = 7.0f;
private float speed;
private float speedIncreaseLastTick;
private float speedIncreaseTime = 2.5f;
private float speedIncreaseAmount = 0.1f;
// DEBUG
public Text xpos, ypos, zpos;
// LANE FOR PLAYER
private int desiredLane = 1; // 0 = Left, 1 = Middle, 2 = Right
private void Start()
{
speed = originalSpeed;
controller = GetComponent<CharacterController>();
anim = GetComponent<Animator>();
}
private void Update()
{
// CHECK FOR BOOLEAN IN GAME MANAGER
if (!isRunning)
return;
if(Time.time - speedIncreaseLastTick > speedIncreaseTime){
speedIncreaseLastTick = Time.time;
speed += speedIncreaseAmount;
// CHANGE THE MODIFIER TEXT
GameManager.Instance.UpdateModifier(speed - originalSpeed);
}
if (MobileInput.Instance.SwipeLeft)
{
MoveLane(false);
}
if (MobileInput.Instance.SwipeRight)
{
MoveLane(true);
}
// Calculate where we should be next
Vector3 targetPosition = transform.position.z * Vector3.forward;
// SWITCH LANES
if (desiredLane == 0)
{
targetPosition += Vector3.left * LANE_DISTANCE;
//Debug.Log(targetPosition);
}
else if (desiredLane == 2)
{
targetPosition += Vector3.right * LANE_DISTANCE;
//Debug.Log(targetPosition);
}
// THIS DIDN'T FIX IT
// ===============================
//switch(desiredLane){
// case 0:
// targetPosition = Vector3.left * LANE_DISTANCE;
// targetPosition = new Vector3(targetPosition.x, targetPosition.y, transform.position.z);
// break;
// case 1:
// targetPosition = Vector3.zero;
// targetPosition = new Vector3(targetPosition.x, targetPosition.y, transform.position.z);
// break;
// case 2:
// targetPosition = Vector3.right * LANE_DISTANCE;
// targetPosition = new Vector3(targetPosition.x, targetPosition.y, transform.position.z);
// break;
//}
// CALCULATE THE MOVE DELTA
Vector3 moveVector = Vector3.zero;
moveVector.x = (targetPosition - transform.position).normalized.x * speed;
// DEVELOPMENT
Vector3 playerPosition = this.transform.position;
xpos.text = playerPosition.x.ToString();
ypos.text = playerPosition.y.ToString();
zpos.text = playerPosition.z.ToString();
// CALL THE FUNCTION HERE AND ASSIGN TO VARIABLE SO WE'RE NOT CALCULATING TWICE
bool isGrounded = IsGrounded();
anim.SetBool("Grounded", isGrounded);
// CALCULATE GRAVITY
// IS CHARACTER ON THE GROUND
if (isGrounded)
{
verticalVelocity = -0.1f;
// IF PLAYER JUMPS
//if (Input.GetKeyDown(KeyCode.Space) || MobileInput.Instance.SwipeUp)
if (MobileInput.Instance.SwipeUp)
{
anim.SetTrigger("Jump");
verticalVelocity = jumpForce;
} else if (MobileInput.Instance.SwipeDown){
// SLIDE
StartSliding();
Invoke("StopSliding", 1.0f);
}
}
else
{
// ALWAYS PULL THE CHARACTER DOWN
verticalVelocity -= (gravity * Time.deltaTime);
// FAST FALLING
//if (Input.GetKeyDown(KeyCode.Space) || MobileInput.Instance.SwipeDown)
if (MobileInput.Instance.SwipeDown)
{
verticalVelocity = -jumpForce;
}
}
moveVector.y = verticalVelocity;
moveVector.z = speed;
// USE THE MOVE DELTA AND APPLY TO CONTROLLER
// NEED TO MOVE THE PLAYER TO MOVE VECTOR WITHIN MARGIN OF ERROR?!
controller.Move(moveVector * Time.deltaTime);
// ROTATE THE PLAYER TO WHERE HE IS GOING
Vector3 dir = controller.velocity;
if (dir != Vector3.zero)
{
dir.y = 0;
transform.forward = Vector3.Lerp(transform.forward, dir, TURN_SPEED);
}
}
private void MoveLane(bool goingRight)
{
// DESIRED LANE TO MOVE TO
desiredLane += (goingRight) ? 1 : -1;
desiredLane = Mathf.Clamp(desiredLane, 0, 2);
}
private bool IsGrounded()
{
Ray groundRay = new Ray(new Vector3(controller.bounds.center.x,
(controller.bounds.center.y - controller.bounds.extents.y) + 0.2f,
controller.bounds.center.z), Vector3.down);
Debug.DrawRay(groundRay.origin, groundRay.direction, Color.cyan, 1.0f);
return (Physics.Raycast(groundRay, 0.2f + 0.1f));
}
public void StartRunning(){
isRunning = true;
anim.SetTrigger("StartRunning");
}
private void StartSliding(){
anim.SetBool("Sliding", true);
controller.height /= 2;
controller.center = new Vector3(controller.center.x, controller.center.y / 2, controller.center.z);
}
private void StopSliding(){
anim.SetBool("Sliding", false);
controller.height *= 2;
controller.center = new Vector3(controller.center.x, controller.center.y * 2, controller.center.z);
}
private void OnControllerColliderHit(ControllerColliderHit hit)
{
switch(hit.gameObject.tag){
case "Obstacle":
Crash();
break;
}
}
private void Crash(){
// PLAYER DIES
anim.SetTrigger("Death");
isRunning = false;
GameManager.Instance.OnDeath();
}
}
Answer by mat_capedkoala · Oct 22, 2021 at 11:23 AM
Hi @TomGa83 - I'm afraid I had to rewrite a lot of this project - once the X values became large enough this issue persisted. If you think of something like Temple Run - you're always turning and going back on yourself (so the values remain small). Changing my Update to LateUpdate helped too - but ultimately I couldn't avoid the issue and so had to rewrite. Sorry I couldn't be more help.