unity-sdk: Hapbeat Unity SDK — usage, specification, and samples (with shared concepts). # アーキテクチャ全体像 > Hapbeat SDK を構成する Studio / Helper / SDK / Firmware / Contracts の役割分担と、設計フロー・実行フローの 2 系統を俯瞰する。 このページは Hapbeat SDK エコシステム全体の **設計判断と役割分担** を説明します。「どのコンポーネントが何をしないか」も含めて理解すると、自分のユースケースでどのツールを使えばよいかが見えてきます。 ## 全体図 [Section titled “全体図”](#全体図) ![Hapbeat の構成図。左側が設定・デザインフロー(Studio → Helper → Hapbeat デバイス、PC 経由)、右側がゲーム / アプリ実行フロー(Unity SDK / Quest / PC / スマートフォン → Wi-Fi UDP → Hapbeat デバイス、直結)](/_astro/hapbeat-sdk-architecture.CwZIpktE_rS38z.svg) Hapbeat には **2 つの独立したフロー** があります: * **設定・デザインフロー** — PC で Kit や UI を設計してデバイスに書き込む(Studio + Helper 経由) * **実行フロー** — ゲーム / アプリが Hapbeat に触覚を発火する(SDK 直結、Helper 不要) 実行時は **Studio も Helper も不要** です。これが「クラウド必須にしない」「オフラインで動く」設計判断の根本です。 ## コンポーネント一覧 [Section titled “コンポーネント一覧”](#コンポーネント一覧) | コンポーネント | 種別 | 役割 | 動作環境 | | --------------------------- | ---------- | -------------------------------------------------------------- | ---------------------- | | **Hapbeat デバイス** | ハードウェア | ESP32 固定ランタイム。Wi-Fi STA/SoftAP / UDP 受信 / Kit ローカル再生 / OLED 表示 | 自走 | | **hapbeat-device-firmware** | ファームウェア | デバイスに焼かれる固定ランタイム。ユーザーは書き換え不要(OTA で更新) | デバイス上 | | **hapbeat-contracts** | 仕様 | プロトコル・Kit 形式・アドレッシング規約の単一情報源 | docs / spec | | **Hapbeat Studio** | Web アプリ | Kit 設計・UI 設定・Wi-Fi 設定・ファーム書込みを GUI で行う | ブラウザ (Chrome/Edge) | | **hapbeat-helper** | CLI daemon | PC 上の常駐デーモン。Studio ↔ デバイス間を mDNS / UDP / TCP / Web Serial で中継 | PC (Mac/Win) | | **hapbeat-unity-sdk** | UPM パッケージ | Unity から触覚イベントを発火する SDK | Unity Editor / Runtime | | **hapbeat-unreal-sdk** | UE5 プラグイン | Unreal Engine 5 から触覚イベントを発火する SDK | Windows / UE5 | | **hapbeat-creative-kit** | ツール群 | OSC / VJ など創作向け SDK(実装予定) | — | ## 役割境界(やらないこと) [Section titled “役割境界(やらないこと)”](#役割境界やらないこと) 設計が「やらないこと」を明示することで責務を絞っています。 * **Studio / Helper はランタイムには関与しない** — ゲーム実行中に Studio が落ちていても触覚は問題なく出る * **デバイスは Wi-Fi UDP を直接受ける** — Bridge は標準経路ではなく、上位オプション(ESP-NOW 経由) * **SDK はプロトコル仕様を独自定義しない** — すべて hapbeat-contracts に従う * **ユーザーはファームを書き換えない** — コンテンツの差し替えは Kit のデプロイで行う(OTA でファーム本体更新は可能) * **クラウドサービスに依存しない** — App ID / API Key / 中央サーバー必須の方式は採らない ## 依存関係 [Section titled “依存関係”](#依存関係) ```plaintext hapbeat-contracts(仕様の起点) ├─ hapbeat-device-firmware ├─ hapbeat-helper ├─ hapbeat-studio ├─ hapbeat-unity-sdk ├─ hapbeat-unreal-sdk └─ hapbeat-creative-kit (WIP) ``` 仕様変更はまず contracts に入れ、その後で各実装 repo へ反映されます。 ## SDK 利用者の視点 [Section titled “SDK 利用者の視点”](#sdk-利用者の視点) 実行フローだけを使う場合(ゲーム開発者の通常のケース): ```plaintext あなたのゲーム / アプリ └─ Unity SDK (or Unreal / Creative Kit) └─ Wi-Fi UDP (既定は unicast) └─ Hapbeat デバイス(事前に Studio で設定済み) ``` セットアップ済みの Hapbeat デバイスと同じ Wi-Fi に PC やスマホ・Quest を繋ぐだけで、SDK が触覚を送れます。Helper や Studio の起動は不要です。 ## 関連 [Section titled “関連”](#関連) * [通信モデル: Wi-Fi UDP / ESP-NOW](./communication-model/) — なぜ Wi-Fi UDP unicast が既定なのか * [Event ID と Kit の関係](./event-id-and-kit/) — 触覚資産の単位 * [gain の乗算構造](./gain-architecture/) — Studio と SDK の責務分離 # 通信モデル > Hapbeat の標準通信経路(Wi-Fi UDP unicast)の性能・制約・選び方と、上位オプション(ESP-NOW)の使いどころ。 Hapbeat の触覚イベントは **Wi-Fi の UDP unicast** でデバイスに届く。SDK が発見済みデバイスの IP へ 1 台ずつ送り、各デバイスはパケット内の **target(player / group)** を見て自分宛てかを判定する。 **この経路は既定で有効であり、設定は不要**。環境に応じて切り替える必要もない。 ## 経路の選び方 [Section titled “経路の選び方”](#経路の選び方) | 規模・要件 | 使うもの | | --------------------- | --------------------------------------------------------------------------------------- | | 〜20 台(大半のプロジェクト) | **Wi-Fi unicast**(既定・設定不要) | | 厳密な同時発火が要る | Wi-Fi unicast + `target_time` | | 数十台以上、または Wi-Fi が使えない | ESP-NOW([後述](#%E4%B8%8A%E4%BD%8D%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3-esp-now)) | ## 台数と同時性 [Section titled “台数と同時性”](#台数と同時性) unicast は台数分を順に送るため、先頭の機体と最後の機体に時間差が出る。 | 台数 | 先頭と末尾の差 | | ----- | --------- | | 2 台 | 0.2〜0.5ms | | 5 台 | 0.8〜2ms | | 10 台 | 1.5〜4ms | | 20 台 | 3〜8ms | | 50 台 | 8〜20ms | | 100 台 | 15〜40ms | 触覚の同時性は概ね **10〜20ms 以内なら知覚されにくい**とされる。**20 台までは実用上問題なく**、50 台で条件次第、100 台では差を感じ得る領域に入る。 ### 厳密な同時性が要る場合 [Section titled “厳密な同時性が要る場合”](#厳密な同時性が要る場合) **`target_time`(予約再生)** を使う。少し先の時刻を指定して全台に送れば、到着順に関係なく同時に鳴る。SDK は「今すぐ」ではなく「100ms 後に発火」と送り、デバイスは時刻同期した自分の時計でその時点に再生するため、ネットワークの揺らぎがあっても発火タイミングが安定する。 ただし送信側とデバイスの時計合わせが必要なため、**大規模な同時駆動では個別の検討を要する**。 ## 制約 [Section titled “制約”](#制約) * **同一サブネット必須** — デバイス発見(mDNS)はルーターを越えない * **2.4 GHz Wi-Fi のみ**(ESP32 の制約) * **VR HMD は AP 機能を持たない** — ルーターなし環境では Hapbeat 自身が SoftAP になる * **発見前のデバイスには unicast できない** — 既知デバイスが 0 台のときは broadcast にフォールバックするため、起動直後でも触覚は届く ## 接続シナリオ [Section titled “接続シナリオ”](#接続シナリオ) | シナリオ | 構成 | 用途 | | ---------------------- | ------------------------------------- | ----------------- | | **A. 単独プレイヤー LAN**(推奨) | 通常ルーター経由 | 自宅 / オフィス | | **B. マルチプレイヤー LAN** | ルーター経由、プレイヤー毎に固有 player / group | 同一 LAN で複数人プレイ | | **C. モバイルホットスポット** | スマホ / PC テザリング(2.4 GHz 固定) | 移動先・出張 | | **D. Hapbeat SoftAP** | Hapbeat 1 台が AP、HMD + 他 Hapbeat が STA | ルーターなし環境(Quest 等) | | **E. 展示ブース隔離** | ブースごとに独立 AP、B と同等 | イベント・展示会 | 詳細: [Hapbeat を初期設定する](/docs/tools/studio/initial-setup/) / [Hapbeat 概要](/docs/hardware/overview/#softap-%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AE%E5%88%87%E6%9B%BF) ## 上位オプション: ESP-NOW [Section titled “上位オプション: ESP-NOW”](#上位オプション-esp-now) 数十台同時、または Wi-Fi が使えない環境では **ESP-NOW** 経路を使う。 ```plaintext SDK / アプリ ↓ UDP / OSC hapbeat-bridge (PC / ホスト) ↓ シリアル hapbeat-transmitter-firmware (ESP32 送信機) ↓ ESP-NOW (2.4 GHz radio, AP 不要) Hapbeat デバイス(複数台一斉) ``` **AP を経由しないため 1 回の送信で全台に同時に届き、台数に依存しない。** 代わりに ACK / 再送が無いので、時間的に分散させた冗長送信で loss を補う設計としている。ルーターも AP も不要。 通常の用途では Wi-Fi unicast で足りるため、ESP-NOW は「大規模パフォーマンス」「Wi-Fi 不在環境」「Hapbeat 専用ネットワークを組みたい」場合に採用する。 *** ## 設計背景 [Section titled “設計背景”](#設計背景) 以下は仕組みを知りたい読者向けの補足であり、通常の導入では読む必要はない。 ### なぜ broadcast ではなく unicast か [Section titled “なぜ broadcast ではなく unicast か”](#なぜ-broadcast-ではなく-unicast-か) Wi-Fi の broadcast(group-addressed フレーム)には、送信側では回避できない遅延要因がある。 * **DTIM バッファリング** — 同じ AP に省電力状態の端末が **1 台でもいると**、AP は group-addressed フレームを次の DTIM ビーコンまで保留する。周期は **100〜300ms 級**で、送信側からは制御できない。Hapbeat 本体は省電力を無効化しているが、保留を起こすのは周囲の無関係な端末(スマホ等)であり、本体側の設定では防げない * **ACK / 再送が無い** — broadcast は最低基本レートで送られ、loss しやすく電波占有時間も長い * **unicast は速い** — MAC 層の ACK + 再送があり、リンクレートで飛ぶ。10 台程度までは電波占有時間の合計も broadcast より短い したがって「専用 AP なら broadcast で十分」ではなく、**どの環境でも unicast が最良**となる。 **大規模なら broadcast が有利ではないか** — 電波の占有時間だけを見れば、台数が増えるほど broadcast が効率的(unicast は台数分の O(N)、broadcast は O(1))。ただしその効率と引き換えに、上記の DTIM 遅延と再送なしの取りこぼしを受け入れることになる。「多数へ同時に確実に」を狙う場面ほど、この 2 つが致命的になる。ESP-NOW が推奨になるのは、**AP を介さないため DTIM が構造上存在せず、かつ 1 回の送信で全台に届く**から。broadcast の利点だけを取り、欠点を捨てた形になっている。 **AP の性能では解決しない** — DTIM 保留は帯域や処理能力の問題ではなく、省電力端末を起こさないための規格上の動作である。省電力の端末を 1 台も入れず、ビーコン間隔と DTIM 周期を詰められる完全な管理下のネットワークなら保留は避けられるが、そこまで管理できるなら AP を挟まない ESP-NOW の方が構成も単純で有利。 このため broadcast は**選択肢ではなくフォールバック**として扱う。SDK も、既知デバイスが 0 台のときだけ自動で broadcast に落ちる。 ### なぜアプリ層で ACK しないか [Section titled “なぜアプリ層で ACK しないか”](#なぜアプリ層で-ack-しないか) > **触覚は「遅れて届くより消えた方がマシ」** ゲーム中の効果音は、200ms 後に届くより、その回だけ脱落するほうが体験を壊さない。アプリ層の ACK / 再送は遅延変動を大きくするため、Hapbeat は固定遅延を優先する(unicast では MAC 層の ACK / 再送が効く)。 ### なぜ Bluetooth を主経路にしないか [Section titled “なぜ Bluetooth を主経路にしないか”](#なぜ-bluetooth-を主経路にしないか) v1 では Bluetooth を使っていたが、ペアリング管理の煩雑さ、ブロードキャストの不得手さ、PC / Quest / スマホでの API 分断、同時接続数の制約から Wi-Fi UDP に移行した。現行 BT 版は v1 互換維持のために残っているが、新規ユーザーは Wi-Fi 版(Duo WL / Band WL)が前提となる。 ## 関連 [Section titled “関連”](#関連) * [アーキテクチャ全体像](/docs/concepts/architecture/) * [Address の仕組み](/docs/concepts/group-player-addressing/) * [Hapbeat を初期設定する](/docs/tools/studio/initial-setup/) # Contracts 概要 > Event ID・Kit・Group ID など、Hapbeat SDK を使う上で知っておくべき基本概念のリファレンス。 Hapbeat SDK を使ううえで登場する主要な概念を説明します。 ## Event ID [Section titled “Event ID”](#event-id) 触覚コンテンツを識別する文字列です。SDK はこの ID を送信するだけで Hapbeat が対応する触覚を再生します。 **形式**: `.` ```plaintext basic-exam-kit.sine_100hz_1s my-game.sword-hit my-game.footstep-grass ``` * `kit-name` は Kit のフォルダ名(スペースなし、ハイフン区切り推奨) * `clip-name` は Kit 内の WAV ファイル名(拡張子なし) * Unity SDK では EventMap ウィンドウで管理し、自動で合成されます ## Kit [Section titled “Kit”](#kit) 触覚コンテンツのパッケージです。**WAV ファイル群 + manifest.json** で構成されます。 ```plaintext my-game/ ← Kit フォルダ(= kit-name) manifest.json ← メタデータ・Event 一覧 install-clips/ sword-hit.wav ← FIRE モード用クリップ footstep-grass.wav stream-clips/ ← CLIP モード用(Studio が自動配置) ``` ### manifest.json の主なフィールド [Section titled “manifest.json の主なフィールド”](#manifestjson-の主なフィールド) ```json { "name": "my-game", "version": "1.0.0", "events": [ { "id": "my-game.sword-hit", "clip": "sword-hit.wav", "mode": "command", "intensity": 0.8 } ] } ``` | フィールド | 意味 | | ----------- | -------------------------------------- | | `name` | Kit 名(Event ID のプレフィックスになる) | | `id` | Event ID(`.` 形式) | | `mode` | `command`(FIRE)または `stream_clip`(CLIP) | | `intensity` | 基本強度 0.0〜1.0(SDK の gain と乗算される) | Kit は **Studio で作成・編集** し、Helper 経由で Hapbeat にデプロイします。 ## 再生モード [Section titled “再生モード”](#再生モード) | モード | SDK 表記 | 動作 | | ---- | ------------- | ---------------------------------------- | | FIRE | `command` | Kit をデバイスに事前デプロイ → 短いコマンドを送るだけで再生。低遅延・安定 | | CLIP | `stream_clip` | WAV を PC からリアルタイムストリーミング。デプロイ不要、長尺対応 | 詳細: [Fire と Clip の比較](/docs/unity-sdk/fire-vs-clip/) ## Group ID と Player 番号 [Section titled “Group ID と Player 番号”](#group-id-と-player-番号) 同じ空間に複数のプレイヤーや展示ブースを混在させる場合に使います。 | 概念 | 用途 | 範囲 | | ------------- | ----------------------------------------- | ---- | | **Group ID** | デバイスを論理グループに分類。同じ Group のデバイスだけがコマンドを受け取る | 1〜99 | | **Player 番号** | 同一グループ内での個体識別 | 1〜99 | **例**: プレイヤー A の Group=1、プレイヤー B の Group=2 に設定すると、A の SDK からの送信は A の Hapbeat だけに届きます。 設定は Studio の Devices タブで行います。デバイスのボタン操作でも ±1 できます。 ## 通信プロトコル [Section titled “通信プロトコル”](#通信プロトコル) 標準の通信経路は **WifiUdp(`wifi_udp`)** です。SDK は既知デバイスへ unicast し、既知デバイスが 0 台のときだけ Wi-Fi UDP broadcast を送信します。Hapbeat は自身の Group ID に一致するパケットだけを処理します。 ```plaintext SDK(PC / Quest / スマートフォン) └─ Wi-Fi UDP unicast(既知 0 台時は broadcast)─→ Hapbeat デバイス(Group ID でフィルタ) ``` Bridge や USB 接続は不要です。同じ Wi-Fi ネットワークに繋がっていれば動作します。legacy `hapbeat-bridge` は現行非対応で、再利用しません。 # Event ID と Kit > Hapbeat の触覚資産を束ねる「Kit」と、SDK が発火する「Event ID」の関係と命名規則。 Hapbeat の触覚コンテンツは **Kit** という単位でまとめられ、SDK は **Event ID** という文字列でクリップを指定して発火します。このページは両者の構造と命名規則を扱います。 仕様の正式定義は [Contracts: kit-format](https://github.com/Hapbeat/hapbeat-contracts/blob/master/specs/kit-format.md) / [event-id](https://github.com/Hapbeat/hapbeat-contracts/blob/master/specs/event-id.md) を参照。 ## Kit = 触覚資産のフォルダ [Section titled “Kit = 触覚資産のフォルダ”](#kit--触覚資産のフォルダ) Kit は **触覚波形 (WAV) + メタデータ (`-manifest.json`)** を 1 つにまとめたフォルダです。Studio で作成し、Helper 経由で Hapbeat デバイスに転送します。 ```plaintext my-game/ ← Kit フォルダ (= kit-name) my-game-manifest.json ← イベント定義・基準 intensity install-clips/ ← Fire (command) 用 WAV sword-hit.wav footstep-grass.wav stream-clips/ ← Clip (stream) 用 WAV bgm-tension.wav ``` `install-clips/` と `stream-clips/` の使い分けは [Fire と Clip の違い](./fire-vs-clip/) を参照。「install されてデバイスに残る」「stream として都度送る」という動詞対比で覚えると直感的です。 > **manifest ファイル名**: host 側 (Studio / Unity SDK / Helper) では `-manifest.json` を正とし、デバイスの LittleFS に転送される際だけ `manifest.json` に rename されます。複数 Kit を 1 プロジェクトに置いたとき OS Explorer や SDK の picker で識別性を上げるための規約です。 ## manifest の主なフィールド [Section titled “manifest の主なフィールド”](#manifest-の主なフィールド) manifest は **`events` (Fire 用) と `stream_events` (Clip 用) の 2 つの bucket** で構成されます。各 bucket は Event ID をキーとしたオブジェクト (辞書) です。同一の Event ID を両 bucket に置くことで「同じ意味的イベントを Fire でも Clip でも再生する (= BOTH モード)」を表現できます。 ```json { "schema_version": "2.0.0", "name": "my-game", "version": "1.0.0", "target_device": { "firmware_version_min": "0.1.0", "board": "duo_wl_v3" }, "events": { "my-game.sword-hit": { "clip": "sword-hit.wav", "parameters": { "intensity": 0.8, "device_wiper": 64 } } }, "stream_events": { "my-game.bgm-tension": { "clip": "bgm-tension.wav", "parameters": { "intensity": 0.5, "loop": true } } } } ``` | フィールド | 必須 | 意味 | | -------------------------------------- | -- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `schema_version` | ✓ | manifest スキーマのバージョン。現行 `"2.0.0"` | | `name` | ✓ | Kit 名。**on-disk のフォルダ名と同じ文字列を使う**。wire 上の `kit_id` payload と同値 ([DEC-028](https://github.com/Hapbeat/hapbeat-sdk-workspace/blob/master/docs/decision-log.md#DEC-028)) | | `version` | ✓ | Kit のバージョン (semver 推奨) | | `target_device.firmware_version_min` | ✓ | 必要なファームウェアの最低バージョン | | `target_device.board` | — | 想定基板識別子 (例: `duo_wl_v3` / `neck_wl_v2`)。ファームのメタ情報と食い違うと warning | | `events` | ✓ | **Fire (command)** イベントの辞書。device の event table を構成し、PLAY/STOP packet の `event_id` フィールドにこのキーが乗る | | `stream_events` | — | **Clip (stream)** イベントの辞書。SDK が UDP audio stream で送信。device は eventId を認識せず、SDK 内部の binding ラベルとしてのみ使う | | `[].clip` | ✓ | bucket に対応する WAV のファイル名 (bare filename)。`events..clip` → `install-clips/`、`stream_events..clip` → `stream-clips/` として自動解決 | | `[].parameters.intensity` | — | 基準振動強度 0.0〜1.0 ([gain の乗算構造](./gain-architecture/) の基準値) | | `events[].parameters.device_wiper` | — | MCP4018 wiper 値 (0..127)。authoring 時の強度を再現するための参照情報。`stream_events` 側は対象外 | | `[].parameters.loop` | — | ループ再生の有無 (default `false`) | > **schema 2.0.0 で `mode` フィールドは廃止**されました ([DEC-031](https://github.com/Hapbeat/hapbeat-sdk-workspace/blob/master/docs/decision-log.md#DEC-031))。entry がどちらの mode で再生されるかは **入っている bucket** で決まります。同じ Event ID を両 bucket に置けば BOTH モード。完全な schema は [kit-format spec](https://github.com/Hapbeat/hapbeat-contracts/blob/master/specs/kit-format.md) を参照。 ## Event ID の命名規則 [Section titled “Event ID の命名規則”](#event-id-の命名規則) Event ID は触覚イベントを一意に識別する文字列です。正準フォーマットは [event-id spec](https://github.com/Hapbeat/hapbeat-contracts/blob/master/specs/event-id.md) (DEC-040) が定義します。 ```plaintext . ``` * **``** — その clip が属する Kit の `name`(上の manifest の `name`・on-disk フォルダ名・wire の `kit_id` と同値) * **``** — Kit 内の clip ファイル名から拡張子 `.wav` を除いた basename * 区切りは **`.` (ドット) 1 個**。`` が名前空間を兼ねるため、異なる Kit 間で ID が衝突しません | 規則 | 値 | | ---------------- | ----------------------------------------------- | | `` | `^[a-z][a-z0-9-]*$`(英小文字始まり・英数字・ハイフン。アンダースコア不可) | | `` | `^[a-z][a-z0-9_-]*$`(英小文字始まり・英数字・アンダースコア・ハイフン) | | 区切り文字 | `.` (ドット) 1 個のみ | | 大文字 | 使用不可(小文字のみに正規化) | | `` 先頭 | 英字始まり(`100hz` ではなく `sine_100hz`) | | ID 全体の最大長 | 255 文字 | 実例: ```plaintext sample-kit.sine_100hz ← 公式の疎通確認イベント (sample-kit) showcase-kit.z1_pin_hit ← showcase-kit の clip z1_pin_hit.wav my-game.sword_slash ← my-game kit の clip sword_slash.wav ``` Event ID は **Studio が Kit 名 + clip ファイル名から自動合成**します(ユーザーが手で組み立てません)。`sample-kit` / `showcase-kit` / `hapbeat-*` は Hapbeat 公式用途に予約されており、コンテンツ開発者は使用しません。 ## 「フォルダ名 = manifest.name = wire 上の kit\_id」を一本化した理由 (DEC-028) [Section titled “「フォルダ名 = manifest.name = wire 上の kit\_id」を一本化した理由 (DEC-028)”](#フォルダ名--manifestname--wire-上の-kit_idを一本化した理由-dec-028) 過去は `manifest.kit_id` と `manifest.name` の 2 フィールドがあり、表記揺れ (`Basic Exam Kit` vs `basic-exam-kit`) で Studio 表示と OS Explorer 表示が一致しない事故が起きていました。2026-04-28 の DEC-028 で `kit_id` を削除し **`name` 1 つに集約**。フォルダ名・JSON・wire payload すべてに同じ文字列を使うルールに統一しました。 wire-protocol 上の `kit_id` field 自体は引き続き存在しますが、その値は常に manifest の `name` と等しいことが物理的に保証されています。 ## デプロイの流れ [Section titled “デプロイの流れ”](#デプロイの流れ) 1. Studio の Library で Kit を編集 (intensity 調整、Fire / Clip / BOTH 切替、clip 追加) 2. Studio が `-manifest.json` と WAV をワーキングディレクトリに書き出し 3. Helper 経由でデバイスの Kit パーティションに転送 (`events` 由来 = Fire 用 WAV のみ。manifest は wire 上で `manifest.json` に rename) 4. `stream_events` 由来の WAV はデプロイ対象外 — 実行時に SDK が直接 UDP で送信 ## 関連リンク [Section titled “関連リンク”](#関連リンク) * [Fire と Clip の違い](./fire-vs-clip/) — mode の選び方 * [gain の乗算構造](./gain-architecture/) — `intensity` が乗算チェーンのどこに入るか * [Kit を作って配布する](/docs/tools/studio/kit-design/) — Studio で Kit を作る手順 (howto) * [Mode を切り替える](/docs/tools/studio/modes/) — Studio UI 上での mode 切替 # Fire と Clip の違い > 触覚イベントの送信モード「Fire (command)」と「Clip (stream)」の本質的な違いと、どちらをどのケースで選ぶか。 Hapbeat の触覚は、Event ごとに **Fire** か **Clip** のどちらで送るかを選択します。同じ触覚体験を実現する道筋が複数あるので、**遅延・帯域・柔軟性のトレードオフ** を踏まえて選ぶことになります。このページは「どちらを選ぶか」の判断材料を 1 か所にまとめます。 ## 2 つの送信方式 [Section titled “2 つの送信方式”](#2-つの送信方式) manifest 上では Event が **どちらの bucket に入っているか** で送信方式が決まります ([DEC-031](https://github.com/Hapbeat/hapbeat-sdk-workspace/blob/master/docs/decision-log.md#DEC-031))。 | 通称 | manifest bucket | wire 上 | 送信内容 | 主な用途 | | -------- | ------------------------ | ------------------------------------------ | ------------------------- | ---------------- | | **Fire** | `events` (command) | PLAY/STOP packet に Event ID | コマンド (数バイト) | 短い one-shot 効果音 | | **Clip** | `stream_events` (stream) | STREAM\_BEGIN / STREAM\_DATA / STREAM\_END | AudioClip 由来の PCM ストリーミング | 長尺・動的変調・プロトタイピング | 同じ Event ID を **両 bucket に置く** ことで「Fire / Clip どちらでも再生できる (= BOTH モード)」を表現できます。Studio の EventMap UI では FIRE / CLIP / BOTH のラジオで切り替えます。 > schema 1.x までは entry に `mode: "command" | "stream_clip" | "stream_source"` フィールドがありましたが、schema 2.0.0 で bucket 分離に置き換わり、`mode` フィールドと `stream_source` mode は廃止されました。 ## Fire (command) と Clip (stream) の比較 [Section titled “Fire (command) と Clip (stream) の比較”](#fire-command-と-clip-stream-の比較) | | **Fire** (`events` bucket) | **Clip** (`stream_events` bucket) | | ------------ | ---------------------------- | --------------------------------------------------------- | | 事前デプロイ | 必要 (Kit を install-clips に焼く) | 不要 | | wire 上を流れるもの | Event ID + パラメータの **数バイト** | PCM chunk (`STREAM_BEGIN` / `STREAM_DATA` / `STREAM_END`) | | 遅延 | 数 ms / 安定 | 数十 ms / 環境依存 | | 長さ | 数 ms 〜 数秒 (Kit パーティション容量内) | 任意 (数十秒・ループ可) | | 動的制御 | `gain` のみ (発火ごとに固定) | 再生中に `gain` / pan を変調可能 | | 停止の即時性 | 即時 | 既送 buffer 分は再生されきる | | 無線帯域消費 | ごく小 | 連続消費 | ## Fire モード [Section titled “Fire モード”](#fire-モード) Kit の WAV をデバイスに **事前デプロイ** しておき、SDK は Event ID と少しのパラメータだけを送ります。デバイス側は受信したコマンドに対応する WAV を自分のストレージから再生します。 ### 強み [Section titled “強み”](#強み) * **遅延が低く安定** — wire 上を流れるのが小さなコマンドだけなので、Wi-Fi が混雑していても再現性が高い * **無線帯域を消費しない** — 大量同時発火でも輻輳しにくい * **停止コマンド (`stop`) も即時反映** ### 弱み [Section titled “弱み”](#弱み) * **事前にデプロイが必要** — Kit を作って Studio から書き込む手順が入る * **Kit パーティション容量に依存** — install-clips/ は数 MB の制約 * **動的に波形を変えられない** — `gain` を変えるだけなら可能だが、波形そのものは固定 ### 向いている用途 [Section titled “向いている用途”](#向いている用途) * ボタン押下感、銃撃音、衝撃音、足音などの **短い one-shot 効果音** * ゲーム本番、XR インタラクション、量産展示など **安定再現が必須** な場面 ## Clip モード [Section titled “Clip モード”](#clip-モード) SDK 側に置いた AudioClip 等を PCM データに変換し、`STREAM_BEGIN` → `STREAM_DATA` × N → `STREAM_END` の UDP メッセージ列としてデバイスに送ります。事前デプロイ不要。device は eventId を認識せず、stream session 単位で受信します。 ### 強み [Section titled “強み”](#強み-1) * **デプロイ不要で即試せる** — 波形を入れ替えながら試行錯誤できる (プロトタイピング向き) * **任意の長さに対応** — 数十秒の持続触覚やループも送れる * **再生中に動的変調** — `gain` を per-chunk で乗算するため、動的パラメータ (距離・速度・体力など) に追従できる ### 弱み [Section titled “弱み”](#弱み-1) * **Wi-Fi 環境依存** — ネットワークが不安定だと chunk drop で途切れる * **遅延が大きめ** — chunk 単位の buffering + 送信で数十 ms (Fire より一桁大きい) * **停止に少し遅れ** — 既に送信済みの buffer 分は再生されきってから止まる ### 向いている用途 [Section titled “向いている用途”](#向いている用途-1) * 開発中の **試作・確認** * **長尺の持続触覚** (BGM 的な背景振動・環境音) * **動的パラメータ連動** (擦り感の速度マッピング・距離減衰など) ## 判断フロー [Section titled “判断フロー”](#判断フロー) ```plaintext 触覚は数秒以内に収まる ? ├─ Yes │ └─ 本番運用 / Wi-Fi が混雑する環境 ? │ ├─ Yes → Fire ← 標準デフォルト │ └─ No (プロトタイピング段階) → Clip └─ No (長尺 / ループ / 動的変調が必要) └─ Clip ``` **典型ワークフロー**: プロトタイピングは Clip で試行錯誤 → 形が決まったら Fire に切り替えて Kit にコミット。Studio EventMap の FIRE / CLIP / BOTH ラジオで切り替えるだけで移行できます (BOTH のままにすれば Kit 配布後も両モードで再生可能)。 ## gain の扱いの違い [Section titled “gain の扱いの違い”](#gain-の扱いの違い) [gain の乗算構造](./gain-architecture/) は両モード共通ですが、**Clip では per-chunk で pre-multiply** されます。 | モード | gain 適用タイミング | | ---- | ----------------------------------------------- | | Fire | デバイス側でコマンド受信時に 1 回 | | Clip | SDK / Helper 側で PCM chunk 1 つごとに掛け合わせ → ストリーミング | このため Clip は **再生中に gain を変えると次の chunk から反映** されます (連続変調に向く)。Fire の途中で強度を動的に変えることはできません (新しい発火 = 新しいコマンドが必要)。 ## Studio UI と SDK API の対応 [Section titled “Studio UI と SDK API の対応”](#studio-ui-と-sdk-api-の対応) | | Studio 表示 | manifest 上の格納先 | Unity SDK API | | ---- | --------- | -------------------- | -------------------------------------------- | | Fire | `▶ FIRE` | `events.` | `HapbeatManager.Play(eventId, gain)` | | Clip | `♪ CLIP` | `stream_events.` | `HapbeatManager.StreamAudioClip(clip, gain)` | | BOTH | `▶♪ BOTH` | 両 bucket に同一 id | 上記両方 (用途に応じて使い分け) | ## Helper の役割 (補足) [Section titled “Helper の役割 (補足)”](#helper-の役割-補足) Clip のストリーミング自体は **SDK が直接デバイスに UDP を送信** すれば成立します。Helper は必須ではなく、Studio の再生テストや SDK 開発時のホスト側中継として使う **任意のツール** です。実行時の最小構成は SDK + Hapbeat デバイスだけです。 ## 関連リンク [Section titled “関連リンク”](#関連リンク) * [Mode を切り替える](/docs/tools/studio/modes/) — Studio で Mode を切り替える手順 (howto) * [Fire と Clip — 使い分けと実装](/docs/sdk-integration/unity-sdk/fire-vs-clip/) — Unity 実装でのコード例 * [Streaming buffer を調整する](/docs/sdk-integration/unity-sdk/streaming/) — Clip モードの buffering / 遅延チューニング * [gain の乗算構造](./gain-architecture/) — gain がどの段で乗算されるか # gain の乗算構造 > Hapbeat の振動強度は「Studio で決めた基準 intensity × SDK の gain」の乗算で決まる。Kit 設計者と SDK 利用者の責務を分離する設計判断。 Hapbeat の振動強度は **複数の gain の乗算** で決まります。これは Kit 設計者(コンテンツ側)と SDK 利用者(アプリ側)の責務を分離するための意図的な設計です。 ## 計算式 [Section titled “計算式”](#計算式) ```plaintext 実際の振動強度 = manifest の intensity × EventMap の gain × SDK の gain ``` * **manifest の intensity** — Studio で Kit に書き込んだ基準強度(0.0 〜 1.0) * **EventMap の gain** — Unity SDK 等の EventMap エントリで設定するオフセット * **SDK の gain** — 実行時にコードや ParameterBinding で動的に与える倍率 ## なぜ乗算なのか [Section titled “なぜ乗算なのか”](#なぜ乗算なのか) ### 役割分担 [Section titled “役割分担”](#役割分担) | 立場 | 触る gain | 視点 | | --------------------- | ---------------------------- | -------------------------------------------- | | **Kit 設計者**(触覚アーティスト) | manifest の intensity | 「銃声は強めに、足音は弱めに」 | | **SDK 利用者**(ゲーム開発者) | EventMap の gain / コード上の gain | 「銃と足音のバランスはアーティストに任せ、自分は『敵が遠いときは半分の強さ』を実装する」 | 両者が **同じパラメータを取り合わない** ことで、変更が独立します: * アーティストが Kit を更新しても、ゲームコードは変更不要 * ゲームコードで距離減衰を入れても、Kit の基準値は壊れない ### `gain = 1.0` の意味 [Section titled “gain = 1.0 の意味”](#gain--10-の意味) SDK の世界では `gain = 1.0` がデフォルトです。これは「Kit 設計者が決めた標準の強さで再生する」という意味になります。 * `0.5` → 半分の強さ * `2.0` → 2 倍の強さ(manifest が 0.5 の場合に 1.0 で再生したいとき等) * `0.0` → 無音(一時的にミュート) ## 具体例 [Section titled “具体例”](#具体例) Studio で `intensity: 0.8` に設定した銃声クリップ(`my-game.gunshot`)があるとします。 | ケース | EventMap gain | SDK gain | 最終強度 | | ----------------------- | ------------- | --------- | --------- | | 通常の発火 | 1.0 | 1.0 | 0.8 | | 「弱めに」(EventMap で調整) | 0.5 | 1.0 | 0.4 | | 距離減衰(Parameter Binding) | 1.0 | 0.3(敵が遠い) | 0.24 | | 演出ピーク(コードで一時ブースト) | 1.0 | 1.5 | 1.2(クリップ) | 最終強度はデバイス側で 0.0〜1.0 にクリップされます。 ## ParameterBinding と組み合わせる [Section titled “ParameterBinding と組み合わせる”](#parameterbinding-と組み合わせる) Unity SDK の `HapbeatParameterBinding` は、ゲーム中の値(速度・距離・体力等)を **SDK gain に動的マッピング** する仕組みです。これは上記の「SDK の gain」レイヤに作用します。 ```plaintext manifest 0.8 (Kit 設計時の標準) × EventMap 1.0 (この Event のオフセット) × Binding 出力 0.3 (敵との距離由来) = 実際の強度 0.24 ``` Kit 側を触らずに、ランタイムの状況に応じた強弱だけ動的に変えられます。 ## 何を変更すべきか [Section titled “何を変更すべきか”](#何を変更すべきか) 迷ったら以下の指針で: | やりたいこと | 触る場所 | | ------------------- | ------------------------------------------------ | | 「このクリップは全体的に強すぎる」 | Studio で manifest の intensity を下げる | | 「この Event だけ少し弱く」 | EventMap の gain を下げる | | 「ゲーム中の状況で強弱を変える」 | コード or ParameterBinding で SDK gain | | 「全 Event を一時的にミュート」 | `HapbeatManager.SetGlobalGain(0)` 相当(実装は SDK 次第) | ## 関連 [Section titled “関連”](#関連) * [Kit を作って配布する](/docs/tools/studio/kit-design/) — Studio での manifest intensity の決め方 * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — EventMap gain の編集 * [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) — 動的 gain マッピング * [Fire と Clip の違い](./fire-vs-clip/) — mode による gain の扱いの違い # 用語集 > Hapbeat SDK に登場する用語の定義集。Event ID / Kit / Group / Player / intensity / gain / Fire / Clip など。 各ドキュメントで使われる用語を 1〜2 行で定義します。**正式な仕様は [Contracts 概要](/docs/concepts/contracts/overview/) を参照** してください。 ## コアコンセプト [Section titled “コアコンセプト”](#コアコンセプト) **Hapbeat** : 触覚デバイス本体。現行モデルは **Duo WL** (首掛けワイヤレス) と **Band WL** (リスト / アンクル装着) の 2 種類。ESP32 を内蔵し、Wi-Fi UDP で触覚イベントを受信して再生する。 **Kit** : 触覚資産のフォルダ単位。`-manifest.json` + WAV 群で構成。Studio で作成し、Helper 経由で Hapbeat デバイスに転送する。詳細: [Event ID と Kit](./event-id-and-kit/) **Event ID** : 触覚イベントを識別する文字列。正準形式は `.`(例: `sample-kit.sine_100hz`)— Kit 名 + 拡張子を除いた clip ファイル名を `.` 1 個で繋いだもの。Studio が自動合成する。詳細: [Event ID と Kit](./event-id-and-kit/) **address** : パケットの宛先文字列。形式は `[prefix/] player_{N} / {position} [/group_{M}]`。詳細: [Address の仕組み](./group-player-addressing/) **Player 番号** : 同じプレイヤーに属する複数デバイスをまとめる単位。1..99。 **Group ID** : プレイヤー / ブース同士を分離する単位。同じ Wi-Fi 上で混信させないために使う。1..99 (省略時は全グループ受信)。 ## 強度・モード [Section titled “強度・モード”](#強度モード) **intensity** : Kit 設計時の基準振動強度。manifest の `events[].parameters.intensity` / `stream_events[].parameters.intensity` に 0.0〜1.0 で記録する。SDK 側 `gain` の **基準値 (× 1.0 の基準)** として働く。 **gain** : SDK 実行時の動的強度倍率。Unity SDK 等で EventMap や ParameterBinding 経由で与える。`gain = 1.0` で「Kit 設計者が決めた標準の強さ」を意味する。 **Fire** : 触覚送信方式の通称。manifest の `events` bucket に格納された Event を Event ID 指定で発火し、デバイス側にプリインストールされた波形を再生する。低遅延・安定で本番向き。詳細: [Fire と Clip](./fire-vs-clip/) **Clip** : 触覚送信方式の通称。manifest の `stream_events` bucket に格納された Event を SDK が PCM データに変換し、`STREAM_BEGIN`/`STREAM_DATA`/`STREAM_END` でストリーミングする。プロトタイピング・長尺・動的変調に向く。 **BOTH モード** : 同一の Event ID を `events` と `stream_events` の両 bucket に置く構成。発火時に Fire / Clip を使い分けできる。Studio EventMap の `▶♪ BOTH` ラジオで設定する。 **bucket (manifest)** : schema 2.0.0 ([DEC-031](https://github.com/Hapbeat/hapbeat-sdk-workspace/blob/master/docs/decision-log.md#DEC-031)) で導入された manifest 上の Event 格納区分。`events` (Fire 用 / device baked) と `stream_events` (Clip 用 / host streaming) の 2 種。Event が **どちらの bucket に入っているかで送信方式が決まる** (旧来の `mode` フィールドは廃止)。 ## ツール [Section titled “ツール”](#ツール) **Hapbeat Studio** : Web ベースの Kit デザイン + デバイス管理ツール。`studio.hapbeat.com/` で動作する SPA。 **hapbeat-helper** : Studio と Hapbeat デバイスを橋渡しする CLI daemon。`pipx install hapbeat-helper` で導入。`localhost:7703` の WebSocket と mDNS / UDP / TCP / Serial を中継する。アプリ実行時は不要。 **Contracts (hapbeat-contracts)** : 各 repo 間の規範的プロトコル仕様を集めた repo。Kit format / message protocol / display layout / device addressing 等の “単一情報源”。 **device firmware** : Hapbeat 本体に焼かれた ESP32 固定ランタイム。ユーザーは書き換え不要 (OTA で更新)。 ## ネットワーク [Section titled “ネットワーク”](#ネットワーク) **Wi-Fi UDP** : 標準通信経路。SDK は既定で発見済みデバイスへ unicast 送信し(既知 0 台のときは broadcast にフォールバック)、各 Hapbeat が address で自己フィルタする。中継サーバ不要。 **ESP-NOW** : 上位オプション通信経路。Bridge + Transmitter ファームウェア経由で AP 不要の独立網を構成する。大規模パフォーマンス / Wi-Fi 不在環境向け。 **SoftAP / STA** : Hapbeat が **AP 機能を持つ場合 (SoftAP)** とルーターに **STA として接続する場合**。VR HMD などルーターなし環境では Hapbeat 自身が SoftAP になる。 **targetTime** : 「N ms 後に発火」という将来時刻指定。ネットワーク揺らぎを吸収して発火タイミングを安定させる。 ## Kit と manifest [Section titled “Kit と manifest”](#kit-と-manifest) **install-clips/** : Fire (`events` bucket) 用 WAV を入れる Kit 内サブディレクトリ。デバイスに **install されて常駐** する意味。 **stream-clips/** : Clip (`stream_events` bucket) 用 WAV を入れる Kit 内サブディレクトリ。実行時に **stream として都度送る**。デバイスにはデプロイされない。 **target\_device** : manifest.json のフィールド。Kit が対象とする基板 (例: `duo_wl_v3` / `neck_wl_v2`) と最低ファームウェアバージョンを記録する。 **device\_wiper** : Hapbeat デバイスの MCP4018 デジタルポテンショメータの wiper 値 (0..127)。Kit / Event 調整時の音量設定を **再現性のための参照情報** として manifest に記録する。 ## 関連リンク [Section titled “関連リンク”](#関連リンク) * [アーキテクチャ全体像](./architecture/) — 各コンポーネントの役割と境界 * [Contracts 概要](/docs/concepts/contracts/overview/) — 仕様の正式定義 * [Contracts 概要](/docs/concepts/contracts/overview/) — API / コマンドのリファレンス # Address の仕組み > 複数 Hapbeat を 1 つのネットワークで同時に動かすための address 設計と、Group / Player の役割。 Hapbeat の通信は UDP で、送信側がどの配送手段(unicast / broadcast)を使うかに関わらず、**再生するかどうかはデバイス側が判断します**。複数のデバイスを同じネットワークに混在させたとき、**どのパケットを誰が再生するか** を決めるのが address です。このページは address 文字列の組み立て方と、その背景にある設計判断を説明します。 仕様の正式定義は [Contracts: device-addressing](https://github.com/Hapbeat/hapbeat-contracts/blob/master/specs/device-addressing.md) を参照。 ## address の形式 [Section titled “address の形式”](#address-の形式) ```plaintext [prefix/] player_{N} / {position} [/group_{M}] ``` * `prefix` — 任意 (0 段以上)。チーム / アプリ識別など自由文字列 (例: `red/alpha`) * `player_{N}` — **必須**。`N` は 1..99 * `{position}` — **必須**。装着部位を示す定義済み語彙 (例: `chest` / `band` / `left_upper_arm`) * `group_{M}` — 任意 (0 または 1 段)。**`{position}` の直後** に置く。`M` は 1..99 セグメント区切りは `/` (スラッシュ)、使用可能文字は `[a-zA-Z0-9_-]`、address 全体の最大長は 64 bytes (null 終端含む) です。 ### address 例 [Section titled “address 例”](#address-例) | 構成 | address | | -------------- | -------------------------------------------------- | | シンプル (1 人 1 台) | `player_1/chest` | | マルチプレイヤー | `player_1/chest`, `player_2/left_upper_arm` | | チーム制 | `red/player_1/chest` | | チーム + 小隊 | `red/alpha/player_3/chest` | | グループ分離 | `player_1/chest/group_1`, `player_1/chest/group_2` | `group_{M}` を **付けない address は全グループのデバイスが受信** します。1 人で使うときは省略して構いません。 ## Player と Group の役割 [Section titled “Player と Group の役割”](#player-と-group-の役割) | 概念 | 用途 | 値域 | | ------------- | ------------------------------------------------------------------------ | ----- | | **Player 番号** | 同じ “プレイヤー” に属する複数デバイスをまとめる単位。1 人が首掛けと腰の 2 台を装着する場合、両方に同じ player 番号を割り当てる | 1..99 | | **Group ID** | プレイヤー同士を分離する単位。同じ Wi-Fi 上の別グループ (別ブース・別チーム) が混信しないようにする | 1..99 | OLED 表示 (`Gr:01..99` / `P:01..99`) と整合させるため、Player / Group とも **`1..99` 固定** です ([DEC-030](https://github.com/Hapbeat/hapbeat-sdk-workspace/blob/master/docs/decision-log.md#DEC-030))。技術的には `uint8_t` で 255 まで保持できるため、将来必要になれば拡張可能です。 ## なぜデバイス側フィルタにしたか [Section titled “なぜデバイス側フィルタにしたか”](#なぜデバイス側フィルタにしたか) | 設計判断 | 理由 | | -------------------------- | ----------------------------------- | | **デバイス IP を管理しない** | DHCP で IP が変わってもアドレス指定の更新は不要 | | **1 台でも複数台でも送信コードが同じ** | 宛先テーブルを書き換えずに送り分けられる(配送手段は SDK が選ぶ) | | **PC / Quest / スマホで同一の動作** | 各プラットフォームの UDP socket API だけで完結 | | **Bridge / 中継サーバ不要** | アプリ起動だけで触覚が出る、オフラインでも動く | このため Hapbeat は「中央サーバが存在しない / クラウド不要」設計が成立しています。配送手段そのもの(既定が unicast である理由・台数と同時性)は [通信モデル](./communication-model/) を参照。 ## 接続シナリオでの使い分け [Section titled “接続シナリオでの使い分け”](#接続シナリオでの使い分け) | シナリオ | Player | Group | | ----------------------- | --------- | -------------- | | 単独プレイヤー LAN | 1 | (省略 — 全グループ受信) | | マルチプレイヤー LAN | プレイヤー毎に固有 | プレイヤー毎に固有 | | Hapbeat SoftAP (HMD など) | 1 | (省略) | | 展示ブース隔離 | 1 | ブース毎に固有 | 「**他人と混信させないために Group を使う**」「**1 人の複数装着を区別するために `{position}` を使う**」と覚えれば運用は単純です。 ## 歴史的経緯 [Section titled “歴史的経緯”](#歴史的経緯) 過去には address とは別に `target_group` (uint8) フィールドが wire protocol 上に存在していました。これは 2026-05-09 の [DEC-030](https://github.com/Hapbeat/hapbeat-sdk-workspace/blob/master/docs/decision-log.md#DEC-030) で廃止され、**Group は address 末尾の `/group_{M}` suffix に統合** されました。1 つの文字列で完結することで spec が単純化し、firmware / SDK / Studio の 3 リポでの整合性も取りやすくなりました。 ## 関連リンク [Section titled “関連リンク”](#関連リンク) * [アーキテクチャ全体像](./architecture/) * [通信モデル](./communication-model/) * [Contracts 概要](/docs/concepts/contracts/overview/) / [device-addressing 仕様](https://github.com/Hapbeat/hapbeat-contracts/blob/master/specs/device-addressing.md) # ターゲティング > どの Hapbeat に触覚を届けるかを決める Target と Address Override の共通ガイド。 Hapbeat のコマンドは、同じネットワーク上の device に届きます。どの device が再生するかは、コマンドに含まれる **Target** と、device が持つ **Address** の照合で決まります。 Address は 3 セグメントの正準形です。 ```text player_ / / group_ │ │ └ 一斉制御の単位(1〜99) │ └ 装着部位(pos_neck、pos_r_arm など) └ プレイヤー番号(1〜99) ``` Target は前方一致で照合され、`*` は 1 セグメント分の任意値に一致します。例えば `player_2` は player 2 の全 device に、`*/pos_neck` は全 player の neck に届きます。形式の詳細は[アドレス形式](/docs/concepts/group-player-addressing/)を参照してください。 ## 構成別の使い分け [Section titled “構成別の使い分け”](#構成別の使い分け) | 構成 | 分ける軸 | 設定 | | ------------------------ | -------------------- | ----------------------------- | | 送信 1 / 受信 1 種類 | 分けない | 既定のまま | | 送信 1 / 1 人が複数装着 | **position** | 部位ごとに Address を設定し、Target で指定 | | 送信 1 / 複数 player | **player** | 各 device に player 番号を割り当て | | 送信 N / 受信 N(1:1 の組が N 組) | **group** + override | 組ごとに同じ group を割り当て | | 複数送信元 / 受信混在 | **group** | 送信元ごとに group 範囲を分ける | ### 1 人が複数装着する場合 [Section titled “1 人が複数装着する場合”](#1-人が複数装着する場合) 部位の識別には `position` を使います。各 device の position を設定し、`*/pos_neck` や `*/pos_r_arm` のような Target を指定します。player を増やす予定があれば、player 軸をこの用途に使わない方が扱いやすくなります。 ### 複数 player に個別送信する場合 [Section titled “複数 player に個別送信する場合”](#複数-player-に個別送信する場合) 各 device に player 1、2、3 のような番号を割り当て、`player_1` のような Target を使います。送信する player を実行時に切り替える場合は Address Override を使います。 ### HMD と Hapbeat が 1:1 で複数組ある場合 [Section titled “HMD と Hapbeat が 1:1 で複数組ある場合”](#hmd-と-hapbeat-が-11-で複数組ある場合) LBE のように複数のペアが並ぶ場合は、`group` をペアの識別子にします。HMD 側の override と Hapbeat 側の Address に同じ group 番号を設定します。Event Map は共通のまま、同じビルドをすべての端末に配布できます。 ### 複数の送信元が混在する場合 [Section titled “複数の送信元が混在する場合”](#複数の送信元が混在する場合) device は送信元を区別しません。別アプリや別 PC が同じネットワークにある場合は、送信元ごとに group の範囲を分けます。例えば送信元 A は group 1〜10、B は group 11〜20 を使います。 ## Address Override [Section titled “Address Override”](#address-override) Address Override は、Event Map に記録した Target の player / group を**送信直前に上書き**します。各軸は独立して指定でき、off の軸は Event Map に記録した Target をそのまま使います。 これにより Event Map を複製せず、端末ごとの実行時設定だけで送信先を切り替えられます。複数端末へ同じビルドを配布する構成に使います。 | 単位 | 用途 | | --------------- | ----------------------------------------- | | **this build** | ビルド全体で固定する override。展示ごとに別ビルドを作る場合に使用 | | **this device** | 実行端末ごとに保持する override。1 本のビルドを複数端末へ配る場合に使用 | 各 SDK の設定場所と API は、その SDK のターゲティングページを参照してください。 ## 運用のヒント [Section titled “運用のヒント”](#運用のヒント) * Event Map を複数端末で使い回す場合、Target の player 軸は `*` にしておくと、override が off のときは全 player、設定済みのときは指定先へ送れます。 * `group` override を使う場合、Hapbeat 側の group 番号も同じ値にします。 * 既定の Address は `player_1` / `group_1` です。設定漏れとの衝突を避けたい展示では、運用番号を 2 以上から始めると確認しやすくなります。 ## 関連 [Section titled “関連”](#関連) * [アドレス形式](/docs/concepts/group-player-addressing/) * [送信経路と台数の目安](/docs/concepts/communication-model/) # AI 支援で組み込む > Claude Code などの AI コーディング支援ツールを使って、既存の Unity シーンに Hapbeat 触覚フィードバックを後付けする実践フロー。コピペ用プロンプト集つき。 Hapbeat SDK は GameObject / Trigger / EventMap のシンプルな構成なので、**AI コーディング支援ツール (Claude Code / Cursor / Codex / GitHub Copilot Workspace 等) との相性がよい** です。 このページでは、AI に「まっさらなシーン」を読ませて触覚フィードバックを設計・実装させる、4 ステップのワークフローと、各ステップで使えるおすすめプロンプトを示します。 > Hapbeat SDK 開発時に作者自身が Claude Code を使って `Showcase` / `XriHandDemoAugment` サンプルを実装したフローをそのまま外部ユーザー向けに整理したものです。 *** ## 必要な前提 [Section titled “必要な前提”](#必要な前提) * **Unity プロジェクト** が AI から読める状態 (Claude Code / Cursor 等を該当プロジェクトのルートで起動済み) * **Hapbeat SDK** インストール済み ([インストール手順](./installation.md)) * **触覚を載せたい既存シーン** が `Assets/` 配下に保存済み * AI が `.unity` シーンファイル (YAML) と SDK の Trigger / EventMap 定義を読める権限を持つこと > AI は基本的にエディタ操作を直接できません。**Editor 用 C# スクリプトを生成させて Unity 側でメニューから実行**、あるいは **手作業で配置するための具体的な手順を出力** させるのが定石です。 *** ## ワークフロー全体像 [Section titled “ワークフロー全体像”](#ワークフロー全体像) ```plaintext Step 1: シーン解析 (触覚候補の洗い出し) ↓ Step 2: EventMap 設計 (Event ID / gain / mode 決定) ↓ Step 3: Wiring 提案 (どの GameObject にどの Trigger を付けるか) ↓ Step 4: 実装 (Editor スクリプトで一括 wiring or 手動配置) ``` 各ステップを 1 プロンプトで終わらせるのではなく、**ステップごとに区切って AI と対話する** のがコツです。AI は触覚デザインの「正解」を持っていないので、各ステップで人間がレビュー・補正する前提でいきます。 *** ## Step 1: シーン解析プロンプト [Section titled “Step 1: シーン解析プロンプト”](#step-1-シーン解析プロンプト) AI に対象シーンを読ませて、触覚を付けると効果的なイベント候補を洗い出させます。 **プロンプト例:** ```plaintext Unity プロジェクト全体を見て、`Assets/Scenes/<対象シーン名>.unity` に対して Hapbeat 触覚 SDK で触覚フィードバックを付けると効果的なイベントを 5〜15 個 リストアップしてください。 各候補について以下の表形式で出力してください: | # | イベント | 対象 GameObject | 検知方法 | 推奨 Trigger | 強度 (low/mid/high) | コメント | 検知方法は以下から選択: - 物理衝突 (OnCollisionEnter / OnTriggerEnter) - Animator state Enter / Exit (AnimatorController の state 単位) - UnityEvent (UI Button / XR Interactable Select / Activate / Hover) - スクリプトの公開メソッド呼び出し - 連続値の変化量 (Slider 等) 推奨 Trigger は以下から選択: - HapbeatCollisionTrigger - HapbeatStateBehaviour (Animator state に直接 attach する StateMachineBehaviour) - HapbeatUnityEventTrigger - HapbeatSequenceTrigger (grab/hold/release を 1 体化) - HapbeatTickEmitter (連続値スナップ) 参考: `Packages/com.hapbeat.sdk/Runtime/Hapbeat*Trigger.cs` および `HapbeatStateBehaviour.cs` を読んでから判断してください。 ``` **期待される出力例:** ```plaintext | # | イベント | 対象 | 検知 | Trigger | 強度 | |---|---|---|---|---|---| | 1 | ボール着地 | Ball | OnCollisionEnter | HapbeatCollisionTrigger | mid | | 2 | ピン倒れ | Pin × 6 | OnCollisionEnter | HapbeatCollisionTrigger | high | | 3 | ドア開閉 | Door | Animator state Open / Closed Enter | HapbeatStateBehaviour | low | | ... | ``` *** ## Step 2: EventMap 設計プロンプト [Section titled “Step 2: EventMap 設計プロンプト”](#step-2-eventmap-設計プロンプト) Step 1 の結果をレビューしたら、EventMap (Event ID 一覧) を設計させます。 **プロンプト例:** ````plaintext 上の候補リストから EventMap (HapbeatEventMap.asset) を設計してください。 各エントリは以下のスキーマです: - displayName: シーン内で見やすい和名 (例: "ボール着地") - category + eventName: Event ID は "." の形に合成される - category にはシーン/ゾーン名や kit 名 (例: "bowling") - eventName には触覚イベント名 (例: "ball_landing") - mode: Command / StreamClip のどちらか - 短い ON/OFF だけで良い → Command - 振動の起動/停止/長さを Unity 側で制御したい・WAV を流したい → StreamClip - gain: 0.0〜1.0 (touch design 段階では 0.5 を起点に) - target: 触覚デバイスのターゲット (空文字 = 全 group / "neck" / "arm" 等) 参考: `Packages/com.hapbeat.sdk/docs/event-map.md` の仕様を読んでから決めて ください。 出力は以下の C# 配列リテラル形式 (Editor スクリプト埋め込み用): ```csharp new[] { new Entry { displayName = "...", category = "...", eventName = "...", mode = HapticMode.Command, gain = 0.5f, target = "" }, ... } ``` ```` *** ## Step 3: Wiring 提案プロンプト [Section titled “Step 3: Wiring 提案プロンプト”](#step-3-wiring-提案プロンプト) EventMap を確定させたら、シーン内のどの GameObject にどの Trigger を付けるかを設計させます。 **プロンプト例:** ```plaintext 上の EventMap を前提に、各エントリに対する scene wiring を提案してください。 各 wiring は以下の表で出力: | Event ID | scene path | Trigger | source event | 補足設定 | scene path は GameObject の絶対パス (例: "Z1_Bowling/Pins/Pin01") source event はその Trigger をどう発火させるかの具体的設定: - HapbeatCollisionTrigger なら: triggerEvent (CollisionEnter etc) + tagFilter - HapbeatStateBehaviour なら: AnimatorController asset + state 名 + OnStateEnter/Exit (Trigger は GameObject ではなく state に attach) - HapbeatUnityEventTrigger なら: 接続元の UnityEvent (例: XRGrabInteractable.selectEntered) 補足設定には、cooldown・gainMode (VelocityScaled/Fixed)・閾値などを含めて ください。 ``` レビューを終えたら、Step 4 で実装に移ります。 *** ## Step 4: 実装プロンプト [Section titled “Step 4: 実装プロンプト”](#step-4-実装プロンプト) ここからは AI に実行可能な Editor スクリプトを生成させて、Unity 側で 1 メニュー実行で wiring が終わる状態を狙います。 **プロンプト例 (Editor スクリプトで一括 wiring):** ```plaintext 上の EventMap と wiring 表を基に、`Assets/Editor/<シーン名>HapbeatAugmentor.cs` を作成してください。要件: 1. メニュー: `Hapbeat/Augment <シーン名>` から実行 2. 実行内容: - HapbeatEventMap.asset を生成または更新 (Event ID は冪等に追加) - 対象シーンを開く (現シーンが dirty なら保存ダイアログ) - 各 wiring 表のエントリに従って: - 対象 GameObject に Trigger コンポーネントを `Undo.AddComponent` で追加 - SerializedObject 経由で _eventMap / _entryId を wire - Trigger 固有のフィールド (tagFilter / cooldown / gainMode 等) を設定 - 完了レポートのダイアログを出す (適用件数 / skip 件数 / warning) 3. 冪等性: 既に同じ Trigger が付いている GameObject は skip + warning 4. Undo: Ctrl+Z 1 回で全配線が巻き戻ること 5. 実行後ユーザーがレビューして手動微調整できるよう、シーンの自動保存はしない 参考: 既存サンプルの BasicExampleSceneBuilder.cs / ShowcaseSceneBuilder.cs を 読んで、API や namespace を合わせてください。 ``` **メリット:** * AI が直接シーンに変更を加えられない代わりに、生成された C# を Unity で実行することで「**やり直せる configuration as code**」になる * メニュー実行 → レビュー → 微調整 → メニュー再実行のループが回せる * 後で別シーンに展開するときも `<シーン名>` を差し替えれば再利用できる *** ## 手動配置のためのプロンプト (Editor スクリプトを書かない場合) [Section titled “手動配置のためのプロンプト (Editor スクリプトを書かない場合)”](#手動配置のためのプロンプト-editor-スクリプトを書かない場合) シンプルなシーンや、AI に Editor スクリプトを書かせるほどの規模でない場合、 **手順書を生成させて自分で Inspector を操作する** やり方も有効です。 **プロンプト例:** ```plaintext 上の EventMap と wiring を、Unity Editor 上で手動配置するための番号付き手順を 出力してください。 各手順は以下の形式: 1. Hierarchy で `` を選択 2. Inspector で Add Component → `Hapbeat/` 3. Event Map に `` をドラッグ 4. Event ドロップダウンから `` を選択 5. 6. (次の wiring へ) 最後に、全配線を確認するチェックリストも出力してください。 ``` *** ## コツ・注意点 [Section titled “コツ・注意点”](#コツ注意点) ### AI に渡すコンテキスト [Section titled “AI に渡すコンテキスト”](#ai-に渡すコンテキスト) `Packages/com.hapbeat.sdk/` 配下を読ませると SDK 仕様を正しく把握してくれます。**特に以下を渡すと精度が上がります**: * `Runtime/Hapbeat*Trigger.cs` の各 Trigger 実装 * `Runtime/HapbeatEventMap.cs` / `HapbeatEventEntry.cs` * `docs/triggers.md` / `docs/event-map.md` * `Samples~/Showcase/Editor/ShowcaseSceneBuilder.cs` (実装パターンの好例) ### 触覚デザインは AI に任せきらない [Section titled “触覚デザインは AI に任せきらない”](#触覚デザインは-ai-に任せきらない) AI は「**衝突したら振動**」のようなパターンマッチが得意ですが、**触覚の心地よさ・没入度の判断はできません**。Step 1 / Step 2 で必ず人間がレビュー・補正してください。 特に: * **強度 (gain)**: AI は high/mid/low を機械的に振るので、実機で都度調整 * **頻度・cooldown**: 連続発火しすぎる候補は cooldown を強めに * **target (デバイス指定)**: シーンの文脈で `arm` / `neck` / 全体を選び分け ### 「まっさら」と言いつつ完全に空ではない [Section titled “「まっさら」と言いつつ完全に空ではない”](#まっさらと言いつつ完全に空ではない) AI が完全に空のシーンから触覚を提案するのは難しいです。最低限以下があると Step 1 が機能します: * 物理オブジェクト (Rigidbody + Collider) * Animator がある GameObject * UI Button / Slider 等 * XR Interactor / XRI Interactable 「ゲームの骨格 (動くもの・触れるもの) ができてから Hapbeat を載せる」くらいのフェーズで使うのが最も効率的です。 ### Editor スクリプトの再実行可能性 [Section titled “Editor スクリプトの再実行可能性”](#editor-スクリプトの再実行可能性) Step 4 で生成させた Augmentor は **冪等** (同じ実行を 2 回しても重複しない) になるよう要件に含めましょう。AI に「`AddComponent` 前に既存の `` を `GetComponent` でチェックして既存ならスキップ」と明示すると ベターです。 ### Undo を必ず要求する [Section titled “Undo を必ず要求する”](#undo-を必ず要求する) `Undo.AddComponent` / `Undo.RecordObject` を AI が省略しがちです。**Ctrl+Z で 1 回戻せること**を要件として明示してください。これがあればミスっても安全に巻き戻せます。 *** ## 参考: Hapbeat SDK 自体の開発で使ったプロンプト [Section titled “参考: Hapbeat SDK 自体の開発で使ったプロンプト”](#参考-hapbeat-sdk-自体の開発で使ったプロンプト) `Showcase` / `XriHandDemoAugment` サンプル実装時に Claude Code に渡した 詳細指示書 (テスト基準 / scene path 表 / Undo 要件 / 環境チェック) が、SDK リポジトリの `instructions/later/` 配下にあります: * [`instructions-xri-handdemo-augment-202605051200.md`](https://github.com/Hapbeat/hapbeat-unity-sdk/blob/master/instructions/later/instructions-xri-handdemo-augment-202605051200.md) — XRI HandDemo に Editor メニュー 1 クリックで触覚を後付けする Augmentor の設計指示書 「触覚を後付けする Editor ツールをどこまで丁寧に書けばよいか」の参考としてお使いください。 # イベント呼び出しを集約する / 分散する > Trigger-first(分散)と HapbeatManager.Play(集約)の使い分け。どちらも SDK は等しくサポート。 Hapbeat SDK は **触覚イベントの呼び出し方** に 2 つのパターンを提供します。どちらが正解という話ではなく、**プロジェクトの規模・チーム構成・既存のアーキテクチャに合わせて選ぶ設計判断** です。 | パターン | 呼び出し主体 | 典型用途 | | --------------------- | ---------------------------------------------------------- | -------------------------------------- | | **Trigger-first(分散)** | ゾーンごとの Trigger コンポーネントを Inspector で wire | Showcase / 中小規模 / デザイナーが Inspector で組む | | **Manager.Play(集約)** | 任意 script から `HapbeatManager.Instance.Play(eventId)` を直接呼ぶ | 既存のゲームに後付け / イベントカタログを script で一元管理したい | 組み合わせも可能(同一プロジェクト内で混在)。 ## Trigger-first パターン(標準・Showcase 採用) [Section titled “Trigger-first パターン(標準・Showcase 採用)”](#trigger-first-パターン標準showcase-採用) 各ゾーンに **`HapbeatXxxTrigger`** コンポーネントを attach し、ゾーン script は `[SerializeField]` でその Trigger を持ち `.Fire()` を呼ぶ。 ```csharp public class ChargeShooter : MonoBehaviour { [SerializeField] private HapbeatUnityEventTrigger _trigger; [SerializeField] private AnimationCurve _gainCurve; private void Release(float chargeT) { _trigger.GainMultiplier = _gainCurve.Evaluate(chargeT); _trigger.Fire(); } } ``` ### Pros [Section titled “Pros”](#pros) * **Inspector で完結** — EventMap entry の選択も dropdown で済む * **デザイナーが触れる** — script を書かずに event ID 変更可 * **wire を見れば全イベントが追える** — どのオブジェクトが何を発火するか hierarchy で可視化 ### Cons [Section titled “Cons”](#cons) * イベント数が増えると Inspector wire が散らばる * script 側から動的に event ID を組み立てたい場合に向かない ## Manager.Play パターン(集約) [Section titled “Manager.Play パターン(集約)”](#managerplay-パターン集約) `HapbeatManager.Instance.Play(eventId, gain, displayName, target)` を直接呼ぶ。Trigger コンポーネントを使わない。 ```csharp public class GameHapticRouter : MonoBehaviour { public static GameHapticRouter Instance { get; private set; } [SerializeField] private HapbeatEventMap _eventMap; // optional: entry lookup 用 private void Awake() => Instance = this; public void Play(string eventId, float gain = 1f, string target = null) { HapbeatManager.Instance.Play(eventId, gain, target: target ?? ""); } public void OnEnemyHit() => Play("game.enemy_hit"); public void OnPickup() => Play("game.pickup", gain: 0.6f); } ``` 呼ぶ側: ```csharp GameHapticRouter.Instance.OnEnemyHit(); ``` ### Pros [Section titled “Pros”](#pros-1) * **イベントカタログを 1 ファイルで管理** — 命名・gain・target を 1 箇所に集約 * **動的構築が容易** — `Play($"weapon.{weaponName}_fire")` のような string 組立 * **既存ゲームに後付けしやすい** — Trigger を散らかさず singleton 1 つで導入完了 ### Cons [Section titled “Cons”](#cons-1) * Inspector では何が呼ばれているか見えない(script 読まないと分からない) * EventMap との連動が手動(runtime に存在チェックは出るが、Inspector で未使用 entry の検知ができない) * **Latency 補正の対象外**: `HapbeatConfig.hapticDelaySeconds` (audio 遅延補正) は Trigger / Bridge / Event / StateBehaviour 経由でのみ自動適用される。`Manager.Instance.Play()` を直叩きする経路は EventMap entry を経由しないので、必要なら呼び出し側で `Invoke` / `StartCoroutine` で同等の delay を入れる必要がある — 集約パターンを採用する場合は注意。 ## 使い分けの目安 [Section titled “使い分けの目安”](#使い分けの目安) * **小〜中規模 / デザイナー協業 / Showcase で学習中** → Trigger-first * **既に game manager / event bus が存在する大規模プロジェクト** → Manager.Play 集約 or 混在 * **デバッグしやすさ重視** → Trigger-first(Hapbeat Event Logger メニューで wire を可視化できる) * **動的イベント命名が多い** → Manager.Play ## 混在も OK [Section titled “混在も OK”](#混在も-ok) たとえば「UI / シーン内オブジェクトは Trigger-first、ゲームロジックの内側で発火するシステミックなイベントは Manager.Play」のような分担は自然です。SDK 内部は両方とも `HapbeatManager` を経由するので、混ぜても動作に影響しません。 ## 参考 [Section titled “参考”](#参考) * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — Trigger-first で使える各種 Trigger * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — entry 管理 * [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) — 初期セットアップ # 変更履歴 — Hapbeat Unity SDK Hapbeat Unity SDK の主要な変更点をまとめます。 形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) に、 バージョン付けは [Semantic Versioning](https://semver.org/lang/ja/) に従います。 *** ## \[Unreleased] [Section titled “\[Unreleased\]”](#unreleased) ## \[0.5.0] - 2026-08-27 [Section titled “\[0.5.0\] - 2026-08-27”](#050---2026-08-27) ### Breaking changes(破壊的変更) [Section titled “Breaking changes(破壊的変更)”](#breaking-changes破壊的変更) * legacy relay API の `HapbeatBridge`、`HapbeatManager.ConnectToBridge()`、`HapbeatConfig.useBridge` / `bridgeHost` を削除しました。command transport は `WifiUdp` のみです(旧 API の alias・serialized migration は提供しません)。 ### Added(追加) [Section titled “Added(追加)”](#added追加) * **1 アプリから複数 target へ StreamClip を同時出力**できるようにしました。PONG で解決した device endpoint ごとに 1 本の wire stream を持ち、同じ device に一致する複数 source は SDK 内で mix します。source ごとに `HapbeatStreamPlayback` を返すため、gain / pan / loop / Stop は独立して制御できます。sample rate と mono / stereo の違いは 16 kHz stereo PCM16 へ正規化します。 * `HapbeatStreamPlayback.Status` / `DeferReason` を追加しました。address 未解決の場合は、誤った device へ broadcast せず `Deferred` として理由を取得できます。 * **FIRE(Command モード)で左右バランス(`pan`)を指定できる**ようにしました。`HapbeatManager.Play(...)` / `PlayScheduled(...)` に `pan` 引数(-1 = 左のみ / 0 = 中央 / +1 = 右のみ、既定 0)が増え、Trigger 系コンポーネントは既存の `Pan` プロパティを FIRE でも送るようになります。デバイス側ミキサーが CLIP と同じ linear balance で展開します(contracts message-format.md §0x01 / DEC-055)。**DEC-055 対応版のデバイスファームウェアが必要**で、未対応版は `pan` を無視して中央で再生します。 ### Changed(変更) [Section titled “Changed(変更)”](#changed変更) * 標準 command transport 名を `WifiUdp` に統一しました。StreamClip は target を持たない `STREAM_DATA` の誤配送を防ぐため、常に PONG-backed endpoint へ明示ユニキャストします。旧 `streamUnicast` 設定と broadcast fallback を削除しました。 ### Fixed(修正) [Section titled “Fixed(修正)”](#fixed修正) * **Unity 6000.0 LTS でエディタ拡張がコンパイルエラーになる問題を修正**しました。オブジェクト解決 API は Unity 6 の途中で `EditorUtility.InstanceIDToObject` から `EditorUtility.EntityIdToObject` に改名されており、**新しい名前は 6000.0 LTS に存在しません**(6000.0.59f2 の `UnityEditor.dll` には無く、6000.3.12f1 には有ることを確認)。そのため `package.json` が `"unity": "6000.0"` と宣言しているにもかかわらず、6000.0 のプロジェクトでは `Hapbeat.Editor` アセンブリが `error CS0117` で落ち、**プロジェクト全体のコンパイルが通らなくなっていました**(SDK のランタイムだけを使うこともできません)。 * 呼び出しをバージョンガード付きヘルパー `HapbeatEditorCompat.IdToObject(int)` に集約しました(6000.3 以降は新 API、それ未満は旧 API。旧 API は 6000.3 にも残っているため、どちらの分岐も安全です)。 * Showcase サンプルの `HierarchySeparator` も同じヘルパー経由に揃えました。こちらはインポート先のプロジェクトで同じエラーを起こしていました。 ## \[0.4.0] - 2026-08-06 [Section titled “\[0.4.0\] - 2026-08-06”](#040---2026-08-06) **接続の安定性**に絞った更新です。展示・常設のような無人運用で「いつの間にか触覚が出なくなり、PC を再起動するまで戻らない」という状態を作っていた原因を取り除きました。あわせて、Hyper-V / WSL2 / Docker を入れた PC でデバイスを一切検出できない問題と、Test Play の再生が途切れる問題を修正しています。 いずれも設定変更は不要で、これまでどおりお使いいただけます。 ### Added(追加) [Section titled “Added(追加)”](#added追加-1) * **SDK 更新の通知**: 新しい版が公開されると、Editor 起動時に Console へ 1 行だけお知らせを出すようになりました。表示は **Editor セッションごとに 1 回**で、スクリプト再コンパイル(domain reload)では重複しません。UPM の Git URL はタグを固定すると Package Manager が更新を検出できないため、これが実質的な唯一の気付き手段になります。 * いつでも確認: `Hapbeat` → `Diagnostics` → `Check for SDK Updates` * 自動確認の ON/OFF: `Hapbeat` → `Diagnostics` → `Check for SDK Updates on Startup` * 取得は 3 秒でタイムアウトし、失敗しても何も出しません(オフライン環境で無害)。この SDK 自体を Local / Embedded で開発しているプロジェクトでは確認しません。 * **接続の診断ログ**: Windows で ICMP 応答の抑止設定(`SIO_UDP_CONNRESET`)の適用に失敗した場合に、警告を出すようになりました。従来は無言で握りつぶしていたため、現場で通信不良が起きても原因の手がかりが残りませんでした。Windows 以外では従来どおり何も出しません(この設定自体が存在しないため)。 ### Fixed(修正) [Section titled “Fixed(修正)”](#fixed修正-1) * **一度の通信エラーで触覚が止まったままになる問題を修正**しました。Wi-Fi の瞬断や経路の一時的な変化など、送信エラーが 1 回起きるだけで接続が切断扱いになり、**アプリを再起動するまで復帰しません**でした。キープアライブの送信も止まるため、デバイスは 15 秒後に「アプリ未接続」表示(LED 緑)へ戻ります。無人運用の展示・常設では気付いて再起動する人がいないため、実質その日の稼働が止まります。 * UDP の送信失敗はソケットの故障を意味しないため、**送信エラーで接続を切らない**ようにしました。 * 接続が落ちた場合は**自動的に再接続**します(2 秒から始まり、最大 30 秒までの指数バックオフ)。`HapbeatConfig` の **Auto Reconnect** で無効化できます。 * 明示的に `Disconnect()` を呼んだ場合は再接続しません(意図した切断を打ち消さないため)。 * 起動時にネットワークがまだ使えない状態でも、使えるようになった時点で自動的に接続されます。 * 送信エラーのログは**障害ごとに 1 回だけ**出力し、**復旧した時点で 1 行**出します。ストリーミング中は 1 秒あたり約 100 回送信するため、抑制しないとログが埋まって現場調査に使えなくなります。 * **切断状態のソケットが後始末されずに残る問題を修正**しました。送信・受信エラーで接続が切断扱いになってもソケットは開いたままで、その状態で再接続するとソケットと受信スレッドが取り残されていました。 * **再接続後にオフラインのデバイスが残り続ける問題を修正**しました。切断時にデバイスの生存情報を破棄するようにしたため、別のネットワークへ再接続した直後に、古い宛先へ送信し続けて無音になることがなくなりました。 * **仮想ネットワーク(Hyper-V / WSL2 / Docker)がある PC でデバイスを検出できない問題を修正**しました。これらが作る仮想アダプタは **LAN ケーブルを繋いでいなくても常時有効**で、Windows の既定では Wi-Fi より優先されることがあります。従来の送信方法ではそちらへ流れてしまい、Hapbeat には一切届きませんでした。 * 各ネットワークアダプタの実際のサブネットに宛てて送るようにしたため、優先順位の設定を変更しなくても届きます。 * デバイスから応答があった時点で、そのネットワークに送信先を固定します。 * 従来の送信方法も併用するので、SoftAP 構成など特殊な環境でも従来どおり動作します。単一のネットワークアダプタしかない環境では挙動は変わりません。 * **Test Play(EventMap / Inspector)の再生が途切れる問題を修正**しました。エディタ用の送信経路だけがデバイスを探索しておらず、常にブロードキャストで送っていたためです。ブロードキャストは Wi-Fi アクセスポイントが一定間隔まで保留するため、ストリーム再生が途切れがちになります。実行時と同じくデバイスを探索し、見つかったデバイスへ直接送るようにしました。 ## \[0.3.0] - 2026-07-26 [Section titled “\[0.3.0\] - 2026-07-26”](#030---2026-07-26) 同一ビルドを複数の端末に配布し、端末ごとに送信先の Hapbeat を選べるようにする **Address Override** を中心に、Wi-Fi 送信経路の安定化(ユニキャスト送信・ストリーム送出のスレッド化)と、VR 実機で設定を行うための新サンプルを追加しました。 ### ⚠ 破壊的変更(移行ガイド) [Section titled “⚠ 破壊的変更(移行ガイド)”](#-破壊的変更移行ガイド) * `HapbeatConfig` の `group` / `overridePlayer` / `overrideGroup` / `discoveryTimeoutMs` を削除しました。`group` と `discoveryTimeoutMs` はどこからも読まれていない dead field で、値を変えても挙動は変わりませんでした。旧 `overridePlayer` / `overrideGroup`(起動時の既定値)は、端末ごとの実行時 API(`SetAddressOverride`)と、ビルド全体を固定する `buildOverridePlayer` / `buildOverrideGroup`(下記 Added)に役割を分けています。 * `HapbeatManager.DefaultGroup` / `EffectiveGroup` を削除しました。CONNECT\_STATUS (0x20) の group バイトはデバイス側で保存後に一切読まれないレガシーフィールドであることが確認できたため、内部処理に単純化しています。 * `HapbeatClient.SendPlay` / `SendStop` / `SendStopAll` の戻り値が `void` から `CommandSendResult`(`Broadcast` / `Unicast`)に変わりました。戻り値を使わない既存の呼び出しはそのままコンパイルできます。 ### Added(追加) [Section titled “Added(追加)”](#added追加-2) * **サンプル「XRI Hand Demo (haptics add-on)」と `Hapbeat > Samples > Augment XRI Hand Demo`** — XR Interaction Toolkit の *Hands Interaction Demo* シーンに触覚を後付けするサンプルです。XRI のサンプルは Unity Companion License のため改変シーンを再配布できません。そこで**シーンは配らず、EventMap(`HandsDemoEventMap.asset`・10 エントリ)と Kit(`hand-demo-kit`・stream clip 9 本)だけを同梱し、配線は Editor コマンドで後付けする**構成にしています。ユーザーは XRI 側で `HandsDemoScene` を import して開き、このコマンドを実行するだけで、掴む / 擦る / 押し込む / スナップ / UI クリックの触覚が入ります。 * 追加されるのは Hapbeat コンポーネント 33 個(`HapbeatUnityEventTrigger` 18・`HapbeatSequenceTrigger` 6・`HapbeatParameterBinding` 4・`HapbeatTickEmitter` 2・`HapbeatManager` 1・`XR Helpers` のフィルタ 2)と、UnityEvent 配線 41 本です。 * **冪等**です。既にあるコンポーネント・同じ対象/メソッドを指す配線はスキップし、追加数 / スキップ数をサマリログに出します。全操作は 1 つの Undo にまとまります。 * 実行前にシーンが *Hands Interaction Demo* かを検証し、違えば中断します。XRI のバージョン差で階層が変わっている場合は、**見つからなかったパスを 1 件ずつ警告として列挙**したうえで、見つかった分だけを適用します(黙って部分適用しません)。 * XRI 側の select イベントには手を加えません。ソケット周りは `XR Helpers` サンプルのフィルタコンポーネント経由で配線します。 * 診断用の `HapbeatEventLogger` 配線(PokeButton の XRI イベント 14 種)は別メニュー **`Augment XRI Hand Demo (+ diagnostic Event Logger)`** に分離しました。 * この Editor ツールは **XRI への asmdef 参照を持ちません**。XRI コンポーネントは型名で探索し、UnityEvent は `SerializedObject` のプロパティパス経由で編集するため、XRI 未導入のプロジェクトでもコンパイルできます。 * **ビルド単位の Address Override 固定(`Hapbeat > Settings` > Override Addressing (this build))** — 複数のデモを同時開催しても混線しないよう、player / group を**ビルド全体で固定**できるようにしました。軸ごとに独立して指定します。端末単位の `Override Addressing (this device)`(Runtime Status ウィンドウ)と対になる名前で、Player / Group は同じ横並びの数値入力で編集します。 * `HapbeatConfig.buildOverridePlayer` / `buildOverrideGroup`(既定 `-1`)— `1-99` = そのビルド全体で強制(端末側の設定パネル / `SetAddressOverride` / `PlayerPrefs` では変更不可)。`-1` = 従来どおり端末ごと。値は `OnValidate` で `-1..99` にクランプされます。 * 想定運用: `group` をビルドで固定してデモを分離し、`player` は `-1` のまま端末ごとにペアリングする。 * 優先順位は軸ごとに「config が `1-99` → それを強制 / config が `-1` → 保存値(`PlayerPrefs`)→ 無効」。`HapbeatManager.ResolveEffectiveOverride(int, int)`(static・純粋関数)として切り出し、ユニットテスト(`AddressOverrideResolutionTests`)を追加しています。 * `HapbeatManager.BuildOverridePlayer` / `BuildOverrideGroup` / `IsPlayerForcedByBuild` / `IsGroupForcedByBuild` — UI が「この軸はビルド固定」と表示するための公開判定。 * 強制軸は `SetAddressOverride` が無視し(`PlayerPrefs` にも書きません)、`ClearPersistedAddressOverride` 後も config 値のまま維持されます。`HapbeatAddressOverridePanel` は該当軸の -/+ を無効化し値に `(build)` を付記、`Hapbeat > Open Runtime Status` は `Build (forced)` 行を表示します。 * 既定値(`-1` / `-1`)では従来の挙動と完全に同一です。 * **Address Override — 実行時に player / group を上書き** 同一ビルドを複数の VR HMD に配布し、端末ごとに自分の Hapbeat へ 1:1 で送りたい、というユースケース向けの機能です。有効にすると EventMap 側の target に関わらず、すべての送信(Play / Stop / StopAll / StreamBegin)の宛先が指定した player / group に強制されます。 * `HapbeatManager.SetAddressOverride(int player, int group, bool persist = false)` — 実行時に切り替え。`persist: true` で `PlayerPrefs` に保存し、次回起動時に復元します(端末ごとの設定なので、プロジェクト共有の `HapbeatConfig` には保存しません)。 * `HapbeatManager.OverridePlayer` / `OverrideGroup` / `TryGetPersistedAddressOverride(out int, out int)`(static)/ `ClearPersistedAddressOverride()`。 * `HapbeatManager.AddressOverrideDisabled`(`= -1`)— 「その軸は上書きしない(EventMap の target をそのまま使う)」ことを表す定数。target が `-1` という値に書き換わるわけではありません。 * `HapbeatClient.ResolveTarget(string target, int overridePlayer, int overrideGroup)`(static・UnityEngine 非依存)— target 文字列の `player_` / `group_` を置換・挿入します。 * `appName` に `

