From 626b4b77791b9df940a713da6c7fd9cf56dca9f0 Mon Sep 17 00:00:00 2001 From: Naruto TAKAHASHI Date: Fri, 31 Jul 2026 19:07:18 +0900 Subject: [PATCH 1/3] update ss-sdk submodule --- ss_player/SpriteStudio-SDK | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ss_player/SpriteStudio-SDK b/ss_player/SpriteStudio-SDK index e516e5c..427024d 160000 --- a/ss_player/SpriteStudio-SDK +++ b/ss_player/SpriteStudio-SDK @@ -1 +1 @@ -Subproject commit e516e5cb251363f083e8e58198672f8b3cdd5344 +Subproject commit 427024d7c68ef11de2011b7db58b02a0b1c58e38 From 8f48c681150766981cac14a5c62059420cd0aad9 Mon Sep 17 00:00:00 2001 From: Naruto TAKAHASHI Date: Fri, 31 Jul 2026 19:07:29 +0900 Subject: [PATCH 2/3] feat: add a four-corner part color override and name queries The runtime has always exposed `ss_runtime_set_part_color_override_corners` alongside the single-color entry point, but only the single-color one was wrapped, so a part could not be given a gradient. Add `set_part_color_override_corners` (and its `_by_index` twin), passing the four colors in the runtime's own `lt, rt, lb, rb` order. Both entry points write the same per-part override slot, so the last call wins and the existing `clear_part_color_override` clears either kind. Also forward `get_cellmap_names` / `get_cell_names` from the bound `SSABResource` onto the player. These are the discovery half of `set_part_cell_override`, and having them on the node saves the caller a round-trip through `get_ssab_resource()`; the resource keeps its own copies for enumerating an `.ssab` that is not on a player yet. The color packing shared by both override paths moves into `pack_color_rgba` so the two cannot drift apart. --- CHANGELOG.md | 7 +-- docs/en/api/player.md | 2 + docs/en/workflow/usage_scripting.md | 16 ++++-- docs/ja/api/player.md | 2 + docs/ja/workflow/usage_scripting.md | 16 ++++-- examples/Override_Ringo/override_demo.gd | 27 ++++++---- .../doc_classes/SpriteStudioPlayer2D.xml | 27 ++++++++++ ss_player/ss_internal_player.cpp | 27 ++++++++-- ss_player/ss_internal_player.h | 7 +++ ss_player/ss_player_node_2d.cpp | 53 +++++++++++++++++++ ss_player/ss_player_node_2d.h | 14 +++++ 11 files changed, 171 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 124562b..01b4ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,11 @@ converted binaries (`.ssab`) instead of parsing `.sspj` at runtime. See the same conversion for CI/CD. - **Resource classes**: `SSABResource` (animation binary) and `SSQBResource` (sequence binary). `.ssab` is loaded zero-copy, so playback starts without a parse step. -- **Per-part Override Layer API**: override a part's color, cell and visibility at runtime, - addressed by part name or part index, each with a matching `clear_*` call. +- **Per-part Override Layer API**: override a part's color (single or per-corner gradient), + cell and visibility at runtime, addressed by part name or part index, each with a matching + `clear_*` call. - **CellMap overrides**: swap an animation's textures at runtime for equipment changes and - color variants. + color variants. Cell map / cell names are enumerable from the player and the resource. - **`SpriteStudioPartAttachment2D` node**: mirrors one part's pose onto a `Node2D` so Godot content can be pinned to a part. Modeled on `RemoteTransform2D`. - **Signals**: timeline `user_data` and `signal_emitted` events, audio events, animation diff --git a/docs/en/api/player.md b/docs/en/api/player.md index 9d0662a..7f9e314 100644 --- a/docs/en/api/player.md +++ b/docs/en/api/player.md @@ -43,6 +43,7 @@ func _ready() -> void: * `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` +* `get_cellmap_names() -> PackedStringArray` / `get_cell_names(cellmap_name: String) -> PackedStringArray`: Names read from the assigned `SSABResource` (empty when none is assigned) — the discovery half of `set_part_cell_override()`. Also available on [`SSABResource`](resource.md) itself for an `.ssab` that is not on a player. ### Arguments for `set_playback_direction` @@ -67,6 +68,7 @@ See [Scripting and Event-Driven Control → Part Tracking](../workflow/usage_scr Override a single part's color / cell / visibility so that it wins over the keyframes. Every method returns `true` on success, or `false` when the part is unknown or the runtime rejects the call. See [Scripting and Event-Driven Control → Part Overrides](../workflow/usage_scripting.md) for the details and caveats. * `set_part_color_override(part_name: String, color: Color, blend_op: int = 0, priority: int = 1) -> bool` +* `set_part_color_override_corners(part_name: String, left_top: Color, right_top: Color, left_bottom: Color, right_bottom: Color, blend_op: int = 0, priority: int = 1) -> bool`: Four-corner (per-vertex) colour, for a gradient across the part. Shares one override slot with `set_part_color_override` — the last call wins, and `clear_part_color_override` clears either kind. * `set_part_cell_override(part_name: String, cellmap_name: String, cell_name: String, priority: int = 1) -> bool` * `set_part_visibility_override(part_name: String, force_hidden: bool, cascade: bool = false) -> bool` * `clear_part_color_override(part_name: String) -> bool` / `clear_part_cell_override(part_name: String) -> bool` / `clear_part_visibility_override(part_name: String) -> bool` diff --git a/docs/en/workflow/usage_scripting.md b/docs/en/workflow/usage_scripting.md index c1d6050..c431b43 100644 --- a/docs/en/workflow/usage_scripting.md +++ b/docs/en/workflow/usage_scripting.md @@ -188,6 +188,10 @@ func _ready(): # Tint a part red (multiply). Applies to normal (image) parts. ss_player.set_part_color_override("body", Color.RED, 1) # 1 = Mul + # Or give each of the four corners its own color, for a gradient. + ss_player.set_part_color_override_corners( + "body", Color.RED, Color.RED, Color.BLUE, Color.BLUE, 0) + # Make a part draw a different cell (cell map name is written without ".ssce"). ss_player.set_part_cell_override("body", "Ringo", "effect3") @@ -203,22 +207,24 @@ func _ready(): |---|---| | `get_part_index(part_name)` | Part index, or `-1` if the part is not in the asset | | `set_part_color_override(part_name, color, blend_op = 0, priority = 1)` | Color override (single color) | +| `set_part_color_override_corners(part_name, left_top, right_top, left_bottom, right_bottom, blend_op = 0, priority = 1)` | Color override with a distinct color per corner (gradient) | | `set_part_cell_override(part_name, cellmap_name, cell_name, priority = 1)` | Draw a different cell | | `set_part_visibility_override(part_name, force_hidden, cascade = false)` | Force-hide (`force_hidden = false` reverts to the animation) | | `clear_part_color_override` / `clear_part_cell_override` / `clear_part_visibility_override` | Clear one override on one part | | `clear_all_part_overrides()` | Clear every override on the player | | `*_by_index(part_index, ...)` | Part-index variant of each method above (skips the name lookup) | -Every method returns `false` when the part is unknown or the runtime rejects the call. +Every method returns `false` when the part is unknown or the runtime rejects the call. A single color and a four-corner color share one override slot per part, so the last call wins and `clear_part_color_override()` clears either kind. -The cell map / cell names you can pass to a cell override are enumerated from the resource: +The cell map / cell names you can pass to a cell override are enumerated from the player: ```gdscript -var ssab := ss_player.get_ssab_resource() -print(ssab.get_cellmap_names()) # -> ["Ringo", ...] -print(ssab.get_cell_names("Ringo")) # -> ["effect3", ...] +print(ss_player.get_cellmap_names()) # -> ["Ringo", ...] +print(ss_player.get_cell_names("Ringo")) # -> ["effect3", ...] ``` +Both read the bound `SSABResource` and return an empty array when none is assigned. The same two methods are also available on the resource itself (`ss_player.get_ssab_resource().get_cellmap_names()`), which is the way to enumerate an `.ssab` you have not put on a player yet. + > **On choosing between a texture swap and a cell override**: `set_cellmap_texture()` in the previous section replaces a **whole cell map (texture)** at once, affecting every part that uses it. This feature instead replaces the cell that a **single part** draws. Pick whichever matches your intent. > **On using part indices**: Part indices are stable within one asset (the same `.ssab`), so if you set overrides frequently, resolve the name once with `get_part_index()` and reuse that index with the `*_by_index()` variants. diff --git a/docs/ja/api/player.md b/docs/ja/api/player.md index 574b768..821fb0e 100644 --- a/docs/ja/api/player.md +++ b/docs/ja/api/player.md @@ -43,6 +43,7 @@ func _ready() -> void: * `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` +* `get_cellmap_names() -> PackedStringArray` / `get_cell_names(cellmap_name: String) -> PackedStringArray`: 割り当て済みの `SSABResource` から読んだ名前一覧(未割り当てなら空)。`set_part_cell_override()` に渡す名前を調べる用途です。まだプレーヤに載せていない `.ssab` を調べたい場合は [`SSABResource`](resource.md) 自身にも同じメソッドがあります。 ### `set_playback_direction` の引数 @@ -67,6 +68,7 @@ func _ready() -> void: パーツ単位で、カラー / セル / 表示指定をキーフレームより優先して上書きします。各メソッドは成功時に `true`、パーツが不明な場合やランタイムが受け付けなかった場合に `false` を返します。詳細と注意点は [スクリプト制御とイベント → パーツオーバーライド](../workflow/usage_scripting.md) を参照してください。 * `set_part_color_override(part_name: String, color: Color, blend_op: int = 0, priority: int = 1) -> bool` +* `set_part_color_override_corners(part_name: String, left_top: Color, right_top: Color, left_bottom: Color, right_bottom: Color, blend_op: int = 0, priority: int = 1) -> bool`: 4 頂点それぞれに色を指定して、パーツ内をグラデーションにします。`set_part_color_override` と同じオーバーライド枠を共有するので、後から呼んだ方が有効になり、`clear_part_color_override` はどちらも解除します。 * `set_part_cell_override(part_name: String, cellmap_name: String, cell_name: String, priority: int = 1) -> bool` * `set_part_visibility_override(part_name: String, force_hidden: bool, cascade: bool = false) -> bool` * `clear_part_color_override(part_name: String) -> bool` / `clear_part_cell_override(part_name: String) -> bool` / `clear_part_visibility_override(part_name: String) -> bool` diff --git a/docs/ja/workflow/usage_scripting.md b/docs/ja/workflow/usage_scripting.md index 3537632..b70e94b 100644 --- a/docs/ja/workflow/usage_scripting.md +++ b/docs/ja/workflow/usage_scripting.md @@ -188,6 +188,10 @@ func _ready(): # パーツを赤く着色(乗算)。通常(画像)パーツに適用されます。 ss_player.set_part_color_override("body", Color.RED, 1) # 1 = Mul + # 4 頂点それぞれに色を指定してグラデーションにすることもできます。 + ss_player.set_part_color_override_corners( + "body", Color.RED, Color.RED, Color.BLUE, Color.BLUE, 0) + # 別のセルを描画させる(セルマップ名は ".ssce" を付けずに指定)。 ss_player.set_part_cell_override("body", "Ringo", "effect3") @@ -203,22 +207,24 @@ func _ready(): |---|---| | `get_part_index(part_name)` | パーツインデックス。アセットに無ければ `-1` | | `set_part_color_override(part_name, color, blend_op = 0, priority = 1)` | パーツカラーオーバーライド(単色) | +| `set_part_color_override_corners(part_name, left_top, right_top, left_bottom, right_bottom, blend_op = 0, priority = 1)` | パーツカラーオーバーライド(4 頂点それぞれに色を指定=グラデーション) | | `set_part_cell_override(part_name, cellmap_name, cell_name, priority = 1)` | 別のセルで描画する | | `set_part_visibility_override(part_name, force_hidden, cascade = false)` | 強制非表示(`force_hidden = false` でアニメーションに戻す) | | `clear_part_color_override` / `clear_part_cell_override` / `clear_part_visibility_override` | 1 パーツの 1 オーバーライドを解除 | | `clear_all_part_overrides()` | そのプレーヤの全オーバーライドを解除 | | `*_by_index(part_index, ...)` | 上記各メソッドのパーツインデックス指定版(パーツ名の解決を省略) | -各メソッドは、パーツが不明な場合やランタイムが受け付けなかった場合に `false` を返します。 +各メソッドは、パーツが不明な場合やランタイムが受け付けなかった場合に `false` を返します。なお単色と 4 頂点色は 1 パーツにつき同じオーバーライド枠を共有するので、後から呼んだ方が有効になり、`clear_part_color_override()` はどちらも解除します。 -セルオーバーライドに指定できるセルマップ名 / セル名は、リソース側から列挙できます。 +セルオーバーライドに指定できるセルマップ名 / セル名は、プレーヤから列挙できます。 ```gdscript -var ssab := ss_player.get_ssab_resource() -print(ssab.get_cellmap_names()) # → ["Ringo", ...] -print(ssab.get_cell_names("Ringo")) # → ["effect3", ...] +print(ss_player.get_cellmap_names()) # → ["Ringo", ...] +print(ss_player.get_cell_names("Ringo")) # → ["effect3", ...] ``` +どちらも割り当て済みの `SSABResource` を読むので、未割り当てなら空の配列を返します。同じ 2 つのメソッドはリソース自身にもあり(`ss_player.get_ssab_resource().get_cellmap_names()`)、まだプレーヤに載せていない `.ssab` を列挙したい場合はそちらを使います。 + > **テクスチャとセルの差し替えの使い分けについて**: 前節の `set_cellmap_texture()` は**セルマップ(テクスチャ)まるごと**の差し替えで、そのセルマップを使う全パーツにまとめて効きます。こちらは**パーツ 1 つ単位**で、描画するセルそのものを差し替える機能です。目的に応じて使い分けてください。 > **パーツインデックスの使い方について**: パーツインデックスは同一アセット(同じ `.ssab`)内では安定しているので、頻繁にオーバーライドするなら `get_part_index()` で一度パーツ名をパーツインデックスに解決して、`*_by_index()` にそのインデックスを使い回すことを推奨します。 diff --git a/examples/Override_Ringo/override_demo.gd b/examples/Override_Ringo/override_demo.gd index b3abbee..bbd3a4f 100644 --- a/examples/Override_Ringo/override_demo.gd +++ b/examples/Override_Ringo/override_demo.gd @@ -7,11 +7,12 @@ extends SpriteStudioPlayer2D ## ## API used (all on SpriteStudioPlayer2D): ## set_part_color_override(part, color, blend_op=0, priority=1) +## set_part_color_override_corners(part, lt, rt, lb, rb, blend_op=0, priority=1) ## set_part_visibility_override(part, force_hidden, cascade=false) ## set_part_cell_override(part, cellmap, cell, priority=1) ## clear_all_part_overrides() -## Cell names can be discovered from the resource: -## ssab.get_cellmap_names() / ssab.get_cell_names(cellmap) +## Cell names can be discovered from the player (or from the resource): +## get_cellmap_names() / get_cell_names(cellmap) var _label: Label @@ -47,9 +48,9 @@ func _step(text: String, secs: float) -> void: func _run_demo() -> void: # Discover what the loaded SSAB offers (also handy as a reference in Output). # print("parts: ", get_part_names()) - var cellmap: String = ssab.get_cellmap_names()[0] # "Ringo" + var cellmap: String = get_cellmap_names()[0] # "Ringo" # print("cellmap: ", cellmap) -# print("cells: ", ssab.get_cell_names(cellmap)) +# print("cells: ", get_cell_names(cellmap)) # Since the same part is accessed repeatedly, identify it by part-ID # rather than its name (accessing by ID is slightly faster). @@ -66,19 +67,27 @@ func _run_demo() -> void: set_part_color_override_by_index(part_id, Color(0.0, 0.2, 1.0, 0.75)) # Access by id. await _step("1) Color override: body -> red", 2.0) - # 2) Visibility — force-hide the 'apple' and cascade to its children (the whole face). + # 2) Color (4 corners) — one colour per vertex gives a gradient across the part. + # Corner order is left-top, right-top, left-bottom, right-bottom. This shares + # the single colour-override slot, so it replaces step 1's flat tint. + set_part_color_override_corners_by_index(part_id, + Color(1.0, 0.1, 0.1, 1.0), Color(1.0, 0.1, 0.1, 1.0), + Color(0.1, 0.3, 1.0, 1.0), Color(0.1, 0.3, 1.0, 1.0)) + await _step("2) Corner colour override: body -> red-to-blue gradient", 2.0) + + # 3) Visibility — force-hide the 'apple' and cascade to its children (the whole face). clear_all_part_overrides() # set_part_visibility_override("apple", true, true) # Access by name. set_part_visibility_override_by_index(part_id, true, false) # Access by id. set_part_visibility_override("heta", true, false) - await _step("2) Visibility override: hide 'apple' with cascade ('heta' disappears)", 2.0) + await _step("3) Visibility override: hide 'apple' with cascade ('heta' disappears)", 2.0) - # 3) Cell — swap the 'apple' sprite to the 'effect3' cell in the Ringo cellmap. + # 4) Cell — swap the 'apple' sprite to the 'effect3' cell in the Ringo cellmap. clear_all_part_overrides() # set_part_cell_override("apple", cellmap, "effect3") # Access by name. set_part_cell_override_by_index(part_id, cellmap, "effect3") # Access by id. set_part_visibility_override("heta", true, false) - await _step("3) Cell override: body sprite -> 'effect3' cell", 2.0) + await _step("4) Cell override: body sprite -> 'effect3' cell", 2.0) clear_all_part_overrides() - await _step("4) No override (Restored)", 2.0) + await _step("5) No override (Restored)", 2.0) diff --git a/ss_player/doc_classes/SpriteStudioPlayer2D.xml b/ss_player/doc_classes/SpriteStudioPlayer2D.xml index 78c078c..5861ebc 100644 --- a/ss_player/doc_classes/SpriteStudioPlayer2D.xml +++ b/ss_player/doc_classes/SpriteStudioPlayer2D.xml @@ -72,6 +72,33 @@ Overrides the texture used for the named cell map. Pass an invalid texture to clear the override. + + + + Names of the cell maps in the assigned [SSABResource] (without the [code].ssce[/code] extension), or an empty array when no resource is assigned. Pass one of these to [method get_cell_names] or [method set_part_cell_override]. + + + + + + + Names of the cells inside [param cellmap_name], or an empty array when no resource is assigned or the cell map is unknown. + + + + + + + + + + + + + Overrides a part's color with a distinct color per corner, producing a gradient across the part. [param blend_op] is [code]0[/code] = Mix, [code]1[/code] = Mul, [code]2[/code] = Add, [code]3[/code] = Sub; [param priority] is [code]0[/code] = until the animation updates the attribute, [code]1[/code] = until the next animation is set up, [code]2[/code] = permanent. + Shares one override slot with [method set_part_color_override], so the last call wins and [method clear_part_color_override] clears either kind. Returns [code]false[/code] when the part is unknown or the runtime rejects the call. + + diff --git a/ss_player/ss_internal_player.cpp b/ss_player/ss_internal_player.cpp index 5ea2b75..60124a7 100644 --- a/ss_player/ss_internal_player.cpp +++ b/ss_player/ss_internal_player.cpp @@ -405,13 +405,30 @@ bool SsInternalPlayer::clear_part_visibility_override(int p_part_index) { return ss_runtime_clear_part_visibility_override(runtime_ctx, (uint32_t)p_part_index); } +// Godot Color (gamma/sRGB 0..1 under Compatibility 2D) -> packed 0xRRGGBBAA, +// the same 8-bit sRGB space the runtime does its color math in. +static uint32_t pack_color_rgba(const Color& p_color) { + auto to_u8 = [](float v) -> uint32_t { return (uint32_t)(int)(CLAMP(v, 0.0f, 1.0f) * 255.0f + 0.5f); }; + return (to_u8(p_color.r) << 24) | (to_u8(p_color.g) << 16) | (to_u8(p_color.b) << 8) | to_u8(p_color.a); +} + bool SsInternalPlayer::set_part_color_override(int p_part_index, const Color& p_color, int p_blend_op, int p_priority) { if (p_part_index < 0 || runtime_ctx == nullptr) return false; - // Godot Color (gamma/sRGB 0..1 under Compatibility 2D) -> packed 0xRRGGBBAA, - // the same 8-bit sRGB space the runtime does its color math in. - auto to_u8 = [](float v) -> uint32_t { return (uint32_t)(int)(CLAMP(v, 0.0f, 1.0f) * 255.0f + 0.5f); }; - uint32_t rgba = (to_u8(p_color.r) << 24) | (to_u8(p_color.g) << 16) | (to_u8(p_color.b) << 8) | to_u8(p_color.a); - return ss_runtime_set_part_color_override(runtime_ctx, (uint32_t)p_part_index, (unsigned char)p_blend_op, rgba, (unsigned char)p_priority); + return ss_runtime_set_part_color_override(runtime_ctx, (uint32_t)p_part_index, (unsigned char)p_blend_op, pack_color_rgba(p_color), (unsigned char)p_priority); +} + +bool SsInternalPlayer::set_part_color_override_corners(int p_part_index, const Color& p_left_top, const Color& p_right_top, + const Color& p_left_bottom, const Color& p_right_bottom, + int p_blend_op, int p_priority) { + if (p_part_index < 0 || runtime_ctx == nullptr) return false; + // ssruntime expects the four corners in `lt, rt, lb, rb` order. + const uint32_t rgba[4] = { + pack_color_rgba(p_left_top), + pack_color_rgba(p_right_top), + pack_color_rgba(p_left_bottom), + pack_color_rgba(p_right_bottom), + }; + return ss_runtime_set_part_color_override_corners(runtime_ctx, (uint32_t)p_part_index, (unsigned char)p_blend_op, rgba, (unsigned char)p_priority); } bool SsInternalPlayer::clear_part_color_override(int p_part_index) { diff --git a/ss_player/ss_internal_player.h b/ss_player/ss_internal_player.h index c0d16c8..17a0e68 100644 --- a/ss_player/ss_internal_player.h +++ b/ss_player/ss_internal_player.h @@ -223,6 +223,13 @@ class SsInternalPlayer { bool set_part_visibility_override(int p_part_index, bool p_force_hidden, bool p_cascade); bool clear_part_visibility_override(int p_part_index); bool set_part_color_override(int p_part_index, const Color& p_color, int p_blend_op, int p_priority); + // Four-corner (per-vertex) variant of the color override. Corners are named + // in the runtime's own order: left-top, right-top, left-bottom, right-bottom. + // Shares the single color-override slot with set_part_color_override, so + // clear_part_color_override clears either kind. + bool set_part_color_override_corners(int p_part_index, const Color& p_left_top, const Color& p_right_top, + const Color& p_left_bottom, const Color& p_right_bottom, + int p_blend_op, int p_priority); bool clear_part_color_override(int p_part_index); bool set_part_cell_override(int p_part_index, const String& p_cellmap_name, const String& p_cell_name, int p_priority); bool clear_part_cell_override(int p_part_index); diff --git a/ss_player/ss_player_node_2d.cpp b/ss_player/ss_player_node_2d.cpp index a7414c0..b716cf1 100644 --- a/ss_player/ss_player_node_2d.cpp +++ b/ss_player/ss_player_node_2d.cpp @@ -131,6 +131,42 @@ Ref SpriteStudioPlayer2D::get_cellmap_texture(const String &cellmap_n return Ref(); } +// Structural names live in the .ssab, not the runtime, so these forward to the +// bound SSABResource — the same source the inspector's cellmap list reads. They +// are the discovery half of set_part_cell_override(); having them on the node +// saves the caller a round-trip through get_ssab_resource(). Empty when no +// resource is bound (or the cellmap is unknown). + +PackedStringArray SpriteStudioPlayer2D::get_cellmap_names() const { + PackedStringArray names; + Ref res = _internal->getSSABResource(); + if (res.is_null()) return names; +#ifdef SPRITESTUDIO_GODOT_EXTENSION + names = res->get_cellmap_names(); +#else + Vector src = res->get_cellmap_names(); + for (int i = 0; i < src.size(); i++) { + names.push_back(src[i]); + } +#endif + return names; +} + +PackedStringArray SpriteStudioPlayer2D::get_cell_names(const String& cellmap_name) const { + PackedStringArray names; + Ref res = _internal->getSSABResource(); + if (res.is_null()) return names; +#ifdef SPRITESTUDIO_GODOT_EXTENSION + names = res->get_cell_names(cellmap_name); +#else + Vector src = res->get_cell_names(cellmap_name); + for (int i = 0; i < src.size(); i++) { + names.push_back(src[i]); + } +#endif + return names; +} + int SpriteStudioPlayer2D::get_part_index(const String& part_name) const { return _internal->resolve_part_index(part_name); } @@ -183,6 +219,12 @@ bool SpriteStudioPlayer2D::set_part_color_override_by_index(int part_index, cons return _internal->set_part_color_override(part_index, color, blend_op, priority); } +bool SpriteStudioPlayer2D::set_part_color_override_corners_by_index(int part_index, const Color& left_top, const Color& right_top, + const Color& left_bottom, const Color& right_bottom, + int blend_op, int priority) { + return _internal->set_part_color_override_corners(part_index, left_top, right_top, left_bottom, right_bottom, blend_op, priority); +} + bool SpriteStudioPlayer2D::clear_part_color_override_by_index(int part_index) { return _internal->clear_part_color_override(part_index); } @@ -210,6 +252,12 @@ bool SpriteStudioPlayer2D::set_part_color_override(const String& part_name, cons return set_part_color_override_by_index(_internal->resolve_part_index(part_name), color, blend_op, priority); } +bool SpriteStudioPlayer2D::set_part_color_override_corners(const String& part_name, const Color& left_top, const Color& right_top, + const Color& left_bottom, const Color& right_bottom, + int blend_op, int priority) { + return set_part_color_override_corners_by_index(_internal->resolve_part_index(part_name), left_top, right_top, left_bottom, right_bottom, blend_op, priority); +} + bool SpriteStudioPlayer2D::clear_part_color_override(const String& part_name) { return clear_part_color_override_by_index(_internal->resolve_part_index(part_name)); } @@ -437,6 +485,9 @@ void SpriteStudioPlayer2D::_bind_methods() { ClassDB::bind_method( D_METHOD( "set_cellmap_texture", "cellmap_name", "texture" ), &SpriteStudioPlayer2D::set_cellmap_texture ); ClassDB::bind_method( D_METHOD( "get_cellmap_texture", "cellmap_name" ), &SpriteStudioPlayer2D::get_cellmap_texture ); + ClassDB::bind_method( D_METHOD( "get_cellmap_names" ), &SpriteStudioPlayer2D::get_cellmap_names ); + ClassDB::bind_method( D_METHOD( "get_cell_names", "cellmap_name" ), &SpriteStudioPlayer2D::get_cell_names ); + ClassDB::bind_method( D_METHOD( "set_play_audio", "enabled" ), &SpriteStudioPlayer2D::set_play_audio ); ClassDB::bind_method( D_METHOD( "is_play_audio" ), &SpriteStudioPlayer2D::is_play_audio ); ClassDB::bind_method( D_METHOD( "set_audio_volume", "volume" ), &SpriteStudioPlayer2D::set_audio_volume ); @@ -467,6 +518,7 @@ void SpriteStudioPlayer2D::_bind_methods() { ClassDB::bind_method( D_METHOD( "set_part_visibility_override", "part_name", "force_hidden", "cascade" ), &SpriteStudioPlayer2D::set_part_visibility_override, DEFVAL(false) ); ClassDB::bind_method( D_METHOD( "clear_part_visibility_override", "part_name" ), &SpriteStudioPlayer2D::clear_part_visibility_override ); ClassDB::bind_method( D_METHOD( "set_part_color_override", "part_name", "color", "blend_op", "priority" ), &SpriteStudioPlayer2D::set_part_color_override, DEFVAL(0), DEFVAL(1) ); + ClassDB::bind_method( D_METHOD( "set_part_color_override_corners", "part_name", "left_top", "right_top", "left_bottom", "right_bottom", "blend_op", "priority" ), &SpriteStudioPlayer2D::set_part_color_override_corners, DEFVAL(0), DEFVAL(1) ); ClassDB::bind_method( D_METHOD( "clear_part_color_override", "part_name" ), &SpriteStudioPlayer2D::clear_part_color_override ); ClassDB::bind_method( D_METHOD( "set_part_cell_override", "part_name", "cellmap_name", "cell_name", "priority" ), &SpriteStudioPlayer2D::set_part_cell_override, DEFVAL(1) ); ClassDB::bind_method( D_METHOD( "clear_part_cell_override", "part_name" ), &SpriteStudioPlayer2D::clear_part_cell_override ); @@ -476,6 +528,7 @@ void SpriteStudioPlayer2D::_bind_methods() { ClassDB::bind_method( D_METHOD( "set_part_visibility_override_by_index", "part_index", "force_hidden", "cascade" ), &SpriteStudioPlayer2D::set_part_visibility_override_by_index, DEFVAL(false) ); ClassDB::bind_method( D_METHOD( "clear_part_visibility_override_by_index", "part_index" ), &SpriteStudioPlayer2D::clear_part_visibility_override_by_index ); ClassDB::bind_method( D_METHOD( "set_part_color_override_by_index", "part_index", "color", "blend_op", "priority" ), &SpriteStudioPlayer2D::set_part_color_override_by_index, DEFVAL(0), DEFVAL(1) ); + ClassDB::bind_method( D_METHOD( "set_part_color_override_corners_by_index", "part_index", "left_top", "right_top", "left_bottom", "right_bottom", "blend_op", "priority" ), &SpriteStudioPlayer2D::set_part_color_override_corners_by_index, DEFVAL(0), DEFVAL(1) ); ClassDB::bind_method( D_METHOD( "clear_part_color_override_by_index", "part_index" ), &SpriteStudioPlayer2D::clear_part_color_override_by_index ); ClassDB::bind_method( D_METHOD( "set_part_cell_override_by_index", "part_index", "cellmap_name", "cell_name", "priority" ), &SpriteStudioPlayer2D::set_part_cell_override_by_index, DEFVAL(1) ); ClassDB::bind_method( D_METHOD( "clear_part_cell_override_by_index", "part_index" ), &SpriteStudioPlayer2D::clear_part_cell_override_by_index ); diff --git a/ss_player/ss_player_node_2d.h b/ss_player/ss_player_node_2d.h index 2e1da4a..5e89c57 100644 --- a/ss_player/ss_player_node_2d.h +++ b/ss_player/ss_player_node_2d.h @@ -95,6 +95,11 @@ class SpriteStudioPlayer2D : public Node2D { void set_cellmap_texture(const String &cellmap_name, const Ref &texture); Ref get_cellmap_texture(const String &cellmap_name) const; + // Cellmap / cell names from the bound SSABResource — the discovery half of + // set_part_cell_override(). Empty when no resource is bound. + PackedStringArray get_cellmap_names() const; + PackedStringArray get_cell_names(const String &cellmap_name) const; + // ---- Audio ------------------------------------------------------------- // Built-in audio playback for the animation's audio events. Toggle off to // handle audio yourself via the "audio" signal. @@ -128,6 +133,12 @@ class SpriteStudioPlayer2D : public Node2D { bool set_part_visibility_override(const String& part_name, bool force_hidden, bool cascade); bool clear_part_visibility_override(const String& part_name); bool set_part_color_override(const String& part_name, const Color& color, int blend_op, int priority); + // Four-corner (per-vertex) color override. Corners follow the runtime's + // order: left-top, right-top, left-bottom, right-bottom. Shares one slot + // with set_part_color_override — clear_part_color_override clears either. + bool set_part_color_override_corners(const String& part_name, const Color& left_top, const Color& right_top, + const Color& left_bottom, const Color& right_bottom, + int blend_op, int priority); bool clear_part_color_override(const String& part_name); bool set_part_cell_override(const String& part_name, const String& cellmap_name, const String& cell_name, int priority); bool clear_part_cell_override(const String& part_name); @@ -136,6 +147,9 @@ class SpriteStudioPlayer2D : public Node2D { bool set_part_visibility_override_by_index(int part_index, bool force_hidden, bool cascade); bool clear_part_visibility_override_by_index(int part_index); bool set_part_color_override_by_index(int part_index, const Color& color, int blend_op, int priority); + bool set_part_color_override_corners_by_index(int part_index, const Color& left_top, const Color& right_top, + const Color& left_bottom, const Color& right_bottom, + int blend_op, int priority); bool clear_part_color_override_by_index(int part_index); bool set_part_cell_override_by_index(int part_index, const String& cellmap_name, const String& cell_name, int priority); bool clear_part_cell_override_by_index(int part_index); From b4be02dde77dcab9de415b449a9bb27647a46264 Mon Sep 17 00:00:00 2001 From: Naruto TAKAHASHI Date: Fri, 31 Jul 2026 19:07:39 +0900 Subject: [PATCH 3/3] docs: drop the blending/crossfade item from the roadmap It was filed as blocked on SDK Phase 3, but the SDK shipped the whole- animation blend and crossfade primitives some time ago and the C API has carried them since. What kept the item from being actionable was therefore not the SDK: the semantics a Godot-facing API would have to commit to (how sources of different lengths relate, whose events fire) are still open. Remove it rather than leave a stale blocker, and re-file it once that design settles. --- ROADMAP.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 5788338..d456f31 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -76,12 +76,6 @@ SSPlayerForGodot leverages Godot's `CanvasItem` API and `Node2D` paradigms. Feat - **Blocked on**: SDK Phase 3 **Instance Lifecycle → Animation Instancing** (Shared evaluation context). - **Task**: Once `ssruntime` supports computing `FrameData` once and rendering it N times, create a node (e.g. `SpriteStudioReplicate2D`) that binds to an original player's context and simply submits the evaluated batches with a different root `Transform2D`, saving Godot CPU time. -### 🕒 Animation blending / Crossfade (⛔ SDK Phase 3) - -- **Goal**: Blend multiple animations or crossfade between them. -- **Blocked on**: SDK Phase 3 **Animation Mixing/Blending** and **State machine implementation**. -- **Task**: Surface the blending/crossfade FFI capabilities to `SpriteStudioPlayer2D`, allowing users to smoothly transition between animations or manage layered blending. - ### 🕒 Dynamic instance swap (⛔ SDK Phase 3) - **Goal**: Replace the animation mounted on an Instance part at runtime (e.g., for equipment or character variations).