动画基础

In this article, I’ll take a look at what needs to be done to play generic animations on an actor. Generic as opposed to bespoke (i.e. cutscenes or other location specific animations).

I’ll start by explaining the underlying concepts of Unreal’s animation system, then going through the set up of an animated actor, using a goal driven method.

NOTE: I am assuming that you already know the animation import pipeline. The code and content have been used with the May 2012 build of UDK.

Concepts

Animation Tree

The backbone of Unreal’s animation system is the blend tree, or Animation Tree. Its role is to define how and under which circumstances animations blend together.

animtree

That is a sample AnimTree. All the end nodes (Idle_1 and the Walk_*) represent animations. Note that I said “represent”, and not “are”. We’ll see later why this makes a difference.

The AnimNodeBlend* boxes take a number of animations, and according to a logic specific to each AnimNodeBlend, pick one of these animations or blend between two (or more) of them. Most of the time, the blending depends on the value of a single variable. In the AnimTree Editor, the slider under each blend node allows you modify that value to preview the blending. Nothing you do with those sliders has an effect in game (well, in most situations). Depending on the node, that variable can be set automatically or must be set manually in code.

You may have noticed that the result of a blend is considered as an animation, and as such can be fed into another blend node. Eventually, a node links to the “Animation” input of the AnimTree (which we’ll call the root node, because that’s what it is), completing the blend tree.

The first obvious lesson to learn here, is that the closer to the root (in the hierarchy) a blend node is, the more influence it has over the overall blending.

The second lesson is much more important. As you may have noticed, the re-occuring word here is blending. It’s not (really) in the animation tree that you define when a specific animation must be played (though it obviously needs to take into account that we can play specific animations at some point). Animation trees DO NOT define an animation system, they’re merely a set of blend rules. Most of the time, it’s the code who decides what animation should be playing.

Unreal being Unreal, it becomes rapidly obvious that the AnimTree system has been primarily designed to manage the locomotion of bots in a multiplayer “old school” FPS, where there is little to no bespoke animation requirement. Which is the reason why it focusses on the transition between two animations, rather than on the animations themselves.

Animation Sets

The same reasons that decided on the design of the Animation Tree system are probably also the origin of one its greatest features.

Basically, an AnimTree is an independent thing. An AnimTree is NOT tied to a skeleton, nor it is to animations. This means that you can define the blending logic once, and apply it to different skeletons with different animations. There are two elements to achieving this feat.

The first is to only have soft references to the animations. Remember when I said that the leaves of the tree represented animations? Well that is because the only thing they are storing is an animation name, a string.

Second thing is to tell the AnimTree at runtime what animation data should be used. That is what Animation Sets are. A bunch of animations packed together. As long as the animations have the same name as what’s defined in the AnimTree, the system will know how to retrieve the data and use it.

Application

To illustrate how to implement an AnimTree for an Actor, I will lean on the following:

What we have here is an helicopter that can move in all horizontal directions, and its speed is proportional to the pressure applied to the joystick. Note that it cannot rotate. The goal of this article is to put some life on that helicopter with a few animations. We’ll proceed one feature at a time.

NOTE: I’m not posting the code to get to that point, but you can get there by using my previous articles. Also, do not ask me to send you the meshes and animations.

Setting up the Actor

Because I’m working with an actor that is controlled by the player, I’ll be extending the Pawn class. However, everything in this article applies to any subclass of Actor. The first thing we need to do is to define the mesh. To do so, we’ll add a SkeletalMeshComponent to our Pawn:


1
2
3
4
5
6
7
8
9
10
11
defaultproperties
{
    Begin Object Class=SkeletalMeshComponent Name=PawnSkeletalMeshComponent
        SkeletalMesh=SkeletalMesh'HelicopterTest.BlackHawk_skinned'
        AnimTreeTemplate=AnimTree'HelicopterTest.AnimTree_Helicopter'
        AnimSets(0)=AnimSet'HelicopterTest.Anims_BlackHawk_skinned'
    End Object
 
    Mesh=PawnSkeletalMeshComponent
    Components.Add(PawnSkeletalMeshComponent)
}


In order for our stuff to work, there are three vital properties we need to set on the Skeletal Mesh:

  • The actual mesh
  • The animation tree
  • The AnimSet

Note that the AnimSets property is an array, meaning that a SkeletalMeshComponent can have more than one AnimSet (though we we’ll stick to the basics and only work with the one).

