After decimating the enemy forces that have attacked your ship, you are charged with taking out as many of the remaining enemy fighters as possible. 3d space fighter game was initially written while I was testing some 3d routines I was implementing for a flight simulator, but my daughter started playing it and seemed to enjoy it so I added a few sound effects, explosions etc. and so this little game was born.

Dependencies:   mbed

main.cpp

Committer:
taylorza
Date:
2015-02-08
Revision:
4:b857db213f10
Parent:
2:db53af3d2e0f

File content as of revision 4:b857db213f10:

#include "mbed.h"
#include <cstdarg>
#include "LCD_ST7735.h"
#include "GameInput.h"
#include "font_IBM.h"
#include "Canvas.h"
#include "Color565.h"
#include "Math.h"
#include "SpriteSheet.h"

#include "SoundBlock.h"
#include "SoundChannel.h"
#include "OneBitSound.h"

#define VIEW_WIDTH      160
#define VIEW_HEIGHT     104

#define NUM_STARS       10
#define NUM_TIES        5
#define NUM_TIE_VERTS   10
#define NUM_TIE_EDGES   10
#define NUM_EXPLOSIONS  (NUM_TIES)

#define TARGET_X        (VIEW_WIDTH >> 1)
#define TARGET_Y        (VIEW_HEIGHT >> 1)

const float NearZ = 10;
const float FarZ = 2000;
const float ViewDistance = 80;
const float HalfViewWidth = VIEW_WIDTH/ 2;
const float HalfViewHeight = VIEW_HEIGHT/ 2;

const float TenPercent = 0.1f;
const float NintyFivePercent = 0.95f;

const float StarVelocity = 10;

Vector3d stars[NUM_STARS];

struct Line3d
{
    uint8_t  V1;
    uint8_t  V2;
    
    Line3d(uint8_t v1, uint8_t v2) : V1(v1), V2(v2) {}
};

struct TieFighter
{
    
    Vector3d Position;
    Vector3d Velocity;
        
    int      MinX;       
    int      MinY;       
    int      MaxX;       
    int      MaxY;       
};

struct Explosion
{
    bool        Active;
    int         Counter;
    Vector3d    P1[NUM_TIE_EDGES];
    Vector3d    P2[NUM_TIE_EDGES];
    Vector3d    Velocity[NUM_TIE_EDGES];
};

Vector3d tieVerts[NUM_TIE_VERTS] = 
{
    Vector3d(-50, 40, 0),
    Vector3d(-40, 0, 0),
    Vector3d(-50, -40, 0),    
    Vector3d(-10, 0, 0),    
    Vector3d(0, 20, 0),    
    Vector3d(10, 0, 0),    
    Vector3d(0, -20, 0),    
    Vector3d(50, 40, 0),
    Vector3d(40, 0, 0),
    Vector3d(50, -40, 0),        
};

Line3d tieShape[NUM_TIE_EDGES] =
{
    Line3d(0, 1),
    Line3d(1, 2),
    Line3d(1, 3),
    Line3d(3, 4),
    Line3d(4, 5),
    Line3d(5, 6),
    Line3d(6, 3),
    Line3d(5, 8),
    Line3d(8, 7),
    Line3d(8, 9),
};

TieFighter  ties[NUM_TIES];
Explosion   explosions[NUM_EXPLOSIONS];
Vector3d    steeringForce;

bool        updateHud = true;
int         hits = 0;
int         misses = 0;
int         score = 0;

Bitmap1bpp image(VIEW_WIDTH, VIEW_HEIGHT);            
Canvas<Bitmap1bpp> canvas(&image);

int cannonState = 0;
int cannonCount = 0;

void initStar(int index, bool initial);    
void initTie(int index);

void startExplosion(int tieIndex);

void showIntro();
void playGame();

void update();
void draw();

char buffer[20];

OneBitSound soundEngine(P0_18);

CREATE_EFFECT(Sound_FireLaser)
    TONE(20, 50, 2000, -50, 128, 0)
END_EFFECT

CREATE_EFFECT(Sound_Explosion)
    NOISE(20, 1000, 10, 5)
END_EFFECT

LCD_ST7735 Surface(
    P0_19,
    P0_20,
    P0_7,
    P0_21,
    P0_22,
    P1_15,
    P0_2,
    LCD_ST7735::RGB);

main()
{
    Surface.setOrientation(LCD_ST7735::Rotate270, false);

    showIntro();    
    playGame();
}

