IAnimatable Interface - coldrockgames/gml-raptor GitHub Wiki

By adding this line after event_inherited() in the create event of your object, you can make it "Animatable".

implement(IAnimatable);

Animation Automation

This powerful interface allows you to define animations that shall happen, when the object enters a specific state. Automatically.

The AnimationEntry Class

This system relies on a series of AnimationEntry instances in the animations member of IAnimatable.
You may add these instances through GML-Code (new AnimationEntry(...)) or through RichJson. Examples follow shortly.

/// @func	AnimationEntry(_name = undefined, _ianimatable = undefined)
/// @desc	Create a new animation definition, optionally directly attached
///			to a specified IAnimatable instance with a specified name
function AnimationEntry(_name = undefined, _ianimatable = undefined) constructor {
	construct(AnimationEntry);

	if (_ianimatable != undefined && _name != undefined)
		struct_set(_ianimatable.animations, _name, self);

	auto_play		= false;     // will start automatically when the named state is entered
	auto_stop		= true;	     // will stop automatically when the named state is left
	exclusive		= false;     // If true, will call animation_finish_all(self) before starting
	state			= true;	     // If true, will call add_event_state(name, state_after)
	state_after		= undefined; // return_to_state after event animation (if state==true)
	delay			= 0;	     // animation arguments
	frames			= 0;	     // animation arguments
	repeats			= 1;	     // animation arguments
	curve			= undefined; // animation arguments
	before_start	= undefined; // scriptor script (richjson) or callback (gml) to be launched
	on_started		= undefined; // scriptor script (richjson) or callback (gml) to be launched
	on_finished		= undefined; // scriptor script (richjson) or callback (gml) to be launched

}

You may define as many named animations as you like.

[!TIP] If the name of the animation matches the name of a state, and if it is set to auto_start, then the StateMachine will start this animation every time the object enters the state.

Just set your behavior (auto_start, auto_stop, exclusive) and define your animation (delay, frames and curve). If you need to add code, use the callback hooks.

Let's examine those hooks a little closer:

  • They can be either functions in GML (simply set them directly in the AnimationEntry instance)
  • They can be #script: commands of RichJson and point to Scriptor scripts.

The timing of the callbacks is as follows:

  • before_start will be invoked after the animation has been created. In your object now is a member event_anim set to point to this animation. You may use event_anim in the function or in the scriptor script.
  • on_started will be invoked with the first rendering frame of the animation (it is implemented as .add_starting_trigger on the StateMachine)
  • on_finished is a .add_finished_trigger on the StateMachine and will be invoked when the animation ends. The event_anim member is still set.

IAnimatable Functions and Members

This interface adds some functions to your object as well as some member variables.

Member Variables

Variable Type Description
animations struct All your state animations are stored in this struct
event_anim Animation The currently active state animation or undefined, if no animation is running
event_anim_data struct Add here any information you would like to have accessible in the script/callback. The receiving function may access this under the same name, event_anim_data.

Functions

/// @func	add_event_state(_name)
/// @desc	Add a pure event state to the state machine.
///		This is an empty state that will do nothing but
///		"return prev_state".
///		It's just there to launch auto_start event animations.
add_event_state = function(_name) {
/// @func	add_event_animation(_name, _animation_entry)
/// @desc	Add a new event animation.
///			If an animation with that name already exists, it is overwritten.
add_event_animation = function(_name, _animation_entry) {
/// @func	delete_event_animation(_name)
/// @desc	Removes an animation from the event animations.
///			Returns bool, whether an animation with that name existed.
delete_event_animation = function(_name) {
/// @func	get_event_animation(_name)
/// @desc	Returns the event animation with the specified name
///			or undefined, if it does not exist
get_event_animation = function(_name) {
/// @func	is_event_animation_auto_play(_name)
is_event_animation_auto_play = function(_name) {
/// @func	is_event_animation_auto_stop(_name)
is_event_animation_auto_stop = function(_name) {
/// @func	run_event_animation(_anim_name)
/// @desc	Starts the event animation.
///		Normally you do not need to calls this by yourself
///		(if the animation is set to auto_start)
///		but you may use it any time to launch a specific
///		animation that is registered as event animation.
run_event_animation = function(_anim_name) {
/// @func	update_event_animation(_state, _prev_state)
/// @desc	Update the state of animation based on a state
///		change in the state machine
///		NOTE: Normally you do not have to call this by yourself!
///		The deep integration of state machine and animation handles
///		this for you. 
///		You MAY of course call this manually when you need it.
update_event_animation = function(_state, _prev_state) {

Example: Default Idle Animation

A definition like this will make your Monster run an idle animation every time it enters the "idle" state.

"Monster::skin/Monster.Elite": {
// ... some other stats and definitions ...
  "animations": {
    "idle::#macro:animation": {
      "auto_play":     true,
      "frames":        60,
      "curve":         "#asset:acDefaultIdleAnim",
      "before_start":  "#script:game/dungeon/monster/default_animations@idle_starting",
    },
  },      
},

This will launch the "idle" animation every time the Monster enters the "idle" state.

Short and sweet. And the best thing about this is:

  • It works for all Monsters even those that haven't been created in your project yet.
  • You do not need to write a single line of code. Just make your Monsters children of the "Monster" base object.
  • You do only need to "implement" the interface once: In your base object. All children will use it.