Jump to content

Animation graph: using events


photo

Recommended Posts

We have animation graph for our simple character, it based on Unigine::AnimationTree (without any changes).

All works fine, but we need use events for realization next pattern: our character has set of idle animations, and any next idle animation must play only when previous was ended (first and last frames in all idle animations is identical). We try to use next events:

	<animation_graph>
	<!-- список idle-анимаций -->
	<combiner type="switch">
		<inputs>
			<input name="case" type="function">Source::Ongots::getIdleState</input>
		</inputs>

		<switch>
			<case value="0">
				<combiner type="animation">
	            	<inputs>
						<input name="speed" type="const" shift="0.5">30</input>
					</inputs>
					<events>
						<event on="active" callback="Source::Ongots::setAnimationState">
							<arg type="const">0</arg>
						</event>
						<event on="deactive" callback="Source::Ongots::setAnimationState">
							<arg type="const">1</arg>
						</event>

						<event on="blend_in_finish" callback="Source::Ongots::setAnimationState">
							<arg type="const">2</arg>
						</event>
						<event on="blend_out_finish" callback="Source::Ongots::setAnimationState">
							<arg type="const">3</arg>
						</event>

						<event on="blend_in_start" callback="Source::Ongots::setAnimationState">
							<arg type="const">4</arg>
						</event>
						<event on="blend_out_start" callback="Source::Ongots::setAnimationState">
							<arg type="const">5</arg>
						</event>

						<event on="update" callback="Source::Ongots::setAnimationState">
							<arg type="const">6</arg>
						</event>
					</events>
	                <animation start="0" end="-1">source/models/creature/ongots/idle_01.sanim</animation>
	            </combiner>
			</case>
			<case value="1">
				<combiner type="animation">
	            	<inputs>
						<input name="speed" type="const" shift="0.5">30</input>
					</inputs>
					<events>
						<event on="active" callback="Source::Ongots::setAnimationState">
							<arg type="const">0</arg>
						</event>
						<event on="deactive" callback="Source::Ongots::setAnimationState">
							<arg type="const">1</arg>
						</event>

						<event on="blend_in_finish" callback="Source::Ongots::setAnimationState">
							<arg type="const">2</arg>
						</event>
						<event on="blend_out_finish" callback="Source::Ongots::setAnimationState">
							<arg type="const">3</arg>
						</event>

						<event on="blend_in_start" callback="Source::Ongots::setAnimationState">
							<arg type="const">4</arg>
						</event>
						<event on="blend_out_start" callback="Source::Ongots::setAnimationState">
							<arg type="const">5</arg>
						</event>

						<event on="update" callback="Source::Ongots::setAnimationState">
							<arg type="const">6</arg>
						</event>
					</events>
	                <animation start="0" end="-1">source/models/creature/ongots/idle_02.sanim</animation>
	            </combiner>
			</case>
			<default>
				 <combiner type="null"/>
			</default>
		</switch>
	</combiner>
</animation_graph>

But in callback passed 2,3,6 - blend_in_finish, blend_out_finish, update - every frame. We thought, that every frame must pass only 6, and 2 and 3 must passed only when one animation ends and starts. What are we doing wrong? How it can be done? Thanks.

Link to comment

The problem is that in previous version of animation tree sources there was different behavior: blend_in_finish and blend_out_finish events were called once. But if we had very low fps this events would never be called at all. So it was decided to call this events every frame to be sure that it will be called at least once. We could not find better solution.

 

To detect if currently played animation is finished you could try to call ObjectMeshSkinned method getFrameTime().

To access ObjectMeshSkinned used inside character call getSkin() method.

Link to comment
  • 4 months later...

To detect if currently played animation is finished you could try to call ObjectMeshSkinned method getFrameTime().

To access ObjectMeshSkinned used inside character call getSkin() method.

ObjectMeshSkinned::getFrameTime

 

float getFrameTime()

Description

 

Returns the time value passed to the last

setFrame()

call.

Return value

 

Time value.

And what is this value? Time elapsed since last animation was changed? What is the pattern, getFrameTime() value will be reset to zero each time when animation starts to play again?

And one more question: do you have any way to determine what name (or file name) of animation currently playing?

Link to comment

The problem is that in previous version of animation tree sources there was different behavior: blend_in_finish and blend_out_finish events were called once. But if we had very low fps this events would never be called at all. So it was decided to call this events every frame to be sure that it will be called at least once. We could not find better solution.

