My game involves ships moving and rotating around a target (i.e., an enemy ship). Rotation depends on whether the user wants to rotate by port/starboard, or just by the closest side to the enemy.
The problem:
The angles are being wrapped with MathHelper.WrapAngle(). Keeping the angles between PI and -PI works great, until the rotating ship gets to the point where -3.141 becomes 3.141 (and vice versa). For example, the ship is rotating to port correctly, then when it hits this line it flips over starboard, then back to port again, then starboard again and so on!
I would be very grateful if the community could point out:
What I can do to make the ship rotate logic work correctly when going over the PI/-PI wrap 'barrier'
Point out any inefficiencies in my code (I'm sure there are many, and I'm sure there are many other ways to do this more efficiently)
Link to any relevant articles or tutorials that can help me overcome this issue (this is my first game)
Additional Information:
Ship.ShipMoveState.NoMoveRotate is essentially a flag that tells the ship to rotate (starting at 100th of max speed up until we hit max speed) either port or starboard, whichever is closest. The ship rotates to these sides as that is where the weapons are located. ShipMoveState.AwaitFurtherOrders is tells the ship to rotate depending on the difference in angle between closest side (port/starboard) and angle to enemy.
ShipCompartment primeCompartment = TargetShip.CenterCompartment;
if (FireState == ShipFireState.FireAtTarget)
primeCompartment = TargetCompartment;
// If ship is to the left of target, below will work
Vector2 distanceToDestination = primeCompartment.Position - CenterCompartment.Position;
float angleToEnemy = (float)Math.Atan2(distanceToDestination.Y, distanceToDestination.X);
angleToEnemy = MathHelper.WrapAngle(angleToEnemy);
CenterCompartment.Rotation = MathHelper.WrapAngle(CenterCompartment.Rotation);
float portBatteryAngle = MathHelper.WrapAngle(CenterCompartment.Rotation - Helpers.RightAngle);
float starboardBatteryAngle = MathHelper.WrapAngle(CenterCompartment.Rotation + Helpers.RightAngle);
float allowance = 0.005f;
bool portIsClosest = false;
switch (primaryFacing)
{
case PreferredFacing.None:
// If port battery not facing enemy
if (angleToEnemy > (MathHelper.WrapAngle(portBatteryAngle + allowance))
|| angleToEnemy < (MathHelper.WrapAngle(portBatteryAngle - allowance)))
{
// And starboard battery not facing either
if (angleToEnemy > (MathHelper.WrapAngle(starboardBatteryAngle + allowance))
|| angleToEnemy < (MathHelper.WrapAngle(starboardBatteryAngle - allowance)))
MoveState = Ship.ShipMoveState.NoMoveRotate;
else
MoveState = ShipMoveState.AwaitFurtherOrders;
}
else
{
portIsClosest = true;
MoveState = ShipMoveState.AwaitFurtherOrders;
}
if (MoveState == ShipMoveState.AwaitFurtherOrders)
{
float diff = 0f;
if (portIsClosest)
diff = angleToEnemy - portBatteryAngle;
else
diff = angleToEnemy - starboardBatteryAngle;
RotateShip(diff);
}
else if (MoveState == Ship.ShipMoveState.NoMoveRotate)
{
// Turn to port (if target is between 6 and 9 o'clock)
if (angleToEnemy < portBatteryAngle)
RotateShip(-MaxRotation / 100);
// Turn to starboard (if target is between 3 and 6 o'clock)
else if (angleToEnemy > starboardBatteryAngle)
RotateShip(MaxRotation / 100);
else
{
if (angleToEnemy > portBatteryAngle && angleToEnemy < starboardBatteryAngle)
{
// Turn to starboard (if target is between 9 and 12 o'clock)
if (angleToEnemy < CenterCompartment.Rotation)
RotateShip(MaxRotation / 100);
// Turn to port (if target is between 12 and 3 o'clock)
else
RotateShip(-MaxRotation / 100);
}
}
}
break;
}
Please let me know if you require any further information. Thank you very much for your assistance.
Instead of, for instance,
a < wrap(b-c)
use
0 < wrap(b-c-a)
or
0 > wrap(a-b+c)
This makes it a little less readable, but is the correct way to compare (supposedly small) angle differences.
Related
I'm working on a game for game jam and part of it is to make platforms that move smoothly. They slow down at the ends of their movement before turning around. The platforms simply move side to side or up and down between two waypoints(which are just empty transforms). I have code that uses cosine to determine the speed which works well except it doesn't align with the waypoints, the platforms tend to slow and change direction before ever reaching the waypoints. I need a way to use the distance between the waypoints as a variable in determining how the cosine equation changes speed so that the platforms slow and reverse direction exactly at the waypoints.
Here is what I have so far:
void Side_to_side()
{
if (waypointIndex < horWaypoints.Length)
{
platformSpeed = (1f * (float)Mathf.Cos(2f * (float)Mathf.PI * 1f * totalTime));
Vector3 targetPosition = horWaypoints[waypointIndex].position;
float delta = platformSpeed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, targetPosition, delta);
if (transform.position.x == targetPosition.x && transform.position.y == targetPosition.y)
{
if (waypointIndex + 1 == horWaypoints.Length)
waypointIndex = 0;
else
waypointIndex++;
}
}
else
{
waypointIndex = 0;
}
//Translate platform back and forth between two waypoints
}
As I have said this code moves the platforms in the motions i want but they don't use the waypoints as turn around points. I understand I could do away with the waypoints and just calculate how far I would like each platform to go before turning around individually but that would take time to do it for each platform whereas I'd like to quickly put down waypoint pairs for them to use and the script calculates what the perfect values would be to match the waypoint locations.
If I understand you correctly you want to move an object forth and back between exactly two positions and apply some smoothing to the movement.
I would rather use a combination of Vector2.Lerp with a Mathf.PingPong as factor and you can then apply ease in and out using additionally Mathf.SmoothStep.
This could look like e.g.
public Transform startPoint;
public Transform endPoint;
// Desired duration in seconds to go exactly one loop startPoint -> endPoint -> startPoint
public float duration = 1f;
private void Update ()
{
// This will move forth and back between 0 and 1 within "duration" seconds
var factor = Mathf.PingPong(Time.time / (2f * duration), 1f);
// This adds additional ease-in and -out near to 0 and 1
factor = Mathf.SmoothStep(0, 1, factor);
// This interpolates between the given positions according to the given factor
transform.position = Vector2.Lerp(startPoint, endPoint, factor);
}
you could of course still use cosine if necessary, basically any function that returns a value between 0 and 1. You just have to use the correct multiplier in order to achieve the desired duration in seconds.
Note: Typed on the phone and not 100% sure on the math but I hope the idea gets clear
I have a bouncing ball application and I have to extend it to prevent overlapping of the balls.
When ball overlaps another, they should move away as in real life.
I have to extend the given MoveBall method:
private void MoveBall()
{
prevX = x;
prevY = y;
x += xVelocity;
y += yVelocity;
// Is there too closed ball?
foreach (Ball ball in parentForm.balls)
{
distance = Math.Sqrt(Math.Pow((double)(ball.prevX - prevX), 2) +
Math.Pow((double)(ball.prevY- prevY), 2));
overlap = ((radius + ball.radius) - distance);// +ball.radius;
if (ball.id != this.id &&
ball.id != lastID &&
overlap > 0)
{
lastID = this.id;
if (xVelocity > 0) // roading right
{
xVelocity = -xVelocity;
x -= xVelocity - ball.xVelocity;
}
else if (xVelocity <= 0) // roading left
{
xVelocity = -xVelocity;
x += xVelocity + ball.xVelocity;
}
if (yVelocity > 0)
{ // going up
yVelocity = -yVelocity;
y -= yVelocity - ball.yVelocity;
}
else if (yVelocity <= 0) // down
{
yVelocity = -yVelocity;
y += yVelocity + ball.yVelocity;
}
}
}
// ***********************************************
// ***************** END MY CODE *****************
if (x > parentForm.Width - 10 - (radius) || x < 0)
{
if (x < 0) x = 0;
if (x > parentForm.Width - 10) x = parentForm.Width - 10 - radius;
xVelocity = -xVelocity;
}
if (y > parentForm.Height - 40 - (radius) || y < 0)
{
if (y < 0) y = 0;
if (y > parentForm.Height - 40) y = parentForm.Height - 40 - (radius);
yVelocity = -yVelocity;
}
}
x,y, xVelocity, yVelocity, radius, prevX, prevY declared as int.
overlap, distance as double.
When 2 overlap, they are getting stuck. Why?
Unfortunately, I can't upload all source code because there are lot of modules.
I'm using Visual C# Express 2010.
As no Question is asked explicitly, I will assume the question "Why are the balls sticking together?"
You have only shown one loop in source code, that's not enough ;-) To check all possible collisions, you need to check n*(n-1)/2 possible collisions. That is normally done with two loops. You have to put in careful measures to avoid handling the same collision twice.
The reason that your balls get stuck is that you handle the same collision multiple times. For example two balls colliding exactly horizontal: The left one has velocity 5 and x-position of 100. The other one shall have a position of 110 and velocity of -6. When the collision happens:
x is set to 105.
Collision detected: x is set to 104 and velocity to -5.
The other Ball handles the same collision:
He moves according to his velocity to position 104.
Collision handling: His velocity becomes 6 and position becomes 105.
The balls were at 100 and 110 resp. and have been moved to 104 and 105. While the velocities are now pointing away from each other, the collision handling in the following step will invert them again. So the positions are close together and the velocities are changing sign every frame. The balls seem "stuck to each other".
I hope the answer helps you to understand your problem. For a better implementation of an elastic collision (that handles each collision exactly once) look here: Ball to Ball Collision - Detection and Handling
Having stumbled upon similar issues when I made my first attempts at collision detection algorithms, I'll try to describe what I think is the problem here.
Maybe the balls move fast enough so that, before collision is even detected by your code, they are already partially "inside" each other. When collision detection comes and notices that, it does what it's supposed to do: change the planned trajectories of the objects according to the details of the collision that just happened. The problem is that, because these objects got sort-of "merged" before collision detection caught them, they can't get unstuck because collision detection is fired again, trapping them with each other.
If this is the source of the problem, then maybe the above code would work with a small enough velocity vector. Of course, that's not a real solution, but if it does work for very small velocities, it probably confirms my hypothesis and you have some idea regarding how to proceed.
I have created a simple method that detects collision between the two balls by calculating the distance. I was wondering, once the collision is detected, how could I update the balls positions to not allow the balls to enter each other(intersect)
private void BallCollisionBlueRed()
{
double fDist;
CentreAX = redBall.Left + ball.Width / 2;
CentreAY = redBall.Top + ball.Height / 2;
CentreBX = blueBall.Left + ball.Width / 2;
CentreBY = blueBall.Top + ball.Height / 2;
vDx = CentreBX - CentreAX;
vDy = CentreBY - CentreAY;
fDist = Math.Sqrt((vDx * vDx) + (vDy * vDy));
if (fDist < radA + radB)
{
// Help!
}
}
vDx and vDy are only used to hold the value for the calculations. I am controlling both the balls with arrow keys(players), I don't want them to bounce off each other, but just not allow them to intersect.
You need to picture the interaction in your head. When the distance is exactly zero the objects will bounce and start moving away from each other.
It's been too long since college to calculate the new trajectory BUT the main thing will be that, if radA + radB - fDist is, say -4, you'll need to set the new distance to radA + radB + 4.
That will accomodate for any low fps you have (until they lag so bad that they go THROUGH each other before you can detect the collision :-p
For some good advice on this type of physics in I would read this blog post:
http://www.wildbunny.co.uk/blog/2011/04/06/physics-engines-for-dummies/
I am trying to create a jump method in XNA, but I am facing a lot of problems, it doesn't work for me, I've been trying doing it for like 2 hours long and still no "luck". Can anybody give me a code sample, or at least a direction?
Note: I want the jump to be realistic, using gravity and such.
Thank you!
I erased all my work but here's the latest I tried, I know it shouldn't work for sure, but still..
public void Jump(GameTime gameTime)
{
float currentTime = (float)0.1;
if (position.Y == 200)
{
position.Y += velocity.Y*currentTime -gravity * (float)(Math.Pow(currentTime,2)) / 2;
}
if (position.Y == 200 + jumpHeight)
{
position.Y -= velocity.Y * currentTime - gravity * (float)(Math.Pow(currentTime, 2)) / 2;
}
}
I see that you've learned the equations of motion from your code (which I don't think was in place when I made my comment). However you're a little off, your issue is with your time variable. You're passing in your game time, but then using .1f for your time variable. What you really want for your time variable is the time since you started the jump. Further, position.Y is unlikely to ever be exactly equal to 200 or 200 + jumpHeight. It's a float (I assume), so you can never trust that it'll be a nice round number. If you want to specify an exact maximum jump height, you'll have to perform some equations before and set your velocity.Y accordingly (solve for when velocity equals 0 i.e. the top of your jump).
So, to fix your original code I think something like this will work, albeit totally untested:
public void Jump(GameTime gameTime)
{
if(jumping && position.Y > groundLevelAtPlayer) {
//Get the total time since the jump started
float currentTime = gameTime.totalTime - character.timeofjumpStart;
//gravity should be a negative number, so add it
position.Y += (velocity.Y * currentTime)
+ (gravity * ((float)(Math.Pow(currentTime, 2)) / 2));
}
else
{
jumping = false;
}
}
How exactly do you want to make your player jump? Since you mentioned realism I am assuming that you want your player to jump and move across both the X and Y axis, so that it will move along an arc(parabola if you will).
If that is what you are after, you will need to replicate projectile motion. This previous SO thread contains various methods in which you could implement such motion.
Edit:
You should be able to use the same equations presented. For vertical jumps, the angle will be 90 degrees, or pi/2 if you are working with radians. If you are pressing your direction keys, you will have to use the same equations. The angle at which you want your player to start the jump will have to be selected by yourself. Usually maximum range is obtained at an angle of 45 degrees (pi/4) assuming that the same force is used, so your option is really between 0 and 45 degrees.
Heads up: Even though this problem arose while I was working with Unity, it has nothing specific to Unity, and is more about programming logic, so please don't shy away.
I'm using Unity and rotating an object by script. The thing is, if I rotate it to, say, 180 degrees, the object does not rotate exactly to that much and tends to stop at between 179 and 181 degrees. So, to check if rotation is complete I check if the rotation angle is targetAngle +/- 1, which works.
I check using
if (transform.eulerAngles.z > lowerLimit && transform.eulerAngles.z < upperLimit)
where
lowerLimit = targetAngle-1;
upperLimit = targetAngle + 1;
Now, the problem arises when the targetAngle is 0. In this case, my script checks if rotation angle is between -1 and 1. But, -1 should really be 359, so it needs to check if the angle lies between 359 and 1.
How can I implement this?
In other words, I guess I'm asking how to implement a wrap-around number system.
EDIT
Found one work-around. If targetAngle is 0, I treat is specially. It works, but isn't the most elegant.
if (targetAngle == 0.0)
{
if ((transform.eulerAngles.z > 359.0 && transform.eulerAngles.z <= 360.0) || (transform.eulerAngles.z >= 0.0 && transform.eulerAngles.z <= 1))
{
rotate = false;
}
}
else
{
if (transform.eulerAngles.z > targetAngle - 1 && transform.eulerAngles.z < targetAngle + 1)
{
rotate = false;
}
}
You could do ...
lowerLimit = (targetAngle % 360) + 359; //360 - 1;
upperLimit = (targetAngle % 360) + 361; //360 + 1;
if (((transform.eulerAngles.z + 360) % 360) > lowerLimit
&& ((transform.eulerAngles.z + 360) % 360) < upperLimit)
This moves the check away from the zero and you wouldn't have to deal with positive/negative checking.
EDIT
The % operator on the targetAngle restricts the rotating to +/-359 degrees, so a target angle of 721 would come down to 1, and a target angle of -359 would come down to 1. This should do nicely for all cases I think.
EDIT 2
To fix the last case you mentioned in your comment, I guess you'd need to apply the same wrapping logic to your transform.eulerAngles.z values. Probably best to put this wrapping in an extra function now, so try this:
int wrapNumber(int input) // replace int with whatever your type is
{
// this will always return an angle between 0 and 360:
// the inner % 360 restricts everything to +/- 360
// +360 moves negative values to the positive range, and positive ones to > 360
// the final % 360 caps everything to 0...360
return ((input % 360) + 360) % 360;
}
lowerLimit = wrapNumber(targetAngle) + 359; //360 - 1;
upperLimit = wrapNumber(targetAngle) + 361; //360 + 1;
if (wrapNumber(transform.eulerAngles.z) + 360 > lowerLimit
&& wrapNumber(transform.eulerAngles.z) + 360 < upperLimit)
Depending on how often you need to use this, checking for some cases might remove unneeded overhead. For example, the final % 360 within wrapNumber is only needed if the input was positive. If you're calling this ten times per minute it probably won't matter. If you're calling it a hundred times per second, you may want to check how it performs in this situation.
This may be an old thread but after looking at many different snippets all trying to deal with Wrapping I found that Unity has a nice builtin function that simply takes care of business, At least in my case that the end result i required was a lerp so i only had to change it to LerpAngle and it returned a solid result.
Mathf.LerpAngle is your friend.. solved all my issues with popping etc..
http://docs.unity3d.com/ScriptReference/Mathf.LerpAngle.html