Prevent player rotation from changing last second in Unity - c#

Unity using C#
In an top down game where the camera rotation is locked, I have made a character controller which is restricted to 8 axis and will only rotate in 45 degree increments. Think Links awakening. Everything works except when moving in a diagonal direction(using WASD).
When I let go of W & D for example, whichever key I let go of last even if by a fraction of a second, the player will end up facing in that direction rather than the intended up and to the right diagonal direction.
I am looking to modify the following code to add some sort of buffer so that the player remains facing the intended direction when I let go of the 2 keys. I'd like to keep it as simple as possible.
public float defaultSpeed = 6f;
CharacterController controller;
private Vector3 playerMovement;
Vector2 input;
float angle;
Quaternion targetRotation;
Transform cam;
// Start is called before the first frame update
void Start()
{
controller = GetComponent<CharacterController>();
cam = Camera.main.transform;
}
// Update is called once per frame
void Update()
{
Movement();
}
public void Movement()
{
//Get inputs
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
//Only update if keys are pressed
if (Mathf.Abs(input.x) < deadZone && Mathf.Abs(input.y) < deadZone) return;
//Get camera angle: input to radians - radians to degrees - degrees plus the camera angle - limit angle to 45 degree increments
angle = Mathf.Atan2(input.x, input.y);
angle = Mathf.Rad2Deg * angle;
angle += cam.eulerAngles.y;
angle = Mathf.Round(angle / 45) * 45;
//Rotate Character
targetRotation = Quaternion.Euler(0, angle, 0 );
transform.rotation = targetRotation;
//Move Character
transform.position += transform.forward * defaultSpeed * Time.deltaTime;
}

Make a courotine that checks if a key is released, and waits, say 0.5 seconds for another key to be released. If another key is released, then stay in that direction. Hope it works. Good luck.

Related

Move 2D object at constant speed and turn towards touch point

I've been trying for a while to get a 2D player to work kind of like a bullet that is always moving forward (forward being in this case the local X axis for the GameObject, as that's the way that the character is facing) and only changes direction when you touch a point on the screen, in which case it should smoothly start turning towards that point.
One problem I have is that I can't manage to keep the character moving smoothly at a constant speed in the last direction it was facing before, and the other problem that I'm finding is that the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis, which makes the sprite become invisible to the camera.
Here's the code that I have right now:
Vector3 lastTouchPoint;
private void Start()
{
lastTouchPoint = transform.position;
}
void Update()
{
if (Input.touchCount > 0)
{
// The screen has been touched so store the touch
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
}
}
transform.position = Vector3.Lerp(transform.position, lastTouchPoint, Time.deltaTime);
//Rotate towards point
Vector3 targetDir = lastTouchPoint - transform.position;
transform.LookAt(lastTouchPoint);
}
Thanks in advance!
keep the character moving smoothly at a constant speed
You probably didn't understand what Lerp actually is: This interpolates between the two positions on the given factor where 0 means fully the first position, 1 means fully the last position and e.g. 0.5f would mean in the center between both positions.
This results in faster speeds if the positions are further apart and becomes slower and slower the smaller the distance between both positions becomes. In some cases especially with a factor that small as in your case the object might even never actually reach the target position.
Using this with a dynamic factor of Time.deltaTime makes no sense as this value changes every frame and jitters somewhere around 0,017 (assumin 60 FPS).
You could rather use Vector3.MoveTowards with a fixed constant speed
// set via the Inspector
public float speedInUnitsPerSecond = 1;
...
transform.position = Vector3.MoveTowards(transform.position, lastTouchPoint, Time.deltaTime * speedInUnitsPerSecond);
if you want to keep moving but stop once the touched position is reached.
If you rather wanted to continue moving in the according direction no matter what you could rather store the direction instead of a position and use a straight forward Transform.Translate
// set via the Inspector
public float speedInUnitsPerSecond = 1;
private Vector2 lastDirection;
privtae void Update()
{
...
// If the finger is on the screen, move the object smoothly to the touch position
var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
lastDirection = (touchPosition - transform.position).normalized;
...
// move with constant speed in the last direction
transform.Translate(lastDirection * Time.deltaTime * speedInUnitsPerSecond);
...
}
the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis
Note that Transform.LookAt has an optional second parameterworldUp which by default is Vector3.up so a rotation around the global Y axis!
Since you rather want a rotation around the Z axis you should pass
transform.LookAt(lastTouchPoint, Vector3.forward);
I don't know your setup ofcourse but also note that
LookAt
Rotates the transform so the forward vector points at worldPosition.
As you describe it it is also possible that you don't want the objects forward vector to point towards the target position but actually rather the objects right (X) vector!
You can do this by rather simply directly setting the transform.right like e.g.
transform.right = (lastTouchPoint - transform.position).normalized;
or
transform.right = lastDirection;
Btw it would actually be enough to set this rotation only once, namely the moment it changes so in
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
transform.right = (lastTouchPoint - transform.position).normalized;
}
or
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
lastDirection = (touchPosition - transform.position).normalized;
transform.right = lastDirection;
}
I ended up finding the answer to my own problem using code to rotate smoothly from another post. Here's the code:
Vector3 lastTouchPoint;
Vector3 direction;
Vector3 vectorToTarget;
//Character controller variables
public float moveSpeed = 5f;
public float angularSpeed = 3f;
private void Start()
{
lastTouchPoint = transform.position;
}
void Update()
{
if (Input.touchCount > 0)
{
// The screen has been touched so store the touch
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
direction = lastTouchPoint - transform.position;
vectorToTarget = lastTouchPoint - transform.position;
}
}
transform.position += direction.normalized * moveSpeed * Time.deltaTime;
float angle = Mathf.Atan2(vectorToTarget.y, vectorToTarget.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * angularSpeed);
}

