Piccolo游戏引擎源码解析与Lua脚本扩展实践
Piccolo游戏引擎源码解析与Lua脚本扩展实践
本文将带领读者深入解析Piccolo游戏引擎的源码结构和核心功能。通过详细讲解引擎的文件组织、编译过程以及关键代码逻辑,帮助读者理解游戏引擎的工作原理。此外,本文还将演示如何在引擎中添加Lua脚本支持,为引擎开发提供实用的实践指导。
一、下载源码压缩包
项目地址:Releases · BoomingTech/Piccolo (github.com)
对于不熟悉Git的用户,可以按照以下方式进行下载:
二、文件和文件夹介绍
以
.
开头的文件或文件夹:这些文件通常是各个平台或工具的配置文件。在某些操作系统中,这些文件可能会默认隐藏,如果下载后发现没有这些文件,无需担心。CMake相关脚本:Piccolo引擎使用CMake进行构建,因此包含了一些CMake相关的脚本文件。
系统批处理文件:这些文件是为了方便构建Piccolo而编写的一些小脚本。
LICENSE、README和ReleaseNotes:这些文件可以在阅读代码之前简单浏览,了解项目的基本信息和使用说明。
除了上述四类文件或文件夹,剩下的主要是engine
文件夹,接下来将详细讲解其内容:
- 3rdparty:存放引擎所需的第三方库源码或二进制库。
- asset:存放引擎启动时默认关卡所需的资源。
- bin:在构建过程中生成的文件夹。
- configs:存放游戏启动时需要的配置文件。
- jolt-asset:存放物理库所需的着色器。
- shader:存放引擎启动时必须的着色器。
- source:引擎的核心源代码。
- template:PiccoloParser生成代码所需的模板文件。
- .gitignore:指明需要忽略的文件夹。
三、Source文件夹解析
- _generated:存放PiccoloParser生成的功能性代码。
- editor:存放编辑器的源代码。
- meta_parser:PiccoloParser的源代码。
- precompile:包含CMake脚本和一个文件,用于在构建工程前解析源代码,生成所需的功能性代码。
- runtime:引擎最核心的代码所在,体现了课程中提到的分层结构。
- test:目前为空,用于以后编写单元测试或其他测试。
四、runtime核心代码关注点
- CMakeLists.txt:指明了runtime工程的构建方式。
- engine.cpp 和 engine.h:整个引擎的入口文件。
五、编译运行Piccolo
- 在终端输入
cmake -B build
生成工程,这行命令的意思是以当前文件夹为工程根目录,生成工程在build文件夹里。 - 再次输入
cmake --build build
命令,去构建build文件夹里面的工程。
完成上述步骤后,引擎就已经编译完成了。接下来,运行引擎:
在终端输入命令.\bin\PiccoloEditor
,引擎将启动。通过右键可以旋转视角,点击Editor Mode可以切换为游戏模式。
六、代码逻辑解读
在engine.h
中,我们发现只有PiccoloEngine
一个类,这个类中只有以下几个public的函数:
- startEngine函数:首先注册了一些类型的元信息,然后调用
g_runtime_global_context
的startSystems
函数,最后打印了一个LOG。 - shutdownEngine函数:与
startEngine
相对应,操作刚好相反。
- run函数:首先取得窗口关闭信息,再决定是否在每个循环中去
tickOneFrame
,tickOneFrame
正是我们要说的第四个函数。
所以,PiccoloEngine做的最重要的两件事情就是启动和关闭g_runtime_global_context
里面的system以及每帧的tick。
- tickOneFrame函数:Piccolo引擎采用逻辑和渲染分离的架构。它首先去Tick逻辑,接下来计算FPS,然后交换逻辑和渲染的数据,再Tick渲染,剩余的代码是关于窗口标题等的处理。
逻辑Tick的主体是World的Tick,一个World由多个level组成,而level最活跃的组成部分是GObject,每个Object本身没有功能,不需要更新,其功能由它身上拥有的不同component实现,所以只需要更新它的component。
七、g_runtime_global_context解析
g_runtime_global_context
是一个全局变量,我们找到了RuntimeGlobalContext
类,这个类包含一堆system和manager的成员变量。
startSystem
函数的主要工作是实例化和初始化这些system和manager。其中,system更多是物理上需要的支持系统,比如窗口、输入、渲染等;而manager更偏向于引擎逻辑上,比如物理系统、粒子系统等。
八、总结
Piccolo引擎以engine.h
中的PiccoloEngine
类作为入口,由RuntimeGlobalContext
类管理的system和manager进行启动和管理,同时负责每帧的逻辑和渲染更新。逻辑tick的主体是World的Tick,一个World由多个level组成,而level最活跃的组成部分是GObject,每个Object的功能由它身上的component实现。
九、动手实践:添加Lua脚本支持
步骤1:下载第三方库
需要下载两个第三方库:
- Lua库:GitHub - lua/lua at v5.4.6
- sol2库:GitHub - ThePhD/sol2 at v3.3.0
将它们解压缩并放置在Piccolo项目的3rdparty目录中。
步骤2:构建第三方库
sol2库已经包含CMakeLists,可以直接在3rdparty目录下的CMakeLists中添加相应内容。
对于Lua库,需要为其编写CMakeLists脚本,可以参考imgui.cmake的格式,新建一个lua.cmake文件,内容如下:
set(lua_SOURCE_DIR_ ${CMAKE_CURRENT_SOURCE_DIR}/lua-5.4.6)
add_library(lua_static STATIC
${lua_SOURCE_DIR_}/lapi.c
${lua_SOURCE_DIR_}/lauxlib.c
${lua_SOURCE_DIR_}/lbaselib.c
${lua_SOURCE_DIR_}/lcode.c
${lua_SOURCE_DIR_}/lcorolib.c
${lua_SOURCE_DIR_}/lctype.c
${lua_SOURCE_DIR_}/ldblib.c
${lua_SOURCE_DIR_}/ldebug.c
${lua_SOURCE_DIR_}/ldo.c
${lua_SOURCE_DIR_}/ldump.c
${lua_SOURCE_DIR_}/lfunc.c
${lua_SOURCE_DIR_}/lgc.c
${lua_SOURCE_DIR_}/linit.c
${lua_SOURCE_DIR_}/liolib.c
${lua_SOURCE_DIR_}/llex.c
${lua_SOURCE_DIR_}/lmathlib.c
${lua_SOURCE_DIR_}/lmem.c
${lua_SOURCE_DIR_}/loadlib.c
${lua_SOURCE_DIR_}/lobject.c
${lua_SOURCE_DIR_}/lopcodes.c
${lua_SOURCE_DIR_}/loslib.c
${lua_SOURCE_DIR_}/lparser.c
${lua_SOURCE_DIR_}/lstate.c
${lua_SOURCE_DIR_}/lstring.c
${lua_SOURCE_DIR_}/lstrlib.c
${lua_SOURCE_DIR_}/ltable.c
${lua_SOURCE_DIR_}/ltablib.c
#${lua_SOURCE_DIR_}/ltests.c
${lua_SOURCE_DIR_}/ltm.c
#${lua_SOURCE_DIR_}/lua.c
${lua_SOURCE_DIR_}/lundump.c
${lua_SOURCE_DIR_}/lutf8lib.c
${lua_SOURCE_DIR_}/lvm.c
${lua_SOURCE_DIR_}/lzio.c
#${lua_SOURCE_DIR_}/onelua.c
)
target_include_directories(lua_static PUBLIC ${lua_SOURCE_DIR_})
在3rdparty目录下的CMakeLists中添加相应内容,并在runtime的CMakeLists中链接这两个库。
步骤3:编写Lua Component代码
将Lua Component代码放在runtime的function的framework的component下,新建一个lua文件夹。
lua_component.h
#pragma once
#include "sol/sol.hpp"
#include "runtime/function/framework/component/component.h"
namespace Piccolo
{
REFLECTION_TYPE(LuaComponent)
CLASS(LuaComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(LuaComponent)
public:
LuaComponent() = default;
void postLoadResource(std::weak_ptr<GObject> parent_object) override;
void tick(float delta_time) override;
protected:
sol::state m_lua_state;
META(Enable)
std::string m_lua_script;
};
} // namespace Piccolo
lua_component.cpp
#include "runtime/function/framework/object/object.h"
#include "runtime/function/framework/component/lua/lua_component.h"
#include "runtime/core/base/macro.h"
namespace Piccolo
{
void LuaComponent::postLoadResource(std::weak_ptr<GObject> parent_object)
{
m_parent_object = parent_object;
//为m_lua_state添加一些基础的函数
m_lua_state.open_libraries(sol::lib::base);
}
void LuaComponent::tick(float delta_time)
{
// LOG_INFO(m_lua_script);
m_lua_state.script(m_lua_script);
}
}
步骤4:配置Lua Component到场景中
默认场景位于engine\asset\world\hello.world.json
,其中设置了关卡"default_level_url": "asset/level/1-1.level.json"
。
在关卡的JSON文件中,找到名为Player的物体,仿照其定义添加Lua Component:
{
"typeName": "LuaComponent",
"context": {
"lua_script": "print('hello world')"
}
}
步骤5:编译运行
在终端依次输入以下命令:
cmake -Bbuild
cmake --build build
.\bin\PiccoloEditor
切换到Game Mode模式,命令行将开始输出"hello world",这表明Lua脚本已经成功运行。
通过以上步骤,我们成功在Piccolo引擎中添加了Lua脚本支持,实现了动态脚本执行的功能。