At the moment, the animation tree isn’t impressive:

copter_tree_01

Feature 1: Directional Lean

The first thing I want to add to my helicopter is the ability to lean in the direction I’m going. There’s already a blend node that will blend between several animations depending on the direction of the actor (in it’s own space). I use that node with 4 poses (i.e. 1 frame animations), each representing the helicopter leaning to one side.

copter_tree_02

With this change, and without touching the code, I get the following result:

We can see that whenever I stop moving, the helicopter comes back into the lean forward pose, as the direction is 0 degrees when the actor is not moving. Also, regardless of how fast I go, the helicopter leans all the way in the direction.

What I’d like to do now is:

  1. Make the helicopter level when I stop moving
  2. Make it lean proportionally to its speed.

Let’s have a look at the following AnimTree:

copter_tree_03

I’ve got a new animation (which is actually just another pose) of the helicopter being level. Note the node called “Throttle Blend”. It’s an AnimNodeBlend which I’ve renamed (I’ve renamed the inputs as well). What that node does is blending between the two inputs, depending on a blend factor in the [0,1] range. 0 means we only play the first input, 1 means we only play the second input, and 0.5 means we play half and half.

That’s how I will get the proportional leaning. You may have noticed that there is an additional node called AnimNodeBlendBySpeed. That sounds like it would have done what I needed, doesn’t it? Actually it doesn’t. AnimNodeBlendBySpeed will switch between several animations according to the Actor’s velocity. There’s obviously some blending going on during the switch, but it’s not doing a proportional blend, so I have to do it myself. I’ll explain later why I do have one of these in the tree.

I said earlier that the blending value of Nodes needs to be set from gameplay code if it’s not automatically set by the engine. To do that, we need to access the blend node from code.

In the Actor class, there is an event called PostInitAnimTree, which is called whenever an animation tree is set for one of the Actor’s SkeletalMeshComponents. It’s in this function that you will save references to the blend nodes you need to modify. In our example, it looks like this:


1
2
3
4
5
6
7
8
9
10
11
var AnimNodeBlend ThrottleBlendNode;
 
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
    super.PostInitAnimTree(SkelComp);
 
    if (SkelComp == Mesh)
    {
        ThrottleBlendNode = AnimNodeBlend(Mesh.FindAnimNode('Throttle Blend'));
    }
}


As you can see, you fetch nodes by name, which is the reason why I renamed the node in the animation tree. Now that I have access to that node, I need to set its blend value. Having looked at the UT code, there isn’t really a specific place to do that, so it makes sense to just do it wherever it makes sense. In our case, I will be doing it whenever the player is moving the helicopter, i.e. in the PlayerMove function. All I need to do is to insert the following line in the function:


1
2
3
CopterPawn(Pawn).ThrottleBlendNode.SetBlendTarget(Throttle,0.5f);
//The Throttle variable is the one in which I store how much pressure the player is applying
//to the joystick, regardless of direction.


The AnimBlendNode has a function called SetBlendTarget, which pretty much does what you’d expect. It takes two parameters:

  • The blend target (i.e. how much of a blend we want between the two animations)
  • The blend time (i.e. how much time it will take to reach the blend target)

There’s one very important thing here. At least it’s important in a team project. The blend time, which is something animators need to care about, is set in code. Meaning that any tweak to blends is likely to involve at least two people (when it could involve only one).

Once all that is set, I get the following result:

