- 已编辑
Unity bone rotation
I'm having a hard time figuring out the math for rotating a spine bone at runtime. My goal is to rotate the bone to point towards a target. More specifically - the bone is a pivot point that moves my character's upper body. As his target moves closer to him, he needs to rotate his upper body so he's facing his target (which in turn will make his gun be pointed at the target too).
I see that I can use bone.Rotation to rotate it, but I can't seem to figure out the math to get the correct value.
Any ideas?
More precisely (if your sprites are rather large), tilting the upper body in the direction of the target wouldn't guarantee that the gun is pointed at the target too. That'd require something extra in your calculations.
Just to have the bone rotated in relation to the target, I assume what you want is to mimick Unity's Transform.LookAt but in 2D
On Spine's side, you'll want to access the setup pose and modify that, because as their video describes, all animations are relative to the setup pose.
If I'm not mistaken, that's under the BoneData class.
If you've already found the bone in question, it should be in that bone's .Data property. That has its own set of fields for local rotation, x, y, etc.
For the math, the straightforward option is using the trigonometric ratio between the differences in x and y of the frame of reference and the target. To convert the ratio of those two lengths into an angle, you'll want the arctangent function. Since Mathf.Atan or Mathf.Atan2 spits out an angle in radians, you need to convert that to degrees then just stick that number in as a rotational offset.
It needs to be an offset because your bone might be originally pointing some way or, in Spine/Unity, since all modifiable values stored in the bones are local transforms, inheriting its parent's rotation, you kinda need to know what local rotation you originally have in your base setup.
Use atan2(bone.worldY - targetWorldY, bone.worldX - targetWorldX), convert the result to degrees, and that is the target world rotation your bone needs to point at targetWorldX, targetWorldY. You can pose your skeleton (eg with an animation), then get the world rotation for the bone. Adjust the bone's local rotation by (target world rotation - bone world rotation), then call updateWorldTransform on the bone (or updateWorldTransform on the skeleton) so the new local rotation is applied to the world rotation. The bone will always point to the target, even while the animation plays. You may want to adjust other bones in a similar way to make it look more natural. Eg:
https://github.com/EsotericSoftware/spi ... l.java#L80
Thanks Nate and Pharan - I'll definitely try that out.
As for making the rotation look natural, for now rotating the upper body should be sufficient since targets won't normally get too close to the hero. I do plan to clamp the rotation so it can't go beyond a certain point and may even have the hero switch to a different type of weapon if a target is close. Perhaps if I have more time I can experiment with rotating other bones to make it more natural.
instead of a hard clamp, you could set it to rotate 100% towards the target when the bone is near it's resting position. Then gradually fade it out so it stops rotating entirely in the end. Then use the same math to make the head also look at the target, but let it rotate a bit more, this will give you nice smooth motion and will look more natural.
Hmm I'm still having trouble with this. The problem I'm having now is every other time Update is called, the bone.WorldRotation changes - even while both the hero and the target are not moving. When I set the new bone.Rotation, it makes the bone.WorldRotation do this.
Here's what my code looks like in the Update function:
var targetRotation = Mathf.Atan2(bone.WorldY - target.transform.rotation.z, bone.WorldX - target.transform.rotation.z) * Mathf.Rad2Deg;
bone.Rotation = targetRotation - bone.WorldRotation;
gameobject.skeleton.UpdateWorldTransform();
With neither the hero or target moving, each time Update is called the bone.WorldRotation will alternate between two values: 84 and 3.5.
This is wrong:
Mathf.Atan2(bone.WorldY - target.transform.rotation.z, bone.WorldX - target.transform.rotation.z)
Do something like:
Mathf.Atan2(bone.WorldY - target.transform.position.y, bone.WorldX - target.transform.position.x)
Doh, that makes sense. Corrected the code - but the bone.WorldRotation problem still remains.
Ah. This is wrong:
bone.Rotation = targetRotation - bone.WorldRotation;
bone.WorldRotation is computed from the parent bone's world rotation and bone.Rotation. You can't make bone.Rotation depend on bone.WorldRotation, it doesn't make sense. Try:
gameobject.skeleton.UpdateWorldTransform();
bone.Rotation = targetRotation - bone.parent.WorldRotation;
gameobject.skeleton.UpdateWorldTransform();
The first UpdateWorldTransform computes bone.parent.WorldRotation. Then you change the local rotation of your bone. The second UpdateWorldTransform makes your local rotation change take affect. You can be more efficient:
gameobject.skeleton.UpdateWorldTransform();
bone.Rotation = targetRotation - bone.parent.WorldRotation;
bone.UpdateWorldTransform(gameobject.skeleton.flipX, gameobject.skeleton.flipY);
This only updates the bone you changed, but you'll need to do this for all child bones of that bone (if any). If there are child bones it is easier to just call skeleton.UpdateWorldTransform. Don't optimize premature!
Ahh ok! That works perfectly. Thank you very much - I was really stumped on this for quite some time!
Sweet!