這次寫了一個小遊戲。裡面包含了前幾次練習的功能,但是有些東西是用 class 來寫出來,感覺很新奇!
這次的遊戲大概就是像宇宙飛船一樣,右邊會跑出移動中的敵人,我們在左邊要射擊他們。
因為我之前對 sprite 還有 texture 的概念不太了解,所以查了一下這一篇
所以可以簡單的分類說
Sprite : 是一個物件,可以透過 sprite 來做各種操作!
Texture: 就是外面的包裝紙,你想要怎樣的圖案,就貼在 sprite 上面!
下面是我試做了一個小小的實驗,看看她到底是怎麼運作的。
#include<iostream>
#include<SFML\Graphics.hpp>
#include<SFML\Window.hpp>
#include<SFML\System.hpp>
#include<sstream>
#include<cstdlib>
#include<math.h>
#include<vector>
using namespace sf;
using namespace std;
int main()
{
	RenderWindow window(VideoMode(800, 600), "Space war");
	window.setFramerateLimit(200);
	Texture doggeTex;
	Sprite dogge;
	if (!doggeTex.loadFromFile("textures/dogge.png"))
		throw "Could not find file: dogge.png!";
	dogge.setTexture(doggeTex);
	dogge.setScale(Vector2f(0.15f, 0.15f));
	// 【game loop】
	while (window.isOpen()) // hp  < 0 的時候就 shut down
	{
		Event event;
		while (window.pollEvent(event))
		{
			if (event.type == Event::Closed)
				window.close();
			if (event.type == Event::KeyPressed && event.key.code == Keyboard::Escape)
				window.close();
		}
		/*===========================================================//
		   【Update】
		//===========================================================*/
			if (Keyboard::isKeyPressed(Keyboard::W))
				dogge.move(0.f, -5.f);
			if (Keyboard::isKeyPressed(Keyboard::S))
				dogge.move(0.f, 5.f);
			if (Keyboard::isKeyPressed(Keyboard::D))
				dogge.move(5.f, 0.f);
			if (Keyboard::isKeyPressed(Keyboard::A))
				dogge.move(-5.f, 0.f);
		/*===========================================================//
		   【Draw】
		//===========================================================*/
		window.clear();
		window.draw(dogge);
		window.display();
	}
}
了解了 Sprite 還有 texture 的概念我們就可以開始了!
首先我們要宣告三個 class ,包含 player、enemy、還有 bullets
class Bullet
{
public:
	Sprite shape;
	Bullet(Texture* texture, Vector2f pos)
	{
		this->shape.setTexture(*texture);
		this->shape.setScale(Vector2f(0.1f, 0.1f));
	
		this->shape.setPosition(pos);
	}
	~Bullet() {}
};
class Player
{
public:
	Sprite shape;
	Texture* texture;
	
	int HP;
	int HPMax;
	vector<Bullet> bullets;
	Player(Texture* texture)
	{
		this->HPMax = 10;
		this->HP = this->HPMax;
		this->texture = texture;
		this->shape.setTexture(*texture);
		this->shape.setScale(Vector2f(0.15f, 0.15f));
	}
	~Player() {}
};
class Enemy
{
public:
	Sprite shape;
	int HP;
	int HPMax;
	Enemy(Texture* texture, Vector2u windowSize)
	{
		this->HPMax = rand() % 3 + 1; // 隨機血量 -> 1~4
		this->HP = this->HPMax;
		this->shape.setTexture(*texture);
		this->shape.setScale(0.3f, 0.3f);
		this->shape.setPosition(windowSize.x - this->shape.getGlobalBounds().width, rand() % (int)(windowSize.y - this->shape.getGlobalBounds().height));
	}
	~ Enemy() {}
};
這三個 class 中可以看到有他們的血量,還有關於 texture size 方面的設定,這些都可以在設定 class 的時候設定好。比較不好的地方是,現在他全部都是用 public (雖然只有我一個人再寫),但有些他們獨有的特性還是盡量要用 private 的方式寫會比較好。
srand(time(NULL)); // 這一部分是為了 random 函數的,但詳細不太清楚
	RenderWindow window(VideoMode(800, 600), "Space war");
	window.setFramerateLimit(200);
	
	/*===========================================================//
		Init Text
	//===========================================================*/
	Font font;
	font.loadFromFile("font/Arial.ttf");
	/*===========================================================//
		Init Textures
	//===========================================================*/
	Texture playerTex;
	playerTex.loadFromFile("textures/spaceship.png");
	Texture enemyTex;
	enemyTex.loadFromFile("textures/enemies.png");
	Texture bulletTex;
	bulletTex.loadFromFile("textures/MissileRight.png");
    /*===========================================================//
	   UI init
    //===========================================================*/ 
	Text scoreText;
	scoreText.setFont(font);
	scoreText.setCharacterSize(20);
	scoreText.setFillColor(Color::White);
	scoreText.setPosition(10.f, 10.f);
	Text gameOverText;
	gameOverText.setFont(font);
	gameOverText.setCharacterSize(30);
	gameOverText.setFillColor(Color::Red);
	gameOverText.setPosition(100.f, window.getSize().y / 2);
	gameOverText.setString("Game Over!!!!");
    /*===========================================================//
	   Player init
    //===========================================================*/ 
	int score = 0;
	Player player(&playerTex);
	int shootTimer = 20;
	Text HpText;
	HpText.setFont(font);
	HpText.setCharacterSize(12);
	HpText.setFillColor(Color::White);
	/*===========================================================//
	    Enemy init
	//===========================================================*/
	int enemySpawnTimer = 0;
	vector<Enemy> enemies;
	enemies.push_back(Enemy(&enemyTex, window.getSize()));
	Text eHpText;
	eHpText.setFont(font);
	eHpText.setCharacterSize(12);
	eHpText.setFillColor(Color::White);