Unity enemy ai always firing bullets high over the player

Got an issue where the enemy will fire at the player, but always seems to go high or to the side of the player even when the player is stationary and isn't moving. Am I doing something wrong in my code which creates this wild issue or is it just a random annoying bug?
Using the same script for the player albeit it under a different name works, which leads me to believe the issue lies within the fire point. Under the player's script I fire like so:
// Get the place the player has clicked
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(ray, out hitInfo))
{
if(hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
Whereas in the enemy script it is just done through the player's position.
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(player.transform.position,transform.forward, out hitInfo))
{
Is this underlying issue in the Physics.Raycast call then?
Rest of code for reference:
//More above this but doesn't influence the firing
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
{
if (hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
}
}
private void FireAtPoint(Vector3 point)
{
// Get the velocity to fire out at
var velocity = FiringVelocity(point, angle);
Rigidbody rg = Instantiate(bulletPrefab.gameObject, firePoint.position, firePoint.rotation).GetComponent<Rigidbody>();
EnemyBulletController newProjectile = rg.GetComponent<EnemyBulletController>();
newProjectile.speed = velocity;
}
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
// Get the direction of the mouse click from the player, then get the height differential.
Vector3 direction = destination - transform.position;
float height = direction.y;
height = 0;
// Get the distance in a float of the vector3
float distance = direction.magnitude;
// Turn the firing angle into radians for calculations, then work out any height differential
float AngleRadians = angle * Mathf.Deg2Rad;
direction.y = distance * Mathf.Tan(AngleRadians);
distance += height / Mathf.Tan(AngleRadians);
// Calculate the velocity magnitude
float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2 * AngleRadians));
// Return the normalized vector to fire at.
return velocity * direction.normalized;
}
Picture for reference:
Your equation for computing the velocity looks doubtful. Let's re-derive it:
The equations of free-fall motion under constant gravity are:
After rearranging by substituting the first into the second, we find an expression for the firing velocity:
This is different to what you have, as you are missing the h/d term; said term also gives a constraint on the allowed values of θ:
(Basically means that if you fire directly at the target the bullet would never reach due to gravity)
There are many other problems with your code; just to list three:
Why set height to zero?
Why add a correction to distance? The correction has no physical interpretation.
The fix suggested by #BasillePerrnoud
Amended code:
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
Vector3 direction = destination - transform.position;
float height = direction.y;
float distance = Mathf.Sqrt(direction.x * direction.x + direction.z * direction.z); // *horizontal* distance
float radians = angle * Mathf.Deg2Rad;
float hOverd = height / distance;
float tanAngle = Mathf.Tan(radians);
if (tanAngle <= hOverd)
// throw an exception or return an error code, because no solution exists for v
float cosAngle = Mathf.Cos(radians);
direction.Y = distance / cosAngle;
float velocity = Mathf.Sqrt((distance * Physics.gravity.magnitude) /
(2 * cosAngle * cosAngle * (tanAngle - hOverd)));
return velocity * direction.normalized;
}
I think you use Raycast wrongly. According to the doc, the second argument is the direction, not the destination:
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
Should be
if (Physics.Raycast(transform.position, player.transform.position -
transform.position, out hitInfo))
That would explain why it is not firing at the right moment and why the direction is not accurate (since hitInfo is wrong)

