Rotate Object around point and move it along sine function - unity3d

First off: I am very new to Unity, as in VERY new.
I want to do the following: I want to rotate a cube around a stationary point (in my case a camera) with a radius that is adjustable in the inspector. The cube should always have its Z-axis oriented towards the camera's position. While the cube is orbiting around the camera, it should additionally follow a sine function to move up and down with a magnitude of 2.
I have some working code, the only problem is an increase in distance over time. The longer the runtime, the higher the distance between the cube and the camera.
Here is what I currently have:
void Awake()
{
cameraPosition = GameObject.FindGameObjectWithTag("MainCamera").transform;
transform.position = new Vector3(x: transform.position.x,
y: transform.position.y,
z: cameraPosition.position.z + radius);
movement = transform.position;
}
I instantiate some variables in the Awake()-method and set the cube's position to where it should be (do you instantiate in Awake()?). I'll use the Vector3 movement later in my code for the "swinging" of the cube.
void Update()
{
transform.LookAt(cameraPosition);
transform.RotateAround(cameraPosition.position, cameraPosition.transform.up, 30 * Time.deltaTime * rotationSpeed);
MoveAndRotate();
}
Here I set the orientation of the cube's z-axis and rotate it around the camera. 30 is just a constant i am using for tests.
void MoveAndRotate()
{
movement += transform.right * Time.deltaTime * movementSpeed;
transform.position = movement + Vector3.up * Mathf.Sin(Time.time * frequency) * magnitude;
}
To be quite frank, I do not understand this bit of code completely. I do however understand that this includes a rotation as it moves the cube along it's x-axis as well as along the world's y-axis. I have yet to get into Vector and matrices, so if you could share your knowledge on that topic as well I'd be grateful for that.

It seems like I have found the solution for my problem, and it is an easy one at that.
First of all we need the initial position of our cube because we need to have access to its original y-coordinate to account for offsets.
So in Awake(), instead of
movement = transform.position;
We simply change it to
initialPosition = transform.position;
To have more readable code.
Next, we change our MoveAndRotate()-method to only be a single line long.
void MoveAndRotate()
{
transform.position = new Vector3(transform.position.x,
Mathf.Sin(Time.time * frequency) * magnitude + initialPosition.y,
transform.position.z);
}
What exactly does that line then? It sets the position of our cube to a new Vector3. This Vector consists of
its current x-value
our newly calculated y-value (our height, if you want to say so) + the offset from our original position
its current z value
With this, the cube will only bop up and down with distancing itself from the camera.
I have also found the reason for the increase in distance: My method of movement does not describe a sphere (which would keep the distance the same no matter how you rotate the cube) but rather a plane. Of course, moving the cube along a plane will automatically increase the distance for some points of the movement.

For instantiating variables in Awake it should be fine, but you could also do it in the Start(){} Method that Unity provides if you wanted to.
For the main problem itself I'm guessing that calling this function every frame is the Problem, because you add on to the position.
movement += transform.right * Time.deltaTime * movementSpeed;
Would be nice if you could try to replace it with this code and see if it helped.
movement = transform.right * Time.deltaTime * movementSpeed;

Related

Unity 3D. How to find out where the movement of an object is directed relative to its rotation?

