How can I smooth rotation which is based on cursor position? - c#

private float rotationZ;
Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
difference.Normalize();
float rotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rotationZ);
So far my code will get the cursor position and instantly perform a transform. However, if I move the cursor near the center of the pivot, the rotation will be instantaneous and choppy. How can I add a slight smooth to this, to make it travel from point A to point B slower?

You would store the actual target value in a field and then move towards it
private Quaternion targetRotation;
private Camera camera;
private void Awake ()
{
// Camera.main is quite expensive so rather store the reference
camera = Camera.main;
// Initially set your current rotation as target
// especially important if you don't always update the target rotation but e.g.
// only if a certain button is pressed
targetRotation = transform.rotation;
}
private void Update ()
{
UpdateTargetRotation();
RotateTowardsTarget();
}
private void UpdateTargetRotation ()
{
var difference = (camera.ScreenToWorldPoint(Input.mousePosition) - transform.position).normalized;
var targetRotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
targetRotation = Quaternion.Euler(0, 0, targetRotationZ);
}
Now for the RotateTowardsTarget you have many options.
One of them would indeed be using Quaternion.Lerp with a certain interpolation factor. Here you interpolate smoothly towards the target value but very fast at the beginning and get slower and slower the closer you get to the target value regardless of how much you rotate in total. This also might never really reach the exact target value.
// Adjust in the Inspector
public float interpolationFactor = 5f;
private void RotateTowardsTarget ()
{
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, interpolationFactor * Time.deltaTime);
}
Or another option would be to rather use Quaternion.RotateTowards which rotates with a fixed speed
// Adjust in the Inspector
public float anglePerSecond = 45;
private void RotateTowardsTarget ()
{
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, anglePerSecond * Time.deltaTime);
}

You're looking for Mathf.Lerp
Example of how you could use it:
float rotationZ;
//Changes how quickly will rotate
const float rotationSpeed = 0.58f;
Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
difference.Normalize();
rotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
float lerpRotation = Mathf.Lerp(transform.rotation.z, rotationZ, Time.deltaTime * rotationSpeed);
transform.rotation = Quaternion.Euler(0f, 0f, lerpRotation);

You should use Quaternion.Slerp() in a coroutine.
You check the cursor position every FixedUpdate then start a coroutine that interpolates and sets the rotation every frame until the next FixedUpdate comes. The created lag is basicly unnoticable and it looks silky smooth.
For the interpolation t value you should use a variable that stores the time elapsed since the last FixedUpdate (you add that up from time.deltatimes), and to get the t you just divide it by 0.02secs.
Then when the coroutin finishes, set the rotation to the final value.

Related

Enemy bot moves away from the player Unity3D

I'm making top down shooting game. I wrote the code where enemies spawn randomly on map and they're trying to catch you. I made them do that and also I wrote a code to make them look at you. Basically rotate towards you only on Z axis. But problem is that when they are spawned on players' right, enemy is moving away from player. but if I rotate and start to move they are trying to fix themselves. Here's my script:
void FixedUpdate () {
Vector3 difference = player.position - transform.position;
float rotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0.0f, 0.0f, rotationZ);
Vector2 toTarget = player.transform.position - transform.position;
float speed = 1.5f;
transform.Translate(toTarget * speed * Time.deltaTime);
}
Consider that Translate is a relative modifier. For this reason, when you specify the direction in the Translate itself, the movement becomes confused. Use Vector3.MoveTowards to solve the problem. If your game is 2D, you can also use Vector2 like below:
Vector2.MoveTowards(currentPosition, targetPosition, step);
Preferably you can fix this code like this and set the return value of MoveTowards equal to transform.Position.
void FixedUpdate () {
Vector3 difference = player.position - transform.position;
float rotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0.0f, 0.0f, rotationZ);
float speed = 1.5f;
// replace Translate to MoveTowards
transform.position = Vector3.MoveTowards(transform.position, player.position, Time.deltaTime * speed);
}

Rotating raycast2D back and forth with object

