I'm programming a starship-game and have just finished the part which manages the rotating. I used a simple if/else-statement to check if the ship must rotate positive or negative in order to face the target as fast as possible. But I saw that the Rotation-Value can get negative, and then the ship does rotate to the wrong direction (It still faces the target-point in the end, but it takes longer). Please tell me what I did wrong :(
The function:
public bool RotateOrMove(Vector2 position)
{
if (IsRotationg == null) //todo
{
Vector2 direction = Position - position;
direction.Normalize();
FinalRotation = (float)Math.Atan2(-direction.X, direction.Y);
IsRotationg = true;
}
if (Equals(FinalRotation, Rotation))
IsRotationg = false;
if (IsRotationg == false)
{
Position = position;
return true;
}
else
{
if (FinalRotation >= Rotation)
{
Rotation += RotationVelocity;
if (FinalRotation - Rotation < RotationVelocity)
{
Rotation = FinalRotation;
IsRotationg = false;
}
}
if (FinalRotation < Rotation)
{
Rotation -= RotationVelocity;
if (FinalRotation - Rotation > -RotationVelocity)
{
Rotation = FinalRotation;
IsRotationg = false;
}
}
return false;
}
}
The Player-Class owns the Ship. When the player press the right mouse-button, this method will be called once per frame until the ship reaches the position where the cursor is pointing at.
if (!Ship.RotateOrMove(Position))
Position -= Velocity;
So if the ship had to rotate and couldn't move, it will remove the velocity it added just before to ensure that the ship won't move.
Hope you understand my problem^^
Math.Atan2 return values from -pi to pi.
to get a smooth rotation you can use this code got from here
private float CurveAngle(float from, float to, float step)
{
if (step == 0) return from;
if (from == to || step == 1) return to;
Vector2 fromVector = new Vector2((float)Math.Cos(from), (float)Math.Sin(from));
Vector2 toVector = new Vector2((float)Math.Cos(to), (float)Math.Sin(to));
Vector2 currentVector = Slerp(fromVector, toVector, step);
return (float)Math.Atan2(currentVector.Y, currentVector.X);
}
private Vector2 Slerp(Vector2 from, Vector2 to, float step)
{
if (step == 0) return from;
if (from == to || step == 1) return to;
double theta = Math.Acos(Vector2.Dot(from, to));
if (theta == 0) return to;
double sinTheta = Math.Sin(theta);
return (float)(Math.Sin((1 - step) * theta) / sinTheta) * from + (float)(Math.Sin(step * theta) / sinTheta) * to;
}
Related
Good day everybody. I'm trying to learn more about rotations in unity and would appreciate some help. I have been bashing my head over this problem for a while now. The purpose of the script I've created is to take another game objects transform in and replicate the difference in rotations of that transform on the game object the script is attached too.
The script needs to be able to follow each independent axis rotation solely, or two of the axes, or even all three. The code below works if all three of the axes are monitored, when the x and z axis are solely monitored, when the y and z axis are solely monitored, but NOT when the x and y axis are solely monitored (The game object that's following (with the script attached)) generates a value for the z axis when trying to rotate just the x and y axis.
It becomes clear that whenever I'm trying to replicate the rotation of two of the axes and the z axis is not one of those axes I'm running into issues. I have done this with quaternions to avoid gimbal lock. I do believe there is an issue with the implementation of my w axis when creating the Quaternion (Which represents the rotation I want to apply to the Game object).
If anyone may provide some insight I'd be very grateful, I am a junior developer and have only started working with unity this year.
Please find the code below
[ExecuteInEditMode]
public class RotationSync2 : MonoBehaviour
{
[SerializeField]
public Transform ExternalTransform;
private Quaternion PreviousExternalTransform;
private Quaternion RotationDifference;
private Quaternion Temp = new Quaternion();
private Quaternion PreviousTemp;
private float xToUse;
private float yToUse;
private float zToUse;
private bool RotationComplete = false;
[HideInInspector] public bool rotateX;
[HideInInspector] public bool rotateY;
[HideInInspector] public bool rotateZ;
[HideInInspector] public bool InvertX;
[HideInInspector] public bool InvertY;
[HideInInspector] public bool InvertZ;
[HideInInspector] public float percentageOfRotationToFollowX = 1f;
[HideInInspector] public float percentageOfRotationToFollowY = 1f;
[HideInInspector] public float percentageOfRotationToFollowZ = 1f;
private float rotationToSetX;
private float rotationToSetY;
private float rotationToSetZ;
// Start is called before the first frame update
void Start()
{
PreviousExternalTransform = ExternalTransform.rotation;
}
// Update is called once per frame
void Update()
{
ClearPreviousRotationValues();
BuildQuarternion();
FindRotateDifference();
SetRotationValues();
SetRotations();
}
public void ClearPreviousRotationValues()
{
rotationToSetX = 0;
rotationToSetY = 0;
rotationToSetZ = 0;
}
private void BuildQuarternion()
{
if (ExternalTransform.rotation.x == PreviousExternalTransform.x && rotateX)
{
Temp.x = 0f;
}
else
{
Temp.x = ExternalTransform.rotation.x;
}
if (ExternalTransform.rotation.y == PreviousExternalTransform.y && rotateY)
{
Temp.y = 0f;
}
else
{
Temp.y = ExternalTransform.rotation.y;
}
if (ExternalTransform.rotation.z == PreviousExternalTransform.z && rotateZ)
{
Temp.z = 0f;
}
else
{
Temp.z = ExternalTransform.rotation.z;
}
Temp.w = ExternalTransform.rotation.w;
}
private void FindRotateDifference()
{
Quaternion a = Quaternion.identity * Quaternion.Inverse(Temp);
Quaternion b = Quaternion.identity * Quaternion.Inverse(PreviousTemp);
if (ExternalTransform.rotation != PreviousExternalTransform && PreviousTemp != null)
{
RotationDifference = b * Quaternion.Inverse(a);
RotationComplete = false;
}
PreviousTemp = Temp;
PreviousExternalTransform = ExternalTransform.rotation;
if (PreviousTemp == null)
{
return;
}
}
public void SetRotationValues()
{
if (rotateX) //Checks if this axis is being used
{
if (InvertX)
{
rotationToSetX = (RotationDifference.x * percentageOfRotationToFollowX) * -1; // set the rotation to the value of the given transform * the percentage to follow * -1 to invert it
}
else
{
rotationToSetX = RotationDifference.x * percentageOfRotationToFollowX; // set the rotation to the value of the given transform * the percentage to follow
}
}
if (rotateY)
{
if (InvertY)
{
rotationToSetY = (RotationDifference.y * percentageOfRotationToFollowY) * -1;
}
else
{
rotationToSetY = RotationDifference.y * percentageOfRotationToFollowY;
}
}
if (rotateZ)
{
if (InvertZ)
{
rotationToSetZ = (RotationDifference.z * percentageOfRotationToFollowZ) * -1;
}
else
{
rotationToSetZ = RotationDifference.z * percentageOfRotationToFollowZ;
}
}
}
public void SetRotations() //Checks which of axis' rotation the transform should follow of the given transform and applies the respective rotations.
{
if (RotationComplete == false)
{
if (rotateX && rotateY && rotateZ)
{
transform.rotation *= new Quaternion(rotationToSetX, rotationToSetY, rotationToSetZ, RotationDifference.w);
}
else if (rotateX && rotateY)
{
transform.rotation *= new Quaternion(rotationToSetX, rotationToSetY, 0f, RotationDifference.w);
}
else if (rotateX && rotateZ)
{
transform.rotation *= new Quaternion(rotationToSetX,0f, rotationToSetZ, RotationDifference.w);
}
else if (rotateY && rotateZ)
{
transform.rotation *= new Quaternion(0f, rotationToSetY, rotationToSetZ, RotationDifference.w);
}
else if (rotateX)
{
transform.rotation *= new Quaternion(rotationToSetX, 0f, 0f, RotationDifference.w);
}
else if (rotateY)
{
transform.rotation *= new Quaternion(0f, rotationToSetY, 0f, RotationDifference.w);
}
else if (rotateZ)
{
transform.rotation *= new Quaternion(0f, 0f, rotationToSetZ, RotationDifference.w);
}
RotationComplete = true;
}
}
}
I think you overcomplicated this a lot and also note that you never want to directly access/assign the individual components of a Quaternion except you are absolutely sure what you are doing!
The components x, y, z alone are not the rotation Euler axes!
I don't know if I fully understand your use case but it sounds to me like what you have/want is
There are two objects A and B
Both have different rotations when starting the app
Whenever object A is rotated you want to perform the same rotation also on object B but maintaining the original rotation difference
additionally there is the option to deactivate or invert individual axes
I think this is a use case where it is actually allowed/the only chance to go through Euler axes instead of Quaternion!
So what you actually want to do is
get the delta rotation of object A between the last and current frame in its local space for each axis individually
assign this same delta rotation(s) to object B in its local space
So I think I would do something like
public Transform ExternalTransform;
[HideInInspector] public bool rotateX;
[HideInInspector] public bool rotateY;
[HideInInspector] public bool rotateZ;
[HideInInspector] public bool InvertX;
[HideInInspector] public bool InvertY;
[HideInInspector] public bool InvertZ;
[HideInInspector] [Min(0)] public float percentageOfRotationToFollowX = 1f;
[HideInInspector] [Min(0)] public float percentageOfRotationToFollowY = 1f;
[HideInInspector] [Min(0)] public float percentageOfRotationToFollowZ = 1f;
private Vector3 lastExternalEuler;
private void Awake ()
{
lastExternalEuler = NormalizeEuler(ExternalTransform.localEulerAngles);
}
// Note: I would use LateUpdate so the object is already rotated for this frame
private void LateUpdate ()
{
var currentExternalEuler = NormalizeEuler (ExternalTransform.localEulerAngles);
var localDelta = NormalizeEulerDelta(currentExternalEuler - lastExternalEuler);
localDelta = ApplySettings(localDelta);
// And then either
transform.Rotate(localDelta);
// or if you want to do something else like smoothing later on
//transform.localRotation *= Quaternion.Euler(localDelta);
lastExternalEuler = currentExternalEuler;
}
// Map all Euler axes into the positive range between 0 and 360
private static Vector3 NormalizeEuler (Vector3 euler)
{
while(euler.x >= 360f) euler.x -= 360;
while(euler.x < 0f) euler.x += 360;
while(euler.y >= 360f) euler.y -= 360;
while(euler.y < 0f) euler.y += 360;
while(euler.z >= 360f) euler.z -= 360;
while(euler.z < 0f) euler.z += 360;
return euler;
}
// map the delta axes into range between -180 and 180
private static Vector3 NormalizeEulerDelta (Vector3 delta)
{
if(delta.x > 180) delta.x = -180 - (180 - delta.x);
if(delta.x < -180) delta.x = -(180 + delta.x);
if(delta.x > 180) delta.y = -180 - (180 - delta.y);
if(delta.y < -180) delta.y = -(180 + delta.y);
if(delta.x > 180) delta.z = -180 - (180 - delta.z);
if(delta.z < -180) delta.z = -(180 + delta.z);
return delta;
}
private static Vector3 ApplySettings (Vector3 delta)
{
delta.x = rotateX ? delta.x : 0f;
delta.y = rotateY ? delta.y : 0f;
delta.z = rotateZ ? delta.z : 0f;
delta.x *= invertX ? -1 : 1;
delta.y *= invertY ? -1 : 1;
delta.z *= invertZ ? -1 : 1;
delta.x *= percentageOfRotationToFollowX;
delta.y *= percentageOfRotationToFollowY;
delta.z *= percentageOfRotationToFollowZ;
return delta;
}
Though basically instead of having individual flags and values
rotateX
invertX
percantageX
why not rather have one single
public float multiplicatorX = 1f;
and accordingly for the other axes? ;)
You only need this single value and it can be 0 (rotateX = false) it can be negative (invertX = true) and it can be an arbitrary percentage
Note: Typed on the phone but I hope the idea gets clear
I'm trying to implement bounce physics on a ball in my game using MonoGame c#. I've googled plenty but I'm unable to understand how to do this.
The circle should be able to hit any of the red lines and bounce realistically (not just invert the velocity).
I'm using this code to detect collision:
public bool IntersectCircle(Vector2 pos, float radius, out Vector2 circleWhenHit)
{
circleWhenHit = default;
// find the closest point on the line segment to the center of the circle
var line = End - Start;
var lineLength = line.Length();
var lineNorm = (1 / lineLength) * line;
var segmentToCircle = pos - Start;
var closestPointOnSegment = Vector2.Dot(segmentToCircle, line) / lineLength;
// Special cases where the closest point happens to be the end points
Vector2 closest;
if (closestPointOnSegment < 0) closest = Start;
else if (closestPointOnSegment > lineLength) closest = End;
else closest = Start + closestPointOnSegment * lineNorm;
// Find that distance. If it is less than the radius, then we
// are within the circle
var distanceFromClosest = pos - closest;
var distanceFromClosestLength = distanceFromClosest.Length();
if (distanceFromClosestLength > radius)
return false;
// So find the distance that places the intersection point right at
// the radius. This is the center of the circle at the time of collision
// and is different than the result from Doswa
var offset = (radius - distanceFromClosestLength) * ((1 / distanceFromClosestLength) * distanceFromClosest);
circleWhenHit = pos - offset;
return true;
}
And this code when the ball wants to change position:
private void GameBall_OnPositionChange(object sender, GameBallPositionChangedEventArgs e)
{
foreach(var boundary in mapBounds)
{
if (boundary.IntersectCircle(e.TargetPosition, gameBall.Radius, out Vector2 colVector))
{
var normalizedVelocity = Vector2.Normalize(e.Velocity);
var velo = e.Velocity.Length();
var surfaceNormal = Vector2.Normalize(colVector - e.CurrentPosition);
e.Velocity = Vector2.Reflect(normalizedVelocity, surfaceNormal) * velo;
e.TargetPosition = e.CurrentPosition;
break;
}
}
}
This code gives a decent result but I'm not using my boundary positions to calculate an angle.
How do I proceed to take those into account?
EDIT:
I've removed the event based update. I've added collision between players and the ball. This is now my map-update method:
foreach (var entity in circleGameEntities)
{
for (int i = 0; i < interpolatePos; i++)
{
entity.UpdatePosition(gameTime, interpolatePos);
var intersectingBoundaries = mapBounds
.Where(b =>
{
var intersects = b.IntersectCircle(entity.Position, entity.Radius, 0f, out _);
if (intersects)
averageNormal += b.Normal;
return intersects;
}).ToList();
if (intersectingBoundaries.Count > 0)
{
averageNormal.Normalize();
var normalizedVelocity = Vector2.Normalize(entity.Velocity); // Normalisera hastigheten
var velo = entity.Velocity.Length();
entity.Velocity = Vector2.Reflect(normalizedVelocity, averageNormal) * velo * entity.Bounciness;
entity.UpdatePosition(gameTime, interpolatePos);
}
foreach (var otherEntity in circleGameEntities.Where(e => e != entity))
{
if (entity.CollidesWithCircle(otherEntity, out Vector2 d))
{
Vector2 CMVelocity = (otherEntity.Mass * otherEntity.Velocity + entity.Mass * entity.Velocity) / (otherEntity.Mass + entity.Mass);
var otherEntityNorm = otherEntity.Position - entity.Position;
otherEntityNorm.Normalize();
var entityNorm = -otherEntityNorm;
var myVelocity = entity.Velocity;
myVelocity -= CMVelocity;
myVelocity = Vector2.Reflect(myVelocity, otherEntityNorm);
myVelocity += CMVelocity;
entity.Velocity = myVelocity;
entity.UpdatePosition(gameTime, interpolatePos);
var otherEntityVelocity = otherEntity.Velocity;
otherEntityVelocity -= CMVelocity;
otherEntityVelocity = Vector2.Reflect(otherEntityVelocity, entityNorm);
otherEntityVelocity += CMVelocity;
otherEntity.Velocity = otherEntityVelocity;
otherEntity.UpdatePosition(gameTime, interpolatePos);
}
}
}
entity.UpdateDrag(gameTime);
entity.Update(gameTime);
}
This code works quite well but sometimes the objects get stuck inside the walls and eachother.
CircleGameEntity class:
class CircleGameEntity : GameEntity
{
internal float Drag { get; set; } = .9999f;
internal float Radius => Scale * (Texture.Width + Texture.Height) / 4;
internal float Bounciness { get; set; } = 1f;
internal float Mass => BaseMass * Scale;
internal float BaseMass { get; set; }
internal Vector2 Velocity { get; set; }
internal float MaxVelocity { get; set; } = 10;
internal void UpdatePosition(GameTime gameTime, int interpolate)
{
var velocity = Velocity;
if (velocity.X < 0 && velocity.X < -MaxVelocity)
velocity.X = -MaxVelocity;
else if (velocity.X > 0 && velocity.X > MaxVelocity)
velocity.X = MaxVelocity;
if (velocity.Y < 0 && velocity.Y < -MaxVelocity)
velocity.Y = -MaxVelocity;
else if (velocity.Y > 0 && velocity.Y > MaxVelocity)
velocity.Y = MaxVelocity;
Velocity = velocity;
Position += Velocity / interpolate;
}
internal void UpdateDrag(GameTime gameTime)
{
Velocity *= Drag;
}
internal bool CollidesWithCircle(CircleGameEntity otherCircle, out Vector2 depth)
{
var a = Position;
var b = otherCircle.Position;
depth = Vector2.Zero;
float distance = Vector2.Distance(a, b);
if (Radius + otherCircle.Radius > distance)
{
float result = (Radius + otherCircle.Radius) - distance;
depth.X = (float)Math.Cos(result);
depth.Y = (float)Math.Sin(result);
}
return depth != Vector2.Zero;
}
}
The surfaceNormal is not the boundary normal, but the angle between the collision point and the center of the circle. This vector takes the roundness of the ball into account and is the negative of the ball's direction(if normalized) as if it hit the surface head-on, which is not needed unless the other surface is curved.
In the Boundary class calculate the angle and one of the normals in the constructor and store them as public readonly:
public readonly Vector2 Angle; // replaces lineNorm for disabiguity
public readonly Vector2 Normal;
public readonly Vector2 Length;
public Boundary(... , bool inside) // inside determines which normal faces the center
{
// ... existing constructor code
var line = End - Start;
Length = line.Length();
Angle = (1 / Length) * line;
Normal = new Vector2(-Angle.Y,Angle.X);
if (inside) Normal *= -1;
}
public bool IntersectCircle(Vector2 pos, float radius)
{
// find the closest point on the line segment to the center of the circle
var segmentToCircle = pos - Start;
var closestPointOnSegment = Vector2.Dot(segmentToCircle, End - Start) / Length;
// Special cases where the closest point happens to be the end points
Vector2 closest;
if (closestPointOnSegment < 0) closest = Start;
else if (closestPointOnSegment > Length) closest = End;
else closest = Start + closestPointOnSegment * Angle;
// Find that distance. If it is less than the radius, then we
// are within the circle
var distanceFromClosest = pos - closest;
return (distanceFromClosest.LengthSquared() > radius * radius); //the multiply is faster than square root
}
The change position code subset:
// ...
var normalizedVelocity = Vector2.Normalize(e.Velocity);
var velo = e.Velocity.Length();
e.Velocity = Vector2.Reflect(normalizedVelocity, boundary.Normal) * velo;
//Depending on timing and movement code, you may need add the next line to resolve the collision during the current step.
e.CurrentPosition += e.Velocity;
//...
This updated code assumes a single-sided non-moving boundary line as prescribed by the inside variable.
I am not a big fan of C# events in games, since it adds layers of delay (both, internally to C# and during proper use the cast of sender.)
I would be remiss not to mention your abuse of the e variable. e should always be treated as a value type: i.e. read-only. The sender variable should be cast(slow) and used for writing purposes.
I'm not quite sure whether I pick correct title for the question, feel free to edit it If you find it misleading.
I'm developing an action game which involving music as the core of its gameplay.
It contains a set of entities (game objects) that move into several directions; top, right, left, bottom and diagonal moves (e.g: top-left, top-right, bottom-left, etc etc).
To determine the position of these entities, there is a value which need to be calculated that involving the music tempo and several properties of the entities itself, which I able produce it in Unity.
I use following function to determine the position of transform of the entities:
private Vector3 _position;
private void DeterminePosition(float offset)
{
// In actual code, the _position is initialized under Start() method
// But for this simplification sake, I'll just put it here
_position = _position == null ? new Vector3(0, 0, 1f) : _position;
if (Direction == Direction.Up || Direction == Direction.RightUp || Direction == Direction.LeftUp)
{
_position.y = offset;
}
if (Direction == Direction.Down || Direction == Direction.RightDown || Direction == Direction.LeftDown)
{
_position.y = -offset;
}
if (Direction == Direction.Left || Direction == Direction.LeftDown || Direction == Direction.LeftUp)
{
_position.x = -offset;
}
if (Direction == Direction.Right || Direction == Direction.RightDown || Direction == Direction.RightUp)
{
_position.x = offset;
}
transform.position = _position;
}
Where the offset is the value that I was talking about before.
The code works perfectly as intended, however the game need to be change.
Instead of fixed direction (e.g: Up, Right, Left, Bottom, RightUp, DownLeft etc etc) we decide to use value of degree for the direction (0 to 360 degree).
And now I've no idea how to impement this. I've tried to use following codes, but it doesn't work:
_position = new Vector3(offset, offset, transform.position.z);
// Where the direction is between 0 .. 360
transform.position = Quaternion.Euler(0, direction, 0) * _position;
Can anybody else come up with solution?
Thanks in advance!
If offset represents the distance from the new position to (0, 0, transform.position.z) (calling that the origin since only x and y positions seem to change), and direction is the angle measured counterclockwise in degrees between the positive x-axis and the vector from the origin to the the new position, you can get the new x and y positions using offset and sin and cos of the direction:
_position.x = offset * Mathf.Cos(Mathf.Deg2Rad * direction);
_position.y = offset * Mathf.Sin(Mathf.Deg2Rad * direction);
_position.z = transform.position.z; //assuming the z position stays the same
transform.position = _position;
Edit
To account for scaling, I think you will need to replace offset depending on the direction.
float multiplier = 0f;
if(0 <= direction && direction <= 45)
{
multiplier = Mathf.Abs(offset / Mathf.Cos(Mathf.Deg2Rad * direction));
}
else if(45 < direction && direction <= direction <= 135)
{
multiplier = Mathf.Abs(offset / Mathf.Sin(Mathf.Deg2Rad * direction));
}
else if(135 < direction && direction <=225)
{
multiplier = Mathf.Abs(offset / Mathf.Cos(Mathf.Deg2Rad * direction));
}
else if(225 < direction && direction <= 315)
{
multiplier = Mathf.Abs(offset / Mathf.Sin(Mathf.Deg2Rad * direction));
}
else if(315 < direction && direction <= 360)
{
multiplier = Mathf.Abs(offset / Mathf.Cos(Mathf.Deg2Rad * direction));
}
_position.x = multiplier * Mathf.Cos(Mathf.Deg2Rad * direction);
_position.y = multiplier * Mathf.Sin(Mathf.Deg2Rad * direction);
_position.z = transform.position.z;
transform.position = _position;
This will break the coordinate plane into 4 90-degree sections (if you combine 315 < direction <= 360 and 0 <= direction < 45) where you are using sin or cos of direction to create a multiplier that should account for the regions where offset needs to be scaled depending on x and y components of the displacement.
Im working with Xna 4, doing a game where i have a game object (spaceship) that moves in a 3D world on the Y axis = 0 plane.. Aka 2.5D..
Until now i used a very complex angle calculation to create a rotation between 2 vectors, yet that algorithm lacks the ability to take into account that the object already is rotated. so the results get funkey..
Therefore i was hoping that someone, could show me a smart and easily implementable way to use Matrices and vector math, to do such a rotation over time thingy.
What i noticed in previous searches, is that people have the following variables in their object classes:
- Position vector3
- right vector3
- up vector3
- Rotation matrix
- transformMatrix matrix
- velocity vector3
- etc..
often i ask myself why its needed to have that many variables for a simple current position.. or maybe im not understanding.. anyways..
I have the position, rotation and transformsMatrix currently, and would like to know what else i need and HOW to calculate it, and then how you would implement JUST the rotation over time.
The method that is called by my right-click movement command trig sends a vector3 position on the Y = 0 plane of where the click happened.
public void MoveCommand(Vector3 pos){ }
ive tested this, and the "pos" given is accurate. Any help will be apreciated ..
You should check the Matrix.CreateRotationX Y or Z acording to the rotation that you want.
X,Y or Z is the axis of the rotation,
If you choose Y you will see a "2D" rotation (yaw) because that is the axis that you are using as depth.
If you choose X or Z axis you will see "3D" rotations (pitch and roll)
The code should look like this:
WorldMatrix = Rotations * Translation
where
Rotations = Matrix.CreateRotationX(angleRadians)
and
Translation = Matrix.CreateTranslation(Position);
The world matrix is the matrix that is affecting your model, the view and projection depends on the camera
Now if you want to know the angle between vectors you should check the dot product or the atan2 function because you are in 2D
Vector3 Position;
float Rotation;
Matrix World
{
get
{
return Matrix.CreateRotationZ(Rotation) * Matrix.CreateTranslation(Position);
}
}
public void RotateInstantly(Vector3 position)
{
Rotation = Math.Atan2(Position.Y - position.Y, Position.x - position.x);
}
public void RotateIncremently(Vector3 position, float maxStep)
{
float targetRotation = Math.Atan2(Position.Y - position.Y, Position.x - position.x);
float diff = targetRotation - Rotation;
if (Math.Abs(diff) > maxStep)
{
if (targetRotation > Rotation)
Rotation += maxStep;
else
Rotation -= maxStep;
}
else
Rotation = targetRotation;
}
You can use the RotateIncremently like this:*
float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
float maxRotationVelocity = Math.TwoPi; //2* Pi is one revolution.
RotateIncremently(target.Position, maxRotationVelocity * dt);
Thanks to Stig-Rune SkansgÄrd's reply (if your danish hi5), i fixed my old Angle calculation and got it to work in every case.. So i thought i would answer my own question with the solution that works for me, so that future visitors can benefit of it.. This is a snippet of a very large Ship class + a helper method to calculate the angle:
public static float CalculateAngleFromVectors(Vector3 v1, Vector3 v2) {
float x = 0;
float X = v2.X - v1.X,
Z = v2.Z - v1.Z;
if (Z == 0) {
x = (X < 0 ? 90 : 270);
} else if (X == 0) {
x = (Z < 0 ? 180 : -180);
} else {
float temp = MathHelper.ToDegrees((float)Math.Atan(X / Z));
if (X < 0) {
x = (Z < 0 ? Math.Abs(temp) : 180 - Math.Abs(temp));
} else {
x = (Z < 0 ? 360 - Math.Abs(temp) : Math.Abs(temp) + 180);
}
}
return x;
}
this method gets you the float angle (in degrees) to rotate your ship (from the standard 0 degrees starting point)..
to use it, simply make some update / animation method in your class that does something like this:
float desiredRotation = 0, currentRotation = 0, totalElapsed = 0, timePerFrame = 0.05f;
if (desiredRotation != 0) {
totalElapsed += elapsed;
if (totalElapsed > timePerFrame) {
if (isRotationComplete()) {
rotX += MathHelper.ToRadians(desiredRotation);
currentRotation = desiredRotation = 0;
} else if (desiredRotation > currentRotation) {
currentRotation += shipTurnSpeed;
} else if (desiredRotation < currentRotation) {
currentRotation -= shipTurnSpeed;
}
totalElapsed -= timePerFrame;
}
}
EDIT: and the completion check:
private bool isRotationComplete() {
bool b = false;
if (desiredRotation > currentRotation && currentRotation + shipTurnSpeed > desiredRotation) {
b = true;
} else if (desiredRotation < currentRotation && currentRotation - shipTurnSpeed < desiredRotation) {
b = true;
}
return b;
}
essentially what this does is to always check wether or not DesiredRotation is bigger than 0.. if it is, then that means the player has given the command to rotate (or the AI).. CurrentRotation in my example is ofc how much has been rotated since a rotation command was last given, and is set to 0 once the rotation is complete.. You should have a Rotations matrix that uses a different variable to display the rotation with.. mine is this:
public float rotX { get; set; }
public float rotY { get; set; }
public Vector3 position { get; set; }
public Matrix Transform {
get {
return (Matrix.Identity *
Matrix.CreateScale(scale) *
Matrix.CreateRotationY(MathHelper.Pi) *
Rotation *
Matrix.CreateTranslation(position));
}
}
public float ShipCurRotation { get { return (rotX + MathHelper.ToRadians(currentRotation)); } }
public Matrix Rotation { get { return (Matrix.CreateRotationY(ShipCurRotation) * Matrix.CreateRotationX(rotY)); } }
The rotX variable is set in my animation when the rotation is complete, and also at Init.. And here is how i use the rotation angle that my first code snippet generates for me:
public void MoveToPosition(Vector3 pos) {
desiredRotation = (CalculateAngleFromVectors(position, pos) - MathHelper.ToDegrees(rotX));
isMoving = true;
}
.. this makes a smooth customizable rotation, transforms and movement setup.. In a XZ plane ofc.. the Y axis is UP and always 0..
Feel free to comment on this, if you have suggestions or changes or ideas to make things even better.. Im always open for improvements.. Thanks for the replies, and hope this helps alot of new developers, took me forever to gather this stuff from the web..
PS. the rotation can be applied directly to rotX for an instant rotation, bypassing the animation and turnspeed..
WithRegards
MatriXz
I am trying to implement basic (for now) collision detection into my platformer. I have tiles that are each 16 x 16 in size. The character is 32 x 32 pixels in size and has its own bounding box. Now, in my Tile class, I have a bool, isSolid. Each of these tiles in my array also have a rect for their respective bounding boxes.
I am checking to see if there's an intersection between the player and tiles by doing:
if (player.GetBoundingBox().Intersects(map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].bounds) && map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].isSolid)
{
...
}
Now, my problem is that this is extremely inaccurate as I'm rounding off the position. I'm tired as heck right now and for the life of me I can't figure out how to properly do this. What is the best way to approach this issue?
Well this might not be exactly "basic", It works very nicely and dosen't have any problems because it calculates the X axis and Y axis seperatley, this collision structure will help you later on. (I switched to this from the old Platformer Starter kit code, which was very glitchy)
Assuming you already have methods for gravity, lets get started.
This should be after your falling and velocity logic, It will see what axises need to be checked.
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; //If you havent already, get the elapsed time
if (velocity.X != 0f)
{
Position += velocity.X * Vector2.UnitX * elapsed;
HandleCollisions(CollisionDirection.Horizontal);
}
if (velocity.Y != 0f)
{
Position += velocity.Y * Vector2.UnitY * elapsed;
HandleCollisions(CollisionDirection.Vertical);
}
Now for the very important HandleCollisons method
private void HandleCollisions(CollisionDirection direction)
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = player.GetBoundingBox();
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
Rectangle tileBounds = Level.GetBounds(x, y);
// If this tile is collidable,
bool IsSolid = map.tiles[x,y].IsSolid;
Vector2 depth;
if (isSolid && TileIntersectsPlayer(BoundingRectangle, tileBounds, direction, out depth))
{
if ((collision == ItemCollision.Platform && movement.Y > 0))
continue;
isOnGround = true;
if (isSolid || isOnGround)
{
if (direction == CollisionDirection.Horizontal)
{
position.X += depth.X;
}
else
{
isOnGround = true;
position.Y += depth.Y;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
public static bool TileIntersectsPlayer(Rectangle player, Rectangle block, CollisionDirection direction, out Vector2 depth)
{
depth = direction == CollisionDirection.Vertical ? new Vector2(0, player.GetVerticalIntersectionDepth(block)) : new Vector2(player.GetHorizontalIntersectionDepth(block), 0);
return depth.Y != 0 || depth.X != 0;
}
Thats it for that! It will detect collisons, but we need to allow it to figure out how much to push the player back up once it collides! You will need these two extension methods.
public static float GetHorizontalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfWidthA = rectA.Width / 2.0f;
float halfWidthB = rectB.Width / 2.0f;
// Calculate centers.
float centerA = rectA.Left + halfWidthA;
float centerB = rectB.Left + halfWidthB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceX = centerA - centerB;
float minDistanceX = halfWidthA + halfWidthB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceX) >= minDistanceX)
return 0f;
// Calculate and return intersection depths.
return distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
}
public static float GetVerticalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfHeightA = rectA.Height / 2.0f;
float halfHeightB = rectB.Height / 2.0f;
// Calculate centers.
float centerA = rectA.Top + halfHeightA;
float centerB = rectB.Top + halfHeightB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceY = centerA - centerB;
float minDistanceY = halfHeightA + halfHeightB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceY) >= minDistanceY)
return 0f;
// Calculate and return intersection depths.
return distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
}
Note you may need to modify this a bit, as the players position is the BOTTOM left. Also a collision enum is needed, for vertical and horizontal. Please tell me if anything seems missing in this.