diff --git a/docs/en/api/player.md b/docs/en/api/player.md index 45c8b0a..0eeab6d 100644 --- a/docs/en/api/player.md +++ b/docs/en/api/player.md @@ -39,7 +39,7 @@ func _ready() -> void: * `set_frame_rate(fps: int)` / `get_frame_rate() -> int` * `set_animation_section(start: int, end: int)`: Limits the playback to a specific frame range. * `set_playback_direction(direction: int, style: int)`: Sets the playback direction and style. See the table below for values. -* `set_loop_count(count: int)` / `get_loop_count() -> int`: `-1` means infinite loop. `0` plays once with no repeat. `n` repeats `n` times. +* `set_loop_count(count: int)` / `get_loop_count() -> int`: `n` plays `n` cycles then stops (`1` plays once). `-1` means infinite loop (`0` is an alias for infinite). * `set_frame_skip_enabled(enabled: bool)` / `is_frame_skip_enabled() -> bool` (default: `true`) * `set_sub_frame_enabled(enabled: bool)` / `is_sub_frame_enabled() -> bool` (default: `false`) * `set_cellmap_texture(cellmap_name: String, texture: Texture2D)` / `get_cellmap_texture(cellmap_name: String) -> Texture2D` @@ -59,8 +59,8 @@ func _ready() -> void: | --- | --- | --- | | `animation_started` | `anim_name: String` | Playback starts | | `animation_changed` | `anim_name: String` | The animation name is changed | -| `animation_finished` | `anim_name: String` | Playback reaches the end (non-looping only) | -| `animation_looped` | `anim_name: String` | The animation loops back to the start | +| `animation_finished` | `anim_name: String` | Every configured loop has been played. Never emitted under an infinite loop | +| `animation_looped` | `anim_name: String` | The animation looped back to the start. Not emitted on the final cycle, which emits `animation_finished` instead | | `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 | diff --git a/docs/en/migration_from_v1.md b/docs/en/migration_from_v1.md index 9fc7f42..4fd5c15 100644 --- a/docs/en/migration_from_v1.md +++ b/docs/en/migration_from_v1.md @@ -12,7 +12,7 @@ When migrating from v1.x to the current version, please note the following major - **Resource Assignment**: - Previously, you had to assign multiple files via `res_player.res_project = load("...sspj")` and `set_anime_pack("...ssae")`. In the current version, a `.ssab` file is generated per `.ssae` (Anime Pack). - Therefore, you simply need to set the `.ssab` containing the animation you want to play using `set_ssab_resource(load("...ssab"))`. -- **Loop Playback**: The previous `set_loop(bool)` method has been replaced with `set_loop_count(int)` (`-1` for infinite loop, `1` for play once / no loop, `0` for no playback). +- **Loop Playback**: The previous `set_loop(bool)` method has been replaced with `set_loop_count(int)` (`-1` for infinite loop, `1` for play once / no loop). - **Method Renames and Removals**: - `set_player_resource()` → `set_ssab_resource()` - `get_fps()` → `get_frame_rate()` diff --git a/docs/en/workflow/usage_scripting.md b/docs/en/workflow/usage_scripting.md index 52887a5..fab68a7 100644 --- a/docs/en/workflow/usage_scripting.md +++ b/docs/en/workflow/usage_scripting.md @@ -40,7 +40,7 @@ One of the most powerful features for Godot users is event linkage using "Signal * `animation_changed(anim_name)`: Emitted when the animation is changed. * `animation_started(anim_name)`: Emitted when animation playback starts. -* `animation_finished(anim_name)`: Emitted when animation playback finishes (for non-looping animations). +* `animation_finished(anim_name)`: Emitted once every configured loop has been played (never under an infinite loop). * `animation_looped(anim_name)`: Emitted when the animation loops and returns to the beginning. * `user_data(payload)`: Emitted when reaching a frame containing user data (events) configured in the animation. diff --git a/docs/ja/api/player.md b/docs/ja/api/player.md index dde36f3..7c171d2 100644 --- a/docs/ja/api/player.md +++ b/docs/ja/api/player.md @@ -39,7 +39,7 @@ func _ready() -> void: * `set_frame_rate(fps: int)` / `get_frame_rate() -> int` * `set_animation_section(start: int, end: int)`: 再生するフレーム区間を限定します。 * `set_playback_direction(direction: int, style: int)`: 再生方向と再生スタイルを指定します。値の意味は下表を参照してください。 -* `set_loop_count(count: int)` / `get_loop_count() -> int`: `-1` で無限ループ、`0` で1回だけ再生、`n` で `n` 回繰り返し。 +* `set_loop_count(count: int)` / `get_loop_count() -> int`: `n` で `n` 回再生して停止(`1` なら1回のみ)。`-1` で無限ループ(`0` も無限ループの別名)。 * `set_frame_skip_enabled(enabled: bool)` / `is_frame_skip_enabled() -> bool` (デフォルト: `true`) * `set_sub_frame_enabled(enabled: bool)` / `is_sub_frame_enabled() -> bool` (デフォルト: `false`) * `set_cellmap_texture(cellmap_name: String, texture: Texture2D)` / `get_cellmap_texture(cellmap_name: String) -> Texture2D` @@ -59,8 +59,8 @@ func _ready() -> void: | --- | --- | --- | | `animation_started` | `anim_name: String` | 再生が開始された時 | | `animation_changed` | `anim_name: String` | アニメーションが切り替わった時 | -| `animation_finished` | `anim_name: String` | 再生が終了した時(非ループ時のみ) | -| `animation_looped` | `anim_name: String` | ループして先頭に戻った時 | +| `animation_finished` | `anim_name: String` | 指定したループ回数をすべて再生し終えた時。無限ループでは発火しない | +| `animation_looped` | `anim_name: String` | 1周して先頭に戻った時。最終周では発火せず `animation_finished` になる | | `user_data` | `payload: Dictionary` | タイムライン上の「ユーザーデータ」キーに到達した時 | | `signal_emitted` | `command: String, value: Dictionary` | タイムライン上の「シグナル」キーに到達した時 | | `audio` | `payload: Dictionary` | タイムライン上の「オーディオ」キーに到達した時 | diff --git a/docs/ja/migration_from_v1.md b/docs/ja/migration_from_v1.md index f8042a6..534ba6e 100644 --- a/docs/ja/migration_from_v1.md +++ b/docs/ja/migration_from_v1.md @@ -12,7 +12,7 @@ v1.x から本バージョンへ移行する場合は、以下の大きな変更 - **リソースの割り当て**: - 以前は `res_player.res_project = load("...sspj")` や `set_anime_pack("...ssae")` のように複数ファイルを設定していましたが、本バージョンでは `.ssab` が `.ssae` (アニメパック) 単位で生成されます。 - そのため、利用する(再生したい)アニメーションが含まれている `.ssab` を `set_ssab_resource(load("...ssab"))` のようにセットしてください。 -- **ループ再生の設定**: 以前の `set_loop(bool)` は廃止され、ループ回数を指定する `set_loop_count(int)` に変更されました(`-1` で無限ループ、`1` で1回再生(ループなし)、`0` は再生しません)。 +- **ループ再生の設定**: 以前の `set_loop(bool)` は廃止され、ループ回数を指定する `set_loop_count(int)` に変更されました(`-1` で無限ループ、`1` で1回再生(ループなし))。 - **メソッドの変更と廃止**: - `set_player_resource()` → `set_ssab_resource()` - `get_fps()` → `get_frame_rate()` diff --git a/docs/ja/workflow/usage_scripting.md b/docs/ja/workflow/usage_scripting.md index 57d7d79..d575db3 100644 --- a/docs/ja/workflow/usage_scripting.md +++ b/docs/ja/workflow/usage_scripting.md @@ -40,8 +40,8 @@ Godot ユーザーにとって最も強力な機能の一つが「シグナル ( * `animation_changed(anim_name)` : アニメーションが切り替わった時 * `animation_started(anim_name)` : アニメーションの再生が開始された時 -* `animation_finished(anim_name)`: アニメーションの再生が終了した時(非ループ時) -* `animation_looped(anim_name)` : アニメーションがループして先頭に戻った時 +* `animation_finished(anim_name)`: 指定したループ回数をすべて再生し終えた時(無限ループでは発火しません) +* `animation_looped(anim_name)` : アニメーションが1周して先頭に戻った時 * `user_data(payload)` : アニメーションに設定されたユーザーデータ(イベント)のフレームに到達した時 ### 実装例: アニメーションの連続再生 diff --git a/ss_player/SpriteStudio-SDK b/ss_player/SpriteStudio-SDK index 81d53be..dc9b987 160000 --- a/ss_player/SpriteStudio-SDK +++ b/ss_player/SpriteStudio-SDK @@ -1 +1 @@ -Subproject commit 81d53be389e403ab774b2be27bbf70ba5de75c91 +Subproject commit dc9b9876d81dd129d891d2791f3626d7c4123003 diff --git a/ss_player/ss_internal_player.cpp b/ss_player/ss_internal_player.cpp index 18bc828..94b2a78 100644 --- a/ss_player/ss_internal_player.cpp +++ b/ss_player/ss_internal_player.cpp @@ -700,11 +700,15 @@ void SsInternalPlayer::update(float delta_seconds) { auto d = delta_seconds * 1000.0f; float frame_no = ss_runtime_update(runtime_ctx, d); + // `is_looped` is a pulse the runtime clears on entry to every update, so + // reading it right after the tick is what catches it. `is_finished` is a + // sticky state instead; the `is_playing` early-return above is what keeps + // it from re-emitting, since only play() clears it. const bool was_looped = ss_runtime_is_looped(runtime_ctx); if (was_looped) { if (_event_sink) _event_sink->onAnimationLooped(_strAnimationSelected); } - if (ss_runtime_is_end_frame_reached(runtime_ctx)) { + if (ss_runtime_is_finished(runtime_ctx)) { if (_event_sink) _event_sink->onAnimationFinished(_strAnimationSelected); }