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.
Related
I'm new to unity, and an amateur C# user. I have a submarine sprite that I would like to flip by the y-axis when rotated more than 90 degrees, and less than -90 degrees so that it won't be upside down. It's rotated by mouse movement which I'll give the code if necessary. I'm not sure why but, this doesn't seem to work. Any help would be appreciated!
Code:(rot90 is a bool)
if (transform.rotation.z > 90 & transform.rotation.z >-90)
{
rot90 = false;
}
if (transform.rotation.z < 90 & transform.rotation.z < -90)
{
rot90 = true;
}
if (rot90 == true)
{
Vector3 scale = transform.localScale;
scale.y = -22;
transform.localScale = scale;
}
if (rot90 == false)
{
Vector3 scale = transform.localScale;
scale.y = 22;
transform.localScale = scale;
}
transform.rotation is a Quaternion!
A Quaternion has not only 3 but 4 component x, y, z and w, and they all move in ranges between -1 and 1.
Your conditions can never become true!
You could use the eulerAngles and do e.g.
var zAngle = transform.eulerAngles.z;
// clean out the angle to a value between -180 and +180
while(zAngle > 180) zAngle -= 360;
while(zAngle < -180) zAngle += 360;
Vector3 scale = transform.localScale;
scale.y = Mathf.Abs(zAngle) > 90 ? -22 : 22;
transform.localScale = scale;
in case you are using a SpriteRenderer component you should rather go for SpriteRenderer.flipY
var zAngle = transform.eulerAngles.z;
// clean out the angle to a value between -180 and +180
while(zAngle > 180) zAngle -= 360;
while(zAngle < -180) zAngle += 360;
// You should of course rather cache this reference e.g. in Awake only once
// and then reuse it here
GetComponent<SpriteRenderer>().flipY = Mathf.Abs(zAngle) > 90;
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
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);
I have what I think is a mathematical problem with my power engine and I've spent the past 2 days trying to solve this. I am relatively new to programming and am in the process of developing a 2D top-down billiard game in Unity.
My code is attached below, but essentially the player will click on the ball and drag back to produce power. Once they reach the desired power and let go the ball will launch in the desired direction at the desired power. For the sake of this example let's say that I set maxPower to 100, and the player object's linear drag and the tile drag remains constant. In the following example scenarios, all shots are at full power, and I'm shooting from the bottom left of my table:
I shoot a purely x-axis shot - the ball is shot at 100 power to the right
I shoot a purely y-axis shot - the ball is shot at 100 power upwards
I shoot at a 45 degree angle - how can I calculate what happens here?
If you look at the code below, I am using AddForce to push the ball on the x and y axis once I release the mouse. I'm tracking the distance of each shot made at a variety of angles and I've tried the following power management:
Splitting the power between x and y axis - this ends up produces a much weaker shot than a pure x/y axis shot (50 power per axis)
Giving each axis the full power - this ends up producing a much more powerful shot (100 power per axis)
Feeling like Goldilocks, I tried to make this juuuuust right and compute this using the Pythagorean theorem, and this is where I am struggling.
Any help would be much appreciated.
Code below - all variables are defined, I've just removed irrelevant code.
public float maxForce = 100;
public float forceMultiplier = 2
void Awake() {
startPosition = transform.position;
}
void Update () {
/// Tracks distance (start vs current)
distance = Vector3.Distance(transform.position,startPosition);
//On Click
if (Input.GetMouseButtonDown (0)) {
startPosX = Input.mousePosition.x;
startPosY = Input.mousePosition.y;
}
/// While being clicked
if (Input.GetMouseButton (0)) {
/// Determines force to be applied based on mouse drag
current_xForce = (startPosX - Input.mousePosition.x);
current_yForce = (startPosY - Input.mousePosition.y);
// Stores the original drag angle
yCheck = current_yForce;
xCheck = current_xForce;
/// if current x/y force is greater than maxForce, set to maxForce
if (current_yForce > 0 && current_yForce > maxForce)
current_yForce = maxForce;
if (current_yForce < 0 && current_yForce < -maxForce)
current_yForce = -maxForce;
if (current_xForce > 0 && current_xForce > maxForce)
current_xForce = maxForce;
if (current_xForce < 0 && current_xForce < -maxForce)
current_xForce = -maxForce;
// Determines the % of x/y while aiming
current_xPercentage = Mathf.Abs (current_xForce) / (Mathf.Abs (current_xForce) + Mathf.Abs (current_yForce));
current_yPercentage = Mathf.Abs (current_yForce) / (Mathf.Abs (current_xForce) + Mathf.Abs (current_yForce));
// Decides the Power bar% - power determined by highest powered axis (may need improvement)
current_powerPercent = Mathf.Max (Mathf.Abs (current_xForce)/maxForce, Mathf.Abs (current_yForce)/maxForce);
// get angle from start position to relative mouse position... add relative mouse position NOT WORKING... need degrees
shotAngle = Vector2.Angle(startPosition,Input.mousePosition);
}
// Mouse button released
if (Input.GetMouseButtonUp (0)) {
/// Only shots with greater than % of total power count - allows you to cancel shots
if (current_powerPercent >= 0.10) {
/// Increase force by public multiplier
xForce = current_xForce * forceMultiplier ;
yForce = current_yForce * forceMultiplier ;
/// Use % of absolute xCheck to determine shot angle
float xForcePercent = Mathf.Abs (xCheck) / (Mathf.Abs (yCheck) + Mathf.Abs (xCheck));
float yForcePercent = Mathf.Abs (yCheck) / (Mathf.Abs (yCheck) + Mathf.Abs (xCheck));
/// Adds force to x/y based off xForce and the shot angle
float x_addForce = xForce * xForcePercent;
float y_addForce = yForce * yForcePercent;
/// Attempt at a-squared + b-squared = c-squared.... this is supposed to return c
powerVar = Mathf.Sqrt(Mathf.Pow (x_addForce*xForcePercent, 2) + Mathf.Pow (y_addForce*yForcePercent, 2));
/// Applies the force to the player object.. .for each axis I take powerVar * angle * sign (to adjust for negative values)
GetComponent<Rigidbody2D>().AddForce(new Vector2(powerVar*xForcePercent*Mathf.Sign(x_addForce), powerVar*yForcePercent*Mathf.Sign(y_addForce)));
yForce = 0;
xForce = 0;
startPosX = 0;
startPosY = 0;
distance = 0;
startPosition = transform.position; /// resets Distance counter
}
}
}
The Pythagorean theorem dictates that in a right triangle the following is true:
c * c = a * a + b * b
Where c is the hypotenuse. Now, in your case c is 100 and the angle between c and a or b is 45°. This means you are equally "splitting" c between a and b. Ok, let's do that:
a = b, therefore:
c * c = a * a + a * a, which means
c * c = 2 * a * a
If we take the square root on both sides:
c = sqrt(2) * a, and therefore
a = c / sqrt(2)
You will see this same relation written may times in the following form:
a = sqrt(2)/2 * c
And there you go. In your case if c is 100 then a and b will be 70,711.
You can generalize this for any angle if you use trigonometric functions (based on the Pythagorean theorem). For any angle d between c and a, the following is true:
a = c * cos(d)
b = c * sin(d)
Make sure you get your units right if you are going to use trigonometric functions. Angles are normally given in radians, not degrees, the relation between the two is:
180 degrees = pi radians
I wrote some code for a projectile class in my game that makes it track targets if it can:
if (_target != null && !_target.IsDead)
{
Vector2 currentDirectionVector = this.Body.LinearVelocity;
currentDirectionVector.Normalize();
float currentDirection = (float)Math.Atan2(currentDirectionVector.Y, currentDirectionVector.X);
Vector2 targetDirectionVector = this._target.Position - this.Position;
targetDirectionVector.Normalize();
float targetDirection = (float)Math.Atan2(targetDirectionVector.Y, targetDirectionVector.X);
float targetDirectionDelta = targetDirection - currentDirection;
if (MathFunctions.IsInRange(targetDirectionDelta, -(Info.TrackingRate * deltaTime), Info.TrackingRate * deltaTime))
{
Body.LinearVelocity = targetDirectionVector * Info.FiringVelocity;
}
else if (targetDirectionDelta > 0)
{
float newDirection = currentDirection + Info.TrackingRate * deltaTime;
Body.LinearVelocity = new Vector2(
(float)Math.Cos(newDirection),
(float)Math.Sin(newDirection)) * Info.FiringVelocity;
}
else if (targetDirectionDelta < 0)
{
float newDirection = currentDirection - Info.TrackingRate * deltaTime;
Body.LinearVelocity = new Vector2(
(float)Math.Cos(newDirection),
(float)Math.Sin(newDirection)) * Info.FiringVelocity;
}
}
This works sometimes, but depending on the relative angle to the target projectiles turn away from the target instead. I'm stumped; can someone point out the flaw in my code?
Update: thinking about it and trying stuff has led me to the conclusion that it has something to do with when the direction (being in radians) is below 0 and the current projectile angle is above 0.
The variable targetDirectionDelta is not always the shortest direction to the target. If the absolute value of targetDirectionDelta is greater than PI radians, it will appear the projectile is turning away from the target. Turning in the other direction is shorter and expected.
Example:
currentDirection = 2
targetDirection = -2
The projectile can turn -4 radians (in the negative direction), or 2*(PI-2) radians (about 2.2 radians) (in the positive direction).
For this case, your code always calculates the longer direction, but you are expecting the projectile to turn towards the shorter direction:
targetDirectionDelta = targetDirection - currentDirection