` / `` を含めると、送信時に現在の override 番号(無効時は `-`)へ置換されます(`HapbeatManager.ApplyAddressPlaceholders`)。デバイスの OLED にペア番号が表示されるので、現場で対応関係を確認できます。 * **`HapbeatAddressOverridePanel`(Runtime コンポーネント)** — GameObject に 1 つ追加するだけで override 設定 UI が出ます。`ScreenSpaceOverlay` / `WorldSpace`(VR 用)の 2 モードに対応。Player -/+、Group -/+、Play、Apply、Exit のボタンを内蔵し、2D フォーカスグリッド(`RegisterFocusable` / `MoveFocus` / `ActivateFocused`)でコントローラー操作にも対応します。Play / Exit の実処理は `OnPlayRequested` / `OnExitRequested` で外部から注入します。 * `WorldSpace` 時の **lazy follow(遅延追従)**(`World Attach Mode` = `LazyFollow` / `WorldFixed`、既定 `LazyFollow`)— 視界中央から `Follow Deadzone Degrees`(既定 `10°`)以内にある間はパネルを**ワールド固定のまま**にし、それを超えて見回したときだけ `Follow Smooth Seconds`(既定 `0.25` s)の時定数で正面へ滑らかに移動します。Canvas をカメラ Transform の子にする**ハードなヘッドロックは採用していません**(頭に追従して動く面に XR コンポジタの再投影が重ねて掛かるため、頭を振るたびに UI が泳いで見えます)。カメラは `Follow Camera`(未設定なら `Camera.main`)、位置は `Follow Distance`(既定 `1.5` m)/ `Follow Vertical Offset`(既定 `0` m)で調整します。カメラが見つからない場合は警告を 1 回出して `WorldFixed` 相当で動作します。 * `PanelCanvasTransform` / `IsFollowingView` / `FollowVerticalOffset` / `SnapToView()`(public)— 外部コントローラーが自前の world-space UI をパネル Canvas の下にぶら下げたり、「視界中央へ戻す」操作を実装するために公開しています。 * **`Hapbeat > Open Runtime Status` ウィンドウ** — 端末ごとに変わる値を 1 画面に集約。Address Override(保存値 / 実行時値 / 直接編集 + 保存 / Clear)、appName のプレースホルダー解決後プレビュー、接続状態(broadcast・unicast / ポート / 生存デバイス数 / 発見済み一覧)を確認できます。Manager インスペクタからも開けます。 * **addressed ユニキャスト送信(STREAM は常時 / コマンドは `commandUnicast`、既定 `true`)** Wi-Fi のブロードキャストは、同じアクセスポイントに省電力状態の端末が 1 台でもいると AP 側で DTIM まで保留されるため、100〜300ms 級の遅延が周期的に発生します(CLIP では可聴な途切れとして現れていました)。PONG で判明している既知デバイスへ直接送ることでこれを回避します。 * `STREAM_BEGIN` / `STREAM_DATA` / `STREAM_END` と `PLAY` / `STOP` / `STOP_ALL` が対象。`PING` / `CONNECT_STATUS` は discovery のため従来どおりブロードキャストです。 * 宛先は解決後の target で絞り込みます(`HapbeatClient.AddressMatches` — firmware の照合と同一セマンティクス)。1 人が複数台装着する構成や、複数ペアが同一 LAN にいる構成でも、自分のペアにだけ送信します。 * STREAM は address 解決まで defer して誤 broadcast を防ぎます。コマンドは既知デバイスが 0 台のときだけ broadcast へフォールバックします。 * `HapbeatProtocol.ParsePongExtended` — PONG からデバイスの address / device\_name / firmware\_version 等を取得します(プロトコル変更はありません。従来 SDK 側が読んでいなかっただけです)。 * **新サンプル `VRConfigExample`** — Quest 等の VR 実機で override を設定・確認するための最小シーン。XR Interaction Toolkit に依存せず、Input System のみで動作します。操作はスティックでフォーカス移動、トリガー / A(X) / B(Y) のいずれかで決定の 2 アクションのみ。パネルとガイドは一体で lazy follow(遅延追従)するので、起動時の recenter は行いません(スティック押し込みの recenter は「今すぐ正面へスナップ」の意味になります)。テスト再生は EventMap の CLIP エントリ(100Hz sine 同梱)なので、デバイスに Kit を配備しなくても振動を確認できます。戻り先シーンを設定すれば Exit で自分のシーンへ復帰できるため、**自プロジェクトの設定画面としてそのまま使えます**。 * ユニットテスト(EditMode)— `ResolveTargetTests` / `AddressMatchesTests` / `AddressPlaceholderTests` と `Tests/Runtime` アセンブリ定義を新設しました。 * `package.json` に `com.unity.ugui` 依存、Runtime asmdef に `UnityEngine.UI` 参照を追加(`HapbeatAddressOverridePanel` の uGUI 利用のため)。 ### Changed(変更) [Section titled “Changed(変更)”](#changed変更-1) * **StreamClip の送出を専用スレッド化** — 従来はコルーチン(Update 駆動)で送っていたため、GC やレンダリングによるフレームヒッチがそのまま送信の空白になり、デバイス側のリングバッファを枯渇させて不定期な途切れを起こしていました。`Stopwatch` を基準にした専用スレッドへ移し、チャンクも MTU 上限一杯(mono 約 44ms 相当)から約 10ms に細分化して、フレームレートに依存しない等間隔送出にしています。 * Settings ウィンドウ: `enableLogging` に誤って付いていた “Verbose Log” ラベルを修正し、`verboseLogging` の項目を追加しました。 * Showcase サンプル: `ZoneSwitcher` の Initial Zone を、生の数値スライダーから設定済みゾーン名のドロップダウンに変更しました。 ### Fixed(修正) [Section titled “Fixed(修正)”](#fixed修正-2) * **group を指定した送信がデバイスに届かない** — デバイスのアドレス照合は位置ベース(i 番目のセグメント同士を比較)ですが、`group_` を target の末尾に付けるだけだったため、player / position を省略した target では group が本来と違うスロットに入り、常に不一致になっていました。position スロットを `*` で補ってから追加するよう修正しています(例: `"" → "*/*/group_2"`)。 なお、デバイスのアドレスは常に `player_//group_` の正規形で、**既定は `player_1` / `group_1`** です(firmware DEC-048 以降、group が省略されることはありません)。設定し忘れた機体は既定の 1 番に合流するので、デモを分けるときは **1 以外の番号から振る**と設定漏れに気付けます。 * **UDP 受信スレッドが Windows の ICMP reset で停止する** — 電源 OFF や再起動中のデバイスへユニキャスト送信すると ICMP port unreachable が返り、Windows ではそれが次の `Receive()` の例外として現れます。従来はこれを致命エラーとして受信ループを終了しており、以後デバイスを一切検出できなくなっていました(`SIO_UDP_CONNRESET` の設定と、回復可能なエラーでループを止めない処理を追加)。 * **world-space パネルがプレイヤーに正対しない** — 目標姿勢がヘッドの yaw に合わせるだけだったため、パネルが目線より上下にある(`Follow Vertical Offset` を付けた、あるいは立ち位置の高さが違う)と斜めを向いていました。カメラ位置を見る look-at に変更しています(ロールは常に 0 のままなので、頭を傾けてもパネルは傾きません)。デッドゾーン内で静止する挙動は従来どおりです。 * `HapbeatAddressOverridePanel` が既存の Canvas の子に配置された場合、生成する Canvas が入れ子になって表示位置がずれる問題を修正しました(Unity の仕様上、子 Canvas は独自の RenderMode を持てないため、シーンルートへ退避します)。 * フォーカス移動の不具合 2 件を修正 — 下方向へ移動できない(`Mathf.Sign(0f)` が `+1` を返す仕様に起因)、および複数座標に登録したボタンでハイライトが消える問題。 * `VRConfigExample` の VR 実機不具合 — UI の微振動(`TrackedPoseDriver` による姿勢取得へ変更)、スティック押し込み / Menu ボタンが反応しない(OpenXR の実コントロール名にバインド)、右手 Menu ボタンは Quest の OS 予約でアプリに届かないため左手のみに変更。起動時 UI が実際の頭の位置から左下にずれる問題(`OnEnable` の recenter が XR トラッキング確立前に走り、シーン上の初期カメラ位置を基準にしていた)は、lazy follow 化(パネル自身が `LateUpdate` で配置する)と起動時 recenter の削除で解消しました。パネルが視界中央より上にずれていた問題も修正しています(`VerticalLayoutGroup` が既定の `UpperLeft` 揃えで、コンテンツより背景が高いときに上端へ寄っていたため)。 * Showcase の `AddressOverrideDemo` を `Z4_Stream` 直下の独立した GameObject に配線し直しました(Canvas 配下にあったため上記の入れ子問題が発生していました)。 * **`VRConfigExample` のガイドテキストがパネルに追従しない(Quest スタンドアロンビルドのみ)** — ガイド Canvas をパネル Canvas の子にする処理を `OnEnable` で 1 回だけ試みており、その時点で前提(パネル Canvas の生成・スケール確定)が未了だと黙って諦めていました。実行順序はエディタと実機ビルドで異なるため、実機だけ親子付けに失敗していました。成功するまで `LateUpdate` で再試行し(上限 5 秒)、打ち切り時には**どの前提で失敗し続けたか**を警告に出すようにしています。成功時も 1 行だけログを出すので、追従しない症状が再発しても親子付けの成否を切り分けられます。 *** ## \[0.2.1] - 2026-05-30 [Section titled “\[0.2.1\] - 2026-05-30”](#021---2026-05-30) v0.2.0 直後に発覚した sample アップグレード時の compile error と、EventMap の使い勝手改善をまとめた hotfix リリース。 ### Fixed(修正) [Section titled “Fixed(修正)”](#fixed修正-3) * **Sample namespace を folder 名に追従** (`Hapbeat.Samples.Tutorial` → `Hapbeat.Samples.Showcase`) v0.2.0 で `Tutorial` → `Showcase` に folder rename したが、namespace が legacy のまま残っていた。 古いバージョン (v0.1.x) の Tutorial sample を import 済みの状態で v0.2.0 の Showcase sample を import すると、同一 namespace 下で同名 class が二重定義になり compile error が発生していた問題を解消。 あわせて Showcase の scene / prefab / animator 内に残っていた legacy namespace 参照 (`m_TargetAssemblyTypeName` 等) も修正し、UnityEvent 配線の解決ずれを解消。 * **`HierarchySeparator` を復活** — v0.2.0 の cleanup で巻き込み削除されていた、Hierarchy 上の区切り装飾 (`-------- ... --------`) を Showcase namespace + Unity 6 API (`EntityIdToObject`) で再追加。 ### Added(追加) [Section titled “Added(追加)”](#added追加-3) * **`Hapbeat > Diagnostics > Check Sample Versions`** — `Assets/Samples/Hapbeat SDK/` 配下に複数バージョンの sample が同時 import されている場合に Console 警告を出す診断ツール。Editor 起動時に自動 scan + 手動再実行可能。 * **EventMap Window: Bulk Edit モーダル** — toolbar の `Bulk Edit` から起動。Mode / Gain / Loop / Target / Delay Offset / Manifest Override / Notes を **Override チェックで選択的に**一括編集できる。最上部の `Select all` トグル (デフォルト ON) で全 entry または Table 選択行を対象に切替。変更は 1 つの Undo group にまとまる。 * **EventMap Table view: Ctrl/Cmd+A で全行選択** — inline text field 編集中はテキスト全選択を奪わない。 * Table view の Target セルクリック / 右クリック `Set Target...` による target 一括適用 (player / position / group) も従来通り利用可能。 ### Changed(変更) [Section titled “Changed(変更)”](#changed変更-2) * **Unity 6 (6000.0) 以上を要件化** — `package.json` の `unity` を `2021.3` → `6000.0` に更新。SDK 本体が既に Unity 6 の `EntityIdToObject` を使用しているため、実態に合わせた是正。 * **EventMap の mode 選択から `LIVE` を削除** — Table / Inspector とも `FIRE` / `CLIP` の 2 択に統一 (`LIVE` は廃止済みで UI ラベルのみ残っていた)。 * **Settings の Haptic Delay 表示を ms 単位に** — スライダーを `Haptic Delay (ms)` (0–500 ms) 表記に変更。内部ストレージは秒のまま (プロトコル / per-entry delay と一貫)。 ### Migration(移行) [Section titled “Migration(移行)”](#migration移行) **v0.2.0 → v0.2.1 で Sample を再 import する場合は古いバージョンの folder を削除してください** Unity の UPM Samples は package 更新時に古い import folder (`Assets/Samples/Hapbeat SDK/<旧バージョン>/`) を自動削除しません。残っていると同一クラスの二重定義で compile error になります。Project ウィンドウで該当 folder を削除してから新しい Sample を import してください。SDK が起動時に自動 scan して警告を出します。 *** ## [0.2.0](https://github.com/Hapbeat/hapbeat-unity-sdk/releases/tag/v0.2.0) - 2026-05-26 [Section titled “0.2.0 - 2026-05-26”](#020---2026-05-26) v0.1.0 以降に蓄積した API 整理・サンプル再編・遅延補正・Editor UX 改修をまとめたリリース。 **Pre-1.0 のため Breaking change を含みます** — 詳細は下記を参照。 ### Breaking changes(破壊的変更) [Section titled “Breaking changes(破壊的変更)”](#breaking-changes破壊的変更-1) * **`HapbeatAnimatorTrigger` を廃止し `HapbeatStateBehaviour` に置換** Animator state に直接 attach する StateMachineBehaviour 方式に変更。 State Enter/Exit に別 entry を bind 可能、`Required Previous State` で A→B 限定発火、 Looping StreamClip は OnStateExit で自動 Stop。**旧 trigger は移行コードなしで削除**。 * **`HapbeatEvent` + `StandardCategories` 削除** v0.1 期に obsolete 化していた legacy component と category 列挙を完全削除。 * **`_entryIndex` legacy fallback 全廃** Trigger → EventMap 参照は **stable GUID (`entry.id`) only** に統一。 古い `_entryIndex` ベースのシリアライズデータは再 pick が必要。 * **`HapbeatBridge` subclass パターンの非推奨化** Trigger-first / EventMap-first 設計を標準とし、Bridge subclass は optional に。 抽象クラス自体は API 互換のため残置。 * **サンプル再編: `Tutorial` → `Showcase` rename** UPM Sample import の表示名・フォルダ名が変わります。 旧 `Tutorial` を import 済みのプロジェクトは、新規に `Showcase` を import し直してください。 ### Added(追加) [Section titled “Added(追加)”](#added追加-4) **Latency compensation** * `HapbeatConfig.hapticDelaySeconds` — グローバル遅延補正 (映像/音声に対して触覚を遅延発火)。 * `HapbeatEventEntry.delayOffsetSeconds` — エントリ単位の追加オフセット (Inspector で編集)。 * Play-mode 中の `hapticDelaySeconds` 変更で **pending coroutine を自動 flush** (即時反映)。 * `HapbeatStateBehaviour` 経由の発火も含む全 fire 経路で delay を honor。 **Trigger 機能拡張** * `HapbeatSequenceTrigger._stopShotDelay` — On Stop one-shot を loop stop から遅延発火 (default 0.05s)。 loop→shot の packet burst を抑制し、shot 強度を安定化。 * `HapbeatTickEmitter` dual mode — `AbsolutePosition` (位置基準) / `AccumulatedMotion` (累積移動量基準) を選択可能。 * Trigger の **binding pre-seed が child / parent GameObject 上の binding も検出** (旧: Self のみ)。 **EventMap Window** * Wiring セクションに **HapbeatStateBehaviour (Animator state)** を表示。 * Wiring に **script-driven 参照** を表示 (`SerializeField` の string heuristic + `[HideInInspector]` field も拾う)。 * Script Wiring scan が `entry.id` (stable GUID) 検出にも対応。 * **Verbose Log 一括 off メニュー** 追加。 **Manifest schema 2.0.0** * 2-bucket layout (`events` + `stream_events`) の reader を実装。 * bare filename + mode-aware lookup により、Studio 側 multi-mode 出力と整合。 **Stream** * ステレオ pan のデフォルトを **passthrough** (equal-power → linear balance) に変更。 中央 (pan=0) で √½ 減衰していた問題を解消。 * `HapbeatStreamPlayback.GainMultiplier` / `Pan` を再生中にリアルタイム push 可能に。 * gain modulation 計算式を `ApplyGainModulation(float)` に集約。 **Editor / Menu** * メニューを 4 セクション構成 (**Window / Create / Tools / Developer**) に再編。flat 構成。 * Window 系メニューを `Open ...` prefix で統一 (Event Map → Batch Setup → Settings の順)。 * `Hapbeat → Create → Initial Scene Setup` を新設 (Manager + Config + EventMap の最小セットを自動生成)。 * `HapbeatManager` Inspector の Test 操作を EventMap-style に刷新 (Gain semantics を EventMap と揃える)。 * **Maintainer Sync の wipe-then-rebuild 化** — `Samples~//` を build artifact として全消し再構築。 dest-only orphan が次回 sync 時に必ず消えるよう保証。 **Samples** * **`Showcase` サンプル新設** (旧 Tutorial の後継) — Z1 Bowling / Z2 Door / Z3 Pickup / Z4 Stream Console / Z5 Charge Shot の 5 ゾーンで SDK 全機能を体験。WAV を `Kit/showcase-kit/` に同梱 (Sample import 直後に EventMap が解決)。 * `BasicExample` をフラット構成に再編。`BasicExample.unity` + `BasicExampleEventMap.asset` + `Kit/basic-exam-kit/` のみ。 * 3rd-party 資産の credits を per-file 化 (`Samples~/Showcase/THIRD_PARTY_NOTICES.md`)。CC0 / CC BY 3.0 を分離記載。 * XR helpers サンプルは引き続き opt-in (`Samples~/XriHelpers/`)。 ### Changed(変更) [Section titled “Changed(変更)”](#changed変更-3) * **UI 文字列を英語に統一** — Tooltip / Label / Dialog / HelpBox / Debug.Log の user-facing 文字列を全 23 ファイルで英語化。日本語は portal docs に集約。 * ドキュメントを **portal (devtools.hapbeat.com) に一本化** — `docs~/` を削除し各サブ repo の docs は portal が一元参照。 ### Fixed(修正) [Section titled “Fixed(修正)”](#fixed修正-4) * `HapbeatUnityEventTrigger.FireWithGain` が `_entryIndex` 参照のままになっていた問題を `ResolveEntry()` 経由に修正。 * Manifest schema 2.0.0 環境で send-ahead lead が指定値を超過するケースを `WaitForSecondsRealtime` で固定。 * Sample deploy 時に destination の親フォルダが未生成だと `AssetDatabase.CopyAsset` が silent fail する問題に対し、事前に `EnsureAssetFolder` を実行。 ### Removed(削除) [Section titled “Removed(削除)”](#removed削除) * `Editor/HapbeatSampleDeployment.cs` — dead code (旧 `BasicExampleSceneBuilder` と `HapbeatSampleImportDeployer` 削除により呼び出し元なし)。 * `Editor/HapbeatSampleImportDeployer.cs` — 「Deploy Imported Sample」メニュー (非標準 UX)。Sample import 後はユーザー自身が `Assets/` 配下に手動コピー。 * `docs~/` ディレクトリ — portal 集約に伴い撤去。 ### Migration notes(移行メモ) [Section titled “Migration notes(移行メモ)”](#migration-notes移行メモ) * **`HapbeatAnimatorTrigger` を使っていた場合**: Animator Controller の対象 state を選択 → Add Behaviour → `HapbeatStateBehaviour` を attach → EventMap entry を pick。 * **Tutorial サンプルを編集していた場合**: 編集内容は `Assets/` 配下にコピー済みのはず。再 import で Showcase が降ってくるが、旧 Tutorial 編集物は上書きされません (UPM の Sample import は新規パスに展開するため)。 * **古い EventMap entry の参照が外れた場合**: Trigger 側で entry を pick し直してください (`_entryIndex` 廃止のため)。 *** ## [0.1.0](https://github.com/Hapbeat/hapbeat-unity-sdk/releases/tag/v0.1.0) - 2026-05-11 [Section titled “0.1.0 - 2026-05-11”](#010---2026-05-11) Initial public release. ### Added(追加) [Section titled “Added(追加)”](#added追加-5) **Core runtime** * `HapbeatManager` — シングルトン。Wi-Fi UDP broadcast で Hapbeat デバイスと通信 * `HapbeatBridge` — `Play / PlayScaled / PlayWithCurve / Stop` を提供するサブクラスベース * `HapbeatClient` — UDP 送受信・PING/PONG・mDNS 自動検出 * `HapbeatDiscovery` — LAN 上の Hapbeat デバイスを mDNS で自動発見 * `HapbeatConfig` — Group ID・ポート・Bridge 設定を ScriptableObject で管理 **Trigger コンポーネント** * `HapbeatCollisionTrigger` — 物理衝突 / Trigger Enter|Exit に連動。速度スケールゲイン・AnimationCurve 対応 * `HapbeatAnimatorTrigger` — Animator パラメータ変化 (Bool / Float / Int) を検知して発火 * `HapbeatUnityEventTrigger` — UnityEvent の `Fire()` メソッドで任意タイミングに発火 * `HapbeatSequenceTrigger` — grab / hold / release を 1 コンポーネントで管理 * `HapbeatTickEmitter` — 連続値 (Slider・ScrollRect 等) の変化量に応じてスナップ触覚を生成 * `HapbeatParameterBinding` — Transform / Rigidbody → gain / pan をリアルタイムマッピング * `HapbeatKeyDispatcher` — キー → UnityEvent のマッピング。Input System Package 完全対応 **EventMap** * `HapbeatEventMap` ScriptableObject — Event ID・gain・mode (FIRE / CLIP) を一元管理 * `HapbeatEventEntry` — manifest.intensity を乗算した effective gain を計算 * `EventMap Window` (`Hapbeat → Event Map`) — 全エントリと配線を GUI で一覧管理、Wiring 逆引きスキャン、Play テスト **App identity (デバイスディスプレイ表示)** * `HapbeatConfig.appName` — Hapbeat デバイスのディスプレイに表示するクライアントアプリ名 (max 16 文字、空欄で `Application.productName` 自動使用) * `CONNECT_STATUS` の周期送信 (Play 中) + 接続成立時 / 終了時の通知パケット送信 **ストリーミング** * StreamClip モード — WAV を chunk 送信し、ParameterBinding で動的ゲイン・パン制御 * `streamSendAheadSeconds` で送信先行バッファを調整 **UI / Editor** * `HapbeatStatusOverlay` — 接続状態・RTT を Canvas に表示するデバッグ UI * `HapbeatEventLogger` — Hapbeat 系ログをフィルタしてファイル保存 * `HapbeatEventMapEditor` — Play-mode Snapshot/Restore、ポータビリティ確認 * `HapbeatSettingsWindow` — 接続設定 / アプリ名 / Bridge / Ping interval 等を一元編集 * Setup メニュー (`Hapbeat → Setup`) — HapbeatSDK フォルダ自動生成 * Build Samples メニュー (`Hapbeat → Build Samples`) — Basic / Tutorial の Scene + EventMap + Kit を自動生成 (Tutorial は With / Without 2 シーン同時生成) * Debug メニュー — Event Logger 配線 / ログ録画 / Logs フォルダ参照などのユーザー向け診断ツール群 **サンプル** * `BasicExample` — キーボード操作で SDK 基本機能を確認する最小構成 * `Tutorial` — 5 ゾーン (Bowling / Door / Pickup / Stream Console / Target Range) で SDK 全機能を体験。XR デバイス不要 * `XriHelpers` — `HapbeatXRGrabFilter` / `HapbeatXRSocketFilter` (XRI opt-in) **XR 向け** * XR Helpers sample で XRI grab / socket イベントを Hapbeat に橋渡し * Quest 3 / Quest 3s 動作確認済み **ドキュメント** * [installation](docs~/installation.md) — UPM Git URL 導線 * [getting-started](docs~/getting-started.md) / [triggers](docs~/triggers.md) / [event-map](docs~/event-map.md) / [parameter-binding](docs~/parameter-binding.md) / [streaming](docs~/streaming.md) — 機能別解説 * [tutorial/](docs~/tutorial/) — Tutorial サンプルの walkthrough (Plain → With 構築手順) * [editor-menus](docs~/editor-menus.md) — Hapbeat メニュー全項目の使い方逆引き * [ai-assisted-workflow](docs~/ai-assisted-workflow.md) — Claude Code 等で既存シーンに触覚を後付けする 4 ステップ + コピペプロンプト集 * [multi-app](docs~/multi-app.md) — 複数アプリ共存時の運用指針 (LAN 分離 / group ID 切り分け) # その他のコンポーネント > Trigger / Parameter Binding 以外のランタイムコンポーネント — 設定パネル・ステータス HUD・キー入力・UnityEvent 用ヘルパーのリファレンス。 Trigger 系は [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/)、Parameter Binding は [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) を参照。ここではそれ以外のランタイムコンポーネントを扱う。いずれも `Add Component → Hapbeat/` から追加できる。 | コンポーネント | 役割 | | ------------------------------------- | --------------------------------------------------------- | | **Hapbeat Address Override Panel** | player / group を実行時に設定する UI を自動生成 | | **Hapbeat Status Overlay** | 接続状態とイベント履歴を UI Text に表示するデバッグ HUD | | **Hapbeat Key Dispatcher** | 単一キー押下を UnityEvent にマップ | | **Hapbeat Action Helper** | Stop / StopAll / StopStream / Ping を Inspector から呼べるようにする | | **Hapbeat Event Logger (Diagnostic)** | UnityEvent の発火を時刻付きで Console に出力 | ## Hapbeat Address Override Panel [Section titled “Hapbeat Address Override Panel”](#hapbeat-address-override-panel) GameObject に 1 つ追加するだけで、Player -/+ ・ Group -/+ ・ Play ・ Apply ・ Exit を備えた実行時 UI が生成される。シーン側で UI 階層を組む必要はない。用途と設計の背景は [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/#override-targeting) を参照。 ### 主な設定 [Section titled “主な設定”](#主な設定) | 項目 | 既定 | 内容 | | ------------------------- | -------------------- | --------------------------------------------------------- | | `Space` | `ScreenSpaceOverlay` | 画面固定 HUD。VR で空間に置く場合は `WorldSpace` | | `World Attach Mode` | `LazyFollow` | `LazyFollow` = 視界から外れたときだけ正面へ移動 / `WorldFixed` = 置いた場所に固定 | | `Follow Distance` | `1.5` m | `WorldSpace` 時のカメラからの距離 | | `Follow Vertical Offset` | `0` m | 同、上下位置 | | `Follow Deadzone Degrees` | `10°` | この角度内にある間は移動しない | | `Follow Smooth Seconds` | `0.25` s | 移動の時定数 | | `World Pixel Density` | `3`(範囲 1〜8) | `WorldSpace` 時のフォントのラスタライズ解像度 | `Follow Camera` を未設定にすると `Camera.main` を使う。カメラが見つからない場合は警告を 1 回出して `WorldFixed` 相当で動作する。 ### コントローラー操作を受け付ける [Section titled “コントローラー操作を受け付ける”](#コントローラー操作を受け付ける) `RegisterFocusable` / `MoveFocus` / `ActivateFocused` で 2D フォーカスグリッドを操作できる。Play / Exit の実処理は `OnPlayRequested` / `OnExitRequested` から外部で注入する。実装例は VR Config Example サンプル(→ [VR Config Example](/docs/sdk-integration/unity-sdk/vr-config-example/))。 `PanelCanvasTransform` / `IsFollowingView` / `FollowVerticalOffset` / `SnapToView()` は public。自前の world-space UI をパネル Canvas の下にぶら下げたり、「視界中央へ戻す」操作を実装するために使う。 ### 実機だけ文字が滲む場合 [Section titled “実機だけ文字が滲む場合”](#実機だけ文字が滲む場合) Editor(Air Link 等)では綺麗なのに Quest 実機ビルドだけ甘い場合、**Quality レベルがプラットフォームごとに別**であることが原因。Air Link での Editor Play は Standalone 側、実機ビルドは Android 側の設定を使う。 * VR テンプレートの既定 URP アセット `Mobile_RPAsset` は **Render Scale 0.8**。`Project Settings > Quality` で Android 側が参照するアセットを開き `1.0` にすると改善する * パネルの `World Pixel Density` を上げると、同じ物理サイズのまま高い解像度でフォントをラスタライズする(フォントアトラスのメモリと引き換え) いずれも**利用側プロジェクトの設定**であり、SDK からは変更できない。 ## Hapbeat Status Overlay [Section titled “Hapbeat Status Overlay”](#hapbeat-status-overlay) 接続状態とイベント履歴を 2 つの UI Text に流し込むデバッグ HUD。 * **Status** — 接続状態 / ストリーミング中フラグ * **Log** — OnConnected / OnDisconnected / OnPong / OnError と Stream の遷移を 1 行ずつ。`maxLogLines` で上限を指定 動作確認用であり、製品ビルドに含める前提のものではない。 ## Hapbeat Key Dispatcher [Section titled “Hapbeat Key Dispatcher”](#hapbeat-key-dispatcher) 単一キーの押下を Inspector 上の UnityEvent にマップする。`PlayerInput` / `InputAction` を組むほどでもない場面(サンプル・プロトタイプ・デバッグ用)向け。 `HapbeatUnityEventTrigger` や `HapbeatActionHelper` と組み合わせると、スクリプトを書かずにキー → 触覚発火の配線ができる。 ## Hapbeat Action Helper [Section titled “Hapbeat Action Helper”](#hapbeat-action-helper) `HapbeatManager` のシングルトン専用メソッド(`Stop` / `StopAll` / `StopStream` / `Ping`)を、インスタンスメソッドとして公開するラッパー。 UI Button・Key Dispatcher・Animation Event などの UnityEvent から**ターゲットとして直接指定できる**ようになるため、この用途のためだけに MonoBehaviour を書く必要がなくなる。 ## Hapbeat Event Logger (Diagnostic) [Section titled “Hapbeat Event Logger (Diagnostic)”](#hapbeat-event-logger-diagnostic) UnityEvent 経由で呼ばれるたびに、タグと時刻を付けた 1 行を Console に出力する診断用コンポーネント。 XRI の Interactable に貼り付けて hoverEntered / selectEntered などを全て配線すると、**どのイベントがどの順序で飛ぶか**を観測できる。配線先を決めるときに使い、通常はノイズになるので外す。 配線は `Hapbeat > Attach Event Logger to Selected` で自動化できる(→ [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/))。 # Editor メニュー一覧 > Unity Editor 上の Hapbeat メニュー項目の使い方リファレンス。Window 系・シーン操作・サンプル生成・Debug の各カテゴリを説明。 Hapbeat SDK は Unity Editor のトップレベルメニュー **`Hapbeat`** にすべての操作を集約しています。役割ごとに区切り線で分かれた flat 構成で、SDK 開発者向けの項目だけが最下部の `Developer/` サブメニューに分離されています(end-user の install では非表示)。 ```plaintext Hapbeat/ Open Event Map ← Window (Event ID + Wiring 管理メイン画面) Open Batch Setup ← Window (複数 GO に Trigger を一括設定) Open Settings ← Window (接続設定 / Bridge / Override Addressing) Open Runtime Status ← Window (実行中の override / appName の解決結果) ───────────────────────────── Samples/Augment XRI Hand Demo ← XRI サンプルシーンに haptics 配線を適用 Samples/Augment XRI Hand Demo (+ diagnostic Event Logger) ───────────────────────────── Create Event Router ← シーンに [Hapbeat Event Router] を配置 Create Event Map ← EventMap .asset だけを作成 ───────────────────────────── Initial Scene Setup ← Router + EventMap を一括作成 (新規シーン用) Create HapbeatSDK Folder ← Assets/HapbeatSDK/ の標準レイアウトを生成 ───────────────────────────── Export Event Map (Selected) ← 選択中 EventMap を Markdown summary に書き出し Export Event Map (All in Project) ← project 内全 EventMap を一括 Markdown 化 Normalize Audio Folder (16kHz · 2ch ...) ← フォルダ内 WAV を 16kHz / stereo / PCM16 に揃える ───────────────────────────── Attach Event Logger to Selected ← 選択 GO の UnityEvent をログに流す配線を追加 Remove Event Logger Wiring from Selected ← 上記の解除 Logs/Start Recording ← Hapbeat 系ログのファイル記録を開始 Logs/Stop Recording ← 記録を停止して保存 Logs/Reveal Current File ← 記録中ログを Explorer/Finder で表示 Logs/Open Logs Folder ← ログ保存先フォルダを開く Logs/Dump Last Recording to Console ← 直近のログを Console に流す Close Edit-mode Transport ← Edit-mode の UDP 接続を強制クローズ Disable Verbose Log on All Hapbeat Components ← _verboseLog / _debugLog 一括 off Diagnostics/Check Sample Versions ← Import 済みサンプルと SDK 版の整合を確認 Diagnostics/Check for SDK Updates ← 新しい SDK が出ていないか今すぐ確認 Diagnostics/Check for SDK Updates on Startup ← 起動時の自動確認の ON/OFF ───────────────────────────── Developer/Build Basic Example ← Basic サンプル一式の scaffold (Local/Embedded install のみ) Developer/Sync HapbeatSDK → Samples~ (Showcase) Developer/Sync HapbeatSDK → Samples~ (BasicExample) ``` セクションの分け方: 1. **Window 系** (top): ウィンドウを開く操作。よく使うので最上段に配置。 2. **Samples**: Import 済みサンプルに対して配線を適用するコマンド。 3. **Create 系**: 日常的な author 操作。Event Router / Event Map を個別に作成。 4. **Initial / 1 回限り**: 初期セットアップや特殊ケースで実行するもの。Initial Scene Setup は (Router + EventMap + フォルダ) の一括コマンド、Deploy Imported Sample は Samples フォルダから HapbeatSDK/ への展開。 5. **Authoring tools**: EventMap export / Audio フォーマット変換 (アセット成果物の加工)。 6. **Diagnostics**: 配線テスト・ログ記録・transport 緊急クローズ・冗長ログ一括 OFF。デバッグ目的。 7. **Developer** (gate hidden in UPM consumer installs): SDK 開発者専用。`HapbeatDevModeMenuGate` で UPM Git URL / registry installs では非表示。 加えて以下のメニュー位置にも Hapbeat エントリがあります: * **GameObject → Hapbeat → Event Router** (Hierarchy 右クリック含む) — `Create Event Router` と同じ * **Assets → Create → Hapbeat → Config / Event Map** — ScriptableObject 生成 * **Add Component → Hapbeat/…** — 各種 Trigger / Bridge / Helper をコンポーネントとして追加 *** ## Window 系 [Section titled “Window 系”](#window-系) ### Settings [Section titled “Settings”](#settings) 接続設定を編集する Window。 | 設定 | 用途 | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Port | UDP ポート (デフォルト 7700) | | アプリ名 | Hapbeat デバイスのディスプレイに表示するクライアントアプリ名。**Max 16 文字** (display grid 幅)。デフォルトの `app_name` 要素 (8x1) では先頭 8 文字のみ表示。空欄なら `Application.productName` が自動使用 (16 文字超過時は切り詰め)。`

