BVH Tools for Unity
BVH Tools for Unity let you record and export motion data from avatars or skeletons to BVH files so they can be edited with Blender or other programs. The included animation loading component makes it possible import BVH files into Unity at runtime.
Setup
The BVHRecorder component should usually run last, so that it can capture any
modifications to bone rotations made by other scripts. To achieve this, after
importing the scripts, add the BVHRecorder component at the end of the script
execution order list. You can find this list under Edit
, Project Settings
,
Script Execution Order
. Press the +
button and select the BVHRecorder
.
It should appear at the end of the list. Finally click Apply
.
Recording
The most simple way to get started is to attach the "BVH Recorder" component to an avatar. Set the "Target Avatar" field to refer to the avatar, and set a filename and path for the BVH file. Then play the scene and check the "Capturing" box. You can uncheck and check it as you like. Captured motion data will be added at the end. Once you are happy with your motion data, press the save button in the inspector panel.
All fields have tooltips, so if you want to delve deeper, please take a look at them. The component also provides a simple API. Looking at the corresponding Editor script should give something of an overview.
Rest pose
If the bones of the model being recorded do not have rotations of zero in the rest pose, the rest pose in the resulting BVH file can look very odd. As a workaround, exporting the model to VRM format using UniVRM and importing it back into unity will produce a model with zero rotation bones. A short guide about the process can be found here.
Editing
If you want to edit your file in Blender, please enable the "Blender" checkbox for both recording and loading and use the following settings during import into Blender:
Forward: Y Forward
Up: Z Up
When importing files into Blender that were recorded with Blender mode disabled, please use the following settings instead:
Forward: -Z Forward
Up: Y Up
When loading files that were exported from Blender, the Blender checkbox always has to be enabled.
Loading
To load the file back into Unity, attach the "BVH Animation Loader" component to your avatar, set it as the "Target Avatar" and enter the filename. Also check the "Auto Start" box and then play the scene. Your animation should play.
Video
You can watch a quick introduction video on the usage of these Unity components here.
License
This software is distributed under the terms of the MIT license.
About
BVH Tools for Unity was made by Emiliana for Virtual YouTuber purposes, but it can also be used for games or other applications.
API
If you want to control the import and export functionality from your own scripts, you will find all the necessary information in this section.
BVHRecorder
This component can be used to record BVH data and save it to a file.
Animator targetAvatar
(required)
This is the only required field. Set the target avatar here.
Transform rootBone
This is usually the hips or pelvis bone. It is the root bone of the skeleton. If not set, it will be automatically detected. However, setting it can be useful to only capture motion for a part of a skeleton.
List<Transform> bones
This list contains the bones for which motion data will be recorded. It can be
filled automatically using the getBones()
function.
float frameRate
This the how often bone rotations will be recorded every second, when the
capturing
flag is set to true. The frame duration written to the BVH file is
also derived from this.
string directory
This is the directory into which BVH files are written. If left empty, it will be initialized to the standard Unity persistant data path, unless the filename field contains a slash or a backslash, in which case this field will be ignored completely instead.
string filename
This field is used by the saveBVH()
function and specifies the filename of
the BVH file it will create. If no filename is given, a new one will be
generated based on a timestamp. If the file already exists, a number will be
appended.
bool overwrite
When this flag is set to true, files will be overwritten. No numbers will be appended to filenames.
bool scripted
Setting this flag will prevent the automatic initialization of this component at startup.
bool capturing
When this flag is set, motion will be captured at the interval given by
frameRate
. It is possible to pause and resume capturing by setting this
flag at any time.
bool blender
This flag changes the coordinate system to match that of Blender instead of the default BVH coordinate system. It is recommended to set this when files are going to be edited in Blender and later loaded back into Unity. It is also enabled by default.
bool enforceHumanoidBones
This makes getBones()
only include humanoid bones in the bone list. It is
mainly useful to exclude things like hair and skirt bones from the recording,
which can make the resulting file a lot bigger.
bool renameBones
Enabling this option will rename humanoid bones to their standard Unity names in the generated file.
bool catchUp
Setting this to true will allow capturing with the capturing
flag to speed
up after if the frame rate drops, to keep the duration of the captured
animation correct. Disabling this flag will ensure that at least as many
milliseconds pass after every captured frame, as one frame should require,
according to the given frameRate
.
int frameNumber
(read-only)
This field shows how many frames are currently captured. Clearing the capture will reset this to 0.
string lastSavedFile
(read-only)
This field will be set to the filename written to by the saveBVH()
function.
void getBones()
(usually required)
Unless the list of bones is set manually, this function has to be called to populate the list of bones that will be recorded.
void cleanupBones()
This function removes empty entries from the bones list. It is also called by other functions, so calling it manually is usually not necessary.
void buildSkeleton()
(required)
This function builds a spanning tree of game objects covering all selected bones. It always has to be called before capturing and after setting the bones.
void genHierarchy()
(required)
This function also has to be called before motion capture. It further prepares hierarchy information about the skeleton built by the previous function.
void captureFrame()
Call this function to record the target avatar's pose at the current frame.
Setting the capturing
flag to true will automatically call this every frame.
void clearCapture()
Using this function, all previously capture motion data can be discarded.
string genBVH()
Calling this function will return a string containing a BVH file for the captured motion data.
void saveBVH()
This function calls genBVH() and writes the string to the file specified by
the filename
field.
static Transform getRootBone (Animator avatar)
This function can be used to detect the root bone of an avatar.
static Transform getRootBone (Animator avatar, List<Transform> bones)
This function can be used to detect the root bone of an avatar, given a set of bones.
static void populateBoneMap(out Dictionary<Transform, string> boneMap, Animator targetAvatar)
This function can be used on humanoid avatars to generate a mapping from bones to standard Unity humanoid bone names.
Example
Here is a short example of how you could start the capturing process:
BVHRecorder recorder = gameObject.AddComponent<BVHRecorder>();
recorder.targetAvatar = GetComponent<Animator>();
recorder.scripted = true;
recorder.getBones();
recorder.buildSkeleton();
recorder.genHierarchy();
recorder.capturing = true;
Then, after you are done capturing your animation, you save it to a file:
recorder.capturing = false;
recorder.filename = "motion.bvh";
recorder.saveBVH();
If you want to capture another animation, just clear out the collected data. You can also skip this, if you want to add to your current animation.
recorder.clearCapture();
recorder.capturing = true;
And save it again:
recorder.capturing = false;
recorder.filename = "motion2.bvh";
recorder.saveBVH();
BVHAnimationLoader
This component allows the creation of (legacy) animation clips from BVH files. The skeleton defined in the BVH file should match that of the avatar for which it is being loaded.
Animator targetAvatar
(required)
This field specifies the avatar to which the animation should be applied.
string filename
This field specifies the filename from which parseFile()
will read the BVH
data.
bool blender
This flag has to be enabled to correctly load BVH files exported from Blender
or written by the BVHRecorder
component with the blender
flag set. When
Blender is part of the animation workflow, it is usually best to enable this
option in both BVHRecorder
and BVHAnimationLoader
. It is also enabled by
default.
float frameRate
When the flag below is set, this frame rate will override that derived from the frame time given in the BVH file.
bool respectBVHTime
When this flag is set, the frame duration will not be overridden by the value
set in frameRate
.
string clipName
This name will be given to the newly created AnimationClip
when loading. If
this field is left empty, a name will be generated automatically.
bool standardBoneNames
When this option is enabled, standard Unity humanoid bone names will be mapped to the corresponding bones of the skeleton.
bool flexibleBoneName
When this option is disabled, bone names have to match exactly.
FakeDictionary[] boneRenamingMap
If the bone names of the avatar do not match those in the file and the file doesn't use Unity's standard bone names, but the structure matches otherwise, you can define a mapping of names in this array to rename the bones to match.
public struct FakeDictionary {
public string bvhName;
public string targetName;
}
bool autoPlay
If this flag is enabled, animations will start playing as soon as they are loaded.
bool autoStart
If this flag is enabled, an animation is loaded as soon as the script starts
running. This also enables the autoPlay
flag.
Animation anim
Once an animation has been loaded, the Animation
component to which it has
been added can be accessed through this field.
AnimationClip clip
This field contains the latest loaded animation clip.
void parseFile()
or void parse(string bvhData)
(required)
First, the BVH data has to be parsed. These functions do not call any Unity API functions and can safely be called from another thread if some of the animation loading process should be done in the background.
In the case of parseFile()
, the BVH data will be loaded from the file
specified through the filename
field.
void loadAnimation()
(required)
This function turns the parsed BVH data into a (legacy) animation clip. If
the autoPlay
flag is set, it will also start playing the animation right
away.
The loaded animation will be added to an Animation
component on the target
avatar. Calling this function multiple times (e.g. with different parsed
files) will add multiple animations to the Animation
component, which
can all played by accessing it. The name will be assigned from the clipName
field or assigned automatically if it is empty.
void playAnimation()
This function plays the animation that was loaded last.
stopAnimation()
This function stops animation playing through the Animation
component.
static string getPathBetween(Transform target, Transform root, bool skipFirst, bool skipLast)
This function generates a string containing the path from one game object to
another. If the skipFirst
flag is set, the first element of the path is
discarded. If the skipLast
flag is set, the last element of the path is
discarded.
Example
It is possible to reuse this component to load multiple animations by calling one of the parsing functions and this function in sequence multiple times.
For example:
BVHAnimationLoader loader = gameObject.AddComponent<BVHAnimationLoader>();
loader.targetAvatar = GetComponent<Animator>();
loader.clipName = "anim1";
loader.filename = "anim1.bvh";
loader.parseFile();
loader.loadAnimation();
loader.clipName = "anim2";
loader.filename = "anim2.bvh";
loader.parseFile();
loader.loadAnimation();
loader.clipName = "anim3";
loader.filename = "anim3.bvh";
loader.parseFile();
loader.loadAnimation();
The loaded animations can then be played through the Animation
component:
loader.anim.Play("anim2");