parent
c168b16748
commit
e008d535c2
@ -0,0 +1,346 @@ |
||||
/*
|
||||
OneLoneCoder.com - Code-It-Yourself! Asteroids at the command prompt (quick and simple c++) |
||||
"This one just kept growing..." - @Javidx9 |
||||
|
||||
Disclaimer |
||||
~~~~~~~~~~ |
||||
I don't care what you use this for. It's intended to be educational, and perhaps |
||||
to the oddly minded - a little bit of fun. Please hack this, change it and use it |
||||
in any way you see fit. BUT, you acknowledge that I am not responsible for anything |
||||
bad that happens as a result of your actions. However, if good stuff happens, I |
||||
would appreciate a shout out, or at least give the blog some publicity for me. |
||||
Cheers! |
||||
|
||||
Background |
||||
~~~~~~~~~~ |
||||
I needed a quick video to keep up with my upload schedule, and thought "oh asteroids |
||||
must be simple", and indeed it was, but it was a bit rubbish, and didn't really |
||||
offer anything educational, until I made the decision to ditch sprites and use |
||||
vector models like the original game. This made the game considerably more sophisticated, |
||||
has opened up new possibilities for the game engine, and just goes to show, jus 'cos |
||||
it's old, doesn't mean it's irrelevant. |
||||
|
||||
I ended up with a demonstration of physics, wireframe graphics, 2D matrix transformations, |
||||
toroidal space mapping and vector manipulation. |
||||
|
||||
Controls |
||||
~~~~~~~~ |
||||
Left and Right arrow keys steer the ship. Up arrow is thrust. There is no reverse thrust. |
||||
Space bar fires bullets. Get the highest score by surviving waves of asteroids. |
||||
|
||||
Author |
||||
~~~~~~ |
||||
Twitter: @javidx9 |
||||
Blog: www.onelonecoder.com |
||||
YouTube: www.youtube.com/javidx9 |
||||
|
||||
Video: |
||||
~~~~~~ |
||||
https://youtu.be/QgDR8LrRZhk
|
||||
|
||||
Last Updated: 30/08/2017 |
||||
*/ |
||||
|
||||
#include <iostream> |
||||
#include <string> |
||||
#include <algorithm> |
||||
using namespace std; |
||||
|
||||
#include "olcConsoleGameEngine.h" |
||||
|
||||
class OneLoneCoder_Asteroids : public olcConsoleGameEngine |
||||
{ |
||||
public: |
||||
OneLoneCoder_Asteroids() |
||||
{ |
||||
m_sAppName = L"Asteroids"; |
||||
} |
||||
|
||||
private: |
||||
struct sSpaceObject |
||||
{ |
||||
int nSize; |
||||
float x; |
||||
float y; |
||||
float dx; |
||||
float dy; |
||||
float angle; |
||||
}; |
||||
|
||||
vector<sSpaceObject> vecAsteroids; |
||||
vector<sSpaceObject> vecBullets; |
||||
sSpaceObject player; |
||||
bool bDead = false; |
||||
int nScore = 0; |
||||
|
||||
vector<pair<float, float>> vecModelShip; |
||||
vector<pair<float, float>> vecModelAsteroid; |
||||
|
||||
protected: |
||||
// Called by olcConsoleGameEngine
|
||||
virtual bool OnUserCreate() |
||||
{ |
||||
vecModelShip =
|
||||
{ |
||||
{ 0.0f, -5.0f}, |
||||
{-2.5f, +2.5f}, |
||||
{+2.5f, +2.5f} |
||||
}; // A simple Isoceles Triangle
|
||||
|
||||
// Create a "jagged" circle for the asteroid. It's important it remains
|
||||
// mostly circular, as we do a simple collision check against a perfect
|
||||
// circle.
|
||||
int verts = 20; |
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
float noise = (float)rand() / (float)RAND_MAX * 0.4f + 0.8f; |
||||
vecModelAsteroid.push_back(make_pair(noise * sinf(((float)i / (float)verts) * 6.28318f),
|
||||
noise * cosf(((float)i / (float)verts) * 6.28318f))); |
||||
} |
||||
|
||||
ResetGame(); |
||||
return true; |
||||
} |
||||
|
||||
void ResetGame() |
||||
{ |
||||
// Initialise Player Position
|
||||
player.x = ScreenWidth() / 2.0f; |
||||
player.y = ScreenHeight() / 2.0f; |
||||
player.dx = 0.0f; |
||||
player.dy = 0.0f; |
||||
player.angle = 0.0f; |
||||
|
||||
vecBullets.clear(); |
||||
vecAsteroids.clear(); |
||||
|
||||
// Put in two asteroids
|
||||
vecAsteroids.push_back({ (int)16, 20.0f, 20.0f, 8.0f, -6.0f, 0.0f }); |
||||
vecAsteroids.push_back({ (int)16, 100.0f, 20.0f, -5.0f, 3.0f, 0.0f }); |
||||
|
||||
// Reset game
|
||||
bDead = false; |
||||
nScore = false; |
||||
} |
||||
|
||||
// Implements "wrap around" for various in-game sytems
|
||||
void WrapCoordinates(float ix, float iy, float &ox, float &oy) |
||||
{ |
||||
ox = ix; |
||||
oy = iy; |
||||
if (ix < 0.0f) ox = ix + (float)ScreenWidth(); |
||||
if (ix >= (float)ScreenWidth()) ox = ix - (float)ScreenWidth(); |
||||
if (iy < 0.0f) oy = iy + (float)ScreenHeight(); |
||||
if (iy >= (float)ScreenHeight()) oy = iy - (float)ScreenHeight(); |
||||
} |
||||
|
||||
// Overriden to handle toroidal drawing routines
|
||||
virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F) |
||||
{ |
||||
float fx, fy; |
||||
WrapCoordinates(x, y, fx, fy);
|
||||
olcConsoleGameEngine::Draw(fx, fy, c, col); |
||||
} |
||||
|
||||
bool IsPointInsideCircle(float cx, float cy, float radius, float x, float y) |
||||
{ |
||||
return sqrt((x-cx)*(x-cx) + (y-cy)*(y-cy)) < radius; |
||||
} |
||||
|
||||
// Called by olcConsoleGameEngine
|
||||
virtual bool OnUserUpdate(float fElapsedTime) |
||||
{ |
||||
if (bDead) |
||||
ResetGame(); |
||||
|
||||
// Clear Screen
|
||||
Fill(0, 0, ScreenWidth(), ScreenHeight(), PIXEL_SOLID, 0); |
||||
|
||||
// Steer Ship
|
||||
if (m_keys[VK_LEFT].bHeld) |
||||
player.angle -= 5.0f * fElapsedTime; |
||||
if (m_keys[VK_RIGHT].bHeld) |
||||
player.angle += 5.0f * fElapsedTime; |
||||
|
||||
// Thrust? Apply ACCELERATION
|
||||
if (m_keys[VK_UP].bHeld) |
||||
{ |
||||
// ACCELERATION changes VELOCITY (with respect to time)
|
||||
player.dx += sin(player.angle) * 20.0f * fElapsedTime; |
||||
player.dy += -cos(player.angle) * 20.0f * fElapsedTime; |
||||
} |
||||
|
||||
// VELOCITY changes POSITION (with respect to time)
|
||||
player.x += player.dx * fElapsedTime; |
||||
player.y += player.dy * fElapsedTime; |
||||
|
||||
// Keep ship in gamespace
|
||||
WrapCoordinates(player.x, player.y, player.x, player.y); |
||||
|
||||
// Check ship collision with asteroids
|
||||
for (auto &a : vecAsteroids) |
||||
if (IsPointInsideCircle(a.x, a.y, a.nSize, player.x, player.y)) |
||||
bDead = true; // Uh oh...
|
||||
|
||||
// Fire Bullet in direction of player
|
||||
if (m_keys[VK_SPACE].bReleased) |
||||
vecBullets.push_back({ 0, player.x, player.y, 50.0f * sinf(player.angle), -50.0f * cosf(player.angle), 100.0f }); |
||||
|
||||
// Update and draw asteroids
|
||||
for (auto &a : vecAsteroids) |
||||
{ |
||||
// VELOCITY changes POSITION (with respect to time)
|
||||
a.x += a.dx * fElapsedTime; |
||||
a.y += a.dy * fElapsedTime; |
||||
a.angle += 0.5f * fElapsedTime; // Add swanky rotation :)
|
||||
|
||||
// Asteroid coordinates are kept in game space (toroidal mapping)
|
||||
WrapCoordinates(a.x, a.y, a.x, a.y); |
||||
|
||||
// Draw Asteroids
|
||||
DrawWireFrameModel(vecModelAsteroid, a.x, a.y, a.angle, (float)a.nSize, FG_YELLOW);
|
||||
} |
||||
|
||||
// Any new asteroids created after collision detection are stored
|
||||
// in a temporary vector, so we don't interfere with the asteroids
|
||||
// vector iterator in the for(auto)
|
||||
vector<sSpaceObject> newAsteroids; |
||||
|
||||
// Update Bullets
|
||||
for (auto &b : vecBullets) |
||||
{ |
||||
b.x += b.dx * fElapsedTime; |
||||
b.y += b.dy * fElapsedTime; |
||||
WrapCoordinates(b.x, b.y, b.x, b.y); |
||||
b.angle -= 1.0f * fElapsedTime; |
||||
|
||||
// Check collision with asteroids
|
||||
for (auto &a : vecAsteroids) |
||||
{ |
||||
//if (IsPointInsideRectangle(a.x, a.y, a.x + a.nSize, a.y + a.nSize, b.x, b.y))
|
||||
if(IsPointInsideCircle(a.x, a.y, a.nSize, b.x, b.y)) |
||||
{ |
||||
// Asteroid Hit - Remove bullet
|
||||
// We've already updated the bullets, so force bullet to be offscreen
|
||||
// so it is cleaned up by the removal algorithm.
|
||||
b.x = -100; |
||||
|
||||
// Create child asteroids
|
||||
if (a.nSize > 4) |
||||
{ |
||||
float angle1 = ((float)rand() / (float)RAND_MAX) * 6.283185f; |
||||
float angle2 = ((float)rand() / (float)RAND_MAX) * 6.283185f; |
||||
newAsteroids.push_back({ (int)a.nSize >> 1 ,a.x, a.y, 10.0f * sinf(angle1), 10.0f * cosf(angle1), 0.0f }); |
||||
newAsteroids.push_back({ (int)a.nSize >> 1 ,a.x, a.y, 10.0f * sinf(angle2), 10.0f * cosf(angle2), 0.0f }); |
||||
} |
||||
|
||||
// Remove asteroid - Same approach as bullets
|
||||
a.x = -100; |
||||
nScore += 100; // Small score increase for hitting asteroid
|
||||
} |
||||
} |
||||
} |
||||
|
||||
// Append new asteroids to existing vector
|
||||
for(auto a:newAsteroids) |
||||
vecAsteroids.push_back(a); |
||||
|
||||
// Clear up dead objects - They are out of game space
|
||||
|
||||
// Remove asteroids that have been blown up
|
||||
if (vecAsteroids.size() > 0) |
||||
{ |
||||
auto i = remove_if(vecAsteroids.begin(), vecAsteroids.end(), [&](sSpaceObject o) { return (o.x < 0); }); |
||||
if (i != vecAsteroids.end()) |
||||
vecAsteroids.erase(i); |
||||
} |
||||
|
||||
if (vecAsteroids.empty()) // If no asteroids, level complete! :) - you win MORE asteroids!
|
||||
{ |
||||
// Level Clear
|
||||
nScore += 1000; // Large score for level progression
|
||||
vecAsteroids.clear(); |
||||
vecBullets.clear(); |
||||
|
||||
// Add two new asteroids, but in a place where the player is not, we'll simply
|
||||
// add them 90 degrees left and right to the player, their coordinates will
|
||||
// be wrapped by th enext asteroid update
|
||||
vecAsteroids.push_back({ (int)16, 30.0f * sinf(player.angle - 3.14159f/2.0f) + player.x, |
||||
30.0f * cosf(player.angle - 3.14159f/2.0f) + player.y, |
||||
10.0f * sinf(player.angle), 10.0f*cosf(player.angle), 0.0f }); |
||||
|
||||
vecAsteroids.push_back({ (int)16, 30.0f * sinf(player.angle + 3.14159f/2.0f) + player.x, |
||||
30.0f * cosf(player.angle + 3.14159f/2.0f) + player.y, |
||||
10.0f * sinf(-player.angle), 10.0f*cosf(-player.angle), 0.0f }); |
||||
} |
||||
|
||||
// Remove bullets that have gone off screen
|
||||
if (vecBullets.size() > 0) |
||||
{ |
||||
auto i = remove_if(vecBullets.begin(), vecBullets.end(), [&](sSpaceObject o) { return (o.x < 1 || o.y < 1 || o.x >= ScreenWidth() - 1 || o.y >= ScreenHeight() - 1); }); |
||||
if (i != vecBullets.end()) |
||||
vecBullets.erase(i); |
||||
} |
||||
|
||||
// Draw Bullets
|
||||
for (auto b : vecBullets) |
||||
Draw(b.x, b.y); |
||||
|
||||
// Draw Ship
|
||||
DrawWireFrameModel(vecModelShip, player.x, player.y, player.angle); |
||||
|
||||
// Draw Score
|
||||
DrawString(2, 2, L"SCORE: " + to_wstring(nScore)); |
||||
return true; |
||||
} |
||||
|
||||
void DrawWireFrameModel(const vector<pair<float, float>> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE) |
||||
{ |
||||
// pair.first = x coordinate
|
||||
// pair.second = y coordinate
|
||||
|
||||
// Create translated model vector of coordinate pairs
|
||||
vector<pair<float, float>> vecTransformedCoordinates; |
||||
int verts = vecModelCoordinates.size(); |
||||
vecTransformedCoordinates.resize(verts); |
||||
|
||||
// Rotate
|
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
vecTransformedCoordinates[i].first = vecModelCoordinates[i].first * cosf(r) - vecModelCoordinates[i].second * sinf(r); |
||||
vecTransformedCoordinates[i].second = vecModelCoordinates[i].first * sinf(r) + vecModelCoordinates[i].second * cosf(r); |
||||
} |
||||
|
||||
// Scale
|
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first * s; |
||||
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second * s; |
||||
} |
||||
|
||||
// Translate
|
||||
for (int i = 0; i < verts; i++) |
||||
{ |
||||
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first + x; |
||||
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second + y; |
||||
} |
||||
|
||||
// Draw Closed Polygon
|
||||
for (int i = 0; i < verts + 1; i++) |
||||
{ |
||||
int j = (i + 1); |
||||
DrawLine(vecTransformedCoordinates[i % verts].first, vecTransformedCoordinates[i % verts].second,
|
||||
vecTransformedCoordinates[j % verts].first, vecTransformedCoordinates[j % verts].second, PIXEL_SOLID, col); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
|
||||
int main() |
||||
{ |
||||
// Use olcConsoleGameEngine derived app
|
||||
OneLoneCoder_Asteroids game; |
||||
game.ConstructConsole(160, 100, 8, 8); |
||||
game.Start(); |
||||
return 0; |
||||
} |
Loading…
Reference in new issue