[godot] Added SpineAnimationTrack to sequence Spine animations with AnimationPlayer, see examples/08-annimation-player

This commit is contained in:
badlogic 2022-04-24 20:05:22 +02:00
parent 9f5eaf89a8
commit a09e765c33
12 changed files with 1811 additions and 7 deletions

View File

@ -6,5 +6,4 @@
[resource]
atlas_res = ExtResource( 1 )
skeleton_file_res = ExtResource( 2 )
animations = null
skins = null
default_mix = 0.2

View File

@ -0,0 +1,6 @@
extends Node2D
onready var player = $AnimationPlayer
func _ready():
player.play("walk-run-die")

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ config_version=4
[application]
config/name="spine-godot-examples"
run/main_scene="res://examples/07-slot-node/slot-node.tscn"
run/main_scene="res://examples/08-animation-player/animation-player.tscn"
run/low_processor_mode=true
config/icon="res://icon.png"

View File

@ -0,0 +1,229 @@
#include "SpineAnimationTrack.h"
#include "core/engine.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/animation.h"
#ifdef TOOLS_ENABLED
#include "editor/plugins/animation_player_editor_plugin.h"
#endif
void SpineAnimationTrack::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_track_index", "track_index"), &SpineAnimationTrack::set_track_index);
ClassDB::bind_method(D_METHOD("get_track_index"), &SpineAnimationTrack::get_track_index);
ClassDB::bind_method(D_METHOD("set_animation_name", "animation_name"), &SpineAnimationTrack::set_animation_name);
ClassDB::bind_method(D_METHOD("get_animation_name"), &SpineAnimationTrack::get_animation_name);
ClassDB::bind_method(D_METHOD("set_loop", "loop"), &SpineAnimationTrack::set_loop);
ClassDB::bind_method(D_METHOD("get_loop"), &SpineAnimationTrack::get_loop);
ClassDB::bind_method(D_METHOD("set_animation_time", "time"), &SpineAnimationTrack::set_animation_time);
ClassDB::bind_method(D_METHOD("get_animation_time"), &SpineAnimationTrack::get_animation_time);
ClassDB::bind_method(D_METHOD("update_animation_state", "spine_sprite"), &SpineAnimationTrack::update_animation_state);
ADD_PROPERTY(PropertyInfo(Variant::INT, "track_index", PROPERTY_HINT_RANGE, "0,256,0"), "set_track_index", "get_track_index");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation_name"), "set_animation_name", "get_animation_name");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "get_loop");
#if VERSION_MAJOR > 3
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "animation_time"), "set_animation_time", "get_animation_time");
#else
ADD_PROPERTY(PropertyInfo(Variant::REAL, "animation_time"), "set_animation_time", "get_animation_time");
#endif
}
SpineAnimationTrack::SpineAnimationTrack(): track_index(-1), loop(false), animation_time(0), sprite(nullptr) {
}
void SpineAnimationTrack::set_track_index(int _track_index) {
track_index = _track_index;
}
int SpineAnimationTrack::get_track_index() {
return track_index;
}
void SpineAnimationTrack::_notification(int what) {
switch(what) {
case NOTIFICATION_PARENTED: {
sprite = Object::cast_to<SpineSprite>(get_parent());
if (sprite) {
sprite->connect("before_animation_state_update", this, "update_animation_state");
} else {
WARN_PRINT("SpineAnimationTrack parent is not a SpineSprite.");
}
NOTIFY_PROPERTY_LIST_CHANGED();
break;
}
case NOTIFICATION_READY: {
setup_animation_player();
break;
}
case NOTIFICATION_UNPARENTED: {
if (sprite) {
sprite->disconnect("before_animation_state_update", this, "update_animation_state");
}
break;
}
default:
break;
}
}
void SpineAnimationTrack::setup_animation_player() {
if (!sprite) return;
if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return;
// If we don't have a track index yet, find the highest track number used
// by existing tracks.
if (track_index < 0) {
int highest_track_number = -1;
for (int i = 0; i < sprite->get_child_count(); i++) {
auto other_track = Object::cast_to<SpineAnimationTrack>(sprite->get_child(i));
if (other_track) {
if (other_track->track_index > highest_track_number)
highest_track_number = other_track->track_index;
}
}
track_index = highest_track_number + 1;
}
// Find the animation player under the track and reset its animation. Create a new one
// if there isn't one already.
AnimationPlayer *animation_player = nullptr;
for (int i = 0; i < get_child_count(); i++) {
animation_player = Object::cast_to<AnimationPlayer>(get_child(i));
if (animation_player) {
break;
}
}
if (!animation_player) {
animation_player = memnew(AnimationPlayer);
animation_player->set_name(String("Track ") + String::num_int64(track_index));
add_child(animation_player);
animation_player->set_owner(sprite->get_owner());
} else {
List<StringName> animation_names;
animation_player->get_animation_list(&animation_names);
for (int i = 0; i < animation_name.size(); i++) {
animation_player->remove_animation(animation_names[i]);
}
}
auto skeleton_data = sprite->get_skeleton_data_res()->get_skeleton_data();
auto &animations = skeleton_data->getAnimations();
for (int i = 0; i < animations.size(); i++) {
auto &animation = animations[i];
Ref<Animation> animation_ref = create_animation(animation, false);
animation_player->add_animation(animation_ref->get_name(), animation_ref);
Ref<Animation> animation_looped_ref = create_animation(animation, true);
animation_player->add_animation(animation_looped_ref->get_name(), animation_looped_ref);
}
Ref<Animation> reset_animation_ref;
INSTANTIATE(reset_animation_ref);
reset_animation_ref->set_name("RESET");
reset_animation_ref->set_loop(true);
reset_animation_ref->set_length(0.5f);
reset_animation_ref->add_track(Animation::TYPE_VALUE);
reset_animation_ref->track_set_path(0, NodePath(".:animation_name"));
reset_animation_ref->track_insert_key(0, 0, "");
animation_player->add_animation(reset_animation_ref->get_name(), reset_animation_ref);
animation_player->add_animation("-- Empty --", reset_animation_ref);
this->animation_player = animation_player;
}
Ref<Animation> SpineAnimationTrack::create_animation(spine::Animation *animation, bool loop) {
float duration = animation->getDuration();
if (duration == 0) duration = 0.5;
Ref<Animation> animation_ref;
INSTANTIATE(animation_ref);
animation_ref->set_name(String(animation->getName().buffer()) + (loop ? "" : "_looped"));
animation_ref->set_loop(!loop);
animation_ref->set_length(duration);
animation_ref->add_track(Animation::TYPE_VALUE);
animation_ref->track_set_path(0, NodePath(".:animation_name"));
animation_ref->track_insert_key(0, 0, animation->getName().buffer());
animation_ref->add_track(Animation::TYPE_VALUE);
animation_ref->track_set_path(1, NodePath(".:animation_time"));
animation_ref->track_insert_key(1, 0, 0);
animation_ref->track_insert_key(1, duration, duration);
animation_ref->add_track(Animation::TYPE_VALUE);
animation_ref->track_set_path(2, NodePath(".:loop"));
animation_ref->track_insert_key(2, 0, !loop);
return animation_ref;
}
void SpineAnimationTrack::update_animation_state(const Variant &variant_sprite) {
sprite = Object::cast_to<SpineSprite>(variant_sprite);
if (!sprite) return;
if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return;
if (track_index < 0) return;
spine::AnimationState *animation_state = sprite->get_animation_state()->get_spine_object();
if (!animation_state) return;
spine::Skeleton *skeleton = sprite->get_skeleton()->get_spine_object();
if (!skeleton) return;
if (Engine::get_singleton()->is_editor_hint()) {
#ifdef TOOLS_ENABLED
// When the animation dock is no longer visible, reset the skeleton.
if (!AnimationPlayerEditor::singleton->is_visible_in_tree()) {
skeleton->setToSetupPose();
animation_state->clearTracks();
animation_state->setTimeScale(1);
return;
}
#endif
if (track_index == 0) skeleton->setToSetupPose();
animation_state->setTimeScale(0);
animation_state->clearTrack(track_index);
if (!EMPTY(animation_name)) {
auto entry = animation_state->setAnimation(track_index, SPINE_STRING(animation_name), loop);
entry->setMixDuration(0);
entry->setTrackTime(animation_time);
}
} else {
auto current_entry = animation_state->getCurrent(track_index);
if (current_entry) {
if (animation_name != current_entry->getAnimation()->getName().buffer() || current_entry->getLoop() != loop) {
if (!EMPTY(animation_name))
animation_state->setAnimation(track_index, SPINE_STRING(animation_name), loop);
else
animation_state->setEmptyAnimation(track_index, 0);
}
} else {
if (!EMPTY(animation_name))
animation_state->setAnimation(track_index, SPINE_STRING(animation_name), loop);
else
animation_state->setEmptyAnimation(track_index, 0);
}
}
}
void SpineAnimationTrack::set_animation_name(const String& _animation_name) {
animation_name = _animation_name;
}
String SpineAnimationTrack::get_animation_name() {
return animation_name;
}
void SpineAnimationTrack::set_loop(bool _loop) {
loop = _loop;
}
bool SpineAnimationTrack::get_loop() {
return loop;
}
void SpineAnimationTrack::set_animation_time(float _animation_time) {
animation_time = _animation_time;
}
float SpineAnimationTrack::get_animation_time() {
return animation_time;
}

