• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
P0jahn

Turret rotation

22 posts in this topic

I have a turret in my 2D game, a turret capable of firing projectiles at its target(often the main character).

The turret is rotating so it is "pointing" at the target, if it can see it.

However, the rotation speed is instant. For example, the target is at the bottom-right-edge of the map, now the target somehow teleported to the top-left-edge of the map. You wouldnt see the turret rotating towards the top-left-edge. Instead, it would just immediately change.

So I want to implement some sort of rotation speed mechanism.

 

Here is an idea I came up with:

The turret is associated with an object that is moving in a circular path. Lets call this circular-moving object for c.

c is following the target. Since c is restricted to move in a circular path, it cant follow the target. So by follow, I mean whether it should move clockwise or counter clockwise. How to detect this, I haven no idea.

 

Then, we use an algorithm explained here: http://www.gamedev.net/topic/636343-get-a-points-coordinate/ , check if the target is intersecting with this line. If it is, we set c's movespeed to 0 and the turret can fire.

0

Share this post


Link to post
Share on other sites

The easiest way to do this, is using vectors and the angle-between-two-vectors algorithm.

 

Suppose you know the turret's location and the target's, then you can find a vector to the target.

 

And suppose you know the turret's current angle, you can find a vector pointing "forwards" from the turret.

 

Normalise these two vectors, then you can find the dot-product, which gives you the angle.

 

But how do you know which way to turn? This can be done by doing the same thing with the "right" vector of the turret. If you dot the target with the right vector, then a + answer means you should turn "right" (i.e. clockwise), -ve, means you should turn left.

 

So:

 

* Find the "right" vector of the turret

* Find the vector to the target

* Assuming both are normalised, the angle between them is arc-sine of the dot-product.

 

You can then decide whether to turn the turret right,left, or shoot the gun. Presumably the turret has a maximum turn rate, so you'll clamp the turn rate at that. You might also want to model accelerator and/or movement delay for the turret.

0

Share this post


Link to post
Share on other sites

I am more interested in your approach. My way was just an overkill.
So this is what I came up with.

 

 

 

public static double getAngle(float x1, float y1, float x2, float y2)
{
float deltaX = x2 - x1;
float deltaY = y2 - y1;


return Math.atan2(deltaY, deltaX) * 180 / Math.PI;
}

public static float rotateTowardsPoint(float x, float y, float targetX, float targetY, float speed)
{
Point2D.Float normalized = normalize(x, y, targetX, targetY);
double angle = getAngle(x, y, normalized.x, normalized.y);


return (float) clamp(angle, -speed, speed);
}


public static double clamp (double i, double low, double high) 
{
   return java.lang.Math.max (java.lang.Math.min (i, high), low);
}


public static Point2D.Float normalize(float x1, float y1, float x2, float y2)
{
float dx = x1 - x2;
float dy = y1 - y2;
double length = Math.sqrt( dx*dx + dy*dy );
dx /= length;
dy /= length;


return new Point2D.Float(dx,dy);
}

 

 

 

The results ingame, is that the unit that is suppose to rotate, rotates, but just to the same angle every frame. And the rotation is not even pointing at the target.

0

Share this post


Link to post
Share on other sites
I like to use complex numbers to do computations in 2D geometry. Applying a rotation is represented by multiplication by a complex number whose modulus is 1. If I have a vector A and I am trying to make it point towards vector B, the rotation I need to apply is B/A normalized. I can then limit that rotation to something close to 1 (1 being no rotation, since I am multiplying).

This code is in C++, but hopefully I managed to make it reasonably clear. Notice how the loop doesn't have any trigonometric functions at all. There are also basically no special cases.
#include <iostream>
#include <complex>
#include <cmath>

typedef std::complex<float> C;

float const pi = std::atan(1.0f)*4.0f;

C normalize(C z) {
  return z/std::abs(z);
}

C limited_rotation_towards(C original, C target, C max_rotation) {
  C rotation = normalize(target / original);
  if (rotation.real() < max_rotation.real())
    return rotation.imag() > 0.0f ? max_rotation : conj(max_rotation);
  return rotation;
}

int main() {
  C z = 1.0f;
  C target (-1.0f, -1.0f);
  float max_angle = pi*0.125f;
  C max_rotation(std::cos(max_angle), std::sin(max_angle));

  for (int i=0; i<20; ++i) {
    std::cout << z << '\n';
    z *= limited_rotation_towards(z, target, max_rotation);
  }
}