Unity bouncing when colliding with object

I have made a script with movement, that finally works as i want it to, except one thing... I want it to be a first person game, but the way the movement works now, is on the global axis, which means that W is always torwards one specific direction, no matter what direction my camera is turning... How do i fix this? i want the movement to stay how it is, but with the W key to always be forward depending on the way the camera or player is looking.
Please let me know how my script would look edited, or atleast what part i have to change.
I would also like to add that i would love to be able to do a wall jump, but i am not sure how to add that behavior.
Here is my movement script:
public class MovementScript : MonoBehaviour {
public float speed;
public float jumpforce;
public float gravity = 25;
private Vector3 moveVector;
private Vector3 lastMove;
private float verticalVelocity;
private CharacterController controller;
// Use this for initialization
void Start () {
controller = GetComponent<CharacterController> ();
//Låser og gemmer musen
Cursor.lockState = CursorLockMode.Locked;
}
// Update is called once per frame
void Update () {
//Låser musen op
if (Input.GetKeyDown ("escape"))
Cursor.lockState = CursorLockMode.None;
moveVector = Vector3.zero;
moveVector.x = Input.GetAxis("Horizontal");
moveVector.z = Input.GetAxis("Vertical");
if (controller.isGrounded) {
verticalVelocity = -1;
if (Input.GetButton("Jump")) {
verticalVelocity = jumpforce;
}
} else {
verticalVelocity -= gravity * Time.deltaTime;
moveVector = lastMove;
}
moveVector.y = 0;
moveVector.Normalize ();
moveVector *= speed;
moveVector.y = verticalVelocity;
controller.Move (moveVector * Time.deltaTime);
lastMove = moveVector;
}
}
You need to go from local to world space:
for example when you want to move on the x-axis regarding the player's orientation, the local vector is (1, 0, 0), which you get from your input axis, but the CharacterController need a world based direction (see the doc of the Move function)
A more complex move function taking absolute movement deltas.
To get this, use Transform.TransformDirection like this
worldMove = transform.TransformDirection(moveVector);
controller.Move (worldMove * Time.deltaTime);
EDIT regarding your issue with the controller moving a bit after releasing the input:
That's because GetAxis gives you a smoothed value. Replace GetAxis by GetAxisRaw and it should work
Modify the final moving code to
controller.Move(moveVector.z * transform.forward * Time.deltaTime);
controller.Move(moveVector.x * transform.right * Time.deltaTime);
controller.Move(moveVector.y * transform.up * Time.deltaTime);
Alternatively, suppose your character is only rotated about the Y axis, you can look into rotating your moveVector by your character's Y rotation so moveVector's forward points parallel to your chracter's forward.
I found a good explaination of rotating a vector here: https://answers.unity.com/questions/46770/rotate-a-vector3-direction.html
Rotating a vector:
moveVector = Quaternion.Euler(0, transform.eulerAngles.y, 0) * moveVector;

Unity: Special movement for game

