mirror of
https://github.com/enduro2d/enduro2d.git
synced 2025-12-16 22:16:53 +07:00
spine events as components
This commit is contained in:
@@ -36,6 +36,7 @@
|
|||||||
#include "components/renderer.hpp"
|
#include "components/renderer.hpp"
|
||||||
#include "components/scene.hpp"
|
#include "components/scene.hpp"
|
||||||
#include "components/spine_player.hpp"
|
#include "components/spine_player.hpp"
|
||||||
|
#include "components/spine_animation_event.hpp"
|
||||||
#include "components/sprite_renderer.hpp"
|
#include "components/sprite_renderer.hpp"
|
||||||
|
|
||||||
#include "systems/flipbook_system.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 spSkeleton;
|
||||||
struct spSkeletonClipping;
|
struct spSkeletonClipping;
|
||||||
struct spVertexEffect;
|
struct spVertexEffect;
|
||||||
|
struct spTrackEntry;
|
||||||
|
struct spEvent;
|
||||||
|
|
||||||
namespace e2d
|
namespace e2d
|
||||||
{
|
{
|
||||||
class spine_player final {
|
class spine_player final {
|
||||||
public:
|
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 animation_ptr = std::shared_ptr<spAnimationState>;
|
||||||
using skeleton_ptr = std::shared_ptr<spSkeleton>;
|
using skeleton_ptr = std::shared_ptr<spSkeleton>;
|
||||||
using clipping_ptr = std::shared_ptr<spSkeletonClipping>;
|
using clipping_ptr = std::shared_ptr<spSkeletonClipping>;
|
||||||
@@ -37,27 +49,15 @@ namespace e2d
|
|||||||
|
|
||||||
[[nodiscard]] bool has_animation(const str& name) const noexcept;
|
[[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 animation_ptr& animation() const noexcept;
|
||||||
const skeleton_ptr& skeleton() const noexcept;
|
const skeleton_ptr& skeleton() const noexcept;
|
||||||
const clipping_ptr& clipper() const noexcept;
|
const clipping_ptr& clipper() const noexcept;
|
||||||
const effect_ptr& effect() const noexcept;
|
|
||||||
const spine_model_asset::ptr& model() const noexcept;
|
const spine_model_asset::ptr& model() const noexcept;
|
||||||
private:
|
private:
|
||||||
animation_ptr animation_;
|
animation_ptr animation_;
|
||||||
spine_model_asset::ptr model_;
|
spine_model_asset::ptr model_;
|
||||||
skeleton_ptr skeleton_;
|
skeleton_ptr skeleton_;
|
||||||
clipping_ptr clipping_;
|
clipping_ptr clipping_;
|
||||||
effect_ptr effect_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|||||||
@@ -15,5 +15,8 @@ namespace e2d
|
|||||||
spine_system();
|
spine_system();
|
||||||
~spine_system() noexcept final;
|
~spine_system() noexcept final;
|
||||||
void process(ecs::registry& owner) override;
|
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 {
|
class game_system final : public ecs::system {
|
||||||
public:
|
public:
|
||||||
game_system(gobject_iptr raptor)
|
|
||||||
: raptor_gobj_(raptor) {}
|
|
||||||
|
|
||||||
~game_system() noexcept override {}
|
|
||||||
|
|
||||||
void process(ecs::registry& owner) override {
|
void process(ecs::registry& owner) override {
|
||||||
E2D_UNUSED(owner);
|
E2D_UNUSED(owner);
|
||||||
const keyboard& k = the<input>().keyboard();
|
const keyboard& k = the<input>().keyboard();
|
||||||
@@ -33,33 +28,39 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use keys R, J, G to start animations
|
// use keys R, J, G to start animations
|
||||||
if ( raptor_gobj_ ) {
|
const bool roar = k.is_key_just_pressed(keyboard_key::r);
|
||||||
if ( k.is_key_just_pressed(keyboard_key::r) ) {
|
const bool jump = k.is_key_just_pressed(keyboard_key::j);
|
||||||
auto player = raptor_gobj_->get_component<spine_player>();
|
const bool gun_grab = k.is_key_just_pressed(keyboard_key::g);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
if ( roar || jump || gun_grab ) {
|
||||||
gobject_iptr raptor_gobj_;
|
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 {
|
class camera_system final : public ecs::system {
|
||||||
@@ -86,47 +87,11 @@ namespace
|
|||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
bool create_scene() {
|
bool create_scene() {
|
||||||
auto spine_raptor = the<library>().load_asset<spine_model_asset>("spine_raptor.json");
|
auto scene_prefab_res = the<library>().load_asset<prefab_asset>("scene_spine_prefab.json");
|
||||||
auto spine_coin = the<library>().load_asset<spine_model_asset>("spine_coin.json");
|
auto scene_go = scene_prefab_res
|
||||||
auto spine_mat = the<library>().load_asset<material_asset>("spine_material.json");
|
? the<world>().instantiate(scene_prefab_res->content())
|
||||||
|
: nullptr;
|
||||||
if ( !spine_raptor || !spine_coin || !spine_mat ) {
|
return !!scene_go;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool create_camera() {
|
bool create_camera() {
|
||||||
@@ -140,13 +105,10 @@ namespace
|
|||||||
|
|
||||||
bool create_systems() {
|
bool create_systems() {
|
||||||
ecs::registry_filler(the<world>().registry())
|
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);
|
.system<camera_system>(world::priority_pre_render);
|
||||||
return true;
|
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 <enduro2d/high/components/spine_player.hpp>
|
||||||
#include <spine/spine.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
|
namespace e2d
|
||||||
{
|
{
|
||||||
spine_player::spine_player(const spine_model_asset::ptr& model) {
|
spine_player::spine_player(const spine_model_asset::ptr& model) {
|
||||||
@@ -32,6 +26,7 @@ namespace e2d
|
|||||||
animation_ = animation_ptr(
|
animation_ = animation_ptr(
|
||||||
spAnimationState_create(model_->content().animation().get()),
|
spAnimationState_create(model_->content().animation().get()),
|
||||||
spAnimationState_dispose);
|
spAnimationState_dispose);
|
||||||
|
E2D_ASSERT(animation_); // TODO exception?
|
||||||
} else {
|
} else {
|
||||||
animation_.reset();
|
animation_.reset();
|
||||||
}
|
}
|
||||||
@@ -42,7 +37,7 @@ namespace e2d
|
|||||||
spSkeleton_setSkinByName(skeleton_.get(), value.empty() ? nullptr : value.c_str());
|
spSkeleton_setSkinByName(skeleton_.get(), value.empty() ? nullptr : value.c_str());
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
spine_player& spine_player::attachment(const str& slot, const str& name) noexcept {
|
spine_player& spine_player::attachment(const str& slot, const str& name) noexcept {
|
||||||
E2D_ASSERT(!slot.empty());
|
E2D_ASSERT(!slot.empty());
|
||||||
E2D_ASSERT(!name.empty());
|
E2D_ASSERT(!name.empty());
|
||||||
@@ -51,7 +46,7 @@ namespace e2d
|
|||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const spine_player::skeleton_ptr& spine_player::skeleton() const noexcept {
|
const spine_player::skeleton_ptr& spine_player::skeleton() const noexcept {
|
||||||
return skeleton_;
|
return skeleton_;
|
||||||
}
|
}
|
||||||
@@ -60,10 +55,6 @@ namespace e2d
|
|||||||
return clipping_;
|
return clipping_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const spine_player::effect_ptr& spine_player::effect() const noexcept {
|
|
||||||
return effect_;
|
|
||||||
}
|
|
||||||
|
|
||||||
spine_player& spine_player::time_scale(float value) noexcept {
|
spine_player& spine_player::time_scale(float value) noexcept {
|
||||||
E2D_ASSERT(animation_);
|
E2D_ASSERT(animation_);
|
||||||
animation_->timeScale = value;
|
animation_->timeScale = value;
|
||||||
@@ -80,65 +71,11 @@ namespace e2d
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
spAnimation* anim = spSkeletonData_findAnimation(
|
spAnimation* anim = spSkeletonData_findAnimation(
|
||||||
model_->content().skeleton().operator->(),
|
model_->content().skeleton().get(),
|
||||||
name.c_str());
|
name.c_str());
|
||||||
return anim != nullptr;
|
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 {
|
const spine_player::animation_ptr& spine_player::animation() const noexcept {
|
||||||
return animation_;
|
return animation_;
|
||||||
}
|
}
|
||||||
@@ -160,24 +97,9 @@ namespace e2d
|
|||||||
"attachments" : {
|
"attachments" : {
|
||||||
"type" : "array",
|
"type" : "array",
|
||||||
"items" : { "$ref": "#/definitions/spine_attachment" }
|
"items" : { "$ref": "#/definitions/spine_attachment" }
|
||||||
},
|
|
||||||
"animations" : {
|
|
||||||
"type" : "array",
|
|
||||||
"items" : { "$ref": "#/definitions/spine_animation" }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions" : {
|
"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" : {
|
"spine_attachment" : {
|
||||||
"type" : "object",
|
"type" : "object",
|
||||||
"required" : [ "slot", "name" ],
|
"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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <enduro2d/high/components/renderer.hpp>
|
#include <enduro2d/high/components/renderer.hpp>
|
||||||
#include <enduro2d/high/components/scene.hpp>
|
#include <enduro2d/high/components/scene.hpp>
|
||||||
#include <enduro2d/high/components/spine_player.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/components/sprite_renderer.hpp>
|
||||||
|
|
||||||
#include <enduro2d/high/systems/flipbook_system.hpp>
|
#include <enduro2d/high/systems/flipbook_system.hpp>
|
||||||
@@ -142,6 +143,7 @@ namespace e2d
|
|||||||
.register_component<renderer>("renderer")
|
.register_component<renderer>("renderer")
|
||||||
.register_component<scene>("scene")
|
.register_component<scene>("scene")
|
||||||
.register_component<spine_player>("spine_player")
|
.register_component<spine_player>("spine_player")
|
||||||
|
.register_component<spine_animation_event>("spine_animation")
|
||||||
.register_component<sprite_renderer>("sprite_renderer");
|
.register_component<sprite_renderer>("sprite_renderer");
|
||||||
safe_module_initialize<library>(params.library_root(), the<deferrer>());
|
safe_module_initialize<library>(params.library_root(), the<deferrer>());
|
||||||
safe_module_initialize<world>();
|
safe_module_initialize<world>();
|
||||||
|
|||||||
@@ -264,9 +264,8 @@ namespace e2d::render_system_impl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
spSkeleton* skeleton = spine_r.skeleton().operator->();
|
spSkeleton* skeleton = spine_r.skeleton().get();
|
||||||
spSkeletonClipping* clipper = spine_r.clipper().operator->();
|
spSkeletonClipping* clipper = spine_r.clipper().get();
|
||||||
spVertexEffect* effect = spine_r.effect().operator->();
|
|
||||||
const material_asset::ptr& src_mat = node_r.materials().front();
|
const material_asset::ptr& src_mat = node_r.materials().front();
|
||||||
const bool use_premultiplied_alpha = spine_r.model()->content().premultiplied_alpha();
|
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 ) {
|
if ( skeleton->color.a == 0 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( effect ) {
|
|
||||||
effect->begin(effect, skeleton);
|
|
||||||
}
|
|
||||||
|
|
||||||
const m4f& sm = node->world_matrix();
|
const m4f& sm = node->world_matrix();
|
||||||
|
|
||||||
@@ -431,10 +426,6 @@ namespace e2d::render_system_impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
spSkeletonClipping_clipEnd2(clipper);
|
spSkeletonClipping_clipEnd2(clipper);
|
||||||
|
|
||||||
if ( effect ) {
|
|
||||||
effect->end(effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
property_cache_.clear();
|
property_cache_.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,26 +7,170 @@
|
|||||||
#include <enduro2d/high/systems/spine_system.hpp>
|
#include <enduro2d/high/systems/spine_system.hpp>
|
||||||
|
|
||||||
#include <enduro2d/high/components/spine_player.hpp>
|
#include <enduro2d/high/components/spine_player.hpp>
|
||||||
|
#include <enduro2d/high/components/spine_animation_event.hpp>
|
||||||
|
|
||||||
#include <spine/AnimationState.h>
|
#include <spine/spine.h>
|
||||||
#include <spine/Skeleton.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
|
namespace e2d
|
||||||
{
|
{
|
||||||
spine_system::spine_system() {}
|
//
|
||||||
|
// internal_state
|
||||||
|
//
|
||||||
|
|
||||||
spine_system::~spine_system() noexcept {
|
class spine_system::internal_state final {
|
||||||
spAnimationState_disposeStatics();
|
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) {
|
void spine_system::internal_state::process_new_animations(ecs::registry& owner) {
|
||||||
float dt = the<engine>().delta_time();
|
owner_ = &owner;
|
||||||
owner.for_each_component<spine_player>([dt](
|
owner.for_joined_components<spine_animation_event, spine_player>(
|
||||||
const ecs::const_entity&,
|
[this, &owner](
|
||||||
|
ecs::entity_id id,
|
||||||
|
const spine_animation_event& events,
|
||||||
spine_player& player)
|
spine_player& player)
|
||||||
{
|
{
|
||||||
spSkeleton* skeleton = player.skeleton().operator->();
|
spSkeleton* skeleton = player.skeleton().get();
|
||||||
spAnimationState* anim_state = player.animation().operator->();
|
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 ) {
|
if ( !skeleton || !anim_state ) {
|
||||||
return;
|
return;
|
||||||
@@ -37,5 +181,19 @@ namespace e2d
|
|||||||
spAnimationState_apply(anim_state, skeleton);
|
spAnimationState_apply(anim_state, skeleton);
|
||||||
spSkeleton_updateWorldTransform(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