首页 > 其他分享 >LearnOpenGL 2D游戏breakout总结

LearnOpenGL 2D游戏breakout总结

时间:2023-10-21 11:13:21浏览次数:26  
标签:glm 渲染 0.0 2D breakout LearnOpenGL GL 着色器 游戏

Breakout

简介 - LearnOpenGL CN (learnopengl-cn.github.io)

​ 2D游戏BreakOut实现以及对OpenGL一些知识点的总结。

1.项目结构

  1. game类:用于管理所有游戏和渲染代码,提供初始化、游戏重置、键盘输入、更新游戏状态、渲染、碰撞检测、生成更新游戏道具的函数。
  2. resource_manager类:提供静态方法,加载着色器和纹理。
  3. sprite_renderer类:渲染类,提供具体的渲染方法以及渲染的变换,被各类的Draw函数调用。
  4. shader&texture类:用于具体生成着色器和纹理,并提供相关状态设置函数。
  5. game_object类:游戏实体,包括位置、大小、速度、颜色、纹理和用于绘制实体的Draw函数。
  6. ball_object类:小球实体,继承自game_object类,添加小球半径、是否固定等属性,移动以及重置函数。
  7. game_level类:从文件读取游戏关卡(砖块放置),检查游戏是否完成、绘制关卡。
  8. particle_generator类:用于生成粒子特效,详见下文。
  9. post_processor类:后期处理,详见下文。
  10. power_up类:道具类,继承自game_object类,添加道具类型、持续时间、是否激活属性。
  11. text_renderer类:用于渲染文字。

2.game类

  1. Init():初始化游戏状态,加载所有需要的着色器、纹理、关卡、音乐、文字、游戏实体,以部分为例。

    // 加载音乐
    SoundEngine->play2D("music/breakout.mp3", GL_TRUE);
    // 设置字体
    Text = new TextRenderer(this->Width, this->Height);
    Text->Load("fonts/OCRAEXT.TTF", 24);
    // 加载着色器
    ResourceManager::LoadShader("shaders/sprite.vs", "shaders/sprite.fs", nullptr, "sprite");
    // 配置着色器
    glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(this->Width), static_cast<GLfloat>(this->Height), 0.0f, -1.0f, 1.0f);
    ResourceManager::GetShader("sprite").Use().SetInteger("sprite", 0);
    ResourceManager::GetShader("sprite").SetMatrix4("projection", projection);
    // 加载纹理
    ResourceManager::LoadTexture("resources/textures/background.jpg", GL_FALSE, "background");
    // 设置渲染控制
    Renderer = new SpriteRenderer(ResourceManager::GetShader("sprite"));
    // 加载关卡
    GameLevel one; one.Load("levels/one.lvl", this->Width, this->Height * 0.5);
    this->Levels.push_back(one);
    // 设置挡板
    glm::vec2 playerPos = glm::vec2(this->Width / 2 - PLAYER_SIZE.x / 2, this->Height - PLAYER_SIZE.y);
    Player = new GameObject(playerPos, PLAYER_SIZE, ResourceManager::GetTexture("paddle"));
    // 设置小球
    glm::vec2 ballPos = playerPos + glm::vec2(PLAYER_SIZE.x / 2 - BALL_RADIUS, -BALL_RADIUS * 2);
    Ball = new BallObject(ballPos, BALL_RADIUS, BALL_VELOCITY, ResourceManager::GetTexture("face"));
    
  2. Update(GLfloat dt):接受一个dt参数表示游戏时间片段,在此进行所有需要更新游戏状态的操作,包括小球移动、碰撞检测、更新例子状态、更新道具状态(持续时间)、判断游戏结束/获胜。

  3. ProcessInput(GLfloat dt):同样接受dt,处理键盘输入,此处有一个技巧,当按一个键时可能会触发多次,可增加KeysProcessed数组标记当前按键为True,防止重复触发。

    if (this->Keys[GLFW_KEY_ENTER] && !this->KeysProcessed[GLFW_KEY_ENTER])
    {
    	this->State = GAME_ACTIVE;
    	this->KeysProcessed[GLFW_KEY_ENTER] = GL_TRUE;
    }
    
  4. Render():根据不同游戏状态渲染文字、游戏物体等。运用帧缓冲区后期处理。

  5. Docollisions():碰撞检测与处理,使用改进的检测圆形的AABB碰撞检测。

    image-20231019192641808

    使用限制操作获取D在AABB中的向量

    float clamp(float value, float min, float max) {
        return std::max(min, std::min(max, value));
    }
    

    处理碰撞后逻辑:与砖块碰撞检查其是否可被摧毁,并重定位小球,反转速度。

    重定位:小球会部分进入AABB,以此判断碰撞,要将其减去进入向量来隔离在物体外部。

  6. 其他类请详见代码及注释。