` / `` は送信直前に override 値へ置換 | | **Override Addressing (this build)** | ビルド全体で固定する player / group。空欄 (無効) なら EventMap のターゲットをそのまま使用 → [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/) | | Use Bridge / Bridge Host | ESP-NOW 経由 (上位構成) を使う場合のみ ON | | Ping Interval | キープアライブ送信間隔 (秒) | | Haptic Delay | 全発火に一律で足す遅延 (ms)。映像との同期合わせ用 | | Stream Send Ahead | CLIP 送信の先行時間 (秒) | | Stream / Command Unicast | 既知デバイスへ unicast で送る (既定 ON) → [通信モデル](/docs/concepts/communication-model/) | | Enable / Verbose Logging | Console ログの出力量 | 実機との Ping テストや、シーン外からの設定編集に使います。`Assets/Create/Hapbeat/Config` で生成した `HapbeatConfig` ScriptableObject の Inspector と内容は同じ。 ### Runtime Status [Section titled “Runtime Status”](#runtime-status) Play モード中の**解決結果**を確認する Window。設定値そのものではなく「いま実際に何が送られているか」を表示します。 * **Override Addressing (this device)** — 実行時 API / 設定パネルで設定された player / group の現在値 * **App Name** — `appName` のテンプレートと、`

` / `` 置換後の実際の送信文字列 override が効いているか、OLED に出るはずの文字列が想定どおりかを、実機を見ずに確認できます。 ### Event Map [Section titled “Event Map”](#event-map) **Hapbeat 統合作業のメイン画面**。シーン内の Event ID 一覧、各 Trigger の wiring (どの GO にどの Trigger が付いているか)、ParameterBinding の設定をすべてここから操作します。 主な機能: * 左ペイン: EventMap.asset の全エントリ一覧 (mode / target / gain などをカード表示) * 右ペイン: 選択中エントリの詳細編集 (Event ID / streamClip / target / gain / Notes / Bindings) * Wiring セクション: 選択中エントリを発火する Trigger を逆引きスキャン * Play モード中の Test 再生 / Snapshot/Restore (実機調整時の値を保存・復元) 詳細: [Event Map ウィンドウ](./event-map.md) ### Batch Setup [Section titled “Batch Setup”](#batch-setup) 複数の GameObject (例: 同じ Tag の Pin × 6) に Trigger コンポーネントを一括追加するための補助 Window。Drag\&Drop で参照を取り込み、適用先を絞ってまとめて配線できます。 ユースケース: * ボウリングのピン 6 個に同じ `HapbeatCollisionTrigger` を配るとき * XR インタラクタブル多数に `HapbeatUnityEventTrigger` を配るとき *** ## サンプル [Section titled “サンプル”](#サンプル) ### Samples/Augment XRI Hand Demo [Section titled “Samples/Augment XRI Hand Demo”](#samplesaugment-xri-hand-demo) XR Interaction Toolkit のサンプルシーン `HandsDemoScene` に、Hapbeat の触覚コンポーネントと UnityEvent 配線を適用します。開いているシーンに対して動作し、Undo 1 回で全て取り消せます。冪等なので再実行しても重複しません。 `(+ diagnostic Event Logger)` 付きの方は、加えて Poke ボタンの XRI イベントをすべて Console に流します。配線先を検討するとき用。 手順の全体は [XRI Hand Demo に haptics を追加](/docs/sdk-integration/unity-sdk/xri-handdemo-quickstart/) を参照。 *** ## シーン操作 [Section titled “シーン操作”](#シーン操作) ### Initial Scene Setup [Section titled “Initial Scene Setup”](#initial-scene-setup) 新規シーンへの推奨入口。次を 1 コマンドで揃えます: * `Assets/HapbeatSDK/` フォルダレイアウト (Kits / Scenes / EventMaps) * `[Hapbeat Event Router]` GameObject (内部に `HapbeatManager` singleton) * `Assets/HapbeatSDK/EventMaps/-EventMap.asset` * Event Map ウィンドウを開いて新規 asset を選択状態にする 再実行は idempotent — 既存の Router / EventMap があれば再利用するだけで、複製や上書きはしません。 ### Create Event Router [Section titled “Create Event Router”](#create-event-router) `[Hapbeat Event Router]` GameObject だけを配置します。中身は `HapbeatManager` (singleton)。EventMap は触らないので、すでに EventMap を持っていてシーンに Router だけ追加したい場合に使います。 > Hierarchy 右クリック → `Hapbeat → Event Router` でも同じことができます。 ### Create Event Map [Section titled “Create Event Map”](#create-event-map) `Assets/HapbeatSDK/EventMaps/...asset` だけを生成します。シーンに Router は追加しません。複数の EventMap を持ちたい advanced ケース用 (例: シーンごとに別の EventMap を持つ)。 > `Assets → Create → Hapbeat → Event Map` でも同じ asset を作れますが、こちらは保存先フォルダを尋ねます (HapbeatSDK 標準パスは尊重されない)。 *** ## Setup / Asset 準備 [Section titled “Setup / Asset 準備”](#setup--asset-準備) ### Create HapbeatSDK Folder [Section titled “Create HapbeatSDK Folder”](#create-hapbeatsdk-folder) `Assets/HapbeatSDK/` 配下に標準レイアウトを生成します: ```plaintext Assets/HapbeatSDK/ ├── Kits/ ← 触覚波形 (Studio から deploy / 自前 Kit を置く場所) ├── Scenes/ ← 生成サンプルシーン └── EventMaps/ ← EventMap.asset ``` `Initial Scene Setup` も内部で呼ぶので、明示的に叩く必要はありません。「最初に手動で枠だけ作っておきたい」時の補助。 ### Normalize Audio Folder (16kHz · 2ch · PCM16) [Section titled “Normalize Audio Folder (16kHz · 2ch · PCM16)”](#normalize-audio-folder-16khz--2ch--pcm16) 指定フォルダ配下の WAV を Hapbeat 標準形式 (16kHz / stereo / PCM16) に揃えます。Tutorial 用音声を一括コンバートする時など、StreamClip mode で送信予定の素材整形に使います。 *** ## Export [Section titled “Export”](#export) ### Export Event Map (Selected) / (All in Project) [Section titled “Export Event Map (Selected) / (All in Project)”](#export-event-map-selected--all-in-project) `HapbeatEventMap.asset` の内容を Markdown summary として書き出します。AI 支援で wiring を相談する時や、デザインドキュメントへ貼る用途を想定。 * `Selected` — Project ビューで選択中の EventMap だけ * `All in Project` — `t:HapbeatEventMap` で project 全体を一括書き出し 詳細: [AI 支援ワークフロー](./ai-assisted-workflow.md) *** ## 診断 / Debug [Section titled “診断 / Debug”](#診断--debug) ユーザーが触ってよい範囲の診断ユーティリティ。バグ報告時に Logs を添付してもらうのが推奨フローです。 ### Attach Event Logger to Selected / Remove Event Logger Wiring from Selected [Section titled “Attach Event Logger to Selected / Remove Event Logger Wiring from Selected”](#attach-event-logger-to-selected--remove-event-logger-wiring-from-selected) 選択中 GameObject の UnityEvent (XR Interactable の `selectEntered` など) を Console にログ出力する補助配線を追加 / 解除します。 何が起きているか可視化したい時、Trigger を仕込む前の発火タイミング確認に便利。AI 支援で wiring を組むときも、まずこれで「どのイベントがいつ飛ぶか」を観察すると設計がブレません。詳細: [AI 支援ワークフロー](./ai-assisted-workflow.md)。 ### Logs/ [Section titled “Logs/”](#logs) Hapbeat 系のログ (Console 出力 + 実行イベント) をファイルに記録する機能群。 | メニュー | 用途 | | ------------------------------ | -------------------------------------- | | Start Recording | フィルタ済みログのファイル記録を開始 | | Stop Recording | 記録を停止し、ファイルを Explorer/Finder で表示 | | Reveal Current File | 記録中ファイルを Explorer/Finder で表示 (記録中のみ有効) | | Open Logs Folder | 過去のログを集めてあるフォルダを開く | | Dump Last Recording to Console | 直近のログを Console に書き出す (記録停止後の確認用) | **バグ報告のおすすめフロー:** 1. `Logs/Start Recording` を実行 2. 再現手順を実行 (Play → 問題発生 → Stop) 3. `Logs/Stop Recording` で保存 → ファイルを Issue / DM に添付 ### Close Edit-mode Transport [Section titled “Close Edit-mode Transport”](#close-edit-mode-transport) Edit-mode で開いている UDP / mDNS transport を強制クローズします。「Play モードに入る前から接続テストしたい」「ポートが掴まれっぱなしで Play できない」などのレアケース用。 通常は触る必要はありません。 *** ## Developer (Local / Embedded install only) [Section titled “Developer (Local / Embedded install only)”](#developer-local--embedded-install-only) SDK 開発者向け。end-user の UPM Git URL / registry / tarball install では `HapbeatDevModeMenuGate` により非表示になり、メニュー自体が現れません。 ### Build Basic Example [Section titled “Build Basic Example”](#build-basic-example) Basic Example サンプル一式 (Kit / EventMap / Scene) を `Assets/HapbeatSDK/SDK_Samples/BasicExample/` に scaffold します。Package Manager で Basic Example を Import 済みであることが前提。 End user は Package Manager の Sample Import で直接 Scene を開けるため、このメニューは通常不要です。 ### Sync HapbeatSDK → Samples\~ (Showcase) / (BasicExample) [Section titled “Sync HapbeatSDK → Samples\~ (Showcase) / (BasicExample)”](#sync-hapbeatsdk--samples-showcase--basicexample) `Assets/HapbeatSDK/SDK_Samples//` で編集した Scene / EventMap / Animation を package の `Samples~//` に書き戻します。SDK 自体を編集している人向けの maintainer 専用コマンド。 *** ## ScriptableObject 生成 (`Assets/Create/Hapbeat/`) [Section titled “ScriptableObject 生成 (Assets/Create/Hapbeat/)”](#scriptableobject-生成-assetscreatehapbeat) Project ビューの右クリック → `Create → Hapbeat`: | 項目 | 用途 | | --------- | ----------------------------------------- | | Config | `HapbeatConfig.asset` (接続設定の置き場) | | Event Map | `HapbeatEventMap.asset` (Event ID 一覧の置き場) | 生成位置: 右クリックしたフォルダの直下。**プロジェクトに 1 つあれば足りる** ため、`Assets/HapbeatSDK/EventMaps/` 配下に置くのを推奨。 *** ## コンポーネント (`Add Component → Hapbeat/`) [Section titled “コンポーネント (Add Component → Hapbeat/)”](#コンポーネント-add-component--hapbeat) GameObject の Inspector → Add Component → 検索欄に `Hapbeat`: | コンポーネント | 用途 | | --------------------------- | ----------------------------------------------- | | Hapbeat Collision Trigger | 物理衝突 / Trigger Enter / Exit で発火 | | Hapbeat Sequence Trigger | grab / hold / release を 1 component で扱う | | Hapbeat Tick Emitter | 連続値 (Slider 等) の変化量に応じてスナップ触覚 | | Hapbeat Unity Event Trigger | UnityEvent の `Fire()` メソッドから任意発火 | | Hapbeat Parameter Binding | Transform / Rigidbody → gain / pan のリアルタイムマッピング | | Hapbeat Action Helper | Stop / StopAll / Ping を UnityEvent から呼ぶラッパ | | Hapbeat Event Logger | UnityEvent 発火を Console に流す (Debug 用) | | Hapbeat Key Dispatcher | キー押下を UnityEvent にマップ (sample / proto 用) | | Hapbeat Status Overlay | 接続状態と Log を Canvas に表示 | > Animator state からの発火は **`HapbeatStateBehaviour`** を使います。これは StateMachineBehaviour なので、GameObject の Add Component ではなく **Animator window で state を選択 → Inspector → Add Behaviour** から追加します。詳細: [Trigger コンポーネント](./triggers.md#hapbeatstatebehaviour)。 詳細: [Trigger コンポーネント](./triggers.md) / [Parameter Binding](./parameter-binding.md) # EventMap ウィンドウ > HapbeatEventMap asset / EventMap ウィンドウの全フィールド・全 UI・全自動機能を網羅した SDK 内部リファレンス。 ![unity-eventmap](/_astro/unity-eventmap.BPQUKMfa_mQIkm.webp) EventMap は **Event ID と触覚エントリの対応関係を一括管理する ScriptableObject + 専用 Editor ウィンドウ**です。Trigger / StateBehaviour / スクリプトはすべて EventMap entry を参照し、エントリ側で mode・gain・target・bindings 等を一元定義します。 本ページは AI / 上級ユーザー向けの **網羅的 reference**。読みやすさより漏れの無さを優先。タスク指向の使い方は [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) と [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) を参照。 *** ## 1. 全体構造 [Section titled “1. 全体構造”](#1-全体構造) ```plaintext HapbeatEventMap (ScriptableObject; .asset) ├─ entries : List └─ revertPlayModeChanges : bool HapbeatEventEntry ├─ _id (stable GUID, lazy-assigned) ├─ mode (Command / StreamClip) ├─ displayName / category / eventName (→ eventId = "category.eventName") ├─ streamClip / loop (StreamClip mode) ├─ bindings : List (StreamClip mode) ├─ gain ├─ target ├─ delayOffsetSeconds ├─ notes ├─ manifestOverride └─ _cachedManifestIntensity (HideInInspector) HapbeatBindingPreset (entry.bindings の要素) ├─ _id (stable GUID) ├─ ownerObjectName ├─ sourceTransformPath ├─ sourceProperty, inputMin/Max ├─ curveType, customCurve ├─ outputParameter, outputMin/Max └─ debugLog, debugLogInterval, debugLogChangeThreshold ``` エントリ間の参照は **stable GUID (`_id`)** で行うため、reorder / insert / delete / duplicate しても trigger 側の wiring は維持される。`_id` は初回 `.id` getter アクセス時に lazy-assign される。Editor は window 起動時 (`EnsureEntryIdsAssigned`) と entry 追加時に proactively 割り当てる。 *** ## 2. HapbeatEventMap (asset 本体) [Section titled “2. HapbeatEventMap (asset 本体)”](#2-hapbeateventmap-asset-本体) | フィールド | 型 | 説明 | | ----------------------- | ------------------------- | --------------------------------------------------------------- | | `entries` | `List` | エントリの並び。順序は表示順のみで wiring には影響しない | | `revertPlayModeChanges` | `bool` | true の場合、Play 中に行った EventMap 変更を Play 終了時に自動 revert(非破壊チューニング用) | ### Asset 作成 [Section titled “Asset 作成”](#asset-作成) | メニュー | 動作 | | --------------------------------------- | ------------------------------------------------------------- | | `Assets → Create → Hapbeat → Event Map` | 右クリックフォルダ直下に asset 生成 | | `Hapbeat → Create Event Map` | `Assets/HapbeatSDK/EventMaps/-EventMap.asset` に生成 | | `Hapbeat → Initial Scene Setup` | Router GameObject + asset + window オープンを一括実行 | Asset 作成後の `.asset` の場所はどこでも良いが、`HapbeatSDK/EventMaps/` 配下が推奨。Initial Scene Setup は active scene 名を asset ファイル名に組み込む (`-EventMap.asset`)。 ### Public API (`HapbeatEventMap` クラス) [Section titled “Public API (HapbeatEventMap クラス)”](#public-api-hapbeateventmap-クラス) | メソッド | 戻り値 | 用途 | | -------------------------------- | -------------------- | ------------------------------------------------- | | `GetEntry(int index)` | `HapbeatEventEntry?` | list index ベース(legacy、index 不安定なので非推奨) | | `FindById(string id)` | `HapbeatEventEntry?` | **stable GUID 検索(推奨)**。Trigger 側はこれを使う | | `IndexOfId(string id)` | `int` | GUID → list index(-1 if not found) | | `FindByName(string displayName)` | `HapbeatEventEntry?` | `displayName` 完全一致 | | `FindByEventId(string eventId)` | `HapbeatEventEntry?` | `category.eventName` 完全一致 | | `GetDisplayNames()` | `string[]` | エディタ dropdown 用。各行に `[index] icon displayName` 形式 | *** ## 3. HapbeatEventEntry 全フィールド [Section titled “3. HapbeatEventEntry 全フィールド”](#3-hapbeatevententry-全フィールド) ### 3.1 識別子 [Section titled “3.1 識別子”](#31-識別子) | フィールド | 型 | 既定値 | 説明 | | ---------------- | -------------------------- | -------------- | ------------------------------------------------------------------ | | `_id` | `string` (HideInInspector) | "" → lazy GUID | stable identifier。reorder / insert に強い。Trigger は `_entryId` でこれを保持 | | `HasId` (getter) | `bool` | — | `.id` getter が副作用なく評価できるか | | `RegenerateId()` | — | — | 強制再生成。Duplicate 時に使う | ### 3.2 Mode [Section titled “3.2 Mode”](#32-mode) | フィールド | 型 | 既定値 | 説明 | | ------ | ----------------- | --------- | ------------------------------------------------------------------ | | `mode` | `HapticMode` enum | `Command` | `Command`(device 内蔵 Kit clip を ID で再生)/ `StreamClip`(PCM を UDP 送信) | UI 表示ラベルは `s_ModeLabels`: * `FIRE (Command)` * `CLIP (Stream Clip)` 旧 `LIVE` (stream\_source) モードは廃止済み。enum 名は API 互換のため `Command` / `StreamClip` のまま。 #### HapticMode.GetModeIcon() の出力 [Section titled “HapticMode.GetModeIcon() の出力”](#hapticmodegetmodeicon-の出力) | Mode | Icon (1 文字) | 備考 | | ------------ | ------------ | --------------- | | `Command` | `>` | FIRE | | `StreamClip` | `♪` (U+266A) | CLIP | | (fallback) | `●` (U+25CF) | 未知 enum value 用 | ### 3.3 識別 / 表示 [Section titled “3.3 識別 / 表示”](#33-識別--表示) | フィールド | 型 | 用途 | | -------------------- | -------- | ----------------------------------------------------------------------------- | | `displayName` | `string` | エディタ表示用ラベル(例: “Landing Impact”) | | `category` | `string` | event-id の前半。`^[a-z][a-z0-9_-]{0,63}$` のセグメント形式 | | `eventName` | `string` | event-id の後半。同上のセグメント形式 | | `eventId` (computed) | `string` | `"{category}.{eventName}"`。`category` 空時は `eventName` のみ、`eventName` 空時は `""` | `IsValidSegment(segment)`: `^[a-z][a-z0-9_-]{0,63}$` の正規表現で検証。 `IsValid()`: category + eventName 両方が valid segment。 ### 3.4 StreamClip mode 専用 [Section titled “3.4 StreamClip mode 専用”](#34-streamclip-mode-専用) | フィールド | 型 | 既定値 | 説明 | | ------------ | ---------------------------- | ----- | ------------------------------------------------------------------- | | `streamClip` | `AudioClip` | null | UDP で送信する PCM16 source。`HapbeatSDK/Kits//stream-clips/` 配下推奨 | | `loop` | `bool` | false | true で Stop() まで再生継続。SequenceTrigger の hold phase / 連続 modulation 用 | | `bindings` | `List` | empty | Parameter Binding preset の登録リスト | streamClip 変更時、所属 Kit の `*-manifest.json` を `manifestOverride` に **auto-attach** する(同じ entry の `_cachedManifestIntensity` も refresh)。 ### 3.5 Gain [Section titled “3.5 Gain”](#35-gain) | フィールド | 型 | 範囲 | 説明 | | ------ | ------- | -------------------------- | ------------------------------------------------------------------ | | `gain` | `float` | `[0, 2]` (Range attribute) | master gain。`GetEffectiveGain() = gain × _cachedManifestIntensity` | ### 3.6 Targeting [Section titled “3.6 Targeting”](#36-targeting) | フィールド | 型 | 例 | | -------- | -------- | ------------------------------------------------------------------------------------------------------ | | `target` | `string` | `""`(全デバイス) / `player_1` / `*/pos_neck` / `player_1/pos_chest` / `team_red/player_1/pos_chest/group_3` | `HasTarget` (getter): `!string.IsNullOrEmpty(target)`。 #### `StandardPositions` 定数 [Section titled “StandardPositions 定数”](#standardpositions-定数) contracts の device-addressing spec に準拠した body position 12 種: ```plaintext pos_neck, pos_chest, pos_abd, pos_l_arm, pos_r_arm, pos_l_wrist, pos_r_wrist, pos_hip, pos_l_thigh, pos_r_thigh, pos_l_ankle, pos_r_ankle ``` 対応する `PositionLabels`: ```plaintext Neck, Chest, Abdomen, Left Arm, Right Arm, Left Wrist, Right Wrist, Hip, Left Thigh, Right Thigh, Left Ankle, Right Ankle ``` #### `BuildTarget(int player = -1, string position = null)` [Section titled “BuildTarget(int player = -1, string position = null)”](#buildtargetint-player---1-string-position--null) target 文字列を組み立てる static ヘルパー: | player | position | 戻り値 | | ------ | ---------- | ----------------------- | | `> 0` | non-empty | `player_/` | | `> 0` | null/empty | `player_` | | `-1` | non-empty | `*/` | | `-1` | null/empty | `""`(全デバイス) | window UI ではさらに **Prefix** (例: `team_red`) と **Group** (`group_` suffix) を加えて 4 セグメント (`prefix/player_N/position/group_N`) を構築できる。 ### 3.7 Latency offset [Section titled “3.7 Latency offset”](#37-latency-offset) | フィールド | 型 | 範囲 | 説明 | | -------------------- | ------- | --------------------- | ---------------------------------------------------------------------------------------------------- | | `delayOffsetSeconds` | `float` | `[-0.2, 0.2]` (Range) | per-entry オフセット。global (`HapbeatConfig.hapticDelaySeconds`) と加算され、`max(0, global + offset)` が実 delay | UI に effective delay を ms 単位で readout 表示。 ### 3.8 Notes / メタ [Section titled “3.8 Notes / メタ”](#38-notes--メタ) | フィールド | 型 | 説明 | | -------------------------- | ------------------------- | ---------------------------------------------------------------------------------- | | `notes` | `string` (TextArea 1-3 行) | デザイナーメモ。device には送られない | | `manifestOverride` | `TextAsset` | 特定 `-manifest.json` を強制参照。未設定なら auto-resolve (clip path → eventId match) | | `_cachedManifestIntensity` | `float` (HideInInspector) | manifest から resolve した intensity 値。`-1f` は “未解決”、`0..1` は authored 値 | `CachedManifestIntensity` (getter): 読み取り専用 accessor。 `SetCachedManifestIntensity(float)`: Editor-only setter (duplicate 時の cache 伝搬等)。 ### 3.9 派生メソッド [Section titled “3.9 派生メソッド”](#39-派生メソッド) | メソッド | 戻り値 | 説明 | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------- | | `GetEffectiveGain()` | `float` | `_cachedManifestIntensity < 0 ? gain : gain × _cachedManifestIntensity`。intensity=0 は honored (silence) | | `GetSummary()` | `string` | List 表示用要約。StreamClip → `streamClip.name`、Command → `eventId` | | `GetModeIcon()` | `string` | `>` / `♪` / `●` | *** ## 4. HapbeatBindingPreset 全フィールド [Section titled “4. HapbeatBindingPreset 全フィールド”](#4-hapbeatbindingpreset-全フィールド) `HapbeatEventEntry.bindings` の要素。StreamClip 再生中に gain / pan を modulate するための preset。 | フィールド | 型 | 既定値 | 説明 | | ------------------------- | ----------------------------- | ------------------- | ------------------------------------------------------------------------------ | | `_id` | `string` (HideInInspector) | "" → lazy GUID | stable identifier。runtime の `HapbeatParameterBinding._linkedBindingId` から参照される | | `ownerObjectName` | `string` | `""` | binding の scope。空 = shared (全 wired GO に適用) / 非空 = 指定 GameObject 名にのみ適用 | | `sourceTransformPath` | `string` | `""` | trigger root からの相対 path。空 or `.` = trigger 自身 | | `sourceProperty` | `BindingSourceProperty` enum | `LocalPositionY` | 入力ソース種別 | | `inputMin` | `float` | `0` | 入力レンジ下端 → `outputMin` にマップ | | `inputMax` | `float` | `1` | 入力レンジ上端 → `outputMax` にマップ | | `curveType` | `BindingCurveType` enum | `Linear` | normalize 後の curve | | `customCurve` | `AnimationCurve` | `Linear(0,0)-(1,1)` | `curveType=Custom` のとき | | `outputParameter` | `BindingOutputParameter` enum | `StreamGain` | modulate 対象 | | `outputMin` | `float` | `0` | 出力下端 | | `outputMax` | `float` | `1` | 出力上端 | | `debugLog` | `bool` | false | per-frame debug log の on/off | | `debugLogInterval` | `float` | `0.1` | log throttle (秒) | | `debugLogChangeThreshold` | `float` | `0.02` | 正規化値がこれ以上動かないと log を出さない | ### 4.1 BindingSourceProperty (10 値) [Section titled “4.1 BindingSourceProperty (10 値)”](#41-bindingsourceproperty-10-値) | 値 | 入力 | 必要 ref | | ---------------------------- | ------------------------------------------------------------------------ | ------------------------------- | | `LocalPositionX` / `Y` / `Z` | `_sourceTransform.localPosition.{x,y,z}` | `_sourceTransform` | | `LocalScaleX` / `Y` / `Z` | `_sourceTransform.localScale.{x,y,z}` | `_sourceTransform` | | `VelocityMagnitude` | `Rigidbody.linearVelocity.magnitude` (Unity 6+) or `.velocity.magnitude` | `_sourceTransform` 上の Rigidbody | | `AngularVelocityMagnitude` | `Rigidbody.angularVelocity.magnitude` | 同上 | | `PositionDeltaMagnitude` | `(pos - prevPos).magnitude / Time.deltaTime`。Kinematic / XRGrab 用 | `_sourceTransform` | | `SliderValue` | `Slider.value` | `_sourceSlider` | | `External` | `binding.SetValue(v)` で外部 push | なし | ### 4.2 BindingCurveType (5 値) [Section titled “4.2 BindingCurveType (5 値)”](#42-bindingcurvetype-5-値) | 値 | 数式 | | ------------- | -------------------------- | | `Linear` | `t` | | `EaseIn` | `t²` | | `EaseOut` | `1 - (1-t)²` | | `Exponential` | `(e^(3t) - 1) / (e^3 - 1)` | | `Custom` | `customCurve.Evaluate(t)` | ### 4.3 BindingOutputParameter (2 値) [Section titled “4.3 BindingOutputParameter (2 値)”](#43-bindingoutputparameter-2-値) | 値 | 範囲 | 効果 | | ------------ | ---------------- | --------------------------------------------------------------------------------------------- | | `StreamGain` | `0..2` (clamped) | `playback.ApplyGainModulation(output)`。`entry.gain × manifest.intensity × bindingOutput` の最終段 | | `StreamPan` | `-1..+1` | `playback.Pan = output`。equal-power pan law。mono clip では無視 | *** ## 5. EventMap ウィンドウ [Section titled “5. EventMap ウィンドウ”](#5-eventmap-ウィンドウ) ### 5.1 起動 [Section titled “5.1 起動”](#51-起動) * メニュー: `Hapbeat → Open Event Map` (SDK menu 旧名: `Hapbeat → Event Map`) * 内部実装: `HapbeatEventMapWindow : EditorWindow` * 最小サイズ: 500 × 300 * Tab タイトル: `Hapbeat Event Map`(未保存時は末尾に `*`) ### 5.2 永続設定 (EditorPrefs) [Section titled “5.2 永続設定 (EditorPrefs)”](#52-永続設定-editorprefs) | Key | 内容 | | ------------------------------ | --------------------------------------------- | | `HapbeatEventMap_SelectedGUID` | 直近選択された EventMap asset の GUID | | `HapbeatEventMap_SplitRatio` | List view の左右ペイン分割比 (`0.2 .. 0.8`、デフォルト 0.42) | | `HapbeatEventMap_ViewMode` | 0 = List / 1 = Table | ### 5.3 Auto-save / Dirty 管理 [Section titled “5.3 Auto-save / Dirty 管理”](#53-auto-save--dirty-管理) * 編集ごとに `EditorUtility.SetDirty` + `AssetDatabase.SaveAssetIfDirty` を実行 * `OnLostFocus` / `OnDisable` でも保存(domain reload / Unity 終了で edit が失われないように) * toolbar に `● Save` (orange) / `✓ Saved` の indicator ### 5.4 Toolbar [Section titled “5.4 Toolbar”](#54-toolbar) 横一列、左から: | 要素 | 機能 | | ---------------------------- | ---------------------------------------------- | | `Event Map:` Object Field | 編集対象 EventMap を切替(GUID で persist) | | `● Save` / `✓ Saved` | dirty 状態と手動保存ボタン | | (FlexibleSpace) | | | `Batch Setup` | `HapbeatBatchSetupWindow` を開く | | `Scan Scene` | scene 内 Trigger / State / Script wiring を再スキャン | | `↻` | manifest intensity cache を全 entry 分 refresh | | `List` / `Table` (segmented) | view mode 切替 | | `+` | 新規 entry 追加(直前 entry の mode を継承) | | `−` | 選択中 entry を削除 | ### 5.5 キーボード操作 [Section titled “5.5 キーボード操作”](#55-キーボード操作) | キー | 動作 | | --------- | ------------------------ | | `↑` / `↓` | 選択 entry 移動 | | `Esc` | 進行中の drag-reorder をキャンセル | *** ## 6. List view [Section titled “6. List view”](#6-list-view) ### 6.1 構成 [Section titled “6.1 構成”](#61-構成) * 左ペイン: entry 一覧(行ごとに mode icon + displayName + summary) * スプリッタ (4px 幅) — ドラッグでペイン比変更 * 右ペイン: 選択 entry の詳細編集 ### 6.2 行操作 (左ペイン) [Section titled “6.2 行操作 (左ペイン)”](#62-行操作-左ペイン) | 操作 | 動作 | | --------------------------- | ----------------------------- | | 左クリック | 選択 | | ドラッグハンドル (`☰`) を 10px 超ドラッグ | 順序変更。GUID 参照なので wiring は保持される | | 右クリック | context menu (下記) | ### 6.3 行 context menu (単一選択時) [Section titled “6.3 行 context menu (単一選択時)”](#63-行-context-menu-単一選択時) * Copy Entry Values / Paste Entry Values(clipboard 経由、binding preset id は再生成) * Add Entry Above / Below * Duplicate Entry(GUID 再生成) * Delete Entry ### 6.4 右ペイン: Entry Detail [Section titled “6.4 右ペイン: Entry Detail”](#64-右ペイン-entry-detail) 順に描画される UI: 1. **Test Play Bar**(次節 §6.5) 2. **Name** — `displayName` の TextField 3. **Mode** — `FIRE (Command)` / `CLIP (Stream Clip)` popup 4. **Mode 別フィールド**: * Command: Category / EventName(segment validation)+ Kit eventId dropdown(`HapbeatSDK/Kits//-manifest.json` から候補列挙) * StreamClip: `Clip` (AudioClip) + Kit folder hint (`stream-clips/`) + `Loop` toggle 5. **Gain** — 0..2 slider 6. **Delay Offset (s)** — −0.2..+0.2 slider + effective delay readout (ms) 7. **Targeting セクション**: * `Prefix` TextField (optional `team_red` 等) * `Player` IntField (1..99 / -1) * `Position` Popup (12 標準 + “(none)”) * `Group` IntField (1..99 / -1) * → `target` 自動構築 + read-only preview 8. **Wiring セクション**(§7.1) 9. **State Wiring セクション**(§7.2) 10. **Script Wiring セクション**(§7.3) 11. **Parameter Bindings セクション**(StreamClip のみ; §8) 12. **Notes** — TextArea ### 6.5 Test Play Bar [Section titled “6.5 Test Play Bar”](#65-test-play-bar) * ボタン: `▶ Test Play` (green) ⇔ `■ Stop` (red) の toggle * Play 中: `HapbeatManager.Instance` 経由 * Edit 中: `HapbeatEditorTransport` を lazy open * 右側: **Manifest** 行(`Hapbeat → Open Settings` で設定する `HapbeatConfig` の port/group を使う) * Label `Manifest` * Picker (`*-manifest.json` 限定 TextAsset field) — `manifestOverride` を設定 * `↻` Refresh ボタン — clip の所属フォルダを walk up して `*-manifest.json` を auto-attach * inline hint: 接続未確立 / missing intensity warning / streaming indicator * パネル幅 < 260px で compact mode(label 省略) *** ## 7. 自動 Wiring スキャン [Section titled “7. 自動 Wiring スキャン”](#7-自動-wiring-スキャン) Trigger / State / Script の 3 種について、scene および AnimatorController asset を walk して **どの entry が誰から発火されているか** を逆引き表示する。`Scan Scene` ボタンで明示再スキャン。`EditorApplication.delayCall` で再スキャンが要求されるケース(destroyed object 検出など)あり。 ### 7.1 Trigger Wiring (TriggerInfo) [Section titled “7.1 Trigger Wiring (TriggerInfo)”](#71-trigger-wiring-triggerinfo) scene 内の全 `HapbeatTriggerBase` を walk: | フィールド | 内容 | | ---------------- | -------------------------------------------------------------------- | | `trigger` | component instance | | `gameObjectName` | display | | `typeName` | `Coll` / `Seq` / `Tick` / `Event` | | `wiredEvents` | UnityEvent reflection で接続元を列挙(例: `XRGrabInteractable.selectEntered`) | Wiring セクションは GameObject 単位で grouping。各 GO 行に inline: * GameObject link button (click で ping) * type tag (`Coll` / `Seq` / `Tick` / `Event`) * `gain` inline editor (live scene component の `_gainMultiplier` を直接 RW) * TickEmitter のみ: `Δ` (tick threshold) + `axis` (X/Y/Mag) inline editor ### 7.2 State Wiring (StateWiringInfo) [Section titled “7.2 State Wiring (StateWiringInfo)”](#72-state-wiring-statewiringinfo) `HapbeatStateBehaviour` を AnimatorController asset 上で列挙。scene 上で対応する Animator GO がいれば、その GO に紐づける(複数 GO で同 controller を共有可)。 | フィールド | 内容 | | ----------------------------------- | ------------------------------------ | | `behaviour` | StateMachineBehaviour instance | | `controller` | AnimatorController asset | | `layerName` / `stateName` / `phase` | ”Enter” / “Exit” | | `animatorObject` | scene Animator GO(null = asset only) | UI: GO 単位 grouping + `State` tag + `gain` editor。asset only entries は最後に “(Controllers without a scene Animator)” 見出しで列挙。 ### 7.3 Script Wiring (ScriptWiringInfo) [Section titled “7.3 Script Wiring (ScriptWiringInfo)”](#73-script-wiring-scriptwiringinfo) 非 Hapbeat MonoBehaviour の `[SerializeField] string` を walk し、値が `displayName` または `eventId` と完全一致するものを surfacing。 | フィールド | 内容 | | --------------- | ------------------------------ | | `script` | MonoBehaviour instance | | `componentName` | e.g. `ChargeShooter` | | `fieldName` | e.g. `_eventName` | | `matchedValue` | 文字列値 | | `matchType` | `"displayName"` or `"eventId"` | heuristic なので false positive あり得る。 *** ## 8. Parameter Bindings セクション (StreamClip mode のみ) [Section titled “8. Parameter Bindings セクション (StreamClip mode のみ)”](#8-parameter-bindings-セクション-streamclip-mode-のみ) エントリ右ペインの最下部、Notes の直前に表示。 ### 8.1 グルーピング [Section titled “8.1 グルーピング”](#81-グルーピング) `HapbeatBindingPreset.ownerObjectName` で 3 種類に分類: 1. **wired GO 単位の foldout** — `ownerObjectName == wiredGO.name`。その GO の inline wiring と並べて表示 2. **Shared (all wired)** — `ownerObjectName == ""`。全 wired GO に attach される 3. **Orphan groups** — `ownerObjectName` が set されているが該当 GO が wiring 一覧に存在しない(rename / delete された GO の残骸) ### 8.2 各 preset 行で編集可能なフィールド [Section titled “8.2 各 preset 行で編集可能なフィールド”](#82-各-preset-行で編集可能なフィールド) §4 の全フィールド + compact 表示用の expand/collapse。`_id` は HideInInspector だが内部で auto-assign される。 ### 8.3 Sync Scene ボタン [Section titled “8.3 Sync Scene ボタン”](#83-sync-scene-ボタン) `SyncLinkedBindingsForEntry(entryIdx)` を呼び、各 preset を対応する scene 上の trigger 子孫に `HapbeatParameterBinding` component として attach + link する(既存 link は維持)。`(owner, sourceTransformPath, sourceProperty)` のタプルが変わったら **自動で deferred sync** も走る。 ### 8.4 1:1 双方向同期 [Section titled “8.4 1:1 双方向同期”](#84-11-双方向同期) * **preset 削除** → window が link 中の全 scene `HapbeatParameterBinding` を `Undo.DestroyObjectImmediate`(標準は EventMap = single source of truth) * **scene 上の binding component 削除** → `HapbeatParameterBinding.OnDestroy` + `EditorApplication.delayCall` で `CleanupOrphanPreset` を実行し、他に link 中の component がいなければ preset も map から除去 * どちらの方向も `Undo.RecordObject` 経由なので Ctrl+Z で一括 revert 可 *** ## 9. Table view [Section titled “9. Table view”](#9-table-view) スプレッドシート形式の bulk 編集 view。 ### 9.1 カラム [Section titled “9.1 カラム”](#91-カラム) | カラム | 内容 | 幅 | | ----------------- | ------------------------------------------------------------------- | ----- | | `☰` | drag handle | 20px | | `#` | list index | 28px | | `Mode` | popup (`FIRE` / `CLIP` / `LIVE`\*) | 70px | | `Name` | `displayName` TextField | flex | | `Event ID / Clip` | Command: `category.eventName` 結合表示 / StreamClip: `AudioClip` picker | 180px | | `Gain` | FloatField | 48px | | `Target` | read-only summary | 110px | | `×` | delete | 20px | \*Table view の Mode popup には `LIVE` が残置されているが、enum 上の `StreamClip` 1 値しかないため LIVE 選択は no-op になる。 ### 9.2 選択 / 一括編集 [Section titled “9.2 選択 / 一括編集”](#92-選択--一括編集) | 操作 | 動作 | | --------------- | ---------------------------- | | クリック | 単一選択 | | Ctrl/Cmd + クリック | toggle multi-select | | Shift + クリック | 範囲 multi-select | | マルチセル状態でセル編集 | 同一カラムを全選択行に伝播(spreadsheet 風) | ### 9.3 行 context menu (multi-select 時) [Section titled “9.3 行 context menu (multi-select 時)”](#93-行-context-menu-multi-select-時) * Set Mode/FIRE (Command) * Set Mode/CLIP (Stream Clip) * Set Gain…/0.5 / 1.0 / 2.0 * Duplicate All * Delete All ### 9.4 Empty state [Section titled “9.4 Empty state”](#94-empty-state) エントリ 0 件のとき `(empty — click + to add)` を表示。 *** ## 10. Manifest intensity の解決 [Section titled “10. Manifest intensity の解決”](#10-manifest-intensity-の解決) `HapbeatEventEntry._cachedManifestIntensity` は Studio で deploy された Kit manifest の `parameters.intensity` 値をキャッシュする。Editor 時にのみ解決し、runtime は cache を読むのみ(device は manifest を見ず、SDK 側で `gain × intensity` を wire の `gain` として送る)。 ### 10.1 解決順序 [Section titled “10.1 解決順序”](#101-解決順序) 1. `manifestOverride` (TextAsset) が set されていればそれを使う 2. StreamClip mode かつ `streamClip` あり → clip asset path から **walk up** して `*-manifest.json` を探す 3. Command mode → `HapbeatSDK/Kits//-manifest.json` を試行 4. project 内全 `*-manifest.json` を scan して、`events` 内の `clip` パス or `event_id` が一致するものを探す ### 10.2 自動 refresh トリガー [Section titled “10.2 自動 refresh トリガー”](#102-自動-refresh-トリガー) | イベント | 動作 | | ------------------------------- | ------------------------------------------------------------ | | streamClip 変更 | 該当 entry の `manifestOverride` を auto-attach + cache 更新 | | category / eventName 変更 | cache 更新 | | `↻` toolbar ボタン | `HapbeatManifestIntensity.Invalidate()` + 全 entry の cache 更新 | | EventMap window 起動 / entries 変更 | `RefreshIntensityCache()` | 未解決のとき `_cachedManifestIntensity = -1f`。`GetEffectiveGain()` は -1 のとき `gain` のみ返す(intensity 乗算なし)。Test Play bar に “manifest intensity not found” warning を表示。 *** ## 11. Play-mode snapshot / revert [Section titled “11. Play-mode snapshot / revert”](#11-play-mode-snapshot--revert) `HapbeatEventMapPlaySnapshot` static class (`[InitializeOnLoad]`) が `EditorApplication.playModeStateChanged` を購読: | 状態遷移 | 動作 | | ----------------- | ---------------------------------------------------------- | | `ExitingEditMode` | `revertPlayModeChanges = true` の全 EventMap を JSON snapshot | | `EnteredEditMode` | snapshot を保持している EventMap を restore | snapshot は `Dictionary` に保持。Play 1 サイクル分のみ有効。Play 中に toggle を false にすれば restore はスキップされる(編集が保持される)。 *** ## 12. EventMap asset Inspector (`HapbeatEventMapEditor`) [Section titled “12. EventMap asset Inspector (HapbeatEventMapEditor)”](#12-eventmap-asset-inspector-hapbeateventmapeditor) `.asset` を Project window で選択したときの Inspector: | セクション | 内容 | | ---------------------------------- | ------------------------------------------------------------------------- | | `Revert Play-mode changes on exit` | `revertPlayModeChanges` toggle | | 手動 Snapshot / Restore ボタン | toggle off でも明示 snapshot を取れる | | `Export as Unity Package` | EventMap + 参照 AudioClip を `.unitypackage` に bundle(portability) | | entries collapsed by default | パフォーマンス対策。`Hapbeat.EventMap.ShowEntriesInInspector` EditorPrefs で persist | `labelWidth` は narrow inspector でも長いラベルが切れないよう `max(170, currentViewWidth × 0.55)` で override。 *** ## 13. Markdown export (`HapbeatEventMapMarkdownExport`) [Section titled “13. Markdown export (HapbeatEventMapMarkdownExport)”](#13-markdown-export-hapbeateventmapmarkdownexport) | メニュー | 動作 | | --------------------------------------------- | ----------------------------------------------------------- | | `Hapbeat → Export Event Map (Selected)` | Project ビューで選択中の 1 EventMap を `.md` として asset 隣に出力 | | `Hapbeat → Export Event Map (All in Project)` | `t:HapbeatEventMap` で project 全 asset を一括 export | 出力フォーマット: 各 entry に `## ` 見出し + メタ情報。AI への context 提供 / design doc 貼付け用途。 *** ## 14. Drag-to-reorder の挙動 [Section titled “14. Drag-to-reorder の挙動”](#14-drag-to-reorder-の挙動) * ハンドル (`☰` カラム) からのみ drag 開始(編集セルを誤って動かさないため) * 10px の threshold 超で confirmed drag に promote * blue ライン (`Color(0.3, 0.7, 1, 1)`, 2px) で drop position を可視化 * Esc キーで drag キャンセル * 同位置 drop (`toSlot == from` or `from + 1`) は no-op * Trigger は GUID 参照なので reorder で wiring は壊れない #### 既知の制約 [Section titled “既知の制約”](#既知の制約) prefab asset 内にのみ存在し、scene にインスタンス化されていない trigger は scan 対象外。「rename / restructure 後に prefab を開いて再 wire」が必要なケースで warning を log。 *** ## 15. Stable GUID と wiring の不変条件 [Section titled “15. Stable GUID と wiring の不変条件”](#15-stable-guid-と-wiring-の不変条件) * Entry は `_id : string` を `[SerializeField, HideInInspector]` で保持し、最初の `.id` getter 呼び出しで `Guid.NewGuid().ToString("N")` を生成 * Trigger は `_entryId : string` で参照(旧 `_entryIndex` は v2.0 で削除済み、id 一本化) * `HapbeatBindingPreset._id` も同様の GUID。`HapbeatParameterBinding._linkedBindingId` と照合 * Duplicate / clipboard paste 時は `RegenerateId()` で新 GUID を割当(runtime の binding が複製元を指したまま残らないように) * 既存 scene の YAML に残った旧 `_entryIndex` 値は load 時に未知 field として無視 *** ## 16. Editor 関連メニュー一覧(EventMap 関連だけ抜粋) [Section titled “16. Editor 関連メニュー一覧(EventMap 関連だけ抜粋)”](#16-editor-関連メニュー一覧eventmap-関連だけ抜粋) | メニューパス | priority | 効果 | | ------------------------------------------- | ----------------- | --------------------------------------------- | | `Hapbeat/Open Event Map` | 10 | window を開く | | `Hapbeat/Initial Scene Setup` | 50 | folder + Router + EventMap asset + window を一括 | | `Hapbeat/Create Event Router` | 30 | scene に `[Hapbeat Event Router]` GO のみ | | `Hapbeat/Create Event Map` | 31 | asset のみ | | `Hapbeat/Export Event Map (Selected)` | 70 | Markdown 1 件 | | `Hapbeat/Export Event Map (All in Project)` | 71 | Markdown 全件 | | `GameObject/Hapbeat/Event Router` | 10 | hierarchy 右クリックメニューにも同コマンドを露出 | | `Assets/Create/Hapbeat/Event Map` | (CreateAssetMenu) | 右クリックフォルダ直下に asset 生成 | *** ## 17. 関連ページ [Section titled “17. 関連ページ”](#17-関連ページ) * [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) — 利用フロー * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — entry を参照する側 * [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) — `bindings[]` の使い方 * [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/) — メニュー全体の俯瞰 * [Fire と Clip — 使い分けと実装](/docs/sdk-integration/unity-sdk/fire-vs-clip/) — `mode` の選び方 # Fire と Clip — 使い分けと実装 > Unity SDK 視点で Fire (command) と Clip (stream_clip) の判断基準・開発フローへの影響・実装パターンを整理。 EventMap entry の `Mode` フィールドの値(Fire / Clip)は、Unity への組み込み方針・触覚素材の置き場・反復作業のフローに大きく影響します。本ページは Unity 開発者の視点で「どちらを選ぶか」と「どう書くか」を整理します。 protocol / schema レベル(manifest bucket・wire 形式・gain 適用タイミング等)の詳細は **[Fire と Clip の違い](/docs/concepts/fire-vs-clip/)** を参照してください。 ## TL;DR [Section titled “TL;DR”](#tldr) | | **Fire** (FIRE / command) | **Clip** (CLIP / stream\_clip) | | --------- | ------------------------- | ------------------------------- | | 一言で | デバイス内蔵 clip を ID で再生 | Unity の AudioClip を PCM 送信 | | 推奨用途 | 本番運用、短い one-shot | 試作、長尺、動的変調 | | 事前 deploy | 必要 (Studio から Kit を書込) | 不要 | | 遅延 | 小・安定 | 大きめ・環境依存 | | 動的変調 | `gain` は発火ごとに固定 | 再生中に gain / pan を per-chunk 変調可 | *** ## 1. どちらを選ぶか [Section titled “1. どちらを選ぶか”](#1-どちらを選ぶか) ### 1-1. Unity 開発フローへの影響 [Section titled “1-1. Unity 開発フローへの影響”](#1-1-unity-開発フローへの影響) | | Fire を選んだ場合 | Clip を選んだ場合 | | ------------- | ------------------------------------------------- | -------------------------------------------------------- | | 触覚素材の置き場 | Hapbeat Studio の Kit (`install-clips/`) | Unity の `AudioClip` | | 反復作業のフロー | WAV 編集 → Studio → device deploy → Unity Play | AudioClip を Inspector で差し替え → 即 Play | | Trigger 側コード | mode 非依存 (Trigger / StateBehaviour は mode を意識しない) | 同左 | | 動的 modulation | 不可 (固定 gain のみ) | 可 (`Trigger.GainMultiplier` / `HapbeatParameterBinding`) | **Trigger / StateBehaviour 側のコードは mode を意識しないため、後から切り替えが可能です**。EventMap entry の `Mode` フィールドを変えるだけで wire 形式が変わります。これは Unity SDK が EventMap を経由した抽象を提供しているためで、Fire / Clip 比較における Unity 側の最大の特徴です。 ### 1-2. 具体例: Showcase での選択 [Section titled “1-2. 具体例: Showcase での選択”](#1-2-具体例-showcase-での選択) | Showcase Zone | 選んだ Mode | 理由 | | ------------------ | ----------- | ------------------------------------------------------ | | Z1 Pin hit | Clip | 開発中に色々な打撃感を試したいため。本番化なら Fire への移行が自然 | | Z2 Door open/close | Clip | Animator state 連動で短い one-shot。試作段階のため Clip 維持 | | Z3 grab loop | Clip (loop) | 長尺ループ + 物体速度に応じた動的 modulation が必要 | | Z5 charge release | Clip | チャージ量を `AnimationCurve` で gain modulation するため Clip 必須 | ### 1-3. ガイドライン [Section titled “1-3. ガイドライン”](#1-3-ガイドライン) * **試作段階** → Clip(差し替えの速さ) * **触覚が確定 + 出荷予定** → Fire に切り替え(低遅延・安定) * **長尺 / ループ / 動的 modulation 必須** → Clip 維持 * **Wi-Fi 混雑 or 多人数同時** → Fire(帯域消費が小さい) * **同じ entry を 2 モード両対応したい** → manifest の BOTH 表現([Fire と Clip の違い](/docs/concepts/fire-vs-clip/) で詳述) *** ## 2. EventMap での mode 切り替え [Section titled “2. EventMap での mode 切り替え”](#2-eventmap-での-mode-切り替え) EventMap entry の `Mode` フィールドで `FIRE (Command)` / `CLIP (Stream Clip)` を選びます。コンポーネント / Behaviour 側は mode を意識せず、内部で適切な API (`HapbeatManager.Play` / `StreamAudioClip`) に分岐します。 | Mode 設定時 | 必須フィールド | wire 形式 | | -------------------- | ------------------------- | ---------------------------------------------- | | `FIRE (Command)` | Category + Event Name | PLAY / STOP packet (Event ID + パラメータ) | | `CLIP (Stream Clip)` | Stream Clip (`AudioClip`) | STREAM\_BEGIN / STREAM\_DATA × N / STREAM\_END | 詳細: [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/)。 *** ## 3. 実装例 [Section titled “3. 実装例”](#3-実装例) [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) と同じ分類で示します。A / B は mode 非依存で使えるため Unity 側コードは共通、C のみ mode 別 API を使います。 ### A. Inspector で wire (mode 非依存) [Section titled “A. Inspector で wire (mode 非依存)”](#a-inspector-で-wire-mode-非依存) GameObject に Hapbeat 系コンポーネント(Collision / Sequence / UnityEvent / TickEmitter)を attach、または `HapbeatStateBehaviour` を AnimatorController state に attach し、Inspector で対象 EventMap entry を選びます。コードは不要。 Fire / Clip の違いは EventMap entry 側の `Mode` と関連フィールドで吸収されるため、コンポーネント / Behaviour の Inspector 操作は同じです: ```plaintext GameObject の Inspector └─ HapbeatUnityEventTrigger Event Map : MyEventMap Event : [▶ sword_hit] ← entry.mode = FIRE : [♪ ambient_drone] ← entry.mode = CLIP ``` EventMap entry 側の `Mode` を切り替えるだけで、コンポーネント / Behaviour を一切触らずに wire 形式が変わります。 ### B. スクリプトから Trigger を呼ぶ (mode 非依存) [Section titled “B. スクリプトから Trigger を呼ぶ (mode 非依存)”](#b-スクリプトから-trigger-を呼ぶ-mode-非依存) `[SerializeField]` で Trigger 参照を持ち、ゲームロジックから `Fire()` を呼びます。entry.mode が FIRE / CLIP どちらでも同じ呼び方です。 ```csharp public class GunController : MonoBehaviour { [SerializeField] private HapbeatUnityEventTrigger _shootTrigger; void OnShoot() { _shootTrigger.Fire(); } } ``` Clip のとき、`GainMultiplier` を毎フレーム書けば再生中の動的変調も可能です(Fire のときは setter 自体は動作しますが、再生中の波形には反映されず、次の `Fire()` から効きます): ```csharp void Update() { if (_isCharging) _shootTrigger.GainMultiplier = _gainCurve.Evaluate(_chargeT); } ``` 実装例: Showcase **Z5 ChargeShooter** (`Samples~/Showcase/Scripts/ChargeShooter.cs`)。 ### C. Manager.Play() / StreamAudioClip() を直接呼ぶ (mode 別 API、特殊ケース) [Section titled “C. Manager.Play() / StreamAudioClip() を直接呼ぶ (mode 別 API、特殊ケース)”](#c-managerplay--streamaudioclip-を直接呼ぶ-mode-別-api特殊ケース) EventMap を介さない場合は mode 別に API を選びます。 **Fire**: ```csharp HapbeatManager.Instance?.Play( eventId: "my-game.sword_hit", gain: 0.8f, target: "player_1/pos_r_arm" ); ``` **Clip**: ```csharp [SerializeField] private AudioClip _footstepClip; void OnFootstep() { var playback = HapbeatManager.Instance?.StreamAudioClip( clip: _footstepClip, gain: 0.7f ); // 必要なら playback.SetGain(0.5f) / playback.Stop() } ``` EventMap 経由の自動補正(manifest intensity / latency offset / wiring 一覧 / Inspector チューニング)は失われます。この経路を選ぶ条件は [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) を参照。 *** ## 4. Kit を device へ deploy する [Section titled “4. Kit を device へ deploy する”](#4-kit-を-device-へ-deploy-する) Fire モードを使う前に、Kit(WAV + manifest.json)を device に書き込む必要があります。手順は Studio 側のドキュメントを参照: * [Hapbeat を初期設定する](/docs/tools/studio/initial-setup/) * [Kit を作って配布する](/docs/tools/studio/kit-design/) *** ## 5. Helper の役割 (補足) [Section titled “5. Helper の役割 (補足)”](#5-helper-の役割-補足) Fire / Clip ともに **runtime の SDK は直接 device に UDP を送る** ため、Helper は不要です。Helper が要るのは: * Studio から Kit を deploy するとき(mDNS discovery + WS 中継) * Studio 上の再生テスト・waveform preview *** ## 関連リンク [Section titled “関連リンク”](#関連リンク) * [Fire と Clip の違い](/docs/concepts/fire-vs-clip/) — protocol / schema レベルの詳細 * [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) — 紐づけ 3 パターンの全体像 * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — コンポーネント・Behaviour の一覧 * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — `Mode` 切り替え画面 * [Streaming buffer を調整する](/docs/sdk-integration/unity-sdk/streaming/) — Clip モードのバッファ調整 # Getting Started > 新規 Unity プロジェクトに SDK をインストールして BasicExample で Hapbeat を鳴らすまでの最短手順。 このガイドでは、**新規 Unity プロジェクト** に SDK をインストールし、Basic Example サンプルを通して Hapbeat デバイスから振動が出るまでを最短で体験します。 既存プロジェクトへの組み込み方については [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) を参照してください。 ## 前提 [Section titled “前提”](#前提) * **Hapbeat** が Unity を実行する PC と同じ Wi-Fi LAN に接続されていること ## 0. Unity Editor をインストール [Section titled “0. Unity Editor をインストール”](#0-unity-editor-をインストール) > 対応バージョンの Editor が既にインストール済みであれば、このステップはスキップできます。 **対応バージョン**: Unity 6000.0 以上(Unity 6 系)(動作確認済み: **Unity 6.3 LTS 6000.3.15f1**) [Unity Hub](https://unity.com/download) から対応バージョンをインストールします。 ### 新規プロジェクトの作成 [Section titled “新規プロジェクトの作成”](#新規プロジェクトの作成) Unity Hub → **New project** → テンプレートは **任意**(例: `3D (Core)`)。 SDK は描画パイプライン非依存なので、URP / HDRP / Built-in どれでも動作します。 ### git のインストール [Section titled “git のインストール”](#git-のインストール) UPM が Git URL でパッケージを取得するために **git** が必要です。 [git-scm.com](https://git-scm.com/) からインストールし、PATH が通っていることを確認してください(`git --version` がターミナルで通れば OK)。 ## 1. SDK をインストールしてサンプルをインポート [Section titled “1. SDK をインストールしてサンプルをインポート”](#1-sdk-をインストールしてサンプルをインポート) 1. Unity Editor: `Window → Package Manager` 2. 左上の **`+`** → **`Install package from git URL...`** 3. 次の URL を貼り付けて **Install**: ```plaintext https://github.com/Hapbeat/hapbeat-unity-sdk.git ``` 4. インポートが完了したら、Package Manager で **Hapbeat SDK** が選択された状態のまま右パネル → **Samples** タブで以下を **Import**: | サンプル | 推奨度 | 内容 | | ----------------- | ------------ | ---------------------------------------------------------------------------------------------- | | **Basic Example** | **必須** | このチュートリアルで使う最小サンプル (キー操作で発火) | | **Showcase** | **強く推奨**(任意) | SDK の触覚配線パターンを 1 シーン × 5 ゾーンで一覧できる実装カタログ。組み込み時の参考に必ず役立つので、Basic Example と一緒に Import しておくのがおすすめ | サンプル一式(Scene / EventMap / Kit)が `Assets/HapbeatSDK/SDK_Samples/` 配下に展開され、**そのまま Play できる状態** で配置されます。インポート完了後、**`Hapbeat`** メニューがメニューバーに現れます。 バージョン固定・更新・トラブルシューティングの詳細は [インストール要件](/docs/sdk-integration/unity-sdk/installation/) を参照。 ## 2. Play して振動を確認(Stream) [Section titled “2. Play して振動を確認(Stream)”](#2-play-して振動を確認stream) `Assets/HapbeatSDK/SDK_Samples/BasicExample/Scenes/BasicExample.unity` を開いて **Play** します(シーン・EventMap・Kit はサンプル同梱なので、追加の生成作業は不要です)。 画面にキー操作ガイドが表示されます: | キー | 動作 | | ----- | ------------------------------------------- | | Space | CLIP (Stream) 1-shot — 100 Hz 正弦波 1 秒 | | R | CLIP (Stream) loop — 100 Hz 正弦波 ループ | | **F** | FIRE (Command) — 200 Hz 正弦波(**Kit が必要**、後述) | | S | Stop all | | C | Ping | **Space** を押してデバイスが振動すれば、SDK ↔ デバイスの通信は確立しています。 > UI に `Pong: RTT=...ms` が表示されていれば通信 OK。表示されない場合はデバイスのオンライン状態を確認してください。 Stream モード(Space / R)は PCM データをリアルタイムでデバイスに送るため、デバイス側に Kit は不要です。 **F キーを押しても反応なし** — これは正常です。Command モードはデバイスに Kit がインストールされていないと動作しません。次のステップで解決します。 ## 3. EventMap を確認する [Section titled “3. EventMap を確認する”](#3-eventmap-を確認する) メニューバー → **`Hapbeat → Open Event Map`** を開きます。 EventMap は SDK が発火する触覚イベントの一覧と設定を管理するウィンドウです。BasicExample には 3 エントリが登録されています: | Event ID | Mode | 対応キー | | ------------------------------------ | -------------- | ----- | | basic-exam-kit.sine\_100hz\_1s | StreamClip | Space | | basic-exam-kit.sine\_100hz\_1s\_loop | StreamClip | R | | basic-exam-kit.sine\_200hz\_1s | Fire (Command) | F | 各エントリ右端の **▶ ボタン(Test Play)** を押すと、Unity の Play モードに入らなくてもエディタ上から直接デバイスに発火できます。 EventMap の詳細: [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) ## 4. Studio で Kit をデプロイして FIRE を有効化 [Section titled “4. Studio で Kit をデプロイして FIRE を有効化”](#4-studio-で-kit-をデプロイして-fire-を有効化) Command モード(F キー)を動かすには、デバイスに `basic-exam-kit` をインストールします。Studio からのデプロイには **hapbeat-helper** が必要です([Hapbeat を初期設定する](/docs/tools/studio/initial-setup/) 参照)。 1. **Hapbeat Studio** を開く(`https://studio.hapbeat.com/`) 2. **Kit タブ(右側)** → フォルダ選択(「フォルダを開く」)で Unity の `Assets/HapbeatSDK/SDK_Samples/BasicExample/Kit/` を指定 3. `basic-exam-kit` が一覧に表示されたら選択 4. デバイスが選択されていることを確認(ページ内右上)→ **Deploy** を実行 デプロイ完了後、Unity の Play モードに戻って **F キー**を押すとデバイスが振動します(200 Hz 正弦波)。 ## 音と触覚のタイミングを合わせる [Section titled “音と触覚のタイミングを合わせる”](#音と触覚のタイミングを合わせる) 音声出力の遅延は、PC・スピーカー・ヘッドホンの組み合わせごとに異なります。Hapbeat は UDP で直接届くため、環境によっては**触覚が音より先に感じられる**ことがあります。これは想定内です。 1. `Hapbeat → Open Settings` を開く 2. **Latency Compensation** の **Haptic Delay (ms)** を `0` から少しずつ上げる 3. 音と触覚が揃う値で止める これはすべての Trigger / EventMap 経由の発火に加わる共通の遅延です。個別のイベントだけを微調整したいときは、EventMap entry の **Delay Offset** を使います。次の発火から新しい値が使われ、調整前に待機していた発火は取り消されます。 ## 次のステップ [Section titled “次のステップ”](#次のステップ) * [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) — 自分のシーンへの追加手順と Showcase サンプル紹介 * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — Collision / Sequence / UnityEvent / TickEmitter / StateBehaviour * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — Event ID と波形の対応を GUI 管理 * [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) — ゲーム状態を gain / pan に動的マッピング # インストール要件 > Hapbeat Unity SDK を Unity プロジェクトに導入する手順 (UPM 経由)。 Hapbeat Unity SDK は Unity Package Manager (UPM) 経由で **Git URL から直接インストール** できます。`.unitypackage` のダウンロードや手動コピーは不要です。 ## 動作環境 [Section titled “動作環境”](#動作環境) * **Unity 6 (6000.0) 以上**(動作確認済み: Unity 6000.3.12f1) * **Git** が PC にインストール済み・PATH 通り済み (Unity が裏で `git clone` するため必須) * デバイスと同一ネットワーク(同一サブネット)に接続できる環境 * Active Input Handling は **“Both”** / “Old” / “Input System Package” いずれでも動作します ## インストール [Section titled “インストール”](#インストール) ### 1. Package Manager から Git URL で追加 [Section titled “1. Package Manager から Git URL で追加”](#1-package-manager-から-git-url-で追加) 1. Unity Editor で `Window` → `Package Manager` 2. 左上の **`+`** → **`Install package from git URL...`** 3. 次の URL を貼り付けて **Add**: ```plaintext https://github.com/Hapbeat/hapbeat-unity-sdk.git ``` 特定バージョンを固定する場合は末尾にタグを付けます。指定できるタグは [Releases](https://github.com/Hapbeat/hapbeat-unity-sdk/releases) を参照してください: ```plaintext https://github.com/Hapbeat/hapbeat-unity-sdk.git#v0.3.0 ``` ### 2. 更新 [Section titled “2. 更新”](#2-更新) * Package Manager → Hapbeat SDK を選択 → 右ペインに **Update** が出ていればクリック * Tag 固定 URL の場合は `Packages/manifest.json` の `#vX.Y.Z` を書き換えて保存 → Unity が自動 reimport 新しい版が出ると、Editor 起動時に Console へ 1 行だけお知らせが出ます。表示は **Editor セッションごとに 1 回**で、スクリプト再コンパイル(domain reload)では重複しません。 * いま最新かどうかを確かめる: `Hapbeat` → `Diagnostics` → `Check for SDK Updates` * 自動確認を止める: `Hapbeat` → `Diagnostics` → `Check for SDK Updates on Startup` タグ固定の URL は Package Manager が更新を検出できないため、この自動確認が実質的な唯一の気付き手段になります。 ### 3. SDK フォルダを作成 (任意・初回のみ便利) [Section titled “3. SDK フォルダを作成 (任意・初回のみ便利)”](#3-sdk-フォルダを作成-任意初回のみ便利) `Hapbeat → Setup → Create HapbeatSDK Folder` を実行すると以下が生成されます: ```plaintext Assets/HapbeatSDK/ Kits/ ← 触覚波形と manifest.json (Studio 連携先) Scenes/ ← 生成シーン EventMaps/ ← EventMap.asset ``` サンプルの Build メニューを使う場合は自動で生成されるので、明示的に呼ぶ必要はありません。 ## サンプル [Section titled “サンプル”](#サンプル) Package Manager で Hapbeat SDK を選択 → 右パネル **Samples** タブから **Import**: | サンプル | 内容 | 動作要件 | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | | **Basic Example** | Trigger × 3 + Helper + Dispatcher + StatusOverlay の最小組合せ。Space/R/F/S/C キーで動作確認 | デバイス + Studio または Helper 起動 | | **Showcase** | 5 ゾーン構成の SDK 全機能ショーケース (Bowling / Door / Fishing / Stream Console / Target Range)。キーマウスで完結、XR 不要 | 同上 | | **XR Helpers** | XR Interaction Toolkit 連携フィルター (XRGrabFilter / XRSocketFilter) | XRI パッケージが入っているプロジェクトのみ | | **XRI Hand Demo (haptics add-on)** | XRI の Hands Interaction Demo に haptics を追加する EventMap + Kit。シーンは非同梱で、Editor コマンドで配線を適用 → [XRI Hand Demo に haptics を追加](/docs/sdk-integration/unity-sdk/xri-handdemo-quickstart/) | XRI + XR Helpers + ハンドトラッキング HMD | | **VR Config Example** | VR 実機での確認用の最小シーン。override の設定とテスト発火のみ。XRI 非依存 → [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/) | Quest 等の VR 実機 | Sample は `Assets/Samples/Hapbeat SDK///` に展開されます。Import 直後にシーン (`Scenes/*.unity`) を開いて Play すれば動作確認できます — 追加のビルド手順は不要です。 ## 動作確認 [Section titled “動作確認”](#動作確認) 1. **Hapbeat Studio** または **Hapbeat Helper** を起動し、デバイスがオンライン表示になることを確認 2. Unity で `Assets/HapbeatSDK/Scenes/BasicExample.unity` を開く 3. Play モード突入 4. **Space** キーで Stream 1-shot, **F** キーで Command (Fire) が再生され、デバイスから振動が出れば成功(**R** = Stream loop, **S** = Stop all, **C** = Ping) UI に `Pong: RTT=...ms` が表示されれば SDK ↔ デバイスの通信は確立しています。 ## ビルド時の注意 [Section titled “ビルド時の注意”](#ビルド時の注意) * **iOS / Android**: 標準で動作 (UDP socket 利用可) * **Quest (Android)**: マニフェストに `INTERNET` 権限が自動付与される * **WebGL**: UDP socket 不可。WebGL ビルドでは Hapbeat 通信は動作しません ## トラブルシューティング [Section titled “トラブルシューティング”](#トラブルシューティング) | 症状 | 対処 | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Package Manager` で URL を貼っても進まない | Git が PATH に通っているか確認 (`git --version` がコマンドラインで通る必要あり) | | サンプルのシーンが見つからない | Package Manager → Hapbeat SDK → **Samples** タブから該当サンプルを Import 済みか確認。古い Sample を再 Import すると最新のシーン・Editor スクリプトが反映される | | Play しても触覚が来ない | Studio/Helper が起動・デバイスがオンラインか / EventMap のターゲット、または Override Addressing の設定がデバイス側のアドレスと一致するか([ターゲティング](/docs/sdk-integration/unity-sdk/targeting/)) | | Space / R / F キーに反応しない | `Edit → Project Settings → Player → Active Input Handling` が `Input Manager (Old)` のみになっていないか確認。`Both` または `Input System Package` に変更(Unity 6 のデフォルトは `Both`) | | `'InputSystem' does not exist` 等のコンパイルエラー | 古い import が残っている可能性。`Assets/Samples/Hapbeat SDK/` 配下の該当 Sample を削除して再 Import | ## 次のステップ [Section titled “次のステップ”](#次のステップ) * [Getting Started](/docs/sdk-integration/unity-sdk/getting-started/) — BasicExample で最短で振動させる * [プロジェクトに組み込む](/docs/sdk-integration/unity-sdk/integration/) — 自分のシーンへの追加手順 * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — Collision / Sequence / UnityEvent / TickEmitter / StateBehaviour * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — Event ID と波形の対応を GUI 管理 * [Streaming buffer を調整する](/docs/sdk-integration/unity-sdk/streaming/) — StreamClip 用バッファの調整 * [AI 支援で組み込む](/docs/sdk-integration/unity-sdk/ai-assisted-workflow/) — Claude Code 等で既存シーンに触覚を後付けする実践フロー * [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/) — Hapbeat メニュー全項目の使い方逆引き * [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/) — どのデバイスを鳴らすかの決め方 (構成に応じた player / position / group の分け方) # プロジェクトに組み込む > 既存 Unity シーンに Hapbeat SDK を追加する流れの全体像。紐づけと調整の 2 段階で組み込む。 [Getting Started](/docs/sdk-integration/unity-sdk/getting-started/) の完了により、自分のプロジェクトに組み込む準備ができています。 ## Showcase サンプル [Section titled “Showcase サンプル”](#showcase-サンプル) 実際の触覚配線パターンの実例は **Showcase サンプル**で確認できます。 > Package Manager の Samples タブから **Showcase** を Import → `Showcase.unity` を開いて Play。詳細は [Showcase Sample](/docs/sdk-integration/unity-sdk/showcase/overview/) を参照。 *** 組み込みは大きく 2 段階です。本ページは全体の流れを示し、詳細は各ページへリンクします。 1. **紐づけ (Wiring)** — ゲーム内の出来事と、再生したい触覚フィードバックを結びつける 2. **触覚フィードバックの調整 (Tuning)** — 再生方式(Fire / Clip)・強度・対象デバイス・動的 modulation を整える *** ## 1. 紐づけ (Wiring) [Section titled “1. 紐づけ (Wiring)”](#1-紐づけ-wiring) ゲーム内のイベント(ボタンクリック・衝突・Animator state 遷移など)と、`EventMap` の各エントリ(再生したい触覚フィードバック)を結びつけるパートです。 ### 1-1. 初回セットアップ [Section titled “1-1. 初回セットアップ”](#1-1-初回セットアップ) メニューバー → **`Hapbeat → Initial Scene Setup`** を実行します。これだけで次が揃います: * `Assets/HapbeatSDK/` フォルダレイアウト (Kits / EventMaps / Scenes) * `[Hapbeat Event Router]` GameObject (内部に `HapbeatManager` singleton) * `Assets/HapbeatSDK/EventMaps/-EventMap.asset` * Event Map ウィンドウのオープン(新規 asset が選択された状態で) 再実行しても既存の Router / EventMap がそのまま再利用される(重複生成されない)ので、セットアップ済みシーンで再度実行しても安全です。「Router だけ追加」「EventMap だけ追加」など個別操作が必要な場合は [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/) を参照してください。 ### 1-2. EventMap でエントリを定義する [Section titled “1-2. EventMap でエントリを定義する”](#1-2-eventmap-でエントリを定義する) ![unity-eventmap](/_astro/unity-eventmap.BPQUKMfa_mQIkm.webp) `Hapbeat → Open Event Map` ウィンドウを開き、**+ Add Event** からエントリを追加します(ウィンドウを開くだけでは asset は作られません — 1-1 で作成済みの前提)。 主な設定項目(**頻繁に触る項目のみ抜粋**。loop / bindings / delayOffsetSeconds / notes / manifestOverride を含む全項目の詳細は [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) を参照): | フィールド | 内容 | | --------- | ----------------------------------------------------------------------------------- | | Name | エディタ表示用の人間可読ラベル | | Mode | `FIRE (Command)`(デバイス内蔵音声を ID で再生) `CLIP (Stream Clip)`(Unity の AudioClip を PCM 送信) | | Event ID | 送信するコマンド | | Clip | StreamClip モードで送信する `AudioClip` | | Gain | 振動強度(0.0〜2.0、Kit manifest の intensity と乗算される) | | Targeting | 送信先(空 = 全デバイス、`player_1` / `*/pos_neck` / `player_1/pos_chest` など) | ### 1-3. ゲーム内イベントとエントリを紐づける [Section titled “1-3. ゲーム内イベントとエントリを紐づける”](#1-3-ゲーム内イベントとエントリを紐づける) 定義したエントリを、ゲーム中のどの瞬間に呼び出すかを設定します。経路は 3 通り。プロジェクトの規模・チーム構成・既存コードの形に合わせて選んでください(併用も可能)。 #### A. コンポーネント / Behaviour 経由 (Inspector で wire) [Section titled “A. コンポーネント / Behaviour 経由 (Inspector で wire)”](#a-コンポーネント--behaviour-経由-inspector-で-wire) GameObject に Hapbeat 系の Trigger コンポーネント(または `HapbeatStateBehaviour`)を attach し、Inspector で EventMap entry を選択します。コードを書かずに wiring が完了します。Showcase サンプルの Z1〜Z5 に各パターンの実装例があります。 | コンポーネント / Behaviour | 用途 | 参考 Showcase Zone | | ---------------------------- | --------------------------------------------------------------------------- | -------------------------------------------- | | **HapbeatCollisionTrigger** | 物理衝突 / Trigger Enter / Exit。VelocityScaled で衝撃連動可 | **Z1 Bowling** (Pin × 6 を BatchSetup で一括) | | **HapbeatStateBehaviour** | Animator state Enter/Exit で発動。state に直接 attach する StateMachineBehaviour | **Z2 Door** (Open / Closed state に attach) | | **HapbeatSequenceTrigger** | Grab / Hold / Release を 1 component で管理 | **Z3 Fishing** (Sequence + ParameterBinding) | | **HapbeatTickEmitter** | 連続値(Slider 等)の変化量に応じてスナップ発動 | **Z4 Stream Console** (Slider に BatchSetup) | | **HapbeatUnityEventTrigger** | 任意の UnityEvent から `Fire()` を呼ぶ。Button / XR Interactable / Animation Event 等 | **Z5 Charge** (TargetReceiver.OnHit → Fire) | 各要素の詳細: [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) #### B. スクリプトから Trigger を呼ぶ (EventMap 経由) [Section titled “B. スクリプトから Trigger を呼ぶ (EventMap 経由)”](#b-スクリプトから-trigger-を呼ぶ-eventmap-経由) Script に Trigger 参照を持たせ、ゲームロジックの中で `trigger.Fire()` を呼びます。EventMap entry を介すので gain / target / latency / manifest intensity 補正がすべて自動で効きます。`GainMultiplier` を毎フレーム書けば動的 modulation も可能です。 ```csharp public class ChargeShooter : MonoBehaviour { [SerializeField] private HapbeatUnityEventTrigger _trigger; [SerializeField] private AnimationCurve _gainCurve; void Release(float chargeT) { _trigger.GainMultiplier = _gainCurve.Evaluate(chargeT); _trigger.Fire(); } } ``` 実装例: Showcase **Z5 ChargeShooter** (`Samples~/Showcase/Scripts/ChargeShooter.cs`)。 A との使い分けは「呼び出し条件が Inspector の宣言で完結するか / script ロジックで計算する必要があるか」の違いです。両方のパターンを併用できます。 #### C. EventMap を介さず Manager.Play() を直接呼ぶ (特殊ケース) [Section titled “C. EventMap を介さず Manager.Play() を直接呼ぶ (特殊ケース)”](#c-eventmap-を介さず-managerplay-を直接呼ぶ-特殊ケース) EventMap を経由せず `HapbeatManager` を直接叩く経路もあります。 ```csharp HapbeatManager.Instance?.Play("my-kit.enemy_hit", gain: 0.8f); ``` **この経路は EventMap 一元管理の利点(デザイナーが Inspector で値調整できる・wiring 一覧が見える・latency 補正が効くなど)を失います**。EventMap で扱える範囲を超える規模・動的性が求められる場合に限定して使うのが目安です。 たとえば「100 プレイヤーぶんの heartbeat を player ID 付きで個別管理したい」ケース。EventMap に静的列挙すると数百エントリが必要になり GUI 管理が現実的でない、加えて Event ID を runtime に動的構築する (`Play($"heartbeat.player_{id}")`) 必要がある、という条件が組み合わさったとき C が有効です。 *** ## 2. 触覚フィードバックの調整 (Tuning) [Section titled “2. 触覚フィードバックの調整 (Tuning)”](#2-触覚フィードバックの調整-tuning) 紐づけが済んだら、各エントリの再生方式・強度・対象デバイスを設計します。多くの項目は [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) で編集できます。本ページでは概要のみ、詳細は各ページへ。 ### 2-1. Fire (Command) と Clip (StreamClip) の選択 [Section titled “2-1. Fire (Command) と Clip (StreamClip) の選択”](#2-1-fire-command-と-clip-streamclip-の選択) EventMap entry の **Mode** で選びます: * **Fire (Command)** — デバイスに deploy 済みの Kit を ID で呼び出す。低遅延・短いコマンド送信のみ * **Clip (StreamClip)** — Unity の `AudioClip` を UDP で送信して再生。Kit deploy 不要・動的 modulation 可 開発初期は Clip でクイック試作 → 形が決まったら Fire に移すのがお勧めです。 選択の判断基準はこちら: [Fire と Clip — 使い分けと実装](/docs/sdk-integration/unity-sdk/fire-vs-clip/)。 ### 2-2. Gain と Target [Section titled “2-2. Gain と Target”](#2-2-gain-と-target) * **Gain** は Kit manifest の `intensity` (Hapbeat Studio で Kit 設計時に決めた基準振動強度) との **乗算**として効きます。1.0 で manifest 通り、0.5 で半分。Gain の階層構造の詳細は [Kit を作って配布する](/docs/tools/studio/kit-design/) を参照 * **Target** は送信先デバイスの指定。空 = 全デバイス、`player_1` で特定プレイヤー、`*/pos_neck` で全プレイヤーの首部位、`player_1/pos_chest` で特定プレイヤー+部位など。詳細は contracts の addressing spec を参照 ### 2-3. 動的 modulation (Parameter Binding / スクリプト) [Section titled “2-3. 動的 modulation (Parameter Binding / スクリプト)”](#2-3-動的-modulation-parameter-binding--スクリプト) StreamClip 中の gain / pan を毎フレーム書き換えて、ゲーム状態(移動量・速度・距離など)に追従させる仕組み。 * **Parameter Binding** — Inspector で declarative に Transform / Rigidbody / Slider 等を gain / pan にマッピング (Showcase Z3 / Z4) * **スクリプト** — `trigger.GainMultiplier = curve(t)` のように毎フレーム書く (Showcase Z5) 詳細・使い分け: [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) *** ## 次のステップ [Section titled “次のステップ”](#次のステップ) * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — 各トリガーの設定例 * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — Wiring の可視化・一括管理 * [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) — ゲーム状態を gain / pan に動的マッピング * [AI 支援で組み込む](/docs/sdk-integration/unity-sdk/ai-assisted-workflow/) — 既存シーンへの触覚後付け実践フロー * [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/) — Hapbeat メニュー全項目の使い方逆引き * [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/) — 構成に応じた player / position / group の分け方 # Parameter Binding > HapbeatParameterBinding でゲーム状態 (移動量・速度・距離) を StreamGain / StreamPan に動的にマッピングする方法。 `HapbeatParameterBinding` は、StreamClip 再生中の **gain** や **pan** をゲーム状態 (Transform の位置、Rigidbody の速度、Animator のパラメータなど) に応じて毎フレーム書き換えるコンポーネントです。 「掴んだ箱を動かしている間だけ手応えが強くなる」「歩く速度に応じて足元の振動が強まる」といった連続的な触覚表現を、コードを書かずに実現できます。 ## どこで動いているか [Section titled “どこで動いているか”](#どこで動いているか) ParameterBinding は `HapbeatTriggerBase` の **ActivePlayback** (StreamClip 再生中の `HapbeatStreamPlayback` ハンドル) に対して書き込みます。仕組みは: ```plaintext [Trigger.Fire] → Manager.StreamAudioClip(clip) → StreamPlayback handle 取得 → Trigger が ActivePlayback として保持 [ParameterBinding.Update] (毎フレーム) → source value (Transform.position 等) を読む → input range で 0..1 に正規化 → curve で形を整える → output range にマップ → ActivePlayback.Gain / ActivePlayback.Pan に書く ``` つまり ParameterBinding は **Stream 中のサンプルを動的に乗算する** ものです。Command mode (eventId 送信) には作用しません — Command は単発で完結するため modulate する余地がありません。 ## 設定項目 [Section titled “設定項目”](#設定項目) | フィールド | 役割 | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Source Transform Path | source として読む Transform。空 = この GameObject 自身 | | Source Property | `LocalPositionX` / `LocalPositionY` / `VelocityMagnitude` / `AngularVelocityMagnitude` / `PositionDeltaMagnitude` / `LocalRotationY` / `SliderValue` (UI Slider 連動) / `External` (スクリプト `binding.SetValue()` で外部入力) 等から選択 | | Input Min / Max | source 値をどの範囲で 0..1 に正規化するか | | Curve Type | `Linear` / `EaseIn` / `EaseOut` / `EaseInOut` / `Custom` | | Output Parameter | `StreamGain` (0..2) または `StreamPan` (-1..+1) | | Output Min / Max | 出力範囲 | | Debug Log | true で Console に input/output を周期出力 (動作確認に便利) | ## Showcase Z3 での実例 [Section titled “Showcase Z3 での実例”](#showcase-z3-での実例) [Showcase Sample](/docs/sdk-integration/unity-sdk/showcase/overview/) の Fishing Rod (Z3) は、釣り糸に attach した物体を振り回している間、loop の gain を **物体の移動速度** に追従させます。 設定 (`Z3_Fishing` root の `HapbeatParameterBinding`): | 項目 | 値 | | --------------------- | ---------------------------- | | Source Transform Path | `FishingObject` (root からの相対) | | Source Property | `PositionDeltaMagnitude` | | Input Min / Max | 0 / 0.5 | | Curve Type | `EaseInOut` | | Output Parameter | `StreamGain` | | Output Min / Max | 0.2 / 1.5 | 挙動: * `FishingObject` を **静止** させていると `PositionDeltaMagnitude = 0` → 正規化 0 → curve 0 → output 0.2 (静かな loop) * **激しく振り回す** と `PositionDeltaMagnitude > 0.5` → 正規化 1 → output 1.5 (強い loop) `FishingController` (script) はマウス入力に応じて `Sequence.Fire() / Stop()` を呼ぶだけ。binding は EventMap entry (`grab_loop`) に preset として登録されているので、BatchSetup や Apply Binding ボタン経由で自動的に `Z3_Fishing` に `HapbeatParameterBinding` が貼られます。 ## EventMap preset と standalone の違い [Section titled “EventMap preset と standalone の違い”](#eventmap-preset-と-standalone-の違い) ParameterBinding には 2 つの設定モードがあります: 1. **Linked preset** (EventMap 管理): EventMap entry の `bindings[]` に preset を登録 → BatchSetup で対象 GameObject に component が自動生成 + preset id で link。preset を編集すると runtime に反映される (live tuning 可能) 2. **Standalone**: GameObject に直接コンポーネント追加して全フィールドをローカル設定。preset との link なし | | Linked preset | Standalone | | ------------------ | ---------------------------------- | ------------------------------------ | | 設定箇所 | EventMap window の Binding section | scene の GameObject に AddComponent | | 保存先 | ScriptableObject (asset) | scene | | source 参照方法 | 文字列パス (Trigger からの相対) | 直接 component ref (Transform/Slider) | | Hierarchy 制約 | **あり** (Trigger の子孫のみ) | **なし** (scene 内自由) | | prefab portability | ◎ (prefab 内で完結) | △ (scene 直 ref は prefab override が要) | | 複数 Trigger 共有 | ✓ 同 entry を引く Trigger 全てに自動 attach | ✗ 1 binding = 1 Trigger | ### Linked preset の Hierarchy 制約について [Section titled “Linked preset の Hierarchy 制約について”](#linked-preset-の-hierarchy-制約について) EventMap preset は ScriptableObject (asset) なので Unity の原則で **scene GameObject への direct ref を保存できません**。代わりに **trigger GameObject からの相対パス文字列** で source を表します: ```plaintext Trigger GO ├─ ChildA ← path = "ChildA" で reachable │ └─ Grandchild ← path = "ChildA/Grandchild" └─ ChildB ← path = "ChildB" ``` Trigger の **子孫しか source に出来ない** ため、設計上の注意: #### 推奨パターン: 触覚関連を root に集約 [Section titled “推奨パターン: 触覚関連を root に集約”](#推奨パターン-触覚関連を-root-に集約) ```plaintext Zone_Root (← ★ Trigger をここに attach) ├─ Visual / Mesh ├─ Physics / Rigidbody └─ SourceObject (← binding source path "SourceObject" で reachable) ``` Zone の root に Trigger を置けば、配下の全 GO を binding source として preset で参照できる (Hierarchy 制約に当たらない)。これが **EventMap 管理を最大化するパターン**。 → Showcase Z3 (`Z3_Fishing` root に SequenceTrigger 配置 + 子の `FishingObject` を source) はこの形。 #### この制約が嫌なら: script 駆動 (Z5 Charge 参照) [Section titled “この制約が嫌なら: script 駆動 (Z5 Charge 参照)”](#この制約が嫌なら-script-駆動-z5-charge-参照) binding source を **Trigger の子孫にできない** ケース (例: UI canvas 上の Slider が Blaster Trigger の子孫でない、別 hierarchy の管理オブジェクトを参照したい、など) は、**EventMap preset は使わず script から直接 modulate** する方式に切替えます: ```csharp // Z5 ChargeShooter 抜粋 private void Update() { if (_charging) { float t = ...; _sequenceTrigger.GainMultiplier = _gainCurve.Evaluate(t); // ↑ setter が ApplyGainModulation を呼ぶ → playback.Gain modulate } } ``` * ParameterBinding を attach しない / 使わない * `Trigger.GainMultiplier = v` を毎フレーム書く (内部で `playback.ApplyGainModulation(v)` が走り、ParameterBinding と同じ entry point で gain が書き換わる) * ゲーム状態の取得が複雑な場合 (時間累積 / 閾値検知 / 複数 state 合成等) も script なら自由 → Showcase Z5 ChargeShooter (`Samples~/Showcase/Scripts/ChargeShooter.cs`) がこの実装の参考例。Blaster の Trigger と chargeBar Slider (UI canvas 配下) が別 hierarchy なため、preset 経由は不可 → script 駆動を採用。 ### どちらを選ぶか — 早見表 [Section titled “どちらを選ぶか — 早見表”](#どちらを選ぶか--早見表) | 状況 | 推奨 | | --------------------------------------------------- | --------------------------------------------------- | | Trigger と source が同じ prefab / 同じ root 配下に置ける | **Linked preset** (EventMap 管理) | | source が UI canvas / 別 hierarchy など Trigger 子孫に置けない | **script 駆動** (Z5 参照) or **Standalone PB + 直接 ref** | | 複数の Trigger で同じ binding を共有したい | **Linked preset** (auto-attach 機能) | | Game state を組み合わせて modulator を計算したい (閾値、複数値合成等) | **script 駆動** | Showcase では **Z3 が Linked preset の典型例、Z5 が script 駆動の典型例**。両方の参照先として読み比べると使い分けの感覚がつかみやすい。 ## スクリプトから動かしたい場合 (imperative pattern) [Section titled “スクリプトから動かしたい場合 (imperative pattern)”](#スクリプトから動かしたい場合-imperative-pattern) ParameterBinding を使わず、スクリプトで直接 `HapbeatTriggerBase.GainMultiplier` (または `ActivePlayback.Gain`) を書き換えることもできます。 Showcase の対応例: * **Z4 Stream Console**: ParameterBinding (Slider → StreamGain / StreamPan) — declarative。EventMap window で wiring 完結 * **Z5 Charge Shooter**: script から `_sequenceTrigger.GainMultiplier = curve(chargeT)` を毎フレーム書込み — imperative。custom 計算ロジック (`AnimationCurve` 評価) や mid-flow ロジック (閾値超え検知) と相性が良い 判断基準: * ゲーム状態の取得が単純 (Transform / Rigidbody / Slider) → ParameterBinding * 複数の game state を組み合わせる / curve や閾値ロジックを script で書きたい → スクリプト ### Gain の連続変化を滑らかにする仕組み [Section titled “Gain の連続変化を滑らかにする仕組み”](#gain-の連続変化を滑らかにする仕組み) どちらの方式でも、SDK 側の mixer thread が **per-sample で gain を線形補間** するので、急な slider 操作でも 16ms (= 1 chunk) 単位の階段状にならず連続変化として device に届きます。これがないと chunk 境界で gain が急にジャンプし、ADPCM 予測器が乱れて warble / 暴れに繋がります。 つまり: ParameterBinding でも script からの GainMultiplier 書込みでも、**device 側の体感はほぼ同じ滑らかさ**。選択基準は Inspector wiring 派か script 派か、で決めて良い。 ## 参考 [Section titled “参考”](#参考) * [Wiring Reference](/docs/sdk-integration/unity-sdk/showcase/wiring/#z3-fishing-rod) — Z3 の wire 詳細 * [BatchSetup vs スクリプト](/docs/sdk-integration/unity-sdk/showcase/method-choice/) — コンポーネント vs スクリプトの使い分け * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — `HapbeatSequenceTrigger` の詳細 # BatchSetup vs スクリプト > Hapbeat Unity SDK の触覚配線をどちらの方式で書くべきか — 判断基準と設計のコツ。 Hapbeat の触覚を Unity に組み込む方法は大きく 2 通りあります: 1. **BatchSetup / Inspector でコンポーネントを貼る** — UnityEvent や Animator state、Collision callback と直結 2. **スクリプトから `HapbeatBridge` / `HapbeatManager` を呼ぶ** — 自前のロジック内で能動的に発火 どちらも正解で、状況によって使い分けます。 ## 早見表 [Section titled “早見表”](#早見表) | 状況 | 推奨方式 | | ------------------------------------------------------ | ---------- | | 同じ Trigger を 3 個以上のオブジェクトに貼る | BatchSetup | | 発火条件が「衝突した・スライダ動いた・state 変わった」だけで決まる | コンポーネント | | gain は EventMap entry そのまま、または velocity scaling だけで足りる | コンポーネント | | 発火条件に複数の game state (charge レベル、アイテム所持、HP 等) が絡む | スクリプト | | gain / pan / target を runtime で計算したい | スクリプト | | 1 つの発火点から複数の Event を分岐させたい | スクリプト | | 同じイベントを多数の場所から発火する (例: 自作 Bridge にロジック集約) | スクリプト | > Showcase の各 Zone がどちらの方式を採用しているかは [Walkthrough](./walkthrough/#%E5%AE%9F%E8%A3%85%E6%96%B9%E5%BC%8F%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91) を参照。「やりたい入力 → 参照 Zone」のマトリクスも同じページに整理してあります。 ## 設計のコツ [Section titled “設計のコツ”](#設計のコツ) ### Bridge 派生クラスを作る [Section titled “Bridge 派生クラスを作る”](#bridge-派生クラスを作る) スクリプト方式を選んだ場合でも、`HapbeatManager.Instance.Play()` を直接呼ぶより、**プロジェクト固有の `HapbeatBridge` 派生クラス** を 1 つ作って、そこに発火ロジックを集約するのが推奨です。 理由: * gain / target / curve のチューニングが 1 箇所に集まる * ロジックが game logic から分離して保守しやすい * Showcase の `ShowcaseBridge` がこの形 ([Scripts/ShowcaseBridge.cs](https://github.com/Hapbeat/hapbeat-unity-sdk/blob/master/Samples~/Showcase/Scripts/ShowcaseBridge.cs)) ### 両方を組み合わせる [Section titled “両方を組み合わせる”](#両方を組み合わせる) Z3 Fishing は典型的な組み合わせ例です: * `HapbeatSequenceTrigger` (コンポーネント) で Fire→Loop→Stop の構造を宣言 * `FishingController` (スクリプト) でマウス入力に応じて `Sequence.Fire()` / `Stop()` を呼ぶ * `HapbeatParameterBinding` (コンポーネント) で loop 中の gain を物体の運動に連動 「コンポーネントで宣言的に書ける部分はコンポーネント、ロジックが必要な部分だけスクリプト」が読みやすいコードになります。 ## まとめ [Section titled “まとめ”](#まとめ) * まず [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/#batch-setup) で書けないか考える * 書けないなら自前 Bridge 派生 + スクリプト * 同じシーンで両方を混在させて OK (Showcase サンプルがそうしている) * 具体的な実装例は [Walkthrough](./walkthrough/) の「Zone 別の wiring 詳細」を参照 # Showcase Sample > SDK の触覚配線パターンを 1 シーン × 5 ゾーンで一覧できる Unity サンプル。動く実装例を真似や改造の起点として使うショーケース。 Showcase は「Hapbeat SDK でこういう書き方ができる」という **実装例の集合** です。手取り足取りの step-by-step ではなく、**動く構造体・実装意図・利用ケース** の 3 点を 1 シーンに詰め込んでいます。読むだけでなく **触る・真似する・改造する起点** として使ってください。 XR デバイス不要・キーマウスだけで完結します。 > **設計方針**: Hapbeat SDK の王道は「**Trigger 系コンポーネントを Inspector で wire**」する宣言的スタイル。Showcase も各 Zone で直接 Trigger を使う構成にしています。Script は **Trigger の `Fire() / Stop()` を呼ぶか、`GainMultiplier` を書くだけ** に留まり、触覚計算は SDK 側に任せます。 ## 構成 [Section titled “構成”](#構成) 5 ゾーンを 1 シーンに収め、**1〜5 キーでゾーン切替** (`ZoneSwitcher` が現在ゾーン以外を `SetActive(false)`)。各ゾーンは SDK の異なる haptic 登録パターンを 1 つだけ担当します。 | Zone | 教示パターン | 中身 | | --------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | **Z1 Bowling Lane** | Collision Trigger (velocity-scaled) | LMB で球を発射、Pin に衝突 | | **Z2 Swing Door** | Animator State Behaviour | F キーで Animator state Open / Close | | **Z3 Fishing Rod** | Sequence Trigger + Parameter Binding | LMB hold で物体 attach、振り回すと gain が変化 | | **Z4 Stream Console** | UnityEvent Trigger (loop) + Tick Emitter | Space で stream、Slider で gain / pan 動的変調。`AddressOverrideDemo` で override の実行時設定も確認可 → [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/) | | **Z5 Charge & Shoot** | UnityEvent Trigger + curve-driven GainMultiplier | LMB hold でチャージ、release で発射、命中で別 trigger | 各 Zone の wire 詳細 (どの GameObject にどの Trigger が貼られているか) は [Wiring Reference](./wiring/) を参照。 ## グローバル操作 [Section titled “グローバル操作”](#グローバル操作) | キー | 役割 | | -------------- | ------------------------------ | | `WASD` / Mouse | プレイヤー移動 / 視点 | | `Tab` | カーソル lock / unlock (UI 操作時に解放) | | `1`〜`5` | ゾーン切替 | | `Q` | `manual_fire` イベント発火 | | `P` | Ping (HUD に RTT 表示) | ## 始め方 [Section titled “始め方”](#始め方) 1. Unity Editor で **Window → Package Manager → Hapbeat SDK → Samples → Showcase → Import** 2. `Assets/Samples/Hapbeat SDK//Showcase/Scenes/Showcase.unity` を開く 3. Hapbeat デバイスを Studio または Helper でオンラインにして **Play** Import 直後にシーンを直接開けます。EventMap / kit manifest / audio clip 等はサンプルフォルダに同梱されているため、別途生成手順は不要です。 ## 次に読む [Section titled “次に読む”](#次に読む) * [Walkthrough](./walkthrough/) — 改造ヒント / Command モード切替 / WASD 衝突対処 / トラブルシューティング * [Wiring Reference](./wiring/) — 各 Zone の wire 実装詳細 (どの GameObject にどの Trigger / Binding が貼られているか) * [BatchSetup vs スクリプト](./method-choice/) — コンポーネント方式 / スクリプト方式の使い分け基準 # Walkthrough > Showcase シーンを Play した後にやってみることのガイド — 改造ヒント、Command モード切替、WASD 衝突対処、トラブルシューティング。 [Showcase Sample](./) を Play しながら **試したいこと・つまずきやすいこと** をまとめたページです。シーンの開き方や Zone 構成は [overview](./) を、各 Zone の wire 詳細は [Wiring Reference](./wiring/) を参照してください。 ## 改造して違いを体感する (Playground) [Section titled “改造して違いを体感する (Playground)”](#改造して違いを体感する-playground) Showcase は読むだけのカタログではなく、**Inspector を書き換えて Play して違いを聞く** ためのプレイグラウンドでもあります。各 Zone におすすめの「小さく変えて違いがすぐ分かる」改造ポイントを挙げます。 ### Z1 Bowling — velocity scaling のカーブを変える [Section titled “Z1 Bowling — velocity scaling のカーブを変える”](#z1-bowling--velocity-scaling-のカーブを変える) * `Pin_*` の `HapbeatCollisionTrigger` の `MaxVelocity` を 5 → 2 に下げる → **弱い衝突でも最大強度** で鳴る * `Gain Mode` を `VelocityScaled` → `Fixed` に切替 → **どの速度でも均一強度**。「ゲーム的衝撃」と「物理的衝撃」のニュアンスの違い * EventMap の `pin_hit` entry の `target` を `*/pos_neck` に変える → 衝撃を首側で受ける ### Z2 Door — 別 Animator state に紐付ける [Section titled “Z2 Door — 別 Animator state に紐付ける”](#z2-door--別-animator-state-に紐付ける) * `DoorAnimator.controller` の他 state に `HapbeatStateBehaviour` を追加 → 「state 遷移」発火のバリエーション増 * `door_open` / `door_close` の EventMap entry の `streamClip` を別 wav に差し替え → ドアの「音」と「触覚」がどれだけ独立に作れるか体感 * EventMap で `door_open` の `gain` を 0.3 に下げる → 静かな閉まり音の触覚版 ### Z3 Fishing — Parameter Binding を変える [Section titled “Z3 Fishing — Parameter Binding を変える”](#z3-fishing--parameter-binding-を変える) * `grab_loop` entry の Bindings 内 preset の **Source Property** を `PositionDeltaMagnitude` → `LocalPositionY` に変更 → **高さ基準で gain が変化** (高く上げると強く震える) * Range を 0.2〜1.5 → 0.5〜3.0 にすると変化が急になる。`Curve` を `Linear` ↔ `EaseInOut` 切替で「擦り始めの立ち上がり」が変わる * Loop entry の `streamClip` を `rain_loop.mp3` → 別のループ素材に変えるとループ感が変わる (短い素材を入れて切れ目に注意) ### Z4 Stream — Gain/Pan binding を加える [Section titled “Z4 Stream — Gain/Pan binding を加える”](#z4-stream--gainpan-binding-を加える) * 既存の Slider → `playback.Gain` の代わりに `HapbeatParameterBinding` を Slider に attach → script レス化 (declarative path) * `HapbeatTickEmitter` の `Tick Threshold` を 0.05 → 0.2 にすると **段階数が減って粗いフィードバック** * StreamClip の `loop` チェックを外して 1 発もの化 → ambient loop と single shot の違い * `target` を空(全デバイス) → `*/pos_r_arm` 固定にして「特定 position だけ受ける」挙動確認 ### Z5 Charge & Shoot — curve の形状を変える [Section titled “Z5 Charge & Shoot — curve の形状を変える”](#z5-charge--shoot--curve-の形状を変える) * `ChargeShooter._gainCurve` (Inspector で AnimationCurve 直接編集) を `EaseInOut` → 上に凸の曲線にすると **チャージ初期から強く立ち上がる** * 同じ curve を `Linear` にしてから 1/4 だけ離してみる → **離すタイミングの違い**が触覚 gain に直結 * `target_hit` の entry mode を `StreamClip` → `Command` に変えて Kit deploy 経由にすると、当たりの応答が低遅延化 ### EventMap 全体 [Section titled “EventMap 全体”](#eventmap-全体) * `Hapbeat → Open Event Map` ウィンドウで各 entry の `gain` 列を 0.5 → 1.5 にするだけで Z 全体の強さが変わる * `target` 列を `*/pos_neck` 一斉適用にして「首だけで全部受ける」モードを試す * `Mode` 列で `StreamClip` → `Command` に切替、Studio で対応 Kit を deploy すれば低遅延化を体感 > 元に戻したくなったら Package Manager から Showcase を再 Import すれば authored 版に戻ります。サンプルフォルダ (`Assets/Samples/Hapbeat SDK//Showcase/`) は再 Import で上書きされるので、永続化したい改造は別フォルダにコピーしてから編集してください。 ## Command モードで再現する (任意, Studio 連動) [Section titled “Command モードで再現する (任意, Studio 連動)”](#command-モードで再現する-任意-studio-連動) Showcase は **StreamClip だけで完結する** ように設計しています。次のステップとして「Hapbeat Studio で Kit を整備すると何が変わるか」を Command モードで体験できます (任意)。 ### Command モードのメリット [Section titled “Command モードのメリット”](#command-モードのメリット) * **低遅延**: event id (短い文字列) だけ送るので、PCM ストリームより到達が速い * **デバイス内蔵 clip 再生**: Unity 側に WAV を持たなくてよい (アプリの容量削減) * **Studio で clip を編集**: Kit を Studio で更新して deploy すれば、Unity 側のビルドし直しは不要 ### 手順 [Section titled “手順”](#手順) 1. **Studio で Kit を作る (または同梱の showcase-kit を流用)** * Sample Import 直後は `Assets/Samples/Hapbeat SDK//Showcase/Kit/showcase-kit-manifest.json` に Kit が同梱されている * Hapbeat Studio でこの Kit を開く (または独立コピーで作業) * `install-clips/` に Command 用 WAV (例: `pin_hit.wav`) を追加 2. **Kit をデバイスに deploy** (Studio の Save → Deploy) 3. **EventMap で mode を Command に切替** * `pin_hit` entry を開いて Mode を `Command` に * Category = `showcase-kit`, Event Name = `pin_hit` (Event ID = `showcase-kit.pin_hit`) * streamClip フィールドはそのまま (Command モードでは無視される) 4. **試したい entry を順次 Command 化** * 全部 Command にする必要はなく、低遅延を効かせたい衝撃系だけ Command、ambient/drag 系は StreamClip という混在運用が実用的 5. Play で動作確認 ### Command + StreamClip 混在の指針 [Section titled “Command + StreamClip 混在の指針”](#command--streamclip-混在の指針) * **Command 向き**: 衝撃 (pin\_hit, target\_hit, charge\_release, manual\_fire など)。低遅延が効く * **StreamClip 向き**: ループ系 (grab\_loop, stream\_demo)。ParameterBinding で動的 modulation できる 実機で両方触ってみると、Command の応答の速さと StreamClip の表現の自由度の違いが体感できます。 ## トラブルシューティング [Section titled “トラブルシューティング”](#トラブルシューティング) | 症状 | 原因 / 対処 | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Slider を触った後に WASD で値が動く | Unity 標準の UI ナビゲーションに WASD がバインドされているため。Showcase 側では対策済み。実プロジェクトでの根治手順は [Wiring Reference](/docs/sdk-integration/unity-sdk/showcase/wiring/#wasd-%E3%81%A8-ui-nav-%E3%81%AE%E8%A1%9D%E7%AA%81%E3%81%B8%E3%81%AE%E5%AF%BE%E5%87%A6) | | 何も鳴らない | Hapbeat デバイスがオフライン → Studio / Helper で接続確認 | | 接続済みなのに鳴らない | `[Hapbeat Event Router]` の `HapbeatManager` が無い、または各 Trigger の `EventMap` 未割当 | | `[Hapbeat] Entry not found` ログが出る | EventMap entry の displayName と Trigger 側 entry 選択がミスマッチ。Inspector の Entry ドロップダウンを確認 | | Sequence の Loop が無音 | `grab_loop` entry の `streamClip` 未割当、または `loop` 未チェック | | Tick がスパムする | Tick Threshold が低すぎる。0.05 程度に調整 | | Picker で切り替えても Z1 が変化しない | 仕様 (entry の固定 target が優先)。Z4・Z5・ホットキーで確認 | # Wiring Reference > Showcase の各 Zone がどう wire されているか — どの GameObject にどの Trigger が貼られ、どの script がどの API を呼んでいるかの実装リファレンス。 [Showcase Sample](./) の各 Zone の **wiring 実装リファレンス** です。Unity で `Showcase.unity` を開きながら、各 GameObject の Inspector と見比べる用途を想定しています。 ## やりたい入力 → 参照 Zone [Section titled “やりたい入力 → 参照 Zone”](#やりたい入力--参照-zone) 「どんな入力から触覚を発火させたいか」で参照すべき Zone は以下: | やりたいこと | 参照 Zone | 中で使う SDK 要素 | | ------------------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------ | | 物理 Collision に直結 | **Z1 Bowling** (Pin) | `HapbeatCollisionTrigger` + velocity gain | | Animator state に紐付けて自動発火 | **Z2 Door** | `HapbeatStateBehaviour` (AnimatorController state に attach) | | UI Toggle / Button から発火 (UnityEvent → Trigger) | **Z4 Stream Console** | UI Toggle.onValueChanged → `HapbeatUnityEventTrigger.Fire` | | Slider 連動で gain / pan を連続 modulate (declarative) | **Z4 Stream Console** | Slider + `HapbeatParameterBinding` (Source=SliderValue, Output=StreamGain/Pan) | | 自前 script が判定 → UnityEvent emit → Trigger | **Z5 TargetBoard** | `TargetReceiver.OnHit` (UnityEvent) → `HapbeatUnityEventTrigger.Fire` | | Custom 物理ロジックから script で Fire/Stop 直叩き | **Z3 Fishing** / **Z5 ChargeShooter** | `_sequenceTrigger.Fire()` / `.Stop()` | | 物理計算結果を script から binding に流す (External source) | **Z3 Fishing** | `HapbeatParameterBinding` (Source=External) + `binding.SetValue(v)` | | Manager API 直叩き (Trigger を介さない最上級) | (Z5 の発展形) | `HapbeatManager.Instance.StreamAudioClip(...)` | → **1 ゾーン = 1 パターン専用** ではなく、各 Zone 内に複数パターンが混在する設計。 ## 実装方式の使い分け [Section titled “実装方式の使い分け”](#実装方式の使い分け) Showcase は **コンポーネント方式 (Inspector で Trigger を wire)** と **スクリプト方式 (API を直接呼ぶ)** を Zone ごとに使い分けています。使い分けの判断基準は [BatchSetup vs スクリプト](./method-choice/) を参照。 ### コンポーネント方式 [Section titled “コンポーネント方式”](#コンポーネント方式) | Zone | コンポーネント | なぜこちらか | | -------------- | --------------------------------------------------------------------------- | ------------------------------------------------ | | Z1 Bowling | `HapbeatCollisionTrigger` (BatchSetup で Pin 6 個に一括) | 同じ設定を多数オブジェクトに貼る典型例。BatchSetup の真骨頂 | | Z2 Door | `HapbeatStateBehaviour` (Animator state に attach) | Animator state Enter は UnityEvent 不要、コンポーネントで宣言的 | | Z3 Fishing | `HapbeatSequenceTrigger` + `HapbeatParameterBinding` | Fire→Loop→Stop と運動量 modulation を Inspector で完結 | | Z4 Slider Tick | `HapbeatTickEmitter` (BatchSetup) | Slider.onValueChanged を自動 wire できる | | Z5 Hit | `HapbeatUnityEventTrigger` (TargetReceiver の OnHit から wiring) | UnityEvent wiring の典型例 | | Hotkeys | `HapbeatKeyDispatcher` + `HapbeatActionHelper` + `HapbeatUnityEventTrigger` | キー操作を UnityEvent で宣言的に wire | ### スクリプト方式 [Section titled “スクリプト方式”](#スクリプト方式) | Zone | 呼び出す API | なぜこちらか | | --------- | ------------------------------------------------------------ | ------------------------------------------- | | Z4 Stream | `StreamPlayback.Gain / Pan` 直接書き換え (`StreamDemoController`) | runtime で gain/pan を毎フレーム計算する | | Z5 Charge | `_trigger.GainMultiplier = curve(chargeT)` (`ChargeShooter`) | charge state を `AnimationCurve` で動的 mapping | ## Zone 別の wiring 詳細 [Section titled “Zone 別の wiring 詳細”](#zone-別の-wiring-詳細) ### Z1 Bowling Lane [Section titled “Z1 Bowling Lane”](#z1-bowling-lane) ボールをピンにぶつけて倒すと、各ピンの `HapbeatCollisionTrigger` が collision velocity に応じた gain で発火する。 * `Z1_Bowling/Pin_1`〜`Pin_6` の各オブジェクトに `HapbeatCollisionTrigger` * **EventMap**: `ShowcaseEventMap`, **Entry**: `pin_hit` * **Trigger Event**: `OnCollisionEnter` * **Gain Mode**: `VelocityScaled`, **Min / Max Velocity**: 0.5 / 5 * `BallLauncher` は物理 (ball reset + launch) のみ。Hapbeat 系コンポーネントは触らない ### Z2 Swing Door [Section titled “Z2 Swing Door”](#z2-swing-door) F キーで Animator の `IsOpen` bool をトグル → state Enter が発火し、`HapbeatStateBehaviour` 経由で触覚 event を呼ぶ。 * `Z2_Door/Door` に `Animator` + `DoorController` (F キーで `IsOpen` bool トグル) * `DoorAnimator.controller` の `Open` / `Closed` state にそれぞれ `HapbeatStateBehaviour` を attach * Open state: **Entry**: `door_open`, **Event**: `OnStateEnter` * Closed state: **Entry**: `door_close`, **Event**: `OnStateEnter` ### Z3 Fishing Rod [Section titled “Z3 Fishing Rod”](#z3-fishing-rod) LMB hold で物体が釣り糸に attach。Hold 中は Sequence loop が走り、`HapbeatParameterBinding` が物体の運動量を gain にリアルタイム反映する。 * `Z3_Fishing` root に `HapbeatSequenceTrigger` * **EventMap**: `ShowcaseEventMap` * **On Start Entry**: `grab_start` * **Loop Entry**: `grab_loop` (loop=true) * **On Stop Entry**: `grab_release` * 同じ root に `HapbeatParameterBinding` (`grab_loop` の preset から auto-attach) * **Source Transform Path**: `FishingObject` (root からの相対) * **Source Property**: `PositionDeltaMagnitude` * **Output**: `StreamGain`, range 0.2〜1.5, curve `EaseInOut` * `FishingController` (script) は LMB hold に応じて `Sequence.Fire() / Stop()` を呼ぶだけ ### Z4 Stream Console [Section titled “Z4 Stream Console”](#z4-stream-console) Space で stream をトグル開始 / 停止、Gain / Pan slider で再生中の値を動的変調する。 * `Z4_Stream/StreamPanel` に `HapbeatUnityEventTrigger` * **EventMap**: `ShowcaseEventMap`, **Entry**: `stream_demo` (StreamClip, loop=true) * `StreamDemoController` (script): Space キー → `trigger.Fire() / Stop()` * 同 script が毎フレーム `trigger.ActivePlayback.Gain = gainSlider.value`、`Pan = panSlider.value` で gain / pan をリアルタイム更新 * Gain / Pan Slider に `HapbeatTickEmitter` (BatchSetup) * **Entry**: `slider_tick`, **Tick Threshold**: 0.05 ### Z5 Charge & Shoot [Section titled “Z5 Charge & Shoot”](#z5-charge--shoot) LMB hold でチャージ → release で発射。`AnimationCurve` で charge 量を gain に mapping。命中時は別 Trigger が発火。 * 発射台に `HapbeatUnityEventTrigger` * **EventMap**: `ShowcaseEventMap`, **Entry**: `charge_release` * `ChargeShooter` (script) が LMB hold で charge → release 時に: ```csharp _trigger.GainMultiplier = _gainCurve.Evaluate(chargeT); _trigger.Fire(); ``` * `Z5_Target/TargetBoard` に `TargetReceiver` + 別の `HapbeatUnityEventTrigger` * **Entry**: `target_hit` * `TargetReceiver.OnHit` (UnityEvent) → `HapbeatUnityEventTrigger.Fire()` を wire ### Global hotkeys [Section titled “Global hotkeys”](#global-hotkeys) Q / P キーは UnityEvent ベースで wire。script はログ表示のみ。 * `[Hapbeat Event Router]` GameObject に以下を集約: * `HapbeatManager` (singleton) * `HapbeatKeyDispatcher` — Bindings リストで Q / P を UnityEvent として宣言的に wire * `HapbeatActionHelper` — `Ping()` を提供 * `HapbeatUnityEventTrigger` (entry: `manual_fire`) * Wiring: * Q → `HapbeatUnityEventTrigger.Fire()` (entry: `manual_fire`) * P → `HapbeatActionHelper.Ping()` * `GlobalHotkeys` (script) は Pong 受信時に HUD テキストを更新するだけ ## WASD と UI nav の衝突への対処 [Section titled “WASD と UI nav の衝突への対処”](#wasd-と-ui-nav-の衝突への対処) Unity の **`InputSystemUIInputModule`** の既定 `UI/Navigate` action には **WASD がバインド**されている。Slider にフォーカスがある状態で WASD を押すと、Player 移動と同時に Slider 値も変化する。 Showcase は Z4 で stream gain slider を触るため、以下の二重対策を入れている。 1. **`SimpleFPSController.HandleMove` は cursor lock 中のみ動作** — Z4 は `unlockCursorOnEnter=true` で cursor unlock 中のため、player は WASD で動かない 2. **`UiDeselectOnPointerUp` を各 Slider に attach** — マウスドラッグを離した時点で EventSystem の selection を解除し、以降 WASD が Slider に届かないようにする ### 実プロジェクトでの根治 [Section titled “実プロジェクトでの根治”](#実プロジェクトでの根治) zero-config を優先する Showcase では上記の対症療法を採っているが、**実プロジェクトでは UI Input Module 側で WASD を外す**のが筋。 1. `Packages/Input System/.../DefaultInputActions.inputactions` を `Assets/` 配下にコピー 2. コピーを Input Actions Editor で開き、**UI / Navigate / 2D Vector Composite** から `/w` `/a` `/s` `/d` の 4 binding を削除(Arrow キーは残す) 3. シーンの **EventSystem → Input System UI Input Module → Actions Asset** をコピーに差し替え これで `UiDeselectOnPointerUp` が不要になり、プロジェクト全体の UI 要素(Slider / Dropdown 等)で WASD が干渉しなくなる。 # Streaming buffer を調整する > StreamClip モードの送信バッファ (streamSendAheadSeconds) の意味、トレードオフ、設定指針。 `StreamClip` モードはホスト (Unity) からデバイスへ PCM オーディオを UDP で送り続ける送信です。`Command` モードと違い、デバイスはホストから届いたサンプルを再生するだけなので、ホスト側の **送信バッファ** がそのまま再生品質と停止遅延に影響します。 ## オーディオインターフェイスとの類比 [Section titled “オーディオインターフェイスとの類比”](#オーディオインターフェイスとの類比) DAW でオーディオインターフェイスのバッファサイズ (samples / ms) を調整するのと同じトレードオフです: | 項目 | DAW のバッファ | Hapbeat の `streamSendAheadSeconds` | | --- | ----------------------------------------- | ---------------------------------- | | 小さい | レイテンシ低い、CPU 過負荷で zipper / drop-out が起きやすい | 停止が速い、ネット遅延 / ジッタで途切れやすい | | 大きい | レイテンシ高い、安定して途切れない | 停止が遅延する (押してから残響)、安定 | DAW では「この曲では 64 sample で攻める / マスタリングでは 1024 sample で安定優先」のように使い分けますが、Hapbeat も用途に応じて調整します。 ## 仕組み [Section titled “仕組み”](#仕組み) SDK は常に「実時間より sendAhead 秒先まで」のサンプルを送信済みにキープし、デバイスはその先送り分を内部バッファとして消費しながら再生します。送信は Unity のフレームクロックではなく専用スレッドが担当するため、**フレームレートや GC スパイクの影響を受けません**。 `StopStream()` を呼んだ時: * SDK は送信スレッドを止めて即座に `STREAM_END` パケットを送る * ただしデバイスは **既に届いた sendAhead 秒分のサンプルを再生し終えてから** 停止する * つまり「Stop を押してから停止までの体感遅延 ≒ sendAhead の値」 ## 設定方法 [Section titled “設定方法”](#設定方法) [インストール要件](/docs/sdk-integration/unity-sdk/installation/) の `streamSendAheadSeconds` で変更できます: ```plaintext HapbeatConfig Behavior streamSendAheadSeconds: 0.05 ← デフォルト 50ms ``` 範囲: 10ms 〜 200ms。 ## 推奨値 [Section titled “推奨値”](#推奨値) | 用途 / 環境 | 推奨値 | 理由 | | ---------------- | --------------------------- | ---------------------------- | | LAN 直結 (有線、低ジッタ) | **20–30 ms** | UDP 遅延が極小なので攻めて OK。停止が機敏 | | 通常の Wi-Fi | **40–60 ms** (default 50ms) | 一般的なジッタ範囲を吸収しつつ停止遅延も実用範囲 | | 混雑 Wi-Fi / 複数台同時 | **80–120 ms** | パケットロスや遅延スパイクへの耐性優先 | | ライブパフォーマンス | 用途次第 | 「途切れたら台無し」なら大きく、「即応性重要」なら小さく | ## 影響を受けるのは StreamClip だけ [Section titled “影響を受けるのは StreamClip だけ”](#影響を受けるのは-streamclip-だけ) | モード | Stop 遅延 | | --------------------- | ----------------------- | | **Command** (FIRE) | 即時 (デバイスがローカル clip を停止) | | **StreamClip** (CLIP) | sendAhead 分の遅延あり | `HapbeatActionHelper.StopEverything()` は両方に対して停止指示を送るので、Command の音は瞬時に止まり、Stream の音だけ \~sendAhead 秒の残響があります。 ## clip フォーマットの統一 (StreamClip 同時再生) [Section titled “clip フォーマットの統一 (StreamClip 同時再生)”](#clip-フォーマットの統一-streamclip-同時再生) Hapbeat の stream session は **単一フォーマットで固定** されます。つまり 1 つの session 中で **sample rate / channel count が同一の clip しか同時 stream できません**。フォーマットが違う 2 つ目以降の clip は SDK で reject されます: ```plaintext [Hapbeat] StreamAudioClip: rate/channel mismatch with active session (session=16000Hz/2ch, new=16000Hz/1ch). Rejecting new source. ``` ### 推奨フォーマット: **16 kHz / 2ch (stereo) PCM16** [Section titled “推奨フォーマット: 16 kHz / 2ch (stereo) PCM16”](#推奨フォーマット-16-khz--2ch-stereo-pcm16) * 全 StreamClip 用 WAV を **`16 kHz / stereo / PCM 16-bit signed LE`** に揃えてください * mono ソースは stereo に up-mix (L=R duplicate) する * 異なる sample rate / channel count の clip を混ぜると同時再生不可 ### Studio 経由なら自動 normalize (2026-05-24 以降) [Section titled “Studio 経由なら自動 normalize (2026-05-24 以降)”](#studio-経由なら自動-normalize-2026-05-24-以降) * **Live streaming** (Studio Devices タブの再生): 送信時に **2ch / 16 kHz / PCM16 に auto-resample + up-mix** * **Kit deploy** (Helper の `pack_normalize`): `ffmpeg -ar 16000 -ac 2 -acodec pcm_s16le` で同じく統一 つまり **Studio を経由している限りユーザは何もしなくて良い**。ソース WAV が mono / 22.05 kHz でも勝手に統一フォーマットに揃って配信される。 ### Studio を経由しない場合は注意 [Section titled “Studio を経由しない場合は注意”](#studio-を経由しない場合は注意) 以下のケースは Studio の auto-normalize を通らないので、**自分で WAV を 16 kHz / 2ch / PCM16 に揃える必要があります**: * Unity AssetDatabase で直接 import した AudioClip を `HapbeatManager.StreamAudioClip` に渡す * 外部ツールで生成した WAV を Kit に直接コピー (Studio 経由でなく) * 自前 deploy スクリプト / CI で WAV を扱う 統一方法は 3 通り: ### 方法 1: SDK Editor メニュー (Unity 内で完結、推奨) [Section titled “方法 1: SDK Editor メニュー (Unity 内で完結、推奨)”](#方法-1-sdk-editor-メニュー-unity-内で完結推奨) メニューバー → **`Hapbeat → Normalize Audio Folder (16kHz · 2ch · PCM16)`** を選択: 1. フォルダピッカーが開く → Assets 配下の WAV 群が入ったフォルダを選ぶ (例: `Assets/HapbeatSDK/Kits/.../clips/`) 2. 「**16kHz / 2ch / PCM16 に変換します。上書きされます**」と確認ダイアログ 3. 実行で再帰的に全 WAV を normalize、進捗バー表示 4. 既に統一済の WAV は skip、変換失敗は warning ログ + 完了 dialog でリスト表示 → ffmpeg / Audacity 不要、Unity 内で完結。mono → stereo (L=R duplicate)、線形補間 resample、PCM16 で上書き。 ### 方法 2: ffmpeg [Section titled “方法 2: ffmpeg”](#方法-2-ffmpeg) ```bash ffmpeg -i input.wav -ar 16000 -ac 2 -acodec pcm_s16le output.wav ``` CI / シェルスクリプトで一括変換したい場合に。 ### 方法 3: Audacity (GUI) [Section titled “方法 3: Audacity (GUI)”](#方法-3-audacity-gui) 1. ファイル → 書き出し → WAV (Microsoft, 16-bit PCM) 2. 「サンプリング周波数」を 16000 Hz に 3. mono の場合: 「トラック → ステレオに変換」を先に実行 ### 単一 clip のみ stream する用途なら format 不問 [Section titled “単一 clip のみ stream する用途なら format 不問”](#単一-clip-のみ-stream-する用途なら-format-不問) session 中に **1 つの stream しか動かさない** 場合 (例: BGM 的に 1 つの clip だけループ) は format 統一は不要。session の最初の clip がそのまま format を決めるので。複数 clip の **同時再生 / 短時間 sequential 再生** で初めて問題になります。 ## 関連 [Section titled “関連”](#関連) * [Getting Started](/docs/sdk-integration/unity-sdk/getting-started/) — `Manager.StreamAudioClip` の基本 * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — StreamClip / Command の違い * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — entry の mode 設定 # ターゲティング > どのデバイスに触覚フィードバックを届けるかを決める仕組みと、構成ごとに player / position / group のどれで分けるべきかの指針。 ## ターゲティングの概要 [Section titled “ターゲティングの概要”](#ターゲティングの概要) Hapbeat のコマンドは、同じネットワーク上の device に届きます。どの device が再生するかは、コマンドに含まれる **Target** と、device が持つ **Address** の照合で決まります。 Address は 3 セグメントの正準形です。 ```text player_ / / group_ │ │ └ 一斉制御の単位(1〜99) │ └ 装着部位(pos_neck、pos_r_arm など) └ プレイヤー番号(1〜99) ``` Target は前方一致で照合され、`*` は 1 セグメント分の任意値に一致します。例えば `player_2` は player 2 の全 device に、`*/pos_neck` は全 player の neck に届きます。形式の詳細は[アドレス形式](/docs/concepts/group-player-addressing/)を参照してください。 ## 構成別の使い分け [Section titled “構成別の使い分け”](#構成別の使い分け) | 構成 | 分ける軸 | 設定 | | ------------------------ | -------------------- | ----------------------------- | | 送信 1 / 受信 1 種類 | 分けない | 既定のまま | | 送信 1 / 1 人が複数装着 | **position** | 部位ごとに Address を設定し、Target で指定 | | 送信 1 / 複数 player | **player** | 各 device に player 番号を割り当て | | 送信 N / 受信 N(1:1 の組が N 組) | **group** + override | 組ごとに同じ group を割り当て | | 複数送信元 / 受信混在 | **group** | 送信元ごとに group 範囲を分ける | ### 1 人が複数装着する場合 [Section titled “1 人が複数装着する場合”](#1-人が複数装着する場合) 部位の識別には `position` を使います。各 device の position を設定し、`*/pos_neck` や `*/pos_r_arm` のような Target を指定します。player を増やす予定があれば、player 軸をこの用途に使わない方が扱いやすくなります。 ### 複数 player に個別送信する場合 [Section titled “複数 player に個別送信する場合”](#複数-player-に個別送信する場合) 各 device に player 1、2、3 のような番号を割り当て、`player_1` のような Target を使います。送信する player を実行時に切り替える場合は Address Override を使います。 ### HMD と Hapbeat が 1:1 で複数組ある場合 [Section titled “HMD と Hapbeat が 1:1 で複数組ある場合”](#hmd-と-hapbeat-が-11-で複数組ある場合) LBE のように複数のペアが並ぶ場合は、`group` をペアの識別子にします。HMD 側の override と Hapbeat 側の Address に同じ group 番号を設定します。Event Map は共通のまま、同じビルドをすべての端末に配布できます。 ### 複数の送信元が混在する場合 [Section titled “複数の送信元が混在する場合”](#複数の送信元が混在する場合) device は送信元を区別しません。別アプリや別 PC が同じネットワークにある場合は、送信元ごとに group の範囲を分けます。例えば送信元 A は group 1〜10、B は group 11〜20 を使います。 ## Address Override [Section titled “Address Override”](#address-override) Address Override は、Event Map に記録した Target の player / group を**送信直前に上書き**します。各軸は独立して指定でき、off の軸は Event Map に記録した Target をそのまま使います。 これにより Event Map を複製せず、端末ごとの実行時設定だけで送信先を切り替えられます。複数端末へ同じビルドを配布する構成に使います。 | 単位 | 用途 | | --------------- | ----------------------------------------- | | **this build** | ビルド全体で固定する override。展示ごとに別ビルドを作る場合に使用 | | **this device** | 実行端末ごとに保持する override。1 本のビルドを複数端末へ配る場合に使用 | 各 SDK の設定場所と API は、その SDK のターゲティングページを参照してください。 ## 運用のヒント [Section titled “運用のヒント”](#運用のヒント) * Event Map を複数端末で使い回す場合、Target の player 軸は `*` にしておくと、override が off のときは全 player、設定済みのときは指定先へ送れます。 * `group` override を使う場合、Hapbeat 側の group 番号も同じ値にします。 * 既定の Address は `player_1` / `group_1` です。設定漏れとの衝突を避けたい展示では、運用番号を 2 以上から始めると確認しやすくなります。 ## 関連 [Section titled “関連”](#関連) * [アドレス形式](/docs/concepts/group-player-addressing/) * [送信経路と台数の目安](/docs/concepts/communication-model/) 共通ガイド「[ターゲティング](/docs/concepts/targeting/)」と同じ内容を表示しています。 *** ## Unity で設定する [Section titled “Unity で設定する”](#unity-で設定する) ### Event Map の Target を設定する [Section titled “Event Map の Target を設定する”](#event-map-の-target-を設定する) 通常の送信先は Event Map entry ごとに決めます。`Hapbeat → Open Event Map` で Event Map を開き、entry の **Targeting** を編集します。例えば `*/pos_neck` を指定すると、その entry を再生するすべての Trigger が neck の Hapbeat だけへ送ります。 Event Map の Target は、触覚演出そのものに属する送信先です。同じ entry を再生する GameObject ごとに設定を重複させる必要はありません。 ### Address Override を選ぶ [Section titled “Address Override を選ぶ”](#address-override-を選ぶ) 複数 HMD で同じ Event Map を使い、端末ごとに送信先だけ変えたい場合は、Event Map を編集せず Address Override を使います。 * **this build** — `Hapbeat → Open Settings` の Override Addressing。展示端末用に player / group を固定する場合。 * **this device** — 下記の API または実行時パネル。1 本の build を配り、起動した端末ごとに player / group を選ぶ場合。 Address Override は Event Map の Target を上書きするだけで、asset 自体は変更しません。 ### スクリプトから設定 [Section titled “スクリプトから設定”](#スクリプトから設定) ```csharp // 起動時、または設定画面で番号を確定するタイミングで呼ぶ HapbeatManager.Instance.SetAddressOverride(player: 3, group: HapbeatManager.AddressOverrideDisabled, persist: true); ``` * `player` / `group` は 1〜99。`AddressOverrideDisabled`(`-1`)を渡した軸は上書きしない * `persist: true` で PlayerPrefs に保存され、次回起動時も復元 * 付け替え時は `ClearPersistedAddressOverride()` を呼ぶ(Play モード中は `HapbeatManager` インスペクタの **Clear Saved Override** ボタン) 現在の値は `Hapbeat > Open Runtime Status` で確認できる(→ [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/))。 ### 設定パネルを置く [Section titled “設定パネルを置く”](#設定パネルを置く) `HapbeatAddressOverridePanel` を GameObject に 1 個追加するだけで、player / group を選んで Apply する実行時 UI が生成される。シーン側で UI 階層を組む必要はない。画面固定の HUD としても、VR 用の 3D パネルとしても置ける。 設定項目とコントローラー操作の受け付け方は [その他のコンポーネント](/docs/sdk-integration/unity-sdk/components/) を参照。 ### 実例 [Section titled “実例”](#実例) | サンプル | 内容 | | -------------------------------- | -------------------------------------------------------- | | **VR Config Example** | override 設定専用のシーン。VR 側の設定画面としてそのまま流用可能 | | **Showcase / Z4 Stream Console** | `AddressOverrideDemo` がパネルを継承しただけの薄いクラス。独自 UI の出発点として読める | いずれも Package Manager の Samples から Import する。 ## 現場での確認手段 [Section titled “現場での確認手段”](#現場での確認手段) `HapbeatConfig.appName` に `