I'm writing movement for my space game and spaceship object (player) with mouse cursor.
Currently have following code:
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class Move : MonoBehaviour {
public float speed = 1.5f;
public float rotationSpeed = 90f;
public float rotPrecision = 0.1f;
public float movePrecision = 0.1f;
private Vector3 pos;
private Quaternion qTo;
void Start () {
pos = transform.position;
qTo = transform.rotation;
}
void Update () {
if (!EventSystem.current.IsPointerOverGameObject())
{
if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(0))
{
pos = Input.mousePosition;
pos.z = transform.position.z - Camera.main.transform.position.z;
pos = Camera.main.ScreenToWorldPoint(pos);
}
var dir = pos - transform.position;
qTo = Quaternion.LookRotation(Vector3.forward, pos - transform.position);
if (Quaternion.Angle(transform.rotation, qTo) >= rotPrecision) //just set your own precision
transform.rotation = Quaternion.RotateTowards(transform.rotation, qTo, Time.deltaTime * rotationSpeed);
if (Vector3.Distance(transform.position, pos) > movePrecision) // 0.1f
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
}
}
But there i have the problem with the movement precision and rotation when the point is too close to player (have infinite loop).
The idea of this movement system described with the following image:
(Player actor is green, path is gray, and destination point is red).
I hope that somebody could help me w/ that.
Thank you!
If I understand your question correctly, the problem is that the player's movement never stops as the code can't reach a finishing point.
To solve this you can add an acceptable precision margin.
So calculate if the difference between the rotation you wish or the movement you wish, and the players actual rotation/position, is less than a given variable, for example less than 0.05%.
That way you could allow the program to know that if it's just within 0.05% precision, then it's okay for it to stop moving.
Otherwise, if the program never reaches a complete and perfect rotation and position, it will continue to adjust endlessly due to slight mathematical imprecision in the calculations and movement pattern.

Slowly moving gameobject to mouse position

I want to change the position of the object to the position of the mouse, moving slowly from first to second position.
My object is moving slowly to the random direction which appears to be connected with lower-left corner. When I go higher than the corner my object is moving upwards, same with left and right.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rocket : MonoBehaviour
{
public float speed = 10f;
private Vector3 shippos;
void Start()
{
shippos = transform.position;
}
void FixedUpdate()
{
if (Input.mousePosition.x > shippos.x)
shippos.x=shippos.x+speed*Time.deltaTime;
if (Input.mousePosition.x < shippos.x)
shippos.x=shippos.x-speed*Time.deltaTime;
if (Input.mousePosition.y > shippos.y)
shippos.y=shippos.y+speed*Time.deltaTime;
if (Input.mousePosition.y < shippos.y)
shippos.y=shippos.y-speed*Time.deltaTime;
transform.position = shippos;
}
}
The mouse position is returned in screenspace coordinates. What you need to do is convert this to world coordinates so that they are compared in the same coordinate space as the transform (shippos).
void FixedUpdate()
{
if (Camera.main.ScreenToWorldPoint(Input.mousePosition).x > shippos.x)
shippos.x = shippos.x + speed * Time.deltaTime;
if (Camera.main.ScreenToWorldPoint(Input.mousePosition).x < shippos.x)
shippos.x = shippos.x - speed * Time.deltaTime;
if (Camera.main.ScreenToWorldPoint(Input.mousePosition).y > shippos.y)
shippos.y = shippos.y + speed * Time.deltaTime;
if (Camera.main.ScreenToWorldPoint(Input.mousePosition).y < shippos.y)
shippos.y = shippos.y - speed * Time.deltaTime;
transform.position = shippos;
}
If I did not misunderstand, you want to change your player's position straight to the point of mouse's position and looking towards mouse.
void Update() {
Transform target = mousePosition; //get your mouse position per frame
Vector3 relativePos = target.position - transform.position; //create a vector3 between them
Quaternion rotation = Quaternion.LookRotation(relativePos); //then give a rotation your player towards this vector.
transform.rotation = rotation; //and apply it.
}
Taken from here: http://answers.unity3d.com/questions/633873/how-to-make-an-object-move-towards-the-mouse-point.html
public float moveSpeed = 2.0; // Units per second
void Update () {
var targetPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
targetPos.z = transform.position.z;
transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
}
May not be accurate C#, but you get the idea.
i guess you are doing wrong , because wile you have Mathf.Lerp you Dont need to go as that clumsy way
Here is a video tutorial from youtube for mathf.lerp
and here is the base code:
someValue = Mathf.Lerp(initialValue , finalValue , Time.deltaTime * smoothness);
just take a look to the youtube link you will definitely get the idea!
AS A SIDE NOTE
you dont need to do alot of stuff to decrease your game performance! be careful about this kinda coding!

Categories