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.
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); }