In unity how to acces transform rotation in script - c#

I'm making a bouncing and spinning box game. For each spin score will increase.I wrote a code about that but doesn't work. I searched internet but couldn't find answer.
GameObject thePlayer = GameObject.Find("Player");
PlayerEverything player1 = thePlayer.GetComponent<PlayerEverything>();
if (!player1.isGrounded)
{
if(thePlayer.transform.localRotation.z == 0)
{
scorePoint++;
scoreCombo++;
}
if(thePlayer.transform.localRotation.z == 90)
{
scorePoint++;
scoreCombo++;
}
if(thePlayer.transform.localRotation.z == 180)
{
scorePoint++;
scoreCombo++;
}
if(thePlayer.transform.localRotation.z == -180)
{
scorePoint++;
scoreCombo++;
}
if(thePlayer.transform.localRotation.z == -90)
{
scorePoint++;
scoreCombo++;
}
scoreCombo = scorePoint;
score += scorePoint;
}
this is my edited code
if (!player1.isGrounded)
{
currentRotation += Vector3.SignedAngle( transform.parent.right,Vector3.up, transform.right);
if (Mathf.Abs(currentRotation) > 90)
{
scorePoint++;
scoreCombo++;
}
Debug.Log("" + currentRotation);
currentRotation = 0;
}

