Link to home
Start Free TrialLog in
Avatar of EFileTahi-A
EFileTahi-AFlag for Portugal

asked on

Top view (2D) Battleship following the mouse cursor (with bearing and limited turning speed)

Ok, this is a difficult one (at least for me).

[Preferably with C# and GDI+ / managed DX]

Imagine that I have an image of a top view battleship. I need a battleship (picture) to follow the mouse around the screen at a given speed and limited turn rate (turning at its own center of course).

How to do this I have no idea as my math skills are poor, specially at trigonometry
Thank you very much for any assistance!

Pseudocode:




public void RenderBattleship(ShipClass _ship)
{
         //Rotates the battleship based on maxturnRate and saves its current bearing to its class;
         [DO THE MATH MAGIC]
         (_ship._currentbearing = _newBearing)

         //Move the battleship based on its current bearing and speed
         [DO THE MATH MAGIC]
         (_ship._currentXpos)
         (_ship._currentYpos)
         (_ship._currentbearing) 
         (_ship._currentSpeed)

         (_ship._currentXpos = _newPosX)
         (_ship._currentYpos = _newPosY)

         (Renders the rotated picture)
}

Open in new window

Avatar of ambience
ambience
Flag of Pakistan image

I would suggest that you look into XNA development kit, freely downloadable from microsoft.com. IT has some easy to use classes for Vectors and Matrices. You can actually build your game in 3D with orthographic projection (no prespective) and use billboards. This way rotations and other math can be easily applied.
Here is some sample
Vector3d shipPos = new Vector3d(0,0,0);
Vector3d velocity = new Vector(0, 1, 0);
float speed = 2.5;
shipPos = shipPos + velocity * speed;  // move forward in the direction moving.
I cant remember the rotation equations but those are pretty staightforward too, creating a rotation matrix and applying it on the velocity vector.
 
Check this sample kit from XNA website
http://creators.xna.com/en-US/starterkit/spacewar 
Avatar of phoffric
phoffric

First suppose your current position is at (0,0)
Let the mouse position be called: (mx,my)

If your mouse position is (0,0) or very close to (0,0), you can ignore it and stay on course. (This may help if you run into problems, since if you are extremely close, then a 1 pixel movement can represent a large change in bearing.)

Draw a triangle so that hypotenuse is from (0,0) to (mx,my)
Then vertical side has length my and horizontal side has length mx.
tan theta = my/mx
theta = arctan my/mx

But arctan returns degrees (or radians) in the range [-90, +90], i.e., the first and fourth quadrants. You need to get the range in all four quadrants. This is call "The Arctangent Problem" in this link:
      http://hyperphysics.phy-astr.gsu.edu/hbase/ttrig.html#c3
The method of getting the angle in the correct quadrant is shown in that link.

But you may have an atan2 function which does this work for you. This function returns an angle in all quadrants. If it returns radians, and you need degrees, then the conversion is: degrees = (360/2*pi)*radians. So, using atan2 can return degrees in the range [-180,+180]
     http://en.wikipedia.org/wiki/Atan2

So, if your current position is (0,0), then
    new bearing = (360/2*pi) * atan2( my, mx );
Here is a link for the C function atan2:
    http://www.cplusplus.com/reference/clibrary/cmath/atan2/

If your current position is at (cx, cy), then you draw the triangle in a similar way, but now the lengths are (mx - cx) and (my - cy) for the horizontal and vertical lengths, respectively.

    new bearing = (360/2*pi) * atan2( my - cy, mx - cx );
Now that you have the new bearing and the current bearing, then in each frame you need to add or subtract delta_bearing to the  current bearing (which is the limited turn rate per frame).

Now, assuming that bearing of 0 is due East; of 90 is due North; of -90 is due South; and of 180 (or -180) is due West, then how do you know whether to add or subtract. You could do either, but the assumption is that you want to take the rotation that gets you to the new bearing as fast as possible.

Let cb and nb be the current and new bearings;
and let ltr = limited_turn_rate>0.

If the delta angle is large, you may want to consider reducing the speed so as to reduce the arc of the ship's path.

For a given bearing, ß, and speed, S, the ship's velocity is:
    (S*cos ß, S*sin ß)

If cb equals nb, then no change. For other cases, below is pseudo code to show whether to add or subtract a positive ltr to the current bearing:
If nb*cb > 0     // (i.e., both have same sign)
    if nb > cb, then add ltr
    else subtract ltr

else if cb >= 0  // (i.e., nb < 0)
    if cb - 180 > nb      (e.g., nb = -170, cb = 30)
       special_add ltr    (i.e., if result > 180, subtract 360)
    else subtract ltr     (e.g., nb = -170, cb = 5)

else if nb >= 0   // (i.e., cb < 0)
    if cb + 180 >= nb, then add ltr
    else
       special_subtract ltr  (i.e., if result < -180, add 360)

Open in new window

Avatar of EFileTahi-A

ASKER

Hello everyone and thank you for the posts so far.

I'm not interested in using XNA. I'm using ManagedDX, and want to recreate all math functions from scratch. This will also help to understand how the magic works.

Anyway, I already have the function to return the mouse's angle relative to the ship's position:
double dDegree = (Math.Atan2(mouse.X - ship.X, ship.Y - mouse.Y) / Math.PI * 180 + 360) % 360;

The only thing I need now to understand is how to turn the ship slowly and make it move towards its destination.

Lets assume the ship is bearing at 0 and order it to go 90 degrees Right. Therefore I need to turn it as little as its max turn rate (0.05 degrees max) per frame and move the ship towards its new bearing until reaching 90º right.

What is the math to rotate and move the ship in 0.05º degrees in any direction?
If you have a line segment (i.e., the ship) that currently has a bearing of 0 degrees (due East, horizontal line segment using my description above - yours may vary), then ltr = 0.5 and you want to subtract from current bearing.
So, nb = cb - 0.5 = 0 - 0.5 degrees

>> For a given bearing, ß, and speed, S, the ship's velocity is:
>>    (S*cos ß, S*sin ß)

The line is rotated, and its slope = sin ß / cos ß = tan ß
For example, after 60 frames, your new bearing will be 0 - 60*(0.5) = -30
So the slope of the line will be m = tan(-30) = -0.577 = -1/sqrt(3)
(i.e., a negative slope making a 30 degree angle with the horizontal).

Now given a negative 30 degree slope, the ship could be moving down (i.e., moving South-East or up moving North-West.

The direction is determined by the unit vector:
(cos ß, sin ß) = (cos -30, sin -30) = ( 0.866, -0.5 )
i.e., a positive x-value (means moving East) and a negative y-value (means moving South) together means moving South-East. When the direction reaches the final bearing, the unit vector will be (cos -90, sin -90) = (0, -1) which means moving South.
Phoffric, I'm pretty sure your explanations are top notch for those who are familiar with trigonometry or math in general.

Unforntunately, I'm one of those guys that need to see a full example to fully understand it. Of course I cannot ask you for this but I don't think I will get there without one.

Anyway, thanks for your time and patience with me.

Take care.
I don't have an example. But what exactly do you not understand?

What do you already have in terms of moving a ship with a certain bearing?
BTW - Just wondered whether you tried your Math.Atan2 solution. When I look at online definitions, I see that the delta y is the first argument, not the second. But, perhaps in your programming language, it is the other way around.

If you have some code showing the ship's positional movement, I may be able to help you further. It is just a matter of defining delta_X and delta_Y, the ship's positional movement per frame.

If the speed of the ship is Sf units per frame, then:
   delta_Y = Sf * sin ß
   delta_X = Sf * cos ß

Of course, every frame, ß changes by an amount ltr.
phoffric:
"When I look at online definitions, I see that the delta y is the first argument, not the second."

If I pass it as second argument 0 bearing ceases to be at North.

"If you have some code showing the ship's positional movement, I may be able to help you further. It is just a matter of defining delta_X and delta_Y, the ship's positional movement per frame."

I will work on it once I get home and try to implement the ships rotation formula and see where my real difficulties lie. Thanks for your persistent help, I'm really gratefull!


plz review this code ; and tell me.

you need in the form
pictureBox1, timer1;
i forget to attach the code in the prev comment , ^_^
public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private int speed = 5;
        private int mouseX = 0;
        private int mouseY = 0;

        private void Form1_MouseClick(object sender, MouseEventArgs e)
        {
            mouseX = e.X;
            mouseY = e.Y;
            timer1.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            int delta_x = pictureBox1.Location.X - mouseX;
            int delta_y = pictureBox1.Location.Y - mouseY;
            double targetRotation = -Math.Atan2(delta_x, delta_y) / (Math.PI / 180);
            if (Math.Sqrt((delta_x * delta_x) + (delta_y * delta_y)) > speed)
            {
                Point l = new Point(pictureBox1.Location.X, pictureBox1.Location.Y);
                l.Y -= (int)(speed * Math.Cos(targetRotation * (Math.PI / 180)));
                l.X += (int)(speed * Math.Sin(targetRotation * (Math.PI / 180)));
                pictureBox1.Location = l;

            }
            else
            {
                timer1.Enabled = false;
            }
        }
    }

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of KingFlash
KingFlash

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you!