- Home /
The question is answered, right answer was accepted
How to rotate a bone during an animation?
I have a character with idle and walk animations. Now I want to rotate the upper body without stopping the animations. So only one bone should stop playing the animation. If I try to stop the animation with animation.Stop(); it tells me that this bone isn't playing any animations. The line of script that is supposed to be rotating the bone looks like this: transform.Rotate(0, Input.GetAxis("Mouse X") sensitivityX, 0);*. It doesn't give me any console errors. Please help or ask for further information on my problem.
I made a little script to perform manual I$$anonymous$$ during a sword swing, to hit a particular target. I performed transform rotation on the particular bone in LateUpdate() and this worked for me. Although, be advised, use of LateUpdate() should be kept to a $$anonymous$$imum for performance reasons.
Answer by Owen-Reynolds · Jun 05, 2011 at 04:03 PM
If the bone you want to not animate is at the top of a chain (suppose the lower spine,) you could try to "subtract" it with:
// path in Heirarchy to bone. Set in Start():
Transform upperSpine = transform.find("bones/lowerSpine/upperspine");
animation["Walk"].AddMixingTransform(upperSpine);
// repeat for hips
AddMixingTransform (see the docs) is a wierd one. The first one subtracts all but that bone and below. The second+ is supposed to add more bone chains (but I haven't done that.)
You upperSpine should then be completely non-animated, and "adding" method you are using should work.
A more robust solution is to just play the animation and overwrite the bone's transform. The problem is, even if the bone never moves, the animation is resetting to that position ever frame. Adding 1 degree/frame won't work any more -- rotation is always reset to 0. Felix's LateUpdate comment is correct -- animation is set in Update, bones are positioned by animation after Update quits (overridding bone rots you made in update,) and then LateUpdate has a crack at them. Try:
// instead of changing rotation, change my rotation var:
float spineSpin = 0;
spineSpin += Input.GetAxis("Mouse X") * sensitivityX;
lowerSpine.eulerAngles = new Vector3(0, spineSpin, 0);
// complete guess. Math to apply overall model tilt to the spine:
lowerSpine.rotation = transform.rotation * lowerSpine.rotation;
I am quite curious whether the solution(s) proposed by Owen work, because I seem to be having quite similar problems (as posted over here: http://answers.unity3d.com/questions/402259/how-to-combine-mecanim-and-script-based-bone-movem.html).
I've already tried to adopt both the LAteUpdate() and Add$$anonymous$$ixingTransform approach, but I am afreaid that in my case the issue seems to go further: By this I've managed to apply player/mouse controlled bone rotation over animation controlled roation. But this leads into trouble whenever I try to propagate the bone rotation changes over RPC in a network multiplayer game. In that case, the additional bone rotation (over RPC) seems to be too slow and laggy, so that the animation controlled rotation (which runs locally on the client and thus much faster) makes the model always flatter and snap back into its original position.
One thing that came to my $$anonymous$$d, concerning Owen's idea:
// complete guess. $$anonymous$$ath to apply overall model tilt to the spine: lowerSpine.rotation = transform.rotation * lowerSpine.rotation;
I wonder whether it would be even better to use the following:
lowerSpine.rotation = lowerSpine.rotation + spineSpin;
I imagine that in this approach lowerSpine.rotation would always return the exact rotation of the bone (as induced by the animation during that specific frame or point in time), and then the + spineSpin would ensure that any additive rotation is really applied additively to that.
I have some hope that this could solve my network/RPC lag issue. I'll have to try this out and post the results asap.
The math to combine rotations (Quaternions) uses the multiply symbol, just because. rotation1*rotation2 computes rotate #1, followed by #2 on local coords. So "adds" 2 to 1.
The network stuff sounds like a data problem. In my limited experience, if you're sending the "user rotation" and having the client remember the old value (not somehow resetting each frame in the hopes of fresh data,) then it should be laggy, but otherwise look the same.
Owen, you're certainly right about the multiplication of the rotation values (my math knowledge isn't very deep in that point). In my case, I am calculating euler angles ins$$anonymous$$d of Quaternions. Don't know whether this makes a difference.
Concerning the network lag issue: I should be more precise about what I am trying to do. I have a 1st person camera, which is controlled in a free mouse look way by a dedicated script. This works absolutely fine. Now I want the upper body of my player model (including the arms, hands and any gear he is holding) to align with the player's camera orientation. In other words: The mouse control is used only for steering the camera, while the player model's spine just needs to "follow" the camera. For achieving this, I am grabbing the rotation of my 1st person camera inside LateUpdate(), and then applying it to my player model's spine bone. This must be fired via RPC, so that any clones of the player on any connected client is updated visually.
This looks roughly like this (don't have the exact code available here on this PC):
var mySpineBone:Transform;
var myCamera:Transform;
function Start()
{
// the following two public variables are actually assigned during design time by drag&drop in the editor
// I am just assigning them dynamically inside the code here for a demonstration
mySpineBone = GameObject.Find("$$anonymous$$y$$anonymous$$odel/Armature/etc./etc./Spine");
myCamera = GameObject.Find("1st Person Camera");
}
function LateUpdate()
{
var rotationX:float = myCamera.rotation.eulerAngles.X;
networkView.RPC("changeBoneOrientation", RPC$$anonymous$$ode.AllBuffered, rotationX);
}
@RPC
function changeBoneOrientation(xValue:float)
{
mySpineBone.rotation.eulerAngles.X = xValue;
}
It is important to know that the player model is animated by several animations (Idle, Run, Walk etc.), each of them having an effect on the spine bone as well.
Now when I run the above code, it works absolutely smoothly on the local client: There's absolutely no visual lag between the animation based bone rotation and the (additive) mouse induced bone rotation. But in any other connected client, the model will be flapping forward and backward. It should be noted that I am testing this only in local LAN and on localhost, i.e. the connection itself is brilliantly fast (1Gbit LAN).
I can't see currently why there should be a problem with transferring the data. I rather suppose that the RPC update of the spine bone rotation is always somewhat slower than the animation itself, so that every frame, the bone is experiencing two interfering rotation updates.
I'd think should post as a fresh Q emphasizing networking. But just looking:
o recommending not to use things like Quat.Eulerangle.x= (but not real problem, just now.)
o You client has no "old value" to go back to if it doesn't get a fresh packet that frame. Suppose animation says x=0, and your RPC xValue is constant at 30. If a packet is delayed past a frame, x won't get the extra rotation that frame, so will appear to "snap back" to 0.
$$anonymous$$ake xValue a global. Still use that to set rotation, but have the RPC only copy into xValue. Now, when you lose a frame, you have the "old" xValue.
Just for better understanding: Why isn't it a good idea to set .rotation.eulerAngles.x to a new value? Thinking about it, I recall that I've read somewhare that it's always recommended to set eulerAngles to a full Vector3(x,y,z) ins$$anonymous$$d of assigning the x,y,z values separately. Is that what you meant by that? But I don't want to go too far off topic here.
$$anonymous$$ost important: You've solved it!!
You are right, it was simply the stored "old value" that was missing! By adding a few more lines to my code (declaring rotationX as a private global, overwriting its value in changeBoneRotation() and ensuring that changeBoneRotation() is invoked at least once per frame for remote clones), it works brilliantly now: No flapping anymore, and bone rotation appears smooth like silk over RPC). Thanks alot!!
Here's my full code that's working fine (the real code now, with some lines that were missing in the original one above). The new lines are marked as // new!
#pragma strict
var playerBoneTo$$anonymous$$ove:Transform;
var camera1st:Transform;
// new!
private var rotationX:float;
private var rotationY:float;
private var rotationZ:float;
function LateUpdate ()
{
if (!networkView.is$$anonymous$$ine)
{
// new!
// ensure for remote clients that the bone rotation is updated at least once each frame with the
// most recently known value.
// This avoids "bone flapping" due to network lags
changeBoneOrientation(rotationX, rotationY, rotationZ);
return;
}
if (camera1st.gameObject.activeSelf)
{
var newRotationX:float = -camera1st.localRotation.eulerAngles.z;
var newRotationY:float = -camera1st.localRotation.eulerAngles.x;
var newRotationZ:float = -camera1st.localRotation.eulerAngles.y;
changeBoneOrientation(newRotationX, newRotationY, newRotationZ);
if (networkView.is$$anonymous$$ine)
{
networkView.RPC("changeBoneOrientation", RPC$$anonymous$$ode.Others, newRotationX, newRotationY, newRotationZ);
}
}
}
@RPC
function changeBoneOrientation(newRotationX:float, newRotationY:float, newRotationZ:float)
{
// new!
rotationX = newRotationX;
rotationY = newRotationY;
rotationZ = newRotationZ;
playerBoneTo$$anonymous$$ove.localRotation.eulerAngles = new Vector3(rotationX, rotationY, rotationZ);
}
BTW: Don't bother about why I am so wildly assigning the x/y/z values of the camera to the rotation variables: I seem to have messed something up with my local/global coords, and so far couldn't find out the reason. The current "cross-assignment" (x=z, y=z, z=y) is the only way that seems to work. But that's also off topic.
Answer by fherbst · Jun 05, 2011 at 03:32 PM
You have to rotate the bone every frame. You cannot stop the animation for a single bone (as long as I know); the animation will rotate the bone every frame to the animated value, so you have to rotate the bone every single frame (possibly in LateUpdate, so it happens after the animation updates your bone rotation).
Follow this Question
Related Questions
Rigging Question: Location of Root bone 0 Answers
fbx animation beats script animation? 2 Answers
Rotate a bone with mouse input while animation is playing 0 Answers
animation & position problem 0 Answers