void showIntro()
{
    // Title - Space Raiders
    Surface.drawBitmap(13, 0, bmp, 0, 0, 133, 13, Color565::Aqua, Color565::Black);
    
    // Image
    Surface.drawBitmap(45, 16, bmp, 0, 16, 69, 83, Color565::Green, Color565::Black);
    
    // Press [] to start
    Surface.drawBitmap(43, 102, bmp, 72, 16, 73, 8, Color565::White, Color565::Black);
    
    // (c)2015
    Surface.drawBitmap(44, 120, bmp, 72, 32, 71, 8, Color565::White, Color565::Black);
    
    while (!GameInput::isSquarePressed());
}

void playGame()
{
    Surface.clearScreen();
    
    // Init stars
    for (int i = 0; i < NUM_STARS; ++i)
    {
        initStar(i, true);        
    }
    
    // Init tie fighters
    for (int i = 0; i < NUM_TIES; ++i)
    {
        initTie(i);        
    }
    
    // Init explosions
    for (int i = 0; i < NUM_EXPLOSIONS; ++i)
    {
        explosions[i].Active = false;
    }
    
    while (true)
    {
        canvas.clear();        
                        
        update();
        draw();
        
        Surface.drawBitmap(0, 16, image, 0, 0, VIEW_WIDTH, VIEW_HEIGHT, Color565::White, Color565::Black);
        
        if (updateHud)
        {
            updateHud = false;
            Surface.setForegroundColor(Color565::White);
            Surface.drawString(font_ibm, 0, 0, "Hits   Score  Misses");

            Surface.setForegroundColor(Color565::Yellow);         
            sprintf(buffer, "%d", hits);
            Surface.drawString(font_ibm, 8, 8, buffer);
            
            Surface.setForegroundColor(Color565::Lime);
            sprintf(buffer, "%d", score);
            Surface.drawString(font_ibm, 56, 8, buffer);
            
            Surface.setForegroundColor(Color565::Red);
            sprintf(buffer, "%d", misses);
            Surface.drawString(font_ibm, 128, 8, buffer);            
        }
        
        if (misses == 50) break;        
    }  
    
    Surface.setForegroundColor(Color565::Red);
    Surface.drawString(font_ibm, 44, 60, "GAME OVER");
}

void initStar(int index, bool initial)
{
    stars[index].X = -VIEW_WIDTH + rand() % (2 * VIEW_WIDTH);
    stars[index].Y = -VIEW_HEIGHT + rand() % (2 * VIEW_HEIGHT);
    if (initial)
        stars[index].Z = NearZ + rand() % (int)(FarZ - NearZ);
    else
        stars[index].Z = 500;
}

void initTie(int index)
{
    ties[index].Position.X = -VIEW_WIDTH + rand() % (2 * VIEW_WIDTH);
    ties[index].Position.Y = -VIEW_HEIGHT + rand() % (2 * VIEW_HEIGHT);
    ties[index].Position.Z = FarZ * (rand() % 3 + 1);
    
    ties[index].Velocity.X = -0.5f + rand() % 2;
    ties[index].Velocity.Y = -0.5f + rand() % 2;
    int difficulty = hits > 48 ? 48 : hits;
    ties[index].Velocity.Z = -4 - rand() % (8 + difficulty);
}

void startExplosion(int tieIndex)
{
    TieFighter &tie = ties[tieIndex];
    for (int i = 0; i < NUM_EXPLOSIONS; ++i)
    {
        Explosion &explosion = explosions[i];
        if (explosion.Active == false)
        {
            explosion.Active = true;
            explosion.Counter = 0;
            
            for (int edge = 0; edge < NUM_TIE_EDGES; ++edge)
            {                
                Line3d &shape = tieShape[edge];
                
                // Start
                Vector3d &p1 = explosion.P1[edge];
                Vector3d &v1 = tieVerts[shape.V1];
                p1.X = tie.Position.X + v1.X;
                p1.Y = tie.Position.Y + v1.Y;
                p1.Z = tie.Position.Z + v1.Z;
                
                // End
                Vector3d &p2 = explosion.P2[edge];
                Vector3d &v2 = tieVerts[shape.V2];
                p2.X = tie.Position.X + v2.X;
                p2.Y = tie.Position.Y + v2.Y;
                p2.Z = tie.Position.Z + v2.Z;
                
                // Trajectory
                Vector3d &velocity = explosion.Velocity[edge];
                Vector3d &tieVelocity = tie.Velocity;
                velocity.X = tieVelocity.X - 8 + rand() % 16;
                velocity.Y = tieVelocity.Y - 8 + rand() % 16;
                velocity.Z = tieVelocity.Z - 8 + rand() % 16;
            }
            soundEngine.play(2, EFFECT(Sound_Explosion));
            break;
        }
    }
}