View File

@ -0,0 +1,71 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated January 1, 2020. Replaces all prior versions.
*
* Copyright (c) 2013-2020, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#ifndef GODOT_SPINEANIMATIONTRACK_H
#define GODOT_SPINEANIMATIONTRACK_H
#include "SpineCommon.h"
#include "SpineSprite.h"
#include "scene/animation/animation_player.h"
#include "scene/resources/animation.h"
class SpineAnimationTrack : public Node {
GDCLASS(SpineAnimationTrack, Node)
protected:
int track_index;
String animation_name;
String last_animation_name;
bool loop;
float animation_time;
SpineSprite *sprite;
AnimationPlayer *animation_player;
static void _bind_methods();
void _notification(int what);
void setup_animation_player();
Ref<Animation> create_animation(spine::Animation *animation, bool loop);
void _on_before_world_transforms_change(const Variant& _sprite);
void update_animation_state(const Variant &variant_sprite);
public:
SpineAnimationTrack();
void set_track_index(int _track_index);
int get_track_index();
void set_animation_name(const String& _animation_name);
String get_animation_name();
void set_loop(bool _loop);
bool get_loop();
void set_animation_time (float _animation_time);
float get_animation_time();
};
#endif

View File

@ -80,6 +80,7 @@ SpineEditorPlugin::SpineEditorPlugin(EditorNode *node) {
add_import_plugin(memnew(SpineJsonResourceImportPlugin));
add_import_plugin(memnew(SpineBinaryResourceImportPlugin));
add_inspector_plugin(memnew(SpineSkeletonDataResourceInspectorPlugin));
add_inspector_plugin(memnew(SpineSpriteInspectorPlugin));
}
bool SpineSkeletonDataResourceInspectorPlugin::can_handle(Object *object) {
@ -314,4 +315,21 @@ void SpineEditorPropertyAnimationMix::update_property() {
updating = false;
}
void SpineSpriteInspectorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("button_clicked"), &SpineSpriteInspectorPlugin::button_clicked);
}
void SpineSpriteInspectorPlugin::button_clicked(const String& button_name) {
}
bool SpineSpriteInspectorPlugin::can_handle(Object* object) {
return Object::cast_to<SpineSprite>(object) != nullptr;
}
void SpineSpriteInspectorPlugin::parse_begin(Object* object) {
sprite = Object::cast_to<SpineSprite>(object);
if (!sprite) return;
if (!sprite->get_skeleton_data_res().is_valid() || !sprite->get_skeleton_data_res()->is_skeleton_data_loaded()) return;
}
#endif