I am quite sure that for real-world complex character animation sequences a better solution for this problem is absolutely mandatory. The current workaround just pushes the basic problem to user code level...but just my personal gut feeling

Link to comment
  • 2 weeks later...

I am quite sure that for real-world complex character animation sequences a better solution for this problem is absolutely mandatory. The current workaround just pushes the basic problem to user code level...but just my personal gut feeling

Yes, Ulf, you are right, it is not a good solution. It is planned to update current animation system in future.

Link to comment

And what is this value? Time elapsed since last animation was changed? What is the pattern, getFrameTime() value will be reset to zero each time when animation starts to play again?

And one more question: do you have any way to determine what name (or file name) of animation currently playing?

 

You can get current animation file name by calling ObjectMeshSkinned::getAnimation().

See this link: https://developer.un...tAnimation

 

Sorry, but my previous answer was incorrect. This pattern will not work for current animation tree version. Everything you can get inside character inherited from this high-level system is only animation file and frame time for the last combiner applied.

Link to comment

Ok, thanks. I'm trying to implement next pattern now: when one animation is ended I wan't to play next animation (all start-end of animations is equals, so it must looks smooth), for this I want do next: get length of current animation, calculate (and accumulate time of playing current animation), when this timie >= than length time I'll change animation, I'm trying to understand what of this data I can get from ObjectMeshSkinned, so I'm playing with next data:

ObjectMeshSkinned skin = getSkin();

string animationName = skin.getAnimation(); // current animation name, now it's clear
float frameTime = skin.getFrameTime(); // is this delta time from last blended frame (time for one frame update)?
float frameIndex = skin.getFrame(); // this is always 0, why?
float frameCount = skin.getNumFrames(); // this is all frames... in seconds?

Can I use next code:

durationIdleAnimation += frameTime;

// change random animation
if (frameTime >= frameCount)
{
_stateIdle = 1 + Math::round(::rand() * (IDLE_ANIMATION_COUNT - 1));
}

Because result looks incorrect... perhaps I use this parameters in wrong way?

Link to comment

ObjectMeshSkinned::getFrameTime() returns a previously set frame time.

 

ObjectMeshSkinned::getFrame() does not return anything, it decomposes a frame from previously set bone transformations.

Note that ObjectMeshSkinned::getFrame() is a function that returns void, that is why you always get 0.

 

ObjectMeshSkinned::getNumFrames() returns number of frames, how many of them you have in the animation file.

 

Note, that all of these functions work with currently set animation layer. So you also have to call ObjectMeshSkinned::setLayer() to be able to get information about other layers.

 

Here is example of usage from tropics (playing gull animation).

 

As you can see frame time accumulates animation time. And when animation reaches its end it becomes equal or higher than total number of frames.But if you don't reset it to zero, frame time will continue to increase. And appropriate animation frame (between 0 and number of frames) will be set.

Link to comment
Sorry I have some problems with editing posts on the forum. You can find example in tropics.cpp, lines 223-228

 

 

Ok, thanks for example... But actually I don't want to use ObjectMeshSkinned::setLayer() and ObjectMeshSkinned::setFrame() directly, because I use yours animation system with complex animation graph, so I need only one thing: in my update() method (actually in method getIdleState() - I call it in animation graph combiner switch) I want determine current animation (I can do this with ObjectMeshSkinned::getAnimation()) and I want determine when animation is ended (in this case I want change variable _stateIdle that will "move" switch combiner in different case animation).

 

I try this:

if (skin.getFrameTime() >= skin.getNumFrames())
{
_stateIdle += 1;

skin.setFrame(0.0f);
}

 

but this pattern doesn't work correct, some time skin.getFrameTime() reset automatically, event when if (skin.getFrameTime() >= skin.getNumFrames()) this is not true, I think this is happen because animation graph changes layers automatically. So now I see only one decision - hard code all animation's decorations, and calculating playing time of current animation, then this time more than length, then I change animation, it looks bad, and I afraid we will have no 100% synchronization, any suggestions?

Thanks.

Link to comment
  • 3 weeks later...

You are right here, in order to have this functionality you'll have to customize Character system (or switch to setting animation via ObjectMeshSkinned directly). Sorry, it seems there's no other way.

Link to comment
×
×
  • Create New...