In this project, we will put into practice everything we learned in part 1 and part 2 of the tutorial series on trigonometric functions. We will draw a simple triangle-shaped spaceship to the screen by drawing lines between three points/vertices. We will then see how we can do the math to smoothly rotate it and calculate its heading in order to fly around the screen.

  • The courses above are up to 95% off - only $19 using voucher code UDEMARCH. Limited time offer.

About this project

Skill level 1
Time to complete 1 hour

New Concepts:

  1. Movement based on a heading in degrees
  2. Rotating a 2d game object

Projects that demonstrate these concepts

To get started create a new project in Android Studio, call it Heading And Rotation and name the Activity HeadingAndRotationActivity then read on.

Making the game full-screen landscape

We want to use every pixel that the device has to offer so we will make changes to the app’s AndroidManifest.xml configuration file.

    1. In the project explorer pane in Android Studio double click on the manifests folder, this will open up the AndroidManifest.xml file in the code editor.
    2. In the AndroidManifest.xml file find the following line of code,  android:name=".HeadingAndRotation"
    3. Immediately below type or copy and paste these two lines to make the game run full screen and lock it in the landscape orientation.
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
This is an important step because otherwise, the controls won’t work.

The class structure

We want to create classes to represent the view and the spaceship. We will call them  HeadingAndRotationViewand Ship. By creating empty implementations of these classes we can then declare objects of them right away and make faster progress through the code.

With this in mind let’s create those empty classes. Right-click the java folder in the Android Studio project explorer as shown in the next image.

Creating a new Java class called Paddle

Creating a new Java class called Paddle

Now select New|Java class then select …\app\source\main\java and click OK. Now enter HeadingAndRotationView as the name for our new class and click OK. We have now created a new Java class in a separate file called HeadingAndRotationView.java. Now do the same thing and create a class called Ship.

Coding the Activity class

This Activity class is no different  from the Activity classes from other projects like the Breakout game so I will just throw the code at you below. If the comments are not enough to remind you of what the code does then check out more detailed explanations in the Breakout project. Delete the contents of the HeadingAndRotationActivity class and add this code instead.

import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
/*
This is the entry point to the game.
It will handle the lifecycle of the game by calling
methods of view when prompted to so by the OS.
*/
public class HeadingAndRotationActivity extends Activity {

    /*
    view will be the view of the game
    It will also hold the logic of the game
    and respond to screen touches as well
    */
    HeadingAndRotationView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Get a Display object to access screen details
        Display display = getWindowManager().getDefaultDisplay();
        // Load the resolution into a Point object
        Point size = new Point();
        display.getSize(size);

        // Initialize gameView and set it as the view
        view = new HeadingAndRotationView(this, size.x, size.y);
        setContentView(view);

    }

    // This method executes when the player starts the game
    @Override
    protected void onResume() {
        super.onResume();

        // Tell the gameView resume method to execute
        view.resume();
    }

    // This method executes when the player quits the game
    @Override
    protected void onPause() {
        super.onPause();

        // Tell the gameView pause method to execute
        view.pause();
    }
}

Coding the view class

The view class holds nothing new either. It sets up the main game loop and calls update and draw as per usual. The only slightly new thing compared to the Breakout or Space Invaders projects is that in the draw method we use the drawLine method of the Canvas class to draw three lines that we get the coordinates for by calling getA, getB and getC of the Ship class that we are yet to implement. For more on drawLine have a look at the Drawing Graphics project.

Also, if anything is unclear about the way we handle the player’s input check out the Space Invaders project. The controls simply allow the player to hold the very bottom left or right of the screen to rotate left and right or hold anywhere above the bottom eighth to thrust. The setMovementState method of the Ship class is called with the appropriate value in each case.

I have liberally sprinkled comments throughout as a reminder what the code does. Enter the following code in the HeadingAndRotationView class and then we can look at the Ship class which is where all the action takes place.

