mirror of
https://github.com/enduro2d/enduro2d.git
synced 2025-12-14 16:09:06 +07:00
spine events as components
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
#include "components/renderer.hpp"
|
||||
#include "components/scene.hpp"
|
||||
#include "components/spine_player.hpp"
|
||||
#include "components/spine_animation_event.hpp"
|
||||
#include "components/sprite_renderer.hpp"
|
||||
|
||||
#include "systems/flipbook_system.hpp"
|
||||
|
||||
70
headers/enduro2d/high/components/spine_animation_event.hpp
Normal file
70
headers/enduro2d/high/components/spine_animation_event.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/*******************************************************************************
|
||||
* This file is part of the "Enduro2D"
|
||||
* For conditions of distribution and use, see copyright notice in LICENSE.md
|
||||
* Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com)
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../_high.hpp"
|
||||
|
||||
#include "../factory.hpp"
|
||||
|
||||
namespace e2d
|
||||
{
|
||||
class spine_animation_event final {
|
||||
public:
|
||||
struct clear_track {
|
||||
u32 track;
|
||||
};
|
||||
|
||||
struct set_anim {
|
||||
u32 track;
|
||||
str name;
|
||||
bool loop;
|
||||
str on_complete;
|
||||
};
|
||||
|
||||
struct add_anim {
|
||||
u32 track;
|
||||
str name;
|
||||
bool loop;
|
||||
secf delay;
|
||||
str on_complete;
|
||||
};
|
||||
|
||||
struct empty_anim {
|
||||
u32 track;
|
||||
secf duration;
|
||||
secf delay;
|
||||
str on_complete;
|
||||
};
|
||||
|
||||
using commands_t = stdex::variant<
|
||||
clear_track,
|
||||
set_anim,
|
||||
add_anim,
|
||||
empty_anim>;
|
||||
public:
|
||||
spine_animation_event() = default;
|
||||
|
||||
std::vector<commands_t>& commands() noexcept { return commands_; }
|
||||
const std::vector<commands_t>& commands() const noexcept { return commands_; }
|
||||
private:
|
||||
std::vector<commands_t> commands_;
|
||||
};
|
||||
|
||||
template <>
|
||||
class factory_loader<spine_animation_event> final : factory_loader<> {
|
||||
public:
|
||||
static const char* schema_source;
|
||||
|
||||
bool operator()(
|
||||
spine_animation_event& component,
|
||||
const fill_context& ctx) const;
|
||||
|
||||
bool operator()(
|
||||
asset_dependencies& dependencies,
|
||||
const collect_context& ctx) const;
|
||||
};
|
||||
}
|
||||
@@ -15,11 +15,23 @@ struct spAnimationState;
|
||||
struct spSkeleton;
|
||||
struct spSkeletonClipping;
|
||||
struct spVertexEffect;
|
||||
struct spTrackEntry;
|
||||
struct spEvent;
|
||||
|
||||
namespace e2d
|
||||
{
|
||||
class spine_player final {
|
||||
public:
|
||||
class on_complete_event final {
|
||||
public:
|
||||
on_complete_event() = default;
|
||||
|
||||
std::vector<str>& completed() noexcept { return completed_; }
|
||||
const std::vector<str>& completed() const noexcept { return completed_; }
|
||||
private:
|
||||
std::vector<str> completed_;
|
||||
};
|
||||
|
||||
using animation_ptr = std::shared_ptr<spAnimationState>;
|
||||
using skeleton_ptr = std::shared_ptr<spSkeleton>;
|
||||
using clipping_ptr = std::shared_ptr<spSkeletonClipping>;
|
||||
@@ -37,27 +49,15 @@ namespace e2d
|
||||
|
||||
[[nodiscard]] bool has_animation(const str& name) const noexcept;
|
||||
|
||||
spine_player& set_animation(u32 track, const str& name, bool loop = false) noexcept;
|
||||
|
||||
spine_player& add_animation(u32 track, const str& name, bool loop, secf delay = secf(0.0f)) noexcept;
|
||||
spine_player& add_animation(u32 track, const str& name, secf delay = secf(0.0f)) noexcept;
|
||||
|
||||
spine_player& add_empty_animation(u32 track, secf duration, secf delay = secf(0.0f)) noexcept;
|
||||
|
||||
spine_player& clear(u32 track) noexcept;
|
||||
spine_player& clear() noexcept;
|
||||
|
||||
const animation_ptr& animation() const noexcept;
|
||||
const skeleton_ptr& skeleton() const noexcept;
|
||||
const clipping_ptr& clipper() const noexcept;
|
||||
const effect_ptr& effect() const noexcept;
|
||||
const spine_model_asset::ptr& model() const noexcept;
|
||||
private:
|
||||
animation_ptr animation_;
|
||||
spine_model_asset::ptr model_;
|
||||
skeleton_ptr skeleton_;
|
||||
clipping_ptr clipping_;
|
||||
effect_ptr effect_;
|
||||
};
|
||||
|
||||
template <>
|
||||
|
||||
@@ -15,5 +15,8 @@ namespace e2d
|
||||
spine_system();
|
||||
~spine_system() noexcept final;
|
||||
void process(ecs::registry& owner) override;
|
||||
private:
|
||||
class internal_state;
|
||||
std::unique_ptr<internal_state> state_;
|
||||
};
|
||||
}
|
||||
|
||||
24
samples/bin/library/scene_spine_prefab.json
Normal file
24
samples/bin/library/scene_spine_prefab.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"components" : {
|
||||
"scene" : {}
|
||||
},
|
||||
"children" : [{
|
||||
"prototype" : "camera_prefab.json"
|
||||
},{
|
||||
"prototype" : "spine_coin_prefab.json",
|
||||
"components" : {
|
||||
"actor" : {
|
||||
"translation" : [200,180,0],
|
||||
"scale" : 0.25
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"prototype" : "spine_raptor_prefab.json",
|
||||
"components" : {
|
||||
"actor" : {
|
||||
"translation" : [-80,-100,0],
|
||||
"scale" : 0.25
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
17
samples/bin/library/spine_coin_prefab.json
Normal file
17
samples/bin/library/spine_coin_prefab.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"components" : {
|
||||
"renderer" : {
|
||||
"materials" : ["spine_material.json"]
|
||||
},
|
||||
"spine_player" : {
|
||||
"model" : "spine_coin.json"
|
||||
},
|
||||
"spine_animation" : {
|
||||
"animations" : [{
|
||||
"track" : 0,
|
||||
"name" : "animation",
|
||||
"loop" : true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
17
samples/bin/library/spine_raptor_prefab.json
Normal file
17
samples/bin/library/spine_raptor_prefab.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"components" : {
|
||||
"renderer" : {
|
||||
"materials" : ["spine_material.json"]
|
||||
},
|
||||
"spine_player" : {
|
||||
"model" : "spine_raptor.json"
|
||||
},
|
||||
"spine_animation" : {
|
||||
"animations" : [{
|
||||
"track" : 0,
|
||||
"name" : "walk",
|
||||
"loop" : true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,6 @@ namespace
|
||||
{
|
||||
class game_system final : public ecs::system {
|
||||
public:
|
||||
game_system(gobject_iptr raptor)
|
||||
: raptor_gobj_(raptor) {}
|
||||
|
||||
~game_system() noexcept override {}
|
||||
|
||||
void process(ecs::registry& owner) override {
|
||||
E2D_UNUSED(owner);
|
||||
const keyboard& k = the<input>().keyboard();
|
||||
@@ -33,33 +28,39 @@ namespace
|
||||
}
|
||||
|
||||
// use keys R, J, G to start animations
|
||||
if ( raptor_gobj_ ) {
|
||||
if ( k.is_key_just_pressed(keyboard_key::r) ) {
|
||||
auto player = raptor_gobj_->get_component<spine_player>();
|
||||
if ( player ) {
|
||||
(*player).set_animation(0, "roar")
|
||||
.add_animation(0, "walk", true);
|
||||
}
|
||||
}
|
||||
if ( k.is_key_just_pressed(keyboard_key::j) ) {
|
||||
auto player = raptor_gobj_->get_component<spine_player>();
|
||||
if ( player ) {
|
||||
(*player).set_animation(0, "jump")
|
||||
.add_animation(0, "walk", true);
|
||||
}
|
||||
}
|
||||
if ( k.is_key_just_pressed(keyboard_key::g) ) {
|
||||
auto player = raptor_gobj_->get_component<spine_player>();
|
||||
if ( player ) {
|
||||
(*player).set_animation(1, "gun-grab")
|
||||
.add_animation(1, "gun-holster", secf(3.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool roar = k.is_key_just_pressed(keyboard_key::r);
|
||||
const bool jump = k.is_key_just_pressed(keyboard_key::j);
|
||||
const bool gun_grab = k.is_key_just_pressed(keyboard_key::g);
|
||||
|
||||
private:
|
||||
gobject_iptr raptor_gobj_;
|
||||
if ( roar || jump || gun_grab ) {
|
||||
owner.for_each_component<spine_player>(
|
||||
[&owner, roar, jump, gun_grab] (ecs::entity_id e, const spine_player& player) {
|
||||
if ( !player.has_animation("walk") ) {
|
||||
return;
|
||||
}
|
||||
auto& events = ecs::entity(owner, e).assign_component<spine_animation_event>();
|
||||
if ( roar ) {
|
||||
events.commands().push_back(spine_animation_event::set_anim{0, "roar", false, "1"});
|
||||
} else if ( jump ) {
|
||||
events.commands().push_back(spine_animation_event::set_anim{0, "jump", false, "1"});
|
||||
} else if ( gun_grab ) {
|
||||
events.commands().push_back(spine_animation_event::set_anim{1, "gun-grab", false, ""});
|
||||
events.commands().push_back(spine_animation_event::add_anim{1, "gun-holster", false, secf(3.0f), ""});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
owner.for_joined_components<spine_player::on_complete_event, spine_player>(
|
||||
[&owner](ecs::entity_id e, const spine_player::on_complete_event& events, spine_player& player) {
|
||||
auto& new_events = ecs::entity(owner, e).assign_component<spine_animation_event>();
|
||||
|
||||
for ( auto& ev : events.completed() ) {
|
||||
if ( ev == "1" ) {
|
||||
new_events.commands().push_back(spine_animation_event::add_anim{0, "walk", true, secf(), ""});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class camera_system final : public ecs::system {
|
||||
@@ -86,47 +87,11 @@ namespace
|
||||
}
|
||||
private:
|
||||
bool create_scene() {
|
||||
auto spine_raptor = the<library>().load_asset<spine_model_asset>("spine_raptor.json");
|
||||
auto spine_coin = the<library>().load_asset<spine_model_asset>("spine_coin.json");
|
||||
auto spine_mat = the<library>().load_asset<material_asset>("spine_material.json");
|
||||
|
||||
if ( !spine_raptor || !spine_coin || !spine_mat ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto scene_i = the<world>().instantiate();
|
||||
|
||||
scene_i->entity_filler()
|
||||
.component<scene>()
|
||||
.component<actor>(node::create(scene_i));
|
||||
|
||||
node_iptr scene_r = scene_i->get_component<actor>().get().node();
|
||||
|
||||
auto coin_i = the<world>().instantiate();
|
||||
coin_i->entity_filler()
|
||||
.component<actor>(node::create(coin_i, scene_r))
|
||||
.component<renderer>(renderer()
|
||||
.materials({spine_mat}))
|
||||
.component<spine_player>(spine_player(spine_coin)
|
||||
.set_animation(0, "animation", true));
|
||||
|
||||
node_iptr coin_n = coin_i->get_component<actor>().get().node();
|
||||
coin_n->scale(v3f(0.25f));
|
||||
coin_n->translation(v3f{200.0f, 180.0f, 0.0f});
|
||||
|
||||
raptor_gobj_ = the<world>().instantiate();
|
||||
raptor_gobj_->entity_filler()
|
||||
.component<actor>(node::create(raptor_gobj_, scene_r))
|
||||
.component<renderer>(renderer()
|
||||
.materials({spine_mat}))
|
||||
.component<spine_player>(spine_player(spine_raptor)
|
||||
.set_animation(0, "walk", true));
|
||||
|
||||
node_iptr raptor_n = raptor_gobj_->get_component<actor>().get().node();
|
||||
raptor_n->scale(v3f(0.25f));
|
||||
raptor_n->translation(v3f{-80.f, -100.f, 0.0f});
|
||||
|
||||
return true;
|
||||
auto scene_prefab_res = the<library>().load_asset<prefab_asset>("scene_spine_prefab.json");
|
||||
auto scene_go = scene_prefab_res
|
||||
? the<world>().instantiate(scene_prefab_res->content())
|
||||
: nullptr;
|
||||
return !!scene_go;
|
||||
}
|
||||
|
||||
bool create_camera() {
|
||||
@@ -140,13 +105,10 @@ namespace
|
||||
|
||||
bool create_systems() {
|
||||
ecs::registry_filler(the<world>().registry())
|
||||
.system<game_system>(world::priority_update, raptor_gobj_)
|
||||
.system<game_system>(world::priority_update)
|
||||
.system<camera_system>(world::priority_pre_render);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
gobject_iptr raptor_gobj_;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
94
sources/enduro2d/high/components/spine_animation_event.cpp
Normal file
94
sources/enduro2d/high/components/spine_animation_event.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/*******************************************************************************
|
||||
* This file is part of the "Enduro2D"
|
||||
* For conditions of distribution and use, see copyright notice in LICENSE.md
|
||||
* Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com)
|
||||
******************************************************************************/
|
||||
|
||||
#include <enduro2d/high/components/spine_animation_event.hpp>
|
||||
|
||||
namespace e2d
|
||||
{
|
||||
const char* factory_loader<spine_animation_event>::schema_source = R"json({
|
||||
"type" : "object",
|
||||
"required" : [],
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"animations" : {
|
||||
"type" : "array",
|
||||
"items" : { "$ref": "#/definitions/spine_animation" }
|
||||
}
|
||||
},
|
||||
"definitions" : {
|
||||
"spine_animation" : {
|
||||
"type" : "object",
|
||||
"required" : [ "track", "name" ],
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"track" : { "type" : "integer", "minimum" : 0, "maximum": 8 },
|
||||
"name" : { "$ref": "#/common_definitions/name" },
|
||||
"loop" : { "type" : "boolean" },
|
||||
"delay" : { "type" : "number" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})json";
|
||||
|
||||
bool factory_loader<spine_animation_event>::operator()(
|
||||
spine_animation_event& component,
|
||||
const fill_context& ctx) const
|
||||
{
|
||||
if ( ctx.root.HasMember("animations") ) {
|
||||
const auto& animations_json = ctx.root["animations"];
|
||||
E2D_ASSERT(animations_json.IsArray());
|
||||
|
||||
for ( rapidjson::SizeType i = 0; i < animations_json.Size(); ++i ) {
|
||||
E2D_ASSERT(animations_json[i].IsObject());
|
||||
const auto& item_json = animations_json[i];
|
||||
|
||||
E2D_ASSERT(item_json.HasMember("track") && item_json.HasMember("name"));
|
||||
u32 track = 0;
|
||||
secf delay;
|
||||
bool loop = false;
|
||||
const char* name = "";
|
||||
|
||||
if ( item_json.HasMember("name") ) {
|
||||
name = item_json["name"].GetString();
|
||||
}
|
||||
|
||||
if ( item_json.HasMember("track") ) {
|
||||
if ( !json_utils::try_parse_value(item_json["track"], track) ) {
|
||||
the<debug>().error("SPINE_ANIMATION: Incorrect formatting of 'animations.track' property");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( item_json.HasMember("delay") ) {
|
||||
if ( !json_utils::try_parse_value(item_json["delay"], delay.value) ) {
|
||||
the<debug>().error("SPINE_ANIMATION: Incorrect formatting of 'animations.delay' property");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( item_json.HasMember("loop") ) {
|
||||
if ( !json_utils::try_parse_value(item_json["loop"], loop) ) {
|
||||
the<debug>().error("SPINE_ANIMATION: Incorrect formatting of 'animations.loop' property");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
component.commands().emplace_back(spine_animation_event::add_anim{
|
||||
track, name, loop, delay, ""});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool factory_loader<spine_animation_event>::operator()(
|
||||
asset_dependencies& dependencies,
|
||||
const collect_context& ctx) const
|
||||
{
|
||||
E2D_UNUSED(dependencies, ctx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,6 @@
|
||||
#include <enduro2d/high/components/spine_player.hpp>
|
||||
#include <spine/spine.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
// spine will allocate storage for each track, so keep number of track as small as possible
|
||||
constexpr e2d::u32 max_track_count = 8;
|
||||
}
|
||||
|
||||
namespace e2d
|
||||
{
|
||||
spine_player::spine_player(const spine_model_asset::ptr& model) {
|
||||
@@ -32,6 +26,7 @@ namespace e2d
|
||||
animation_ = animation_ptr(
|
||||
spAnimationState_create(model_->content().animation().get()),
|
||||
spAnimationState_dispose);
|
||||
E2D_ASSERT(animation_); // TODO exception?
|
||||
} else {
|
||||
animation_.reset();
|
||||
}
|
||||
@@ -42,7 +37,7 @@ namespace e2d
|
||||
spSkeleton_setSkinByName(skeleton_.get(), value.empty() ? nullptr : value.c_str());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
spine_player& spine_player::attachment(const str& slot, const str& name) noexcept {
|
||||
E2D_ASSERT(!slot.empty());
|
||||
E2D_ASSERT(!name.empty());
|
||||
@@ -51,7 +46,7 @@ namespace e2d
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
const spine_player::skeleton_ptr& spine_player::skeleton() const noexcept {
|
||||
return skeleton_;
|
||||
}
|
||||
@@ -60,10 +55,6 @@ namespace e2d
|
||||
return clipping_;
|
||||
}
|
||||
|
||||
const spine_player::effect_ptr& spine_player::effect() const noexcept {
|
||||
return effect_;
|
||||
}
|
||||
|
||||
spine_player& spine_player::time_scale(float value) noexcept {
|
||||
E2D_ASSERT(animation_);
|
||||
animation_->timeScale = value;
|
||||
@@ -80,65 +71,11 @@ namespace e2d
|
||||
return false;
|
||||
}
|
||||
spAnimation* anim = spSkeletonData_findAnimation(
|
||||
model_->content().skeleton().operator->(),
|
||||
model_->content().skeleton().get(),
|
||||
name.c_str());
|
||||
return anim != nullptr;
|
||||
}
|
||||
|
||||
spine_player& spine_player::set_animation(u32 track, const str& name, bool loop) noexcept {
|
||||
E2D_ASSERT(model_ && animation_);
|
||||
E2D_ASSERT(track < max_track_count);
|
||||
spAnimation* anim = spSkeletonData_findAnimation(
|
||||
model_->content().skeleton().operator->(),
|
||||
name.c_str());
|
||||
|
||||
if ( !anim ) {
|
||||
the<debug>().error("SPINE_PLAYER: animation '%0' is not found", name);
|
||||
return *this;
|
||||
}
|
||||
spAnimationState_setAnimation(animation_.get(), track, anim, loop);
|
||||
return *this;
|
||||
}
|
||||
|
||||
spine_player& spine_player::add_animation(u32 track, const str& name, secf delay) noexcept {
|
||||
return add_animation(track, name, false, delay);
|
||||
}
|
||||
|
||||
spine_player& spine_player::add_animation(u32 track, const str& name, bool loop, secf delay) noexcept {
|
||||
E2D_ASSERT(model_ && animation_);
|
||||
E2D_ASSERT(track < max_track_count);
|
||||
spAnimation* anim = spSkeletonData_findAnimation(
|
||||
model_->content().skeleton().operator->(),
|
||||
name.c_str());
|
||||
|
||||
if ( !anim ) {
|
||||
the<debug>().error("SPINE_PLAYER: animation '%0' is not found", name);
|
||||
return *this;
|
||||
}
|
||||
spAnimationState_addAnimation(animation_.get(), track, anim, loop, delay.value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
spine_player& spine_player::add_empty_animation(u32 track, secf duration, secf delay) noexcept {
|
||||
E2D_ASSERT(animation_);
|
||||
E2D_ASSERT(track < max_track_count);
|
||||
spAnimationState_addEmptyAnimation(animation_.get(), track, duration.value, delay.value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
spine_player& spine_player::clear(u32 track) noexcept {
|
||||
E2D_ASSERT(animation_);
|
||||
E2D_ASSERT(track < max_track_count);
|
||||
spAnimationState_clearTrack(animation_.get(), track);
|
||||
return *this;
|
||||
}
|
||||
|
||||
spine_player& spine_player::clear() noexcept {
|
||||
E2D_ASSERT(animation_);
|
||||
spAnimationState_clearTracks(animation_.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
const spine_player::animation_ptr& spine_player::animation() const noexcept {
|
||||
return animation_;
|
||||
}
|
||||
@@ -160,24 +97,9 @@ namespace e2d
|
||||
"attachments" : {
|
||||
"type" : "array",
|
||||
"items" : { "$ref": "#/definitions/spine_attachment" }
|
||||
},
|
||||
"animations" : {
|
||||
"type" : "array",
|
||||
"items" : { "$ref": "#/definitions/spine_animation" }
|
||||
}
|
||||
},
|
||||
"definitions" : {
|
||||
"spine_animation" : {
|
||||
"type" : "object",
|
||||
"required" : [ "track", "name" ],
|
||||
"additionalProperties" : false,
|
||||
"properties" : {
|
||||
"track" : { "type" : "integer", "minimum" : 0, "maximum": 8 },
|
||||
"name" : { "$ref": "#/common_definitions/name" },
|
||||
"loop" : { "type" : "boolean" },
|
||||
"delay" : { "type" : "number" }
|
||||
}
|
||||
},
|
||||
"spine_attachment" : {
|
||||
"type" : "object",
|
||||
"required" : [ "slot", "name" ],
|
||||
@@ -241,49 +163,6 @@ namespace e2d
|
||||
}
|
||||
}
|
||||
|
||||
if ( ctx.root.HasMember("animations") ) {
|
||||
const auto& animations_json = ctx.root["animations"];
|
||||
E2D_ASSERT(animations_json.IsArray());
|
||||
|
||||
for ( rapidjson::SizeType i = 0; i < animations_json.Size(); ++i ) {
|
||||
E2D_ASSERT(animations_json[i].IsObject());
|
||||
const auto& item_json = animations_json[i];
|
||||
|
||||
E2D_ASSERT(item_json.HasMember("track") && item_json.HasMember("name"));
|
||||
u32 track = 0;
|
||||
secf delay;
|
||||
bool loop = false;
|
||||
const char* name = "";
|
||||
|
||||
if ( item_json.HasMember("name") ) {
|
||||
name = item_json["name"].GetString();
|
||||
}
|
||||
|
||||
if ( item_json.HasMember("track") ) {
|
||||
if ( !json_utils::try_parse_value(item_json["track"], track) ) {
|
||||
the<debug>().error("SPINE_PLAYER: Incorrect formatting of 'animations.track' property");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( item_json.HasMember("delay") ) {
|
||||
if ( !json_utils::try_parse_value(item_json["delay"], delay.value) ) {
|
||||
the<debug>().error("SPINE_PLAYER: Incorrect formatting of 'animations.delay' property");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( item_json.HasMember("loop") ) {
|
||||
if ( !json_utils::try_parse_value(item_json["loop"], loop) ) {
|
||||
the<debug>().error("SPINE_PLAYER: Incorrect formatting of 'animations.loop' property");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
component.add_animation(track, name, loop, delay);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <enduro2d/high/components/renderer.hpp>
|
||||
#include <enduro2d/high/components/scene.hpp>
|
||||
#include <enduro2d/high/components/spine_player.hpp>
|
||||
#include <enduro2d/high/components/spine_animation_event.hpp>
|
||||
#include <enduro2d/high/components/sprite_renderer.hpp>
|
||||
|
||||
#include <enduro2d/high/systems/flipbook_system.hpp>
|
||||
@@ -142,6 +143,7 @@ namespace e2d
|
||||
.register_component<renderer>("renderer")
|
||||
.register_component<scene>("scene")
|
||||
.register_component<spine_player>("spine_player")
|
||||
.register_component<spine_animation_event>("spine_animation")
|
||||
.register_component<sprite_renderer>("sprite_renderer");
|
||||
safe_module_initialize<library>(params.library_root(), the<deferrer>());
|
||||
safe_module_initialize<world>();
|
||||
|
||||
@@ -264,9 +264,8 @@ namespace e2d::render_system_impl
|
||||
return;
|
||||
}
|
||||
|
||||
spSkeleton* skeleton = spine_r.skeleton().operator->();
|
||||
spSkeletonClipping* clipper = spine_r.clipper().operator->();
|
||||
spVertexEffect* effect = spine_r.effect().operator->();
|
||||
spSkeleton* skeleton = spine_r.skeleton().get();
|
||||
spSkeletonClipping* clipper = spine_r.clipper().get();
|
||||
const material_asset::ptr& src_mat = node_r.materials().front();
|
||||
const bool use_premultiplied_alpha = spine_r.model()->content().premultiplied_alpha();
|
||||
|
||||
@@ -279,10 +278,6 @@ namespace e2d::render_system_impl
|
||||
if ( skeleton->color.a == 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( effect ) {
|
||||
effect->begin(effect, skeleton);
|
||||
}
|
||||
|
||||
const m4f& sm = node->world_matrix();
|
||||
|
||||
@@ -431,10 +426,6 @@ namespace e2d::render_system_impl
|
||||
}
|
||||
|
||||
spSkeletonClipping_clipEnd2(clipper);
|
||||
|
||||
if ( effect ) {
|
||||
effect->end(effect);
|
||||
}
|
||||
|
||||
property_cache_.clear();
|
||||
}
|
||||
|
||||
@@ -7,26 +7,170 @@
|
||||
#include <enduro2d/high/systems/spine_system.hpp>
|
||||
|
||||
#include <enduro2d/high/components/spine_player.hpp>
|
||||
#include <enduro2d/high/components/spine_animation_event.hpp>
|
||||
|
||||
#include <spine/AnimationState.h>
|
||||
#include <spine/Skeleton.h>
|
||||
#include <spine/spine.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
// spine will allocate storage for each track, so keep number of track as small as possible
|
||||
constexpr e2d::u32 max_track_count = 8;
|
||||
}
|
||||
|
||||
namespace e2d
|
||||
{
|
||||
spine_system::spine_system() {}
|
||||
//
|
||||
// internal_state
|
||||
//
|
||||
|
||||
spine_system::~spine_system() noexcept {
|
||||
spAnimationState_disposeStatics();
|
||||
class spine_system::internal_state final {
|
||||
public:
|
||||
void clear_events(ecs::registry& owner) const;
|
||||
void process_new_animations(ecs::registry& owner);
|
||||
void update_animations(ecs::registry& owner);
|
||||
|
||||
void add_event(spTrackEntry* entry, const str& ev_name);
|
||||
private:
|
||||
struct animation_visitor_;
|
||||
static void event_listener_(spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event);
|
||||
private:
|
||||
flat_map<const spTrackEntry*, str> anim_events_;
|
||||
ecs::registry* owner_ = nullptr;
|
||||
};
|
||||
|
||||
//
|
||||
// animation_visitor_
|
||||
//
|
||||
|
||||
struct spine_system::internal_state::animation_visitor_ {
|
||||
animation_visitor_(
|
||||
spSkeletonData* skeleton,
|
||||
spAnimationState* anim_state,
|
||||
internal_state& state)
|
||||
: skeleton_(skeleton)
|
||||
, anim_state_(anim_state)
|
||||
, state_(state) {}
|
||||
|
||||
void operator()(const spine_animation_event::clear_track& cmd) const noexcept {
|
||||
E2D_ASSERT(cmd.track < max_track_count);
|
||||
spAnimationState_clearTrack(anim_state_, cmd.track);
|
||||
}
|
||||
|
||||
void operator()(const spine_animation_event::set_anim& cmd) const noexcept {
|
||||
E2D_ASSERT(cmd.track < max_track_count);
|
||||
|
||||
spAnimation* anim = spSkeletonData_findAnimation(skeleton_, cmd.name.c_str());
|
||||
if ( !anim ) {
|
||||
the<debug>().error("SPINE_PLAYER: animation '%0' is not found", cmd.name);
|
||||
return;
|
||||
}
|
||||
spTrackEntry* entry = spAnimationState_setAnimation(anim_state_, cmd.track, anim, cmd.loop);
|
||||
if ( entry && !cmd.on_complete.empty() ) {
|
||||
state_.add_event(entry, cmd.on_complete);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const spine_animation_event::add_anim& cmd) const noexcept {
|
||||
E2D_ASSERT(cmd.track < max_track_count);
|
||||
|
||||
spAnimation* anim = spSkeletonData_findAnimation(skeleton_, cmd.name.c_str());
|
||||
if ( !anim ) {
|
||||
the<debug>().error("SPINE_PLAYER: animation '%0' is not found", cmd.name);
|
||||
return;
|
||||
}
|
||||
spTrackEntry* entry = spAnimationState_addAnimation(anim_state_, cmd.track, anim, cmd.loop, cmd.delay.value);
|
||||
if ( entry && !cmd.on_complete.empty() ) {
|
||||
state_.add_event(entry, cmd.on_complete);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const spine_animation_event::empty_anim& cmd) const noexcept {
|
||||
E2D_ASSERT(cmd.track < max_track_count);
|
||||
spTrackEntry* entry = spAnimationState_addEmptyAnimation(anim_state_, cmd.track, cmd.duration.value, cmd.delay.value);
|
||||
if ( entry && !cmd.on_complete.empty() ) {
|
||||
state_.add_event(entry, cmd.on_complete);
|
||||
}
|
||||
}
|
||||
private:
|
||||
spSkeletonData* skeleton_;
|
||||
spAnimationState* anim_state_;
|
||||
internal_state& state_;
|
||||
};
|
||||
|
||||
void spine_system::internal_state::add_event(spTrackEntry* entry, const str& ev_name) {
|
||||
anim_events_.insert_or_assign(entry, ev_name);
|
||||
entry->listener = &event_listener_;
|
||||
entry->userData = this;
|
||||
}
|
||||
|
||||
void spine_system::internal_state::event_listener_(spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event) {
|
||||
if ( !entry || !entry->userData || type == SP_ANIMATION_DISPOSE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_state* self = reinterpret_cast<internal_state*>(entry->userData);
|
||||
auto entry_to_event = self->anim_events_.find(entry);
|
||||
if ( entry_to_event == self->anim_events_.end() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs::entity_id id(size_t(state->userData));
|
||||
ecs::entity ent(*self->owner_, id);
|
||||
|
||||
switch ( type ) {
|
||||
case SP_ANIMATION_COMPLETE: {
|
||||
auto* comp = ent.find_component<spine_player::on_complete_event>();
|
||||
if ( !comp ) {
|
||||
comp = &ent.assign_component<spine_player::on_complete_event>();
|
||||
}
|
||||
comp->completed().push_back(std::move(entry_to_event->second));
|
||||
|
||||
self->anim_events_.erase(entry_to_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void spine_system::internal_state::clear_events(ecs::registry& owner) const {
|
||||
owner.remove_all_components<spine_player::on_complete_event>();
|
||||
}
|
||||
|
||||
void spine_system::process(ecs::registry& owner) {
|
||||
float dt = the<engine>().delta_time();
|
||||
owner.for_each_component<spine_player>([dt](
|
||||
const ecs::const_entity&,
|
||||
void spine_system::internal_state::process_new_animations(ecs::registry& owner) {
|
||||
owner_ = &owner;
|
||||
owner.for_joined_components<spine_animation_event, spine_player>(
|
||||
[this, &owner](
|
||||
ecs::entity_id id,
|
||||
const spine_animation_event& events,
|
||||
spine_player& player)
|
||||
{
|
||||
spSkeleton* skeleton = player.skeleton().operator->();
|
||||
spAnimationState* anim_state = player.animation().operator->();
|
||||
spSkeleton* skeleton = player.skeleton().get();
|
||||
spAnimationState* anim_state = player.animation().get();
|
||||
|
||||
if ( !skeleton || !anim_state ) {
|
||||
return;
|
||||
}
|
||||
|
||||
anim_state->userData = reinterpret_cast<void*>(size_t(id));
|
||||
|
||||
animation_visitor_ vis(skeleton->data, anim_state, *this);
|
||||
for ( auto& ev : events.commands() ) {
|
||||
stdex::visit(vis, ev);
|
||||
}
|
||||
});
|
||||
owner.remove_all_components<spine_animation_event>();
|
||||
owner_ = nullptr;
|
||||
}
|
||||
|
||||
void spine_system::internal_state::update_animations(ecs::registry& owner) {
|
||||
const float dt = the<engine>().delta_time();
|
||||
owner_ = &owner;
|
||||
owner.for_each_component<spine_player>(
|
||||
[dt](
|
||||
ecs::entity_id id,
|
||||
spine_player& player)
|
||||
{
|
||||
spSkeleton* skeleton = player.skeleton().get();
|
||||
spAnimationState* anim_state = player.animation().get();
|
||||
|
||||
if ( !skeleton || !anim_state ) {
|
||||
return;
|
||||
@@ -37,5 +181,19 @@ namespace e2d
|
||||
spAnimationState_apply(anim_state, skeleton);
|
||||
spSkeleton_updateWorldTransform(skeleton);
|
||||
});
|
||||
owner_ = nullptr;
|
||||
}
|
||||
|
||||
spine_system::spine_system()
|
||||
: state_(new internal_state()) {}
|
||||
|
||||
spine_system::~spine_system() noexcept {
|
||||
spAnimationState_disposeStatics();
|
||||
}
|
||||
|
||||
void spine_system::process(ecs::registry& owner) {
|
||||
state_->clear_events(owner);
|
||||
state_->process_new_animations(owner);
|
||||
state_->update_animations(owner);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user