OpenGL 图形库的使用(四十八)—— 实战之2D游戏 - 准备工作

版本记录

版本号 时间
V1.0 2018.01.20

前言

OpenGL 图形库项目中一直也没用过,最近也想学着使用这个图形库,感觉还是很有意思,也就自然想着好好的总结一下,希望对大家能有所帮助。下面内容来自欢迎来到OpenGL的世界
1. OpenGL 图形库使用(一) —— 概念基础
2. OpenGL 图形库使用(二) —— 渲染模式、对象、扩展和状态机
3. OpenGL 图形库使用(三) —— 着色器、数据类型与输入输出
4. OpenGL 图形库使用(四) —— Uniform及更多属性
5. OpenGL 图形库使用(五) —— 纹理
6. OpenGL 图形库使用(六) —— 变换
7. OpenGL 图形库的使用(七)—— 坐标系统之五种不同的坐标系统(一)
8. OpenGL 图形库的使用(八)—— 坐标系统之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之高级数据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 图形库的使用(四十五)—— 实战之调试Debugging
46. OpenGL 图形库的使用(四十六)—— 实战之文本渲染Text Rendering
47. OpenGL 图形库的使用(四十七)—— 实战之2D游戏 - Breakout

准备工作

在开始真正写游戏机制之前,我们首先需要配置一个简单的框架,用来存放这个游戏,这个游戏将会用到几个第三方库,它们的大多数都已经在前面的教程中介绍过了。在需要用到新的库的时候,我会作出适当的介绍。

首先,我们定义一个所谓的超级(Uber)游戏类,它会包含所有相关的渲染和游戏代码。这个游戏类的主要作用是(简单)管理你的游戏代码,并与此同时将所有的窗口代码从游戏中解耦。这样子的话,你就可以把相同的类迁移到完全不同的窗口库(比如SDL或SFML)而不需要做太多的工作。

抽象并归纳游戏或图形代码至类与对象中有成千上万种方式。在这个系列教程中你所看到的仅是其中的一种。如果你觉得能有更好的方式进行实现,你可以尝试改进我的这个实现。

这个游戏类封装了一个初始化函数、一个更新函数、一个处理输入函数以及一个渲染函数:

class Game
{
    public:
        // 游戏状态
        GameState  State;   
        GLboolean  Keys[1024];
        GLuint     Width, Height;
        // 构造函数/析构函数
        Game(GLuint width, GLuint height);
        ~Game();
        // 初始化游戏状态(加载所有的着色器/纹理/关卡)
        void Init();
        // 游戏循环
        void ProcessInput(GLfloat dt);
        void Update(GLfloat dt);
        void Render();
};

这个类应该包含了所有在一个游戏类中会出现的东西。我们通过给定一个宽度和高度(对应于你玩游戏时的分辨率)来初始化这个游戏,并且使用Init函数来加载着色器、纹理并且初始化所有的游戏状态。我们可以通过调用ProcessInput函数,并使用存储在Keys数组里的数据来处理输入。并且在Update函数里面我们可以更新游戏设置状态(比如玩家/球的移动)。最后,我们还可以调用Render函数来对游戏进行渲染。注意,我们将运动逻辑与渲染逻辑分开了。

这个Game类同样了封装了一个叫做State的变量,它的类型是GameState,定义如下:

// 代表了游戏的当前状态
enum GameState {
    GAME_ACTIVE,
    GAME_MENU,
    GAME_WIN
}; 

这个类可以帮助我们跟踪游戏的当前状态。这样的话我们就可以根据当前游戏的状态来决定渲染和/或者处理不同的元素(Item)了(比如当我们在游戏菜单界面的时候就可能需要渲染和处理不同的元素了)。

目前为止,这个游戏类的函数还完全是空的,因为我们还没有写游戏的实际代码,但这里是Game类的头文件代码文件


工具类

因为我们正在开发一个大型应用,所以我们将不得不频繁地重用一些OpenGL的概念,比如纹理和着色器等。因此,为这两个元素创建一个更加易用的接口也是情理之中的事了,就像在我们前面教程中创建的那个着色器类一样。

着色器类会接受两个或三个(如果有几何着色器)字符串,并生成一个编译好的着色器(如果失败的话则生成错误信息)。这个着色器类也包含很多工具(Utility)函数来帮助快速设置uniform值。纹理类会接受一个字节(Byte)数组以及宽度和高度,并(根据设定的属性)生成一个2D纹理图像。同样,这个纹理类也会封装一些工具函数。

我们并不会深入讨论这些类的实现细节,因为学到这里你应该可以很容易地理解它们是如何工作的了。出于这个原因,你可以在下面找到它们的头文件和代码文件,都有详细的注释:

注意当前的纹理类仅是为2D纹理设计的,但你很容易就可以将其扩张至更多的纹理类型。


资源管理

尽管着色器与纹理类的函数本身就很棒了,它们仍需要有一个字节数组或一些字符串来调用它们。我们可以很容易将文件加载代码嵌入到它们自己的类中,但这稍微有点违反了单一功能原则(Single Responsibility Principle),即这两个类应当分别仅仅关注纹理或者着色器本身,而不是它们的文件加载机制。

出于这个原因,我们通常会用一个更加有组织的方法(译注:来实现文件的加载),就是创建一个所谓资源管理器的实体,专门加载游戏相关的资源。创建一个资源管理器有多种方法。在这个教程中我们选择使用一个单一实例(Singleton)的静态资源管理器,(由于它静态的本质)它在整个工程中都可以使用,它会封装所有的已加载资源以及一些相关的加载功能。

使用一个具有静态属性的单一实例类有很多优点也有很多缺点。它主要的缺点就是这样会损失OOP属性,并且丧失构造与析构的控制。不过,对于我们这种小项目来说是这些问题也是很容易处理的。

和其它类的文件一样,这个资源管理器的代码如下:

通过使用资源管理器,我们可以很容易地把着色器加载到程序里面:

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

Game类、资源管理器类,以及很容易管理的Shader和Texture2D类一起组成了之后教程的基础,我们之后会广泛使用这些类来实现我们的Breakout游戏。


程序

我们仍然需要为这个游戏创建一个窗口并且设置一些OpenGL的初始状态。我们确保使用OpenGL的面剔除功能和混合功能。我们不需要使用深度测试,因为这个游戏完全是2D的,所有顶点都有相同的z值,所以开启深度测试并没有什么用,反而可能造成深度冲突(Z-fighting)

这个Breakout游戏的起始代码非常简单:我们用GLFW创建一个窗口,注册一些回调函数,创建一个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;
    }
}

运行这个代码,你应该能得到下面的输出:

现在我们已经为之后的教程构建了一个坚实的框架,我们将不断地拓展这个游戏类,封装新的功能。如果你准备好了,就可以开始下一节的学习了。

后记

本篇已结束,下篇更精彩~~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容