It’s a good question, but I think yes you have to separate them. Btw I think it’s a common solution I saw it in unreal engine too and there you can create easily transitions between them.
With Heaps it’s a little bit more tricky, I have tried it several times without luck but now finally works!
How I solved the animation blending finally…
(First of all sorry it’s a little bit long :D)
There is the h3d.anim.Transition class which probably will be the solution. I have tried it several times but I had no luck with this. Let’s check it one more time.
Based on the Transition class I have to pass 3 params: name, anim1 and anim2. Name param looks not interesting - could be anything, anim 1 and 2 should be “from” and “to” animations. Transition class extends the Aniamtions so It looks I have to play this one on the model.
Ok, soo looks easy, let’s try it:
// Create the base model
view = AssetCache.instance.getModel(config.assetGroup + ".idle");
view.scale(config.modelScale);
parent.addChild(view);
// Play the idle animation
view.playAnimation(AssetCache.instance.getAnimation(config.assetGroup + ".idle"));
// Wait 5 sec and change the animation with a transition
Timer.delay(() ->
{
trace("TRANSITION START");
var newAnim = AssetCache.instance.getAnimation(config.assetGroup + ".walk");
newAnim.onAnimEnd = () -> trace("ON ANIM END");
var animTransition = new Transition("idle-to-walk", view.currentAnimation, newAnim);
animTransition.onAnimEnd = () -> trace("ON TRANSITION END");
view.playAnimation(animTransition);
}, 5000);
Hm there is an error:
You must instanciate this animation first
But if I’m using the current animation it shouldn’t be a problem, or at least I thought. No problem than maybe it’s not possible to reuse this animation. Let’s create a new one.
var animTransition = new Transition("idle-to-walk", view.currentAnimation.createInstance(view), newAnim);
The error is the same. How does it possible?
When I call the playAnimation on the view it will create a new instance instead of using what you passed and it creates clones from the object’s properties which means the anim1 and anim2 will be not what you passed, both of them will be just clones.
public function playAnimation( a : h3d.anim.Animation ) {
return currentAnimation = a.createInstance(this);
}
Let’s make a hack here, modify the heaps source code, clone the isInstance property too in the Animation.hx’s clone function.
a.isInstance = isInstance;
Now I can revert my first modification, but I have to create an instance from the 2nd animation because it was never created.
var animTransition = new Transition("idle-to-walk", view.currentAnimation, newAnim.createInstance(view));
Great, no more errors but there is no transition between the 2 animations. It changes immediatly from the current animation to the new one just like when you simple call a new playAnimation with a new animation.
What’s next, where is the transition? Basically it was a transition but without blending. You can find one other transition in Heaps - h3d.anim.SmoothTransition.hx. It has 3 params too: anim1, anim2 and speed. Ok it looks this is what I need but what is the speed param? Based on the source code it looks this is the speed of blending but who knows what is the expected value here. Let’s change the h3d.anim.Transition to h3d.anim.SmootTransition and try it with speed: 1000, maybe it means it is 1000ms duration.
Timer.delay(() ->
{
trace("TRANSITION START");
var newAnim = AssetCache.instance.getAnimation(config.assetGroup + ".walk");
newAnim.onAnimEnd = () -> trace("ON ANIM END");
var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 1000);
animTransition.onAnimEnd = () -> trace("ON TRANSITION END");
view.playAnimation(animTransition);
}, 1000);
The result is the same, maybe there was a problem with the time, let’s check the source code.
blendFactor += st * tspeed;
if( blendFactor >= 1 ) {
blendFactor = 1;
onAnimEnd();
}
I see, blend factor is the percentage (0-1) and blending is based on the elapsed time multiply with the speed. Ok than it should be much smaller, try it with 0.01.
var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 0.01);
No luck, still no transition and one more interesting thing: There is no console log related the transition end. Check again the source code. Now I see the problem, it’s again the view.playAnimation(animTransition); It creates a new instance and during this process the onAnimEnd will be dissapeard. Based on that I have to add the onAnimEnd to view’s currentAnimation right after I call the playAnimation.
view.playAnimation(animTransition);
view.currentAnimation.onAnimEnd = () -> trace("ON TRANSITION END");
Now I have information related the transition end, I can move forward, what’s happened with the transition? Let’s play a little bit with the speed maybe there is a problem with that one. Try it with extreme small and extreme huge values. The result is the same and I just realized doesnt matter the value, the result is the same and I see the console log with the same delay, doesn’t matter is it huge or small value. What’s happening here? Add a console log into SmoothTransition’s update function.
trace(tspeed);
Hm! There is no any log. Check the code again. The SmoothTransition has NO clone function which means when I call view.playAnimation(animTransition); It creates a clone from it but the clone will be just a simple Transition not a SmoothTransition. Okkkey. Let’s create a new clone function in the SmoothTransition class.
override function clone(?a : Animation) : Animation {
var a : SmoothTransition = cast a;
if( a == null )
a = new SmoothTransition(anim1, anim2, tspeed);
super.clone(a);
return a;
}
New build, new error.
VM1278:23586 Uncaught TypeError: Cannot read property '_12' of undefined
It looks there is no tmpMatrix in the sync function. I don’t really want to understand it’s behaviour so let’s try to solve it with a simple null check.
// ...
for ( o in objects ) {
if (o.tmpMatrix == null) continue;
// ...
Amazing! Something is happening! Not perfect but now at least it looks the animation was mixed. Let’s play a little bit with the speed, increase it to 1.
var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 1);
Much better! Still not the best, There are 2 remaining issues,
1, There are a tons of trace related to on transition end.
2, The current animation jumps a little bit before it starts to blend.
Let’s start with the first problem. Based on the source code it’s normal because.
if( blendFactor >= 1 ) {
blendFactor = 1;
onAnimEnd();
}
If this is the expected behaviour it means the transition instance class has to be temporary. After the anim end I have to change the animation to the proper one. Let’s modify my onAnimEnd function.
view.currentAnimation.onAnimEnd = () -> {
trace("ON TRANSITION END");
view.playAnimation(newAnim);
});
The first problem was solved, no more unnecessary trace but there is a problem again with the play animation, similiar to the 2nd issue. The problem is because when I call a playAnimation it creates new instances and every animations current frame will be 0 again. Fortunately we can get and set that property. After a little modification here is my code:
// Create the base model
view = AssetCache.instance.getModel(config.assetGroup + ".idle");
view.scale(config.modelScale);
parent.addChild(view);
// Play the idle animation
view.playAnimation(AssetCache.instance.getAnimation(config.assetGroup + ".idle"));
// Wait 5 sec and change the animation with a transition
Timer.delay(() ->
{
var newAnim = AssetCache.instance.getAnimation(config.assetGroup + ".walk");
newAnim.onAnimEnd = () -> trace("ON ANIM END");
var lastFrame = view.currentAnimation.frame;
var animTransition = new SmoothTransition(view.currentAnimation, newAnim.createInstance(view), 1);
view.playAnimation(animTransition);
var currentTransition:SmoothTransition = cast view.currentAnimation;
currentTransition.anim1.setFrame(lastFrame);
view.currentAnimation.onAnimEnd = () -> {
var lastFrame = currentTransition.anim2.frame;
view.playAnimation(newAnim);
view.currentAnimation.setFrame(lastFrame);
};
}, 5000);
I can’t believe, it works well! Let me share the result (idle to walk than walk to attack animations)
What’s next? I found a few issues and I’m not sure it’s because I’m using this transition in a wrong way or because it was not implemented well, so I’m creating now a pull request for heaps related these changes and I will ask a little bit more info from @ncannasse maybe there is a better way.