• Runtimes
  • How to move bone?

Hello,

I have 2 spine projects. One contains character and second one contains vfx for this character.

I got "target" bone in on of vfx to move end position of lighting strike. It works fine in Spine I move this bone and it moves the end of lighting strike, but when I exported it to game and try to calculate this bone position everything is messed up.

I tried to get monster "x" and "y" from canvas and use comand temp1.skeleton.findBone('S3-lightning').worldToLocal({x: monsterX, y: monsterY}) , but it gives me strange positions that leads to diffrent places than monster really is.

What is "world" in this context and how I can convert monster "x" and "y" to spine local?

WorldToLocal returns random numbers with same coordinates:

Related Discussions
...

The local, parent, and world axes for translation are described here:
Tools - Spine User Guide: Translate tool

You can switch axes in Spine and look at the translation values in Spine as you move the bone around.

At runtime, if you have a world position (which is relative to the skeleton position) you need to use Bone worldToLocal to translate the world position into the local coordinates of the parent of the bone you want to move. Pseudo code:

bone = skeleton.findBone("lightning")
position = {x: 123, y: 345} // The world position where you want the lightning bone.
bone.parent.worldToLocal(position) // Changes it to the parent's local position.
bone.x = position.x
bone.y = position.y

It wouldn't make sense for a bone to be positioned relative to itself. Bones are positioned relative to their parent, so make sure you use the parent bone. Note that the parent bone will be null if the bone is the root bone.

Hey, Nate

I don't get it right.

I got a viewport that is 844x390. Spine character is {x: 87, y: 361} and from her hand, it comes lighting that needs to be targeted into an enemy that is placed {x: 422, y: 292}.

If world would be viewport. The enemy cordinates would transfer it to local with the below code.

 
const cords = vfx.skeleton.findBone("S3-lightning").worldToLocal({x: -335, y:0})
vfx.skeleton.findBone("S3-lightning-target").data.x = cords.x
vfx.skeleton.findBone("S3-lightning-target").data.y = cords.y

Does world mean in this context root bone? When I tried:

vfx.skeleton.findBone("root").worldToLocal({x: -335, y:0}) // returns {x: -335: y: 0 } 

Can I somehow calculate bone distance relative to viewport?

My take on it:

Blue dots are character/monster and vfx project roots.
Yellow dots are vfx starting point and ending.

I think vfx ending dot should be calculated by enemy.x(in that case 422) - character.x(87) - vfx.x(49) (distance from root) this value should be transfered by worldToLocal of main bone of lighting strike. Cause of lighting strike is 1288px wide by start and it doesn't show that in his bone.x this diffrence will be subtracted from worldOfLocal. I think it should be perfect ,but for some reasons it isn't.

Screenshot how it looks in game.

You've set the x-coordinate on the bone data, not the bone itself. The bone data is the raw data as read from the JSON or .skel file. It serves as a basis for initializing a bone. But once a bone is initialized at runtime, that data is (mostly) never used again.

vfx.skeleton.findBone("S3-lightning-target").data.x = cords.x

should become

vfx.skeleton.findBone("S3-lightning-target").x = cords.x

For some reason this don't work

vfx.skeleton.findBone("S3-lightning-target").x = cords.x

only changing data affects it visually.

vfx.skeleton.findBone("S3-lightning-target").data.x = cords.x

The BoneData is the setup pose. All skeleton instances shared the same BoneData, so if you change it, you'll check it for all skeleton instances. It is used when resetting to the setup pose. Also, all animations are relative to the setup pose (BoneData).

When you change x or y on the Bone, you are changing it only for that skeleton instance. Just like after applying an animation (which sets the local transforms), after you've modified a bone's local position you'll need to call Skeleton updateWorldTransform to update the world transform for all the bones (and apply constraints). See here:
Runtime Skeletons - Spine Runtimes Guide: World transforms

Rendering is done using the world transforms. If you change the local transforms and don't call updateWorldTransform, then you won't see your changes rendered. The last computed world transform will be used.

If you apply an animation after changing a Bone position and the animation keys that bone's position, then applying the animation will overwrite your position. It is common to do this every frame:
1) Apply animations.
2) Make changes to bone local transforms.
3) Call Skeleton updateWorldTransform.
4) Render the skeleton.