package com.gamecodeschool.headingandrotationproject;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class HeadingAndRotationView extends SurfaceView implements Runnable{

    Context context;

    // This is our thread
    Thread gameThread = null;

    // Our SurfaceHolder to lock the surface before we draw our graphics
    SurfaceHolder ourHolder;

    // A boolean which we will set and unset
    // when the game is running- or not.
    volatile boolean playing;

    // Game is paused at the start
    boolean paused = true;

    // A Canvas and a Paint object
    Canvas canvas;
    Paint paint;

    // This variable tracks the game frame rate
    long fps;

    // This is used to help calculate the fps
    private long timeThisFrame;

    // The size of the screen in pixels
    int screenX;
    int screenY;

    // The player's ship
    Ship ship;

    // When we initialize (call new()) on view
    // This special constructor method runs
    public HeadingAndRotationView(Context context, int x, int y) {

        /*
        The next line of code asks the
        SurfaceView class to set up our object.
        How kind.
        */

        super(context);

        // Make a globally available copy of the context so we can use it in another method
        this.context = context;

        // Initialize ourHolder and paint objects
        ourHolder = getHolder();
        paint = new Paint();

        screenX = x;
        screenY = y;

        // Make a new player space ship
        ship = new Ship(context, screenX, screenY);

    }

    @Override
    public void run() {
        while (playing) {

            // Capture the current time in milliseconds in startFrameTime
            long startFrameTime = System.currentTimeMillis();

            // Update the frame
            if(!paused){
                update();
            }

            // Draw the frame
            draw();

            /*
            Calculate the fps this frame
            We can then use the result to
            time animations and more.
            */

            timeThisFrame = System.currentTimeMillis() - startFrameTime;
            if (timeThisFrame >= 1) {
                fps = 1000 / timeThisFrame;
            }

        }

    }

    private void update(){

        // Move the player's ship
        ship.update(fps);

    }

    private void draw(){
        // Make sure our drawing surface is valid or we crash
        if (ourHolder.getSurface().isValid()) {
            // Lock the canvas ready to draw
            canvas = ourHolder.lockCanvas();

            // Draw the background color
            canvas.drawColor(Color.argb(255, 26, 128, 182));

            // Choose the brush color for drawing
            paint.setColor(Color.argb(255,  255, 255, 255));

            // Now draw the player spaceship
            // Line from a to b
            canvas.drawLine(ship.getA().x, ship.getA().y,
                    ship.getB().x, ship.getB().y,
                    paint);

            // Line from b to c
            canvas.drawLine(ship.getB().x, ship.getB().y,
                    ship.getC().x, ship.getC().y,
                    paint);

            // Line from c to a
            canvas.drawLine(ship.getC().x, ship.getC().y,
                    ship.getA().x, ship.getA().y,
                    paint);

            canvas.drawPoint(ship.getCentre().x, ship.getCentre().y,paint);

            paint.setTextSize(60);
            canvas.drawText("facingAngle = "+ (int)ship.getFacingAngle()+ " degrees", 20, 70, paint);

            // Draw everything to the screen
            ourHolder.unlockCanvasAndPost(canvas);
        }
    }

    // If the Activity is paused/stopped
    // shutdown our thread.
    public void pause() {
        playing = false;
        try {
            gameThread.join();
        } catch (InterruptedException e) {
            Log.e("Error:", "joining thread");
        }

    }

    // If the Activity is started then
    // start our thread.
    public void resume() {
        playing = true;
        gameThread = new Thread(this);
        gameThread.start();
    }

    // The SurfaceView class implements onTouchListener
    // So we can override this method and detect screen touches.
    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {

        switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {

            // Player has touched the screen
            case MotionEvent.ACTION_DOWN:

                paused = false;

                if(motionEvent.getY() > screenY - screenY / 8) {
                    if (motionEvent.getX() > screenX / 2) {
                        ship.setMovementState(ship.RIGHT);
                    } else {
                        ship.setMovementState(ship.LEFT);
                    }

                }

                if(motionEvent.getY() < screenY - screenY / 8) {
                    // Thrust
                    ship.setMovementState(ship.THRUSTING);

                }

                break;

            // Player has removed finger from screen
            case MotionEvent.ACTION_UP:

                ship.setMovementState(ship.STOPPED);

                break;
        }
        return true;
    }
}

  • The courses above are up to 95% off - only $19 using voucher code UDEMARCH. Limited time offer.

Coding the Ship class

Now for the fun part. As this class contains some new ideas we will break it into a few parts and explain the code as we go along. Just enter each section of code after the previous section and at the end you should end up with a fully working project.

The  class declaration and member variables

After the import directives and class declaration, we declare four PointF objects to hold the x and y values for the vertices of the ship ( a, b, c and the center point of the ship ( centre)). Next, we declare and initialize facingAngle to 270. This will be the variable that keeps track of which way the ship is pointing.

Next, we have length and width which do as you probably suspect. We then have a speed variable which will be the speed of the ship in pixels per second. Next, we have horizontalVelocity and verticalVelocity which will hold values relative to each other and will enable us to move at a specific heading. We will multiply both of these variables by speed each frame so they are still relative to each other but quicker than the base values returned by our trigonometric functions.

