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
Related
I'm working on a simple bit of code in C# to make sure a player model's head points at the mouse's Vector3 position (lookPoint), but that it clamps between a 90 degree range, 45 degrees to either side of the torso's current direction.
I've played around with the results of euler angles to make sure I'm getting the desired rotation value for the y axis, but I struggle with when the euler angle should cycle over to 0 again, and I can't seem to figure out how to sort it out.
minRot = myTorso.transform.rotation.eulerAngles.y-180f-45f;
maxRot = myTorso.transform.rotation.eulerAngles.y-180f+45f;
lookDirection = Mathf.Atan2(lookPoint.x - transform.position.x, lookPoint.z - transform.position.z);
lookRotation = Mathf.Clamp(Mathf.Rad2Deg * lookDirection, minRot, maxRot);
myHead.eulerAngles = new Vector3(0,lookRotation,0);
This is causing the head to snap back to one of the extremes when it cannot figure out what it's max or min should be.
Can anyone help me to define the minRot and maxRot so that it accounts for the 180 degree crossover?
This should do what you're looking for. I'm just basing it off of the variables and code provided, so there's a chance things may not work perfectly. So let me know if it doesn't and we can adjust:
lookDirection = Mathf.Atan2(lookPoint.x - transform.position.x,
lookPoint.z - transform.position.z) * Mathf.Rad2Deg;
Quaternion q = Quaternion.Euler(new Vector3(0, lookDirection, 0));
Quaternion targetRotation = new Quaternion();
Quaternion torsoRotation = myTorso.transform.rotation;
// Check if the angle is outside the 45degree range
if (Quaternion.Angle(q, torsoRotation) <= 45.0f)
targetRotation = q;
else
{
// This is to check which direction we're out of range
float d = Mathf.DeltaAngle(q.eulerAngles.y, torsoRotation.eulerAngles.y);
if (d > 0.0f)
target = torsoRotation * Quaternion.Euler(0, -45f, 0);
else if (d < 0.0f)
target = torsoRotation * Quaternion.Euler(0, 45f, 0);
}
myHead.rotation = targetRotation;
I have spaceships that chase each other. They currently move to their target exactly, but I want to replicate physics you might see on water or in zero gravity, where the object would overshoot its target, turn and move back toward it. Possibly hovering around the target back and forth. I've tried addforce and addrelativeforce, and velocity, but those don't seem to give me the desired effect. Any ideas?
This is my code...
Vector3 dir = (new Vector3(x, y, 0) - transform.parent.position).normalized;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
float heading = Mathf.Atan2(dir.x, dir.y);
transform.parent.rotation = Quaternion.Slerp(transform.parent.rotation, Quaternion.Inverse(Quaternion.Euler(0f, 0f, heading * Mathf.Rad2Deg)), Time.deltaTime * 12f);
//transform.parent.position += dir * speed * Time.deltaTime;
//rb.AddForce(dir);
rb.velocity = new Vector3(dir.x * speed, dir.y * speed, 0);
Here's how I achieve that AI in a 2D space shooter:
void FixedUpdate()
{
var target = Player != null ? PlayerObject : Target; // If player is dead, go for secondary target
Vector2 dir = -(Vector2)(transform.position - target.transform.position + targetOffset); // Direction
// dir.magnitude is the distance to target
if (dir.magnitude > reaction * rangeModifier) // If I'm far away from the target, slowly rotate towards it
{
// calculate angle toward target and slowly rotate child ship object toward it in the speed of turnRate
float attackAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90;
ship.transform.rotation = Quaternion.Slerp(ship.transform.rotation, Quaternion.AngleAxis(attackAngle, Vector3.forward), turnRate * Time.deltaTime);
}
else // If I'm close to the target just keep going straight ahead and fire until I'm too far away again
{
My.weapon.Shoot(dir);
}
_rb.velocity = ship.transform.up.normalized * My.Movementspeed; // Add velocity in the direction of the ship's rotation
}
This will give you an AI that goes in eight (8) shapes around the target. If you have many objects running this code I recommend adding a random offset to the target, to simulate swarming and more realistic flight aim.
I added comments to explain some details of the code.
Aight so I've been at this for several days and with how much it's slowing the project down I've made the hard decision to come beg the internet.
I'm working on a physics-based golf game in Unity and am working on implementing the magnus effect. This is my calculation for tangential force based on torque:
void FixedUpdate()
{
float _BallRadius = .0427f;
float _halfmass = Mathf.Sqrt(_BallRadius*.5f );
float _vv = new Vector3(GetComponent<Rigidbody>().velocity.x, 0,
GetComponent<Rigidbody>().velocity.z).magnitude;
//MAGNUS FORMULA (SIDESPIN ONLY)//
float _mag = -((1.429f * Mathf.Abs(_vv)* (2 * Mathf.PI * _halfmass *
GetComponent<Rigidbody>().angularVelocity.y))) * (_BallRadius * .5f);
Vector3 _xmagdir = new Vector3(GetComponent<Rigidbody>().velocity.x, 0, GetComponent<Rigidbody>().velocity.z);
GetComponent<Rigidbody().AddForce(Vector3.Cross(_xmagdir.normalized,Vector3.up)*_mag, ForceMode.Acceleration);
}
This works well enough, but now I need to figure out the correct amount to give the ball to get this result:
Diagram of Desired Curve
I've tried several different approaches, and I've come to the conclusion that this is mostly comes down to trigonometry. Here is where I'm at currently (this is before the tangential direction is applied to the final velocity vector):
Vector3 GetMagnusTorque(float Distance, Vector3 V_BallForce, float xdir)
{
float _BallRadius = .0427f;
float _halfmass = Mathf.Sqrt(_BallRadius*.5f);
Vector3 v = GO_Camera.transform.InverseTransformDirection(V_BallForce);
float HorizontalRange = Distance;
//THIS IS THE LATERAL DISTANCE WE NEED TO TRAVEL//
float opposite = (Mathf.Abs(Mathf.Tan(Mathf.Deg2Rad * xdir))) * (HorizontalRange*.5f);
//THIS IS THE TIME IT WILL NEED TO TAKE//
float time = ((2 * v.magnitude * Mathf.Sin((2 * (v.normalized.y )))) / -Physics.gravity.y);
//THIS IS THE SPEED THE MAGNUS EFFECT WILL NEED TO PRODUCE//
float linearSpeed =Mathf.Abs((opposite/time))*Mathf.Sign(xdir)Mathf.Abs((opposite/time))*Mathf.Sign(xdir)*(Time.timeScale+Time.fixedDeltaTime);
return GO_Camera.transform.TransformDirection((((linearSpeed /( 2 * Mathf.PI * _halfmass * (_BallRadius * .5f))) / 1.429f))*Mathf.Deg2Rad*GO_PGolfBall.transform.up);
}
Logic behind this
The result is very inconsistent based on xdir and V_BallForce, sometimes travels further, sometimes barely at all. This has been a pretty sobering adventure in discovering that I'm pretty bad at math.
If anyone has any advice for this issue I would forever be in your debt.
Update: Here is some visualization of the issue:
visualization
Update: I figured I should include a code sample of the function GetMagnusTorque is called from, to better put things in context:
void BallApplyForce()
{
//_rmult is a [0,1] value that is dependent on the accuracy of the player's swing//
float _rmult = GetRMult();
//GETS BALL READY BY ENABLING PHYSICS AND SETTING ITS ROTATION TO THE CAMERA//
GO_PGolfBall.transform.rotation = new Quaternion(0, GO_Camera.transform.rotation.y, GO_Camera.transform.rotation.z, GO_Camera.transform.rotation.w);
GO_PGolfBall.GetComponent<SCR_GOLFBALLCONTROL>().B_PhysicsActive = true;
//YDIR = PITCH, XDIR = YAW. V_ContactPoint is a [-1,1] Vector2 used similar to Mario Golf//
Vector3 _vdir = Vector3.zero;
float ydir = ((Mathf.Rad2Deg * DIC_Clubs[I_CurrentClub].LandAngle) + (-2.5f * (.1f) * (DIC_Clubs[I_CurrentClub].LoftAngle * Mathf.Rad2Deg)));
float _xdir = Mathf.Rad2Deg * (-(V_ContactPoint.x) * (DIC_Clubs[I_CurrentClub].LoftAngle)*3f);
_vdir.y = ydir;
_vdir = _vdir.normalized;
_vdir.y *= Mathf.Rad2Deg;
//MAX DISTANCE OF THE CLUB BEING USED TO METERS//
float _dist = ((DIC_Clubs[I_CurrentClub].MaxDistance * F_UPower) * _rmult) * .9144f;
//GET FORWARD AND UPWARDS VELOCITIES BASED ON THE DISTANCE GIVEN, XDIR ISN'T FACTORED FOR NOW//
float Vi = Mathf.Sqrt(_dist * -Physics.gravity.y / (Mathf.Sin(Mathf.Deg2Rad * _vdir.y * 2)));
float Vy, Vz;
Vy = Vi * Mathf.Sin(Mathf.Deg2Rad*_vdir.y);
Vz = Vi * Mathf.Cos(Mathf.Deg2Rad*_vdir.y);
GO_Camera.transform.eulerAngles = new Vector3(0, GO_Camera.transform.eulerAngles.y, 0);
Vector3 _velo = GO_Camera.transform.TransformVector(new Vector3(0f, Vy, Vz));
//CALCULATE VERTICAL ANGULAR VELOCITY, THIS DOESNT NEED TO FOLLOW ANY SORT OF PATTERN FOR NOW//
float _verRoll = Mathf.Sign(V_ContactPoint.y - .1f) * ((.7135f) * _dist) * Mathf.Sin(DIC_Clubs[I_CurrentClub].LoftAngle +
((Mathf.Abs(V_ContactPoint.y + .1f)) * (DIC_Clubs[I_CurrentClub].LaunchAngle))) * 60 * Mathf.Deg2Rad;
GO_PGolfBall.GetComponent<Rigidbody>().AddTorque(Vector3.Scale(GO_PGolfBall.transform.right, new Vector3(1, 0, 0)) * _verRoll, ForceMode.VelocityChange);
//CALCULATE HORIZONTAL ANGULAR VELOCITY//
Debug.Log("MAGNUS CALC:" + GetMagnusTorque(_dist, _velo, _xdir));
GO_PGolfBall.GetComponent<Rigidbody>().AddTorque(GetMagnusTorque(_dist, _velo, _xdir), ForceMode.VelocityChange);
//APPLY XDIR TO THE CAMERA ANGLE AND RECALCULATE VELOCITY//
GO_Camera.transform.eulerAngles = new Vector3(0, GO_Camera.transform.eulerAngles.y+_xdir, 0);
_velo = GO_Camera.transform.TransformVector(new Vector3(0f, Vy, Vz));
//APPLY VELOCITY//
GO_PGolfBall.transform.GetComponent<Rigidbody>().AddForce(_velo, ForceMode.VelocityChange);
}
Update 11/21: I'm currently in the process of faking it through trial and error. But for those still interested in solving this anomaly, I may have found in issue in how I'm calculating the direction to apply the side-spin derived force to the ball. Hitting the ball with a lob wedge, which has a maximum distance of 70 yards and a loft angle of 60 deg, the desired curve looks something like this: Lob Wedge This obviously makes no sense, because the ball flies behind the golfer for a short period of time. I'll report back with the final numbers and calculations, though it will not answer the question it could help point it in the correct direction.
Got an issue where the enemy will fire at the player, but always seems to go high or to the side of the player even when the player is stationary and isn't moving. Am I doing something wrong in my code which creates this wild issue or is it just a random annoying bug?
Using the same script for the player albeit it under a different name works, which leads me to believe the issue lies within the fire point. Under the player's script I fire like so:
// Get the place the player has clicked
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(ray, out hitInfo))
{
if(hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
Whereas in the enemy script it is just done through the player's position.
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(player.transform.position,transform.forward, out hitInfo))
{
Is this underlying issue in the Physics.Raycast call then?
Rest of code for reference:
//More above this but doesn't influence the firing
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
{
if (hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
}
}
private void FireAtPoint(Vector3 point)
{
// Get the velocity to fire out at
var velocity = FiringVelocity(point, angle);
Rigidbody rg = Instantiate(bulletPrefab.gameObject, firePoint.position, firePoint.rotation).GetComponent<Rigidbody>();
EnemyBulletController newProjectile = rg.GetComponent<EnemyBulletController>();
newProjectile.speed = velocity;
}
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
// Get the direction of the mouse click from the player, then get the height differential.
Vector3 direction = destination - transform.position;
float height = direction.y;
height = 0;
// Get the distance in a float of the vector3
float distance = direction.magnitude;
// Turn the firing angle into radians for calculations, then work out any height differential
float AngleRadians = angle * Mathf.Deg2Rad;
direction.y = distance * Mathf.Tan(AngleRadians);
distance += height / Mathf.Tan(AngleRadians);
// Calculate the velocity magnitude
float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2 * AngleRadians));
// Return the normalized vector to fire at.
return velocity * direction.normalized;
}
Picture for reference:
Your equation for computing the velocity looks doubtful. Let's re-derive it:
The equations of free-fall motion under constant gravity are:
After rearranging by substituting the first into the second, we find an expression for the firing velocity:
This is different to what you have, as you are missing the h/d term; said term also gives a constraint on the allowed values of θ:
(Basically means that if you fire directly at the target the bullet would never reach due to gravity)
There are many other problems with your code; just to list three:
Why set height to zero?
Why add a correction to distance? The correction has no physical interpretation.
The fix suggested by #BasillePerrnoud
Amended code:
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
Vector3 direction = destination - transform.position;
float height = direction.y;
float distance = Mathf.Sqrt(direction.x * direction.x + direction.z * direction.z); // *horizontal* distance
float radians = angle * Mathf.Deg2Rad;
float hOverd = height / distance;
float tanAngle = Mathf.Tan(radians);
if (tanAngle <= hOverd)
// throw an exception or return an error code, because no solution exists for v
float cosAngle = Mathf.Cos(radians);
direction.Y = distance / cosAngle;
float velocity = Mathf.Sqrt((distance * Physics.gravity.magnitude) /
(2 * cosAngle * cosAngle * (tanAngle - hOverd)));
return velocity * direction.normalized;
}
I think you use Raycast wrongly. According to the doc, the second argument is the direction, not the destination:
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
Should be
if (Physics.Raycast(transform.position, player.transform.position -
transform.position, out hitInfo))
That would explain why it is not firing at the right moment and why the direction is not accurate (since hitInfo is wrong)
We have some sound for when the player is moving or rolling being the player is a ball. We want to increase the pitch of the audio the faster the ball goes. I tried the below code but it doesn't do anything. I think it's because the value of p comes out too small.
I remember reading somewhere that there is something built in to handle this but I can't think of where I saw or it what it was called.
Thanks in advance!
void FixedUpdate()
{
#if UNITY_EDITOR || UNITY_STANDALONE
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 move = new Vector3(-moveHorizontal, 0.0f, -moveVertical);
move = move * (speed / 15f);
//maxSpeed = maxSpeed / 5;
#else
// Player movement in mobile devices
// Building of force vector
Vector3 move = new Vector3(-Input.acceleration.x, 0.0f, -Input.acceleration.y);
// Adding force to rigidbody
move = move * (speed / 15f);
//move = movement * speed * Time.deltaTime;
#endif
rigidbdy.AddForce(move);
var p = rigidbdy.velocity.magnitude / speed;
audio.pitch = Mathf.Clamp(p, 1.0f, 2.0f); // p is clamped to sane values
//Limits the max speed
if (rigidbdy.velocity.magnitude > maxSpeed)
{
rigidbdy.velocity = rigidbdy.velocity.normalized * maxSpeed;
}
}
You can use the map function for easy control over the pitch value.
float mapValue(float mainValue, float inValueMin, float inValueMax, float outValueMin, float outValueMax)
{
return (mainValue - inValueMin) * (outValueMax - outValueMin) / (inValueMax - inValueMin) + outValueMin;
}
You pass in AudioSource.pitch to the mainValue parameter.
For the inValueMin value, you pass in the default/MIN value of the Rigidbody.velocity.magnitude which is 0.
For the inValueMax value, you pass in the MAX value your ball can go.
You can easily determine this number with Debug.Log("RB: " + ballRigidbody.velocity.magnitude); and running the game. 10 seems to be fine for this. You must determine your own value.
The default AudioSource.pitch value is 1, so outValueMin parameter should be 1.
The outValueMax parameter will be the maximum pitch you think is acceptable to you. I found 1.5 to be ok for this so 1.5 will be used for outValueMax.
Whatever you get from the mapValue function is what you assign to the AudioSource.pitch. This gives you much more control over the pitch of you sound. You can read more about this function on the Arduino site.
Remove your current Audio code and replace it with this:
float rigidBodyMangintude = rigidbdy.velocity.magnitude;
float pitch = mapValue(rigidBodyMangintude, 0f, 10f, 1f, 1.5f);
audio.pitch = pitch;
Debug.Log("Pitch: " + pitch);
The mapValue function is at the top of this answer.