View File

@ -147,8 +147,6 @@ class SpineSkeletonDataResourceInspectorPlugin: public EditorInspectorPlugin {
GDCLASS(SpineSkeletonDataResourceInspectorPlugin, EditorInspectorPlugin)
public:
SpineSkeletonDataResourceInspectorPlugin() = default;
bool can_handle(Object *object) override;
#if VERSION_MAJOR > 3
bool parse_property(Object *object, Variant::Type type, const String &path, PropertyHint hint, const String &hint_text, uint32_t usage, bool wide) override;
@ -195,6 +193,18 @@ public:
void update_property() override;
};
class SpineSpriteInspectorPlugin: public EditorInspectorPlugin {
GDCLASS(SpineSpriteInspectorPlugin, EditorInspectorPlugin)
SpineSprite *sprite;
static void _bind_methods();
void button_clicked(const String &button_name);
public:
bool can_handle(Object *object) override;
void parse_begin(Object *object) override;
};
#endif
#endif//GODOT_SPINEEDITORPLUGIN_H

View File

@ -50,6 +50,7 @@ class SpineSkeleton : public REFCOUNTED {
friend class SpineAnimation;
friend class SpineAnimationState;
friend class SpineCollisionShapeProxy;
friend class SpineAnimationTrack;
protected:
static void _bind_methods();

View File

@ -27,7 +27,7 @@ void SpineSlotNode::_notification(int what) {
_change_notify("rotation_deg");
_change_notify("scale");
} else {
WARN_PRINT("SpineSlotProxy parent is not a SpineSprite.");
WARN_PRINT("SpineSlotNode parent is not a SpineSprite.");
}
NOTIFY_PROPERTY_LIST_CHANGED();
break;

View File

@ -58,6 +58,8 @@ void SpineSprite::_bind_methods() {
ADD_SIGNAL(MethodInfo("animation_completed", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry")));
ADD_SIGNAL(MethodInfo("animation_disposed", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry")));
ADD_SIGNAL(MethodInfo("animation_event", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite"), PropertyInfo(Variant::OBJECT, "animation_state", PROPERTY_HINT_TYPE_STRING, "SpineAnimationState"), PropertyInfo(Variant::OBJECT, "track_entry", PROPERTY_HINT_TYPE_STRING, "SpineTrackEntry"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_TYPE_STRING, "SpineEvent")));
ADD_SIGNAL(MethodInfo("before_animation_state_update", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite")));
ADD_SIGNAL(MethodInfo("before_animation_state_apply", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite")));
ADD_SIGNAL(MethodInfo("before_world_transforms_change", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite")));
ADD_SIGNAL(MethodInfo("world_transforms_changed", PropertyInfo(Variant::OBJECT, "spine_sprite", PROPERTY_HINT_TYPE_STRING, "SpineSprite")));
@ -158,7 +160,6 @@ void SpineSprite::generate_meshes_for_slots(Ref<SpineSkeleton> skeleton_ref) {
mesh_instance->set_material(default_materials[spine::BlendMode_Normal]);
add_child(mesh_instance);
mesh_instance->set_owner(this);
mesh_instances.push_back(mesh_instance);
slot_nodes.add(spine::Vector<SpineSlotNode*>());
}
@ -233,10 +234,12 @@ void SpineSprite::update_skeleton(float delta) {
if (!(skeleton.is_valid() && animation_state.is_valid()) || EMPTY(mesh_instances))
return;
emit_signal("before_animation_state_update", this);
animation_state->update(delta);
if (!is_visible_in_tree())
return;
emit_signal("before_animation_state_apply", this);
animation_state->apply(skeleton);
emit_signal("before_world_transforms_change", this);
skeleton->update_world_transform();

View File

@ -35,6 +35,7 @@
#include "SpineSprite.h"
#include "SpineSkeleton.h"
#include "SpineAnimationState.h"
#include "SpineAnimationTrack.h"
#include "SpineEventData.h"
#include "SpineEvent.h"
#include "SpineTrackEntry.h"
@ -98,8 +99,10 @@ void register_spine_godot_types() {
ClassDB::register_class<SpineTransformConstraint>();
ClassDB::register_class<SpineTimeline>();
ClassDB::register_class<SpineConstant>();
ClassDB::register_class<SpineCollisionShapeProxy>();
ClassDB::register_class<SpineSlotNode>();
ClassDB::register_class<SpineAnimationTrack>();
#if VERSION_MAJOR > 3
atlas_loader.instantiate();