之前假期无聊没事干,看着一堆从舟里面解包的 Spine 模型,就想着将 Spine Runtime 3.8 移植到 Godot 上。
先上成品:

移植基于官方的 spine-godot 运行时集成进行。
注释
所有操作均在 Arch Linux 下进行,使用 Godot 4.5。
Windows 和 macOS 请根据实际情况调整相关命令。
提前安装好 SCons 和 GCC/Clang/MSVC 等工具链。
克隆 Spine Runtime 3.8 和 4.2 的仓库,可以使用稀疏检出加快克隆速度。
1
2
3
4
5
6
7
8
9
| git clone git@github.com:EsotericSoftware/spine-runtimes.git --depth=1 --filter=tree:0 -n --branch=3.8 sp38
cd sp38
git sparse-checkout set --no-cone spine-cpp
git checkout
cd ~
git clone git@github.com:EsotericSoftware/spine-runtimes.git --depth=1 --filter=tree:0 -n --branch=4.2 sp42
cd sp42
git sparse-checkout set --no-cone spine-godot spine-cpp
git checkout
|
克隆 godot-cpp
,用于后续 GDExtension 的编译,我这里使用 4.5:
1
2
| cd sp42/spine-godot
git clone --depth=1 git@github.com:godotengine/godot-cpp.git -b 4.5
|
复制 Spine Runtime 3.8 的 spine-cpp
目录:
1
| cp -r sp38/spine-cpp sp42/spine_godot/spind_godot/spine-cpp
|
注释
此时可以运行 SCons 生成 compile_commands.json
,方便后续导入项目到 IDE:
1
| scons -j8 target=editor compiledb=true use_llvm=true dev_build=true
|
移植过程#
移除新版 Spine Runtime 相关 API 和绑定#
主要是移除 Spine 4.2 增加的物理模块(SpinePhysicsConstraintData
和 SpinePhysicsConstraint
),以及 Spine 4.0 的时间轴扩展(各种 mix 方法)等部分。
这一部分可以借助 IDE 进行处理。