8 jours plus tard

Hey,

I managed to move bone directly by using bone.x and bone.y instead of bone.data.x and bone.data.y.

Had to set bone to {x:0, y:0 } in spine2d before export. For some reason bones that are already set to predefined values don't move for me in Pixi.spine that utilize yours spine runtimes. This trick managed to fix my Issues... Just for first use. Second use is random.

I tried to after first iteration:

skeleton.bones.map((bone) => bone.updateWorldTransform() 

Then apply new cordinates of Another enemy with

cords = skeleton.findBone('bone').worldToLocal(x:cordX, y: cordY)
Skeleton.findBone('targetingBone').x = cords.skeleton.x
Skeleton.findBone('targetingBone').y = cords.skeleton.y

, but this time this is completely off.

jakubdev a écrit

Had to set bone to {x:0, y:0 } in spine2d before export.

This should not be required. If you are applying an animation that keys the bone, you'll need to move your bone afterward, every frame.

In the last code you showed, is bone the parent bone of targetingBone? Also the code looks wrong for a couple reasons. Try:

var parentBone = skeleton.findBone('bone');
var cords = parentBone.worldToLocal({ x: cordX, y: cordY });
var moveBone = skeleton.findBone('targetingBone');
moveBone.x = cords.x;
moveBone.y = cords.y;

I set up once position of 'bone' and didn't had to set up it everyframe, but can't answer if its keyed as animator did Spine2d stuff.

Yes 'bone' is 'S3-Lighting' and targetingBone is 'S3-Lighting-target'.

Did change code to look more like yours without success of fixing the issue.

I managed to fix it. When I don't change 'S3-Lighting' bone position it calculates it perfectly every time. Video below:
https://drive.google.com/file/d/1DemzYFhTEFXS-gKmUDJRMt2NDmSAEDlJ/view?usp=sharing

I wrote down code that on every onComplete it reverts this value to {x: 0, y:0 } as I want to reuse once preloaded skills spritesheets of spine json and it works perfectly
Video and code below:

https://drive.google.com/file/d/1tLRzMva4WOcyxKgE71_m-rBClHudiETN/view?usp=sharing

preloadedSkills[characterData.data.name].state.onComplete = () => {
    if(animationSequence.startingPoint) {
        preloadedSkills[characterData.data.name].skeleton.findBone(animationSequence.startingPoint.bone).x = 0;
        preloadedSkills[characterData.data.name].skeleton.findBone(animationSequence.startingPoint.bone).y = 0;
    }

preloadedSkills[characterData.data.name].skeleton.bones.map((bone) => bone.updateWorldTransform());
}

What I can do to fix this issue and don't need to restart it every onComplete or even better set this position in spine2d to not calculate it here in Spine Runtimes.

jakubdev a écrit

can't answer if its keyed as animator did Spine2d stuff.

You can look at the Spine project or JSON data and check if it is keyed. Or you can move the bone every frame, after applying animations.

I can't tell what is going on in the video, not what your code is doing.

You should create a skeleton with two bones and a new Unity project that displays the skeleton, then add code to move a bone. Once you get that working and understand it, you can do the same in your more complex project. It works as I showed above.

un an plus tard

I know this is an old discussion, but I landed on this page facing a similar issue.
I'm using Phaser 3.55.2, and I exported skeleton data using Spine 3.8.
Now, this thread helped me a lot, but I was using a camera that followed the protagonist in my game, so I had to compensate for the scrollX and scrollY offset, as well as the height of the canvas. This may have been because of my setup, but I hope it can be useful for someone else facing a similar issue in the future.

Here's the code I ended up using to be able to move a bone in the Spine skeleton

const pos = new Phaser.Math.Vector2(
  cordX - this.scene.cameras.main.scrollX,
  this.scene.cameras.main.scrollY - cordX + this.scene.cameras.main.height
);

skeleton.findBone('root').worldToLocal(pos);
const bone = skeleton.findBone('theBoneToMove');
bone.x = pos.x;
bone.y = pos.y;