這次寫了一個小遊戲。裡面包含了前幾次練習的功能,但是有些東西是用 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 的方式。
接下來就是我自己的小遊戲了!!!!!
這個是這次的示範影片