mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-05 18:26:52 +08:00
Bone follower: add with pick, allows multiple followers, remove with pick, remove all.
This commit is contained in:
parent
71baae2cc8
commit
758058ec39
@ -508,8 +508,50 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "detach-instance-from-bone",
|
||||
"scriptName": "DetachInstanceFromBone",
|
||||
"id": "attach-object-to-bone",
|
||||
"scriptName": "AttachObjectToBone",
|
||||
"highlight": false,
|
||||
"params": [
|
||||
{
|
||||
"id": "object",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"id": "bone-name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "offset-x",
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"id": "offset-y",
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"id": "offset-angle",
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "detach-object-from-bone",
|
||||
"scriptName": "DetachObjectFromBone",
|
||||
"highlight": false,
|
||||
"params": [
|
||||
{
|
||||
"id": "object",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"id": "bone-name",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "detach-all-from-bone",
|
||||
"scriptName": "DetachAllFromBone",
|
||||
"highlight": false,
|
||||
"params": [
|
||||
{
|
||||
|
||||
@ -150,8 +150,22 @@ C3.Plugins.EsotericSoftware_SpineConstruct3.Acts =
|
||||
this.attachInstanceToBone(uid, boneName, offsetX, offsetY, offsetAngle);
|
||||
},
|
||||
|
||||
DetachInstanceFromBone (this: SDKInstanceClass, boneName: string) {
|
||||
this.detachInstanceFromBone(boneName);
|
||||
AttachObjectToBone (this: SDKInstanceClass, objectClass: IObjectType, boneName: string, offsetX: number, offsetY: number, offsetAngle: number) {
|
||||
const pickedInstances = objectClass.getPickedInstances();
|
||||
for (const instance of pickedInstances) {
|
||||
this.attachInstanceToBone(instance.uid, boneName, offsetX, offsetY, offsetAngle);
|
||||
}
|
||||
},
|
||||
|
||||
DetachObjectFromBone (this: SDKInstanceClass, objectClass: IObjectType, boneName: string) {
|
||||
const pickedInstances = objectClass.getPickedInstances();
|
||||
for (const instance of pickedInstances) {
|
||||
this.detachInstanceFromBoneByUid(instance.uid, boneName);
|
||||
}
|
||||
},
|
||||
|
||||
DetachAllFromBone (this: SDKInstanceClass, boneName: string) {
|
||||
this.detachAllFromBone(boneName);
|
||||
},
|
||||
|
||||
AddHandle (this: SDKInstanceClass, type: 0 | 1, name: string, radius: number, debug: boolean) {
|
||||
|
||||
@ -35,6 +35,7 @@ const spine = globalThis.spine;
|
||||
spine.Skeleton.yDown = true;
|
||||
|
||||
type BoneOverride = Partial<BoneLocal> & { mode: "game" | "local" };
|
||||
type BoneFollower = { uid: number, offsetX: number, offsetY: number, offsetAngle: number };
|
||||
|
||||
class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
propAtlas = "";
|
||||
@ -87,7 +88,7 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
|
||||
private verticesTemp = spine.Utils.newFloatArray(2 * 1024);
|
||||
|
||||
private boneFollowers = new Map<string, { uid: number, offsetX: number, offsetY: number, offsetAngle: number }>();
|
||||
private boneFollowers = new Map<string, BoneFollower[]>();
|
||||
|
||||
private bonesOverride: Map<Bone, BoneOverride> = new Map();
|
||||
|
||||
@ -819,36 +820,62 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
||||
return;
|
||||
}
|
||||
|
||||
this.boneFollowers.set(boneName, { uid, offsetX, offsetY, offsetAngle });
|
||||
const follower = { uid, offsetX, offsetY, offsetAngle };
|
||||
const followers = this.boneFollowers.get(boneName);
|
||||
if (!followers) {
|
||||
this.boneFollowers.set(boneName, [follower]);
|
||||
} else {
|
||||
followers.push(follower);
|
||||
}
|
||||
|
||||
this.isPlaying = true;
|
||||
}
|
||||
|
||||
public detachInstanceFromBone (boneName: string) {
|
||||
public detachInstanceFromBoneByUid (uid: number, boneName: string) {
|
||||
const followers = this.boneFollowers.get(boneName);
|
||||
if (!followers) return;
|
||||
|
||||
const index = followers.findIndex(f => f.uid === uid);
|
||||
if (index !== -1) {
|
||||
followers.splice(index, 1);
|
||||
if (followers.length === 0) {
|
||||
this.boneFollowers.delete(boneName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public detachAllFromBone (boneName: string) {
|
||||
this.boneFollowers.delete(boneName);
|
||||
}
|
||||
|
||||
private updateBoneFollowers (matrix: C3Matrix) {
|
||||
if (this.boneFollowers.size === 0) return;
|
||||
|
||||
for (const [boneName, follower] of this.boneFollowers) {
|
||||
for (const [boneName, followers] of this.boneFollowers) {
|
||||
const bone = this.skeleton?.findBone(boneName);
|
||||
if (!bone) continue;
|
||||
|
||||
const instance = this.runtime.getInstanceByUid(follower.uid) as IWorldInstance;
|
||||
if (!instance) continue;
|
||||
|
||||
const { x, y } = matrix.boneToGame(bone);
|
||||
const boneRotation = bone.applied.getWorldRotationX();
|
||||
|
||||
const rotationRadians = boneRotation * Math.PI / 180;
|
||||
const cos = Math.cos(rotationRadians);
|
||||
const sin = Math.sin(rotationRadians);
|
||||
const rotatedOffsetX = follower.offsetX * cos - follower.offsetY * sin;
|
||||
const rotatedOffsetY = follower.offsetX * sin + follower.offsetY * cos;
|
||||
|
||||
instance.x = x + rotatedOffsetX;
|
||||
instance.y = y + rotatedOffsetY;
|
||||
instance.angleDegrees = boneRotation + follower.offsetAngle;
|
||||
for (const follower of followers) {
|
||||
const instance = this.runtime.getInstanceByUid(follower.uid) as IWorldInstance;
|
||||
if (!instance) {
|
||||
this.detachInstanceFromBoneByUid(follower.uid, boneName);
|
||||
continue;
|
||||
}
|
||||
|
||||
const rotatedOffsetX = follower.offsetX * cos - follower.offsetY * sin;
|
||||
const rotatedOffsetY = follower.offsetX * sin + follower.offsetY * cos;
|
||||
|
||||
instance.x = x + rotatedOffsetX;
|
||||
instance.y = y + rotatedOffsetY;
|
||||
instance.angleDegrees = boneRotation + follower.offsetAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -607,10 +607,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"detach-instance-from-bone": {
|
||||
"list-name": "Detach instance from bone",
|
||||
"display-text": "Detach instance from bone {0}",
|
||||
"description": "Stop an object instance from following a bone. The instance will remain at its current position.",
|
||||
"attach-object-to-bone": {
|
||||
"list-name": "Attach object to bone",
|
||||
"display-text": "Attach {0} to bone {1} with offset ({2}, {3}, {4}°)",
|
||||
"description": "Make picked object instances follow a skeleton bone's position and rotation. The instances will automatically update their position and angle to match the bone every frame. Offsets are applied in the bone's local space (rotated with the bone).",
|
||||
"params": {
|
||||
"object": {
|
||||
"name": "Object",
|
||||
"desc": "The object type to attach (picked instances will be attached)"
|
||||
},
|
||||
"bone-name": {
|
||||
"name": "Bone name",
|
||||
"desc": "Name of the bone to follow"
|
||||
},
|
||||
"offset-x": {
|
||||
"name": "Offset X",
|
||||
"desc": "X offset from bone position (in bone's local space)"
|
||||
},
|
||||
"offset-y": {
|
||||
"name": "Offset Y",
|
||||
"desc": "Y offset from bone position (in bone's local space)"
|
||||
},
|
||||
"offset-angle": {
|
||||
"name": "Offset angle",
|
||||
"desc": "Angle offset in degrees from bone rotation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detach-object-from-bone": {
|
||||
"list-name": "Detach object from bone",
|
||||
"display-text": "Detach {0} from bone {1}",
|
||||
"description": "Stop picked object instances from following a bone. The instances will remain at their current positions.",
|
||||
"params": {
|
||||
"object": {
|
||||
"name": "Object",
|
||||
"desc": "The object type to detach (picked instances will be detached)"
|
||||
},
|
||||
"bone-name": {
|
||||
"name": "Bone name",
|
||||
"desc": "Name of the bone to detach from"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detach-all-from-bone": {
|
||||
"list-name": "Detach all from bone",
|
||||
"display-text": "Detach all instances from bone {0}",
|
||||
"description": "Stop all object instances from following a bone. The instances will remain at their current positions.",
|
||||
"params": {
|
||||
"bone-name": {
|
||||
"name": "Bone name",
|
||||
|
||||
@ -607,10 +607,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"detach-instance-from-bone": {
|
||||
"list-name": "将实例从骨骼分离",
|
||||
"display-text": "将实例从骨骼{0}分离",
|
||||
"description": "停止对象实例跟随骨骼。实例将保持在其当前位置。",
|
||||
"attach-object-to-bone": {
|
||||
"list-name": "将对象附加到骨骼",
|
||||
"display-text": "将{0}附加到骨骼{1},偏移({2}, {3}, {4}°)",
|
||||
"description": "使选中的对象实例跟随骨架骨骼的位置和旋转。实例将每帧自动更新其位置和角度以匹配骨骼。偏移量在骨骼的本地空间中应用(随骨骼旋转)。",
|
||||
"params": {
|
||||
"object": {
|
||||
"name": "对象",
|
||||
"desc": "要附加的对象类型(选中的实例将被附加)"
|
||||
},
|
||||
"bone-name": {
|
||||
"name": "骨骼名称",
|
||||
"desc": "要跟随的骨骼名称"
|
||||
},
|
||||
"offset-x": {
|
||||
"name": "X偏移",
|
||||
"desc": "距骨骼位置的X偏移(在骨骼本地空间中)"
|
||||
},
|
||||
"offset-y": {
|
||||
"name": "Y偏移",
|
||||
"desc": "距骨骼位置的Y偏移(在骨骼本地空间中)"
|
||||
},
|
||||
"offset-angle": {
|
||||
"name": "角度偏移",
|
||||
"desc": "距骨骼旋转的角度偏移(度)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detach-object-from-bone": {
|
||||
"list-name": "将对象从骨骼分离",
|
||||
"display-text": "将{0}从骨骼{1}分离",
|
||||
"description": "停止选中的对象实例跟随骨骼。实例将保持在其当前位置。",
|
||||
"params": {
|
||||
"object": {
|
||||
"name": "对象",
|
||||
"desc": "要分离的对象类型(选中的实例将被分离)"
|
||||
},
|
||||
"bone-name": {
|
||||
"name": "骨骼名称",
|
||||
"desc": "要分离的骨骼名称"
|
||||
}
|
||||
}
|
||||
},
|
||||
"detach-all-from-bone": {
|
||||
"list-name": "将所有实例从骨骼分离",
|
||||
"display-text": "将所有实例从骨骼{0}分离",
|
||||
"description": "停止所有对象实例跟随骨骼。实例将保持在其当前位置。",
|
||||
"params": {
|
||||
"bone-name": {
|
||||
"name": "骨骼名称",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user