接下來這一段是在初始化,也就是設定文字,設定 player, enemy, 還有 bullet 的一些設定。
	/*===========================================================//
		【game loop】
	//===========================================================*/ 
	while (window.isOpen()) // hp  < 0 的時候就 shut down
	{
		Event event;
		while (window.pollEvent(event))
		{
			if (event.type == Event::Closed)
				window.close();
			if (event.type == Event::KeyPressed && event.key.code == Keyboard::Escape)
				window.close();
		}
		if (player.HP > 0)
		{
		
		
		/*===========================================================//
		   【Update】 player 
		//===========================================================*/
			// movement
			int speedInc = 0;
			if (Keyboard::isKeyPressed(Keyboard::W))
				player.shape.move(0.f, -5.f - speedInc);
			if (Keyboard::isKeyPressed(Keyboard::S))
				player.shape.move(0.f, 5.f + speedInc);
			if (Keyboard::isKeyPressed(Keyboard::D))
				player.shape.move(5.f + speedInc, 0.f);
			if (Keyboard::isKeyPressed(Keyboard::A))
				player.shape.move(-5.f - speedInc, 0.f);
			// collision with window
			if (player.shape.getPosition().x <= 0) // left
				player.shape.setPosition(0.f, player.shape.getPosition().y);
			if (player.shape.getPosition().x >= window.getSize().x - player.shape.getGlobalBounds().width) // right
				player.shape.setPosition(window.getSize().x - player.shape.getGlobalBounds().width, player.shape.getPosition().y);
			if (player.shape.getPosition().y <= 0) // Top
				player.shape.setPosition(player.shape.getPosition().x, 0.f);
			if (player.shape.getPosition().y >= window.getSize().y - player.shape.getGlobalBounds().height) // bottom
				player.shape.setPosition(player.shape.getPosition().x, window.getSize().y - player.shape.getGlobalBounds().height);
			// Text set
			HpText.setPosition(player.shape.getPosition().x, player.shape.getPosition().y - HpText.getGlobalBounds().height);
			HpText.setString(to_string(player.HP) + "/" + to_string(player.HPMax));
		
		/*===========================================================//
		   【Update】 controls
		//===========================================================*/
	
			if (shootTimer < 20)
				shootTimer++;
		
			if (Mouse::isButtonPressed(Mouse::Left) && shootTimer >= 20)
			{
				player.bullets.push_back(Bullet(&bulletTex, player.shape.getPosition()));
				shootTimer = 0; // reset timer
			}
		/*===========================================================//
		   【Update】 Bullet 
		//===========================================================*/
			for (size_t i = 0; i < player.bullets.size(); i++)
			{
				// Move
				player.bullets[i].shape.move(20.f, 0.f);
				// Out of window bounds
				if (player.bullets[i].shape.getPosition().x > window.getSize().x)
				{
					player.bullets.erase(player.bullets.begin() + i);
					break;
				}
				
				// enemy collision
				for (size_t k = 0; k < enemies.size(); k++)
				{
					if (player.bullets[i].shape.getGlobalBounds().intersects(enemies[k].shape.getGlobalBounds()))
					{	
						if (enemies[k].HP <= 1)
						{
							score += enemies[k].HPMax;
							enemies.erase(enemies.begin() + k);
						}
						
						else
							enemies[k].HP--;
						player.bullets.erase(player.bullets.begin() + i);
						break;
					}
				}
			}	
		/*===========================================================//
		   【Update】 Enemy
		//===========================================================*/
			if (enemySpawnTimer < 30)
				enemySpawnTimer++;
			if (enemySpawnTimer >= 30)
			{
				enemies.push_back(Enemy(&enemyTex, window.getSize()));
				enemySpawnTimer = 0;
			}
			for (size_t i = 0; i < enemies.size(); i++)
			{
				enemies[i].shape.move(-2.3f, 0.f);
				if (enemies[i].shape.getPosition().x <= 0 - enemies[i].shape.getGlobalBounds().width)
				{ 
					enemies.erase(enemies.begin() + i);
					break;
				}
				if (enemies[i].shape.getGlobalBounds().intersects(player.shape.getGlobalBounds()))
				{
					enemies.erase(enemies.begin() + i);
				
					player.HP--;
					break;
				}
			}
			/*===========================================================//
			   【Update】 UI
			//===========================================================*/
			scoreText.setString("Score: " + to_string(score));
		}
	/*===========================================================//
	   【Draw】
	//===========================================================*/
		window.clear();
    /*===========================================================//
	   【Draw】  player
    //===========================================================*/
		window.draw(player.shape);
		
    /*===========================================================//
	   【Draw】 bullets
    //===========================================================*/		
		for (size_t i = 0; i < player.bullets.size(); i++)
		{
			window.draw(player.bullets[i].shape);
		}
		
    /*===========================================================//
	   【Draw】 Enemies
    //===========================================================*/
		for (size_t i = 0; i < enemies.size(); i++)
		{
			eHpText.setString(to_string(enemies[i].HP) + "/" + to_string(enemies[i].HPMax));
			eHpText.setPosition(enemies[i].shape.getPosition().x, enemies[i].shape.getPosition().y - eHpText.getGlobalBounds().height);
			window.draw(eHpText);
			window.draw(enemies[i].shape);
		}
    /*===========================================================//
	    【Draw】 UI
    //===========================================================*/  
		window.draw(scoreText);
		window.draw(HpText);
		if (player.HP <= 0)
			window.draw(gameOverText);
			
		window.display();
	}
