---
# System prepended metadata

title: 〈 體驗用 C++ 和 SDL2 手刻小遊戲 Part.1 〉 基本遊戲控制和玩法操作
tags: [C++, GameDev]

---

---
Tu 2023/6/26

***「綜合建築、音樂、繪畫、雕塑、詩歌、文學和舞蹈之後，將電影加入藝術的行列，名為『第八藝術』。而無數的電子遊戲前輩和工作者，將遊戲推上了『第九藝術』的行列。」***       **- 森納映畫 喬伊**

![](https://hackmd.io/_uploads/H11DyJPun.jpg)

### **[本專題 github repo 連結](https://github.com/Eric-Tu32/2D-Game-Attempt-SDL2)**

## 一、前言
雖然我目前只有寫過機器學習相關的文章，但其實我對遊戲開發一直很有興趣，但開發一個有品質的遊戲往往需要大量的心血和時間，也因此一直沒有嘗試。

雖然之前有試過Unity，但我完全搞不懂那個UI怎麼用(有可能是我不常接觸)。而最近剛好暑假有時間，我想說就來玩玩看。

C是我第一個接觸到的程式語言，但後來的專案幾乎都用python實作因此生疏許多，最近剛好想學static type的語言，我就索性決定用C++來寫小遊戲，一方面熟悉C\++的使用方式，一方面滿足我的小願望。

這次的介紹與以往不太一樣，因為牽涉到太多檔案所以我會附上Github的連結，請搭配食用。我基本上只介紹大概念，細節的部分就自己看我的程式碼ㄅ。

## 二、SDL2 簡介
SDL2不像Unity或Unreal一樣是遊戲引擎，它是一個C++的涵式庫，功能只有接收輸入、渲染材質等，其他諸如碰撞偵測、實體創建、遊戲狀態等，都要自己從頭寫。因為我下學期對於比較低階的開發有股莫名的衝動，所以才會開始研究。

我先介紹一下我目前對遊戲開發的理解
![](https://hackmd.io/_uploads/rJXOoHD_h.jpg)

遊戲狀態(gameState)是拿來決定比如開始畫面、遊戲畫面、結算畫面等的參數，而每個遊戲狀態下，都會去偵測玩家的操作決定是要更改遊戲狀態(update gameState)還是更新畫面內容(update + graphic)。

當然，我寫的程式碼可能不是那麼好，畢竟我也剛開始學，還沒使用design pattern去保證我的可延性(extensibility)和可維護性，可能之後會專門出一篇改吧。

## 三、實作過程
這次只是我覺得開始熟悉SDL2後的第一個紀錄，所以只會挑重點紀錄，但SDL2的相關資料沒有Unity多，所以我覺得對於需要的人應該會有相當大的幫助。

但後來我發現我有點懶，所以還是先對SDL2有些基本理解再回來翻ㄅ。

推薦自學資源：
* [Codergopher的SDL2系列](https://www.youtube.com/playlist?list=PL2RPjWnJduNmXHRYwdtublIPdlqocBoLS)：可以學到SDL2一開始的基本設置和一些C++基本概念，而且他講蠻好笑的，相當推薦(但是該系列貌似還沒做完就腰斬了)
* [Mike Shah的SDL2系列](https://www.youtube.com/playlist?list=PLvv0ScY6vfd-p1gSnbQhY7vMe2rng0IL0)：這個內容十分的完整詳細，還會導讀SDL2的Documentation，我沒有全部看完，基本上只有再遇到特定問題的時候會去查一下

我想紀錄(你們可能學到)的內容：
1. 初始化和創建視窗 + 渲染材質
2. 實體創建
3. 玩家創建和基本2D控制
4. 敵人創建和基本AI
5. 碰撞偵測
6. 遊戲狀態：重新開始

### 初始化和創建視窗 + 渲染材質
貌似是固定寫法，建議開始的時候先上網查一下，這裡的重點是確保初始化沒問題、創建空白視窗和創建渲染器(renderer)，之後有關遊戲畫面的展示都是由Renderer負責。

我這邊把視覺相關的放到一個header file，在github中是RenderWindow的檔案。
```cpp
#pragma once
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

#include "Entity.hpp"

class RenderWindow
{
public:
	RenderWindow(const char* p_title, int p_w, int p_h);
	SDL_Texture* loadTexture(const char* p_filePath);
	void cleanUp();
	void clear();
	void render(Entity* p_entity);
	void render(int p_x, int p_y, SDL_Texture* p_tex);
	void display();
private:
	SDL_Window* window;
	SDL_Renderer* renderer;
};
```
這裡在Marty的教學中大多都會講到，我這邊有多加東西的是render(Entity*)的部分，但和影片中提到的概念是一模一樣的。

### 實體創建
這邊指的是Entity Class，我有透過這個稍微理解C++的OOP怎麼用，還有像繼承、多形之類的。在操作的過程也難免碰到一些指標操作，雖然對使用方法多少有點概念，但還是好難。

程式碼請參見repo中Entity、Player、Enemy的檔案

Entity包含了實體應該要有的基本資料：位置、材質、角度、碰撞箱等。而Player和Enemy是Entity的子類別，Player和Enemy多了移動、衝刺相關的涵式，但重點是最後的update()

Player和Enemy的update會更新實體每幀的位置、速度、角度等信息，計算完後再由renderer更新遊戲的畫面。

## 玩家創建和基本2D控制
在main.cpp中的update()就是用來接收來自玩家的操作
```cpp=
void update()
{
	lastTick = currentTick;
	currentTick = SDL_GetPerformanceCounter();
	deltaTime = (double)((currentTick - lastTick)*1000 / (double)SDL_GetPerformanceFrequency());

	//keyboard detection
	bool up_button_down = false;
	bool down_button_down = false;
	bool left_button_down = false;
	bool right_button_down = false;

	const Uint8 *state = SDL_GetKeyboardState(NULL);
	if (state[SDL_SCANCODE_W]) up_button_down = true;
	if (state[SDL_SCANCODE_A]) left_button_down = true;
	if (state[SDL_SCANCODE_S]) down_button_down = true;
	if (state[SDL_SCANCODE_D]) right_button_down = true;

	//mouse detection (1-left key, 3-right key)
	int mouse_x, mouse_y;
	bool left_mouse_down = false;
	bool right_mouse_down = false;
	if(SDL_GetMouseState(&mouse_x,&mouse_y) & 1) left_mouse_down = true;
	if(SDL_GetMouseState(&mouse_x,&mouse_y) & 4) right_mouse_down = true;

	//player angle
	Vector2f player_to_mouse;
	if (mouse_x != 0 || mouse_y != 0)
	{
		player_to_mouse.x = mouse_x - (double)(player->getPos().x + player->getCurrentFrame().w / 2.0d * player->getScale());
		player_to_mouse.y = mouse_y - (double)(player->getPos().y + player->getCurrentFrame().h / 2.0d * player->getScale());
	}
		
	//Get our controls and events
	while (SDL_PollEvent(&event))
	{
		switch(event.type)
		{
		case SDL_QUIT:
			gameRunning = false;
			break;
		}
	}

	bool playerHit = false;
	for (long long unsigned int i=0; i<enemies.size(); i++)
	{
		if(player->isHit(enemies[i])) playerHit = true;
	}

	gameState = player->update(deltaTime, left_button_down, right_button_down, up_button_down, down_button_down, left_mouse_down, right_mouse_down, player_to_mouse, playerHit);

	for (long long unsigned int i=0; i<enemies.size(); i++)
	{
		Vector2f enemy_to_player;
		enemy_to_player.x = player->getPos().x - enemies[i]->getPos().x;
		enemy_to_player.y = player->getPos().y - enemies[i]->getPos().y;
		enemies[i]->update(deltaTime, enemy_to_player, false);
	}
}
```
這裡能說的就非常多了：
* **3-5行**：熟悉遊戲開發的人應該都知道deltaTime能在不同framerate下控制東西的速度，更詳細的可以自己去查。
* **7-17行**：這裡是有關鍵盤輸入的部分，最重要的點就是不要使用SDL_Event中的鍵盤偵測，在操作上會出現巨大的延遲讓你完全沒有遊戲體驗。
* **19-32行**：偵測滑鼠輸入以及位置，每個鍵有各自代表的一個整數，要自己去查官方文件。
* **34-43行**：神奇的SDL_Event，我也還沒完全搞懂，但目前只把它拿來關閉視窗。
* **45-49行**：偵測玩家的碰撞信息，決定等等player->update的結果。
* **51行**：player->update()會更新玩家實體的所有資訊，並回傳新的gameState，決定要進入結束畫面還是繼續遊戲。
* **53-60行**：更新敵人實體的資訊。

### 敵人創建和基本AI
弄到這邊讓我頭很痛，因為我根本不清楚temporary object和那一堆指標怎麼處理，我也還沒弄懂要怎麼釋放敵人占存的記憶體空間，目前就先這樣寫著，希望有人能在留言中指點我。
```cpp
//Entities
std::vector<class Entity*> entities;
Player* player = new Player(Vector2f(500,300), playerTexture, 2, 0.5f);

//enemy vectord
std::vector<class Enemy*> enemies;
//enemies

void load_Entities()
{
	for (int i=0; i<1; i++)
	{
		Vector2f p_pos;
		p_pos.x = 100+100*i;
		p_pos.y = 200;
		Enemy* enemy = new Enemy(p_pos, enemyTexture, 2, 0.3f);
		enemies.emplace_back(enemy);
		entities.emplace_back(enemy);
	}
	entities.emplace_back(player);
}
```

### 碰撞偵測
```cpp
SDL_bool Entity::isHit(Entity* obj)
{
	hitBox.x = pos.x;
	hitBox.y = pos.y;
	hitBox.w = textureWidth * scale;
	hitBox.h = textureHeight * scale; 

	SDL_bool isCollided;

	SDL_Rect objhitbox = obj->getHitBox();

	const SDL_Rect* selfhitboxptr = &hitBox;
	const SDL_Rect* objhitboxptr = &objhitbox;

	SDL_Rect inter;
	inter.x = 0;
	inter.y = 0;
	inter.w = 0;
	inter.h = 0;

	isCollided = SDL_IntersectRect(selfhitboxptr, objhitboxptr, &inter);

	return isCollided;
}
```
沒啥好說，有問題問我

### 遊戲狀態：重新開始
上面有提到player實體會在update的時候回傳新的gameState，我這邊的設定是當碰到敵人後就會進結束畫面，然後按一下滑鼠左鍵就會重新開始遊戲。
```cpp=
void endScreen()
{
	//Get our controls and events
	while (SDL_PollEvent(&event))
	{
		switch(event.type)
		{
		case SDL_QUIT:
			gameRunning = false;
			break;
		}
	}

	int mouse_x, mouse_y;
	bool left_mouse_down = false;
	if(SDL_GetMouseState(&mouse_x,&mouse_y) & 1) left_mouse_down = true;
	
	if (left_mouse_down) gameState-=1;

	window.clear();
	window.render(0, 0, bgTexture);

	window.display();
}

const int fps = 60;
const int frameDelay = 1000/fps;

Uint32 frameStart;
int frameTime;

int main(int argc, char* args[ ])
{
	load_Entities();
	while (gameRunning)
	{	
		frameStart = SDL_GetTicks();

		if (gameState == 1)
		{
			update();
			graphics();
		}
		
		if (gameState == 2)
		{
			endScreen();
		}
		
		frameTime = SDL_GetTicks() - frameStart;
		if (frameDelay > frameTime) SDL_Delay(frameDelay - frameTime); //Cap to 60 fps
	}

	window.cleanUp();
	SDL_Quit();

	return 0;
}
```
最後提幾個重點
* **fps/frameDelay**：每次GameLoop的時候會做一次計時，透過SDL_Delay限制最大fps(60)，以保證電腦不會將所有CPU資源投入到遊戲中。
* **main函數的引數**：SDL2的要求寫法，不然跑不動。

## 四、未來方向
這篇雖然涵蓋了很多基本內容，但知道遊戲開發的就知道其實要研究的多到數不清，我這邊列幾個之後有時間想繼續研究的項目。
* 加入design pattern，提高程式碼品質
* 角色、場景動畫(sprite animation)
* 光影、粒子特效
* 敵人實體的生成與消滅
* 敵人AI
* 基本戰鬥系統
* 劇情、玩法設計

遠期目標(不太可能，有時間+心血來潮有可能)
* 多人連機(ENet套件)
* 發佈到Steam上
* 參加Game Jam或黑客松

3D遊戲的話還是乖乖去用遊戲引擎，從頭開始開發成本太高了。

### 五、結語
我很喜歡的2D遊戲很多。Undertale、To the moon的劇情，元氣騎士、The Eldest Soul的roguelike風格和Rain World、Hollow Knight之類玩法豐富的遊戲。但我也深諳要做出這種品質所需要的人力和熱情，以及面對可能血本無歸的勇氣。

這個專案我本來只是拿來熟悉C++用的，而我也確實收穫了很多。遊戲開發確實不是我目前想首位鑽研的目標，但我希望我能一直保持著這個興趣。(就看這個系列文之後能不能繼續就知道了)