记录自己从零开发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);

编译运行,窗口正常显示,这是一个好的开始!

image

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;

}

运行,可以看到活动的小方块和静止的大方块,可以控制小方块的移动。
image
接下来的任务就是加入碰撞侦听的功能,以及关卡文件的设计。

posted @   Plzsdad  阅读(498)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示