I need to make a 3d top down character controller and animate it.
To begin with, I made a code for the character's movement relative to the camera. And this code works, the character walks and turns well.
Vector3 inputDirection = new Vector3(_input.GetAxis.x, 0, _input.GetAxis.y);
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + _camera.rotation.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(transformPlayer.eulerAngles.y, targetAngle, ref _turnVelocity, TurnSmoothTime);
transformPlayer.rotation = Quaternion.Euler(0f, angle, 0f);
Vector3 moveDirection = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
moveDirectionNormalized = moveDirection.normalized;
_characterPlayer.Move(moveDirectionNormalized * SpeedMultiplier * Time.deltaTime);
The character model is a child object of _characterPlayer.
And if, when the character moves, an enemy gets into his radius, then the player's model will turn towards the enemy, and the character himself will go further along the moveDirectionNormalized.
For turns, I wrote the following code:
if (NearestEnemyAtAttackRadius != null)
{
Quaternion rotation = Quaternion.LookRotation(NearestEnemyAtAttackRadius.transform.position - MeshCharacter.position);
MeshCharacter.rotation = Quaternion.RotateTowards(MeshCharacter.rotation, rotation, 800f * Time.deltaTime);
}
else
{
MeshCharacter.localRotation = Quaternion.RotateTowards(MeshCharacter.localRotation, Quaternion.Euler(Vector3.zero), 800f * Time.deltaTime);
}
Now if there is an enemy nearby, my character swings towards the enemy, and if there is no enemy or the enemy has left the radius, then the initial state is returned.
And here my problem begins. I wanted to add an animation of the movement. The character has 4 animations: movement with a tilt to the right, left, forward and backward.
In the Animator, I made a Blend Tree (2d simple direction) with 4 animations. Added 2 Float values MoveDirectionX, MoveDirectionY correctly configured for all motion pos x and pos y.
And if I am in MoveDirectionX, MoveDirectionY will feed the vector moveDirectionNormalized, then it does not work correctly. The values of moveDirectionNormalized do not depend on the repetition of my model in any way, and if my character moves away from the enemy, he looks at the enemy but in fact goes backwards, the animation should turn on as he leans back. But now moveDirectionNormalized does not depend on the repetition of my model, and the character tilt animations randomly switch depending on moveDirectionNormalized. As I understand it, it is necessary to create a new vector that will take into account the rotation of my model, but I do not understand how to do this.
As a result, I want to get a vector where x - shows where the character is moving to the left or right (from -1 to 1) and y - shows the movement forward, backward (also from -1 to 1), and this vector should take into account the rotation of the player. At the moment, in the character model, only y changes in rotation. moveDirectionNormalized returns a value that indicates where the movement is directed relative to world space. And I need to make a vector that will return directions relative to the player's rotation. That is, if the character's gaze is directed at the enemy, and the character himself retreats from him, I would like to receive a vector (0, -1). And if the player's gaze is directed at the enemy and he goes to the right (I remind you that the player is constantly looking towards the enemy, that is, you can walk around the enemy and the character will constantly turn in his direction), then I would like to get a vector (1,0)
I am not 100% sure I understand, but if I do, this should help:
You can use Quaternion.Inverse() on the rotation of your player, and multiply that by the vector.
Vector3 yourVector;
Quaternion inverse = Quaternion.Inverse([player rotation]);
return inverse * yourVector;
Multiplying a quaternion by a vector rotates it with the quaternion, so if you invert it it will cancel out the rotation of the player.

Unity: GameObject bug in rotation axis

In the GIF above I am moving the plane with the mouse up and down. Watch how when I rotate the plane on its Z axis the plane still moves up and down even if the plane is side ways now. That is my bug. Why is this happening? What is wrong with my code? I think I am using the global axis and not the gameobjects axis. How do I fix this please.
x += Input.GetAxis("Mouse X") * xMouseSpeed * 0.02f;
y -= Input.GetAxis("Mouse Y") * yMouseSpeed * 0.02f;
if (Input.GetKey(KeyCode.Q))
{
rotZ += Time.deltaTime * 50f;
}
if (Input.GetKey(KeyCode.E))
{
rotZ -= Time.deltaTime * 50f;
}
Quaternion rotation = Quaternion.Euler(y, x, rotZ);
planeBodyToMove.localRotation = rotation;
What happens is that the localRotation is just the rotation in relation to the parent object.
The rotation of the transform relative to the transform rotation of the parent.
Or in other words: The localRotation is not local to the object itself but rather a rotation in the local space of the parent (or Unity world space if there is no parent). It does not mean that you rotate around your local Y axis necessarily. It applies the rotations in the parent's local space in the order Z, X, Y .. that is why sometimes you actually don't note the difference until you rotate Y!
As you can see here the same thing happens also without your code by just doing the rotation via the Inspector
Or to demonstrate it a bit better lets see what happens if we rotate it the way you actually want it to rotate:
as you can see as soon as one axis already is rotated, changing another axis now changes all three rotation values.
Solution
So how to solve this?
Usually I'm to lazy to do Quaternion calculations myself → I let the Transform do this for me ^^
Instead of storing the expected rotation in fields and apply it everytime by converting it into a Quaternion you can directly use Transform.Rotate which rotates the object actually using its own local axis:
// Just a question of taste but I would prefer to have
// values that belong together in one single struct instead of multiple fields
public Vector2 mouseSensitivity = Vector3.one;
private void Update()
{
var xDif = Input.GetAxis("Mouse X") * mouseSensitivity.x;
var yDif = - Input.GetAxis("Mouse Y") * mouseSensitivity.y;
var zDif = 0f;
if (Input.GetKey(KeyCode.Q))
{
zDif= Time.deltaTime * 50f;
}
else if (Input.GetKey(KeyCode.E))
{
zDif= -Time.deltaTime * 50f;
}
// Wihout the optional space parameter uses local space by default
planeBodyToMove.Rotate(new Vector3(xDif, yDif, zDif));
}
As you can see now each rotation is actually applied in the plane's local axis:

