其他分享
首页 > 其他分享> > 记录自己从零开发2D游戏引擎的过程(1)

记录自己从零开发2D游戏引擎的过程(1)

作者:互联网

这个项目已发布在github上:https://github.com/zenitsh/glbox2d 因此以后不会贴大段代码。

6.创建Object类

Unity使用了GameObject存储游戏对象,Godot Engine使用了Node,因此我们也需要一个游戏对象类。这个过程中也踩了不少坑,大多数是C++的坑。

上次说b2Body可以直接使用,但是实际上它用的是自己的内存回收机制,因此不能用delete直接删除。因此为了将b2Body和其他对象统一起来,还是需要写一个类来包装它们。这个类就起名叫GB2Pack(我不是很会起名)。析构函数是虚函数,这样它销毁的时候就会自动选择析构函数。这里特别建了两种Pack:析构存储对象的Pack,和不析构存储对象的Pack。

class GB2BodyPack : public GB2Pack{
    public:
    GB2BodyPack(b2Body * body):m_body(body){
    }
    virtual ~GB2BodyPack(){
        m_body->GetWorld()->DestroyBody(m_body);
    }
    b2Body * m_body;
    b2Body * GetBody(){
        return m_body;
    }
};

然后就是写Object基类了,其实是主要为了派生出两种子类:字典对象和数组对象。暂时使用STL库提供的容器,设计上是可以任意选择喜欢的容器,所以使用STL的部分全写在头文件里。

特别注意的是,is_storage用于标记所有权,如果标记了storage,那么该GB2Pack存储的对象也会随GB2Pack销毁而销毁。

class GB2BaseObject{
public:
    GB2BaseObject();
    virtual ~GB2BaseObject();
    virtual void DebugPrint(unsigned int level);
    void SetName(std::string name);
    void SetInt(int n);
    int GetInt();
    void SetFloat(float f);
    float GetFloat();
    void SetPointer(GB2Pack * p, bool is_storage);
    GB2Pack * GetPointer();
    unsigned int m_type;
    std::string m_name;
    char m_data[64];
};

这里只贴出字典对象的实现

class GB2Object : public GB2BaseObject{
public:
    GB2Object():GB2BaseObject(){
        m_type = GB2_TYPE_OBJECT;
    }
    virtual ~GB2Object(){
        for(std::map<std::string, GB2BaseObject*>::iterator i=m_children.begin();i!=m_children.end();i++){
            delete i->second;
        }
    }
    virtual void DebugPrint(unsigned int level){
        GB2BaseObject::DebugPrint(level);
        for(int j=level;j>0;j--)std::cout<<"\t";std::cout<<"{\n";
        for(std::map<std::string, GB2BaseObject*>::iterator i=m_children.begin();i!=m_children.end();i++){
            for(int j=level;j>0;j--)std::cout<<"\t";std::cout<<i->first<<":\n";
            i->second->DebugPrint(level+1);
        }
        for(int j=level;j>0;j--)std::cout<<"\t";std::cout<<"}\n";
    }
    std::map<std::string, GB2BaseObject*> m_children;
    void SetChild(std::string name, GB2BaseObject* object){
        m_children[name] = object;
    }
    void DeleteChild(std::string name){
        m_children.erase(name);
    }
    GB2BaseObject* operator[](std::string name){
        std::map<std::string, GB2BaseObject*>::iterator i=m_children.find(name);
        if(i==m_children.end()){
            return nullptr;
        }
        return i->second;
    }
};

7.游戏脚本的实现

有了游戏对象,需要一个游戏脚本来驱动。这个脚本必须是容易更改的,以满足不同种类游戏的需求,并且方便mod的编写。如果选择编译型语言的话,会要求游戏或游戏开发工具附带一个编译器,而Python之类的脚本语言运行环境体积太庞大了。故选择自己实现一个解释型语言。

整个脚本运行在一个GB2Instance里面,这个对象存储了一个游戏运行时的几乎所有参数。

由于学过汇编,所以就按照汇编的思路写了。用32个int伪寄存器,32个float伪寄存器,以及两个字符串伪寄存器(用的是std::string),一个指针伪寄存器存储输入参数,这样就可以把执行的命令的函数指针统一成一个格式。

用栈来存储要操作的对象的指针,但是所有的对象都挂在m_root下。默认栈顶是主要操作的对象。m_root是一个数组,可以存储需要大量访问的数据,而不用查找map消耗时间。

int m_reg_int[32];
float m_reg_float[32];
std::string m_reg_str_1;
std::string m_reg_str_2;
char * m_reg_pointer;

std::stack<GB2BaseObject*> m_stack;
GB2Array * m_root;

typedef void (GB2Instance::*SingleFunc)();

然而这里踩坑了,执行类中的函数指针时,即使是在类的成员函数中,也必须使用这样一种语法:

(this ->* m_func[temp])();

其中一个函数的写法:

    void AddObject(){
        GB2BaseObject * obj = new GB2Object();
        ((GB2Object*)m_stack.top())->SetChild(m_reg_str_1, obj);
        obj->SetName(m_reg_str_1);
        m_stack.push(obj);
    }

