首页 > 编程语言 >GDExtension的C++示例

GDExtension的C++示例

时间:2024-04-19 20:22:19浏览次数:21  
标签:bin godot GDExtension 示例 插件 C++ libgdexample cpp example

GDExtension的C++示例

本文按照官方文档,进行c++的GDExtension​插件开发,主要进行文档进行复刻,同时对文档中未涉及步骤进行补充

什么是GDExtension

除了GDScript​和C#​这两种脚本语言外,Godot​引擎可以执行其他编程语言编写的代码。目前有两种方式实现:C++模块GDExtension

简单的对这两种方式进行对比:

支持的语言 是否需要和引擎一起编译
C++模块 C++
GDExtension C++,GoDHaxeRustSwift

GDExtension官方明确维护C++版本,同时目前没有别的语言的维护计划,上表所列出的其他语言均由社区维护,用于生产前请进行充分调研

由上表可以知道,GDExtension​由于无需和引擎一起编译,使用更灵活,并且支持更多语言。

使用GDExtension的依赖

  • Godot4可执行程序
  • c++编译器
  • SCons 作为构建工具
  • godot-cpp 仓库的副本

安装godot-cpp

推荐将godot-cpp​作为项目的git子模块

# 创建项目目录
mkdir gdextension_cpp_example

# 进入目录
cd gdextension_cpp_example
# 创建demo目录用于存放godot项目相关文件
mkdir demo
# 创建src目录用于存放GDExtension的c++代码
mkdir src

# 初始化git版本管理
git init
# 将godot-cpp作为git子模块加入项目,并切换至4.2分支(对应你的godot版本)
git submodule add -b 4.2 https://github.com/godotengine/godot-cpp
# 更新godot-cpp子模块
cd godot-cpp
git submodule update --init

项目结构

本次项目结构如下所示

gdextension_cpp_example/
|
+--demo/                  # game example/demo to test the extension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building

SCons构建系统

使用Anaconda​+Python虚拟环境​+pip​来搭建SCons​构建系统环境

安装Anaconda

该步骤可参考官方文档

Conda安装Python虚拟环境

All SCons software (SCons itself, tests, supporting utilities) will be written to work with Python version 3.6+.

根据官网手册介绍,Scons的python依赖版本为3.6+​,这里我搭建一个3.11​的环境(版本介绍页面,python每个版本支持5年,3.11支持到2027-10)

# 查看当前环境(可选)
conda env list

# 查看当前环境的包(可选)
conda list
pip list

# 检查conda更新(可选)
conda update conda

# conda清理(可选)
conda clean -p      //删除没有用的包(推荐)

# 创建虚拟环境
conda create -n your_env_name python=3.11

# 激活虚拟环境
conda activate your_env_name

安装SCons

由于conda中SCons​最新版为V3.12​无法进行godot构建,要求V4.0+​,因此需要使用pip方式安装

pip install scons

# 安装后使用查看版本
scons --version

构建C++绑定

项目中的godot-cpp/gdextension/extension_api.json

仓库中该文件已经存在,当你的版本与仓库配置文件中描述的版本不同时,你需要自己手动生成你使用版本对应的extension_api.json

extension_api.json​解读

查看版本

查看header​字段的版本与使用的版本是否相同,例子中我使用的godot版本为V4.2.2​,与配置文件对应

	"header": {
		"version_major": 4,
		"version_minor": 2,
		"version_patch": 2,
		"version_status": "stable",
		"version_build": "official",
		"version_full_name": "Godot Engine v4.2.2.stable.official"
	},

image

生成当前版本的extension_api.json

当你的版本与仓库中的extension_api.json​版本不匹配时,你可以手动生成当前版本的配置文件,详细信息查看官方手册,方法如下:

# 进入godot安装目录
cd godot_bin_path

# 生成extension_api.json
godot --dump-extension-api

# 执行结束会在可执行程序godot目录下生成配置文件godot_bin_path/extension_api.json

# 拷贝到项目目录中,放在哪里自己决定,后续需要根据该文件配置scons
cp extension_api.json path_to_your_project/

构建绑定