void update()
{
    // Move ties
    for (int i = 0; i < NUM_TIES; ++i)
    {
        TieFighter &tie = ties[i];
        
        if (cannonState == 1)
        {
            if (tie.MinX < TARGET_X && tie.MaxX > TARGET_X  
                && tie.MinY < TARGET_Y && tie.MaxY > TARGET_Y)
            {                                
                startExplosion(i);                
                ++hits;
                score += ((int)tie.Position.Z) / 10;
                updateHud = true;                
                initTie(i);                
                continue;
            }
        }
        
        tie.Position.X += (tie.Velocity.X + steeringForce.X);
        tie.Position.Y += (tie.Velocity.Y + steeringForce.Y);
        tie.Position.Z += (tie.Velocity.Z + steeringForce.Z);

        if (tie.Position.Z <= NearZ)
        {
            initTie(i);
            ++misses;
            updateHud = true;
        }
    }
    
    // Move stars
    for (int i = 0; i < NUM_STARS; ++i)
    {
        Vector3d &star = stars[i];
        star.X += (steeringForce.X * TenPercent);
        star.Y += (steeringForce.Y * TenPercent);
        star.Z -= StarVelocity;
                        
        if (star.Z <= NearZ)
        {
            initStar(i, false);
        }
    }
    
    // Update explosions
    for (int i = 0; i < NUM_EXPLOSIONS; ++i)
    {
        Explosion &explosion = explosions[i];
        if (explosion.Active == false) continue;
        
        for (int edge = 0; edge < NUM_TIE_EDGES; ++edge)
        {
            Vector3d &v = explosions[i].Velocity[edge];
            
            Vector3d &p1 = explosions[i].P1[edge];
            p1.X += v.X;
            p1.Y += v.Y;
            p1.Z += v.Z;

            Vector3d &p2 = explosions[i].P2[edge];            
            p2.X += v.X;
            p2.Y += v.Y;
            p2.Z += v.Z;
        }
        
        if (++explosions[i].Counter > 100)
        {
            explosions[i].Active = false;
        }
    }
    
    if (GameInput::isLeftPressed()) steeringForce.X += 1.5;
    if (GameInput::isRightPressed()) steeringForce.X -= 1.5;
    if (GameInput::isUpPressed()) steeringForce.Y -= 1.5;
    if (GameInput::isDownPressed()) steeringForce.Y += 1.5;
    
    if (GameInput::isCirclePressed() && cannonState == 0)
    {
        cannonState = 1;
        cannonCount = 0;            
    }
    
    if (cannonState == 1)
        if (++cannonCount > 10)
            cannonState = 2;
    
    if (cannonState == 2)
        if (++cannonCount > 20)
            cannonState = 0;    
    
    // Dampen thrusters
    steeringForce.X *= NintyFivePercent;    
    steeringForce.Y *= NintyFivePercent;    
    steeringForce.Z *= NintyFivePercent;        
}