3.主函数

​ 创建OpenGL主窗口,进行相关设置。创建游戏类,在游戏循环中调用用户输入与更新游戏状态的函数。

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

		// 管理用户输入
		Breakout.ProcessInput(deltaTime);

		// 更新游戏状态
		Breakout.Update(deltaTime);
		
		// Render
		glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		Breakout.Render();
		glfwSwapBuffers(window);
	}

4.渲染

​ Breakout是静态游戏,故不需要View矩阵,是2D游戏,故使用正交矩阵变换至裁剪空间。

glm::mat4 projection = glm::ortho(0.0f, 800.0f, 600.0f, 0.0f, -1.0f, 1.0f);

​ 顶点着色器如下,片元着色器获取纹理与颜色向量。

#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>

out vec2 TexCoords;

uniform mat4 model;
uniform mat4 projection;

void main()
{
    TexCoords = vertex.zw;
    gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0);
}

​ 将其封装至SpriteRenderer类中,DrawSprite函数用于具体渲染,供各类的Draw函数调用以渲染物体。注意变换顺序:缩放、选装、位移。由于旋转是绕原点(0, 0)的所以要先把旋转原点移至图形中心,再旋转,再移回。(games101也有提过)

model = glm::translate(model, glm::vec3(0.5f * size.x, 0.5f * size.y, 0.0f)); 
// 移至中心
model = glm::rotate(model, rotate, glm::vec3(0.0f, 0.0f, 1.0f)); // 旋转
model = glm::translate(model, glm::vec3(-0.5f * size.x, -0.5f * size.y, 0.0f)); 

5.粒子

​ 一个粒子就是个总是面向摄像机方向且(通常)包含一个大部分区域是透明的纹理的小四边形,使用粒子发生器发射,不断随时间消亡并产生,给每个粒子初始化一个生命值、位置(在小球周围)、颜色,当生命值随时间减少时粒子消失。

​ 由于预先规定粒子总数,不能简单地直接添加粒子,需要找到第一个消亡的粒子然后用新产生的粒子更新他(下一个消亡的粒子总是在上一个的下标右边),下边是更新粒子的代码。

void ParticleGenerator::Update(GLfloat dt, GameObject& object, GLuint newParticles, glm::vec2 offset) {
    // 每帧添加newParticle个新粒子
    for (GLuint i = 0; i < newParticles; i++) {
        int unusedParticle = this->firstUnusedParticle();
        this->respawnParticle(this->particles[unusedParticle], object, offset);
    }
    // 更新所有粒子
    for (GLuint i = 0; i < this->amount; i++) {
        Particle& p = this->particles[i];
        p.Life -= dt;
        if (p.Life > 0.0f) {
            p.Position -= p.Velocity * dt;
            p.Color.a -= dt * 2.5;
        }
    }
}

6.后期处理

详细请见:[帧缓冲 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/04 Advanced OpenGL/05 Framebuffers/)

渲染步骤:

  1. 绑定至多重采样的帧缓冲(每个像素可以存储大于1个颜色值的颜色缓冲)
  2. 和往常一样渲染游戏
  3. 将多重采样的帧缓冲内容传输至一个普通的帧缓冲中(这个帧缓冲使用了一个纹理作为其颜色缓冲附件)
  4. 解除绑定(绑定回默认的帧缓冲)
  5. 在后期处理着色器中使用来自普通帧缓冲的颜色缓冲纹理
  6. 渲染屏幕大小的四边形作为后期处理着色器的输出

在渲染场景前后使用BeginRender和EndRender函数,可以将渲染转到帧缓冲区进行

void PostProcessor::BeginRender()
{
    glBindFramebuffer(GL_FRAMEBUFFER, this->MSFBO);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
}

