记录自己从零开发2D游戏引擎的过程(0)
1.起因
一直对游戏制作很感兴趣,本来想趁暑假的时间制作一个游戏,但是尝试了几个游戏引擎之后感觉操作很复杂,于是就想自己写一个游戏引擎。因为之前曾经做过类似的努力,然而都半途而废了,所以就开一个博客专门记录自己开发游戏引擎的过程,以此激励自己。
2.开发准备
2.1编译环境
由于大家玩PC游戏主要是在Windows平台游玩的,所以要在Windows上配置编译环境。然而这个过程不如想象中顺利。
由于不想用几十GB的Visual Studio,故选择了MinGW+vscode作为开发工具。但是MinGW安装程序因为网络原因一直卡死,反复试了几次也不成功。所以去安装了msys2,再用msys2安装了MinGW,然后又配置了半天环境变量,终于可以在Windows下编译C++了。
编译器安装好了,然而编辑器又出问题了,vscode的代码提示功能一直用不了,上网查也不知道什么原因。于是放弃了,纯手打代码也不费多少时间。
2.2库
我选择了glad+glfw作为图形渲染的库,box2d作为物理引擎,libpng作为png图片的解析库。分别下载之后编译,因为前期环境配置好了,还是比较顺利的。因为使用这些库的原因,这个项目就暂定为GLBox2D。
3.Hello World
首先复制glfw官网的源码,然后粘贴编译运行,发现运行不了。
上网查发现是因为没有读取GL的函数指针,然而没有找到gladLoadGLLoader和GLADloadproc,凭自己感觉把它们改成了gladLoadGL和GLADloadfunc,居然改对了。然后发现没有加framebuffer size callback,遂加上。
void framebufferSizeCallback(GLFWwindow* window,int w,int h){
glViewport(0,0,w,h);
}
glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
gladLoadGL((GLADloadfunc)glfwGetProcAddress);
编译运行,窗口正常显示,这是一个好的开始!
4.对glfw的初步封装
为了调用方便,把glfw里的一些东西封装到一个类里面。
这里我就直接贴代码了。目前只封装了暂时用的上的接口,其他的比如纹理渲染等以后再加上。OpenGL初学者,写的很渣,希望不要喷。
gb2graphics.h
#ifndef __GB2_GRAPHICS_H
#define __GB2_GRAPHICS_H
#include <glad/gl.h>
#include <GLFW/glfw3.h>
struct GB2VertexArray{
unsigned int type;
unsigned int vao;
unsigned int vbo;
float vertices[4096];
float groups[1024];
unsigned int vertex_count;
unsigned int vertex_increment_count;
unsigned int group_count;
};
class GB2Graphics{
public:
GB2Graphics();
int Init(const char * title);
void CreateVertexArray(GB2VertexArray * p_vertex_array, unsigned int type);
void VAVertex(GB2VertexArray * p_vertex_array, float x, float y);
void VAEnd(GB2VertexArray * p_vertex_array);
void VAClear(GB2VertexArray * p_vertex_array);
void DrawVertexArray(GB2VertexArray * p_vertex_array);
void DeleteVertexArray(GB2VertexArray * p_vertex_array);
void Clear();
void Flush();
bool GetKey(unsigned int key);
bool WillContinue();
private:
unsigned int m_shader;
GLFWwindow* m_window;
};
#endif
gb2graphics.cpp
#include "gb2graphics.h"
void framebufferSizeCallback(GLFWwindow* window,int w,int h){
glViewport(0,0,w,h);
}
GB2Graphics::GB2Graphics(){
}
int GB2Graphics::Init(const char * title){
if(!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
m_window = glfwCreateWindow(1024, 768, title, nullptr, nullptr);
if(!m_window){
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(m_window);
glfwSetFramebufferSizeCallback(m_window, framebufferSizeCallback);
gladLoadGL((GLADloadfunc)glfwGetProcAddress);
m_shader = glCreateProgram();
const char * str_vertex_shader =
"#version 330 core\n"
"layout (location = 0) in vec4 position;\n"
"void main()\n"
"{\n"
"gl_Position = position;\n"
"}\0";
const char * str_fragment_shader =
"#version 330 core\n"
"in vec4 in_color;\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
"color=vec4(in_color.r*0.4,in_color.g*0.3,in_color.b*0.2,in_color.a);\n"
"}\0";
unsigned int vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, &str_vertex_shader, nullptr);
glCompileShader(vs);
unsigned int fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, 1, &str_fragment_shader, nullptr);
glCompileShader(fs);
glAttachShader(m_shader, vs);
glAttachShader(m_shader, fs);
glLinkProgram(m_shader);
glValidateProgram(m_shader);
glDeleteShader(vs);
glDeleteShader(fs);
return 0;
}
void GB2Graphics::CreateVertexArray(GB2VertexArray * p_vertex_array,unsigned int type){
glGenVertexArrays(1, &p_vertex_array->vao);
glGenBuffers(1, &p_vertex_array->vbo);
glBindVertexArray(p_vertex_array->vao);
glBindBuffer(GL_ARRAY_BUFFER, p_vertex_array->vbo);
glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0);
glEnableVertexAttribArray(0);
VAClear(p_vertex_array);
p_vertex_array->type = type;
}
void GB2Graphics::VAVertex(GB2VertexArray * p_vertex_array, float x, float y){
int w, h;
glfwGetFramebufferSize(m_window, &w, &h);
if(w>h){
x = x*h/w;
}else{
y = y*w/h;
}
p_vertex_array->vertices[p_vertex_array->vertex_count++] = x;
p_vertex_array->vertices[p_vertex_array->vertex_count++] = y;
p_vertex_array->vertex_increment_count ++;
}
void GB2Graphics::VAEnd(GB2VertexArray * p_vertex_array){
p_vertex_array->groups[p_vertex_array->group_count++] = p_vertex_array->vertex_increment_count;
p_vertex_array->vertex_increment_count = 0;
}
void GB2Graphics::VAClear(GB2VertexArray * p_vertex_array){
p_vertex_array->vertex_count = 0;
p_vertex_array->group_count = 0;
p_vertex_array->vertex_increment_count = 0;
}
void GB2Graphics::DrawVertexArray(GB2VertexArray * p_vertex_array){
if(p_vertex_array->group_count == 0) return;
glBindVertexArray(p_vertex_array->vao);
glBindBuffer(GL_ARRAY_BUFFER, p_vertex_array->vbo);
glBufferData(GL_ARRAY_BUFFER,
p_vertex_array->vertex_count*sizeof(float), p_vertex_array->vertices, GL_STATIC_DRAW);
glUseProgram(m_shader);
unsigned int count = 0;
for(int i=0;i<p_vertex_array->group_count;++i){
glDrawArrays(GL_LINE_LOOP, count, p_vertex_array->groups[i]);
count += p_vertex_array->groups[i];
}
}
void GB2Graphics::DeleteVertexArray(GB2VertexArray * p_vertex_array){
glDeleteVertexArrays(1, &p_vertex_array->vao);
glDeleteBuffers(1, &p_vertex_array->vbo);
}
void GB2Graphics::Clear(){
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
void GB2Graphics::Flush(){
glfwSwapBuffers(m_window);
glfwPollEvents();
}
bool GB2Graphics::GetKey(unsigned int key){
return glfwGetKey(m_window, key) == GLFW_PRESS;
}
bool GB2Graphics::WillContinue(){
return !glfwWindowShouldClose(m_window);
}
5.Box2D的使用
Box2D是一个以MIT许可证发行的开源的2D物理引擎,据说被很多游戏引擎使用。
Box2D是用C++写的,包含很多封装好的类,感觉自己再封装一遍的意义不大,所以就直接用它提供的类。
为了测试一下Box2D运行的效果,写了一个测试文件
test.cpp
#include "gb2graphics.h"
#include <box2d/box2d.h>
#include <thread>
#include <chrono>
b2Body * AddBlock(b2World * p_world, float x, float y, float hw, float hh, bool is_dynamic){
b2BodyDef bodyDef;
if(is_dynamic)
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(x, y);
b2Body * p_body = p_world->CreateBody(&bodyDef);
b2PolygonShape box;
box.SetAsBox(hw, hh);
b2FixtureDef fixtureDef;
fixtureDef.shape = &box;
if(is_dynamic){
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
}
p_body->CreateFixture(&fixtureDef);
return p_body;
}
void SetVelocity(b2Body * p_body, const b2Vec2& direction, float strength){
b2Vec2 v = p_body->GetLinearVelocity();
b2Vec2 v2 = direction;
v2.Normalize();
float d = strength - b2Dot(v, v2);
v = v + d * v2;
p_body->SetLinearVelocity(v);
}
int main(){
GB2Graphics * p_graphics = new GB2Graphics();
if(p_graphics->Init("Test GlBox2d") == -1){
return -1;
}
GB2VertexArray va;
p_graphics->CreateVertexArray(&va, GL_LINE_LOOP);
b2Vec2 gravity(0.0f, -10.0f);
b2World * p_world = new b2World(gravity);
AddBlock(p_world, 0.0f, -10.0f, 50.0f, 10.0f, false);
b2Body * p_player = AddBlock(p_world, 0.0f, 4.0f, 1.0f, 1.0f, true);
b2Transform camera_transform;
camera_transform.SetIdentity();
float scale = 0.05;
std::chrono::duration<double> frameTime(0.0);
std::chrono::duration<double> sleepAdjust(0.0);
while(p_graphics->WillContinue()){
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
p_world->Step(1.0f/60.0f, 6, 2);
p_graphics->VAClear(&va);
for(b2Body * p_body = p_world->GetBodyList();p_body;p_body = p_body->GetNext()){
for(b2Fixture * p_fixture = p_body->GetFixtureList();p_fixture;p_fixture = p_fixture->GetNext()){
if(p_fixture->GetType() == b2Shape::e_polygon){
b2PolygonShape * p_polygon_shape = (b2PolygonShape*)p_fixture->GetShape();
b2Transform xf = p_body->GetTransform();
for(int i=0;i<p_polygon_shape->m_count;++i){
b2Vec2 v = p_polygon_shape->m_vertices[i];
v = b2Mul(xf, v);
v = scale * b2Mul(camera_transform, v);
p_graphics->VAVertex(&va, v.x, v.y);
}
p_graphics->VAEnd(&va);
}
}
}
b2Vec2 dv = camera_transform.p + p_player->GetPosition();
if(dv.Length()>1.0f)
dv.Normalize();
dv = 0.5 * dv;
camera_transform.p = camera_transform.p - dv;
b2Vec2 up(0.0f, 1.0f);
b2Vec2 right(1.0f, 0.0f);
if(p_graphics->GetKey(GLFW_KEY_UP)){
SetVelocity(p_player, up, 10.0f);
}
if(p_graphics->GetKey(GLFW_KEY_LEFT)){
SetVelocity(p_player, right, -10.0f);
}
if(p_graphics->GetKey(GLFW_KEY_RIGHT)){
SetVelocity(p_player, right, 10.0f);
}
if(p_graphics->GetKey(GLFW_KEY_DOWN)){
SetVelocity(p_player, up, -10.0f);
}
p_graphics->Clear();
p_graphics->DrawVertexArray(&va);
p_graphics->Flush();
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::chrono::duration<double> target(1.0 / 60.0);
std::chrono::duration<double> timeUsed = t2 - t1;
std::chrono::duration<double> sleepTime = target - timeUsed + sleepAdjust;
if (sleepTime > std::chrono::duration<double>(0))
{
std::this_thread::sleep_for(sleepTime);
}
}
delete p_world;
}
运行,可以看到活动的小方块和静止的大方块,可以控制小方块的移动。
接下来的任务就是加入碰撞侦听的功能,以及关卡文件的设计。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)