Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/en/api/player.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func _ready() -> void:
| `animation_looped` | `anim_name: String` | The animation loops back to the start |
| `user_data` | `payload: Dictionary` | A "User Data" keyframe on the timeline is hit |
| `signal_emitted` | `command: String, value: Dictionary` | A "Signal" keyframe on the timeline is hit |
| `audio` | `payload: Dictionary` | An "Audio" keyframe on the timeline is hit |

### `user_data` payload fields

Expand All @@ -79,6 +80,18 @@ The User Data values configured in SpriteStudio are delivered as a `Dictionary`.

The parameters configured on the timeline "Signal" keyframe are delivered as a `Dictionary` keyed by parameter ID, with each value as `bool` / `int` / `float` / `String`, etc. The `command` argument receives the signal name (`command_id`).

### `audio` payload fields

The information configured on the timeline audio keyframe is delivered as a `Dictionary`. The player does not play sound itself, so bridge it to an `AudioStreamPlayer` (or similar) on the game side.

| Key | Type | Meaning |
| --- | --- | --- |
| `part_index` | `int` | Index of the part that fired |
| `sound_list_name_hash` | `int` | Hash of the sound list name |
| `sound_name_hash` | `int` | Hash of the sound name |
| `sound_name` | `String` | Sound name (present only when set) |
| `loop_num` | `int` | Loop count |

> [!NOTE]
> For the exact types and the latest set of accepted values, also refer to the implementation files `ss_player/ss_player_node_2d.h` and `ss_player/ss_internal_player.cpp`.

Expand Down
13 changes: 13 additions & 0 deletions docs/ja/api/player.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func _ready() -> void:
| `animation_looped` | `anim_name: String` | ループして先頭に戻った時 |
| `user_data` | `payload: Dictionary` | タイムライン上の「ユーザーデータ」キーに到達した時 |
| `signal_emitted` | `command: String, value: Dictionary` | タイムライン上の「シグナル」キーに到達した時 |
| `audio` | `payload: Dictionary` | タイムライン上の「オーディオ」キーに到達した時 |

### `user_data` の `payload` フィールド

Expand All @@ -79,6 +80,18 @@ SpriteStudio 上でユーザーデータに設定した値が `Dictionary` と

タイムライン上の「シグナル」に設定したパラメータが `Dictionary` として渡されます。パラメータ ID をキーに、各値(`bool` / `int` / `float` / `String` 等)が格納されます。`command` 引数にはシグナル名(`command_id`)が入ります。

### `audio` の `payload` フィールド

タイムライン上のオーディオキーに設定された情報が `Dictionary` として渡されます。再生はプレーヤ側では行わないため、ゲーム側で `AudioStreamPlayer` 等に橋渡ししてください。

| キー | 型 | 内容 |
| --- | --- | --- |
| `part_index` | `int` | 発火したパーツのインデックス |
| `sound_list_name_hash` | `int` | サウンドリスト名のハッシュ |
| `sound_name_hash` | `int` | サウンド名のハッシュ |
| `sound_name` | `String` | サウンド名(設定時のみ) |
| `loop_num` | `int` | ループ回数 |

> [!NOTE]
> 引数の正確な型・最新の取り得る値は実装 `ss_player/ss_player_node_2d.h` / `ss_player/ss_internal_player.cpp` を併せて参照してください。

Expand Down
33 changes: 28 additions & 5 deletions ss_player/ss_internal_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,20 @@ void SsInternalPlayer::update(float delta_seconds) {
}

