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
5 changes: 4 additions & 1 deletion docs/en/api/player.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ func _ready() -> void:

## Part queries

* `get_part_names() -> PackedStringArray`: Every part name in the current animation.
* `get_part_names() -> PackedStringArray`: Every part name in the asset (`.ssab`). Parts do not depend on the animation, so the list is the same for every animation in that asset.
* `get_part_index(part_name: String) -> int`: Resolves a part name to its part index, or `-1` if it does not exist.
* `get_part_transform(part_name: String) -> Transform2D`: The part's `Transform2D` on the current frame, in the player node's local space (`flip_h` / `flip_v` / `offset` included). Returns the identity when the part is unknown.
* `is_part_hidden(part_name: String) -> bool`: Whether the part is hidden on the current frame.

See [Scripting and Event-Driven Control → Part Tracking](../workflow/usage_scripting.md) for `SpriteStudioPartAttachment2D`, the node that makes another node follow a specified part.

## Part overrides

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.
Expand Down Expand Up @@ -99,6 +101,7 @@ Override a single part's color / cell / visibility so that it wins over the keyf
| `animation_changed` | `anim_name: String` | The animation name is changed |
| `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 |
| `frame_updated` | `frame_no: float` | The frame's part poses have just been finalized (right after the player's update, before the render phase). Which process it fires in follows `animation_process_mode` |
| `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 |
Expand Down
73 changes: 73 additions & 0 deletions docs/en/workflow/usage_scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,79 @@ This feature allows you to build an efficient avatar system without needing to p

---

## Part Tracking (Following a Specified Part)

A feature that makes a user-provided node (weapon, effect, hit detection, etc.) follow a specified part every frame. The player neither creates nor frees nodes; it only writes the pose (constraint style), and you own the target's lifecycle.

Tracking is done with the dedicated **`SpriteStudioPartAttachment2D`** node. Place it as a child of `SpriteStudioPlayer2D` and set `part_name` to the part you want to follow. Anything you hang under that node — a weapon, an effect — follows along through scene-tree inheritance.

> **The properties follow Godot's own `RemoteTransform2D`**, plus `follow_path` / `part_name` to say which player and which part to read.