Multiple issues here!
First of all never compare two float values using ==! Due to the floating point precision this might fail even e.g. for 5f * 0.2f / 10f == 1f .. the result might be 0.9999999 or 1.000000001.
Instead you rather would use a certain range like e.g.
if(Mathf.Abs(a-b) <= someThreshold)
Unity provides Mathf.Approximately
Compares two floating point values and returns true if they are similar.
Floating point imprecision makes comparing floats using the equals operator inaccurate. For example, (1.0 == 10.0 / 10.0) might not return true every time. Approximately() compares two floats and returns true if they are within a small value (Epsilon) of each other.
so using
if(Mathf.Approximately(a-b))
basically equals doing
if(Mathf.Abs(a-b) <= Mathf.Epsilon)
where Mathf.Epsilon is
The smallest value that a float can have different from zero.
Then Transform.rotation and Transform.localRotation is a Quaternion which has four components x, y, z and w. Each of these moves in a range [-1; 1]. Except you know exactly what you are doing (which you don't ;) ) never directly read or write components of a Quaternion!
Your checks will simply never be true!
Instead you should rather work with vectors and check e.g.
// for storing the current rotation
private float currentRotation;
// for storing the last right direction
private Vector3 lastRight;
...
if (!player1.isGrounded)
{
// add the rotation delta since the last frame
currentRotation += Vector3.SignedAngle(lastRight, transform.right, transform.forward);
// if it exceeds +/- 90°
if(Mathf.Abs(currentRotation) > 90)
{
// get points
scorePoint++;
scoreCombo++;
// and reset the rotation counter
currentRotation = 0;
}
}
// Update the last right direction with the current one
lastRight = transform.right;
see Vector3.SignedAngle

Related

Accelerate rotation from 0 to x angle in x seconds in Unity

This is more of a math question than a coding question. I would like to reach for example an rotation angle of 90 in 1 second while speed is accelerating at constant value. My current version takes 1.4 seconds to reach the desired rotation angle, and it should reach it in 1 second. I believe that the reason for that is that it currently accelerates to speed of 90 in 1 second and not to rotation angle of 90. Since I am not that good in math, I have no idea how I need to adjust the acceleration calculation. I am unable to find any solution to this.
NOTE: I need to adjust the rotation angles manually, I am not able to use any existing functions, like for example transform.Rotate(), since in my complete version the rotation direction can change at any time and the rotation also has deceleration value.
This is a very simplified version of what I have (it only rotates the z axis to one direction and runs once on start):
private float accelerationInSeconds = 1;
private float targetAngle = 90f;
private float speed = 0;
private float axis = 1;
private bool rotate = true;
private float acceleration;
void Start() {
// Calculate acceleration (this calculation should be changed)
acceleration = targetAngle / accelerationInSeconds;
}
void Update() {
if (rotate) {
// Accelerate
speed += axis * (acceleration * Time.deltaTime);
// Calculate next rotation position
Vector3 rotationVector = transform.rotation.eulerAngles;
rotationVector.z += speed * Time.deltaTime;
// Rotate object
transform.rotation = Quaternion.Euler(rotationVector);
// Check if rotation has gone over the target angle
if (rotationVector.z >= targetAngle) {
rotationVector.z = targetAngle;
speed = 0;
rotate = false;
}
}
}
Thanks in advance for anyone who can help!
EDIT: Modified code to be more efficient. I can't use RotateTowards() since in my complete code I need to clamp the rotation between targetAngle and negative targetAngle. Hopefully this code is more efficient and performance friendly. But I still have not found a solution for my original math related question, which was the whole point of this question.
private float accelerationInSeconds = 1;
private float targetAngle = 90f;
private float speed = 0;
private float angle = 0;
private float axis = 1;
private bool rotate = true;
private float acceleration;
void Start() {
// Calculate acceleration (this calculation should be changed)
acceleration = targetAngle / accelerationInSeconds;
}
void Update() {
if (rotate) {
// Accelerate
speed += axis * (acceleration * Time.deltaTime);
// Calculate next rotation position
angle += speed * Time.deltaTime;
// Check if rotation has gone over the target angle
if (angle >= targetAngle) {
angle = targetAngle;
speed = 0;
rotate = false;
}
// Rotate object
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}
I finally figured it out, thanks to Math section in StackExchange.
So the simple answer is this:
acceleration = 2 * targetAngle / Mathf.Pow(accelerationInSeconds, 2);
As was suggested before I would use a Coroutine. Coroutines are like temporary Update methods and often easier to control and maintain than doing stuff directly in Update.
// Flag to avoid concurrent routines
private bool isRotating;
public void Rotate(float targetAngle, float duration)
{
if(! isRotating) StartCoroutine (RotateRoutine(targetAngle, duration));
}
private IEnumerator RotateRoutine (float targetAngle, float duration)
{
// Just to be sure
if(isRotating) yield break;
// block concurrent routines
isRotating = true;
// Pre-calculate the start and end rotation
var start = transform.rotation;
var end = Quaternion.Euler(0, 0, targetAngle);
var timePassed = 0f;
while(timePassed < duration)
{
// This value will grow linear from 0 to 1 in exactly "duration" seconds
var x = timePassed / duration;
// TODO!
var y = MAGIC;
// Interpolate between the start and end rotation using given factor "y"
transform.rotation = Quaternion.Lerp(start, end, y);
// "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// Increase by the time passed since last frame
timePassed += Time.deltaTime;
}
// To be sure to end with clean values
transform.rotation = end;
// Allow next routine
isRotating = false;
}
So what do we have to fill in for MAGIC?
Basically it can be any mathematical function that maps given input 0 to 1 to 0 to 1.
There are multiple possibilities.
What you currently ask for is a linear growing speed. That means the resulting movement shall be quadratic! So we already know the Formular
var y = a * x * x + b;
We further know from your code that speed always starts from 0 -> b = 0. And the last step is pretty straight forward:
What value do we have to fill in so y goes from 0 to 1 at the same time that x goes from 0 to 1?
1 = a * 1 * 1 + 0;
=> a = 1!
So in your case it is simply
var y = x * x;
If you also want ease-out you could also simply use Mathf.Smoothstep which automatically adds ease-in and ease-out
var y = Mathf.SmoothStep(0, 1, x);
To make it even easier to control you could use an AnimationCurve and adjust the movement curve exactly to your needs in the Inspector
[SerializeField] private AnimationCurve curve;
The curve editor already comes with some preset curves like e.g. linear, logarithmic, exponential and eased-in/-out grow from 0 to 1!
And then use AnimationCurve.Evaluate to get the value (y) in the routine for a given input time (x).
var y = curve.Evaluate(x);

How to realistically reflect a 3d sphere in Unity with C#

I've been trying to realistically reflect a 3d sphere on the walls of a box for a while now in Unity. For some reason, the reflection is generally correct, but when the ball hits a wall in certain directions, the reflection is incorrect.
To illustrate what happens to the ball upon hitting a wall: T = top wall, R = right wall, L = left wall, and B = bottom wall. Let r = the ball comes/goes to the right, l = for the left, and s = the ball stops/slows down significantly. The instructions below take this format: Xyz, where X = the wall the ball is about to hit, y = the ball's initial direction, z = the reflection. The game has a top-down perspective, and the instructions are based on the wall's perspective. I'm also new to C#, so the code is potentially eye burning.
Instructions: Tll, Trl; Bll, Brl; Rls or after hitting another wall Rlr, Rrl; Lls or after hitting another wall Llr, Lrl
Generally, when the ball stops, it jumps in the air. I wonder if this is because the angle reflects along the wrong axis, but why would this only sometimes happen? Also, when only one key is held, the ball bounces back and forth until it leaves the arena. I know about discrete and continuous hit detection, and the setting is on discrete, but the walls generally contain the ball well enough, with this case being the exception.
What I tried:
Figuring out how to use Vector3.Reflect. I do not understand what variables this function should contain and how to apply this to my code. I did look at the Unity Documentation page for help, but it did not answer my question.
Changing the negative signs, as the angle has to be a reflection on the y-axis, and this does change how the reflections work, but does not solve the problem. The current way the negatives are ordered are the most ideal I found.
Giving the ball a physics material for bounciness.
Adding a small number to the denominator in the arctan equation to help prevent a division by zero. This did not help at all.
Creating different equations (basically changing the negatives) for varying combinations of positive and negative accelerations. Since there is a certain positive or negative acceleration associated with each button press (see Movement script), and there seems to be an issue with said signs, I wondered if associating each acceleration with its own set of equations could solve the problem. It did not work.
Checked if the walls were at different angles.
Deleting the variables xA and yA, or placing them in different spots.
Experimented with finding the speed of the object, but had no idea how to implement it.
The code for the Movement script for the player named Controller:
public class Movement : MonoBehaviour
{
public static float xAcceleration = 0.0f;
public static float yAcceleration = 0.0f;
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W)) //If the key W is pressed:
{
Vector3 position = this.transform.position; //Variable position is set to transform the players placement in the game.
if (yAcceleration >= -5 && yAcceleration <= 5) //If the y vector of the acceleration is >= -5 and <= 5:
{
yAcceleration = yAcceleration + 0.01f; //The y vector of the acceleration increases by 0.01 as long as the key W is pressed.
}
position.z = position.z + (0.1f * yAcceleration); //The position of the object on the z-axis (pretend it is the y-axis in the game world) is transformed by its original position plus its speed times its yAcceleration.
this.transform.position = position; //The gameObject is now transformed to a position equal to the variable position by the z-axis.
}
else //If the key W is let go of:
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position; //The position of the gameObject continues to update, but its acceleration does not change. Basically, it continues to move forward.
}
//The rest of the code is very similar to the above, but I included it just in case there was something wrong.
if (Input.GetKey(KeyCode.S))
{
Vector3 position = this.transform.position;
if (yAcceleration >= -5 && yAcceleration <= 5)
{
yAcceleration = (yAcceleration) - 0.01f;
}
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.z = position.z + (0.1f * yAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.A))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) - 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
if (Input.GetKey(KeyCode.D))
{
Vector3 position = this.transform.position;
if (xAcceleration >= -5 && xAcceleration <= 5)
{
xAcceleration = (xAcceleration) + 0.01f;
}
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
else
{
Vector3 position = this.transform.position;
position.x = position.x + (0.1f * xAcceleration);
this.transform.position = position;
}
}
}
This is the code for the collider and reflection:
public class Collider : MonoBehaviour
{
public float xA;
public float yA;
void OnCollisionEnter(Collision collision) //If a gameObject enters the collision of another object, this immediately happens once.
{
if (gameObject.tag == "Boundary") //If the gameObject has a tag named Boundary:
{
yA = -Movement.yAcceleration; //yA stores the value of yAcceleration after being called from script Movement as a negative. Its a reflection.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration)); //xAcceleration is changed based on this equation: A * artan(A_y / A_x). The 0.000001 was here, adding to A_x to help prevent a 0 as the denominator.
xA = Movement.xAcceleration; //This is declared now...
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA)); //This uses xA because Movement.xAcceleration is changed, and the yAcceleration calculation is based on the xAcceleration prior the collision.
}
}
void OnCollisionStay(Collision collision)
{
if (gameObject.tag == "Boundary")
{
yA = Movement.yAcceleration; //The same thing happens as before.
Movement.xAcceleration = (Movement.xAcceleration * -Mathf.Atan(yA / Movement.xAcceleration));
xA = Movement.xAcceleration;
Movement.yAcceleration = (Movement.yAcceleration * Mathf.Atan(Movement.yAcceleration / xA));
Movement.xAcceleration = -Movement.xAcceleration / 2; //On collision, the ball is reflected across the x-axis at half its speed.
Movement.yAcceleration = Movement.yAcceleration / 2; //yAcceleration is half its original value.
}
}
}
The picture below is the game setup. I apologize that it is a link; I do not have enough Reputation to merit a loaded image on this page. Also, if anything is unclear, please message me.
https://i.stack.imgur.com/VREV4.png
I would really appreciate the help. Thanks!
One very important note here: As soon as there is any Rigidbody involved you do not want to set any values through the .transform - This breaks the physics and collision detection!
Your Movement should rather alter the behavior of the Rigidbody e.g. by simply changing its Rigibody.velocity
I would then also place the collision check directly into the balls's component and check whether you hit a wall ("Boundary")
Then another note: Your code is currently frame-rate dependent. It means that if your target device runs with only 30 frames per second you will add 0.3 per second to the acceleration. If you run however on a more powerful device that manages to run with 200 frames per second then you add 2 per second.
You should rather define the de/increase per second and multiply it by Time.deltaTime
All together maybe something like this
public class Movement : MonoBehaviour
{
// Adjust these settings via the Inspector
[SerializeField] private float _maxMoveSpeed = 5f;
[SerializeField] private float _speedIncreasePerSecond = 1f;
// Already reference this via the Inspector
[SerializeField] private Rigidbody _rigidbody;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Get User Input in Update
private void Update()
{
var velocity = _rigidbody.velocity;
velocity.y = 0;
if (Input.GetKey(KeyCode.W) && velocity.z < _maxMoveSpeed)
{
velocity.z += _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.S) && velocity.z > -_maxMoveSpeed)
{
velocity.z -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) && velocity.x > -_maxMoveSpeed)
{
velocity.x -= _speedIncreasePerSecond * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D) && velocity.x < _maxMoveSpeed)
{
velocity.x += _speedIncreasePerSecond * Time.deltaTime;
}
// clamp to the max speed in case you move diagonal
if(velocity.magnitude > _maxMoveSpeed)
{
velocity = velocity.normalized * _maxMoveSpeed;
}
_rigidbody.velocity = velocity;
}
}
And then finally simply add a PhysicsMaterial with desired settings to the walls and ball.
I used Friction = 0f and Bounciness = 0.7f for ball and walls. For slow movements you also might want/have to adjust the Bounce Threshold in the Project's Physics Settings otherwise there will be no bouncing if the velocity is smaller then 2 by default.
This depends a bit on your definition of "realistic". I disabled gravity so the ball also has no rotation and angular friction:

Equalize over slope and flat surface velocity in Unity

I've posted a similar question but it is not exactly the same problem so here I go.
I'm not using physics in my project, so every force it's calculated and applied by me. The point is that the character covers the same area in the same time regardless the inclination of the surface is running on.
Being applied the same movement into the CharacterController.Move() function, the velocity of the controller increases exponentially to try to move along the same surface than being in a flat area.
For example. If a prints the movemement calculated that will be applied in the "Move()" function, it is a normalize one, being (0,0,1) in perpendicular and (0.7,0,0.7) in diagonal. However, if I retrieve the velocity via CharacterController.velocity.magnitude I get a different one, being 8 on flat surface and 11.47 in a 45º slope.
I've made a formula to calculate what is the value that should be retrieved with that "velocity.magnitude" function.
groundAngle = Mathf.Abs(Mathf.Round(Vector3.Angle(hit.normal, transform.forward)) - 90);
groundMovementMagnitude = characterController.velocity.magnitude;
slopeMovementIdeal = ((((groundAngle/2) - groundAngle) + 100) * (groundMovementMagnitude / 100));
With this formula, I get in fact a value of "8" in a flat surface and instead of "11.47", the value of the velocity retrieved in a 45º inclination slope is "6.4"
Nevertheless, this value is simply informative, because the velocity of the character controller can not be set. Instead of that, I need a way to modify the movement Vector3 that will be used to trigger the movement, so, instead of moving (0,0,1) on a slope, or (0.7,0,0.7) in diagonal on a slope, apply a reducer to deduct this Vector3 depending on the angle of the slope.
What I've finally done is using the velocity magnitude to calculate the difference between on flat movement and on slope. Then, I just converted that value into a normalized vector and deduct it to the characters movement.
float minMovement = 0.01f;
float difference = 0.0f;
float x = 0.0f;
float z = 0.0f;
if (OnSlope() && characterController.velocity.magnitude - moveSpeed > minMovement) {
difference = characterController.velocity.magnitude - moveSpeed;
//Diagonal
if ((movement.x > minMovement || movement.x < -minMovement) && (movement.z > minMovement || movement.z < -minMovement)) {
x = z = (difference / 2) / characterController.velocity.magnitude;
if (movement.x < 0)
x *= -1;
if (movement.z < 0)
z *= -1;
}
//Perpendicular
else {
if (movement.x > minMovement || movement.x < -minMovement) {
x = difference / moveSpeed;
if (movement.x < 0)
x *= -1;
}
if (movement.z > minMovement || movement.z < -minMovement) {
z = difference / moveSpeed;
if (movement.x < 0)
z *= -1;
}
}
movement.x -= x;
movement.z -= z;
}
It is working fine.