I made a circle and attach a lazer box on top of it.
The lazer will fire a raycast to its upper y axis (straight up). I also add a line renderer to view it.
I want the raycast to rotate 90 degrees back and forth. Sort of like its scanning everything on top. My problem is that its not working properly. It does rotate back and forth but If I move the x position of the lazer object, the raycast will rotate in a weird angle.
Script for lazer object
public LineRenderer lineRenderer;
public LayerMask layerMask;
public float laserSpeed;
Vector3 pointA;
Vector3 pointB;
Vector3 castPosition;
RaycastHit2D rayCast;
float time;
void Start()
{
pointA = transform.eulerAngles + new Vector3(0f, 0f, 90f);
pointB = transform.eulerAngles + new Vector3(0f, 0f, -90f);
}
void Update()
{
time = Mathf.PingPong(Time.time * laserSpeed, 1);
transform.eulerAngles = Vector3.Lerp(pointA, pointB, time);
castPosition = new Vector3(transform.position.x, transform.position.y, transform.position.z);
rayCast = Physics2D.Raycast(castPosition, transform.TransformDirection(Vector2.up), 10f, layerMask);
lineRenderer.SetPosition(0, castPosition);
lineRenderer.SetPosition(1, transform.TransformDirection(Vector2.up) * 10f);
}
Using eulerAngles for continuous animations is quite "dangerous". Unity stores the rotations as Quaternion and there are multiple ways of how to represent these in euler space!
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.
so you should rather go for Quaternion and do e.g.
And then you are using transform.TransformDirection(Vector2.up) which is a direction and pass it to your line renderer as a position.
What you want there is rather the position combined from
transform.position + transform.up
So together it should probably rather be
public LineRenderer lineRenderer;
public LayerMask layerMask;
public float laserSpeed;
private Quaternion originalRotation;
private Quaternion minRotation;
private Quaternion maxRotation;
void Start()
{
originalRotation = transform.rotation;
minRotation = originalRotation * Quaternion.Euler(0, 0, -90);
maxRotation = originalRotation * Quaternion.Euler(0, 0, 90);
}
void Update()
{
// Note that Vector3 is a "struct" -> there is no need to manually use "new Vector3(transform.position.x, ...)"
var startPosition = transform.position;
lineRenderer.SetPosition(0, startPosition);
var factor = Mathf.PingPong(Time.time * laserSpeed, 1);
// instead of the eulers rather use Quaternion
transform.rotation = Quaternion.Lerp(minRotation, maxRotation, factor);
// "transform.up" basically equals using "transform.TransformDirection(Vector3.up)"
var rayCast = Physics2D.Raycast(startPosition, transform.up, 10f, layerMask);
if(rayCast.collider)
{
// when you hit something actually use this hit position as the end point for the line
lineRenderer.SetPosition(1, rayCast.point);
}
else
{
// otherwise from the start position go 10 units in the up direction of your rotated object
lineRenderer.SetPosition(1, startPosition + transform.up * 10f);
}
}

Can't limit Object's Rotation Unity3D

I have an object in my scene that move forward and rotate with input.GetAxis, and I want to limit its X rotation between -45 and 45 degree. So I tried the Clamp method but the object can't rotate anymore! is there something wrong in my code?
float Speed = 10f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
transform.Rotate(-Input.GetAxis("Vertical") * 2f, 0, -Input.GetAxis("Horizontal"));
float rotationX = Mathf.Clamp(transform.rotation.x, -45.0f, 45.0f);
transform.localEulerAngles = new Vector3(rotationX, transform.localEulerAngles.y, transform.localEulerAngles.z);
}
private void FixedUpdate()
{
transform.position += transform.forward * Speed * Time.fixedDeltaTime;
}
Personally I wouldn't try setting localEulerAngles directly, I'd reset the rotation to Quaternion.Identity (no rotation) or to another reference transform's rotation and then use Transform.rotate rotationX degrees around the desired axis whenever the rotation changes
The main issue your having atm is transform.rotation is a Quaternion and not a vector3. Since Quaternions have a min/max value of -1/1 the vector3 X rotation can't ever be outside that range.
You probably wanted to do: float rotationX = Mathf.Clamp(transform.localEulerAngles.x, -45.0f, 45.0f);

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;

