@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.