We also declare and initialize rotationSpeed. We will see how we use this variable to lock the rate at which the spaceship rotates in a frame rate independent manner. Finally, we have our ship’s movement states and the shipMoving variable to hold the current state. Enter the code below into the Ship class.

import android.content.Context;
import android.graphics.PointF;

public class Ship {

    PointF a;
    PointF b;
    PointF c;
    PointF centre;

    /*
    Which way is the ship facing
    Straight up to start with
    */

    float facingAngle = 270;

    // How long will our spaceship be
    private float length;
    private float width;

    // This will hold the pixels per second speed that the ship can move at
    private float speed = 100;

    /*
    These next two variables control the actual movement rate per frame
    their values are set each frame based on speed and heading
    */

    private float horizontalVelocity;
    private float verticalVelocity;

    /*
    How fast does the ship rotate?
    100 degrees per second
    */

    private float rotationSpeed = 100;

    // Which ways can the ship move
    public final int STOPPED = 0;
    public final int LEFT = 1;
    public final int RIGHT = 2;
    public final int THRUSTING = 3;

    // Is the ship moving and in which direction
    private int shipMoving = STOPPED;

Coding the Ship constructor

In the constructor we make the length and width variables equal to one fifth of the screen resolution, it will be quite a large ship but this is good for seeing what is going on. Then we initialize all four of our PointF objects as blank points.

Next, we initialize the members of the four PointF objects with the relevant coordinates. We make centre the exact center of the screen. Now it is important to draw the ship in its initial position at the same angle that we set facingAngle otherwise it will be out of sync from the start. We used 270 which is pointing straight up. Remember 0 degrees is facing east/3O’clock and the angle grows bigger anti-clockwise).

We initialize the members of a, b and c by using centre, length and width. If you were to plot the points and join them up you would end up with a ship much like the image in the 2d graphics rotation tutorial. Obviously, the exact sizes will vary dependent on the screen resolution of the specific device you use. Add the constructor code to the Ship class.

/*
    This the the constructor method
    When we create an object from this class we will pass
    in the screen width and height
    */

    public Ship(Context context, int screenX, int screenY){

        length = screenX / 5;
        width = screenY / 5;

        a = new PointF();
        b = new PointF();
        c = new PointF();
        centre = new PointF();

        centre.x = screenX / 2;
        centre.y = screenY / 2;

        a.x = centre.x;
        a.y = centre.y - length / 2;

        b.x = centre.x - width / 2;
        b.y = centre.y + length / 2;

        c.x = centre.x + width / 2;
        c.y = centre.y + length / 2;

    }

Ship class getters and setters

Now we have a bunch of getters and setters so the view can access and set the necessary members of the Ship class. These include a getter for each point and the facingAngle as well as setMoveMentState so that the onTouchEvent method from the view can change the movement state. Add these methods to the Ship class.


    public PointF getCentre(){
        return  centre;
    }

    public PointF getA(){
        return  a;
    }

    public PointF getB(){
        return  b;
    }

    public PointF getC(){
        return  c;
    }

    float getFacingAngle(){
        return facingAngle;
    }

    /*
    This method will be used to change/set if the
    ship is rotating left, right or thrusting
    */

    public void setMovementState(int state){
        shipMoving = state;
    }

The update method

As this method holds all the magic that we learned about in the trigonometric functions part 1 and part 2 tutorials we will break it into a few pieces to be clear about what is going on. If anything is unclear be sure to read those tutorials.

The first part of the update method is the simplest. You can see that if shipMoving equals LEFT or RIGHT we simply reduce or increase facingAngle by rotaionSpeed divided by fps. We also check to see if the facing angle has reached the full extent of a rotation (less than 1 or greater than 360) if it has we set it to the appropriate reset angle. However, note the very first line of code where we initialize a local variable previousFA to whatever the current value of facingAngle is. We will need this value when we do the rotation math because we use the angle of change when rotating not the actual current angle. Remember how we rotated a point by 180 degrees? This time we will be rotating by tiny fractions of 1 degree each frame so we need the value for change (facingValue – previousFA). We will see this in action soon. Enter the first part of the update method.

