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-01-03
Revision:
0:01829868570e
Child:
1:9ff7384171ec

File content as of revision 0:01829868570e:

#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"

#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 Fix16 NearZ(10);
const Fix16 FarZ(2000);
const Fix16 ViewDistance(80);
const Fix16 HalfViewWidth(VIEW_WIDTH/ 2);
const Fix16 HalfViewHeight(VIEW_HEIGHT/ 2);

const Fix16 TenPercent = Fix16::createDirect(0x00001999);       // 0.1f
const Fix16 NintyPercent = Fix16::createDirect(0x0000e666);     // 0.9f
const Fix16 NintyFivePercent = Fix16::createDirect(0x0000f333); // 0.95f
const Fix16 Ten(10);

const Fix16 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
{
    int      HitCount;
    
    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 update();
void draw();

LCD_ST7735 Surface(
    P0_19,
    P0_4,
    P0_5,
    P0_21,
    P0_22,
    P1_15,
    P0_2,
    LCD_ST7735::RGB);

char buffer[20];
main()
{
    Surface.setOrientation(LCD_ST7735::Rotate270, false);
    
    // 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);
        ties[i].HitCount = 0;
    }
    
    // Init explosions
    for (int i = 0; i < NUM_EXPLOSIONS; ++i)
    {
        explosions[i].Active = false;
    }
    
    Timer frameTimer;
    frameTimer.start();
    int lastFrameTime = frameTimer.read_us();
    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);            
        }
        
        int frameTime = frameTimer.read_us();
        int duration = frameTime - lastFrameTime;
        lastFrameTime = frameTime;        
        
        sprintf(buffer, "%d   ", duration);
        Surface.drawString(font_ibm, 0, 120, buffer);
    }    
}

void initStar(int index, bool initial)
{
    stars[index].X = -VIEW_WIDTH / 2 + rand() % VIEW_WIDTH;
    stars[index].Y = -VIEW_HEIGHT / 2 + rand() % VIEW_HEIGHT;
    if (initial)
        stars[index].Z = NearZ + Fix16(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;
    ties[index].Velocity.Z = -4 - rand() % (8 + ties[index].HitCount);
}

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;
            }
            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;
                if (tie.HitCount < 48) ++tie.HitCount;
                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 += Fix16::One;
    if (GameInput::isRightPressed()) steeringForce.X -= Fix16::One;
    if (GameInput::isUpPressed()) steeringForce.Y -= Fix16::One;
    if (GameInput::isDownPressed()) steeringForce.Y += Fix16::One;
    
    if (GameInput::isCirclePressed() && cannonState == 0)
    {
        cannonState = 1;
        cannonCount = 0;
    }
    
    if (cannonState == 1)
        if (++cannonCount > 15)
            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
        Fix16 x_per = ViewDistance * (star.X / star.Z);
        Fix16 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);
        }
        else
        {
            canvas.drawLine(20, VIEW_HEIGHT - 1, -4 + rand() % 8 + TARGET_X, -4 + rand() % 8 + TARGET_Y, 1);
        }
    }
    
    // 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);
}