1st person space ship pitch and yaw isue

I've been trying for some time now with different tutorials to get a nice firstperson (cockpit view) spaceship controll system. I tried using mouse only, keyboard only and combinations but I keep encountering the same problem with all tutorials. even this simple line of code does it:
transform.position += transform.forward * Time.deltaTime * 90f;
transform.Rotate( Input.GetAxis("Vertical"), Input.GetAxis("Horizontal"), 0.0f);
The problem I keep getting is that if I pitch its ok. it I yaw its ok. But If I do both (so I go diagonaly) it also rotates my ship on the Z axis and messes up my orientation. I tried locking the z rotation in the rigidbody but that does'nt help either. I tried making code myself alternating with apply torque and simply rotating, followed some tutorials including this one: Tutorial but keep getting the rotation problem.
What I want to make is a game that controlls like the old game Helbender
Does anyone of you know of a way that I can get spaceship controlls to work?
----EDIT-----
Got a bit further now. It doenst turn on my z axis anymore becouse I keep setting it to 0. Only problem now is that if I try to make a looping the ship flips around instead of nicely looping.
if (Input.GetKey("up"))
{
transform.Rotate(transform.right * -ShipPanSpeed * Time.deltaTime, Space.World);
}
if (Input.GetKey("down"))
{
transform.Rotate(transform.right * ShipPanSpeed * Time.deltaTime, Space.World);
}
if (Input.GetKey("left"))
{
transform.Rotate(transform.up * -ShipPanSpeed * Time.deltaTime,Space.World);
}
if (Input.GetKey("right"))
{
transform.Rotate(transform.up * ShipPanSpeed * Time.deltaTime,Space.World);
}
float z = transform.eulerAngles.z;
transform.Rotate(0, 0, -z);
You could always try to make one parent object for the Controls and then a child object (the spaceshit) that you can rotate for the pivot but not attach any movement beside rotation.
What i mean is that you can rotate the parent on the Y axis to make it rotate and move the transform forward or backward at the same time. If you want to pivot up or down you can transform forward at the sametime you transform up/down multiplied with the axis you want to pivot with
Example:
private void Update(){
//PARENT--------------------------------------------
// MOVING FORWARD AND BACKWARD
transform.position += transform.forward * Input.GetAxis("Vertical") * speed * Time.deltaTime();
// MOVING RIGHT AND LEFT
transform.position += transform.right * Input.GetAxis("Horizontal") * speed * Time.deltaTime();
//PIVOT UP AND DOWN
transform.position += transform.up * -Input.GetAxis("Mouse Y") * speed * Time.deltaTime();
//ROTATE AROUND THE Y AXIS
tranform.Rotate(0f, Input.GetAxis("Horizontal") * rotationspeed, 0f);
}
For the spaceship (child) for the pivot i would recommend you to make a emptyobject and set the chip to lookAt that object based on the input axis you use to move up/down and forward/backward.
Hope this helped or at least gave you a idea of how to make it :)

Eliminating camera jitters caused by repositioning camera within defined bounds

This is the image of what I'm trying to achieve, in a 3D space. I want the object to follow my finger within the green zone, but stay on the edge of the green zone if I move my finger outside of it. I have achieved this with the code below, but when moving my finger around the red zone a lot of jitters and clipping occurs as the object keeps snapping back within it's bounds. The jitters I'm seeing are caused when holding my finger in the red zone out of the players circle bounds. Instead of being "stuck" in the bounds the player is trying to continue and then being positioned back within the bounds, causing jitters. I'm looking for a way to limit the movement of the player within the bounds without having to reset it's position. My main camera is attached to the moving object so it's important that I eliminate the jitters. How can I smooth this out?
public class Player : MonoBehaviour
{
public Camera movementCam;
readonly float radius = 0.45f;
readonly float speed = 3f;
Ray firstTouchPos;
Vector2 playerPos;
[SerializeField] Vector3 targetPosition;
readonly float followDelay = 20f;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
firstTouchPos = movementCam.ScreenPointToRay(Input.mousePosition);
playerPos = transform.position;
}
if (Input.GetMouseButton(0))
{
Ray currentTouchPos = movementCam.ScreenPointToRay(Input.mousePosition);
Vector2 direction = currentTouchPos.origin - firstTouchPos.origin;
float distance = Vector3.Distance(transform.position, Vector3.zero);
targetPosition = distance >= radius ? (Vector3) . (direction.normalized * radius) : (Vector3)(playerPos + direction * speed);
}
transform.position = Vector3.Lerp(transform.position, targetPosition, followDelay);
}
}
Your issue is incorrectly clamping, a simple fix would be:
if (Input.GetMouseButton(0))
{
Ray currentTouchPos = movementCam.ScreenPointToRay(Input.mousePosition);
Vector2 direction = currentTouchPos.origin - firstTouchPos.origin;
float distance = Vector3.Distance(transform.position, Vector3.zero);
targetPosition = (Vector3)(playerPos + direction * speed);
if (targetPosition.sqrMagnitude > radius * radius) //if our calculated position is greater than the radius...
targetPosition = targetPosition.normalized * radius; //set our calculated position to be exactly on the radius.
}
The jitter was caused by your object leaving the radius one frame, and on the next frame would be clamped back to the radius, only for it to attempt to move outside the radius again the next frame.
This way removes the ternary operator, which means it will behave consistently across frames, rather than switching between clamp and movement each frame.
Here are some additional pieces of advice for this issue, once you fix the above problem:
You should multiply speed and followDelay by time.deltaTime in order to smooth them across frames correctly.
You should probably apply your camera motion during LateUpdate() instead of Update(), LateUpdate() happens after all your updates happen, what can happen is during Update() objects can move around before and after your camera code is called, causing it to behave slightly inconsistently from frame to frame, applying the motion in LateUpdate() to the camera means your camera moves only when all your objects have 'settled' into place after their update, making it behave more consistently.
Additionally you're technically using Lerp() wrong here, it shouldn't cause jitter but it's not exactly how lerp should be used. Are you sure you don't want Vector3.MoveTowards() instead?

How to Calculate Target Destination

I'm having a bit of trouble figuring this one out. What I'm trying to achieve is a sort of tackling motion. The player lunges at the target from a distance.
The diagram shows the set up. The blue diamond is the player and the red thing is the target. The purple box is the renderer bounds of the targets SkinnedMeshRenderer. I'm using renderer bounds because some target's mesh are much larger than other. Currently, the player is shooting to the orange star...which is unrealistic. I want him to, no matter what way the target is facing, always target the closest point of the target relative to his position...in the diagram's case that would be the brown star. Here's the code I've been using...
public IEnumerator Blitz()
{
rigidbody.velocity = Vector3.zero; //ZERO OUT THE RIGIDBODY VELOCITY TO GET READY FOR THE BLITZ
SkinnedMeshRenderer image = target.GetComponentInChildren<SkinnedMeshRenderer>();
Vector3 position = image.renderer.bounds.center + image.renderer.bounds.extents;
position.y = target.transform.position.y;
while(Vector3.Distance(transform.position, position) > 0.5f)
{
transform.position = Vector3.Lerp(transform.position, position, Time.deltaTime * 10);
yield return null;
}
Results(); //IRRELEVANT TO THIS PROBLEM. THIS CALCULATES DAMAGE.
Blitz.Stop(); //THE PARTICLE EFFECT ASSOCIATED WITH THE BLITZ.
GetComponent<Animator>().SetBool(moveName, false); //TRANSITIONS OUT OF THE BLITZ ANIMATION
GetComponent<Input>().NotAttacking(); //LET'S THE INPUT SCRIPT KNOW THE PLAYER CAN HAVE CONTROL BACK.
}
//Get the derection to tarvel in and normalize it to length of 1
Vector3 Direction = (Target - transform.position).normalized
With Direction, you can do many things. For example, you can multiply the direction by your speed and add that to your position.
transform.position += Direction * MoveSpeed;
If you want to get to the closest point, you can use the handy Collider.ClosestPointOnBounds method.
Target = TargetObject.GetComponent<Collider>().ClosestPointOnBounds(transform.position)
and plug the Target into the code used to get the direction.
Alternatively, you can use Vector3.Lerp without getting a direction since it's just interpolating.
transform.position = Vector3.Lerp(Target,transform.position,time.DeltaTime);
For stopping at the target point, you can use the arrival behavior.
//Declare the distance to start slowing down
float ClosingDistance = Speed * 2;
//Get the distance to the target
float Distance = (Target - transform.position).magnitude;
//Check if the player needs to slow down
if (Distance < ClosingDistance)
{
//If you're closer than the ClosingDistance, move slower
transform.position += Direction * (MoveSpeed * Distance / ClosingDistance);
}
else{
//If not, move at normal speed
transform.position += Directino * MoveSpeed;
}