用C++和SFML寫遊戲-Game類的建立(2)

這一節咱們將會學習到遊戲的基本結構,其中的內容包括了:c++

  • Game類的建立
  • 什麼是幀數
  • Player類的建立
  • 事件管理器

Game類

在上一節中,咱們用盡量少的代碼建立了一個基本遊戲,它包括了:框架

  • 窗口的建立
  • 圖形的繪製
  • 處理用戶的輸入
  • 將遊戲元素繪製到屏幕上

上一節中的實例代碼所有寫在了 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() ,下面一一介紹:性能

  • 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();
}
複製代碼

運行效果跟前一節是同樣的

02_Game_Archi

FPS

FPS(frames per second): 每秒的幀數,一幀就是一般指一個畫面。

因爲電腦硬件的不一樣,同一個遊戲在不一樣電腦的運行速度極可能是不同的。若是開發者沒有注意到這個問題,可能會出現角色穿牆的狀況,如 圖 1 所示。

圖 1

爲了解決這個問題,一般有三種方案,一是動態時間步長,二是固定時間步長,三是二者一塊兒使用。

一、動態時間步長

因爲每臺計算機的性能可能不同,所以處理一幀所花費的時間也是不一樣的,可是現實世界中時間的流逝是同樣的。 所以這種方法更新的主要原理就是計算出上幀到如今所花費的時間,而後將這個時間傳入到 update() 函數。

最後的繪製效果如 圖 2 所示,能夠看到在運行快的計算機上面用戶的幀數更高,而運行較慢的計算機上幀數較低,可是完成這個過程所花費的時間是同樣的。

圖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() 方法的時間參數不大於一個值,具體過程如圖所示:

圖 3

具體代碼實現:

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() 方法都被調用,可是咱們確保了傳入參數不會太大。


下一節將會學習如何移動咱們的角色😃。

相關文章
相關標籤/搜索