Importing Blender Pose Markers to Unity Animation Events

a.k.a. Blender to Unity Asset Builder (Part 2)

I didn’t plan on writing a second part to the Blender to Unity Asset Builder post, but I found myself continuing work on my asset build scripts because for whatever reason, Unity does not recognize Blender’s pose markers during FBX import, so no animation events get loaded for all the animation clips. But for a full explanation, let me rewind a bit.

Some Background

Definitions

3D animation for games consists of short clips of animation made up of keyframes that define the motions of a skeleton or armature. Short clips can be defined as “walk” cycles or “idle” loops. Furthermore, for real-time animation used in video games, “event triggers” can be attached to specific frames of the animation to let other systems know about a key point in the animation to react to. For example, events can be added to footsteps in a walk cycle, so that the audio system can trigger a particular footfall sound.

For my purposes, I needed event triggers for “attack” animations, in which a character winds up for a punch or sword stroke, and subsequently lunges forward for the strike. That strike needs a trigger, so that the character on the receiving end of the attack can play the reaction animation.

Blender’s event triggers are called Pose Markers (in the Dope Sheet/Action Editor):

pose_marker_in_blender

Whereas Unity’s event triggers are called AnimationEvents (as seen on the FBX asset’s Animation tab):

Unity_anim_events

The Problem

When exporting a Blender file to the FBX format, the Pose Markers do not get saved out with the file. This means Unity will never know about these animation events. While Unity has a way of storing these events, it’s preferable that the data comes from Blender because this is where the animation is being authored. Adding the events on the Unity side means that every time a model is modified in Blender, the animation events must also be re-added back to Unity. That’s a lot of throw-away work:bad_good_asset_workflow

Solution Hints

After a lot of researching online, I came across two good sources that were promising:

Both of these solutions are pretty much the same, but they’re just implemented slightly differently. I’m personally using Mr. Restemeier’s solution as the basis for my solution, since it uses XML as the intermediate data format, so I’ll use that as the basis of my explanation below.

Solution Overview

Here are the main steps that need to happen:

  1. Blender constructs an XML structure of the animation events.
  2. Blender exports the resulting XML file into the Unity Asset directory.
  3. Blender exports the model as an FBX file into the Unity Asset directory. (See the previous blog post.)
  4. Unity processes the XML file after processing the FBX model.

Starting with the XML-based solution, I mainly had to modify the Python and C# scripts to be usable with the latest versions of Blender and Unity. For the most part, I didn’t have to update any API calls, which surprised me. The majority of my changes had to deal more with getting those scripts to work with my own build system.

Integrating Into the Existing Asset Builder

Blender Scripts

There’s not that much that’s different from the Blender Operator script that I discussed in the previous blog post. The MyExportFBXOperator class would simply be adapted into something like a MyAnimEventOperator class. The obvious difference would be in the execute(self, context) function, of course, in that instead of exporting an FBX file, the operator would write out an XML file, and Mr. Restemeier’s solution in export_events.py provides a perfectly sound solution to this. What is of interest though, is how the Pose Markers are obtained. Here’s a Python snippet that will loop through all pose markers in each action clip in a Blender file.

for action in bpy.data.actions:
    print('action: ' + action.name)
    for marker in action.pose_markers:
        print('marker: ' + marker.name)
        print('frame: ' + (int)marker.frame)

 

Unity Scripts

The Unity scripts also did not require much editing. The file of interest is EventImporter.cs. The EventImporter class is derived from the AssetPostProcessor class, which is a class used by Unity’s build pipeline to allow any custom post processing. The function of interest is OnPostprocessModel() which reads the XML file generated by Blender and populates each animation clip’s AnimationEvents from that XML data.

The only part that gave me problems was the introduction of the EventReceiver class in that function. The EventReceiver is intended to receive the events (duh) from the AnimationEvents that fired. For instance, an AnimationEvent with the name “Stab”, will call the function

public void Stab() {}

in the EventReceiver class. Naturally, this class is very game-specific, and is only intended to serve as an example. I decided to comment out anything regarding the EventReceiver in the OnPostprocessModel() function, since the EventReceiver component is dynamically added in this function, it has a tendency to leak memory.

I also commented out this code block:

if (methodInfo != null){
...
} else {
...
}

This code populates the user-defined data for the AnimationEvent. It can be composed of a float, int, string, or object reference. It may be necessary for some projects, but I don’t have a particular need for it now. Additionally, it’s not absolutely necessary to use C# reflection (the only usage of the EventReceiver component in this function) to determine a match between the receiving function and the supplied data. Although it’s probably good programming practice, it unnecessarily ties a game-related class to a generic build pipeline solution.

After everything is working, the “Stab” event shows up in Unity’s Animation Window.

animation_window_stab_event

 

Unfortunately, I couldn’t get the event to show up when selecting the FBX asset in the Project window, but that’s okay. At least the Animation Window shows proof that it actually works.

Blender Export Dialog

To tie together all of the export functionality in Blender, I decided to write up a Dialog to invoke from a keyboard shortcut.

bl_info = {
    "name": "Export Master Menu",
    "author": "Under the Weather, LLC",
    "category": "Import-Export",
    "description": "Export Master Menu",
}

import bpy
from bpy.types import Menu, Panel, UIList
import MyExportFBXOperator
import MyExportAnimEventsOperator

# export flags
gModel = True
gTextures = True
gAnimEvents = True


class DialogOperator(bpy.types.Operator):
    bl_idname = "wm.exportmastermenu"
    bl_label = "Export Master"
     
    sTargetGameAssetPath = bpy.props.StringProperty(name="Target")
    
    bExportModel = bpy.props.BoolProperty(name="Model")
    bExportTextures = bpy.props.BoolProperty(name="Textures")
    bExportAnimEvents = bpy.props.BoolProperty(name="Anim Events")
    
 
    def invoke(self, context, event):
        global gModel, gTextures, gAnimEvents
        
        self.bExportModel = gModel
        self.bExportTextures = gTextures
        self.bExportAnimEvents = gAnimEvents
        return context.window_manager.invoke_props_dialog(self)
    
    def execute(self, context):
        print('Processing')
         if bpy.data.filepath == "":
            print('Blend file needs to be saved first.')
            return {'FINISHED'}
        
        if self.bExportModel:
            bpy.ops.wm.exportfbx('EXEC_DEFAULT', targetGameAssetPath=self.sTargetGameAssetPath, copyTextures=self.bExportTextures)
            
        if self.bExportAnimEvents:
            bpy.ops.wm.exportanimevents('EXEC_DEFAULT', targetGameAssetPath=self.sTargetGameAssetPath)
            
        print('Process complete!')
            
        return {'FINISHED'}    
    
def register():
    bpy.utils.register_class(DialogOperator)    
    

def unregister():
    bpy.utils.unregister_class(DialogOperator)

Which looks something like this:

ExportMasterDialog

Conclusion

I seriously thought I was done writing my custom asset builder the last time I wrote about it. As I was working more on my game, I was trying to avoid using Unity’s AnimationEvent system by simply capturing state changes in the Animator component’s AnimatorStateInfo. Soon enough, I found that I needed to capture events in the middle of animation clips rather than just at the end or beginning, so I dug around for solutions, and the ones I linked to above seemed to be the most promising ones. There were a few solutions in the Unity Asset store as well, but I wasn’t able to get the free one working out of the box, and the other solutions weren’t free. I think that’s about all I want to talk about in regards to the asset build pipeline for a while. It’s time to make a game!

  • Blender: 2.74
  • Unity 3D: 5.1.1f1

 

This entry was posted in Dev, Programming. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *