Camera Follow - Consider storing the value in a temporary variable - c#

Can't get this line to work without error, it needs a temp variable. Without it the camera is flat on the ground
transform.position.y = currentHeight;
error CS1612: Cannot modify a value type return value of `UnityEngine.Transform.position'. Consider storing the value in a temporary variable
Part of the code is not mine, just trying to convert from java script to c-sharp and implement in my current camera script
// Calculate the current rotation angles
var wantedRotationAngle = target.eulerAngles.y;
var wantedHeight = target.position.y + height;
var currentRotationAngle = transform.eulerAngles.y;
var currentHeight = transform.position.y;
// Damp the rotation around the y-axis
currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);
// Damp the height
currentHeight = Mathf.Lerp (currentHeight, wantedHeight, heightDamping * Time.deltaTime);
// Convert the angle into a rotation
var currentRotation = Quaternion.Euler (0, currentRotationAngle, 0);
// Set the position of the camera on the x-z plane to:
// distance meters behind the target
transform.position = target.position;
transform.position -= currentRotation * Vector3.forward * distance;
// Set the height of the camera
transform.position.y = currentHeight;
// Always look at the target
transform.LookAt (target);

I do not believe you can set the seperate axis' of the vector like that, one way you'd do it is
// Calculate the current rotation angles
var wantedRotationAngle = target.eulerAngles.y;
var wantedHeight = target.position.y + height;
var currentRotationAngle = transform.eulerAngles.y;
var currentHeight = transform.position.y;
// Damp the rotation around the y-axis
currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime);
// Damp the height
currentHeight = Mathf.Lerp (currentHeight, wantedHeight, heightDamping * Time.deltaTime);
// Convert the angle into a rotation
var currentRotation = Quaternion.Euler (0, currentRotationAngle, 0);
// Set the position of the camera on the x-z plane to:
// distance meters behind the target
transform.position = target.position;
transform.position -= currentRotation * Vector3.forward * distance;
// Set the height of the camera
Vector3 temp = transform.position; //Get the current vector the transform is at
temp.y = currentHeight; //assign the new value to the Y axis
transform.position = temp; //replace the existing vector with the new one we just modified.
// Always look at the target
transform.LookAt (target);
so we are replacing your transform.position.y = currentHeight; line with
Vector3 temp = transform.position; //Get the current vector the transform is at
temp.y = currentHeight; //assign the new value to the Y axis
transform.position = temp; //replace the existing vector with the new one we just modified.
This is simply due to the way C# is handling structs and properties, to my belief.

You cannot modify transform.position x and y directly, instead of that you should create a Vector3 variable with desired values and then assign your transform.position to it. For example:
Vector3 newPosition = new Vector3(0, currentHeight, 0);
transform.position = newPosition;

Related

How to use LookRotation to rotate an object based on hand positions?

I'm creating an interaction system for a VR game and I'm struggling with two hand interactions. I'm using the Quaternion.LookRotation() method to generate the rotation of the grabbed object based on the positions and rotations of the hands. The forward part is pretty simple:
Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;
The "up" part is what I have difficulty with. Initially I tried using the average up direction of both hands:
Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;
There is an issue with this approach: the hand's up vector might get aligned with the fwd vector, which causes the object to flip over when it goes over it. Here is a simple illustration of the problem:
The light green arrows represent the up direction of the hands, while the dark green is the calculated direction used as an argument for the LookRotation() method.
The obvious solution seems to be to pick a different fixed vector instead of "up", one which won't be so easily aligned with the fwd vector. In the example it could be a vector aligned with the fingers. But keep in mind that there are no restrictions on initial hand rotation so no matter which vector you choose the hands can always happen to be rotated so that the vectors align. And even if you pick the an optimal vector dynamically (one that is perpendicular to fwd), it's still at best 90 degrees from aligning with fwd.
To solve this I tried restricting the direction to the values which don't cause problems but this caused another issues (I had difficulties with determining which values are ok and which should be discarded). I feel like I'm doing something wrong here, is there any better solution to this problem?
You can calculate the delta rotations of the hands and apply it to the "base up" of the object (the new up if we only take into account the change in position of hands...which will of course be orthogonal to the axis of the object). Then determine the change in angle that results in that up being rotated with each hand. Average those angles out, then apply those angles with the hand-hand axis using Quaternion.AngleAxis to the "base up" from earlier. Then you have your forward and up for Quaternion.LookRotation).
Below is an example of how you can use this, including VR hand noise simulation. To see the test, create a new scene in unity and attach this to the camera and it will build the scene on play start. There is a grip/release gui that will appear in Play view. You can adjust the hand rotation in Scene view
Vector3 leftHandPosCurrent;
Vector3 rightHandPosCurrent;
Vector3 worldAxisPrev;
Quaternion leftHandRotPrev;
Quaternion leftHandRotCurrent;
Quaternion rightHandRotPrev;
Quaternion rightHandRotCurrent;
bool isGripping;
bool firstFrameGripping;
Rigidbody grippedRB;
Transform leftHand;
Transform rightHand;
Quaternion targetRot;
/*
* On subsequent frames of gripping, calculate deltas in positions and
* rotations, average out the hand's effects, then apply them to the gripped
* object
*/
void HandleGrippedRot()
{
Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;
if (!firstFrameGripping)
{
Vector3 prevUp = targetRot * Vector3.up;
// we haven't moved the transform based on the hands yet, so
// find the new up would be ignoring hand rotations
Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
worldAxisCurrent) * prevUp;
float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
leftHandRotCurrent, worldAxisCurrent);
float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
rightHandRotCurrent, worldAxisCurrent);
float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;
newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;
targetRot = Quaternion.LookRotation(worldAxisCurrent,
newUp);
}
else
{
firstFrameGripping = false;
}
leftHandRotPrev = leftHandRotCurrent;
rightHandRotPrev = rightHandRotCurrent;
worldAxisPrev = worldAxisCurrent;
}
/*
* Given the "up" of the object without taking hand rotations into account
* and the axis, determine how a hand's delta rotation affects that up
* around the axis and return the angle of that rotation
*/
float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
Vector3 axis)
{
Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
adjUp = Vector3.ProjectOnPlane(adjUp, axis);
return Vector3.SignedAngle(baseUp, adjUp, axis);
}
void Update()
{
AddVRNoise(leftHand);
AddVRNoise(rightHand);
leftHandPosCurrent = leftHand.position;
rightHandPosCurrent = rightHand.position;
leftHandRotCurrent = leftHand.rotation;
rightHandRotCurrent = rightHand.rotation;
if (isGripping)
{
HandleGrippedRot();
}
}
void StartGrip()
{
if (isGripping) return;
isGripping = true;
firstFrameGripping = true;
// grippedTransform is set accordingly at some point
}
void EndGrip()
{
if (!isGripping) return;
isGripping = false;
}
/*
* example of using targetRot to move rb
*/
private void FixedUpdate()
{
if (!isGripping) return;
Quaternion delta = targetRot
* Quaternion.Inverse(grippedRB.transform.rotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
// convert to shortest angle form
if (angle > 180f)
{
axis = -axis; angle = 360f - angle;
}
grippedRB.angularVelocity = angle * 0.25f * axis;
}
/*
* just for testing purposes
*/
void Start()
{
leftHand = CreateHand(true);
leftHand.position = Vector3.left;
rightHand = CreateHand(false);
rightHand.position = Vector3.right;
CreateArrow();
}
/*
* just for testing purposes
*/
void AddVRNoise(Transform hand)
{
Quaternion noise = Random.rotation;
noise.ToAngleAxis(out float angle, out Vector3 axis);
angle = 100f * Time.deltaTime;
noise = Quaternion.AngleAxis(angle, axis);
Quaternion noisyRot = hand.rotation * noise;
hand.rotation = noisyRot;
}
/*
* just for testing purposes
*/
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
{
StartGrip();
}
if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
{
EndGrip();
}
}
/*
* just for testing purposes
*/
Transform CreateHand(bool isLeft)
{
string handName = isLeft ? "Left" : "Right";
GameObject hand = new GameObject($"{handName}hand");
GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
palm.transform.SetParent(hand.transform);
GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
thumb.transform.SetParent(hand.transform);
thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
0f, -.31f);
return hand.transform;
}
/*
* just for testing purposes
*/
void CreateArrow()
{
GameObject arrow = new GameObject();
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.localScale = new Vector3(1f, 1f, 5f);
body.transform.SetParent(arrow.transform);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
head.transform.SetParent(arrow.transform);
head.transform.localEulerAngles = Vector3.up * 45f;
head.transform.localPosition = new Vector3(0f, 0f, 2.5f);
grippedRB = arrow.AddComponent<Rigidbody>();
grippedRB.useGravity = false;
arrow.transform.position = 2f * Vector3.up;
}

Mathf.Clamp inside of a sphere radius Unity

I have a simple joystick move script. I restricted its movement on x and z axis with a Mathf.Clamp. What I am trying to achive is to use that function inside a sphere radius.
I need the gameobject movement restrict its movement if the gameobjects position is outside of the radius.
void UpdateMoveJoystick()
{
horizontale = moveJoystick.Horizontal;
float ver = moveJoystick.Vertical;
Vector2 convertedXY = ConvertWithCamera(Camera.main.transform.position, horizontale, ver);
Vector3 direction = new Vector3(convertedXY.x, 0, convertedXY.y).normalized;
transform.Translate(direction * 0.02f * speed, Space.World);
Quaternion targetRotation = Quaternion.LookRotation(direction);
RestrictMovement();
}
void RestrictMovement()
{
var position1 = transformPos.transform.position.x;
var position2 = transformPos.transform.position.x;
var position3 = transformPos.transform.position.z;
var position4 = transformPos.transform.position.z;
float xMovementClamp = Mathf.Clamp(transform.position.x, position1 += leftBoundry, position2 += rightBoundry);
float zMovementClamp = Mathf.Clamp(transform.position.z, position3 += DownBoundry, position4 += UpBoundry);
transform.position = new Vector3(xMovementClamp, transform.position.y, zMovementClamp);
}
Anyway Thanks for help :)
The function you are looking for is Vector2.ClampMagnitude. This method takes a Vector2 and allows you to ensure that the magnitude (or vector length) is no longer than the desired length (or the radius of the circle).
With this in mind, the RestrictMovement method from your code would look something like this:
public float boundryRadius = 20.0f;
void RestrictMovement()
{
var positionXY = new Vector2(transform.position.x, transform.position.y);
positionXY = Vector2.ClampMagnitude(positionXY, boundryRadius);
transform.position = new Vector3(positionXY.x, positionXY.y, transform.position.z);
}
This would clamp the GameObject in the boundryRadius specified around the world position of 0, 0, 0. Additional maths can be used to change the centre of the circular boundary.

How to rotate a quanternion and calculate direction vector in Unity (ECS)

I started to work with ECS and I have a Rotation component (quaternion) and a Translation component (float3). I am writing a System that will handle user input and rotate and/or move forward the player.
The problem is I don't know how to do it. The existing API is lacking in my opinion.
// Before it was easy
if (Input.GetKey(KeyCode.W))
var newPostion = new Vector3(player.Position.x, player.Position.y, 0.3f)
+ _playerTransform.up * Time.deltaTime * PlayerSpeed;
if (Input.GetKey(KeyCode.A))
_playerTransform.Rotate(new Vector3(0, 0, PlayerRotationFactor));
// now it is not - that's part of the system's code
Entities.With(query).ForEach((Entity entity, ref Translation translation, ref Rotation rotation) =>
{
float3 tmpTranslation = translation.Value;
quaternion tmpRotation = rotation.Value;
if (Input.GetKey(KeyCode.W))
// how to retrieve forward vector from the quaternion
tmpTranslation.y += Time.deltaTime * PlayerSpeed;
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
// this always set the same angle
tmpRotation = quaternion.RotateZ(PlayerRotationFactor);
// ECS need this
var newTranslation = new Translation { Value = tmpTranslation };
PostUpdateCommands.SetComponent(entity, newTranslation);
var newRotation = new Rotation { Value = tmpRotation };
PostUpdateCommands.SetComponent(entity, newRotation);
});
To get the vector from a quaternion you can simply multiply it with a forward vector, example:
if (Input.GetKey(KeyCode.W))
{
Vector forward = tmpRotation * float3(0,0,1);
tmpTranslation += forward * Time.deltaTime * PlayerSpeed;
}
For the rotation part, as you say // this always set the same angle. Looks like you're creating a new angle of static degree. Try change to multiply with the current angle, as with quaternions you multiply to combine. Example:
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
tmpRotation = mul(tmpRotation, quaternion.RotateZ(PlayerRotationFactor));
}
Sidenote: I'm also a bit new to Unity ECS, and I've not tested above codes.
// correct forward
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow))
{
float3 forwardVector = math.mul(rotation.Value, new float3(0, 1, 0));
newTranslation += forwardVector * Time.deltaTime * PlayerSpeed;
}
// correct rotation
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
quaternion newRotation = math.mul(rotation.Value, quaternion.RotateZ(PlayerRotationFactor * Time.deltaTime));
// insert new value to the component
PostUpdateCommands.SetComponent(entity, new Rotation { Value = newRotation });
}
// thx akaJag
NOTICE to other viewers.
That code does not use the delegate variable references as they should be.
Setting the component in PostUpdateCommands (or EntityManager, or any other CommandBufferSystem) is utterly unnecessary as the translation / rotation is directly present - in the delegate function - to be manipulated by ref.
AKA:
rotation.Value = newRotation
translation.Value = newTranslation
NOT
EntityManager(CommandBuffer).SetComponentData(entity, new Rotation{ Value = newRotation})

Rotate object with RotateAround and give it a limit

I have an object in my scene that rotate (RotateAround) by the mouse swipe. and I want to give the object some rotation limits, for example -45 and 45 degree for the X axis, so when its rotation become 45 degree it can't go beyond it.
So I tried Mathf.Clamp method in my script as you see bellow, and its working fine for the Y axis, the object rotate around his X axis and didn't go beyond the Y limits. but in the X axis, when the object's Y rotation reach O it change immediately to 30 degree with a weird rotation! Can you please tell what's wrong in my code?
Rotation scripts:
float sensitivity = 10f;
Vector3 firstPressPos;
Vector3 secondPressPos;
float minRotationX = 45;
float maxRotationX = 100;
float minRotationY = 30;
float maxRotationY = 30;
void Update () {
if (Input.GetMouseButtonDown(0))
{
//save began touch 2d point
firstPressPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y);
}
if (Input.GetMouseButton(0))
{
//save ended touch 2d point
secondPressPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y);
if (firstPressPos != secondPressPos)
{
float RotX = Input.GetAxis("Mouse X") * sensitivity * Time.deltaTime;
float RotY = Input.GetAxis("Mouse Y") * sensitivity * Time.deltaTime;
transform.RotateAround(Vector3.up, RotX);
transform.RotateAround(Vector3.right, -RotY);
Vector3 angles = transform.eulerAngles;
angles.x = Mathf.Clamp(angles.x, minRotationX, maxRotationX);
angles.y = Mathf.Clamp(angles.y, -minRotationY, maxRotationY);
angles.z = 0;
transform.eulerAngles = angles;
}
}
}
In the editor, rotation values are between -180 and 180, but in transform.eulerAngles they are actually between 0 and 360.
So you need to adjust the value of your angle before clamping it.
if(angles.y > 180)
{
angles.y -= 180f;
}
angles.y = Mathf.Clamp(angles.Y, minY, maxY);

clamping a vertical rotation in Unity

I started a FPS project. I have a capsule as a Player, an empty GO as the head and the camera is the child object of the head.
On the vertical Axis, I clamp my y-rotation to -70 (min) and 70 (max). Surprisingly the value does not get clamped.
[SerializeField]
private Transform playerHead;
float inputHorizontal;
float inputVertical;
float sensitivity = 50;
float yMin = -70;
float yMax = 70;
Vector2 direction; // movement direction
Transform playerTransform;
void Start()
{
playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
}
private void Update()
{
inputHorizontal = Input.GetAxisRaw("Mouse X");
inputVertical = -Input.GetAxisRaw("Mouse Y"); // Inverted Input!
direction = new Vector2(
inputHorizontal * sensitivity * Time.deltaTime,
Mathf.Clamp(inputVertical * sensitivity * Time.deltaTime, yMin, yMax)); // The horizontal movement and the clamped vertical movement
playerTransform.Rotate(0, direction.x, 0);
playerHead.Rotate(direction.y, 0, 0);
}
The value
direction.y
gets clamped but I can still turn around my head by 360 degrees..
You are clamping your delta rotation - not your actual rotation.
Said in another way, direction is not the final rotation, it is the change in rotation. What you are effectively doing, is limiting the rotation to 70 degrees per frame.
You probably want to limit the actual rotation of playerHead, for example by adding the following lines to the end of update:
Vector3 playerHeadEulerAngles = playerHead.rotation.eulerAngles;
playerHeadEulerAngles.y = Mathf.Clamp(playerHeadEulerAngles.y, yMin, yMax);
playerHead.rotation = Quaternion.Euler(playerHeadEulerAngles);
There is also no reason to create a direction vector, when you are using each component separately anyway.
Clamp the final Y direction value not the current mouse movement -
[SerializeField]
private Transform playerHead;
float inputHorizontal;
float inputVertical;
float sensitivity = 50;
float yMin = -70;
float yMax = 70;
Vector2 direction; // movement direction
float currYDir = 0,prevYDir = 0;
Transform playerTransform;
void Start()
{
playerTransform = GameObject.FindGameObjectWithTag("Player").transform;
}
private void Update()
{
inputHorizontal = Input.GetAxisRaw("Mouse X");
inputVertical = -Input.GetAxisRaw("Mouse Y"); // Inverted Input!
direction = new Vector2(
inputHorizontal * sensitivity * Time.deltaTime,
inputVertical * sensitivity * Time.deltaTime); // The horizontal movement and the clamped vertical movement
playerTransform.Rotate(0, direction.x, 0);
currYDir = prevYDir + direction.y;
currYDir = Mathf.Clamp(currYDir, yMin, yMax);
playerHead.Rotate(currYDir-prevYDir, 0, 0);
prevYDir = currYDir;
}

Categories