OpenGL 圖形庫的使用(四十八)—— 實戰(zhàn)之2D游戲 - 準備工作

版本記錄

版本號 時間
V1.0 2018.01.20

前言

OpenGL 圖形庫項目中一直也沒用過,最近也想學著使用這個圖形庫网杆,感覺還是很有意思羹饰,也就自然想著好好的總結一下,希望對大家能有所幫助碳却。下面內容來自歡迎來到OpenGL的世界队秩。
1. OpenGL 圖形庫使用(一) —— 概念基礎
2. OpenGL 圖形庫使用(二) —— 渲染模式、對象昼浦、擴展和狀態(tài)機
3. OpenGL 圖形庫使用(三) —— 著色器馍资、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標系統(tǒng)之五種不同的坐標系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復習總結
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級OpenGL之深度測試
22. OpenGL 圖形庫的使用(二十二)—— 高級OpenGL之模板測試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級OpenGL之面剔除Face culling
25. OpenGL 圖形庫的使用(二十五)—— 高級OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫的使用(二十六)—— 高級OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫的使用(二十七)—— 高級OpenGL之高級數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級OpenGL之高級GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級OpenGL之實例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫的使用(三十二)—— 高級光照之高級光照Advanced Lighting
33. OpenGL 圖形庫的使用(三十三)—— 高級光照之Gamma校正Gamma Correction
34. OpenGL 圖形庫的使用(三十四)—— 高級光照之陰影 - 陰影映射Shadow Mapping
35. OpenGL 圖形庫的使用(三十五)—— 高級光照之陰影 - 點陰影Point Shadows
36. OpenGL 圖形庫的使用(三十六)—— 高級光照之法線貼圖Normal Mapping
37. OpenGL 圖形庫的使用(三十七)—— 高級光照之視差貼圖Parallax Mapping
38. OpenGL 圖形庫的使用(三十八)—— 高級光照之HDR
39. OpenGL 圖形庫的使用(三十九)—— 高級光照之泛光
40. OpenGL 圖形庫的使用(四十)—— 高級光照之延遲著色法Deferred Shading
41. OpenGL 圖形庫的使用(四十一)—— 高級光照之SSAO
42. OpenGL 圖形庫的使用(四十二)—— PBR之理論Theory
43. OpenGL 圖形庫的使用(四十三)—— PBR之光照Lighting
44. OpenGL 圖形庫的使用(四十四)—— PBR之幾篇沒有翻譯的英文原稿
45. OpenGL 圖形庫的使用(四十五)—— 實戰(zhàn)之調試Debugging
46. OpenGL 圖形庫的使用(四十六)—— 實戰(zhàn)之文本渲染Text Rendering
47. OpenGL 圖形庫的使用(四十七)—— 實戰(zhàn)之2D游戲 - Breakout

準備工作

在開始真正寫游戲機制之前,我們首先需要配置一個簡單的框架关噪,用來存放這個游戲鸟蟹,這個游戲將會用到幾個第三方庫乌妙,它們的大多數(shù)都已經在前面的教程中介紹過了。在需要用到新的庫的時候建钥,我會作出適當?shù)慕榻B藤韵。

首先,我們定義一個所謂的超級(Uber)游戲類熊经,它會包含所有相關的渲染和游戲代碼泽艘。這個游戲類的主要作用是(簡單)管理你的游戲代碼,并與此同時將所有的窗口代碼從游戲中解耦镐依。這樣子的話匹涮,你就可以把相同的類遷移到完全不同的窗口庫(比如SDL或SFML)而不需要做太多的工作。

抽象并歸納游戲或圖形代碼至類與對象中有成千上萬種方式槐壳。在這個系列教程中你所看到的僅是其中的一種然低。如果你覺得能有更好的方式進行實現(xiàn),你可以嘗試改進我的這個實現(xiàn)务唐。

這個游戲類封裝了一個初始化函數(shù)脚翘、一個更新函數(shù)、一個處理輸入函數(shù)以及一個渲染函數(shù):

class Game
{
    public:
        // 游戲狀態(tài)
        GameState  State;   
        GLboolean  Keys[1024];
        GLuint     Width, Height;
        // 構造函數(shù)/析構函數(shù)
        Game(GLuint width, GLuint height);
        ~Game();
        // 初始化游戲狀態(tài)(加載所有的著色器/紋理/關卡)
        void Init();
        // 游戲循環(huán)
        void ProcessInput(GLfloat dt);
        void Update(GLfloat dt);
        void Render();
};

