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/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).
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/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
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);