這一節咱們將會學習到遊戲的基本結構,其中的內容包括了:c++
在上一節中,咱們用盡量少的代碼建立了一個基本遊戲,它包括了:框架
上一節中的實例代碼所有寫在了 main 函數了,並無使用到 C++ 的面向對象特性。爲了提升咱們代碼的可複用性,從本節開始,咱們將會一步步的使用 OOP 設計思想封裝咱們的遊戲基本元素,搭建出一個遊戲框架的雛形。首先是Game類的實現,以下所示:ide
class Game {
public:
Game(const Game&) = delete;
Game& operator=(const Game&) = delete;
Game();
void run();
private:
void processEvents();
void update();
void render();
sf::RenderWindow _window;
sf::CircleShape _player;
};
int main(int argc,char* argv[]) {
Game game;
game.run();
return 0;
}
複製代碼
Game類中的 =delete 是爲了刪除C++類默認的拷貝構造函數以及拷貝賦值運算符,有關這方面的介紹能夠去了解一下,這裏就不作贅述。函數
能夠看到,咱們的 main 函數裏面不在包含任何循環,遊戲的運行只須要調用 Game 類中的 run 方法。對於類中的其它方法 processEvents(),update(),render() ,下面一一介紹:性能
下面,先作簡單的實現,爲了簡單,咱們的遊戲角色先用一個圓形表示:學習
一、構造方法的實現spa
Game::Game() : _window(sf::VideoMode(800, 600),"02_Game_Archi"),
_player(150) {
_player.setFillColor(sf::Color::Blue);
_player.setPosition(10, 20);
}
複製代碼
二、Game.run() 隱藏了 main 裏面的循環體設計
void Game::run() {
while (_window.isOpen()) {
processEvents();
update();
render();
}
}
複製代碼
三、processEvents() 用於處理用戶的輸入,這裏它只是簡單地經過輪詢從上一幀到如今的事件。例如點擊按鈕或者按下鍵盤按鍵,咱們這裏就只檢查用戶按下窗口的關閉按鈕以及 ESC 鍵,而後窗口就會關閉。rest
void Game::processEvents() {
sf::Event event;
while (_window.pollEvent(event)) {
if ((event.type == sf::Event::Closed)
|| ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape))) {
_window.close();
}
}
}
複製代碼
四、update() 方法更新了咱們的遊戲邏輯。可是如今咱們尚未具體的邏輯實現,後面將會加入。code
void Game::update() {}
複製代碼
五、render() 負責將遊戲畫面渲染到屏幕上。首先默認用 sf::Color::Black 清除窗口,而後將咱們的遊戲對象渲染到窗口,最後在屏幕上顯示出來。
void Game::render() {
_window.clear();
_window.draw(_player);
_window.display();
}
複製代碼
運行效果跟前一節是同樣的
FPS(frames per second): 每秒的幀數,一幀就是一般指一個畫面。
因爲電腦硬件的不一樣,同一個遊戲在不一樣電腦的運行速度極可能是不同的。若是開發者沒有注意到這個問題,可能會出現角色穿牆的狀況,如 圖 1 所示。
爲了解決這個問題,一般有三種方案,一是動態時間步長,二是固定時間步長,三是二者一塊兒使用。
因爲每臺計算機的性能可能不同,所以處理一幀所花費的時間也是不一樣的,可是現實世界中時間的流逝是同樣的。 所以這種方法更新的主要原理就是計算出上幀到如今所花費的時間,而後將這個時間傳入到 update() 函數。
最後的繪製效果如 圖 2 所示,能夠看到在運行快的計算機上面用戶的幀數更高,而運行較慢的計算機上幀數較低,可是完成這個過程所花費的時間是同樣的。
這個過程的代碼大概長這個樣子:
void Game::run() {
sf::Clock clock;
while (_window.isOpen()) {
processEvents();
update(clock.restart());
render();
}
}
複製代碼
這時候,咱們的 update 方法也須要作些改變:
void update(sf::Time deltaTime);
複製代碼
deltaTime 參數表明的是上次調用 update 到如今通過了多少時間
自上一次遊戲循環過去了必定量的真實時間。 須要爲遊戲的「當前時間」模擬推動相同長度的時間,以追上玩家的時間。 咱們使用一系列的固定時間步長。 代碼大體以下:
void Game::run(int frame_per_seconds) {
sf::Clock clock;
sf::Time timeSinceLastUpdate = sf::Time::Zero;
sf::Time TimePerFrame = sf::seconds(1.f/frame_per_seconds);
while (_window.isOpen()) {
processEvents();
bool repaint = false;
timeSinceLastUpdate += clock.restart();
while (timeSinceLastUpdate > TimePerFrame) {
timeSinceLastUpdate -= TimePerFrame;
repaint = true;
update(TimePerFrame);
}
if(repaint)
render();
}
}
複製代碼
在每幀的開始,根據過去了多少真實的時間,更新timeSinceLastUpdate
。 這個變量代表了遊戲世界時鐘比真實世界落後了多少,而後咱們使用一個**固定時間步長(fix time step)**的內部循環進行追趕。 一旦咱們追上真實時間,咱們就渲染而後開始新一輪循環。
這個方法把前面兩個方法結合。經過確保傳入 update() 方法的時間參數不那麼高使得遊戲須要運行的足夠快,也就是咱們經過這個方法設置了最小的幀數,可是沒有最大的。
就是將傳入 update() 方法的時間參數不大於一個值,具體過程如圖所示:
具體代碼實現:
void Game::run(int minimum_frame_per_seconds)) {
sf::Clock clock;
sf::Time timeSinceLastUpdate;
sf::Time TimePerFrame = sf::seconds(1.f/minimum_frame_per_seconds);
while (_window.isOpen()) {
processEvents();
timeSinceLastUpdate = clock.restart();
while (timeSinceLastUpdate > TimePerFrame) {
timeSinceLastUpdate -= TimePerFrame;
update(TimePerFrame);
}
update(timeSinceLastUpdate);
render();
}
}
複製代碼
在每一幀中,update() 方法都被調用,可是咱們確保了傳入參數不會太大。
下一節將會學習如何移動咱們的角色😃。