Hi I am using this code to have objects moving on the y axis.
using UnityEngine;
using System.Collections;
public class TargetMovementVertical : MonoBehaviour
{
public int maxSpeed;
private Vector3 startPosition;
// Use this for initialization
void Start ()
{
startPosition = transform.position;
}
// Update is called once per frame
void Update ()
{
MoveVertical ();
}
void MoveVertical()
{
transform.position = new Vector3(transform.position.x, Mathf.Sin(Time.time * maxSpeed), transform.position.z);
if(transform.position.y > 1.0f)
{
transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
}
else if(transform.position.y < -1.0f)
{
transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z);
}
}
}
My only problem is that the object are only moving within 1 and -1 and i would like to have them move lower. is there a possible way please ?
Programming
In your code, you are setting the position using this line:
transform.position = new Vector3(transform.position.x, Mathf.Sin(Time.time * maxSpeed), transform.position.z);
Here, the only coordinate that is changing is the y coordinate. And it changes according to the function Mathf.Sin.
If you read the documentation for Mathf.Sin you will find that it returns values between -1 and +1.
That is why...
the object are only moving within 1 and -1
The simple solution is to multiply the result of Mathf.Sin by some factor.
Math
This is the sine function:
red plot: y = sin(x)
As you can see, the range of the sine function is [-1, 1]. Thus, regardless of what input value you put into the function, you will get a result in the interval [-1, 1].
If you multiply the input, you are changing the frequency of the sine wave, for example:
Red plot: y = sin(5x)
Observe that placing a factor inside the function will not affect the amplitud of the wave. Compare with the following:
Red plot: y = 5sin(x)
The above graph, at difference with the prior ones, has the range [-5, 5].
Here you can see them all for comparison:
Red plot: 5sin(x)
Blue plot: sin(x)
Purple plot: sin(5x)
These plots were created with the graphing calculator from meta-calculator. You can try the functions there yourself if you don't want to take my word for it.
To understand why the sin function has this shape, remember that the sine function takes an angle and returns the vertical component of a unit vector that has angle with the horizontal...
I mean this:
Unit circle with sine and cosine, θ=45 degrees.
Since we are taking a unit vector, (we are working on the unit circle), the maximum value that the vertical (sine) will take is 1, and the minimum is -1.
To understand how the sine plots we saw above come from this, I hope this animation makes it clearer:
Animation showing how the sine function (in red) y = sin(θ) is graphed from the y-coordinate (red dot) of a point on the unit circle (in green) at an angle of θ in radians.
Back to programming
As I said at the start of the answer, if you want to scale the movement, you can change the amplitude to the sine wave by multipliying the result by some factor, for example: Mathf.Sin(angle) * amplitude.
That amplitude value will tell how far the value will reach, that is, by multiplying by Mathf.Sin by amplitude you get a value in the range - amplitude and + amplitude.
I expect that you find that approach reasonable know that the reasoning behind it have been presented.
I hope the above explanation makes it clear that the sine function does not preserve factors. That is: sin(a*x) ≠ a*sin(x). In other words that the sine function is not transitive with scaling, the reason for that is that the sine function is NOT a linear transformation.
Related
I'm trying to make an airplane controller, I am kind of aiming for something between arcade and realistic, so I want the plane to turn with a force proportional to the roll.
I haven't coded in any adjustments and I'm still prototyping the whole thing, but I encountered a problem with getting the signed rotation angle while using quaternions, I had a look at Determining if quarternion rotation is clockwise or counter clockwise here on SO but I am having trouble generalizing the solution to the (almost) arbitrary plane the rotation can be at.
What I made by now:
private void FixedUpdate()
{
float desiredYaw = _yaw * _rotationSpeed * Time.fixedDeltaTime;
float desiredPitch = -_pitch * _rotationSpeed * Time.fixedDeltaTime;
float rotationStepSize = _throttle * Time.fixedDeltaTime;
Quaternion toRotate = Quaternion.Euler(desiredPitch, 0, desiredYaw);
Quaternion straighRotation = Quaternion.LookRotation(_transform.forward, Vector3.up );
_rotation = _transform.rotation * toRotate;
float turningForce = Quaternion.Angle( _rotation, straighRotation );
_rigidbody.MoveRotation( _rotation );
_rigidbody.AddTorque( turningForce * _rotationForce * rotationStepSize * Vector3.up );
_rigidbody.AddRelativeForce( _speed * rotationStepSize * Vector3.forward );
}
EDIT: I realized I'm calculating the turning force using the roll rather then the yaw, that was intended just wrong wording, corrected now.
Since all you need is a factor that describes how downward the plane's right is, you can just use the y component of the plane's right for that. No need to bring in quaternions or even trigonometry. Explanation in comments:
private void FixedUpdate()
{
// ...
// Calculate how downward local right is in range [-1,1]
// The more downward, the more tilted right the plane is
// positive = tilted right
// negative = tilted left
float turnFactor = -_transform.right.y;
// Could do things to modify turnFactor to affect easing here.
// For instance, if turning rate should start slower then rapidly increase:
// turnFactor = Mathf.Sign(turnFactor) * turnFactor * turnFactor;
// Use factor and _rotationForce member to calculate torque, apply along
// global up.
// We expect to call this every fixed frame so we can just use the default
// ForceMode of ForceMode.Force which multiplies fixed delta time inside.
_rigidbody.AddTorque(_rotationForce * turnFactor * Vector3.up);
// ...
}
I would like to recreate one on one the rotation of the real life controller joystick (i.e. 360 controller) into a 3D joystick mesh (that resembles the 360 controller one).
I thought about doing it by rotating the joystick in the X axis according to the magnitude of the input (mapping it to a min and max rotation in the X axis). And then figure the angle of the input and apply it to the Y axis of the 3D joystick.
This is the code I have, the joystick tilts properly in the X axis but the rotation in the Y axis doesn't work:
public void SetStickRotation(Vector2 stickInput)
{
float magnitude = stickInput.magnitude;
// This function converts the magnitude to a range between the min and max rotation I want to apply to the 3D stick in the X axis
float rotationX = Utils.ConvertRange(0.0f, 1.0f, m_StickRotationMinX, m_StickRotationMaxX, magnitude);
float angle = Mathf.Atan2(stickInput.x, stickInput.y);
// I try to apply both rotations to the 3D model
m_Stick.localEulerAngles = new Vector3(rotationX, angle, 0.0f);
}
I am not sure why is not working or even if I am doing it the right way (i.e. perhaps there is a more optimal way to achieve it).
Many thanks for your input.
I would recommend rotating it by an amount determined by the magnitude around a single axis determined by the direction. This will avoid the joystick spinning around, which would be especially noticeable in cases of asymmetric joysticks such as pilots joysticks:
Explanation in comments:
public void SetStickRotation(Vector2 stickInput)
{
/////////////////////////////////////////
// CONSTANTS (consider making a field) //
/////////////////////////////////////////
float maxRotation = 35f; // can rotate 35 degrees from neutral position (up)
///////////
// LOGIC //
///////////
// Convert input to x/z plane
Vector3 stickInput3 = new Vector3(stickInput.x, 0f, stickInput.y);
// determine axis of rotation to produce that direction
Vector3 axisOfRotation = Vector3.Cross(Vector3.up, stickInput3);
// determine angle of rotation
float angleOfRotation = maxRotation * Mathf.Min(1f, stickInput.magnitude);
// apply that rotation to the joystick as a local rotation
transform.localRotation = Quaternion.AngleAxis(angleOfRotation, axisOfRotation);
}
This will work for joysticks where:
the direction from its axle to its end is the local up direction,
it should have zero (identity) rotation on neutral input, and
stickInput with y=0 should rotate the knob around the stick's forward/back axis, and stickInput with x=0 should rotate the knob around the stick's left/right axis.
Figure out the problem, atan2 returns the angle in radiants, however the code assumes it is euler degrees, as soon as I did the conversion it worked well.
I put the code here if anyone is interested (not the change in the atan2 function):
public void SetStickRotation(Vector2 stickInput)
{
float magnitude = stickInput.magnitude;
// This function converts the magnitude to a range between the min and max rotation I want to apply to the 3D stick in the X axis
float rotationX = Utils.ConvertRange(0.0f, 1.0f, m_StickRotationMinX, m_StickRotationMaxX, magnitude);
float angle = Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg;
// Apply both rotations to the 3D model
m_Stick.localEulerAngles = new Vector3(rotationX, angle, 0.0f);
}
How can I keep the diagonal speed to be the same as the horizontal and vertical speed without clamping any value or using ".normaized". I tryed to normalize the values but I lost the joystick values between 1 and 0. Here is my code :
void ListenInput()
{
Vector3 rightDirection = camera.right;
Vector3 frontDirection = camera.GetForwardFromAngleY();
move = new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
);
MoveCharacter(rightDirection * move.x);
MoveCharacter(frontDirection * move.y);
}
void MoveCharacter(Vector3 velocity)
{
transform.position += velocity * Time.deltaTime * runningSpeed;
}
Here, you should clamp the magnitude of the input Vector2.
For example with Vector2.ClampMagnitude() from the Unity API.
That will keep the input non-binary and prevent the diagonal from getting larger than purely horizontal/vertical inputs.
void ListenInput()
{
Vector3 rightDirection = camera.right;
Vector3 frontDirection = camera.GetForwardFromAngleY();
move = new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
);
move = Vector2.ClampMagnitude(move, 1f);
MoveCharacter(rightDirection * move.x);
MoveCharacter(frontDirection * move.y);
}
void MoveCharacter(Vector3 velocity)
{
transform.position += velocity * Time.deltaTime * runningSpeed;
}
If you normalize a vector you will make sure it's length is 1. This is a great way to avoid quirks like "diagonal movement is faster than normal movement".
However, the fact that the length is always 1 also means that there is no "move slowly" or "move at full speed" distinction from the joystick. When you say "I lost the joystick values between 1 and 0" is due to this fact.
One way developers get around this is by using a mathematical formula to scale the speed.
You could:
Use the largest value (horizontal or vertical) to control the speed
Use the smallest value
Use a combination of the two
Another way to do this is to store how long ago the movement started, then scale the speed based on that. This method has its own challenges, but is very familiar to players.
Examples
For instance, if I have:
horizontalInput = 1
verticalInput = 0.5
This means my normalized vector looks like this:
I could:
Use the largest value
Move at full speed (1) on the direction of my vector.
Use the smallest value
Move at half speed (0.5) on the direction of my vector.
Use a Use a combination of the two values
For this instance, lets use the following formula: (x+y)/2.
Move at 3/4 speed (0.75) on the direction of my vector.
NOTE: This formula will not "feel" as nice if you have x=0 and y=1, this is just an example. You most likely want to use Min, Max, Avg and if-clauses to control how the speed works.
You can use different formulas and different techniques to make the movement in your game feel like what you want, but take the time to analyze WHY it feels like that.
I need to display the rotation in Euler angles of an object's certain axis.
I am aware that retrieving the rotation of an object in Euler angles gives inconsistent results, some of which can be solved by simply using modulo 360 on the result. however one permutation that unity sometimes does when assigning a vector with the value of "transform.localRotation.eulerAngles" is instead of retrieving the Vector3 "V", it retrieves "(180, 180, 180) - V".
to my understanding, "(180, 180, 180) - V" does not result in the same real world rotation as V, unlike "(180, 180, 180) + V" which does leave the actual rotation unaffected.
what is the explanation for the phenomenon, and what is the best way of normalizing an Euler angles rotation vector assuming I know the desired and feasible value of one of its axes? (for example, to normalize it such that all of it's values are mod 360 and it's Z axis equals 0 assuming it does have a representation in which Z = 0)
I don't know about the first part of the question (it is different enough to be its own question imo) but I can answer your second one.
So, you have these inputs :
Quaternion desiredRotation;
float knownZ;
And you're trying to find Vector3 eulers where eulers.z is approximately knownZ and Quaternion.Euler(eulers) == desiredRotation.
Here's the procedure I would use:
First, determine the up direction rotated by desiredRotation and the up and right direction rotated by a roll of knownZ:
Vector3 upDirEnd = desiredRotation * Vector3.up;
Quaternion rollRotation = Quaternion.Euler(0,0,knownZ);
Vector3 upDirAfterRoll = rollRotation * Vector3.up;
Vector3 rightDirAfterRoll = rollRotation * Vector3.right;
We know the local up direction after desiredRotation is applied and that the only thing that can adjust the up direction after the roll knownZ is applied is the rotation done by the euler pitch component. So, if we can calculate the angle from upDirAfterRoll to upDirEnd as measured around the rightDirAfterRoll axis...
float determinedX = Vector3.SignedAngle(upDirAfterRoll, upDirEnd, rightDirAfterRoll);
// Normalizing determinedX
determinedX = (determinedX + 360f) % 360f;
...we can determine the x component of eulers!
Then, we do the same with the yaw component of eulers to make the new forward direction line up with the end forward direction:
Vector3 forwardDirEnd = desiredRotation * Vector3.forward;
Quaternion rollAndPitchRotation = Quaternion.Euler(determinedX, 0, knownZ);
Vector3 forwardDirAfterRollAndPitch = rollAndPitchRotation * Vector3.forward;
Vector3 upDirAfterRollAndPitch = upDirEnd; // unnecessary but here for clarity
float determinedY = Vector3.SignedAngle(forwardDirAfterRollAndPitch, forwardDirEnd, upDirAfterRollAndPitch );
// Normalizing determinedY
determinedY = (determinedY + 360f) % 360f;
Vector3 eulers = new Vector3(determinedX, determinedY, knownZ);
To ensure that the given quaternion can be made with the given component, you could check if the axes given to SignedAngle actually can rotate the input vector to the target vector, or you can just compare the calculated eulers and the given quaternion:
Quaternion fromEuler = Quaternion.Euler(eulerAngles);
if (fromEuler==desiredRotation)
{
// use eulerAngles here
}
else
{
// component and quaternion incompatible
}
Hopefully that helps.
I'm not quite sure I understand your question correctly, but the euler angles just represent the angles of 3 rotations applied around the 3 axis in a specific order, right? So why would you normalize it by adding 180 everywhere? You should bring each angle individually into the range 0-360 by modulo-ing them.
Your question seems to imply that you can obtain any orientation by only rotating around two axis instead of three... is that what you are trying to achieve?
Using quaternions could possibly help you, in fact an orientation can be defined with just 4 scalar values: an axis and an angle
I'm a bit confused about some documentation for Unity pertaining to Euler angles. I just want to know if I'm not understanding a difference, or the sample does not follow the best practice. The documentation here
states:
Only use this variable to read and set the angles to absolute values. Don't increment them, as it will fail when the angle exceeds 360 degrees. Use Transform.Rotate instead.
Meanwhile, the code sample appears to be using increments that could exceed 360 degrees:
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour {
public float yRotation = 5.0F;
void Update() {
yRotation += Input.GetAxis("Horizontal");
transform.eulerAngles = new Vector3(10, yRotation, 0);
}
void Example() {
print(transform.eulerAngles.x);
print(transform.eulerAngles.y);
print(transform.eulerAngles.z);
}
}
Wouldn't incrementing a variable and then using that variable to set the value absolutely still run the risk of exceeding 360 degrees if the variable is over 360 degrees?
When you rotate your Object using euler angles then when it reaches 360 and try to exceed further, it becomes (minus)-360 and gradually increase from -359 to -1.
After executing following code your values will not exceed from 360 and will remain positive.
float rotateAngle = rotateObject.transform.localEulerAngles.y;
// Convert negative angle to positive angle
rotateAngle = (rotateAngle > 180) ? rotateAngle - 360 : rotateAngle;
rotateObject.transform.localEulerAngles = new Vector3(rotateObject.transform.localEulerAngles.x, rotateAngle, rotateObject.transform.localEulerAngles.z);
There are differences. When doing:
transform.eulerAngles.y += 1F;
You are invoking the += operator in Vector3.
However, when you set eulerAngles this way:
float newY = transform.eulerAngles.y + 1F;
transform.eulerAngles = new Vector3(.., newY, ..);
You are invoking a setter in Transform, and inside this setter, it probably includes the action of updating Transform.rotation.
The difference is that Unity can implement the updating logic in Transform class instead of the Vector3 class, which makes much more sense there.
We can verify this further below in the documentation:
Do not set one of the eulerAngles axis separately (eg. eulerAngles.x =
10; ) since this will lead to drift and undesired rotations. When
setting them to a new value set them all at once as shown above. Unity
will convert the angles to and from the rotation stored in
Transform.rotation.