Using relative rotation

Rotating entities relative to the position of other entities in 2D space is a great function to know. The uses for relative rotation are limitless and always seems to be a "hot topic" for newer mobile game developers. One of the more prominent examples of this technique being used is in tower-defense games, which allows a tower's turret to aim towards the direction that an enemy, non-playable character is walking. In this recipe, we're going to introduce a method of rotating our Entity objects in order to point them in the direction of a given x/y position. The following image displays how we will create an arrow on the scene, which will automatically point to the position of the circle image, wherever it moves to:

Using relative rotation

Getting ready…

We'll need to include two images for this recipe; one named marble.png at 32 x 32 pixels in dimension and another named arrow.png at 31 pixels wide by 59 pixels high. The marble can be any image. We will simply drag this image around the scene as we please. The arrow image should be in the shape of an arrow, with the arrowhead pointing directly upward on the image. See the screenshot in the introduction for an example of the images to include. Include these assets in an empty BaseGameActivity test project then please refer to the class named RelativeRotation in the code bundle.

How to do it…

Follow these steps:

  1. Implement the IOnSceneTouchListener listener in the BaseGameActivity class:
    public class RelativeRotation extends BaseGameActivity implements IOnSceneTouchListener{
  2. Set the Scene object's onSceneTouchListener in the activity's onCreateScene() method:
    mScene.setOnSceneTouchListener(this);
  3. Populate the Scene object with the marble and arrow sprites. The arrow sprite is positioned in the center of the scene, while the marble's position is updated to the coordinates of any touch event location:
        /* Add our marble sprite to the bottom left side of the Scene initially */
        mMarbleSprite = new Sprite(mMarbleTextureRegion.getWidth(), mMarbleTextureRegion.getHeight(), mMarbleTextureRegion, mEngine.getVertexBufferObjectManager());
        
        /* Attach the marble to the Scene */
        mScene.attachChild(mMarbleSprite);
        
        /* Create the arrow sprite and center it in the Scene */
        mArrowSprite = new Sprite(WIDTH * 0.5f, HEIGHT * 0.5f, mArrowTextureRegion, mEngine.getVertexBufferObjectManager());
        
        /* Attach the arrow to the Scene */
        mScene.attachChild(mArrowSprite);
  4. Step four introduces the onSceneTouchEvent() method which handles the movement of the marble sprite via a touch event on the device's display:
      @Override
      public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
        // If a user moves their finger on the device
        if(pSceneTouchEvent.isActionMove()){
          
          /* Set the marble's position to that of the touch even coordinates */
         mMarbleSprite.setPosition(pSceneTouchEvent.getX(), pSceneTouchEvent.getY());
          
          /* Calculate the difference between the two sprites x and y coordinates */
          final float dX = mMarbleSprite.getX() - mArrowSprite.getX();
          final float dY = mMarbleSprite.getY() - mArrowSprite.getY();
          
          /* Calculate the angle of rotation in radians*/
          final float angle = (float) Math.atan2(-dY, dX);
          /* Convert the angle from radians to degrees, adding the default image rotation */
          final float rotation = MathUtils.radToDeg(angle) + DEFAULT_IMAGE_ROTATION;
          
          /* Set the arrow's new rotation */
          mArrowSprite.setRotation(rotation);
          
          return true;
        }
        
        return false;
      }

How it works…

In this class, we're creating a sprite which is represented by an arrow image and placing it in the direct center of the screen, automatically pointing to a another sprite represented by a marble. The marble is draggable via touch events through the use of an IOnSceneTouchListener listener implementation within our BaseGameActivity class. We then register the touch listener to the mScene object. In situations where an entity rotates according to another entity's position, we'll have to include the rotation functionality in some method that is consistently updated, otherwise our arrow would not continuously react. We can do this through update threads, but in this instance we'll include that functionality in the onSceneTouchEvent() overridden method as the "target" will not actually move until we touch the scene anyway.

In the first step, we're allowing our activity to override the onSceneTouchEvent() method by implementing the IOnSceneTouchListener interface. Once we've implemented the touch listener, we can take care of step two and allow the Scene object to receive touch events and respond according to the code situated inside the activity's overridden onSceneTouchEvent() method. This is done with the setOnSceneTouchListener(pSceneTouchListener) method.

In step four, the if(pSceneTouchEvent.isActionMove()) conditional statement determines whether a finger is moving over the scene, updating the marble's position, and calculating the new rotation for the arrow sprite if the conditional statement returns true.

We first start by updating the marble's position to the location of touch through the use of the setPosition(pX, pY) method as seen in the following code snippet:

mMarbleSprite.setPosition(pSceneTouchEvent.getX(), pSceneTouchEvent.getY());

Next, we subtract the pointer's x/y coordinates from the target's x/y coordinates. This gives us the difference between each of the sprites' coordinates which will be used to calculate the angle between the two positions. In this case, the pointer is the mArrowSprite object and the target is the mMarbleSprite object:

/* Calculate the difference between the two sprites x and y coordinates */
final float dX = mMarbleSprite.getX() - mArrowSprite.getX();
final float dY = mMarbleSprite.getY() - mArrowSprite.getY();
              
/* Calculate the angle of rotation in radians*/
final float angle = (float) Math.atan2(-dY, dX);

Lastly, since AndEngine's setRotation(pRotation) method uses degrees and the atan2(pY, pX) method returns radians, we must perform a simple conversion. We will use AndEngine's MathUtils class which includes a radToDeg(pRadian) method to convert our angle value from radians to degrees. Once we obtain the correct angle in degrees, we will set the mArrowSprite object's rotation:

/* Convert the angle from radians to degrees, adding the default image rotation */
final float rotation = MathUtils.radToDeg(angle) + DEFAULT_IMAGE_ROTATION;
      
/* Set the arrow's new rotation */
mArrowSprite.setRotation(rotation);

One last thing to note is that the DEFAULT_IMAGE_ROTATION value is an int value which represents 90 degrees. This value is simply used to offset the rotation of the mArrowSprite sprite, otherwise we would be required to properly rotate the image within our image editing software. If the pointer within our custom images is not facing the uppermost point of the image, this value may require adjustments in order to properly align the pointer with the target.