I’m trying to create a script that makes a rotation animation using an AnimationCurve, and then use multiple copies of the script to overlay them. But to do that I need to apply rotations additively, and herein lies the issue I’m having…
When I do:
var anim = curve.Evaluate(time);
transform.rotation = Quaternion.Euler(toRot*anim);
Everything works fine, but when I do:
var animstep = curve.Evaluate(time) - curve.Evaluate(prevtime);
transform.rotation *= Quaternion.Euler(toRot*animstep);
The rotation goes wrong. It’s not out by some rounding error either. (Also for reference, doing this same style code for position works fine)
Is anyone savvy enough to explain why the second code doesn’t work, and how to change it so that it does without losing any rotational adjustments by other scripts?
Goes wrong in what way? What kind of rotation are you trying to achieve? What are the types of curve, animstep and toRot? When and where is this code running? Once? Each frame? In a coroutine?
The only thing I can say for certain as that the * operator composes quaternions. You can think of the result of A * B as applying the rotation of A then applying the rotation of B.
Sorry I didn’t make this clear in the OP:
As a specific example I’m trying to apply a rotation of the euler angle (-25, 180, 0) to a transform that starts at (-90, 180, 0). Essentially as if I were dragging the rotation values within the editor, except I’m doing it in a script.
The code is running in Update, so every frame. curve is an AnimationCurve with an EaseInOut line from 0 to 1. animstep is a float that represents the difference between the curve output of this frame vs the previous frame. toRot is the target rotation which is a Vector3.
It’s unclear what toRot indicates. Is this the target rotation you are trying to rotate towards? if so it doesn’t make sense to include it in the right side of the *= operator here… that would be adding the target rotation every frame. Very strange.
If your goal is to use an animation curve to go from a starting rotation to a target rotation you would use Quaternion.Slerp. Here’s a quick and dirty sample script that goes from the start rotation to the end rotation over 5 seconds according to an animation curve.
Euler angles are tricky and ripe with issues. I can imagine the above line may flip a sign or make the values go out of bounds (must be in -90 to +90 range). Depends on what the curve looks like and what its range is.
Praetor points you in the right direction. Animating rotations should be done by slerping (or similar Quaternion methods) because that calculates the rotation entirely within the quaternion space.
The problem is localRotation is being affected by multiple scripts. If script A rotates to targetRotation, and script B rotates by an additional specific amount, then in this case script A will overrule script B’s rotation. How do I combine them?
That’s entirely up to you. There are many possible potentially valid behaviors here and you need to decide what kind of resolution you’re looking for. What is your desired behavior here, and also why do you have multiple scripts manipulating the rotation at once?
Perhaps it’s time to step out of the world of abstract concepts and give us an explanation/screenshots/etc of what this code is really doing in terms of your game and what you’re trying to accomplish.
Simplest way is to just insert additional parented Transforms in your hierarchy and manipulate one single axis at each Transform level.
This has the added benefit that you can trivially test your rig in the editor by rotating the intended axis of each Transform by mouse and seeing that it does what you want.
Here’s an example with battleship turrets: the traverse lives “above” the elevate. See hierarchy:
So in this case I’m making a card game, and I want an animation where a card is drawn from the deck with multiple “stages”. It lifts up, flips over, then moves into the floating hand (in a position and rotation dependant on how many cards you have). I also want the stages to overlap, make multiple variants, even some with a bit of flare. This is why I want to add the rotations together. The timing will vary, and I want to make it feel natural rather than robotic.
I suppose as long as I have multiple quaternions, I don’t need multiple transforms and I can still apply the rotations like a hierarchy. For the stage 1 I can just do transform.localRotation = Quaternion.Slerp(startRot, toRot, anim)
I’m just not sure what code I need to apply for stage 2 onwards transform.localRotation *= Quaternion.Slerp(Quaternion.identity, Quaternion.Euler(0,180,0), anim)?
No, if the 3 rotations represent absolute orientations in space, you can not simply treat them as relative rotations and apply them all at once. Quaternions are always relative rotations. When a quaternion is used as an absolute orientation, it simply means it’s a relative rotation from the identity rotation, so from being aligned with the parent / worldspace. In order to have a “hierarchy” of quaternions, they would need to be relative to the end rotation of the previous one. Just like any kinematic chain.
Since you also want to affect the position, it’s probably way easier to use an actual gameobject hierarchy. It’s best to only do one transformation per gameobject hierachy. So a rotation on one local axis or one translation in one axis. That kinematic chain could be initially placed in it’s end position. To animate this chain all you would have to do is to lerp / slerp each of the changed attributed in the chain from 0 to the target orientation. The first “stages” of your animation would probably be constant. The only thing you would need to adjust (before starting the animation) are the last few chain segments ehivh would probably be a rotation, a translation and another rotation. The first rotation would specify the deviation from the default direction where the card should end up. The translation would indicate how far the card should fly towards the screen from the last fix point in the chain. The last rotation would make sure that the card is facing the camera.
This chain can be easily constructed when you have the end position and orientation as a Transform / GameObject. You use Quaternion.LookRotation from the last fix point to make it look at the target position. Now you just need to parent the target object to that rotated object and the chain is complete.
To animate this all you need to do is remember those initial (or final) local positions and rotation and lerp / slerp all the local positions and rotations from (0,0,0) / Quaternion.Identity towards the recorded positions. This could be simply done with a small script on each piece that does the lerping with its own local properties. When all those things are animated at the same time, you should get a smooth transition from the start position on the table to the end position on the screen.
IMHO the easiest solution is just to create an Animation clip and call it a day. Also why are you calculating the difference between two Curves evaluations over time? this defeats the whole purpose of a timed Curve, which gives you directly a y output for a given time t. So basically you’re turning a non cumulative function to a cumulative one, which is wrong in your use case.
But the point was that the animation should end up at different end positions if I understood the question correctly. So you usually have either a line or a grid of cards which you hold. So this card drawing animation needs to end up at different locations.
I know some card games get around this by having a generic drawing animation that lifts and rotates the card in the middle of the screen and once that’s done the card is moved linearly to the target position. In that case you don’t have a smooth transition from start to finish. In certain cases this may be even preferable as it kinda resembles how humans draw cards. You usually lift and rotate it in your hand, then you look at it and after that you sort it into your other hand at the right spot. So I agree that if the whold drawing animation is the same every time, just creating an animation clip in Unity would be the simplest solution. Especially since the AnimationWindow and the recording of animation is super simple in Unity.
Just do a lerp from the final rotation of step 2 (you can either cache it the moment you start step 2, or just use the previous target value as a starting point), to whatever target rotation you want. No need for *=
There’s also the tweening solution. I think it would be pretty easy to do it with DOTween for eg., probably no more than 3-5 lines of code, with a nice callback at the end to notify you when the card has reached its final destination.
Ok after reading all the replies and testing some things out, I’ve decided to use 2transforms. A parent transform for controlling the movement & rotation directly to the hand, and a child transform for the flip, flare and any other variance that I want to add along the way. From my testing, it looks like I can blend simple euler rotations without needing a hierarchy, but not when attempting to combine that with a rotation to a specific point.
The reason I don’t want a full hierarchy of transforms is because these transitions are rapid, and it would mean a lot of creating & destroying objects for the sake of a few animations. I do believe using maths is more efficient, correct me if I’m wrong.
Unity’s Animations don’t offer enough control over variety for one. I want to be able to randomize the animation values in subtle ways to create natural feeling variance in movement. Also having motions only in chain rather than blended looks too robotic.
DOTween also looks promising, but it’s more of an unknown to me than using multiple transforms, so I’m choosing transforms. Thanks everyone for the replies. Maybe I’ll let you know how I get on.