這個類應該包含了所有在一個游戲類中會出現(xiàn)的東西绍哎。我們通過給定一個寬度和高度(對應于你玩游戲時的分辨率)來初始化這個游戲来农,并且使用Init函數(shù)來加載著色器、紋理并且初始化所有的游戲狀態(tài)崇堰。我們可以通過調用ProcessInput函數(shù)沃于,并使用存儲在Keys數(shù)組里的數(shù)據(jù)來處理輸入。并且在Update函數(shù)里面我們可以更新游戲設置狀態(tài)(比如玩家/球的移動)海诲。最后繁莹,我們還可以調用Render函數(shù)來對游戲進行渲染。注意特幔,我們將運動邏輯與渲染邏輯分開了咨演。

這個Game類同樣了封裝了一個叫做State的變量,它的類型是GameState蚯斯,定義如下:

// 代表了游戲的當前狀態(tài)
enum GameState {
    GAME_ACTIVE,
    GAME_MENU,
    GAME_WIN
}; 

這個類可以幫助我們跟蹤游戲的當前狀態(tài)薄风。這樣的話我們就可以根據(jù)當前游戲的狀態(tài)來決定渲染和/或者處理不同的元素(Item)了(比如當我們在游戲菜單界面的時候就可能需要渲染和處理不同的元素了)。

目前為止拍嵌,這個游戲類的函數(shù)還完全是空的遭赂,因為我們還沒有寫游戲的實際代碼,但這里是Game類的頭文件代碼文件横辆。


工具類

因為我們正在開發(fā)一個大型應用撇他,所以我們將不得不頻繁地重用一些OpenGL的概念,比如紋理和著色器等。因此困肩,為這兩個元素創(chuàng)建一個更加易用的接口也是情理之中的事了划纽,就像在我們前面教程中創(chuàng)建的那個著色器類一樣。

著色器類會接受兩個或三個(如果有幾何著色器)字符串锌畸,并生成一個編譯好的著色器(如果失敗的話則生成錯誤信息)勇劣。這個著色器類也包含很多工具(Utility)函數(shù)來幫助快速設置uniform值。紋理類會接受一個字節(jié)(Byte)數(shù)組以及寬度和高度蹋绽,并(根據(jù)設定的屬性)生成一個2D紋理圖像。同樣筋蓖,這個紋理類也會封裝一些工具函數(shù)卸耘。

我們并不會深入討論這些類的實現(xiàn)細節(jié),因為學到這里你應該可以很容易地理解它們是如何工作的了粘咖。出于這個原因蚣抗,你可以在下面找到它們的頭文件和代碼文件,都有詳細的注釋:

注意當前的紋理類僅是為2D紋理設計的,但你很容易就可以將其擴張至更多的紋理類型讽坏。


資源管理

盡管著色器與紋理類的函數(shù)本身就很棒了锭魔,它們仍需要有一個字節(jié)數(shù)組或一些字符串來調用它們。我們可以很容易將文件加載代碼嵌入到它們自己的類中路呜,但這稍微有點違反了單一功能原則(Single Responsibility Principle)迷捧,即這兩個類應當分別僅僅關注紋理或者著色器本身,而不是它們的文件加載機制胀葱。

出于這個原因漠秋,我們通常會用一個更加有組織的方法(譯注:來實現(xiàn)文件的加載),就是創(chuàng)建一個所謂資源管理器的實體抵屿,專門加載游戲相關的資源庆锦。創(chuàng)建一個資源管理器有多種方法。在這個教程中我們選擇使用一個單一實例(Singleton)的靜態(tài)資源管理器轧葛,(由于它靜態(tài)的本質)它在整個工程中都可以使用搂抒,它會封裝所有的已加載資源以及一些相關的加載功能。

使用一個具有靜態(tài)屬性的單一實例類有很多優(yōu)點也有很多缺點尿扯。它主要的缺點就是這樣會損失OOP屬性燕耿,并且喪失構造與析構的控制。不過姜胖,對于我們這種小項目來說是這些問題也是很容易處理的誉帅。

和其它類的文件一樣,這個資源管理器的代碼如下:

通過使用資源管理器蚜锨,我們可以很容易地把著色器加載到程序里面:

Shader shader = ResourceManager::LoadShader("vertex.vs", "fragment.vs", nullptr, "test");
// 接下來使用它
shader.Use();
// 或者
ResourceManager::GetShader("test").Use();

Game類档插、資源管理器類,以及很容易管理的Shader和Texture2D類一起組成了之后教程的基礎亚再,我們之后會廣泛使用這些類來實現(xiàn)我們的Breakout游戲郭膛。


程序

