edited May 2016 in ORK Support
I've been using mocap to make a lot of story-specific animations for my characters to use during cutscenes, but it seems incorrect to add all of these animations I'm only going to use once to my mecanim animation controller. Is there a more correct way to play these animations during an ork event (cutscene) without filling up my state machine with one-time use animations?

Is there perhaps a way to call an animation to be played on a character from a folder full of animations? Thanks in advance!

UPDATE: to anyone who also has a similar question about what to do when you have a lot of animations, this question's answer helped me a lot. http://answers.unity3d.com/questions/354357/how-complex-should-mecanim-state-machines-be.html

ANOTHER UPDATE: aaaand Keldryn's comment below explains how things have changed since 2012. :P
Post edited by braytendo on
  • edited May 2016
    Oh, I just learned about substate machines acting as a sort of folder for the state machine, so maybe that's the direction to go in.
    Post edited by braytendo on
  • @braytendo, it was mentioned in one of the comments, but just to make sure that nobody misses it, you can now transition to a sub-state machine and not just a state contained within. Since that answer was written, Entry and Exit nodes have been added to sub-state machines, so they are actually state machines on their own now, not just "folders" for animations.

    This can help you keep your Any State -> transitions list manageable, as well as your parameters list.

    So you could do something like have a trigger called "Emote" that transitions to the "Emote-SM" sub-state machine. The Entry node can have transitions to different types of "emote" animations based on an int parameter that you set on the animator.

    I do this with my attack animations in ORK. I have the Attack trigger transition to the "Attack-SM" sub-state machine, and the Entry node checks an int parameter named "WeaponType" to determine which attack animation to play.

    Actually, I made it more complicated that this, as each weapon type has its own sub-state machine within the "Attack-SM" one; the Entry node in each checks a different parameter named "AttackType" to determine which attack animation to play for that weapon (swing right, swing left, etc).

    So if you have a large number of cutscene animations, you could use one trigger parameter combined with two int parameters to play a specific animation.
    The first int would designate the category of the cutscene animation, and the second would designate the specific animation within that category. Keep a spreadsheet of your animations along with their parameters. Your broad categories might use the values 100, 200, 300, etc; if you start getting too many animations in category 200, you could split them into 200, 210, 220, etc. That way the numbers still designate which categories are related and thus have some meaning. Use a similar system within each category, going by 100s or 10s so that you have sufficient room to add new animations later on while still giving the numbering system some meaning.

    I hope that made sense. :-)
  • I think I'm following, but could you possibly describe a quick play-by-play of how you'd set up an Ork event using this setup?
  • @baytendo, sure. Can you give me a high-level example of what you want to do in the event? Just brief descriptions or names of animations are enough.
  • edited May 2016
    3 characters involved in the scene. Their eyes and mouths are sprite based, everything else is 3D and ready for mecanim animations. All the cutscene animations I would probably want in a substate machine using the sort of system you explained. It plays out like so:

    1.Two characters, Hero and Feeble walk onto the scene.
    2. A third character Sarah executes a wave animation toward Hero and Feeble as she's walking over to them.
    3. They execute wave animations back.
    4. Sarah then executes a specific mocap animation of her talking and swaying her body and then making a hand gesture.
    5. Midway through Sarah's animation, the hero executes a specific mocap animation of him laughing and patting Feeble's back.
    Post edited by braytendo on
  • One question before I get started... do the characters have specific walk animations for the cutscene, or will they move using the same animations that they do in the game? If you do have specific walk animations for the cutscene, do they use root motion or will you need to update their positions via script (or the event)?
  • Hero and Feeble would be using their default walk animations, though I may provide a more feminine walk for Sarah so I suppose she would have a specific walk animation.

    I'd like to use root motion for all of them since I can't seem to make the walk look natural without it, but I remember reading that ORK has issues with Root motion so I guess I'd need to update their positions using the event? They need to end up standing in very specific spots during the cutscene as well.
  • edited May 2016
    Okay, it looks like you've got some generic animations (such as a character waving his/her hand) as well as possibly some character-specific ones (from the way you describe it).
    Hero and Feeble would be using their default walk animations, though I may provide a more feminine walk for Sarah so I suppose she would have a specific walk animation.
    That's easy enough to do. Once you've set up your Animator, you can create an Override Animator for Sarah (and other female characters). It uses the same states and logic as your base Animator, so you don't get the node view, just a list of animations. It defaults to the same animation clips as the Animator that it is based on, but you can replace any or all of the animations with new ones. You could just have one Animator for male characters and one for females, or you could give some characters their own distinctive movements by giving those characters their own Override Animators and using unique animation clips. You know, for when you have a huge animation budget. :-)
    I'd like to use root motion for all of them since I can't seem to make the walk look natural without it, but I remember reading that ORK has issues with Root motion so I guess I'd need to update their positions using the event? They need to end up standing in very specific spots during the cutscene as well.
    ORK's Automatic Move Animation (as set on your Combatant pages) doesn't support root motion, but all you need to do is use a different character controller instead of one of ORK's built-in controllers. Opsive's Third Person Controller uses root motion, as does ootii's Motion Controller. Or you could build your own (based on examples). Are you using direct movement (i.e. move the control stick/press WASD to move the character based on your input) or indirect movement (click a destination on the screen with the mouse pointer and your character attempts to move there using pathfinding)?

    Now back to your question...

    So let's sketch out some basic categories and some parameter ranges here. In my first response I suggested using two int parameters, one for the broad category and one for the specific animation, but I think it might be best to use a single int parameter and check it twice. You'll see what I mean in a bit.

    Emotes (hand waves, etc): 100 to 999. 100 to 199 could be hand gestures, 200 to 299 could be talking animations, etc.

    There could be a lot more "generic" animations that you want to use, so let's start the character-specific animations at 10,000:

    Hero: 10000 to 10099
    Feeble: 10100 to 10199
    Sarah: 10200 to 10299

    If you think you'll have more than 100 unique animations for each character, then use a wider range of values. :-)

    We'll add two parameters to our Animator to support these animations, a trigger parameter called "playCutsceneAnim" and an int called "cutsceneAnimID". Feel free to use different names if you don't like mine. :-)

    The next post will cover setting up your Animator.

    Post edited by Keldryn on
  • edited May 2016
    Here's what the Base Layer of your Animator might look like:

    image

    The Cutscene-SM sub-state machine is where all of your cutscene animations will go.

    Create a transition to Cutscene-SM from the Any State node in your Base Layer. There aren't any states inside it yet, so it won't give you any options as to where to transition to; it will default to the sub-state machine itself. Set it to use the playCutsceneAnim trigger parameter:

    image


    Inside Cutscene-SM

    I set up this sub-state machine with two child sub-state machines. Let's just call them SSMs, because I'm getting tired of typing "sub-state machine."

    image

    I should actually have named the first one "Emotes" or "Greetings" or something like that, but I don't feel like redoing the screen caps.

    Your first transition will be to the (Up) Base Layer node. A context menu should pop up when you connect the transition, so select your Idle state on the Base Layer. If no matching cutsceneAnimID parameter is found, it will automatically go back to the Idle state:

    image

    The other two connections demonstrate a pattern that we'll use again:

    image

    image

    Now we could just skip this intermediate step and just have the Entry node of this SSM check individual cutsceneAnimID values -- that's what I would do if I only had a couple dozen animations to handle in this part of the Animator. But if you're looking at potentially having hundreds of animations, first checking the ranges of the parameter values keeps each SSM looking clean and uncluttered. It also cuts down on the number of parameter values that any one node has to check, as this can cause performance issues.

    Inside Wave_Hello-SM

    This looks pretty similar to the first SSM. The Entry -> Exit transition is automatically there if the SSM itself transitions to an Exit node (which this one does). The Exit node of its parent doesn't connect to anything though. This is a rough draft. :-) You'll need to decide in what state characters should end up once a specific cutscene animation has been played. You might want to connect Cutscene-SM back to your default Idle, or you might want to require a new state to be set explicitly in an event.

    image

    This time, each transition specifies that cutsceneAnimID Equals a specified value:

    image

    image

    Each state should transition to Exit once it's done playing (this is what Has Exit Time does):

    image

    You should probably set the Interruption Source on these Exit transitions to Current State, Then Next State. By default, transitions cannot be interrupted, so if all of these animations transition back to Idle when finished, you might want to be able to set a new animation before the character gets back to the Idle state.

    Inside Characters-SM

    This looks much like the previous SSM. I didn't make an SSM for each character, but if I had several characters each with a dozen or more animations, I would probably split each one off into its own SSM:

    image

    The transitions are set up the same way as before:

    image

    image

    image

    The exit transitions on these are all going to be the same as above, unless you want to chain together multiple animations (in which case you Exit when the last is done).

    That's the basic structure of the Animator. You could have different Exit states for each SSM if you need to. Instead of connecting the SSM to the Exit node of its parent, connect it to the (Up) Base Layer node and select the state you want to transition to instead.


    The next post covers how you might use this in an event.
    Post edited by Keldryn on
  • edited May 2016
    Disclaimer: I mocked up the Animator (above) and this Event in their respective visual editors, but I didn't add any actual animation clips, nor did I even attempt to test anything. It's entirely possible that I missed something along the way, but the core logic and structure of these are sound.

    Here's how a Game Event that runs this cutscene might look. As I am focused on the Mecanim animations, I made liberal use of Comment nodes as placeholders for things like movement:

    image

    The general flow of this event would be:
    1. Hero and Feeble move to wherever they're supposed to be.
    2. Sarah starts walking towards them.
    3. Once she gets within 6m of them, she waves.
    4. Hero waves back at Sarah, with Feeble waving a split second later
    5. Sarah stops moving, says something to then while swaying her bodfy. Then she makes a hand gesture.
    6. At the midpoint of Sarah's hand gesture, Hero laughs and pats Feeble on the back.

    Let's look at the relevant parts of the event:

    Event Settings

    In the Event Settings, you'll want to block the Actor Move AI, Control Maps, Player Control, and probably Camera Control as well. Possibly also Move AI in general:

    image

    Come to think of it, it should probably be a Blocking Event as well so that you can't have two cutscenes running at the same time.

    You'll need to set up your actors for the event. This could be different if the cutscene is started by an Event Interaction on one of the characters.

    image
    image

    Actor 0 is the Player.
    Actor 1 is Feeble. I'm assuming that he's a party member. If he's not, see below:
    Actor 2 is Sarah. I'm assuming that she's not a party member, so we'll need to Find her in the scene.

    Sarah walks towards Hero & Feeble and waves at them

    I did a simple distance check just because it was easy. You could have it time-based, or when she enters a trigger, or when she reaches a specified waypoint. It's up to you. Whenever the criteria is met, use a Mecanim Animation node to play your animation:

    image

    For this simple example, I'm just going to use Wait nodes to allow animations time to play before proceeding. I'll touch on other solutions at the end.

    Hero & Feeble wave back

    Basically the same thing as with Sarah:

    image

    image

    I spaced them out by 0.5s so their animations aren't perfectly matched. With a custom event step or a Call Function to a simple component, you could also change the Animator speed before and after the wave animation to vary it a bit.

    EDIT: You can also attach a State Machine Behaviour (SMB) component to the "Wave_Hello" animation state and have the SMB change the speed in OnStateEnter() and change it back in OnStateExit(). You could use a random value (may not be the best choice for a cutscene), but you can also get a reference to a specific character in the scene and vary the playback speed based on who is being animated by this Animator instance. The examples in the doc use a GameObject.Find() call, but I think you should be able to get a reference to the GameObject via the Animator parameter in the OnStateEnter() function. I think this should work:


    public class VariableWaveHelloBehaviour : StateMachineBehaviour
    {
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
    // assuming the Animator component is on the root GameObject:
    var combatant = ComponentHelper.GetCombatant(animator.gameObject);

    if (combatant == null)
    return;

    var combatantName = combatant.GetName();

    switch (combatantName)
    {
    case "Hero":
    animator.speed = 1.1f;
    break;
    case "Feeble":
    animator.speed = 0.85f;
    break;
    case "Sarah":
    animator.speed = 1.02f;
    break;
    }
    // alternatively, if you don't want to hard-code names, you could check the Combatant's
    // ID, TypeID, Class ID, etc and vary the animator speed based on that. Unless you've got
    // your lists arranged such that their IDs are meaningful, you won't get anything really
    // tailored for specific characters, but it will be deterministic unless you re-order your lists.

    base.OnStateEnter(animator, stateInfo, layerIndex);
    }

    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
    animator.speed = 1.0f;
    base.OnStateExit(animator, stateInfo, layerIndex);
    }
    }




    Sarah talks and sways her body then gestures

    I gave enough time for both waves to finish playing and I'm assuming that Sarah stops walking at this point. Play her animation the same way as the others:

    image

    Followed by the hand gesture:

    image

    Now, if these two animations are only used in the context of this one cutscene (and I'm correct in interpreting these as two animations and not one), then a better approach would be to have Talking/Swaying transition directly into Hand Gesture in the Animator:

    image

    But don't allow interruptions on this transition:

    image

    This way you won't need a unique ID for the Hand Gesture animation, as you'll never be playing it directly from script or an Event.

    Midway through the gesture, Hero laughs and pats Feeble on the back

    Since you know exactly how long the gesture animation is, you could just use a Wait step of whatever the time stamp is at the midpoint of the animation. There are better ways to do this (see below). Then play the Hero's animation:

    image

    I left it out of the event, but you probably want to wait until the Hero's animation is finished playing before allowing the event to exit.

    The better way than using Wait steps

    I know what to do, but I haven't actually done it yet, so I need to spend some time actually implementing it myself before I can share how to do it. :-)

    What you will want to do is set up an Animation Event on the frame of the animation clip that you want to respond to. This way will be much more deterministic than waiting a specified number of milliseconds.

    I've been meaning to write up an ORK event step that waits for an Animation Event, but I haven't gotten around to it yet. I'll post a link to it here once I get that done. For the time being, you can use Wait steps if you don't want to get into working with Animation Events.
    Post edited by Keldryn on
  • This is absolutely wonderful! It should almost be submitted as a main tutorial, lol.

    As for using wait...unless there's some chance that a wait node might react unpredictably (like sometimes triggering the animation a little too early or late of when it's supposed to) I wouldn't be apposed to using that. Should I assume an animation event would be more consistent?
  • I started this as an edit to an above post, regarding the ranges of animation ID values, but it started getting long so I moved it down here. :-)

    If you REALLY wanted to, you could only have character-specific animations in the Animator belonging to that specific character. However, this is a lot of extra work and you lose the ability to use an Override Animator (as you can only substitute animation clips; you can't even look at the animation state machines, much less modify them).

    My first thought was to divide the animation IDs according to which character they're for. It might be a better approach to group the animation IDs according to which cutscene they're used in. Either way, you could use certain digits in the ID to encode useful data. For example, going from right-to-left:

    Two digits for the specific animation
    One or two digits to designate a specific actor in a cutscene
    Two or three digits to designate the specific cutscene
    One digit designating that this is a cutscene animation

    How many digits you use and how narrow the categories are is entirely dependent on the scope of your game and the number of animations you have.

    For example, when I see the cutsceneAnimID 1780122, I know that it is not some random, meaningless value:

    It is a cutscene animation (1xxxxxx)
    It is used in cutscene #78 (178xxxx)
    It is used by the first actor in the Game Event playing the sequence, probably the player (1xx01xx)

    To take it a step further, we could also state that values for cutscene #00 (100xxxx) are animations that are used in multiple cutscenes. Actor #00 (1xx00xx) means that the animations are used by multiple actors. If we have more than 100 animations that are used by multiple actors in multiple cutscenes, then we'll need to re-think our ranges a bit.

    It is a bit of a hassle to use IDs like this for animations, rather than bool or trigger parameters that are written in plain English, but I'm sure you don't want a list of 100+ parameters in your Animator either. Just make sure you keep track of your list of animations and their IDs in a spreadsheet, and you're good.

    This approach is likely a bit heavy-handed for very simple Animators, but it scales up very nicely, whereas adding more and more parameters and more transitions from Any State scales up rather poorly.
  • This is absolutely wonderful! It should almost be submitted as a main tutorial, lol.

    As for using wait...unless there's some chance that a wait node might react unpredictably (like sometimes triggering the animation a little too early or late of when it's supposed to) I wouldn't be apposed to using that. Should I assume an animation event would be more consistent?
    Thanks! Glad I could be of help!

    Yes, an animation event should be more consistent. I'm not sure how Wait steps work if there are performance issues and for example, the animation plays more slowly than it is supposed to.

    And speaking of Animation Events... this is not what I would call an elegant design, but I think it should work (I tested that Animation Events are received by this component, but I didn't test our your specific scenario or the "guard clauses" in the animation event handlers). It would be better if the animation event handlers in turn sent messages/events that were being listened for elsewhere instead of having an ORK Event polling to see if an animation is complete or an event has been triggered, but it should be functional for now

    Put this component on whichever characters need to be part of a cutscene:

    [RequireComponent(typeof(Animator))]
    [AddComponentMenu("Your Project Name/Cutscene Animator")]
    public class CutsceneAnimator : MonoBehaviour
    {
    private class CutsceneAnimation
    {
    public int AnimationID { get; set; }
    public bool IsComplete { get; set; }
    public string AnimationEventName { get; set; }
    public bool HasAnimationEvent { get { return string.IsNullOrEmpty(AnimationEventName); } }
    public bool HasEventTriggered { get; set; }
    }

    void Awake()
    {
    _animator = GetComponent<Animator>();
    }

    public void PlayAnimation(int cutsceneAnimID)
    {
    _currentAnimation = new CutsceneAnimation
    {
    AnimationID = cutsceneAnimID
    };
    PlayCurrentAnimation();
    }

    public void PlayAnimation(int cutsceneAnimID, string eventName)
    {
    _currentAnimation = new CutsceneAnimation
    {
    AnimationID = cutsceneAnimID,
    AnimationEventName = eventName
    };
    PlayCurrentAnimation();
    }

    private void PlayCurrentAnimation()
    {
    _animator.SetInteger("cutsceneAnimID", _currentAnimation.AnimationID);
    _animator.SetTrigger("playCutsceneAnim");
    }

    public bool CheckEventTriggered(string eventName, int cutsceneAnimID)
    {
    // always return true if the animation being queried doesn't match
    // what we are tracking, so that the ORK event doesn't get stuck in a loop
    if (_currentAnimation == null
    || _currentAnimation.AnimationID != cutsceneAnimID
    || !_currentAnimation.HasAnimationEvent
    || _currentAnimation.AnimationEventName != eventName)
    {
    return true;
    }

    return _currentAnimation.HasEventTriggered;
    }

    public void Completed(int cutsceneAnimID)
    {
    // Ensure the event matches the animation that we are monitoring here
    if ( _currentAnimation == null || _currentAnimation.AnimationID != cutsceneAnimID)
    return;

    _currentAnimation.IsComplete = true;
    }

    public void MidpointReached(int cutsceneAnimID)
    {
    // Ensure the event matches the animation that we are monitoring here
    if (_currentAnimation == null
    || _currentAnimation.AnimationID != cutsceneAnimID
    || _currentAnimation.AnimationEventName != "MidpointReached")
    return;

    _currentAnimation.HasEventTriggered = true;
    }

    private CutsceneAnimation _currentAnimation;
    private Animator _animator;
    }


    I put those guard clauses in there to help ensure that we're reacting to the correct animations.

    Instead of using the Mecanim Animation step to play these animations, use a Call Function step and specify this component:

    image

    Using this component to play the animations makes sure that we're only responding to an event that was raised by an animation played by this component.

    In your animation clip, go to the frame where you want the Hero's laughter to begin:

    image

    Click the little "Add Event" button to the left of the timeline under Events. Enter the name of the function on the CutsceneAnimator that you want to call and set the int parameter to the ID you used to transition to this animation clip:

    image

    Close the Edit Animation Event dialog (don't worry, it doesn't lose the settings) and then click Apply to save this change to the animation clip.

    Now we'll change our Game Event to wait for the animation event to be triggered:

    image

    And here is the Check Function step:

    image

    Depending on how precise your timing needs to be, you may need to reduce the length of the Wait step in the loop.

    You can use the same approach to listen for a "Completed" event on an animation as well; simply add an Animation Event on the last frame of the clip and have it call the "Completed" function on the CutsceneAnimator.

    As I said, this isn't the most elegant solution -- it was just the first thing that i tried. I'm sure I'll come up with something better later on. And again, don't yell at me if you take this exactly as I wrote it and it doesn't work, as I only tested the approach, not the full implementation. :-)
  • edited May 2016
    So far this seems like the most precise solution for triggering an animation at a certain frame of another animation. Considering I have several cutscenes where timing is key to the emotional impact of the event, I'm definitely going to make use of this. Though really it would be wonderful to find out that the wait function accounted for performance issues because that would be a much simpler setup haha.

    Also, THANK YOU SO MUCH! :D :D :D :D
    Post edited by braytendo on
  • You're welcome!

    I also find that writing these things up helps me solidify my understanding of the concept, so I find them helpful myself.

    Don't use the name "Completed" when an animation finished. I think that the function specified in the event gets called (if it exists) on every component on the game object that receives the event, and Completed might not be an uncommon method name. "AnimationComplete" is probably better, or even 'CutsceneAnimationComplete".

    The other benefit to using Animation Events is that you don't need to specify anything about the length of the clip on ORK. Thus you can use placeholder animations and just replace them when you get finalized ones. As long as you set up the event on the clip, you don't need to change anything in ORK.
Sign In or Register to comment.