I am trying to achieve basic racing game. Infinite racing game, movement method like a subway surfers. I have a problem about changing lane. I dont want to teleport to other lane, I want to smoothly. I am newbee in unity, I have try Lerp method but it is not working.
using UnityEngine;
using System.Collections;
public class VehicleController : MonoBehaviour
{
public float drift;
public Vector3 positionA;
public Vector3 positionB;
public Vector3 positionC;
public Vector3 positionD;
private Transform tf;
private Rigidbody rb;
private Vector3 vehiclePos;
void Awake()
{
//rb = GetComponent<Rigidbody> ();
tf = transform;
}
void Update()
{
vehiclePos = tf.position;
if (Input.GetKey( KeyCode.Space ))
DecreaseSpeed ();
else
IncreaseSpeed ();
if (Input.GetKeyDown (KeyCode.A))
{
MoveToRight ();
Debug.Log( "Move to Right!" );
}
if (Input.GetKeyDown (KeyCode.D))
{
MoveToLeft ();
Debug.Log( "Move to Left!" );
}
}
void FixedUpdate()
{
tf.Translate (Vector3.forward * speed * Time.deltaTime);//My Movement Method.
}
void MoveToLeft()
{
if (vehiclePos.position.x == positionA.x)
vehiclePos = Vector3.Lerp (vehiclePos.position, positionB, Time.deltaTime * drift);
}
void MoveToRight()
{
if (vehiclePos.position.x == positionB.x)
vehiclePos = Vector3.Lerp (vehiclePos.position, positionA, Time.deltaTime * drift);
}
}
First: Don't use == for position.x, since it's a floating-point (decimals) value and in this case it would be very rare for it to return "true". Here's some info about comparing floats.
Second: It doesn't look like you're connecting your actual position with vehiclePos anywhere. transform.position is what you want there.
Third: Input.GetAxis() is a cleaner way to deal with direction input. Instead of specifically calling out each button you can deal with just one float value between -1 and 1. It will also let you reconfigure the keys easily.
Fourth: In an infinite runner it is better to have the world move towards your character and camera than to have the character and camera actually move forward. Floating point numbers get less precise as you move further away from zero, so you should have your action take place relatively close to the world origin (0,0,0) point if you can.
If you want to press the button once to change lanes, you should keep an integer variable that saves which lane you're currently in. If you press LEFT you subtract one, and if you press RIGHT you add one. You should also add a check to make sure it stays within the desired range.
Then in Update() you just need to ALWAYS Lerp towards that X value. You can use Mathf.Lerp to lerp only one variable at a time if you want.
public int laneNumber = 0;
public int lanesCount = 4;
bool didChangeLastFrame = false;
public float laneDistance = 2;
public float firstLaneXPos = 0;
public float deadZone = 0.1f;
public float sideSpeed = 5;
void Update() {
//"Horizontal" is a default input axis set to arrow keys and A/D
//We want to check whether it is less than the deadZone instead of whether it's equal to zero
float input = Input.GetAxis("Horizontal");
if(Mathf.Abs(input) > deadZone) {
if(!didChangeLastFrame) {
didChangeLastFrame = true; //Prevent overshooting lanes
laneNumber += Mathf.roundToInt(Mathf.Sign(input));
if(laneNumber < 0) laneNumber = 0;
else if(laneNumber >= lanesCount) laneNumber = lanesCount - 1;
}
} else {
didChangeLastFrame = false;
//The user hasn't pressed a direction this frame, so allow changing directions next frame.
}
Vector3 pos = transform.position;
pos.x = Mathf.Lerp(pos.x, firstLandXPos + laneDistance * laneNumber, Time.deltaTime * sideSpeed);
transform.position = pos;
}
You could likely just use this code as-is, but I suggest you look it over and try to figure out an understanding of how and why it works. A newbie today who always seeks to improve their skill can do something amazing next week. Hope this helps. :)
Related
Hi I am creating a hyper casual game with unity, but I have encountered a problem with the swerve control (I have also seen many git hubs but even these do not work perfectly)
I've put this in my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
private float lastframeposx;
private float movefactorx;
public float MoveFactorX => movefactorx;
public Camera m_MainCam;
private float speed = 2.0f;
[SerializeField]
GameObject character;
[SerializeField] private float swerveSpeed = 0.5f;
[SerializeField] private float maxSwerveAmount = 1f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position += Vector3.forward * speed * Time.deltaTime;
Cammina();
/*Vector3 destra = Camera.main.ScreenToWorldPointt(Input.touches[i].position);
transform.position += Vector3.zero destra;*/
}
void Cammina()
{
if(Input.GetMouseButtonDown(0))
{
lastframeposx = Input.mousePosition.x;
float swerveAmount = Time.deltaTime * swerveSpeed * MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
}
else if (Input.GetMouseButton(0))
{
movefactorx = Input.mousePosition.x - lastframeposx;
lastframeposx = Input.mousePosition.x;
float swerveAmount = Time.deltaTime * swerveSpeed * MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
}
else if(Input.GetMouseButtonUp(0))
{
movefactorx = 0f;
float swerveAmount = Time.deltaTime * swerveSpeed * MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
}
}
/*void vaidoveschiacciato()
{
if (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Began)
{
Ray ray = Camera.main.ScreenPointToRay(Input.touches[0].position);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
if(hit.collider != null)
{
transform.position += hit.collider.GetComponent<Transform.position> * speed * Time.deltaTime;
}
}
}
}*/
}
1 Problem: he don't go when the finger is
2 Problem: How do I eliminate the movement from right to left (Without making it go out of the path)
(Langauge: C#)
The problem: When you swerve, it swerves just in the direction, there is no limits on how far it goes.
How I would fix this: I would put the movement to change it through a function. This could clamp it, so the higher the distance to the center of the track, the less it swerves. Or, you can altogether check if the distance is a maximum and then stop swerving.
Note: you can use other functions to do this (they just have to flatten out the larger the input).
Smooth, good looking bell curve way
For example you could use a bell curve. Look one up if you've never seen one before. It is at it's highest possible output of one, at a zero input. When it gets hiher or lower, it outputs lower to zero.
the simplest equation is y = i-(x2). The lower i is (above 1), the wider the curve, or the larger the output is for a larger input. I made a graph here.
x can be the distance to the center of the track, so you should adjust i, so the maximum distance from the track is flat.
This output is what you should change swerveAmount to.
The flatter parts of the graoh is how much you will swerve when you are that distance from the center (x axis)
Alternatively
You could just check the distance, and if it passes a certain distance don't translate it.
Let me know in the omments if there are any problems! :)
So I'm working on a 2D game right now just to learn different stuff or codes related to making 2D games.
So I've come to a trouble where I got curious on how to make a character move a certain distance like per say 1 block per tap of button. I will give an example here. So take imagine the grid as a land.
Move a certain distance.
This one is the one my character is doing with my current code
and here's my movement code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class keycontrol : MonoBehaviour
{
private float moveSpeed;
private Rigidbody2D rb2d;
private Vector2 change;
private Animator animator;
bool isXMoving;
bool isYMoving;
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
}
void Update()
{
change.x = Input.GetAxisRaw("Horizontal");
change.y = Input.GetAxisRaw("Vertical");
if (Mathf.Abs(change.x) > Mathf.Abs(change.y))
{
change.y = 0;
}
else
{
change.x = 0;
}
animator.SetFloat("walk_right", change.x);
animator.SetFloat("walk_left", -change.x);
animator.SetFloat("walk_down", -change.y);
animator.SetFloat("walk_up", change.y);
}
void FixedUpdate()
{
rb2d.MovePosition(rb2d.position + change * moveSpeed * Time.fixedDeltaTime);
if(Input.GetKey("left shift"))
{
moveSpeed = 150;
animator.speed = 1.5f;
}
else
{
moveSpeed = 70;
animator.speed = 1f;
}
}
}
Thanks a lot for help
I think the problem is just your movement speed. You multiply your speed with Time.fixedDeltaTime but that isn't necessary because that value will be constant. Instead, try just setting the speed to 1 and remove the Time.fixedDeltaTime.
Note: Time.deltaTime is used to make the character move a certain amount per frame because the shorter the time between frames, the less the character will move. Time.fixedDeltaTime stays constant because it is the time between each physics frame.
If I'm understanding correctly, the reason it keeps moving is because the input stays greater than zero. You could add a variable to check if the keys are already down to stop the movement.
There are two options here I think,
Make the user click everytime then want to move (press key, lift finger, press key, etc)
Add a timer to make it move every Nth second
For (1) we could just add an variable to say we are moving already.
//Changes IsXMoving and IsYMoving to a single boolean
bool isMoving = false;
///Start, Update, etc
void FixedUpdate()
{
//Check if we are already moving - if we are not, update movement
if (!isMoving)
{
rb2d.MovePosition(rb2d.position + change * moveSpeed * Time.fixedDeltaTime);
if(Input.GetKey("left shift"))
{
moveSpeed = 150;
animator.speed = 1.5f;
}
else
{
moveSpeed = 70;
animator.speed = 1f;
}
}
//Check we are moving by getting the magnitude - if it zero, we are still
isMoving = (change.magnitude != 0);
}
The second option (2) would basically replace this with a basic timer.
//Removed IsXMoving and IsYMoving
//lastMove will store the time we last allowed movement and delay is
//the minimum time between movements in seconds
float lastMove;
float delay = 1; //1 second
void FixedUpdate()
{
//Check if enough time has passed to be allowed to move
if (lastMove + delay < Time.time)
{
rb2d.MovePosition(rb2d.position + change * moveSpeed * Time.fixedDeltaTime);
if(Input.GetKey("left shift"))
{
moveSpeed = 150;
animator.speed = 1.5f;
}
else
{
moveSpeed = 70;
animator.speed = 1f;
}
//Only update the time if we are moving
if (change.magnitude > 0)
lastMove = Time.time;
}
}
I have made 4 different types of animation clips in an animator for an an empty gameobject called "Animator". The main camera is a child of this.
The animations feature a running cycle, a walking cycle, a crouch cycle, and an idle cycle. They all have a trigger that let's them play.
How am I able to measure the speed of the player and execute these animations when the player reaches a certain speed. I have found someone else trying to do this and it works but only for idle and walk. But unfortunately I can't get the sprint and crouch to work. I'm not sure what to do, either have the sprint and crouch animations or just change the speed of the walk animation depending on whether the player is sprinting or crouching. I'll leave a comment where the code I found is.
Here's what I have in my player controller (thge trigger stop is for the idle animation):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public CharacterController controller;
Animator _ar;
public float speed;
[Range(-5, -20)]
public float gravity = -9.81f;
public float sprintSpeed = 6f;
public float walkSpeed = 4f;
public float crouchSpeed = 2f;
public float standHeight = 1.6f;
public float crouchHeight = 1f;
Vector3 velocity;
bool isGrounded;
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
public Light _l;
//Set this to the transform you want to check
public Transform objectTransfom;
private float noMovementThreshold = 0.0001f;
private const int noMovementFrames = 1;
Vector3[] previousLocations = new Vector3[noMovementFrames];
public bool isMoving;
//Let other scripts see if the object is moving
public bool IsMoving
{
get { return isMoving; }
}
void Awake()
{
//For good measure, set the previous locations
for (int i = 0; i < previousLocations.Length; i++)
{
previousLocations[i] = Vector3.zero;
}
}
void Start()
{
_ar = GameObject.Find("Animator").GetComponentInChildren<Animator>();
}
// Update is called once per frame
void Update()
{
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
//Below here is the code I found. The if statements for isMoving, is what I put in to see if
//this worked.
//Store the newest vector at the end of the list of vectors
for (int i = 0; i < previousLocations.Length - 1; i++)
{
previousLocations[i] = previousLocations[i + 1];
}
previousLocations[previousLocations.Length - 1] = objectTransfom.position;
//Check the distances between the points in your previous locations
//If for the past several updates, there are no movements smaller than the threshold,
//you can most likely assume that the object is not moving
for (int i = 0; i < previousLocations.Length - 1; i++)
{
if (Vector3.Distance(previousLocations[i], previousLocations[i + 1]) >= noMovementThreshold)
{
//The minimum movement has been detected between frames
isMoving = true;
break;
}
else
{
isMoving = false;
}
}
if(isMoving == true)
{
if (Input.GetKeyDown(KeyCode.LeftShift))
{
speed = sprintSpeed;
_ar.SetTrigger("WalkSprint");
}
else if (Input.GetKeyUp(KeyCode.LeftControl))
{
speed = crouchSpeed;
_ar.SetTrigger("WalkCrouch");
//transform.localScale = new Vector3(0.8f, 0.5f, 0.8f);
}
else
{
speed = walkSpeed;
_ar.SetTrigger("Walk");
//transform.localScale = new Vector3(0.8f, 0.85f, 0.8f);
}
}
else
{
_ar.SetTrigger("Stop");
}
}
}
Unfortunately, as with many issue in Game Dev, this could be a number of different issues (or all of them!). Here is where you can start to debug:
Check your error log to see if there is anything obvious that jumps out at you, like a bad reference to a Game Object/Componenet.
Watch the animator. You should be able to have the Animator open, side-by-side with your game window while the game is running. You should be able to see the animations running and transitioning. See if something is not linked properly, or if an animation time is configured incorrectly, etc.
Add some debug statements, like outputting the current trigger being set. I would even verify that your inputs are configured correctly. Maybe add some additional conditionals at the beginning that debug what inputs are being pressed.
As #OnionFan said, your Input check is for KeyUp for the Crouch key. That should probably be KeyDown.
I want to do a Game like slither.io in Unity but it's hard to detect when the mouse stopped moving, I want the ball to not stop, instead it should move on, worked several hours on this problem but can't quite get it to work. I thought if I save where the last known position is I can keep the velocity but I don't know how to implement yet.
Thanks in advance!
This is my Source Code:
private float xMin, xMax, yMin, yMax;
[SerializeField] float constantSpeed = 100f;
[SerializeField] float padding = 5f;
private Rigidbody2D rb2D;
private Vector3 mousePosition;
private Vector2 direction;
private List<Vector3> ListPos = new List<Vector3>();
private Vector3 empty;
// Use this for initialization
void Start () {
empty = Vector3.zero;
SetUpMoveBoundaries();
rb2D = gameObject.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
Move();
}
private void SetUpMoveBoundaries()
{
Camera gameCamera = Camera.main;
xMin = gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).x + padding;
xMax = gameCamera.ViewportToWorldPoint(new Vector3(1, 0, 0)).x - padding;
yMin = gameCamera.ViewportToWorldPoint(new Vector3(0, 0, 0)).y + padding;
yMax = gameCamera.ViewportToWorldPoint(new Vector3(0, 1, 0)).y - padding;
}
private void Move()
{
if (Input.mousePosition != empty)
{
mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
direction = (mousePosition - transform.position).normalized;
ListPos.Add(direction);
rb2D.velocity = new Vector2(direction.x * constantSpeed, direction.y * constantSpeed);
}
else
{
var last = ListPos.LastOrDefault();
rb2D.velocity = new Vector2(last.x * constantSpeed, last.y * constantSpeed);
}
}
Assuming you want it to trigger one of the two functions depending on whether the mouse has moved or not since last frame:
Vector2 lastMousePosition;
void WhenMouseIsMoving()
{
}
void WhenMouseIsntMoving()
{
}
void Update()
{
if (Input.mousePosition!=lastMousePosition)
{
lastMousePosition=Input.MousePosition;
WhenMouseIsMoving();
} else
WhenMouseIsntMoving();
}
You'll need to add one more bool variable to keep track of whether it has just started or stopped moving;
If you want to check for mouse movement without having to keep track of the mouse position in a variable, you can use the GetAxis function from Unity's Input class.
To do this you will have to make sure that mouse movement is hooked up to axes in Unity's input manager. It is normally there by default already, with 'Mouse X' and 'Mouse Y' mapped to the mouse delta for their respective axes.
For example:
// Is true when the mouse has moved
if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0)
{
// Do something with mouse input
}
In your case, it looks like you could do something like this as your move function:
Vector2 direction = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
if (direction.magnitude != 0)
{
// ListPos.Add(direction);
rb2D.velocity = direction.normalized * constantSpeed;
}
As an unrelated note, your ListPos variable is growing in size potentially every frame without limit. If you need to keep track of previous positions for reasons other than your attempt at detecting mouse position changes, you should consider how much storage you will need and give it a fixed size replacing the oldest entries, or consider whether or not the values need to be independent or can be merged instead.
I'm making a game in which the player controls two different characters (each one has its own empty object with a camera as child), and switchs one or another by pressing the control key. The thing is, I'm trying to make a little transition between both characters cameras by using another camera, so it doesn't just teleports between one and another but I can't seem to do it. I tried with lerp but I don't know if I got it right, so I read and tried Vector3.MoveTowards but still couldn't do it. This is my code so far (the while is because a last-moment-braindead I had):
public class CameraController : MonoBehaviour
{
public Camera cam1;
public Camera cam2;
public Camera movingCamera;
public bool isCurrentPlayer;
public Transform target1;
public Transform target2;
public float speed = 0.2f;
void FixedUpdate()
{
float step = speed * Time.deltaTime;
if (Input.GetButtonDown("Control"))
{
if (isCurrentPlayer)
{
movingCamera.enabled = true;
cam2.enabled = false;
while (transform.position != target1.position)
{
transform.position = Vector3.MoveTowards(transform.position, target1.position, step);
}
if (transform.position == target1.transform.position)
{
movingCamera.enabled = false;
cam1.enabled = true;
}
isCurrentPlayer = false;
}
else if (!isCurrentPlayer)
{
movingCamera.enabled = true;
cam1.enabled = false;
while (transform.position != target2.position)
{
transform.position = Vector3.MoveTowards(transform.position, target2.position, step);
}
if (transform.position == target2.transform.position)
{
movingCamera.enabled = false;
cam2.enabled = true;
}
isCurrentPlayer = true;
}
}
}
I'm curious about two things. Why did you use FixedUpdate to manage your updates? This isn't physics code. Is there a particular reason you are using multiple cameras? If I may, I propose the following changes.
You can simply make use of the main camera instead of multiple cameras. Additionally, you can increase the number of player objects you can toggle through by using an array of player GameObjects, and by changing the input parameters to left control and right control, you can toggle between next player and previous player to navigate bi-directionally through the array of players.
Here's my example code that implements these changes (tested and works, though improvements can be made.)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// Attached to Main Camera
public class CameraController : MonoBehaviour {
// set manually in inspector
public GameObject[] players;
public float movementSpeed = 1.0f;
public float rotationSpeed = 1.0f;
private int currentPlayer;
private float startTime;
private float distanceToPlayer;
private Vector3 startPosition;
private Quaternion startOrientation;
// Use this for initialization
void Start () {
currentPlayer = 0;
ResetCamera();
}
// Update is called once per frame
void Update () {
float distanceCovered;
float rotationCovered;
float fractionTraveled;
// switch to previous
if (Input.GetButtonDown("left ctrl")) {
if (currentPlayer == 0) currentPlayer = players.Length - 1;
else currentPlayer--;
ResetCamera();
}
// switch to nextPlayer
if (Input.GetButtonDown("right ctrl")) {
if (currentPlayer == players.Length - 1) currentPlayer = 0;
else currentPlayer++;
ResetCamera();
}
// Keep moving camera
if (transform.position != players[currentPlayer].transform.position)
{
distanceCovered = (Time.time - startTime) * movementSpeed;
fractionTraveled = distanceCovered / distanceToPlayer;
rotationCovered = (Time.time - startTime) * rotationSpeed;
// Lerp to player position
transform.position = Vector3.Lerp(
startPosition,
players[currentPlayer].transform.position,
fractionTraveled
);
// match player orientation
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
players[currentPlayer].transform.rotation,
rotationCovered
);
// Stop moving camera
} else {
// Match orientation
if (transform.rotation != players[currentPlayer].transform.rotation)
transform.rotation = players[currentPlayer].transform.rotation;
// Set parent transform to current player
transform.parent = players[currentPlayer].transform;
}
}
void ResetCamera() {
transform.parent = null;
startTime = Time.time;
startPosition = transform.position;
startOrientation = transform.rotation;
distanceToPlayer = Vector3.Distance(
transform.position,
players[currentPlayer].transform.position
);
}
}
Obviously the values would need to be tweaked, and the movement algorithm is pretty basic. Crude but function. You can also add player movement code into the camera, just make sure it points to players[currentPlayer] and it will work for each of your player objects without having to use additional scripts unless there is a reason to do so.
Feel free to use this code. Like I said, it works. However, should you choose to do so, it can easily be modified to function like your original code simply by removing the array and reinstating the individual GameObjects.
transform.position points to the position of CameraController game object. If you want to move movingCamera you probably want to use movingCamera.transform.position. Also keep in mind that in your script the MoveTowards() would only fire when you are pressing your "Control" button.
As memBrain said it would be the best practice to use only one camera for this - visually it would look the same.
The script should look something like this:
// Assuming target1 is player 1 and target2 is player 2
private float snapThreshold = 0.1f;
private Vector3 movingCameraDestination = Vector3.zero;
void FixedUpdate()
{
if(Input.GetButtonDown("Control"))
{
if(isCurrentPlayer)
{
//Set position of transition camera to player 1 and set it's destination to player's 2 position
movingCamera.transform.position = player1.position;
movingCameraDestination = player2.position;
//Disable player 1 camera and enable transition camera
cam1.enabled = false;
movingCamera.enabled = true;
}
else
{
//Set position of transition camera to player 21 and set it's destination to player's 1 position
movingCamera.transform.position = player2.position;
movingCameraDestination = player1.position;
//Disable player 1 camera and enable transition camera
cam2.enabled = false;
movingCamera.enabled = true;
}
}
//If transition camera is enabled and its destination is not Vector3.zero - move it
if(movingCameraDestination != Vector3.zero && movingCamera.enabled)
{
movingCamera.transform.position = Vector3.Lerp(movingCamera.transform.position, movingCameraDestination, speed * Time.deltaTime);
//If the distance between transition camera and it's destination is smaller or equal to threshold - snap it to destination position
if(Vector3.Distance(movingCamera.transform.position, movingCameraDestination) <= snapThreshold)
{
movingCamera.transform.position = movingCameraDestination;
}
//If transition camera reached it's destination set it's destination to Vector3.zero and disable it
if(movingCamera.transform.position == movingCameraDestination)
{
movingCameraDestination = Vector3.zero;
movingCamera.enabled = false;
}
}
}