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 von zpConsoleEngine 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).


  • 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 eigene SpaceObject. 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 von zpConsoleEngine. Und diese verwaltet ein Konsolenfenster, während zpEngineselbst 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.


Anmelden zum Antworten