if (auto audios = events_per_frame->audios()) {
// TODO: Audio integration
for (uint32_t j = 0; j < audios->size(); j++) {
auto audio_event = audios->Get(j);
if (!audio_event || !audio_event->value()) continue;
auto val = audio_event->value();

Dictionary payload;
payload["part_index"] = audio_event->part_index();
payload["sound_list_name_hash"] = (int64_t)val->sound_list_name_hash();
payload["sound_name_hash"] = (int64_t)val->sound_name_hash();
if (val->sound_name()) payload["sound_name"] = String::utf8(val->sound_name()->c_str());
payload["loop_num"] = val->loop_num();

if (_event_sink) _event_sink->onAudio(payload);
}
}
}
}
Expand Down Expand Up @@ -1342,6 +1355,15 @@ void SsInternalPlayer::_setup_instance_children() {
auto pt = p->part_type_as_PartTypeInstance();
if (!pt) continue;

// Cyclic / runaway-nesting guard: instance children live one level
// deeper than this player. Refuse to descend past MAX_INSTANCE_DEPTH so
// a cyclic reference (A → B → A authoring mistake) can't recurse until
// the stack overflows on load.
if (_instance_depth + 1 >= MAX_INSTANCE_DEPTH) {
WARN_PRINT(vformat("[SS] instance part %d: nesting exceeded %d levels; skipping to guard against cyclic references", i, MAX_INSTANCE_DEPTH));
continue;
}

String pack_name = pt->ref_anime_pack() ? String::utf8(pt->ref_anime_pack()->c_str()) : String();
String anim_name = pt->ref_anime_name() ? String::utf8(pt->ref_anime_name()->c_str()) : String();
uint32_t pack_name_hash = pt->ref_anime_pack_hash();
Expand All @@ -1356,6 +1378,7 @@ void SsInternalPlayer::_setup_instance_children() {

SsInternalPlayer* child = memnew(SsInternalPlayer);
child->setParentDriven(true);
child->setInstanceDepth(_instance_depth + 1);
child->setSubFrameEnabled(_sub_frame_enabled);
// Hand the child the SSAB that actually contains the referenced
// animation — may be `_ssabRes` itself or an external sibling.
Expand Down Expand Up @@ -2585,10 +2608,10 @@ void SsInternalPlayer::_fetchAnimation() {
}

// Recursive setup — instance children also populate their own
// `_instance_children`, so any depth of nested Instance parts is
// supported. Cross-SSAB cycles (A → B → A authoring mistakes) are not
// detected; they recurse until stack overflow on load. Trust the
// converter / authoring tool to keep references acyclic.
// `_instance_children`, so nested Instance parts are supported. Cross-SSAB
// cycles (A → B → A authoring mistakes) are bounded by MAX_INSTANCE_DEPTH:
// `_setup_instance_children` stops spawning children past that depth (with
// a warning) instead of recursing until the stack overflows on load.
_setup_instance_children();
_setup_effect_slots();

Expand Down
12 changes: 12 additions & 0 deletions ss_player/ss_internal_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SsPlayerEventSink {
virtual void onAnimationLooped(const String& anim_name) {}
virtual void onUserData(const Dictionary& payload) {}
virtual void onSignal(const String& command, const Dictionary& value) {}
virtual void onAudio(const Dictionary& payload) {}
};

// Engine-integration-agnostic SpriteStudio player core. Owns the
Expand Down Expand Up @@ -160,6 +161,14 @@ class SsInternalPlayer {
void setParentDriven(bool p_enabled);
bool isParentDriven() const { return _parent_driven; }

// Nesting depth within an Instance-part chain (0 = the root player the host
// owns). `_setup_instance_children` sets each spawned child to parent depth
// + 1 and refuses to descend past `MAX_INSTANCE_DEPTH`, guarding against
// cyclic Instance references (A → B → A authoring mistakes) that would
// otherwise recurse until the stack overflows on load.
void setInstanceDepth(int p_depth) { _instance_depth = p_depth; }
int getInstanceDepth() const { return _instance_depth; }

// Per-tick update (in seconds; the host typically passes
// `process_delta_time`). No-op when paused or in instance-child mode.
void update(float delta_seconds);
Expand Down Expand Up @@ -319,6 +328,9 @@ class SsInternalPlayer {
float _speed_rate = 1.0f;
bool _sub_frame_enabled = false;
bool _parent_driven = false;
int _instance_depth = 0;
// Hard cap on Instance-part nesting depth; see `setInstanceDepth`.
static constexpr int MAX_INSTANCE_DEPTH = 16;

SsPlayerEventSink* _event_sink = nullptr;

Expand Down
9 changes: 9 additions & 0 deletions ss_player/ss_player_node_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ class SpriteStudioPlayer2D::_SignalSink : public SsPlayerEventSink {
void onSignal(const String& command, const Dictionary& value) override {
_owner->emit_signal(SNAME("signal_emitted"), command, value);
}
void onAudio(const Dictionary& payload) override {
_owner->emit_signal(SNAME("audio"), payload);
}

private:
SpriteStudioPlayer2D* _owner;
Expand Down Expand Up @@ -350,6 +353,12 @@ void SpriteStudioPlayer2D::_bind_methods() {
PropertyInfo(Variant::DICTIONARY, "value")
)
);
ADD_SIGNAL(
MethodInfo(
"audio",
PropertyInfo(Variant::DICTIONARY, "payload")
)
);

ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING, "anim_name")));
ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING, "anim_name")));
Expand Down
Loading