void draw()
{    
    // Draw stars    
    for (int i = 0; i < NUM_STARS; ++i)
    {
        Vector3d &star = stars[i];
        
        // Perspective transform
        float x_per = ViewDistance * (star.X / star.Z);
        float y_per = ViewDistance * (star.Y / star.Z);
        
        // Screen coordiante transform
        int x_screen = (int)(HalfViewWidth + x_per);
        int y_screen = (int)(HalfViewHeight + y_per);
        
        // Clip to screen
        if (x_screen >= VIEW_WIDTH || x_screen < 0 || 
            y_screen >= VIEW_HEIGHT || y_screen < 0)
        {
            initStar(i, false);
            continue;
        }
        
        canvas.setPixel(x_screen, y_screen, 1);
    }
            
    // Draw ties
    for(int i = 0; i < NUM_TIES; ++i)
    {
        TieFighter &tie = ties[i];
        
        tie.MinX = 1000000;
        tie.MinY = 1000000;
        tie.MaxX = -1000000;
        tie.MaxY = -1000000;
        
        for (int edge = 0; edge < NUM_TIE_EDGES; ++edge)
        {                        
            Vector3d p1_per, p2_per;
            
            Line3d &shape = tieShape[edge];
            Vector3d &v1 = tieVerts[shape.V1];
            Vector3d &v2 = tieVerts[shape.V2];
            
            p1_per.X = ViewDistance * ((tie.Position.X + v1.X) / (tie.Position.Z + v1.Z));
            p1_per.Y = ViewDistance * ((tie.Position.Y + v1.Y) / (tie.Position.Z + v1.Z)); 
            
            p2_per.X = ViewDistance * ((tie.Position.X + v2.X) / (ties[i].Position.Z + v2.Z)); 
            p2_per.Y = ViewDistance * ((tie.Position.Y + v2.Y) / (ties[i].Position.Z + v2.Z)); 
            
            int p1_screen_x = (int)(HalfViewWidth + p1_per.X);
            int p1_screen_y = (int)(HalfViewHeight + p1_per.Y);
            int p2_screen_x = (int)(HalfViewWidth + p2_per.X);
            int p2_screen_y = (int)(HalfViewHeight + p2_per.Y);                         
            
            if (p1_screen_x < tie.MinX) tie.MinX = p1_screen_x;
            if (p2_screen_x < tie.MinX) tie.MinX = p2_screen_x;
            if (p1_screen_x > tie.MaxX) tie.MaxX = p1_screen_x;
            if (p2_screen_x > tie.MaxX) tie.MaxX = p2_screen_x;
            
            if (p1_screen_y < tie.MinY) tie.MinY = p1_screen_y;
            if (p2_screen_y < tie.MinY) tie.MinY = p2_screen_y;
            if (p1_screen_y > tie.MaxY) tie.MaxY = p1_screen_y;
            if (p2_screen_y > tie.MaxY) tie.MaxY = p2_screen_y;
            
            canvas.drawLine(p1_screen_x, p1_screen_y, p2_screen_x, p2_screen_y, 1);
        }        
    }
    
    // Draw explosions
    for (int i = 0; i < NUM_EXPLOSIONS; ++i)
    {
        Explosion &explosion = explosions[i];
        if (explosion.Active == false) continue;
        
        for (int edge = 0; edge < NUM_TIE_EDGES; ++edge)
        {            
            Vector3d &p1 = explosion.P1[edge];
            Vector3d &p2 = explosion.P2[edge];
            
            if (p1.Z < NearZ && p2.Z < NearZ) continue;
            
            Vector3d p1_per, p2_per;   
            
            p1_per.X = ViewDistance * (p1.X / p1.Z);
            p1_per.Y = ViewDistance * (p1.Y / p1.Z);
            
            p2_per.X = ViewDistance * (p2.X / p2.Z);
            p2_per.Y = ViewDistance * (p2.Y / p2.Z);
            
            int p1_screen_x = (int)(HalfViewWidth + p1_per.X);
            int p1_screen_y = (int)(HalfViewHeight + p1_per.Y);
            int p2_screen_x = (int)(HalfViewWidth + p2_per.X);
            int p2_screen_y = (int)(HalfViewHeight + p2_per.Y);
            
            canvas.drawLine(p1_screen_x, p1_screen_y, p2_screen_x, p2_screen_y, 1);
        }
    }
    
    // Draw laser fire
    if (cannonState == 1)
    {
        if (rand() % 2 == 1)
        {
            canvas.drawLine(VIEW_WIDTH - 21, VIEW_HEIGHT - 1, -4 + rand() % 8 + TARGET_X, -4 + rand() % 8 + TARGET_Y, 1);
            soundEngine.play(0, EFFECT(Sound_FireLaser));
        }
        else
        {
            canvas.drawLine(20, VIEW_HEIGHT - 1, -4 + rand() % 8 + TARGET_X, -4 + rand() % 8 + TARGET_Y, 1);
            soundEngine.play(1, EFFECT(Sound_FireLaser));
        }
    }
    
    // Draw cross-hair
    canvas.drawLine(VIEW_WIDTH / 2, VIEW_HEIGHT / 2 - 10, VIEW_WIDTH / 2, VIEW_HEIGHT / 2 - 5, 1);
    canvas.drawLine(VIEW_WIDTH / 2, VIEW_HEIGHT / 2 + 10, VIEW_WIDTH / 2, VIEW_HEIGHT / 2 + 5, 1);
    
    canvas.drawLine(VIEW_WIDTH / 2 - 10, VIEW_HEIGHT / 2, VIEW_WIDTH / 2 - 5, VIEW_HEIGHT / 2, 1);
    canvas.drawLine(VIEW_WIDTH / 2 + 10, VIEW_HEIGHT / 2, VIEW_WIDTH / 2 + 5, VIEW_HEIGHT / 2, 1);
}