How to make a Game Object point towards the mouse in Unity? (C#)

I am creating a game involving a turret and it needs to "point" (that is, rotate) to the mouse. It's in 3-D environment, but at a bird's eye view. So for my purposes we are in a 2-D environment.
Here is my code:
using UnityEngine;
using System.Collections;
public class Turret : MonoBehaviour {
// Use this for initialization
void Start () {
}
int speed; float friction; float lerpSpeed ; private float xDeg ;
private float yDeg; private Quaternion fromRotation; private Quaternion toRotation;
void Update () {
xDeg -= Input.GetAxis ("Mouse X"); yDeg += Input.GetAxis ("Mouse Y");
fromRotation = transform.rotation;
toRotation = Quaternion.Euler(yDeg,xDeg,0);
transform.rotation = Quaternion.Lerp(fromRotation,toRotation,Time.deltaTime * lerpSpeed);
}
}
If you could tell me what I'm doing wrong or give me the correct code that would be great! Please note that I am using a C# script.
Input and rotation calculations are not right.
xDeg -= Input.GetAxis ("Mouse X"); yDeg += Input.GetAxis ("Mouse Y");
toRotation = Quaternion.Euler(yDeg,xDeg,0);
You are making a top down game. So I assume that you are trying to aim at where mouse points on a 2D plane, which is ground. You should get your input not based on mouse axes but taking account where your mouse cursor is.
That being said, you can use this method to achieve your goal:
public class CharacterInput : MonoBehaviour
{
public Transform CharacterTransform;
void Update()
{
var groundPlane = new Plane(Vector3.up, -CharacterTransform.position.y);
var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float hitDistance;
if (groundPlane.Raycast(mouseRay, out hitDistance))
{
var lookAtPosition = mouseRay.GetPoint(hitDistance);
CharacterTransform.LookAt(lookAtPosition, Vector3.up);
}
}
}
And to rotate it smoothly:
public class CharacterInput : MonoBehaviour
{
public Transform CharacterTransform;
public float RotationSmoothingCoef = 0.1f;
void Update()
{
var groundPlane = new Plane(Vector3.up, -CharacterTransform.position.y);
var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float hitDistance;
if (groundPlane.Raycast(mouseRay, out hitDistance))
{
var lookAtPosition = mouseRay.GetPoint(hitDistance);
var targetRotation = Quaternion.LookRotation(lookAtPosition - CharacterTransform.position, Vector3.up);
var rotation = Quaternion.Lerp(CharacterTransform.rotation, targetRotation, RotationSmoothingCoef);
CharacterTransform.rotation = rotation;
}
}
}
Better calculate smoothing in FixedUpdate to make it independent of frames per second. So it rotates at the same speed on every computer configuration:
public class CharacterInput : MonoBehaviour
{
public Transform CharacterTransform;
public float RotationSmoothingCoef = 0.01f;
private Quaternion targetRotation;
void Update()
{
var groundPlane = new Plane(Vector3.up, -CharacterTransform.position.y);
var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float hitDistance;
if (groundPlane.Raycast(mouseRay, out hitDistance))
{
var lookAtPosition = mouseRay.GetPoint(hitDistance);
targetRotation = Quaternion.LookRotation(lookAtPosition - CharacterTransform.position, Vector3.up);
}
}
void FixedUpdate()
{
var rotation = Quaternion.Lerp(CharacterTransform.rotation, targetRotation, RotationSmoothingCoef);
CharacterTransform.rotation = rotation;
}
}
I think this is a common mistake for a Unity beginner (as I had it wrong the first time as well).
As you probably know by now, the Update() method is called every new frame.
So, every new frame (in your code) you calculate where the mouse is, how to rotate and call Lerp.
What you probably miss is how Lerp works and that is interpolating the motion by making one step every frame , i.e every time Lerp is called it rotates (in your case) by some interval. Your interval is Time.deltaTime * lerpSpeed which changes every frame since Time.deltaTime is the time between 2 consecutive frames.
So to make Lerp work properly (== smooth interpolation) you must call it with the same start and end position and complete the interpolation between them (call Lerp from 0 to 1 with as many intervals as you wish).
What I suggest you do is move this code:
xDeg -= Input.GetAxis ("Mouse X"); yDeg += Input.GetAxis ("Mouse Y");
fromRotation = transform.rotation;
toRotation = Quaternion.Euler(yDeg,xDeg,0);
to a different place (one which Update has access to these variables) and:
Set toRotation everytime it should move (xDeg can be computed along side)
Remove fromRotation and instead call Lerp like this:
Lerp(transform.rotation, toRotation,Time.deltaTime * lerpSpeed)

Categories