The output is this:
(1,0)
(0.92388,-0.382683)
(0.707107,-0.707107)
(0.382683,-0.92388)
(-1.49012e-07,-1)
(-0.382684,-0.923879)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
(-0.707107,-0.707107)
Edited by Álvaro
1

Share this post


Link to post
Share on other sites

Thanks, but I cant understand that code.

typedef std::complex<float> C;?

Why do I need a max rotation?

Why doesnt it return a float/double?

0

Share this post


Link to post
Share on other sites
Thanks, but I cant understand that code.

typedef std::complex<float> C;?

Why do I need a max rotation?

Why doesnt it return a float/double?

typedef is used to create alias for types in c and c++. In all places after this where you see C, you can substitute that for std::complex<float>.

 

Max rotation is basically the speed at which you can rotate.

 

It returns the angle as a complex number. If you really want an angular rotation represented by degrees you'll need to use trig to get that information. When the complex number is normalized then the real component represents the cosine of the angle of rotation and the imaginary component represents the sine of the angle of rotation. To get the angle of rotation as a float then you just take the inverse cosine of the real or the inverse sine of the imaginary. If you don't know if the complex number you have is normalized you can also use arctan of the real and imaginary numbers.

 

You might want to read this guide about complex numbers. It could help explain more of the math Alvaro does and it's also pretty informative in general.

0

Share this post


Link to post
Share on other sites

Here is the code from tank game I developed. It rotates the turret clockwise or counter-clockwise (depeding on which path is shorter) with desired speed.

 

destinationRotation = Math.Atan2(turret.Y - target.Y, turret.X - target.X) + Math.PI;

if (destinationRotation != currentRotation)
{
    if (destinationRotation > currentRotation)
    {
        if (currentRotation < destinationRotation - Math.PI)
            currentRotation -= rotationSpeed;
        else
            currentRotation += rotationSpeed;
    }
    else if (destinationRotation  < topRotation)
    {
        if (currentRotation > destinationRotation + Math.PI)
            currentRotation += rotationSpeed;
        else
            currentRotation -= rotationSpeed;
    }

    if (currentRotation > Math.PI * 2.0f) currentRotation = 0;
    if (currentRotation < 0) currentRotation = Math.PI * 2.0f;
}
0

Share this post


Link to post
Share on other sites

Thanks, that code make a lot more sense, Just one thing, what do topRotation represent and how do I get that value?

0

Share this post


Link to post
Share on other sites

Ah, sorry - this topRotation should be currentRotation. I was renaming the variables and missed this one.

0

Share this post


Link to post
Share on other sites

Hmm. Does not work for some reason.

 

I was circulated the turret, and the turret was stuck in two angles.

Results(the returned angle):

 

1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
2.2831855
1.2831855
0

Share this post


Link to post
Share on other sites
public static float rotateTowardsPoint(float srcX, float srcY, float targetX, float targetY, float currRotation, float speed)
{
float destinationRotation = (float) (Math.atan2(srcY - targetY, srcX - targetX) + Math.PI);


if (destinationRotation != currRotation)
{
   if (destinationRotation > currRotation)
   {
       if (currRotation < destinationRotation - Math.PI)
        currRotation -= speed;
       else
        currRotation += speed;
   }
   else if (destinationRotation  < currRotation)
   {
       if (currRotation > destinationRotation + Math.PI)
        currRotation += speed;
       else
        currRotation -= speed;
   }


   if (currRotation > Math.PI * 2.0f) currRotation = 0;
   if (currRotation < 0) currRotation = (float) (Math.PI * 2.0f);
}
return currRotation;
}
0

Share this post


Link to post
Share on other sites

These values don't make any sense. Are you sure you are using Atan2 method? Also keep in mind that this code makes use of radian angles (not degrees). Or maybe your rotationSpeed value is too high. In my project it was like 0.02f (i was calling this method every update).

 

#EDIT You can also try replacing that "else if" with "if" only. Or swapping Atan2 arguments from "src - target" to "target - src".

Edited by alexsok90
0

Share this post


Link to post
Share on other sites

Okey, I made small modifications, so it use radians and then at the bottom, convert it back to degrees and return it.

I am still getting erratic results. Btw this function is called every frame.

 

 

rotation = rotateTowardsPoint(x1,y1,x2,y2, rotation, rot);
3.4906584E-4
3.491722E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
3.4917222E-4
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0.10966227
0