{youtube url=’http://youtube.com/v/_T4kJdwj6x4′}

Much better, isn’t it? It’s not perfect though, there’s a jerkiness issue when the helicopter nears a stationary position. This is caused by the directional blend and the AnimNodeBlend not being in sync, making the helicopter lean forward (because the direction gets close to 0). This is the reason why I’ve added an AnimNodeBlendBySpeed, so I can disable the leaning below a certain speed. It’s not quite enough, but then this is a matter of tweaking, the functionality we wanted is there.

Feature 2: Additive animation

Okay, we’ve got of helicopter leaning quite nicely. To make that look a bit more realistic, it would be nice to have the rotors running. This one is quite easy and doesn’t require any additional code. Have a look at the updated AnimTree:

copter_tree_04

I’ve got an additive animation of the rotors turning, and I just need to layer that on top of my movement animations. AnimNodeAdditiveBlending does the “addition” by default, which is why I don’t need to do any coding. This yields the following result:

Feature 3: State-based animation

To take it further, I want the rotors to stay still at the beginning, slowly start, and then blend to the rotor cycle. I’ve also made it so that you can’t move the helicopter during the startup phase, but that’s irrelevant. This will allow us to look at how to play one shot animations. The AnimTree now looks like that.

copter_tree_05

The first new node is an AnimNodeBlendByProperty. This one switches between the inputs according to a variable on the owning actor. That’s quite useful if your class has a property that has a direct influence on animation, because you then don’t have to worry about the AnimTree, which is alway a good thing. You don’t even have to save a reference to that node, i’ts all automagic.

In my Pawn class, I’ve added a boolean variable called EngineStarted. The sole purpose of this variable is to cut off an entire branch of the AnimTree if the helicopter isn’t ready to fly yet.

The other new node is an AnimNodePlayCustomAnim. Its role is to allow you to override one part of the tree with a custom animation. It needs to be renamed as we need a reference to this node in code. On the AnimSequence node, I have to tick the “Cause Actor Anim End” because I want to be notified when the animation ends to switch to a state where I can move the helicopter.

The code side of this is split in two (in this case):

My PlayerController will start in a default state and tell the helicopter to play the rotor_start animation.

1
2
3
4
5
6
7
auto state CopterStarting
 {
 begin:
 
CopterPawn(Pawn).RotorSlot.PlayCustomAnim('rotor_start',1.f);
 //Don't forget to register the RotorSlot node in PostInitAnimTree.
 }

Then the helicopter will react to the end of the animation and tell the PlayerController to change state.


1
2
3
4
5
6
7
8
9
10
event OnAnimEnd(AnimNodeSequence SeqNode, float PlayedTime, float ExcessTime)
{
    super.OnAnimEnd(SeqNode, PlayedTime, ExcessTime);
 
    if (SeqNode.AnimSeqName == 'rotor_start')
    {
        EngineStarted = true; //Switching on the leaning and stuff.
        Controller.GotoState('PlayerWalking'); //Switching on locomotion. Yes, my helicopter is walking!
    }
}


Voilà!

Feature 4: Opening the doors

The last thing I want to cover is how to play on demand animations. As an example, I’d like to open the helicopter’s cargo doors whenever I press a button (see the article on input commands). In order to do this, I need an AnimNodeSlot. These are basically placeholders in the AnimTree, where the code can “inject” an animation at any moment. In my case, I want the rest of the AnimTree to still work when I open the doors. I could use an additive animation, but I’ll be using another method, just for the sake of showing something different.

copter_tree_06

DoorSlot is the AnimNodeSlot. The UDKAnimBlendBySlotActive is a neat filter node that will automatically blend in the animation from the slot if it’s playing. Will save me the trouble of handling that myself. There are a few things I need to set on that node for it to work as intended.

blend_per_bone

The animation I want to layer on top of the rest only affect the door joints, so I’m telling the filter node to only override those joints. This is called bone masking, as any animation playing on these joints would be ignored, and the slot animation would play instead. The Force Local Space Blend must be ticked as well or the animation won’t play correctly if the actor is rotated (e.g. the helicopter leaning).

Once this is done, I need to actually play the animation, and that happens in code. As usual, I need a reference to the AnimNode to play anything on it.


1
2
3
4
5
6
7
8
9
10
11
12
var AnimNodeSlot DoorSlot;
simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
{
    super.PostInitAnimTree(SkelComp);
 
    if (SkelComp == Mesh)
    {
        ThrottleBlendNode = AnimNodeBlend(Mesh.FindAnimNode('Throttle Blend'));
        RotorSlot = AnimNodePlayCustomAnim(Mesh.FindAnimNode('RotorSlot'));
        DoorSlot = AnimNodeSlot(Mesh.FindAnimNode('DoorSlot'));
    }
}


Then I just need to play the animation when the exec function is called. (That function lives in the PlayerController.)

1
2
3
4
exec function OpenDoors()
{
    CopterPawn(Pawn).DoorSlot.PlayCustomAnim('doors_open',1.f);
}

As you can see, the amoount of code involved isn’t massive, but is necessary. On the other hand, a big chunk of the system is handled by the AnimTree. As stated in the documentation, a working animation system is the result of a collaboration between a programmer and a (technical) animator. You’l also note that parts of that system cannot be tested in any other way than playing the game.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值