暂时没有写对数组操作的函数,先试验一下效果。

test2.cpp

#include "glbox2d/glbox2d.h"

int main(){

    GB2Instance * p_instance = new GB2Instance();

    p_instance->Init();

    p_instance->SetRegString1("stage");
    p_instance->AddObject();

    p_instance->SetRegString1("ground");
    p_instance->AddObject();

    p_instance->SetRegFloat(0, 0.0f);
    p_instance->SetRegFloat(1, -10.0f);
    p_instance->SetRegFloat(2, 50.0f);
    p_instance->SetRegFloat(3, 10.0f);
    p_instance->SetRegInt(0,0);

    p_instance->CreateBody();

    p_instance->PopObject();

    p_instance->SetRegString1("box");
    p_instance->AddObject();

    p_instance->SetRegFloat(0, 0.0f);
    p_instance->SetRegFloat(1, 4.0f);
    p_instance->SetRegFloat(2, 1.0f);
    p_instance->SetRegFloat(3, 1.0f);
    p_instance->SetRegInt(0, 1);

    p_instance->CreateBody();

    p_instance->PopObject();

    p_instance->SetRegString1("ui");
    p_instance->AddObject();

    p_instance->SetRegString1("info");
    p_instance->AddObject();

    p_instance->DebugPrint();

    p_instance->Run();

    delete p_instance;

    return 0;

}

运行结果:

[unnamed]empty
[
        [unnamed]empty
        {
        stage:
                [stage]empty
                {
                box:
                        [box]pointer
                        {
                        }
                ground:
                        [ground]pointer
                        {
                        }
                ui:
                        [ui]empty
                        {
                        info:
                                [info]empty
                                {
                                }
                        }
                }
        }
]

8.代码的解释和编译

目前没有想到好的方法,只能用switch来控制不同种类的操作,效率可能会比较低。以后会全部替换成函数指针。std::string以后也会换。

    void ReadCode(const char* code, unsigned int length){
        unsigned int cursor = 0;
        unsigned int temp;
        char op;
        while(cursor<length){
            op = *(char*)(code+cursor);
            std::cout<<"Read: "<<(int)op<<"\n";
            cursor += sizeof(char);
            switch(op){
                case 0:
                    temp = *(unsigned int*)(code+cursor);
                    cursor+=sizeof(unsigned int);
                    (this ->* m_func[temp])();
                    break;
                case 1:
                    temp = *(unsigned int*)(code+cursor);
                    cursor+=sizeof(unsigned int);
                    m_reg_float[temp] = *(float*)(code+cursor);
                    cursor+=sizeof(float);
                    break;
                case 2:
                    temp = *(unsigned int*)(code+cursor);
                    cursor+=sizeof(unsigned int);
                    m_reg_float[temp] = *(float*)(code+cursor);
                    cursor+=sizeof(float);
                    break;
                case 3:
                    m_reg_str_1 = (char*)(code+cursor);
                    while(*(char*)(code+cursor))cursor+=sizeof(char);
                    cursor+=sizeof(char);
                    break;
                case 4:
                    m_reg_str_2 = (char*)(code+cursor);
                    while(*(char*)(code+cursor))cursor+=sizeof(char);
                    cursor+=sizeof(char);
                    break;
            }
        }
    }

粗略地写一个简单的编译器,测试下。

test3.cpp

#include <glbox2d/glbox2d.h>

int main(){
    char source[1024] =
    "3 stage\n"
    "0 3\n"
    "3 ground\n"
    "0 3\n"
    "2 0 0.0\n"
    "2 1 -10.0\n"
    "2 2 50.0\n"
    "2 3 10.0\n"
    "1 0 0\n"
    "0 100\n"
    "0 2\n"
    "0 0\n";
    char code[1024];
    unsigned int len;
    gb2CompileCode(code, &len, source);
    std::cout<<"Compiled:\n";
    for(int i=0;i<len;i++)std::cout<<std::hex<<(int)code[i]<<" ";
    GB2Instance * p_instance = new GB2Instance();
    p_instance->Init();
    p_instance->ReadCode(code, len);
    p_instance->Run();
    delete p_instance;
}

运行结果:

Compiled:
3 73 74 61 67 65 0 0 3 0 0 0 3 67 72 6f 75 6e 64 0 0 3 0 0 0 2 0 0 0 0 0 0 0 0 2 1 0 0 0 0 0 20 ffffffc1 2 2 0 0 0 0 0 48 42 2 3 0 0 0 0 0 20 41 1 0 0 0 0 0 0 0 0 0 64 0 0 0 0 2 0 0 0 0 0 0 0 0
[unnamed]empty
[
        [unnamed]empty
        {
        stage:
                [stage]empty
                {
                ground:
                        [ground]pointer
                        {
                        }
                }
        }
]

标签:std,cursor,char,code,游戏,int,2D,instance,引擎
来源: https://www.cnblogs.com/zwh12/p/16537074.html