【Godot】GDExtention 扩展环境配置和简单示例

内容学习文档时制作

GDExtension 介绍:What is GDExtension? — Godot Engine (latest) documentation in English

一个教程文章:Introducing GDNative’s successor, GDExtension (godotengine.org)

你要确保已经下载安装了:

如果没有安装请看文档:Compiling for Windows — Godot Engine (stable) documentation in English

配置好环境之后,开始以下步骤:

编写代码

进入一个目录内,比如我进入 D:\ 盘的 godot 文件夹,创建一个 gde_test 目录

cd 'd:\godot\cpp'
mkdir gde_test
cd gde_test

git 克隆下载 godot-cpp 依赖文件,并初始化为 git 子模块

git init
git submodule add -b master https://github.com/godotengine/godot-cpp
cd godot-cpp
git submodule update --init

如果您正在使用 Git 对项目进行版本控制,那么最好将其添加为 Git 子模块

进入 godot-cpp 构建一遍代码

cd godot-cpp
scons platform=windows

使用指定 Godot 编辑器构建绑定,在 godot-cpp 目录里执行以下命令。

D:\godot\Engine\Godot_v4.0-stable_win64.exe --dump-extension-api extension_api.json

我的 Godot 编辑器路径为 D:\godot\Engine\Godot_v4.0-stable_win64.exe 所以是上面的路径

如果不这样做,godot-cpp 里的代码编译后会是过时的代码

在 gde_test 下目录结构,创建 demo 和 src 文件夹

|-gde_test
  |-demo                  # Godot 游戏测试项目
  |-godot-cpp/            # 构建源码时所需的文件
  |-src                   # 自己写的 c++ 对 Godot 扩展的源码

src 文件夹中创建 gdexample.h 文件,代码

#ifndef GDEXAMPLE_H
#define GDEXAMPLE_H

#include <godot_cpp/classes/sprite2d.hpp>

namespace godot {

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

private:
    float time_passed;

protected:
    static void _bind_methods();

public:
    GDExample();
    ~GDExample();

    void _process(float delta);
};

}

#endif

再创建 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(float 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);
}

以上是自定义的任意类,然后需要创建注册这些类的文件

register_types.h

//
// Created by z on 2023/3/26.
//

#ifndef GDE_TEST_REGISTER_TYPES_H
#define GDE_TEST_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 //GDE_TEST_REGISTER_TYPES_H

register_types.cpp

#include <gdextension_interface.h>

#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/godot.hpp>


#include "gdexample.h"


using namespace godot;

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

    // 注册类
    /*ClassDB::register_class<ExampleVirtual>(true);
    ClassDB::register_abstract_class<ExampleAbstract>();*/
    ClassDB::register_class<GDExample>();

}

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


// 下面的 example_library_init 为接口名称

extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT
example_library_init(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library,
                     GDExtensionInitialization *r_initialization) {
    godot::GDExtensionBinding::InitObject init_obj(p_interface, 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();
}
}

下载 SConstruct (用于构建当前写的源码)文件放到 gde_test 目录下

文档对这个文件的描述:We cannot easily write by hand a SConstruct file that SCons would use for building. For the purpose of this example, just use this hardcoded SConstruct file we’ve prepared. We’ll cover a more customizable, detailed example on how to use these build files in a subsequent tutorial.

输入命令对刚刚的文件进行构建一遍

scons -Q

注意下载的 SConstruct 文件,里面包含有两行代码

env.Append(CPPPATH=["src/"])
sources = Glob("src/*.cpp")
  • src/src/*.cpp 要编译的源码文件,* 代表 src 目录下所有 cpp 文件,如果有其他目录,则还要加上 src/*/*.cpp 扫描的目录,否则会出现包含下面信息系的错误
    register_types.windows.template_debug.x86_64.obj : error LNK2019: 无法解析的外部符号 "protected: static void __cdecl godot::GDExample::_
    bind_methods(void)" (?_bind_methods@GDExample@godot@@KAXXZ),函数 "private: static void __cdecl godot::ClassDB::_register_class<class godot::GDExample,0>(bool)" (??_register_class@VGDExample@godot@@0A@@ClassDB@godot@@CAX_N@Z) 中引用了该符号
    register_types.windows.template_debug.x86_64.obj : error LNK2019: 无法解析的外部符号 "public: __cdecl godot::GDExample::GDExample(void)"
    (??0GDExample@godot@@QEAA@XZ),函数 "public: static void * __cdecl godot::GDExample::create(void *)" (?create@GDExample@godot@@SAPEAXPEAX@Z) 中引用了该符号
    demo\bin\libgdexample.windows.template_debug.x86_64.dll : fatal error LNK1120: 3 个无法解析的外部命令
    

    也可能是:有定义未实现,或者说编辑器找不到实现。有可能是你给ide设置了文件包含路径,写代码没问题,但是编译器方面并没设置。也有可能就是单纯头文件有定义就是cpp没实现

  • 代码里还包含有个路径: demo/bin/libgdexample。这个是要编译到的路径和 libgdexample 文件名,如果修改可以更换要编译到的路径位置

关于编译的文档:Compiling for Windows — Godot Engine (latest) documentation in English

然后在 Godot 那个 demo 项目里,添加一个 example.gdextension 文件,写入如下代码进行添加插件

[configuration]

entry_symbol = "example_library_init"

[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"

代码中 entry_symbol = "example_library_init" 里的 example_library_init 要和之前的 register_types.cpp 文件中的那个保持一致,否则会找不到
libraries 部分代表对应的平台的编译的文件路径,根据需要进行更改

写代码时可以参考源码:godot/node.cpp at 4.0 · godotengine/godot (github.com) 末尾的绑定方法、信号、属性的代码


我这里有个稍微写得 SConstruct 文件,就是一个 Python 文件

下面

注意下面不能有中文字符串,否则 scons 命令运行会报错

#!/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

def scan_files(directory, prefix=None, postfix=None):
    files_list = []

    for root, sub_dirs, files in os.walk(directory):
        for special_file in files:
            if postfix:
                if special_file.endswith(postfix):
                    files_list.append(os.path.join(root, special_file))
            elif prefix:
                if special_file.startswith(prefix):
                    files_list.append(os.path.join(root, special_file))
            else:
                files_list.append(os.path.join(root, special_file))

    return files_list


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

# Sources to compile
sources = []
sources.extend(scan_files("src/", None, "cpp"))


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

Default(library)

写代码时要注意 Callable 还不能使用 bind 方法:https://github.com/godotengine/godot-cpp/issues/802


其他代码测试

summator.h

#include "godot_cpp/classes/ref_counted.hpp"

namespace godot {

    class Summator : public RefCounted {
    GDCLASS(Summator, RefCounted);

        int count;

    protected:
        static void _bind_methods();

    public:
        void add(int p_value);

        void reset();

        int get_total() const;

        Summator();
    };

}

summator.cpp

#include "summator.h"

using namespace godot;

void Summator::add(int p_value) {
    count += p_value;
}

void Summator::reset() { count = 0; }


int Summator::get_total() const { return count; }

void Summator::_bind_methods() {
    ClassDB::bind_method(D_METHOD("add"), &Summator::add);
    ClassDB::bind_method(D_METHOD("reset"), &Summator::reset);
    ClassDB::bind_method(D_METHOD("get_total"), &Summator::get_total);
}

Summator::Summator() { count = 0; }

Summator::~Summator() {};

发表评论