Day 29 - 这个游戏制作人疯了吧

Intro

这次写了一个小游戏。里面包含了前几次练习的功能,但是有些东西是用 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 的方式。

接下来就是我自己的小游戏了!!!!!

这个是这次的示范影片

Ref

  • 一样也是 这个 YouTuber 的影片!

SFML Tutorials


<<:  day26 老板我赶时间,给我最快完成的料理 select

>>:  Day 28 : 撰写LineBot,利用短短三天认识自动化机器人(中)

Day 0x7 UVa11417 GCD

Virtual Judge ZeroJudge Zerojudge 直接破图 题意 输入一数字 N...

[Kata] Clojure - Day 28

Sum of a sequence our task is to make function, wh...

Day 28: Incident Response on AWS

天有不测风云,任何单位就算已经完全做到了前面四大面向,你应该还是要在内部建立一套事件回应的机制,透过...

IOS、Python自学心得30天 Day-2 Anaconda

前言: 因为套件有可能会有版本问题的缘故,所以我照网路上的推荐用Anaconda,来管理我每个Pro...

Day 21 - 背景 Gradient 使用

欢乐的时光总是过得特别快,不知不觉连假就要结束了,不过威尔猪也太悲催,为了铁人赛,中秋节还要在电脑...