` / `` を含めると、送信直前に現在の override 値へ置換されて OLED に表示される(無効時は `-`)。 ```text appName = "Booth

/" → player=3, group 無効: "Booth 3/-" ``` 「この HMD が正しい Hapbeat とペアか」をデバイスの画面だけで確認可能。 ## 運用上の注意 [Section titled “運用上の注意”](#運用上の注意) * **EventMap のターゲットのプレイヤー部分は `*` にしておく** — override 無効の端末では全デバイスに、設定済みの端末ではペア先だけに届き、同じ EventMap を使い回せる * **group override を使うならデバイス側の group 番号も合わせる** — 照合は位置ベースのため、`group_5` 宛ては group\_5 のデバイスにしか届かない * **送信元アプリ単位の排他制御は未実装** — 同じ番号を使う複数アプリを排他的に切り替える機能(デバイスが最初の app\_id を pin する方式)は、contracts / firmware / SDK の同時改修を要するため現時点では無い。必要な場合はユースケースを添えて [GitHub Issues](https://github.com/Hapbeat/hapbeat-unity-sdk/issues) へ ## 関連 [Section titled “関連”](#関連-1) * [Address の仕組み](/docs/concepts/group-player-addressing/) — アドレスの仕様 * [通信モデル](/docs/concepts/communication-model/) — 送信経路と台数の目安 * [Editor メニュー一覧](/docs/sdk-integration/unity-sdk/editor-menus/) — Settings / Runtime Status の場所 # Trigger コンポーネント > Animator / Collision / Sequence など、Event 発火用 Trigger コンポーネントの種類と使い分け。 Hapbeat SDK は多様な Trigger コンポーネントを用意しており、コードを書かずに触覚を組み込めます。 ## 一覧 [Section titled “一覧”](#一覧) | コンポーネント | 発火タイミング | 主な用途 | 参考 Showcase Zone | | ---------------------------- | ---------------------------- | ----------------------------------------------- | ----------------- | | **HapbeatUnityEventTrigger** | UnityEvent から `Fire()` を呼ぶ | UI Button / XR Interactable / Animation Event 等 | Z5 Charge | | **HapbeatStateBehaviour** | Animator state Enter / Exit | キャラクターアクション、UI アニメーション、ドア開閉等 | Z2 Door | | **HapbeatCollisionTrigger** | OnCollision / OnTrigger | 衝突、当たり判定、銃弾命中 | Z1 Bowling | | **HapbeatSequenceTrigger** | grab / hold / release の 3 段階 | XR Interaction(つかむ・持つ・離す) | Z3 Fishing | | **HapbeatTickEmitter** | 連続値の変化量に応じてスナップ発火 | Slider / ScrollRect 等のスクロール触覚 | Z4 Stream Console | ## HapbeatUnityEventTrigger [Section titled “HapbeatUnityEventTrigger”](#hapbeatunityeventtrigger) UnityEvent(Button.OnClick / XRI Activate / Animation Event 等)の `Fire()` メソッドを紐付けて発火します。コードなしで任意の UnityEvent から触覚を呼べます。 設定: * **Event Map**: EventMap.asset を参照 * **Event**: EventMap 内のエントリをドロップダウンで選択 ```plaintext On Activated: [Hapbeat Event Router] → HapbeatUnityEventTrigger.Fire() ``` ## HapbeatStateBehaviour [Section titled “HapbeatStateBehaviour”](#hapbeatstatebehaviour) `AnimatorController` の **state に直接 attach** する `StateMachineBehaviour` 派生。state Enter / Exit の瞬間に Animator runtime から直接呼ばれるため、scene 側 MonoBehaviour で Animator パラメータを poll する方式より遅延も発火条件も明快です。 > 通常の Trigger と違い、Add Component メニューには出ません。**Animator window で対象 state を選択 → Inspector → Add Behaviour → Hapbeat State Behaviour** で追加します。 設定: * **Event Map**: EventMap.asset * **Entry On Enter** / **Entry On Exit**: state 遷移ごとに別エントリを発火可能(片方だけでも OK) * **Required Previous State**: 非空時は「指定 state からの遷移時のみ Enter 発火」(例: `Closed → LockedRattle` だけ鳴らす、など) * **Gain Multiplier**: entry gain × manifest intensity への追加倍率 実装上の特徴: * looping StreamClip を Enter で発火した場合、Exit で **自動的に Stop** されるので「state に紐付く擦り音」がクリーンに切れます * 参照フィールド (EventMap 等) は AnimatorController asset 上に保存される(ScriptableObject 同士の参照なので scene 依存なし) * Showcase **Z2 Door**: `Open` / `Closed` state に各 1 個ずつ attach して `door_open` / `door_close` を発火 ## HapbeatCollisionTrigger [Section titled “HapbeatCollisionTrigger”](#hapbeatcollisiontrigger) Collision / Trigger イベントで発火。**速度連動 (Gain Mode: VelocityScaled)** が可能。 設定: * **Tag Filter**: 反応するオブジェクトの Tag(空 = 全対象) * **Layer Mask**: レイヤーフィルタ * **Gain Mode**: Fixed(固定)/ VelocityScaled(速度連動) * **Min Velocity**: これ以下の速度では発火しない * **Cooldown**: 連続発火防止(秒) > 強い衝撃ほど強い触覚、というマッピングが組めます。 ## HapbeatSequenceTrigger [Section titled “HapbeatSequenceTrigger”](#hapbeatsequencetrigger) XR Interaction Toolkit や独自の grab system と組み合わせ、**3 段階の Event を 1 コンポーネントで管理**します。 設定: * **On Grab**: つかんだ瞬間の Event(例: `bowling.grab`) * **On Hold**: 持っている間の継続 Event(CLIP 推奨、例: `bowling.hold`) * **On Release**: 離した瞬間の Event(例: `bowling.release`) XR Helpers サンプル (`Samples~/XriHelpers/`) を Import すれば XRGrabInteractable / XRSocketInteractor との接続が自動でセットアップできます(プロジェクト側で XRI を導入していなくても scaffold 自体は壊れません)。 ## HapbeatTickEmitter [Section titled “HapbeatTickEmitter”](#hapbeattickemitter) Slider / ScrollRect などの連続値の変化量に応じて、スナップアルゴリズムでトリガーします。 * Cooldown 不要(アルゴリズムが連続発火を自制) * `snap interval` で感度を調整 ## ParameterBinding と組み合わせる [Section titled “ParameterBinding と組み合わせる”](#parameterbinding-と組み合わせる) `HapbeatParameterBinding` を併用すると、Transform / Rigidbody の値を StreamClip の gain / pan に毎フレームマッピングできます。 例: 距離が近いほど触覚を強くする、移動速度が上がるほど振動が強まる。 詳細は [Parameter Binding](/docs/sdk-integration/unity-sdk/parameter-binding/) ## デバッグ [Section titled “デバッグ”](#デバッグ) メニューバー → `Hapbeat` → `Attach Event Logger to Selected` を実行すると、選択中 GameObject の UnityEvent 発火をコンソールにログ出力できます。XRI のイベント発火順序の可視化に有効です。 詳細ログを記録する場合: `Hapbeat → Logs → Start Recording` ## 次のステップ [Section titled “次のステップ”](#次のステップ) * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) * [Streaming buffer を調整する](/docs/sdk-integration/unity-sdk/streaming/) — StreamClip モードの停止遅延・途切れ耐性のトレードオフ * [Showcase Sample](/docs/sdk-integration/unity-sdk/showcase/overview/) — 各 Trigger の実例 (Z1 Collision / Z2 Animator / Z3 Sequence + Binding / Z4 TickEmitter / Z5 UnityEvent + スクリプト) * [BatchSetup vs スクリプト](/docs/sdk-integration/unity-sdk/showcase/method-choice/) # VR Config Example > VR 実機で Address Override の設定とテスト再生を行う最小サンプル。自分のプロジェクトの設定画面としてそのまま流用できる。 VR 実機(Quest 等)で **Address Override の設定とテスト再生**を行うための最小シーン。ヘッドセットを被ったまま、どの Hapbeat とペアにするかを決められる。 **XR Interaction Toolkit に依存しない。** Input System だけで動くため、XRI を導入していないプロジェクトにもそのまま入る。 ## 何ができるか [Section titled “何ができるか”](#何ができるか) * **player / group の設定** — パネル上で番号を選び、Apply で確定。`persist` されるので次回起動時も復元される * **テスト再生** — Play ボタンでその場で振動を確認。同梱の EventMap が CLIP エントリ(100Hz sine)なので、**デバイスへの Kit 配備は不要** * **自分のシーンへの復帰** — Exit の戻り先を設定しておけば、設定画面として自プロジェクトに組み込める ## 導入 [Section titled “導入”](#導入) 1. **Package Manager の Samples から *VR Config Example* を Import** 2. **`Scenes/VRConfigExample.unity` を開いて Build / Play** 3. **Hapbeat を同じ Wi-Fi に接続** ## 操作 [Section titled “操作”](#操作) コントローラーの操作は **2 つだけ**。左右どちらの手でも同じ操作ができる(左右で役割が分かれていない)。 | 操作 | 割り当て | キーボード | | ----------- | --------------------------------- | ------------- | | フォーカス移動 | スティックを倒す(左右どちらでも) | 矢印キー | | 決定 | トリガー / A(X) / B(Y) のいずれか(左右どちらでも) | Enter / Space | | パネルを正面へ引き寄せ | スティック押し込み | R | Player -/+ ・ Group -/+ ・ Play ・ Apply ・ Exit のすべてがパネル内のボタンとして 2D グリッドに並んでおり、上記 2 操作だけで完結する。 パネルは視界中央から外れたときだけ正面へ移動する。起動時に自動で正面へ寄せることはしないので、位置を合わせたいときはスティック押し込みで引き寄せる。 ## 自分のプロジェクトに組み込む [Section titled “自分のプロジェクトに組み込む”](#自分のプロジェクトに組み込む) `VRConfigExampleController` の **Return Scene** に戻り先シーンを指定すると、Exit で自分のシーンへ復帰する。これにより、**このシーンをそのまま「Hapbeat 設定画面」として使える**。 自前の UI を作りたい場合は、`HapbeatAddressOverridePanel` を GameObject に 1 つ追加するだけでも同等の設定 UI が生成される(→ [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/#override-targeting))。 ## 関連 [Section titled “関連”](#関連) * [ターゲティング](/docs/sdk-integration/unity-sdk/targeting/) — player / group の決め方と Override targeting の仕組み * [Showcase Sample](/docs/sdk-integration/unity-sdk/showcase/overview/) — XR 不要で全配線パターンを確認できるサンプル # XRI Hand Demo の APK を入れて試す > Hapbeat の触覚フィードバックを追加した XRI Hands Interaction Demo のビルド済み APK を Quest 3 / 3S に入れて体験する手順。リリースチャンネル / CLI / SideQuest の 3 通り。 [XR Interaction Toolkit](https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/index.html) の **Hands Interaction Demo** に Hapbeat の触覚フィードバックを追加した、**ビルド済み APK** を配布中。Unity を開かずに体験可能。 Hapbeat 実機が必要です このデモは Hapbeat が無いと何も起きません。デバイスは Quest と**同じ Wi-Fi** に接続してください。 自分の Unity プロジェクトで再現・改造したい場合は [XRI Hand Demo に haptics を追加](/docs/sdk-integration/unity-sdk/xri-handdemo-quickstart/) を参照。 ## 必要環境 [Section titled “必要環境”](#必要環境) * **Meta Quest** — 動作確認済みは **Quest 3 / 3S**。ハンドトラッキング対応機であれば **Quest 2 / Quest Pro** も配布対象に含む(未検証) * **Hapbeat 実機** — Quest と同じ Wi-Fi ## どの方法を選ぶか [Section titled “どの方法を選ぶか”](#どの方法を選ぶか) | | 方法 | 必要なもの | APK の入手 | | ----- | -------------- | ------------------ | ------- | | **A** | リリースチャンネル | Meta アカウント | 不要 | | **B** | CLI(adb) | PC・USB ケーブル・開発者モード | 必要 | | **C** | アプリ(SideQuest) | B と同じ + SideQuest | 必要 | 開発者登録をしていない場合は **A** が最も簡単。ストアから通常のアプリと同じように入る。 ## A. リリースチャンネル [Section titled “A. リリースチャンネル”](#a-リリースチャンネル) Meta Horizon Store の **ALPHA チャンネル**に招待する方式。APK のダウンロードも USB 接続も不要で、必要なのは Meta アカウントのみ。開発者登録も開発者モードもいらない。 招待は個別に送付するため、[お問い合わせ](/docs/support/contact/) から連絡すること。 1. **招待を受け取り、承諾** * 承諾はブラウザで完結する 2. **ヘッドセットのライブラリからインストール** * 招待制チャンネルのアプリは**ストア検索には出ない**。ライブラリに並ぶ ## APK のダウンロード(B / C 用) [Section titled “APK のダウンロード(B / C 用)”](#apk-のダウンロードb--c-用) [hapbeat-handdemo\_all.apk をダウンロード](https://github.com/hapbeat/hapbeat-demos/releases/latest/download/hapbeat-handdemo_all.apk) 過去のビルドは [hapbeat-demos の Releases](https://github.com/hapbeat/hapbeat-demos/releases) にある。以降のコマンドは、**この APK を置いたディレクトリで実行**する。 ## B. CLI(adb) [Section titled “B. CLI(adb)”](#b-cliadb) 1. **開発者モードを有効化** * 手順は [Meta 公式: Set up development environment](https://developers.meta.com/horizon/documentation/native/android/mobile-device-setup/) を参照 * Unity で Build and Run が通っている場合はすでに有効なので不要 2. **Quest を USB-C で PC に接続し、ヘッドセット内で USB デバッグを許可** * 許可ダイアログはヘッドセット内にしか出ない。装着して **Always allow from this computer** を選ぶ 3. **APK を置いたディレクトリで実行** ```bash adb install -r hapbeat-handdemo_all.apk ``` * `adb` は [Android SDK Platform Tools](https://developer.android.com/tools/releases/platform-tools) に含まれる。Unity の Android Build Support を導入済みなら `/Editor/Data/PlaybackEngines/AndroidPlayer/SDK/platform-tools/` にもある * コマンドの詳細は [adb 公式ドキュメント](https://developer.android.com/tools/adb) * `INSTALL_FAILED_UPDATE_INCOMPATIBLE`(署名不一致)が出た場合は、先に `adb uninstall com.Hapbeat.HapticHandDemo` を実行 4. **ライブラリの「提供元不明のアプリ(Unknown Sources)」から起動** * 既定のフィルタには出ない。切り替えが必要 ## C. アプリ(SideQuest) [Section titled “C. アプリ(SideQuest)”](#c-アプリsidequest) 1. **開発者モードを有効化** * 手順は [Meta 公式: Set up development environment](https://developers.meta.com/horizon/documentation/native/android/mobile-device-setup/) を参照 2. **PC に SideQuest を導入** * [SideQuest 公式: Get SideQuest](https://sidequestvr.com/setup-howto)(Advanced Installer 版) 3. **Quest を USB 接続し、SideQuest の接続インジケータが緑になるのを確認** 4. **APK を SideQuest のウィンドウにドラッグ&ドロップ** 5. **ライブラリの「提供元不明のアプリ(Unknown Sources)」から起動** ## 動作しないとき [Section titled “動作しないとき”](#動作しないとき) * Hapbeat と Quest が**同じ Wi-Fi**(同一サブネット)にいるか確認する * このビルドはアドレスを固定していないため、**同じネットワーク上の Hapbeat はすべて反応する**。特定の 1 台に絞る設定は入っていない * 解決しない場合は [Q\&A](/docs/support/faq/) の接続関連と [トラブルを解決する](/docs/hardware/troubleshooting/) を参照 ## ライセンス表記 [Section titled “ライセンス表記”](#ライセンス表記) 本デモには Unity 製の [XR Interaction Toolkit](https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/index.html) のサンプルアセットを含む。 * XR Interaction Toolkit copyright © Unity Technologies * ライセンス: [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License) # XRI Hand Demo に haptics を追加 > XR Interaction Toolkit の Hands Interaction Demo に、Editor メニュー 1 回で Hapbeat の触覚フィードバックを追加する手順。 Unity 公式の [XR Interaction Toolkit (XRI)](https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/index.html) のサンプル「Hands Interaction Demo」に、Hapbeat の触覚フィードバックを後付けで追加する。掴む・押す・スナップする・こするといった操作すべてに haptics が追加される。 ## 必要なもの [Section titled “必要なもの”](#必要なもの) * **Unity 6 (6000.0) 以上** * **[XR Interaction Toolkit](https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/index.html)** — 動作確認済みは 3.3.1 * **Hapbeat SDK** * **Hapbeat 実機** — Unity を動かす PC と同じ Wi-Fi * **ハンドトラッキング対応 HMD** — Quest 3 / 3S など ## 手順 [Section titled “手順”](#手順) 1. **XR Interaction Toolkit を Install** * Package Manager → Unity Registry * VR テンプレートで作成したプロジェクトには導入済み 2. **XRI の Samples から *Starter Assets* と *Hands Interaction Demo* を Import** * 展開先は `Assets/Samples/XR Interaction Toolkit//Hands Interaction Demo/` 3. **Hapbeat SDK を Install** * Package Manager → `+` → `Install package from git URL...` に次を貼り付け ```plaintext https://github.com/Hapbeat/hapbeat-unity-sdk.git ``` * バージョン固定は末尾にタグを付与(例 `#v0.3.0`) 4. **Hapbeat SDK の Samples から *XR Helpers* と *XRI Hand Demo (haptics add-on)* を Import** * **両方必要**。前者は XRI 用のフィルタコンポーネント、後者は EventMap と Kit 5. **`HandsDemoScene.unity` を開く** 6. **メニュー `Hapbeat > Samples > Augment XRI Hand Demo` を実行** * 触覚コンポーネントを配置し、XRI 側の UnityEvent に配線 * Undo 1 回で全て取り消し可能。再実行しても重複なし * 適用件数・スキップ件数・警告数を Console に 1 行で出力 7. **OpenXR で Hand Interaction Profile と Hand Tracking Subsystem を有効化** * 設定先は `Project Settings → XR Plug-in Management → OpenXR` の**ビルド対象のタブ**(Quest 単体なら Android、Air Link で Editor Play なら PC) * 未設定だと掴めない。poke は指の位置だけで成立するが、grab はピンチ = select 入力を要し、それを供給するのが Hand Interaction Profile 8. **Hapbeat を同じ Wi-Fi に接続して Play** ## 動作しないとき [Section titled “動作しないとき”](#動作しないとき) | 症状 | 対処 | | ------------------------------------------------- | --------------------------------------------------------------------------------------- | | `HandsDemoEventMap.asset` が見つからない | サンプル *XRI Hand Demo (haptics add-on)* が未 Import → 手順 4 | | 開いているシーンが Hands Interaction Demo ではない | シーン違い、または XRI のバージョン差。見つからなかったパスはダイアログに列挙 | | Console に `GameObject '…' not found` | XRI 側で改名・移動。警告に出たパスを手動で配線([Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/)) | | Console に `type '…HapbeatXRGrabFilter' not found` | サンプル *XR Helpers* が未 Import → 手順 4 | | 掴めない | Hand Interaction Profile と Hand Tracking Subsystem を有効化 → 手順 7 | | 触覚フィードバックが出ない | [Q\&A](/docs/support/faq/) の接続関連を参照 | ## ライセンスとツール方式の理由 [Section titled “ライセンスとツール方式の理由”](#ライセンスとツール方式の理由) Hapbeat SDK が配布するのは **EventMap・Kit・配線を適用する Editor コマンド**の 3 点のみで、シーン本体は非同梱。XRI 由来のアセットは 1 つも含まない。 XRI のサンプルは [Unity Companion License (UCL)](http://www.unity3d.com/legal/licenses/Unity_Companion_License) 下にある。改変したシーンの権利の帰属や著作権表示の義務が絡むため、SDK に第三者アセットを含めない形にしている。 ビルド済みアプリ(APK など)の配布は別で、UCL が想定するアプリケーションそのものであり許諾範囲に収まる → [XRI Hand Demo の APK を入れて試す](/docs/sdk-integration/unity-sdk/xri-handdemo-apk/) ## 次に読む [Section titled “次に読む”](#次に読む) * [Trigger コンポーネント](/docs/sdk-integration/unity-sdk/triggers/) — 適用された Trigger コンポーネントの役割 * [EventMap ウィンドウ](/docs/sdk-integration/unity-sdk/event-map/) — EventMap を編集して感触を調整する