再來這一段,就是遊戲的 loop,
首先可以看到 player 的部分
/*===========================================================//
		   【Update】 player 
		//===========================================================*/
			// movement
			int speedInc = 0;
			if (Keyboard::isKeyPressed(Keyboard::W))
				player.shape.move(0.f, -5.f - speedInc);
			if (Keyboard::isKeyPressed(Keyboard::S))
				player.shape.move(0.f, 5.f + speedInc);
			if (Keyboard::isKeyPressed(Keyboard::D))
				player.shape.move(5.f + speedInc, 0.f);
			if (Keyboard::isKeyPressed(Keyboard::A))
				player.shape.move(-5.f - speedInc, 0.f);
			// collision with window
			if (player.shape.getPosition().x <= 0) // left
				player.shape.setPosition(0.f, player.shape.getPosition().y);
			if (player.shape.getPosition().x >= window.getSize().x - player.shape.getGlobalBounds().width) // right
				player.shape.setPosition(window.getSize().x - player.shape.getGlobalBounds().width, player.shape.getPosition().y);
			if (player.shape.getPosition().y <= 0) // Top
				player.shape.setPosition(player.shape.getPosition().x, 0.f);
			if (player.shape.getPosition().y >= window.getSize().y - player.shape.getGlobalBounds().height) // bottom
				player.shape.setPosition(player.shape.getPosition().x, window.getSize().y - player.shape.getGlobalBounds().height);
			// Text set
			HpText.setPosition(player.shape.getPosition().x, player.shape.getPosition().y - HpText.getGlobalBounds().height);
			HpText.setString(to_string(player.HP) + "/" + to_string(player.HPMax));
一樣是包含了上下左右的移動,還有與 window 撞到的反應,最後是會顯示再 player 頭上的 血量。
下面這段則是 bullet 與敵人的碰撞還有移動。
	/*===========================================================//
		   【Update】 Bullet 
		//===========================================================*/
			for (size_t i = 0; i < player.bullets.size(); i++)
			{
				// Move
				player.bullets[i].shape.move(20.f, 0.f);
				// Out of window bounds
				if (player.bullets[i].shape.getPosition().x > window.getSize().x)
				{
					player.bullets.erase(player.bullets.begin() + i);
					break;
				}
				
				// enemy collision
				for (size_t k = 0; k < enemies.size(); k++)
				{
					if (player.bullets[i].shape.getGlobalBounds().intersects(enemies[k].shape.getGlobalBounds()))
					{	
						if (enemies[k].HP <= 1)
						{
							score += enemies[k].HPMax;
							enemies.erase(enemies.begin() + k);
						}
						
						else
							enemies[k].HP--;
						player.bullets.erase(player.bullets.begin() + i);
						break;
					}
				}
			}	
		/*===========================================================//
		   【Update】 Enemy
		//===========================================================*/
			if (enemySpawnTimer < 30)
				enemySpawnTimer++;
			if (enemySpawnTimer >= 30)
			{
				enemies.push_back(Enemy(&enemyTex, window.getSize()));
				enemySpawnTimer = 0;
			}
			for (size_t i = 0; i < enemies.size(); i++)
			{
				enemies[i].shape.move(-2.3f, 0.f);
				if (enemies[i].shape.getPosition().x <= 0 - enemies[i].shape.getGlobalBounds().width)
				{ 
					enemies.erase(enemies.begin() + i);
					break;
				}
				if (enemies[i].shape.getGlobalBounds().intersects(player.shape.getGlobalBounds()))
				{
					enemies.erase(enemies.begin() + i);
				
					player.HP--;
					break;
				}
			}
值得注意的是,我們在碰撞到的時候都記得要 break loop,否則會當掉。
剩下的就是 一些 draw 的功能了。
簡單的來說,這次的小遊戲包含了前幾次的練習,再加上一些使用 class 的方式。
接下來就是我自己的小遊戲了!!!!!
這個是這次的示範影片