| Property | Description |
|---|---|
| **Part Name** (`part_name`) | The name of the part to follow (the PartData name in the `.ssab`). The inspector offers a dropdown populated from the asset's part names (still typable, for when the player cannot be resolved) |
| **Follow Path** (`follow_path`) | The `SpriteStudioPlayer2D` to read from. Empty (default) uses the **nearest ancestor** player |
| **Remote Path** (`remote_path`) | The `Node2D` to drive. Empty (default) drives this node itself, and its children follow through scene-tree inheritance. Set it to push the pose to an external node instead (for assets that live outside the player's subtree) |
| **Use Global Coordinates** (`use_global_coordinates`) | ON (default) writes the pose in global coordinates, OFF in the target's local coordinates |
| **Update Position / Update Rotation** (`update_position` / `update_rotation`) | Reflect position / rotation (both ON by default) |
| **Update Scale** (`update_scale`) | Reflect scale (OFF by default) |
| **On Part Hidden** (`on_part_hidden`) | Behavior on frames where the part is hidden. `Follow Always` (keep following; default) / `Hide Target` (hide the target) |

### Querying from a Script

Instead of placing a node, you can also ask the player for a part's pose directly.

```gdscript
@onready var ss_player = $SpriteStudioPlayer2D
@onready var muzzle = $Muzzle

func _ready():
print(ss_player.get_part_names()) # -> ["root", "body", "hand_R", ...]

# Emitted every time the frame's part poses are finalized
ss_player.frame_updated.connect(_on_frame_updated)

func _on_frame_updated(frame_no: float):
# get_part_transform() is player-local; multiply by the player's transform for global
muzzle.global_transform = ss_player.global_transform * ss_player.get_part_transform("hand_R")
```

| API | Description |
|---|---|
| `get_part_names()` | Every part name in the asset (`.ssab`) |
| `get_part_index(part_name)` | Part index, or `-1` if the part is not in the asset |
| `get_part_transform(part_name)` | The part's transform for the current frame (a `Transform2D`, player-local, with `flip_h` / `flip_v` / `offset` already applied). Identity if the part is unknown |
| `is_part_hidden(part_name)` | Whether the part is hidden on the current frame. `false` if the part is unknown |
| signal `frame_updated(frame_no: float)` | Emitted right after the frame's part poses are finalized |

> When you only need the pose at a single moment (a projectile spawn point, for example) rather than continuous following, calling `get_part_transform()` directly is simpler than placing a `SpriteStudioPartAttachment2D`.

### Timing and Accuracy

Tracking is driven by the `frame_updated` signal the player emits right after finishing its own update. That is after the part transforms are finalized and before the render phase, so the target updates **within the same frame**. Which process it fires in follows the player's `animation_process_mode` (`Idle` (default) / `Physics`).

Godot's `Transform2D` holds a full 2x3 affine transform, so when `update_position` / `update_rotation` / `update_scale` are **all ON** the transform is assigned whole. That matches the part **exactly, including skew and negative scale**, whether the target sits under the player or in a separate hierarchy.

Turning any of them OFF writes only the enabled components individually, like `RemoteTransform2D`, and skew is not preserved. Only `update_scale` is OFF by default, so **position and rotation alone are reflected out of the box**.

> **A target in a separate hierarchy can lag by one frame.** The pose is written using the player's `global_transform` as sampled at drive time, so if you move the player afterwards, the target does not follow until the next frame. A `SpriteStudioPartAttachment2D` (and its children) placed under the player always follows, through hierarchy inheritance.

> **Do not track with a `RigidBody2D`.** Overwriting its transform every frame reads as a teleport to the solver and breaks the physics. If you need to push other bodies — a moving platform, say — target Godot's `AnimatableBody2D` (with `sync_to_physics` ON) and set the player's `animation_process_mode` to `Physics` so tracking is driven on the physics frame. To merely carry a hit box, `Area2D` / `StaticBody2D` is enough.

### Notes

- **The attachment controls the target's `visible`.** It is hidden automatically in the two cases below, and shown again automatically once the condition clears, so a visibility state you set yourself may be overwritten.
- The part name does not exist in the asset (always hidden, regardless of the `On Part Hidden` setting)
- The part is hidden on this frame and `On Part Hidden` is `Hide Target`
- Part names resolve against the parts of the `.ssab` the player itself has loaded. **Parts inside an Instance part (the child animation) cannot be specified** (the Instance part itself can).
- Part names resolve per asset (`.ssab`), independent of the animation. Swapping the `.ssab` re-resolves them automatically, so nothing has to be set up again.
- If several parts share a name, the first one found is used.
- Only the **spatial transform** is tracked. Draw order (Z order) is not, so a tracked node is never slotted automatically *between* SpriteStudio parts. Use `z_index` or similar when you need a specific ordering.
- Targets must be `Node2D`-based nodes. `Control` (UI) is laid out by anchors and rects and cannot be targeted.
- In the editor, tracking is applied as well whenever the player updates — during preview playback or while scrubbing frames.

---

## Part Overrides (Color / Cell / Visibility)

Per-part runtime overrides let a script say "make this part this color / this cell / hidden **now**". An override wins over both the keyframe and any animation blending, so it does not have to fight the animation.
Expand Down
5 changes: 4 additions & 1 deletion docs/ja/api/player.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ func _ready() -> void:

## パーツの参照

* `get_part_names() -> PackedStringArray`: 現在のアニメーションに含まれる全パーツ名。
* `get_part_names() -> PackedStringArray`: アセット(`.ssab`)に含まれる全パーツ名。パーツはアニメーションに依存しないため、同じアセット内のどのアニメーションでも同じ一覧になります。
* `get_part_index(part_name: String) -> int`: パーツ名をパーツインデックスへ解決します。存在しない場合は `-1`。
* `get_part_transform(part_name: String) -> Transform2D`: 現在のフレームでのパーツの `Transform2D`(プレイヤーノードのローカル空間。`flip_h` / `flip_v` / `offset` を含みます)。パーツが不明な場合は単位行列を返します。
* `is_part_hidden(part_name: String) -> bool`: 現在のフレームでそのパーツが非表示かどうか。

指定したパーツにノードを追従させる `SpriteStudioPartAttachment2D` については [スクリプト制御とイベント駆動 → パーツトラッキング](../workflow/usage_scripting.md) を参照してください。

## パーツオーバーライド

パーツ単位で、カラー / セル / 表示指定をキーフレームより優先して上書きします。各メソッドは成功時に `true`、パーツが不明な場合やランタイムが受け付けなかった場合に `false` を返します。詳細と注意点は [スクリプト制御とイベント → パーツオーバーライド](../workflow/usage_scripting.md) を参照してください。
Expand Down Expand Up @@ -99,6 +101,7 @@ func _ready() -> void:
| `animation_changed` | `anim_name: String` | アニメーションが切り替わった時 |
| `animation_finished` | `anim_name: String` | 指定したループ回数をすべて再生し終えた時。無限ループでは発火しない |
| `animation_looped` | `anim_name: String` | 1周して先頭に戻った時。最終周では発火せず `animation_finished` になる |
| `frame_updated` | `frame_no: float` | そのフレームのパーツ姿勢が確定した直後(プレーヤの更新直後、描画の前)。発火するプロセスは `animation_process_mode` に従う |
| `user_data` | `payload: Dictionary` | タイムライン上の「ユーザーデータ」キーに到達した時 |
| `signal_emitted` | `command: String, value: Dictionary` | タイムライン上の「シグナル」キーに到達した時 |
| `audio` | `payload: Dictionary` | タイムライン上の「オーディオ」キーに到達した時 |
Expand Down
73 changes: 73 additions & 0 deletions docs/ja/workflow/usage_scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,79 @@ func change_costume():

---

## パーツトラッキング(指定パーツへの追従)

指定したパーツに、ユーザーが用意したノード(武器・エフェクト・当たり判定など)を毎フレーム追従させる機能です。プレーヤはノードを生成も破棄もせず、姿勢を書き込むだけの連動(Constraint)方式で、対象のライフサイクルはユーザーが所有します。

追従には専用ノード **`SpriteStudioPartAttachment2D`** を使います。`SpriteStudioPlayer2D` の子として置き、`part_name` に追従したいパーツ名を指定してください。このノードの下に武器やエフェクトをぶら下げれば、シーンツリーの継承でまとめて追従します。

> **プロパティは Godot 標準の `RemoteTransform2D` を踏襲**しています。これに「どのプレーヤの、どのパーツに追従するか」を指定する `follow_path` / `part_name` が加わった形です。

| プロパティ | 説明 |
|---|---|
| **Part Name** (`part_name`) | 追従対象のパーツ名(`.ssab` の PartData 名)。インスペクタではアセットのパーツ名からドロップダウンで選べます(プレーヤを解決できない場面のために手入力も可) |
| **Follow Path** (`follow_path`) | 追従元の `SpriteStudioPlayer2D`。空(既定)なら**最も近い祖先**のプレーヤを使います |
| **Remote Path** (`remote_path`) | 駆動する対象の `Node2D`。空(既定)なら自分自身を動かし、子はシーンツリーの継承で追従します。指定するとその外部ノードへ姿勢を書き込みます(プレーヤのサブツリーの外に置いた資産を追従させたい場合) |
| **Use Global Coordinates** (`use_global_coordinates`) | ON(既定)でグローバル座標として、OFF で対象のローカル座標として書き込みます |
| **Update Position / Update Rotation** (`update_position` / `update_rotation`) | 位置 / 回転を反映します(ともに既定 ON) |
| **Update Scale** (`update_scale`) | スケールを反映します(既定 OFF) |
| **On Part Hidden** (`on_part_hidden`) | パーツが hide のフレームでの挙動。`Follow Always`(追従を継続。既定)/ `Hide Target`(対象を非表示) |

### スクリプトからの参照

ノードを置かずに、プレーヤへ直接パーツの姿勢を問い合わせることもできます。

```gdscript
@onready var ss_player = $SpriteStudioPlayer2D
@onready var muzzle = $Muzzle

func _ready():
print(ss_player.get_part_names()) # → ["root", "body", "hand_R", ...]

# そのフレームのパーツ姿勢が確定するたびに通知される
ss_player.frame_updated.connect(_on_frame_updated)

func _on_frame_updated(frame_no: float):
# get_part_transform() はプレーヤローカル。グローバルにするならプレーヤの変換を掛ける
muzzle.global_transform = ss_player.global_transform * ss_player.get_part_transform("hand_R")
```

| API | 説明 |
|---|---|
| `get_part_names()` | アセット(`.ssab`)に含まれる全パーツ名 |
| `get_part_index(part_name)` | パーツインデックス。アセットに無ければ `-1` |
| `get_part_transform(part_name)` | そのパーツの現在フレームの変換(`Transform2D`。プレーヤローカルで、`flip_h` / `flip_v` / `offset` を適用済み)。パーツが不明なら単位行列 |
| `is_part_hidden(part_name)` | そのパーツが現在フレームで hide かどうか。パーツが不明なら `false` |
| signal `frame_updated(frame_no: float)` | そのフレームのパーツ姿勢が確定した直後に発火する |

> **一瞬の姿勢だけが要る場合**: 弾の発射位置を取るなど、常時追従させるまでもない場合は、`SpriteStudioPartAttachment2D` を置かずに `get_part_transform()` を直接呼ぶ方が簡潔です。

### 追従のタイミングと精度

追従は、プレーヤが自身の更新を終えた直後に発行する `frame_updated` シグナルで駆動されます。パーツの変換が確定した後・描画の前なので、追従先は**同じフレーム内**で更新されます。どのプロセスで発火するかは、プレーヤの `animation_process_mode`(`Idle`(既定)/ `Physics`)に従います。

Godot の `Transform2D` は 2×3 のアフィン変換をそのまま保持できるため、`update_position` / `update_rotation` / `update_scale` が**すべて ON** のときは変換を丸ごと代入します。この場合は**せん断(Skew)や負のスケールも含めて厳密に一致**し、対象をプレーヤの子に置いても別階層に置いても差はありません。

1 つでも OFF にすると、`RemoteTransform2D` と同じく有効な成分だけを個別に書き込むため、せん断は保持されません。既定は `update_scale` のみ OFF なので、**既定では位置と回転だけが反映されます**。

> **別階層の対象は 1 フレーム遅れることがあります。** 姿勢は駆動時点のプレーヤの `global_transform` を使って書き込むため、その後にプレーヤ自身を動かしても、対象が追随するのは次のフレームです。`SpriteStudioPartAttachment2D`(とその子)をプレーヤの子に置いた場合は、シーンツリーの継承で常に追随します。

> **`RigidBody2D` を追従対象にしないでください。** 毎フレーム transform を直接書き換えるとソルバがテレポートとして扱い、物理が破綻します。動く床のように他の剛体を押す必要がある場合は、Godot 標準の `AnimatableBody2D`(`sync_to_physics` を ON)を対象にし、プレーヤの `animation_process_mode` を `Physics` にして物理フレームで駆動してください。当たり判定を運ぶだけなら `Area2D` / `StaticBody2D` で十分です。

### 注意点

- **対象の `visible` はアタッチメントが操作します。** 次の 2 つの場合に自動で非表示になり、条件が解消すると自動で再表示されます。ユーザー側で設定した表示状態は上書きされることがあります。
- パーツ名がアセットに存在しない(この場合は `On Part Hidden` の設定に関係なく常に非表示)
- パーツが hide のフレームで、かつ `On Part Hidden` が `Hide Target`
- パーツ名は、そのプレーヤ自身が読み込んでいる `.ssab` のパーツから解決されます。**インスタンスパーツの内部(子アニメーション)のパーツは指定できません**(インスタンスパーツ自体は指定できます)。
- パーツ名の解決はアセット(`.ssab`)単位で、アニメーションには依存しません。`.ssab` を差し替えると自動で解決し直されるため、設定をやり直す必要はありません。
- 同名のパーツが複数ある場合は、最初に見つかったものが使われます。
- 追従するのは**空間的な変換だけ**です。描画順(Z 順)は追従しないため、追従させたノードが SpriteStudio のパーツの「間」に自動で挟まることはありません。前後関係が必要な場合は `z_index` などで別途調整してください。
- 対象にできるのは `Node2D` 系のノードです。`Control`(UI)はアンカーと矩形でレイアウトされるため対象にできません。
- 編集モードでも、プレビュー再生やフレームのスクラブでプレーヤが更新されれば、そのタイミングで追従します。

---

## パーツオーバーライド(パーツカラー / セル / 表示指定)

パーツ単位のランタイムオーバーライドは、「このパーツを**今**この色に / このセルに / 非表示に」とスクリプトから指示する機能です。オーバーライドはキーフレームやアニメーションのブレンドよりも優先されるので、アニメーションと取り合いになりません。
Expand Down