Share this post


Link to post
Share on other sites

Alright, I fixed it. There was some errors with my radians to degree and vice versa conversions.

However, this statement: if (destinationRotation != currRotation) is not sufficient enough. I need better detection.

When it is facing the target, is start shaking. Instead, it should just stand still.

 

I think the problem is that the double to float cast is destroying the precision.

0

Share this post


Link to post
Share on other sites
No, the problem is that you are always adding or subtracting `speed', even if the difference between the angles is smaller than `speed'. When the current angle is less than `speed' away from the target angle, you should set the current angle to the target angle.
0

Share this post


Link to post
Share on other sites

Thanks all, it is fully working now. Here is the complete code if anyone is interested:

 

 

public static float rotateTowardsPoint(float srcX, float srcY, float targetX, float targetY, float currRotation, float speed)
{
   float destinationRotation = (float) (Math.atan2(srcY - targetY, srcX - targetX) + Math.PI);
   currRotation = (float) Math.toRadians(currRotation);

   if(Math.abs((currRotation + 180 -  destinationRotation) % 360 - 180) < speed)
      currRotation = destinationRotation;
   else
   {
      if (destinationRotation > currRotation)
      {
          if (currRotation < destinationRotation - Math.PI)
             currRotation -= speed;
          else
             currRotation += speed;
      }
      else if (destinationRotation  < currRotation)
      {
          if (currRotation > destinationRotation + Math.PI)
             currRotation += speed;
          else
             currRotation -= speed;
      } 
      if (currRotation > Math.PI * 2.0f) currRotation = 0;
      if (currRotation < 0) currRotation = (float) (Math.PI * 2.0f);
   }
   return (float) Math.toDegrees(currRotation);
}
0

Share this post


Link to post
Share on other sites

if(Math.abs((currRotation + 180 -  destinationRotation) % 360 - 180) < speed)



I don't know much Java, but I suspect that code will do the wrong thing when `currRotation' is 0 and `destinationRotation' is 359. The angles will be considered to be 359 degrees away, instead of 1.

I really think you should give up using angles: They force you to use expensive trigonometric functions all over the place, they make the code hard to get right because there are so many special cases that one has to consider, and in the end it is hard to decide if that code won't blow up in some circumstances.

The code using complex numbers is really much much simpler, once you get past the mental block of representing rotations as complex numbers with mudulus 1. I don't quite know why you dismissed the idea earlier. Edited by Álvaro
0

Share this post


Link to post
Share on other sites

Well, the reason I use degrees is because the functions that rotates images wants the value to be in degrees and not radians. And radians is something I am unfamiliar with. I never really understood what radians tbh. I know it is a way to measure/represent angles, but thats about it. Wiki was not good at explaining it either.

 

As for complex numbers. I dont even know how a complex number work, the equivalent in java etc.

This is what the C++ API says:

 

The complex library implements the complex class to contain complex numbers in cartesian form and several functions and overloads to operate with them:
0

Share this post


Link to post
Share on other sites
Perhaps you could use a few Math classes. Are you in school? At what level?

nobodynews posted a very good guide to complex numbers that you should read.
0

Share this post


Link to post
Share on other sites

Hey Alvaro, thanks for the sample and link on complex numbers. This is high school level stuff to me, but it's been so long since I've used it that I've now finally come to grips with it and it makes perfect sense for 2D rotations. Perhaps now I can get back to working on the rolling marble game I so much put off :D

0

Share this post


Link to post
Share on other sites
Well, the reason I use degrees is because the functions that rotates images wants the value to be in degrees and not radians. And radians is something I am unfamiliar with. I never really understood what radians tbh. I know it is a way to measure/represent angles, but thats about it. Wiki was not good at explaining it either.

 

A thing you should understand to help get radians is that the units for degrees are arbitrary. What is a degree defined as? 1/360th of a full rotation. Why use 360 instead of any other number? It's just so common we keep on using it. Once you learn it it's easy to use in conversation and it has more whole numbers than radians which are harder to talk about. Despite being harder to talk about, radians are more natural. They don't rely on an aribtrary definition like degrees do and as such they are easier to work with in the language of math. A radian is defined as the rotation around an arc that's the same length as the radius. This means the radian can be derived independantly for any circle you'll ever look at. The reason radians come up in trigonometry is because of the relationship between triangles and circles.

 

As you continue learning about trigonometry you'll gain a better understanding of radians.

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0