Breaking changes 处理#
修改 SpineBoneNode.cpp
中的 update_world_transform
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @@ -190,7 +190,7 @@
if (!sprite) return;
if (bone_mode == SpineConstant::BoneMode_Drive) return;
sprite->get_skeleton()->set_to_setup_pose();
- sprite->get_skeleton()->update_world_transform(SpineConstant::Physics_Update);
+ sprite->get_skeleton()->update_world_transform();
Transform2D global_transform = sprite->get_global_bone_transform(bone_name);
set_global_transform(global_transform);
update_transform(sprite);
@@ -274,7 +274,7 @@
auto sprite = find_parent_sprite();
if (!sprite) return;
sprite->get_skeleton()->set_to_setup_pose();
- sprite->get_skeleton()->update_world_transform(SpineConstant::Physics_Update);
+ sprite->get_skeleton()->update_world_transform();
}
}
|
修改 SpineSkeleton.cpp
中的 get_bounds
:
1
2
3
4
5
6
7
8
| Rect2 SpineSkeleton::get_bounds() {
SPINE_CHECK(skeleton, Rect2(0, 0, 0, 0))
float x, y, w, h;
- spine::SkeletonClipping clipper;
- skeleton->getBounds(x, y, w, h, bounds_vertex_buffer, &clipper);
+ skeleton->getBounds(x, y, w, h, bounds_vertex_buffer);
return Rect2(x, y, w, h);
}
|
并调整方法签名:
1
2
3
4
5
6
7
8
9
| @@ -33,7 +33,7 @@
#include <spine/SkeletonClipping.h>
void SpineSkeleton::_bind_methods() {
- ClassDB::bind_method(D_METHOD("update_world_transform", "physics"), &SpineSkeleton::update_world_transform);
+ ClassDB::bind_method(D_METHOD("update_world_transform"), &SpineSkeleton::update_world_transform);
ClassDB::bind_method(D_METHOD("set_to_setup_pose"), &SpineSkeleton::set_to_setup_pose);
ClassDB::bind_method(D_METHOD("set_bones_to_setup_pose"), &SpineSkeleton::set_bones_to_setup_pose);
ClassDB::bind_method(D_METHOD("set_slots_to_setup_pose"), &SpineSkeleton::set_slots_to_setup_pose);
|
加入部分 API 并更新头文件#
修改 AnimationStateData.h
和 AnimationStateData.cpp
1
2
3
4
5
| // AnimationStateData.cpp
void AnimationStateData::clear() {
_defaultMix = 0;
_animationToMixTime.clear();
}
|
1
2
3
4
| // AnimationStateData.h
public:
// ...
void clear();
|
并使用 Spine Runtime 4.2 的 SpineString.h
(位于 include/spine/SpineString.h
) 替换 3.8 的 SpineString.h
。
更改 Skel 加载部分#
更改 SpineSkeletonFileResource.cpp
:
主要修改检查的 Skel 版本字符串为 3.8,同时需要先读取一次 Skel 的 hash,才能正常读取版本,记得释放读取的 hash:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| @@ -99,7 +98,7 @@
static bool checkVersion(const char *version) {
if (!version) return false;
- char *result = (char *) (strstr(version, SPINE_VERSION_STRING) - version);
+ char *result = (char *) (strstr(version, "3.8") - version);
return result == 0;
}
@@ -118,7 +117,9 @@
input.cursor = (const unsigned char *) binaryData;
input.end = (const unsigned char *) binaryData + length;
// Skip hash
- input.cursor += 8;
+ // input.cursor += 8;
+ char *hash = readString(&input);
+ spine::SpineExtension::free(hash, __FILE__, __LINE__);
char *version = readString(&input);
bool result = checkVersion(version);
spine::SpineExtension::free(version, __FILE__, __LINE__);
|
更改 Atlas 加载部分#
在 SpineAtlasResource.cpp
中查找并进行以下修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| @@ -159,7 +159,7 @@
renderer_object->texture = Ref<Texture>(nullptr);
renderer_object->normal_map = Ref<Texture>(nullptr);
renderer_object->specular_map = Ref<Texture>(nullptr);
- page.texture = (void *) renderer_object;
+ page.setRendererObject((void *) renderer_object);
return;
}
@@ -209,8 +209,7 @@
renderer_object->canvas_texture->set_normal_texture(renderer_object->normal_map);
renderer_object->canvas_texture->set_specular_texture(renderer_object->specular_map);
#endif
-
- page.texture = (void *) renderer_object;
+ page.setRendererObject((void *) renderer_object);
page.width = texture->get_width();
page.height = texture->get_height();
}
@@ -335,7 +334,7 @@
clear();
texture_loader = new GodotSpineTextureLoader(&textures, &normal_maps, &specular_maps, normal_map_prefix, specular_map_prefix, false);
auto utf8 = atlas_data.utf8();
- atlas = new spine::Atlas(utf8.ptr(), utf8.size(), source_path.get_base_dir().utf8(), texture_loader);
+ atlas = new spine::Atlas(utf8.ptr(), utf8.size() - 1, source_path.get_base_dir().utf8(), texture_loader);
if (atlas) return OK;
clear();
|
不知道为什么,这里需要将 size
减去 1 才能让 Runtime 正常读取 atlas。
更改 Bone 和 RendererObject 渲染部分#
在 SpineSprite.cpp
中进行以下修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| @@ -869,8 +870,8 @@
auto *region = (spine::RegionAttachment *) attachment;
vertices->setSize(8, 0);
- region->computeWorldVertices(*slot, *vertices, 0);
- renderer_object = (SpineRendererObject *) ((spine::AtlasRegion *) region->getRegion())->page->texture;
+ region->computeWorldVertices(slot->getBone(), *vertices, 0);
+ renderer_object = (SpineRendererObject *) ((spine::AtlasRegion *) region->getRendererObject())->page->getRendererObject();
uvs = ®ion->getUVs();
indices = &statics.quad_indices;
@@ -884,7 +885,7 @@
vertices->setSize(mesh->getWorldVerticesLength(), 0);
mesh->computeWorldVertices(*slot, *vertices);
- renderer_object = (SpineRendererObject *) ((spine::AtlasRegion *) mesh->getRegion())->page->texture;
+ renderer_object = (SpineRendererObject *) ((spine::AtlasRegion *) mesh->getRendererObject())->page->getRendererObject();
uvs = &mesh->getUVs();
indices = &mesh->getTriangles();
@@ -1052,7 +1053,7 @@
auto *region = (spine::RegionAttachment *) attachment;
auto *vertices = &statics.scratch_vertices;
vertices->setSize(8, 0);
- region->computeWorldVertices(*slot, *vertices, 0);
+ region->computeWorldVertices(slot->getBone(), *vertices, 0);
// Render triangles.
createLinesFromMesh(statics.scratch_points, statics.quad_indices, vertices);
|
编译和安装#
全部准备好后就可以开始编译了。
1
| scons -j8 target=editor compiledb=true use_llvm=true dev_build=true
|
最终编译的 so 位于 bin/linux/libspine_godot.linux.editor.dev.x86_64.so
。
将 bin/linux/libspine_godot.linux.editor.dev.x86_64.so
复制到项目的 bin/linux/libspine_godot.linux.editor.x86_64.so
。
将 spine_godot_extension.gdextension
复制到项目的 bin/
。
完成后启动 Godot 编辑器即可。
所有的 Patch 已经放在下面的仓库:litwak913/spine38-godot-patches
目前的 Patch 基于 EsotericSoftware/spine-runtimes 仓库中 commit 3653540558ba09104065addc56178dc2bceafc24
修改。
警告
此 Patch 仍处于实验状态,请勿用于生产环境。