Asteroids Example (Code)
-
Hallo,
Ich denke, dies ist eine kleine ruhige Ecke, wo ich was vorstellen kann, was im Hauptbereich vielleicht etwas aufdringlich wirken kann.Vielleicht hat jemand mein Foto eines Asteroids Klon gesehen, kann sich aber immer noch nicht richtig was darunter vorstellen. Um zu zeigen, wie easy das sein kann, will ich mal mein Example in Codeform zeigen. Ich bin mir bewußt, das der selbe Code von mir in 2 Jahren ganz anders aussehen wird, es ist also ausdrücklich kein Master, sondern eher ein momentanes proof of concept.
Das ganze ist eine Windows Konsole Ausgabe, es wird geerbt von
zpEngine
, welche wieder vonzpConsoleEngine
erbt.#pragma once #include "zpEngine.h" #include "TransformVector_T.h" class AsteroidsGame : public zpEngine { struct SpaceObject { zp::VectorShape2D<float> Shape, Source; zp::TransformVector<float> Transform; zp::Vector2D<float> Position, Start; zp::Vector2D<float> Velocity; float Size = 0; float Heading = 0; float Rotate = 0; }; // variables SpaceObject Ship; zp::ColorDot ShipColor; int StartNumOfAsteroids = 0; zp::Vector2D<float> StartPosOfAsteroids; float StartSizeOfAsteroids = 0; float StartSpeedOfAsteroids = 0; float SpeedOfAsteroids = 0; std::vector<SpaceObject> Asteroids; zp::ColorDot AsteroidColor; std::vector<SpaceObject> Bullets; zp::ColorDot BulletColor; bool PlayerDead = false; bool PlayerWon = false; int GameScore = 0; int GameStage = 0; int StartStageTime = 0; float StageTime = 0; // methodes void stageDone(); void gameOver(); void resetShip(); void resetGame(); void createAsteroids(const int N, const float size, const zp::Vector2D<float>& position, std::vector<SpaceObject>& asteroids); void removeOffscreenAsteroids(); void removeOffscreenBullets(); void wrapCoordinates(const zp::Vector2D<float>& in, zp::Vector2D<float>& out) const; void wrapCoordinates(zp::Vector2D<float>& pos) const; void drawShip(); void drawAsteroid(const SpaceObject& asteroid); void drawBullet(const SpaceObject& bullet); bool checkCollision(const SpaceObject& lhs, const SpaceObject& rhs) const; bool checkCollision(const SpaceObject& obj, const zp::Vector2D<float>& point) const; bool isPointInsideCircle(const zp::Vector2D<float>& center, const float radius, const zp::Vector2D<float>& point) const; void stopObject(SpaceObject& obj) const; void stopObjects(); // overwritten methodes virtual bool onUserCreate() override; virtual bool onUserUpdate(float elapsedTime) override; virtual bool handleInput(const float elapsedTime) override; virtual void setDot(const int32_t x, const int32_t y, const zp::ColorDot& color) override; public: AsteroidsGame(const std::string& appName = {}); };
#include "AsteroidsGame.h" AsteroidsGame::AsteroidsGame(const std::string& appName) { setAppName(appName); }; bool AsteroidsGame::onUserCreate() { screenFont().text.color = { zp::DCHAR::MED, zp::COLOR::DRED, zp::COLOR::DRED, zp::PLOTMODE::BGROUND }; // create Ship Ship.Source = zp::TriangleShape2D<float>({ -5, 0 }, { 5, 0 }, { 0, -15 }); Ship.Position = canvas().size.center(); ShipColor = zp::ColorDot::DGREY(); // create Asteroids StartNumOfAsteroids = 3; StartPosOfAsteroids = { 50, 50 }; StartSizeOfAsteroids = 25.f; StartSpeedOfAsteroids = 20.f; SpeedOfAsteroids = StartSpeedOfAsteroids; AsteroidColor = zp::ColorDot::YELLOW(); createAsteroids(StartNumOfAsteroids, StartSizeOfAsteroids, StartPosOfAsteroids, Asteroids); // create bullet BulletColor = zp::ColorDot::BLUE(); // create score GameStage = 1; GameScore = 0; StartStageTime = 500; StageTime = static_cast<float>(StartStageTime); return true; } bool AsteroidsGame::onUserUpdate(float elapsedTime) { // update and draw ship Ship.Shape = Ship.Source; Ship.Transform.translateAbsolute(Ship.Position); // thrust Ship.Transform.rotateAt(Ship.Shape.center(), Ship.Heading); // steer Ship.Position += Ship.Velocity * elapsedTime; Ship.Transform.transform(Ship.Shape); wrapCoordinates(Ship.Position); // keep in space drawShip(); // update and draw asteroids for (auto& asteroid : Asteroids) { asteroid.Shape = asteroid.Source; asteroid.Position += asteroid.Velocity * elapsedTime; asteroid.Transform.translateAbsolute(asteroid.Position); // move asteroid.Transform.rotate(asteroid.Rotate += 0.5f * elapsedTime); // rotate if (asteroid.Rotate > 2.f * zp::math::PI<float>) asteroid.Rotate = asteroid.Rotate - 2.f * zp::math::PI<float>; asteroid.Transform.transform(asteroid.Shape); wrapCoordinates(asteroid.Position); // keep in space drawAsteroid(asteroid); } // update and draw bullets std::vector<SpaceObject> childAsteroids; // prepare child asteroids for (auto& bullet : Bullets) { bullet.Position += bullet.Velocity * elapsedTime; // move drawBullet(bullet); // check collision with asteroids const float childAsteroidSize = StartSizeOfAsteroids / 2.f; for (auto& asteroid : Asteroids) { if (checkCollision(asteroid, bullet.Position)) { // asteroid hit bullet.Position = screen().size.right().negate(); // move offscreen if (asteroid.Size > childAsteroidSize) { // create 2 child asteroids createAsteroids(2, childAsteroidSize, asteroid.Shape.center(), childAsteroids); } asteroid.Position = screen().size.right().negate(); // move offscreen GameScore += 100; } } } // append child asteroids to existing vector for (const auto& asteroid : childAsteroids) Asteroids.push_back(asteroid); removeOffscreenBullets(); removeOffscreenAsteroids(); // stage time if (!PlayerDead && !PlayerWon) { StageTime -= 15.f * elapsedTime; } if (StageTime < 0) { PlayerDead = true; } // check ship collision with asteroids for (const auto& asteroid : Asteroids) { if (checkCollision(asteroid, Ship)) { PlayerDead = true; } } // player won? if (Asteroids.empty()) { PlayerWon = true; } // draw score drawString( screenFont().text.font, 0, 1, " Score " + std::to_string(GameScore) + " Stage " + std::to_string(GameStage) + " Time " + std::to_string(zp::math::toInt<int>(StageTime)), screenFont().text.color); return true; } bool AsteroidsGame::handleInput(const float elapsedTime) { if (getKey(VK_ESCAPE).pressed) // exit game return false; if (PlayerWon) { stageDone(); } if (PlayerDead) { gameOver(); } if (getKey(VK_BACK).pressed) // reset game { resetGame(); } // steer ship if (getKey(VK_RIGHT).held) { Ship.Heading -= 2.2f * elapsedTime; } if (getKey(VK_LEFT).held) { Ship.Heading += 2.2f * elapsedTime; } // thrust ship if (getKey(VK_UP).held) { Ship.Velocity += zp::vector::getDirectionVector2D<float>(-Ship.Heading) * 35.f * elapsedTime; } if (getKey(VK_DOWN).held) { Ship.Velocity -= zp::vector::getDirectionVector2D<float>(-Ship.Heading) * 35.f * elapsedTime; } // fire bullet if (getKey(VK_SPACE).released) { SpaceObject bullet; bullet.Position = Ship.Shape.vertices()[2]; bullet.Heading = Ship.Heading; bullet.Velocity = zp::vector::getDirectionVector2D<float>(-bullet.Heading) * 50.f; Bullets.push_back(bullet); } return true; } void AsteroidsGame::createAsteroids(const int N, const float size, const zp::Vector2D<float>& position, std::vector<SpaceObject>& asteroids) { asteroids.clear(); constexpr auto V = 20; // vertices of asteroid for (auto i = 0; i < N; ++i) { std::vector<zp::Vector2D<float>> vertices; for (auto i = 0; i < V; ++i) { const float radius = random().uniformReal<float>(0.9f, 1.1f) * size; const float angle = (i * 1.f) / (V * 1.f) * (2.f * zp::math::PI<float>); vertices.push_back(zp::math::toCartesian<float>({ radius, angle })); } SpaceObject asteroid; asteroid.Source = zp::PolygonShape2D(vertices); asteroid.Position = random().uniformReal<float>(0.7f, 1.3f) * position; asteroid.Size = size; asteroid.Heading = random().uniformReal<float>(0, 2.f * zp::math::PI<float>); asteroid.Velocity = random().uniformReal<float>(0.7f, 1.3f) * zp::vector::getDirectionVector2D<float>(asteroid.Heading) * SpeedOfAsteroids; asteroids.push_back(asteroid); } } void AsteroidsGame::removeOffscreenAsteroids() { if (std::size(Asteroids)) { auto i = std::remove_if(Asteroids.begin(), Asteroids.end(), [&](SpaceObject obj) { return (obj.Position.x < 0); }); if (i != Asteroids.end()) Asteroids.erase(i); } } void AsteroidsGame::removeOffscreenBullets() { const float scrWidth = screen().size.right().x; const float scrHeight = screen().size.right().y; if (std::size(Bullets)) { auto i = std::remove_if(Bullets.begin(), Bullets.end(), [&](SpaceObject obj) { return ( obj.Position.x < 1 || obj.Position.x >= scrWidth - 1 || obj.Position.y < 1 || obj.Position.y >= scrHeight - 1); }); if (i != Bullets.end()) Bullets.erase(i); } } void AsteroidsGame::drawShip() { drawVectorShape(Ship.Shape, ShipColor); } void AsteroidsGame::drawAsteroid(const SpaceObject& asteroid) { drawVectorShape(asteroid.Shape, AsteroidColor); } void AsteroidsGame::drawBullet(const SpaceObject& bullet) { drawPoint(bullet.Position, BulletColor); } void AsteroidsGame::wrapCoordinates(const zp::Vector2D<float>& in, zp::Vector2D<float>& out) const { const float scrWidth = screen().size.right().x; const float scrHeight = screen().size.right().y; out = in; if (in.x < 0.f) out.x = in.x + scrWidth; if (in.x >= scrWidth) out.x = in.x - scrWidth; if (in.y < 0.f) out.y = in.y + scrHeight; if (in.y >= scrHeight) out.y = in.y - scrHeight; } void AsteroidsGame::wrapCoordinates(zp::Vector2D<float>& pos) const { const float scrWidth = screen().size.right().x; const float scrHeight = screen().size.right().y; const zp::Vector2D<float> posIn = pos; if (posIn.x < 0.f) pos.x = posIn.x + scrWidth; if (posIn.x >= scrWidth) pos.x = posIn.x - scrWidth; if (posIn.y < 0.f) pos.y = posIn.y + scrHeight; if (posIn.y >= scrHeight) pos.y = posIn.y - scrHeight; } void AsteroidsGame::setDot(const int32_t x, const int32_t y, const zp::ColorDot& color) { zp::Vector2D<float> wrappedPos; wrapCoordinates(zp::utils::castPointToVector2D<float>({ x, y }), wrappedPos); zpConsoleEngine::setDot(zp::math::toInt<int32_t>(wrappedPos.x), zp::math::toInt<int32_t>(wrappedPos.y), color); } bool AsteroidsGame::isPointInsideCircle(const zp::Vector2D<float>& center, const float radius, const zp::Vector2D<float>& point) const { return std::sqrtf((point.x - center.x) * (point.x - center.x) + (point.y - center.y) * (point.y - center.y)) < radius; } bool AsteroidsGame::checkCollision(const SpaceObject& lhs, const SpaceObject& rhs) const { bool collide = false; for (const auto& point : rhs.Shape.vertices()) { if (isPointInsideCircle(lhs.Shape.center(), lhs.Size, point)) { collide = true; break; } } return collide; } bool AsteroidsGame::checkCollision(const SpaceObject& obj, const zp::Vector2D<float>& point) const { return isPointInsideCircle(obj.Shape.center(), obj.Size, point); } void AsteroidsGame::stageDone() { stopObjects(); } void AsteroidsGame::gameOver() { stopObjects(); fillScreen(zp::ColorDot::RED()); screenFont().text.color = { zp::DCHAR::SOLID, zp::COLOR::BLACK, zp::COLOR::BLACK, zp::PLOTMODE::BGROUND }; } void AsteroidsGame::resetGame() { fillScreen(screen().color); screenFont().text.color = { zp::DCHAR::SOLID, zp::COLOR::DRED, zp::COLOR::DRED, zp::PLOTMODE::BGROUND }; if (PlayerDead) { GameStage = 1; GameScore = 0; SpeedOfAsteroids = StartSpeedOfAsteroids; PlayerDead = false; } else if (PlayerWon) { GameScore += 300 * GameStage; GameScore += zp::math::toInt<int>(StageTime) * 2; GameStage ++; SpeedOfAsteroids += 5.f; PlayerWon = false; } resetShip(); Bullets.clear(); StageTime = static_cast<float>(StartStageTime); createAsteroids(StartNumOfAsteroids, StartSizeOfAsteroids, StartPosOfAsteroids, Asteroids); } void AsteroidsGame::stopObject(SpaceObject& obj) const { obj.Velocity.clear(); } void AsteroidsGame::stopObjects() { stopObject(Ship); for (auto& asteroid : Asteroids) stopObject(asteroid); for (auto& bullet : Bullets) stopObject(bullet); } void AsteroidsGame::resetShip() { Ship.Position = screen().size.center(); Ship.Velocity.clear(); Ship.Heading = 0; }
#include "AsteroidsGame.h" int main() { zp::Rect console = { 256, 240 }; zp::Rect dot = { 4, 4 }; zp::Rect window = { console.width * dot.width, console.height * dot.height }; AsteroidsGame example("Asteroids!"); if (!example.constructConsole(window, dot)) return -1; example.start(); }
Yoh, irgendwann muss ich nunmal in die Öffentlichkeit...
-
Hallo,
was ist zpEngine? Willst du feedback?
Ich kann deinen Code sehr gut lesen, fand beim überfliegen erstmal nur das hier// append child asteroids to existing vector for (const auto& asteroid : childAsteroids) Asteroids.push_back(asteroid);
unschön (besser: insert).
-
@zeropage Als kurzes Feedback: Du unterteilst deine,
OnUserUpdate
, mit Kommentaren. Die Unterteilung könnten gute Startpunkte sein, um das in weitere Teilfunktionen aufzuteilen. Deine anderen Funktionen haben jeweils eine schöne Länge und kommentieren sich damit fast von selbst.
-
Hier meine 2 cent:
- checkCollision() mit
std::find_if
Noch besser als freie Funktion, da sie nur von den beiden Parametern abhängt (isPointIndsideCircle
dann auch als freie Funktion)
bool checkCollision(const SpaceObject& lhs, const SpaceObject& rhs) const { auto predicate = [&]( Point const& pt ) { return isPointIndsideCircle( lhs.Shape.center(), lhs.Size, pt ); }; return std::find_if( std::begin( rhs.vertices() ), std::end( rhs.vertices() ), predicate ) != std::end( std::end( rhs.vertices() ) ); // der Lesbarkeit wegen vllt auch als Zweizeiler auto const pos = std::find_if( std::begin( rhs.vertices() ), std::end( rhs.vertices() ), predicate ); return pos != std::end( std::end( rhs.vertices() ); }
- analog dazu removeOffScreenIrgendwas:
void AsteroidsGame::removeOffscreenAsteroids() { auto predicate = []( SpaceObject const& obj ) { return obj.Position.x < 0; }; Asteroids.erase( std::remove_if( std::begin( Asteroids ), std::end( Asteroids ), predicate ), std::end( Asteroids ) ); }
- wrapCoordinates mit return value
zp::Vector2D<float> AsteroidsGame::wrapCoordinates(zp::Vector2D<float> const& pos) const { return /*code*/ } void AsteroidsGame::setDot(const int32_t x, const int32_t y, const zp::ColorDot& color) { zp::Vector2D<float> const wrappedPos = wrapCoordinates(zp::utils::castPointToVector2D<float>({ x, y }) ); zpConsoleEngine::setDot(zp::math::toInt<int32_t>(wrappedPos.x), zp::math::toInt<int32_t>(wrappedPos.y), color); }
- Score, Anzahl Asteroiden, etc. in eine eigene Struktur
GameState
verpacken. Damit könntest du Spielstände speichern und wieder laden, wenn du den GameState irgendwo sicherst - Farbe etc. in eigene Struktur
DisplaySettings
etc. verpacken, dann liegen sie zentral vor und können ebenfalls als Konfiguration gespeichert/geladen werden. Man könnte dann ebenfalls mehrere Farbschemata definieren und auswählbar machen (oder selbst definieren).
- checkCollision() mit
-
Danke für die Beiträge. Das freut mich ja, das mein Code leserlich ist. Die Anregungen nehme ich gerne auf.
Aber eine Frage wegen "freien Funktionen". Das sind ja keine Membermethoden, sondern eben freie Funktionen? Die müsste ich ja dann in einen eigenen namespace packen. Davon habe ich zwar ein paar passende, aber diese genannten Funktionen hier sind doch viel zu speziell, die müsste ich doch viel mehr allgemeinern, damit sie freie Funktionen sein können?
Hier bezieht sich
checkCollision()
nur auf einen Circle, Point und dann noch das eigeneSpaceObject
. Wenn es eine freie Funktion wäre, dann müsste sie doch zB auch Rectangles prüfen, bzw beliebige Formen?
-
@Jockelx sagte in Asteroids Example (Code):
Hallo,
was ist zpEngine?
Ja,
zpEngine
erbt eben vonzpConsoleEngine
. Und diese verwaltet ein Konsolenfenster, währendzpEngine
selbst nur dessen Methoden verwendet. Wahrscheinlich blöd ausgedrückt, aber es soll etwas wie verschiedene Layers darstellen.Und der Code ist schon deutlich größer und bietet wohl noch mehr Verbesserungsmöglichkeiten. Alleine die
.h
sieht schon so aus, und direkt beim Einfügen fallen mir selbst schon ein paar Dinge auf, die ich besser machen könnte#pragma once #include "zpConsoleEngine.h" #include "zpConsolePlot.h" #include "zpUtils.h" #include "zpCursor.h" #include "zpFigletFont.h" #include "zpColors.h" #include "Random_T.h" #include "VectorForms_T.h" namespace zp { using CanvasRect = tRect<float>; struct zpFont { figlet::Font font; ColorDot color = ColorDot::Default();; }; struct Canvas { CanvasRect size; ColorDot color; Rect dotSize; zpFont text, number; Vector2D<float> offset; Vector2D<float> startPan; Cursor mouseCursor; Cursor keyCursor; MOUSE_BUTTON scrollButton = zp::M_MIDDLE; Point infoPos; Vector2D<float> gridSize; Vector2D<float> gridScale; ColorDot gridColor; bool setGrid = false; bool printSelf = false; bool printCursorInfo = false; bool drawCursor = false; bool printCursorCoords = false; bool scrollEnabled = false; bool scaleEnabled = false; }; } class zpEngine : public zpConsoleEngine { private: zp::Directory fontDir, colorDir; zp::Canvas worldCanvas, screenCanvas; zp::CanvasRect clippedToScreenRect; zp::Random rand; public: zpEngine(const std::string& appName = {}); zp::Canvas& canvas(); zp::Canvas& screen(); const zp::Canvas& canvas() const; const zp::Canvas& screen() const; zp::Random& random(); const zp::Vector2D<float>& toWorldPoint(const zp::Vector2D<float>& scr) const; const zp::Vector2D<float>& toScreenPoint(const zp::Vector2D<float>& wrld) const; std::vector<zp::Vector2D<float>> toWorldPoints(const std::vector<zp::Vector2D<float>>& scr) const; std::vector<zp::Vector2D<float>> toScreenPoints(const std::vector<zp::Vector2D<float>>& wrld) const; protected: const zp::CanvasRect& screenRect() const; const zp::Directory& fontDirectory() const; const zp::Directory& colorDirectory() const; void setColors( const zp::ColorDot& scr, const zp::ColorDot& text, const zp::ColorDot& number, const zp::ColorDot& mouseCur, const zp::ColorDot& keyCur); void updateDotSize(const int32_t n); virtual bool resetScreen(); private: bool onEngineCreate(); bool onEngineUpdate(float elapsedTime); virtual bool onUserCreate(); virtual bool onUserUpdate(float elapsedTime); virtual bool handleInput(const float elapsedTime); void setWorldCanvas(); bool loadCanvasFonts(); bool resetScreenCanvas(); void clipToScreen(); void showCanvasInfo(const int32_t x, int32_t& y); void showCursorInfo(const int32_t x, int32_t& y); void drawCursor(); virtual void printCanvasInfo(const int32_t x, int32_t& y); virtual void printCursorInfo(const int32_t x, int32_t& y); virtual void drawKeyCursor(); virtual void drawMouseCursor(); virtual void printCursorCoords(); public: void drawOffsetAxes(const zp::ColorDot& color); void drawGridDots(const zp::Vector2D<float>& gridSize, const zp::ColorDot& color); void drawPoint(const zp::Vector2D<float>& pos, const zp::ColorDot& color); void drawLine(const zp::Vector2D<float>& begin, const zp::Vector2D<float>& end, const zp::ColorDot& color); void drawLines(const std::vector<zp::Vector2D<float>>& points, const zp::ColorDot& color); void drawTriangle(const zp::Vector2D<float>& pointA, const zp::Vector2D<float>& pointB, const zp::Vector2D<float>& pointC, const zp::ColorDot& color); void drawFillTriangle(const zp::Vector2D<float>& pointA, const zp::Vector2D<float>& pointB, const zp::Vector2D<float>& pointC, const zp::ColorDot& color); void drawBox(const zp::tRect<float>& rect, const zp::ColorDot& color); void drawBox(const zp::Vector2D<float>& left, const zp::Vector2D<float>& right, const zp::ColorDot& color); void drawBox(const zp::Vector2D<float>& pos, const float width, const float height, const zp::ColorDot& color); void drawBox(const zp::Vector2D<float>& center, const int delta, const zp::ColorDot& color); void drawFillBox(const zp::tRect<float>& rect, const zp::ColorDot& color); void drawFillBox(const zp::Vector2D<float>& left, const zp::Vector2D<float>& right, const zp::ColorDot& color); void drawFillBox(const zp::Vector2D<float>& pos, const float width, const float height, const zp::ColorDot& color); void drawFillBox(const zp::Vector2D<float>& center, const int delta, const zp::ColorDot& color); void drawCircle(const zp::Vector2D<float>& center, const int radius, const zp::ColorDot& color); void drawCircle(const zp::Vector2D<float>& center, const float radius, const zp::ColorDot& color); void drawCircle(const zp::Vector2D<float>& center, const zp::Vector2D<float>& radius, const zp::ColorDot& color); void drawFillCircle(const zp::Vector2D<float>& center, const int radius, const zp::ColorDot& color); void drawFillCircle(const zp::Vector2D<float>& center, const float radius, const zp::ColorDot& color); void drawEllipse(const zp::Vector2D<float>& center, const zp::Vector2D<float>& radius, const zp::ColorDot& color); void drawWireFrame(const std::vector<zp::Vector2D<float>>& model, const zp::Vector2D<float>& pos, const float angle, const float scale, const zp::ColorDot& color); // to do void drawPolygon(const std::vector<zp::Vector2D<float>>& points, const zp::ColorDot& color); void drawVectorShape(const zp::VectorShape2D<float>& shape, const zp::ColorDot& color); void drawArea(const zp::Vector2D<float>& pos, const zp::ColorDot& color); void drawString(const zp::figlet::Font& font, const zp::Vector2D<float>& pos, const std::string& str, const zp::ColorDot& color); void drawString(const zp::figlet::Font& font, const int x, const int y, const std::string& str, const zp::ColorDot& color); };
-
@Jockelx sagte in Asteroids Example (Code):
Willst du feedback?
Hallo, das ist eine gute Frage, weshalb ich das Bedürfnis habe, Code zeigen zu wollen. Denn wenn ich meine Codes vor 5 Jahren sehe, schäme ich mich in Grund und Boden. Was müssen die Leute wohl gedacht haben, als ich sie gezeigt habe?
Trotzdem will man sich nicht so ganz alleine damit beschäftigen.Auf jeden Fall habe ich jetzt Eure Anregungen und Verbesserungen umgesetzt, wie es mir möglich ist. Ich wollte noch jeden hardcodierten Wert in eine Variable überführen, aber das wurde mir zu kleinteilig, und ob das wirklich zur Übersichtlichkeit beiträgt?
Die Namen der User, dessen Vorschläge ich umgesetzt habe, habe ich an den betreffenden Stellen kommentiert.
#pragma once #include "zpEngine.h" #include "TransformVector_T.h" namespace game { struct SpaceObject { zp::VectorShape2D<float> Shape, Source; zp::TransformVector<float> Transform; zp::Vector2D<float> Position, Velocity; float Size = 0; float Heading = 0; float Rotate = 0; }; // @Hustbaer // to do: load or switch in onUserCreate struct DisplaySettings { zp::ColorDot ScreenColor; zp::ColorDot AsteroidColor; zp::ColorDot ShipColor; zp::ColorDot BulletColor; zp::zpFont ScoreFont; }; // dito struct GameSettings { zp::TriangleShape2D<float> Ship; zp::Vector2D<float> ShipStartPosition; float ShipSpeed = 0; float ShipSteerSpeed = 0; float BulletSpeed = 0; int NumOfAsteroids = 0; zp::Vector2D<float> AsteroidsStartPosition; float AsteroidsRadius = 0; float AsteroidsSpeed = 0; float AsteroidsDeltaSpeed = 0; float AsteroidsRotateSpeed = 0; int StageTime = 0; float StageDeltaTime = 0; }; // to do: autosave every 5 GameStages, load on user input in onUserCreate struct PlayerState { float AsteroidsSpeed = 0; int GameScore = 0; int GameStage = 0; float StageTime = 0; bool Dead = false; bool Won = false; }; // @Hustbaer static const bool checkCollision(const SpaceObject& lhs, const SpaceObject& rhs) { auto predicate = [&](const zp::Vector2D<float>& point) { return zp::utils::isPointInsideCircle(lhs.Shape.center(), lhs.Size, point); }; const auto pos = std::find_if(std::begin(rhs.Shape.vertices()), std::end(rhs.Shape.vertices()), predicate); return pos != std::end(rhs.Shape.vertices()); } static const bool checkCollision(const SpaceObject& obj, const zp::Vector2D<float>& point) { return zp::utils::isPointInsideCircle(obj.Shape.center(), obj.Size, point); } static zp::Vector2D<float> offscreenPos() { return { -1, -1 }; } static void removeOffscreen(std::vector<SpaceObject>& objects) { auto predicate = [](const SpaceObject& obj) { return obj.Position.x < 0 || obj.Position.y < 0; }; const auto pos = std::remove_if(std::begin(objects), std::end(objects), predicate); objects.erase(pos, std::end(objects)); } static void wrapCoordinates(zp::Vector2D<float>& pos, const zp::Vector2D<float>& boundary) { const zp::Vector2D<float> posIn = pos; if (posIn.x < 0.f) pos.x = posIn.x + boundary.x; if (posIn.x >= boundary.x) pos.x = posIn.x - boundary.x; if (posIn.y < 0.f) pos.y = posIn.y + boundary.y; if (posIn.y >= boundary.y) pos.y = posIn.y - boundary.y; } static zp::Vector2D<float> wrapCoordinates(const zp::Vector2D<float>& pos, const zp::Vector2D<float>& boundary) { zp::Vector2D<float> wrappedPos = pos; if (pos.x < 0.f) wrappedPos.x = pos.x + boundary.x; if (pos.x >= boundary.x) wrappedPos.x = pos.x - boundary.x; if (pos.y < 0.f) wrappedPos.y = pos.y + boundary.y; if (pos.y >= boundary.y) wrappedPos.y = pos.y - boundary.y; return wrappedPos; } } class AsteroidsGame : public zpEngine { // variables game::DisplaySettings Display; game::GameSettings Game; game::PlayerState Player; game::SpaceObject Ship; std::vector<game::SpaceObject> Asteroids; std::vector<game::SpaceObject> Bullets; // methodes void createDisplay(); void createGame(); void createPlayerState(); void createShip(); void createStartAsteroids(); void createAsteroids( const int N, const float radius, const zp::Vector2D<float>& position, std::vector<game::SpaceObject>& asteroids); void updateShip(const float elapsedTime); void updateAsteroids(const float elapsedTime); void updateBullets(const float elapsedTime); void updatePlayerState(const float elapsedTime); void drawShip(); void drawAsteroid(const game::SpaceObject& asteroid); void drawBullet(const game::SpaceObject& bullet); void drawScore(); void stopObject(game::SpaceObject& obj) const; void stopObjects(); void stageDone(); void gameOver(); void resetShip(); void resetGame(); // overwritten methodes virtual bool onUserCreate() override; virtual bool onUserUpdate(float elapsedTime) override; virtual bool handleInput(const float elapsedTime) override; virtual void setDot(const int32_t x, const int32_t y, const zp::ColorDot& color) override; public: AsteroidsGame(const std::string& appName = {}); };
#include "AsteroidsGame.h" using namespace game; AsteroidsGame::AsteroidsGame(const std::string& appName) { setAppName(appName); }; void AsteroidsGame::createDisplay() { Display.ScreenColor = zp::ColorDot::Default(); Display.AsteroidColor = zp::ColorDot::YELLOW(); Display.ShipColor = zp::ColorDot::DGREY(); Display.BulletColor = zp::ColorDot::BLUE(); Display.ScoreFont.font = canvas().text.font; Display.ScoreFont.color = zp::ColorDot::DRED(); canvas().color = Display.ScreenColor; canvas().text.color = Display.ScoreFont.color; } void AsteroidsGame::createGame() { Game.Ship = { { -5, 0 }, { 5, 0 }, { 0, -15 } }; Game.ShipStartPosition = canvas().size.center(); Game.ShipSpeed = 35.f; Game.ShipSteerSpeed = 2.2f; Game.BulletSpeed = 50.f; Game.NumOfAsteroids = 3; Game.AsteroidsStartPosition = { 50, 50 }; Game.AsteroidsRadius = 25.f; Game.AsteroidsSpeed = 20.f; Game.AsteroidsDeltaSpeed = 5.f; Game.AsteroidsRotateSpeed = 0.5f; Game.StageTime = 500; Game.StageDeltaTime = 15.f; } void AsteroidsGame::createPlayerState() { Player.GameStage = 1; Player.GameScore = 0; Player.StageTime = static_cast<float>(Game.StageTime); Player.AsteroidsSpeed = Game.AsteroidsSpeed; } void AsteroidsGame::createShip() { Ship.Source = Game.Ship; Ship.Position = Game.ShipStartPosition; } void AsteroidsGame::createAsteroids(const int N, const float size, const zp::Vector2D<float>& position, std::vector<SpaceObject>& asteroids) { asteroids.clear(); constexpr auto V = 20; // vertices of asteroid for (auto i = 0; i < N; ++i) { std::vector<zp::Vector2D<float>> vertices; for (auto i = 0; i < V; ++i) { const float radius = random().uniformReal<float>(0.9f, 1.1f) * size; const float angle = (i * 1.f) / (V * 1.f) * zp::math::PI_2<float>; vertices.push_back(zp::math::toCartesian<float>(radius, angle)); } SpaceObject asteroid; asteroid.Source = zp::PolygonShape2D(vertices); asteroid.Position = random().uniformReal<float>(0.7f, 1.3f) * position; asteroid.Size = size; asteroid.Heading = random().uniformReal<float>(0, zp::math::PI_2<float>); asteroid.Velocity = random().uniformReal<float>(0.7f, 1.3f) * zp::vector::getDirectionVector2D<float>(asteroid.Heading) * Player.AsteroidsSpeed; asteroids.push_back(asteroid); } } void AsteroidsGame::createStartAsteroids() { createAsteroids(Game.NumOfAsteroids, Game.AsteroidsRadius, Game.AsteroidsStartPosition, Asteroids); } bool AsteroidsGame::onUserCreate() { // @Schlangenmensch createDisplay(); createGame(); createPlayerState(); createShip(); createStartAsteroids(); return true; } void AsteroidsGame::updateShip(const float elapsedTime) { Ship.Shape = Ship.Source; Ship.Position += Ship.Velocity * elapsedTime; Ship.Transform.translate(Ship.Position); // thrust Ship.Transform.rotateRelative(Ship.Shape.center(), Ship.Heading); // steer Ship.Transform.transform(Ship.Shape); wrapCoordinates(Ship.Position, screen().size.right()); // keep in space drawShip(); } void AsteroidsGame::updateAsteroids(const float elapsedTime) { for (auto& asteroid : Asteroids) { asteroid.Shape = asteroid.Source; asteroid.Position += asteroid.Velocity * elapsedTime; asteroid.Transform.translate(asteroid.Position); // move asteroid.Transform.rotate(asteroid.Rotate += Game.AsteroidsRotateSpeed * elapsedTime); // rotate if (asteroid.Rotate > zp::math::PI_2<float>) asteroid.Rotate = asteroid.Rotate - zp::math::PI_2<float>; asteroid.Transform.transform(asteroid.Shape); wrapCoordinates(asteroid.Position, screen().size.right()); // keep in space drawAsteroid(asteroid); } } void AsteroidsGame::updateBullets(const float elapsedTime) { std::vector<SpaceObject> childAsteroids; // prepare child asteroids for (auto& bullet : Bullets) { bullet.Position += bullet.Velocity * elapsedTime; // move if (bullet.Position.x >= screen().size.right().x || bullet.Position.y >= screen().size.right().y) bullet.Position = offscreenPos(); // move offscreen // check collision with asteroids const float childAsteroidSize = Game.AsteroidsRadius / 2.f; for (auto& asteroid : Asteroids) { if (checkCollision(asteroid, bullet.Position)) { // asteroid hit bullet.Position = offscreenPos(); // move offscreen if (asteroid.Size > childAsteroidSize) { // create 2 child asteroids createAsteroids(2, childAsteroidSize, asteroid.Shape.center(), childAsteroids); } asteroid.Position = offscreenPos(); // move offscreen Player.GameScore += 100; } } drawBullet(bullet); } // append child asteroids to existing vector Asteroids.insert(Asteroids.end(), childAsteroids.begin(), childAsteroids.end()); // @JockelX } void AsteroidsGame::updatePlayerState(const float elapsedTime) { // check stage time if (!Player.Dead && !Player.Won) { Player.StageTime -= Game.StageDeltaTime * elapsedTime; } if (Player.StageTime < 0) { Player.Dead = true; } // check ship collision with asteroids for (const auto& asteroid : Asteroids) { if (checkCollision(asteroid, Ship)) { Player.Dead = true; } } // player won? if (Asteroids.empty()) { Player.Won = true; } } bool AsteroidsGame::onUserUpdate(float elapsedTime) { // @Schlangenmensch updateShip(elapsedTime); updateAsteroids(elapsedTime); updateBullets(elapsedTime); removeOffscreen(Bullets); removeOffscreen(Asteroids); updatePlayerState(elapsedTime); drawScore(); return true; } bool AsteroidsGame::handleInput(const float elapsedTime) { if (getKey(VK_ESCAPE).pressed) // exit game return false; if (Player.Won) { stageDone(); } if (Player.Dead) { gameOver(); } if (getKey(VK_BACK).pressed) // reset game { resetGame(); } // steer ship if (getKey(VK_RIGHT).held) { Ship.Heading -= Game.ShipSteerSpeed * elapsedTime; } if (getKey(VK_LEFT).held) { Ship.Heading += Game.ShipSteerSpeed * elapsedTime; } // thrust ship if (getKey(VK_UP).held) { Ship.Velocity += zp::vector::getDirectionVector2D<float>(-Ship.Heading) * Game.ShipSpeed * elapsedTime; } if (getKey(VK_DOWN).held) { Ship.Velocity -= zp::vector::getDirectionVector2D<float>(-Ship.Heading) * Game.ShipSpeed * elapsedTime; } // fire bullet if (getKey(VK_SPACE).released) { SpaceObject bullet; bullet.Position = Ship.Shape.vertices()[2]; bullet.Heading = Ship.Heading; bullet.Velocity = Ship.Velocity + zp::vector::getDirectionVector2D<float>(-bullet.Heading) * Game.BulletSpeed; Bullets.push_back(bullet); } return true; } void AsteroidsGame::setDot(const int32_t x, const int32_t y, const zp::ColorDot& color) { const zp::Vector2D<float> wrappedPos = wrapCoordinates(zp::utils::castPointToVector2D<float>({ x, y }), screen().size.right()); zpConsoleEngine::setDot(zp::math::toInt<int32_t>(wrappedPos.x), zp::math::toInt<int32_t>(wrappedPos.y), color); } void AsteroidsGame::drawShip() { drawVectorShape(Ship.Shape, Display.ShipColor); } void AsteroidsGame::drawAsteroid(const SpaceObject& asteroid) { drawVectorShape(asteroid.Shape, Display.AsteroidColor); } void AsteroidsGame::drawBullet(const SpaceObject& bullet) { drawPoint(bullet.Position, Display.BulletColor); } void AsteroidsGame::drawScore() { drawString( Display.ScoreFont.font, 0, 1, " Score " + std::to_string(Player.GameScore) + " Stage " + std::to_string(Player.GameStage) + " Time " + std::to_string(zp::math::toInt<int>(Player.StageTime)), screen().text.color); } void AsteroidsGame::stageDone() { stopObjects(); } void AsteroidsGame::gameOver() { stopObjects(); fillScreen(zp::ColorDot::RED()); screen().text.color = zp::ColorDot::BLACK(); } void AsteroidsGame::resetGame() { fillScreen(Display.ScreenColor); screen().text.color = Display.ScoreFont.color; if (Player.Dead) { Player.GameStage = 1; Player.GameScore = 0; Player.AsteroidsSpeed = Game.AsteroidsSpeed; Player.Dead = false; } else if (Player.Won) { Player.GameScore += 300 * Player.GameStage; Player.GameScore += zp::math::toInt<int>(Player.StageTime) * 2; Player.GameStage ++; Player.AsteroidsSpeed += Game.AsteroidsDeltaSpeed; Player.Won = false; } resetShip(); Bullets.clear(); Player.StageTime = static_cast<float>(Game.StageTime); createAsteroids(Game.NumOfAsteroids, Game.AsteroidsRadius, Game.AsteroidsStartPosition, Asteroids); } void AsteroidsGame::stopObject(SpaceObject& obj) const { obj.Velocity.clear(); } void AsteroidsGame::stopObjects() { stopObject(Ship); for (auto& asteroid : Asteroids) stopObject(asteroid); for (auto& bullet : Bullets) stopObject(bullet); } void AsteroidsGame::resetShip() { Ship.Position = Game.ShipStartPosition; Ship.Velocity.clear(); Ship.Heading = 0; }
Danke fürs lesen
Edit: Beim selber rüberschauen sind mir ein paar Ungereimtheiten aufgefallen, die habe ich editiert, also nicht wundern, wenn sich Kleinigkeiten geändert haben.