若要生成和编译绑定,请使用以下命令(替换 <platform>​ 为 windows​ 或 linuxmacos​ 取决于您的操作系统):

cd path_to_your_project/godot-cpp
scons platform=<platform> custom_api_file=<PATH_TO_FILE>
cd ..

这一步将需要一段时间. 完成后, 你应该有一个静态库, 可以编译到你的项目中, 存储在 godot-cpp / bin / ​中.

你可能需要在 Windows 或 Linux 的命令行中添加 bits=64​。

实现一个简单的插件

  • 打开Godot​在demo​中新建一个项目
  • 在我们的演示项目中, 我们将创建一个包含名为 "Main" 的节点的场景, 我们将其保存为 main.tscn​ . 我们稍后再回过头来看看.

该例中创建一个名为GDExample​的插件,该插件继承自Sprite2D

创建插件头文件

首先新建插件的头文件gdexample.h

#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H

#include <godot_cpp/classes/sprite2d.hpp>

namespace godot {

class GDExample : public Sprite2D {
	GDCLASS(GDExample, Sprite2D)

private:
	double time_passed;

protected:
	static void _bind_methods();

public:
	GDExample();
	~GDExample();

	void _process(double delta) override;
};

}

#endif

我们解读上述的代码

  • 命名空间godot​,因为GDExtension中所有内容都在这个命名空间内
  • 继承Sprite2D​包含sprite2d.hpp
  • GDCLASS​用于设置一些例行执行的操作
  • 我们定义了一个time_passed​成员,用于保存运行时间
  • 静态方法_bind_methods​,Godot 将调用它来找出可以调用哪些方法以及它公开了哪些属性。
  • _process​方法,与GDScript​中的_process​工作方式相同,每帧会进行调用

创建源文件

新建gdexample.cpp​,上述插件方法的具体实现在此处进行实现

#include "gdexample.h"
#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void GDExample::_bind_methods() {
}

GDExample::GDExample() {
	// Initialize any variables here.
	time_passed = 0.0;
}

GDExample::~GDExample() {
	// Add your cleanup here.
}

void GDExample::_process(double delta) {
	time_passed += delta;

	Vector2 new_position = Vector2(10.0 + (10.0 * sin(time_passed * 2.0)), 10.0 + (10.0 * cos(time_passed * 1.5)));

	set_position(new_position);
}
  • _process​中我们根据当前时间,使用正弦与余弦函数计算出精灵的新位置

除了以上插件类的c++代码,我们的GDExtension​插件可以包含多个插件类,每个类都有自己的头文件与源文件,就像GDExample​一样。

在插件类编码完毕后,我们还需要一个register_types.cpp​文件,该文件负责告诉Godot​我们插件中的所有类。

编写注册函数

创建register_types.cpp

#include "register_types.h"

#include "gdexample.h"

#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>

using namespace godot;

void initialize_example_module(ModuleInitializationLevel p_level) {
	if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
		return;
	}

	ClassDB::register_class<GDExample>();
    // ClassDB::register_class<YourOtherClass>();
}

void uninitialize_example_module(ModuleInitializationLevel p_level) {
	if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
		return;
	}
}

extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
	godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);

	init_obj.register_initializer(initialize_example_module);
	init_obj.register_terminator(uninitialize_example_module);
	init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);

	return init_obj.init();
}
}

解读上述代码:

  • initialize_example_module​、uninitialize_example_module​:分别在Godot​进行加载​或卸载​我们的GDExtension​时执行。

    目前在这里所做的只是解析绑定模块中的函数以初始化它们,但您可能需要根据需要,设置更多内容。我们为库中的每个类调用该函数register_class​ 。

  • 最重要的是第三个函数example_library_init​,该函数可以认为是插件的入口,后续会在配置文件(xxx.gdextension​)中标注出来。

    这个函数进行了如下工作:

    • 注册加载​时需要执行的操作initialize_example_module

    • 注册卸载​时需要执行的操作uninitialize_example_module

    • 设置初始化级别(level of initialization)​涉及(core(核心), servers(服务器), scene(场景), editor(编辑器), level(级别))

      注:官方文档中对此没有更多说明,本文作者猜测涉及到插件在何时初始化的时机设置