calculating rotation delta based off initial angle and current angle

I'm looking to track the rotation delta (y axis) between two readings.
There's a game object that I read an initial angle from when it starts moving.
Then I have another variable that reads the live angle during the update loop.
I want the delta value to be agnostic of whether it's negative or positive, essentially I'm going to apply the same updates whether the object rotation 20 degrees to the left or right.
EDIT:
I want the delta value to be the smallest angle, so the maximum it could be is 180, before counting back down to 0.
EXAMPLE:
If my initFaceAngle == 5 and currentFaceAngle == 355 then myAngle == 10
void Start()
{
initFaceAngle = hmd.transform.rotation.eulerAngles.y;
}
void update()
{
currentFaceAngle = hmd.transform.rotation.eulerAngles.y;
// My terrible first attempt... look ma' I can math.
float myAngle = (float)Math.Abs(initFaceAngle - currentFaceAngle);
}
Obviously my calculation won't work because getting the difference between two angles doesn't take into account the 360 degree. So I figured I need some pie (pi) on this, but outside of being an impressive pie (pi) eater I don't have a clue how to invoke its magical math powers.
What formula do I need to use to capture the delta?
How can I make it read the same whether rotating left or right?
Because you only want the angle between the initial and current position, it's actually no math involved, only a simple check to see which value is the greatest.
void update()
{
currentFaceAngle = hmd.transform.rotation.eulerAngles.y;
float myAngle;
if(initFaceAngle > currentFaceAngle)
myAngle = initFaceAngle - currentFaceAngle;
else
myAngle = currentFaceAngle - initFaceAngle;
if(myAngle > 180)
myAngle = 360 - myangle;
}
I think I've worked out a solution, not sure if it's the most graceful but the output values look correct.
void Start()
{
initFaceAngle = hmd.transform.rotation.eulerAngles.y;
}
void update()
{
currentFaceAngle = hmd.transform.rotation.eulerAngles.y;
float myAngle = getDeltaAngle(initFaceAngle, currentFaceAngle);
}
public static float getDeltaAngle(float a, float b)
{
float x;
float y;
if (a > b)
{
x = a;
y = b;
}
else
{
x = b;
y = a;
}
if (x - y < 180)
{
x = x - y;
}
else
{
x = (360 - x) + y;
}
return x;
}