我們仍然需要為這個游戲創(chuàng)建一個窗口并且設置一些OpenGL的初始狀態(tài)。我們確保使用OpenGL的面剔除功能和混合功能氛悬。我們不需要使用深度測試则剃,因為這個游戲完全是2D的,所有頂點都有相同的z值如捅,所以開啟深度測試并沒有什么用棍现,反而可能造成深度沖突(Z-fighting)

這個Breakout游戲的起始代碼非常簡單:我們用GLFW創(chuàng)建一個窗口镜遣,注冊一些回調函數(shù)己肮,創(chuàng)建一個Game對象,并將所有相關的信息都傳到游戲類中悲关。代碼如下:

/*******************************************************************
** This code is part of Breakout.
**
** Breakout is free software: you can redistribute it and/or modify
** it under the terms of the CC BY 4.0 license as published by
** Creative Commons, either version 4 of the License, or (at your
** option) any later version.
******************************************************************/
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "game.h"
#include "resource_manager.h"


// GLFW function declerations
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);

// The Width of the screen
const GLuint SCREEN_WIDTH = 800;
// The height of the screen
const GLuint SCREEN_HEIGHT = 600;

Game Breakout(SCREEN_WIDTH, SCREEN_HEIGHT);

int main(int argc, char *argv[])
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Breakout", nullptr, nullptr);
    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    glewInit();
    glGetError(); // Call it once to catch glewInit() bug, all other errors are now from our application.

    glfwSetKeyCallback(window, key_callback);

    // OpenGL configuration
    glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    glEnable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    // Initialize game
    Breakout.Init();

    // DeltaTime variables
    GLfloat deltaTime = 0.0f;
    GLfloat lastFrame = 0.0f;

    // Start Game within Menu State
    Breakout.State = GAME_ACTIVE;

    while (!glfwWindowShouldClose(window))
    {
        // Calculate delta time
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        glfwPollEvents();

        //deltaTime = 0.001f;
        // Manage user input
        Breakout.ProcessInput(deltaTime);

        // Update Game state
        Breakout.Update(deltaTime);

        // Render
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        Breakout.Render();

        glfwSwapBuffers(window);
    }

    // Delete all resources as loaded using the resource manager
    ResourceManager::Clear();

    glfwTerminate();
    return 0;
}

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    // When a user presses the escape key, we set the WindowShouldClose property to true, closing the application
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    if (key >= 0 && key < 1024)
    {
        if (action == GLFW_PRESS)
            Breakout.Keys[key] = GL_TRUE;
        else if (action == GLFW_RELEASE)
            Breakout.Keys[key] = GL_FALSE;
    }
}

運行這個代碼谎僻,你應該能得到下面的輸出:

現(xiàn)在我們已經為之后的教程構建了一個堅實的框架,我們將不斷地拓展這個游戲類寓辱,封裝新的功能艘绍。如果你準備好了,就可以開始下一節(jié)的學習了秫筏。

后記

本篇已結束江滨,下篇更精彩~~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末腊瑟,一起剝皮案震驚了整個濱河市洛退,隨后出現(xiàn)的幾起案子敲霍,更是在濱河造成了極大的恐慌,老刑警劉巖鹅颊,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敷存,死亡現(xiàn)場離奇詭異,居然都是意外死亡堪伍,警方通過查閱死者的電腦和手機锚烦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帝雇,“玉大人涮俄,你說我怎么就攤上這事∈ⅲ” “怎么了彻亲?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵孕锄,是天一觀的道長。 經常有香客問我苞尝,道長畸肆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任宙址,我火速辦了婚禮轴脐,結果婚禮上,老公的妹妹穿的比我還像新娘抡砂。我一直安慰自己大咱,他們只是感情好,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布注益。 她就那樣靜靜地躺著碴巾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪聊浅。 梳的紋絲不亂的頭發(fā)上餐抢,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天现使,我揣著相機與錄音低匙,去河邊找鬼。 笑死碳锈,一個胖子當著我的面吹牛顽冶,可吹牛的內容都是我干的。 我是一名探鬼主播售碳,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼强重,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贸人?” 一聲冷哼從身側響起间景,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎艺智,沒想到半個月后倘要,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡十拣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年封拧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夭问。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡泽西,死狀恐怖,靈堂內的尸體忽然破棺而出缰趋,到底是詐尸還是另有隱情捧杉,我是刑警寧澤陕见,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站糠溜,受9級特大地震影響淳玩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜非竿,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一蜕着、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧红柱,春花似錦承匣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至零聚,卻和暖如春袍暴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背隶症。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工政模, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚂会。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓淋样,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胁住。 傳聞我的和親對象是個殘疾皇子趁猴,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容