创建对应的头文件register_types.h

#ifndef GDEXAMPLE_REGISTER_TYPES_H
#define GDEXAMPLE_REGISTER_TYPES_H

#include <godot_cpp/core/class_db.hpp>

using namespace godot;

void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);

#endif // GDEXAMPLE_REGISTER_TYPES_H

编译插件

编译插件需要手工编写SConstruct​文件,这并不容易,官方提供一个根据本例硬编码的文件,如下所示:

#!/usr/bin/env python
import os
import sys

env = SConscript("godot-cpp/SConstruct")

# For reference:
# - CCFLAGS are compilation flags shared between C and C++
# - CFLAGS are for C-specific compilation flags
# - CXXFLAGS are for C++-specific compilation flags
# - CPPFLAGS are for pre-processor flags
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags

# tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=["src/"])
sources = Glob("src/*.cpp")

if env["platform"] == "macos":
    library = env.SharedLibrary(
        "demo/bin/libgdexample.{}.{}.framework/libgdexample.{}.{}".format(
            env["platform"], env["target"], env["platform"], env["target"]
        ),
        source=sources,
    )
else:
    library = env.SharedLibrary(
        "demo/bin/libgdexample{}{}".format(env["suffix"], env["SHLIBSUFFIX"]),
        source=sources,
    )

Default(library)

将改文件放入项目根目录,即gdextension_cpp_example/​,与godot-cpp​,src​,demo​同级,然后开始执行编译操作,执行如下命令

scons platform=<platform>

你现在应该能够在demo/bin/<platform>​ 中找到该模块.

在这里,我们将 godot-cpp 和我们的 gdexample 库编译为调试版本。对于优化的构建,应使用target=template_release​开关进行编译。

使用GDExtension模块

终于,我们可以使用自己编译的插件了,再回到Godot​之前,我们需要在demo/bin/​中创建一个文件

这个文件让Godot​知道,在不同平台应该从哪里拿到不同的动态库文件,同时声明模块的入口函数,函数定义在编写注册函数中。

[configuration]

entry_symbol = "example_library_init"
compatibility_minimum = "4.2"

[libraries]

macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
macos.release = "res://bin/libgdexample.macos.template_release.framework"
windows.debug.x86_32 = "res://bin/libgdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "res://bin/libgdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "res://bin/libgdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "res://bin/libgdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "res://bin/libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "res://bin/libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "res://bin/libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "res://bin/libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "res://bin/libgdexample.linux.template_release.rv64.so"
android.debug.x86_64 = "res://bin/libgdexample.android.template_debug.x86_64.so"
android.release.x86_64 = "res://bin/libgdexample.android.template_release.x86_64.so"
android.debug.arm64 = "res://bin/libgdexample.android.template_debug.arm64.so"
android.release.arm64 = "res://bin/libgdexample.android.template_release.arm64.so"

这个文件中我们需要关注如下信息

  • configuration​中定义了兼容的Godot​最低版本,填写该字段防止旧版本Godot​加载你的插件
  • libraries​ 部分是重要的部分:它告诉 Godot 每个受支持平台的动态库在项目文件系统中的位置。这也将导致在导出项目时仅导出该文件,这意味着数据包将不包含与目标平台不兼容的库。
  • 最后,该dependencies​ 部分允许您声明还应包含的其他动态库。当您的GDExtension插件实现其他人的库并要求您为项目提供第三方动态库时,这一点很重要。

现在我们的项目结构看起来应该像这样:

gdextension_cpp_example/
|
+--demo/                  # game example/demo to test the extension
|   |
|   +--main.tscn
|   |
|   +--bin/
|       |
|       +--gdexample.gdextension
|
+--godot-cpp/             # C++ bindings
|
+--src/                   # source code of the extension we are building
|   |
|   +--register_types.cpp
|   +--register_types.h
|   +--gdexample.cpp
|   +--gdexample.h

添加我们的插件

现在在节点中就可以找到我们的插件GDExample

image