Rotation direction continuos check

I'm working on a moltitouch application in actionscript 3, I'm also porting it in C#, Basically, im working on a Knob, that can be rotated with a finger, what i would like to achieve is that rotating CW or CCW i can have continuos direction, instead everytime angle is passing by 180 I got an inversion of direction, any hint ?
Which way can be detected a continuos rotation direction ?
this is the code I'm uding to detect direction:
private function findDirection(currentAngle : Number, targetAngle : Number) : int
{
currentAngle = refineAngle(currentAngle);
targetAngle = refineAngle(targetAngle);
if (targetAngle < 0)
{
targetAngle += (Math.PI * 2);
}
if (currentAngle < 0)
{
currentAngle += (Math.PI * 2);
}
if (targetAngle < currentAngle)
{
targetAngle += (Math.PI * 2);
}
if (targetAngle - currentAngle <= Math.PI)
{
return 1;
}
else
{
return -1;
}
}
private function refineAngle(angle : Number) : Number
{
return angle * Math.PI / 180;
}
Maybe this helps. The variable continuousAngle will track the total knob turning performed, i.e. turning the knob twice counterclockwise will get you to 720. Then turning it three times clockwise takes you back down to -360. Everything else should be easy to derive - limiting the minimum and maximum values, making the value wrap around, scale the value to for example 1 per turn or whatever else you want.
var lastAngle = 0;
var continuousAngle = 0;
function HandleDown(angle)
{
lastAngle = angle;
}
function HandleMove(angle)
{
// The orientation change in degrees of the knob since the last event with
// a range of [-180;+180). A positive value indicates counterclockwise, a
// negative value clockwise turning.
var change = (360 + angle - lastAngle) % 360;
if (change >= 180)
{
change -= 360;
}
// It may also be a good idea to not update continuousAngle if the absolute
// value of change is larger than say 10°, 20° or 40° because such large
// changes may indicate some kind of glitch like the user moving straight
// across the knob. But I am not sure and 20 is just a random guess.
if (Math.Abs(change) <= 20)
{
continuousAngle += change;
}
lastAngle = angle;
}
For floating point numbers the reminder can be calculated using Math.IEEEReminder instead of the remainder operator %. The linked page also shows how to implement this function yourself if it is not available in your language.

Categories