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