我们加入该节点,并进行如下设置:对Sprite2d​添加Texture​,同时取消勾选Offset/Centered

image

此时我们可以运行我们的项目:

gdextension_run

自定义编辑器图标

默认情况下, Godot 使用 GDExtension 节点的场景停靠栏中的节点图标.可以通过 gdextension ​文件添加自定义图标。节点的图标是通过引用其名称和 SVG 文件的资源路径来设置的。

例如

[icons]

GDExample = "res://icons/gd_example.svg"

参考资料

[官方资料]GDExtension C++ 示例

标签:bin,godot,GDExtension,示例,插件,C++,libgdexample,cpp,example
From: https://www.cnblogs.com/Biiigwang/p/18146724/gdextensionde-c-shi-li-z2bpicf

相关文章

  • C++字符串常见混淆方案
    正文将字符串转换成等效int数组std::vector<uint32_t>convert_wstring_to_int_array(constwchar_t*str){std::vector<uint32_t>vec;for(size_ti=0;i<wcslen(str);i+=2){uint32_tval=(uint32_t)str[i]<<16&0xffff0000;i......
  • 为什么有很多出名开源的C/C++方面的高性能网络库,比如libevent,boost-asio,有些企业还要
    为什么有很多出名开源的C/C++方面的高性能网络库,比如libevent,boost-asio,有些企业还要自己写?    我个人很倾向用著名的开源软件来完成功能需求,但是发现在实际开展中很多人会反对开源,而要求自己实现一套,我不知道是我考虑太少,还是他们太武断。 因为KPI的原因更多......
  • 美国政府敦促开发者:停止使用 C、C++
    美国政府敦促开发者:停止使用C、C++开源Linux​ ​关注他 5人赞同了该文章整理|屠敏出品|CSDN(ID:CSDNnews)“C、C++不安全,新应用开发时就别用了,旧应用应该采取迁移行动”,近日,美国白宫国家网络主任办公室(ONCD)在一份主题为《回到基础构件:通......
  • 音视频开发是不是C++开发中最难的细分方向?
    音视频开发是不是C++开发中最难的细分方向?     关注者611被浏览599,438关注问题​写回答​邀请回答​好问题7​3条评论​分享​  查看全部67个回答luluce不关心国事的程序猿(不会QT)。已关注......
  • C++六种内存序详解
    前言要理解C++的六种内存序,我们首先须要明白一点,处理器读取一个数据时,可能从内存中读取,也可能从缓存中读取,还可能从寄存器读取。对于一个写操作,要考虑这个操作的结果传播到其他处理器的速度。并且,编译器的指令重排和CPU处理器的乱序执行也是我们需要考虑的因素。 我们先看......
  • 使用示例解释.NET中的Mocking是什么?
    让我们踏上探索.NET软件开发中Mocking概念的旅程,让我们深入了解Mocking是多么简单易懂、易于访问。请与我一起穿越这个主题,我将涵盖以下内容:理解Mocking:为何它对于构建强大的测试策略至关重要。探索一些最常见的Mocking库:如Moq、NSubstitute、FakeItEasy和RhinoMocks等。掌握......
  • [8] UE C++ Mario
    创建了盒子,定义了盒子的碰撞位置能在if里面直接声明赋值局部变量但不能赋值成员变量friend关键词应用导入类的时候如果是灰色就删掉,并且查看头文件 ......
  • Java的六种线程状态及代码示例
    Java的线程有6个状态,分别是NEW           新建状态。刚new出来的thread,还没有调用start,就是这个状态RUNNABLE     运行状态(分为运行中和就绪)。正在运行,或者等待CPU调度,调用完start,就是这个状态BLOCKED       阻塞状态。还未竞争......
  • 在 VSCode 中使用正则表达式的示例
    下面是一些在VSCode中使用正则表达式的示例。在某个文件中查找相同的单词假设有一个名为sample.txt的文件,其内容如下:thequickbrownfoxjumpsoverthelazydog.thequickbrowncatjumpsoverthelazydog.thequickredhenjumpsoverthelazydog.我们想要查......
  • c++函数模板和运行机制
    C++_templatec++提供了函数模板(functiontemplate.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函......