void PostProcessor::EndRender()
{
    // Now resolve multisampled color-buffer into intermediate FBO to store to texture
    glBindFramebuffer(GL_READ_FRAMEBUFFER, this->MSFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, this->FBO);
    glBlitFramebuffer(0, 0, this->Width, this->Height, 0, 0, this->Width, this->Height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // Binds both READ and WRITE framebuffer to default framebuffer
}

​ 通过不同的卷积核作用于片元着色器来实现不同效果。

GLint edge_kernel[9] = {	// chaos
        -1, -1, -1,
        -1,  8, -1,
        -1, -1, -1
};
GLint edge_kernel[9] = {	// shake
        -1, -1, -1,
        -1,  8, -1,
        -1, -1, -1
};    

7. OpenGL初始化

// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

标签:glm,渲染,0.0,2D,breakout,LearnOpenGL,GL,着色器,游戏
From: https://www.cnblogs.com/zhangfirst1/p/17778636.html

相关文章

  • 2D物理引擎 Box2D for javascript Games 第五章 碰撞处理
    2D物理引擎Box2DforjavascriptGames第五章碰撞处理碰撞处理考虑到Box2D世界和在世界中移动的刚体之间迟早会发生碰撞。而物理游戏的大多数功能则依赖于碰撞。在愤怒的小鸟中,小鸟摧毁小猪的城堡时,便是依赖碰撞而实现的;在图腾破坏者中,当神像坠落到图腾上或摔碎在地面上......
  • 编辑器Scene视图扩展 - Handles.Slider2D
    效果 #ifUNITY_EDITORusingUnityEditor;usingUnityEngine;publicclassTestSceneGUIWindow:EditorWindow{[MenuItem("MyTools/TestSceneGUIWindow")]publicstaticvoidShowWindow(){EditorWindow.GetWindow<TestSceneGU......
  • Cocos Creator 2D/3D基础/ 第003节3D模型的基本概念
    3.1计算机如何制作一个3D模型 讲述这个问题之前,我们先来看下现实生活中我们要做一个模型,应该如何做呢?首先我们要把模型的形状给雕刻构建出来,现实生活中的物体都是由分子组成的连续的表面,计算机是离散的无法做到这点,所以计算机通过微分的方式,把一个曲面分成”多个平面”来模拟......
  • Cocos Creator 2D/3D基础/ 第002节创建项目与显示第一个物体
    2.1创建第一个项目 从本节开始我们将详细的来开始学习CocosCreator的开发基础与操作。我们先来创建一个项目,打开CocosDashboard,选择”新建”,选3D游戏模板”Empty(3D)”,输入项目的名字(classGame)与路径(D:\Home\workspace),(注意项目的名字不要用中文与空格,要用英文缩写,......
  • [题解]CF514D R2D2 and Droid Army
    思路首先,可以转化题意,找到一个极长的区间\([l,r]\)使得(其中\(mx_i\)表示\([l,r]\)区间中属性\(i\)的最大值):\[\sum_{i=1}^{m}mx_i\leqk\]显然对于这个东西当\(l,r\)发生移动时,是极其好维护的,所以想到双指针。因为\(m\leq5\),所以我们可以直接开\(m\)个ST表......
  • Mitsubishi 三菱FX5U与NZ2MFB1-32DT输入输出模块CC-Link通讯
    01先点参数,系统参数,设置主机型号; 02点击“以太网端口”,进入画面选择CC-LinkIEFBasic设置中的“网络配置设置”; 03将NZ2MFB1-32DT输入输出模块拖拽出配置与本站同一网段的IP; 04点击CC-LinkIEFBasic设置中的“刷新设置”; 05设置刷新地址; 06设程序编写; 最后将......
  • 2D物理引擎 Box2D for javascript Games 第四章 将力作用到刚体上
    2D物理引擎Box2DforjavascriptGames第四章将力作用到刚体上将力作用到刚体上Box2D是一个在力作用下的世界,它可以将力作用于刚体上,从而给我们一个更加真实的模拟。但是,如果你想要移动刚体,发射子弹,抛掷小鸟,驾驶汽车和当你在玩物理游戏时你看到的一切令人起劲的事情,那么你......
  • Codeforces 512D. Fox And Travelling 题解
    FoxAndTravelling题面翻译给定一张\(n\)个点\(m\)条边的无向图。一个点只有当与它直接相连的点中最多只有一个点未被选择过时才可被选择。询问对于每个\(k\in[0,n]\),有序选择\(k\)个点的方案数。\(n\le100\),\(m\le\frac{n(n-1)}2\),答案对\(10^9+9\)取模。......
  • python32days
    异常元类—————————————————————————————————————————————异常就是错误发生的信号,我们需要对该信号做处理,如果不处理,往后的代码就不能执行了异常的分类 逻辑错误#是允许出现的,但是呢,编程的时候尽量避免逻辑错误的发生语法错......
  • threejs CSS2DObject点击事件触发不了
    原因:在three.js  0.13X版本后,上面dom的onclick不会触发,原因是控制器Controls,可以尝试一下去掉控制器,看看dom上的点击事件是否ok letobtControls=newOrbitControls(camera,container); // OrbitControls对页面的事件进行监听,并且阻止穿透 ......