/*
    This update method will be called from update in HeadingAndRotationView
    It determines if the player ship needs to move and changes the coordinates
    and rotation when necessary.
    */

    public void update(long fps){

        /*
        Where are we facing at the moment
        Then when we rotate we can work out
        by how much
        */

        float previousFA = facingAngle;

        if(shipMoving == LEFT){

            facingAngle = facingAngle -rotationSpeed / fps;

            if(facingAngle < 1){                 facingAngle = 360;             }         }         if(shipMoving == RIGHT){             facingAngle = facingAngle + rotationSpeed / fps;             if(facingAngle > 360){
                facingAngle = 1;
            }
        }

Now we handle what happens when the player is thrusting.

Here we use our heading algorithms to calculate the horizontal and vertical velocities. Remember the important thing about the values returned is the ratio of the two values to each other.

Next, we update the coordinates of each and every point using the newly calculated horizontalVelocity and verticalVelocity. We also multiply by speed to maintain our desired pixels per second and divide by fps for smooth frame rate independent motion.

if(shipMoving == THRUSTING){

            /*
            facingAngle can be any angle between 1 and 360 degrees
            the Math.toRadians method simply converts the more conventional
            degree measurements to radians which are required by
            the cos and sin methods.
            */

            horizontalVelocity = (float)(Math.cos(Math.toRadians(facingAngle)));
            verticalVelocity = (float)(Math.sin(Math.toRadians(facingAngle)));

            // move the ship - 1 point at a time
            centre.x = centre.x + horizontalVelocity * speed / fps;
            centre.y = centre.y + verticalVelocity * speed / fps;

            a.x = a.x + horizontalVelocity * speed / fps;
            a.y = a.y + verticalVelocity * speed / fps;

            b.x = b.x + horizontalVelocity * speed / fps;
            b.y = b.y + verticalVelocity * speed / fps;

            c.x = c.x + horizontalVelocity * speed / fps;
            c.y = c.y + verticalVelocity * speed / fps;

        }

Now we have moved our points we can rotate them. We declare some local variables tempX and tempY to temporarily hold the values of the rotations. Starting with point a we subtract the centre coordinates, both x and y from it.

We then rotate these newly acquired object space coordinates and assign the result to our temporary variables. Notice that the angle we pass in is facingAngle - previousFA and is wrapped in the Math.toRadians method.

Then we reassign to the actual point a along with adding back the x and y values held in centre.

So we converted the coordinates to object space, rotated them, then moved them back to world space. We do the same for point b and c then we are done. Add the last part of the update method and you can run the game.

/*
        Now rotate each of the three points by
        the change in the rotation this frame
        facingAngle - previousFA
        */

        float tempX = 0;
        float tempY = 0;

        // rotate point a
        a.x = a.x - centre.x;
        a.y = a.y - centre.y;

        tempX = (float)(a.x * Math.cos(Math.toRadians(facingAngle - previousFA)) -
                a.y * Math.sin(Math.toRadians(facingAngle - previousFA)));

        tempY = (float)(a.x * Math.sin(Math.toRadians(facingAngle - previousFA)) +
                a.y * Math.cos(Math.toRadians(facingAngle - previousFA)));

        a.x = tempX + centre.x;
        a.y = tempY + centre.y;

        // rotate point b
        b.x = b.x - centre.x;
        b.y = b.y - centre.y;

        tempX = (float)(b.x * Math.cos(Math.toRadians(facingAngle - previousFA)) -
                b.y * Math.sin(Math.toRadians(facingAngle - previousFA)));

        tempY = (float)(b.x * Math.sin(Math.toRadians(facingAngle - previousFA)) +
                b.y * Math.cos(Math.toRadians(facingAngle - previousFA)));

        b.x = tempX + centre.x;
        b.y = tempY + centre.y;

        // rotate point c
        c.x = c.x - centre.x;
        c.y = c.y - centre.y;

        tempX = (float)(c.x * Math.cos(Math.toRadians(facingAngle - previousFA)) -
                c.y * Math.sin(Math.toRadians(facingAngle - previousFA)));

        tempY = (float)(c.x * Math.sin(Math.toRadians(facingAngle - previousFA)) +
                c.y * Math.cos(Math.toRadians(facingAngle - previousFA)));

        c.x = tempX + centre.x;
        c.y = tempY + centre.y;

    }// End of update method
}// End of Ship class

What next?

Soon I will publish another full game tutorial that uses these techniques. If we have dozens of rotating game objects then we will need a smarter way of handling them, probably an array. Also, it is worth noting that Math.sin and Math.cos are computationally expensive. So, if we kept adding more objects then eventually the game would begin to lag. One simple solution to this is to create look-up tables for the values of sine and cosine. This could be just an array with the returned values of sine and cosine perhaps for all the whole numbers 1 through 360. We could then stop calling Math.toRadian, Math.cos and Math.sin just do something like this,  arrayCOS[facingAngle- previousFA] instead.

I hope you enjoyed the tutorial please comment if you liked it or if you didn’t and if you have any thoughts about it. Also, I am trying to get followers/friends on Facebook and would really appreciate it if you can like or follow Game Code School.

Please visit the Android category of our game coding bookstore for beginners