前言

之前假期无聊没事干,看着一堆从舟里面解包的 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 增加的物理模块(SpinePhysicsConstraintDataSpinePhysicsConstraint),以及 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.hAnimationStateData.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 = &region->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 仍处于实验状态,请勿用于生产环境。