---
title: ジェスチャー
slug: world-sdk-guide-ja/9BMvUq832ozfJqUdUeWPU
docTags: 
createdAt: 2024-09-02T08:34:13.134Z
---

ZepetoWorldContent APIを使用すると、希望するジェスチャー/ポーズカテゴリのサムネイルを設定し、サムネイルがクリックされたときに特定のジェスチャー/ポーズを有効にすることができます。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-UnpfP_uZECoMnF5rgTIym-20240904-102427.gif)



ZepetoWorldContent APIを使用するには、次のようにインポート文を書く必要があります。

```typescript
import { OfficialContentType, WorldService, ZepetoWorldContent, Content } from 'ZEPETO.World';
```



ジェスチャー/ポーズ情報を含むContentクラスのメンバ変数および関数情報は以下の通りです。

| **API**                                                  | **説明**                                                                                                                                                                                                                              |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| public get Id(): string                                  | コンテンツユニークID                                                                                                                                                                                                                         |
| public get Title(): string                               | ジェスチャー、ポーズタイトルテキスト<br /><br />- 言語はデバイスの言語に応じて自動的に翻訳されます                                                                                                                                                                            |
| public get Thumbnail(): UnityEngine.Texture2D            | 2Dサムネイル                                                                                                                                                                                                                             |
| public get AnimationClip(): UnityEngine.AnimationClip    | ジェスチャーアニメーションクリップ                                                                                                                                                                                                                   |
| public get IsDownloadedThumbnail(): boolean              | このサムネイルを以前にダウンロードしたかどうかを判断するための関数                                                                                                                                                                                                   |
| public get IsDownloadedAnimation(): boolean              | このアニメーションクリップを以前にダウンロードしたかどうかを判断するための関数                                                                                                                                                                                             |
| public DownloadAnimation($complete: System.Action)\:void | アニメーションクリップをダウンロードする関数で、完了コールバックを受け取ります<br /><br />- IsDownloadedAnimation()がfalseの場合、DownloadAnimation()を呼び出すように実装します。                                                                                                             |
| public DownloadThumbnail($complete: System.Action)\:void | サムネイルをダウンロードするための関数<br /><br />- IsDownloadedThumbnail()がfalseの場合、DownloadThumbnail()を呼び出すように実装します。                                                                                                                                 |
| OfficialContentType : enum                               | コンテンツの種類（World 1.9.0以降）<br /><br />- ジェスチャー = 2<br />- ポーズ = 4<br />- セルフィー = 8<br />- ジェスチャー挨拶 = 16<br />- ジェスチャーポーズ = 32<br />- ジェスチャー肯定 = 64<br />- ジェスチャーダンス = 128<br />- ジェスチャー否定 = 256<br />- ジェスチャーその他 = 512<br />- すべて = 14 |

:::hint{type="danger"}
- 既存の関数を使用できますが、`public DownloadThumbnail($character: ZEPETO_Character_Controller.ZepetoCharacter, $complete: System.Action):void`、機能に関する問題はありません。ただし、Zepetoキャラクターを引数として受け付けなくなったため、新しく修正された関数を使用してください。`public DownloadThumbnail($complete: System.Action):void` 代わりに。
:::



## ステップ 1 : UIを設定する



## ステップ 1-1 : ジェスチャーボタンを作成する

1\) 階層 > UI > キャンバスを追加し、他のUIに隠れないようにソート順を2に設定します。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-YHGDX6duGrC90en3Yt8CX-20240904-102427.png)



2\) 階層を追加 > UI > ボタン。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-23HjYw463uI38rM3540vu-20240904-102427.png "例のジェスチャーボタン")



## ステップ 1-2 : ジェスチャーパネルを整理する

1\) 階層を追加 > 空のオブジェクトを作成し、PanelParentと名前を変更します。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-kA-CWlNNslUUr_9KBHlFJ-20240904-102427.png)



2\) 階層を追加 > UI > PanelをPanelParentの子として追加します。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-WHpk5yBybkZb05MFvmovg-20240904-102427.png "例のジェスチャーパネル")



3\) 閉じるボタン: UI > ボタンを追加した後、ジェスチャーパネルを無効にするonClickイベントを追加します。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-HR4FNZGvT1UZlDe3IuXtF-20240904-102427.png "閉じるボタンの設定例")



4\) 開くボタン: 上記で作成した開くボタンに、ジェスチャーパネルをアクティブにするonClickイベントを追加してください。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-MKqJTnRH4QzrlXIJOyehI-20240904-102427.png "開くボタンの設定例")



5\) タイトルエリアとして画像を追加します。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-0xjmwA4pNtH_rlxgjL4S9-20240904-102427.png "タイトル画像の例")



6\) ジェスチャーサムネイルを表示するためにスクロールビューを設定します。

- 階層を追加 > UI > スクロールビュー。
- 横方向をチェックし、スクロールバー画像を無効にします。縦方向のスクロールのみを使用し、横方向のスクロールは必要ありません。
- スクロールビューのコンテンツにグリッドレイアウトを追加して、サムネイルをグリッドパターンで整列させます。
- オブジェクトのサイズをコンテンツのサイズに適切にするために、コンテンツサイズフィッターを追加します。
- スクリプトを実装する際は、スクロールビューのコンテンツをジェスチャーサムネイルの親として設定する必要があります（全体の領域が認識され、スクロールされるように）。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-NTHIAOgZfjHaBM6_m7vF3-20240904-102427.png "スクロールビュー設定の例")



7\) ジェスチャータイプによってタブを設定します。

- 階層を追加 > パネルの子として空のオブジェクトを作成し、それをGestureTitleに名前変更します。
  - これはトグルボタンの親オブジェクトです。
  - タブを水平に整列させるために水平レイアウトを追加します。
  - トグルグループコンポーネントを追加します。

:::hint{type="success"}
**👍&#x20;**&#x30BF;ブをさらに構成するには、階層を追加 > UI > スクロールビューを追加し、スクロールビューオプションで水平をチェックします。
:::

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N--8G4Wi0_yKQCjS5EKh3Z6-20240904-102427.png "GestureTitleの設定例")



8\) トグルボタンとして使用するテキストをGestureTitleの子として追加し、それを「すべて」に置き換えます。

- テキストの色を灰色に設定します。
- チェックされたときに表示されるハイライトされたテキストをテキストの子として追加します。
  - フォントの内容、サイズ、太さを同じに設定し、色を黒に設定します。
- トグルコンポーネントを追加します。
  - グループ内の親オブジェクトを指定します。
  - Graphicの子として追加したハイライトされたテキストを追加します。
  - 最初に表示されるすべてのトグルコンポーネントについてのみisOnをチェックします。
- GestureとPoseのトグルボタンを同じ方法で作成します。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-QzGzbIJmv1keZcc5bWn8x-20240904-102427.png "トグルボタンの設定の例")



## ステップ 1-3 : サムネイルプレハブを作成する

サムネイルボタンをプレハブとして作成し、スクリプト内でインスタンスとして生成する方法を使用します。

1\) Scroll ViewのContentの子としてUI > Buttonを追加し、名前をpreThumbに変更します。

2\) Raw Imageを追加した後、名前をThumbに変更してください。

- この画像はサムネイルになります。サイズを適切に調整してください。

3\) テキストを追加します。

- 画像の下部を中央に配置します。
- 文字のサイズと太さを調整し、Content Size Fitterを追加します。
  - 横のフィット : 推奨サイズ
  - 縦のフィット : 推奨サイズ

4\) 設定が完了したら、プレハブにしてResourcesフォルダに入れてください。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-HEjBUarUzOHukKwXOjYmO-20240904-102427.png "サムネイルプレハブ設定の例")

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N--8qFrc8v8chrUoQTFkeLH-20240904-102427.png "実行後にジェスチャーパネルにサムネイルボタンが設定される例")



## ステップ 1-4 : UI設定ガイド動画

::embed[[https://www.youtube.com/watch?v=V-iaS8t8Wq0](https://www.youtube.com/watch?v=V-iaS8t8Wq0)]{url="https://www.youtube.com/watch?v=V-iaS8t8Wq0"}

:::hint{type="success"}
**👍&#x20;**&#x52D5;画に表示されているUIのサイズと位置の値は推奨ですが、希望する値に変更できます！
:::

UI設定が完了したら、スクリプティングに進んでください。



## ステップ 2 : スクリプトを書く

このスクリプトはシングルプレイに基づいています。



## ステップ 2-1 : サムネイル

- プロジェクト > 作成 > ZEPETO > TypeScript としてサムネイルに名前を変更します。
- 以下のようなサンプルスクリプトを書きます。
  - これは、ジェスチャーコンテンツ情報（タイトル、画像）を UI に整理するスクリプトです。

:::CodeblockTabs
サムネイル

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { Content } from 'ZEPETO.World';
import { RawImage, Text } from 'UnityEngine.UI';
import { Texture2D } from 'UnityEngine';
 
export default class Thumbnail extends ZepetoScriptBehaviour {
 
    @HideInInspector() public content: Content;
     
    Start() {
        this.GetComponentInChildren<Text>().text = this.content.Title;
        this.GetComponentInChildren<RawImage>().texture = this.content.Thumbnail as Texture2D;
    }
 
}
```
:::

- スクリプトを作成した後、preThumb プレハブを開いてスクリプトを追加します。

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-mMxqLpUWz04xT28luxwWO-20240904-102427.png" size="86" width="470" height="641" position="center" showCaption="false"}



## ステップ 2-2 : GestureLoader

- 階層を作成 > 空のオブジェクトを作成し、GestureLoaderに名前を変更します。
- プロジェクトを作成 > 作成 > ZEPETO > TypeScriptを作成し、GestureLoaderに名前を変更します。
- 以下のようなサンプルスクリプトを書きます。

:::CodeblockTabs
GestureLoader

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { LocalPlayer, SpawnInfo, ZepetoCharacter, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { OfficialContentType, WorldService, ZepetoWorldContent, Content } from 'ZEPETO.World';
import { RawImage, Text, Button } from 'UnityEngine.UI';
import { GameObject, Texture2D, Transform, WaitUntil } from 'UnityEngine';
import Thumbnail from './Thumbnail';
 
export default class GestureLoader extends ZepetoScriptBehaviour {
 
    @HideInInspector() public contents: Content[] = [];
    @HideInInspector() public thumbnails: GameObject[] = [];
 
    @SerializeField() private _count: number = 50;
    @SerializeField() private _contentsParent: Transform;
    @SerializeField() private _prefThumb: GameObject;
 
    private _myCharacter: ZepetoCharacter;
     
    Start() {
        // キャラクターを作成
        ZepetoPlayers.instance.CreatePlayerWithUserId(WorldService.userId, new SpawnInfo(), true);
         
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._myCharacter = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
 
            // 自分のキャラクターでサムネイルを取得するためには、キャラクターが作成された後にコンテンツをリクエストする必要があります。
            this.ContentRequest();
        });
    }
 
 
    // 1. サーバーからコンテンツを受信
    private ContentRequest() {
         
        // すべてのタイプのリクエスト
        ZepetoWorldContent.RequestOfficialContentList(OfficialContentType.All, contents => {
            this.contents = contents;
             
            for (let i = 0; i < this._count; i++) {
                if (!this.contents[i].IsDownloadedThumbnail) {
                    // 自分のキャラクターを使ってサムネイル写真を撮る
                    this.contents[i].DownloadThumbnail(() =>{
                        this.CreateThumbnailObjcet(this.contents[i]);
                    });
                } else {
                    this.CreateThumbnailObjcet(this.contents[i]);
                }
            }
        });
 
    }
 
    // 2. サムネイルオブジェクトの作成
    private CreateThumbnailObjcet(content: Content) {
        const newThumb: GameObject = GameObject.Instantiate(this._prefThumb, this._contentsParent) as GameObject;
        newThumb.GetComponent<Thumbnail>().content = content;
 
        // 各サムネイルのボタンリスナー
        newThumb.GetComponent<Button>().onClick.AddListener(() => {
            this.LoadAnimation(content);
        });
         
        this.thumbnails.push(newThumb);
    }
 
    // 3. アニメーションの読み込み
    private LoadAnimation(content: Content) {
        // アニメーションの読み込みを確認
        if (!content.IsDownloadedAnimation) {
            // アニメーションがダウンロードされていない場合は、ダウンロードします。
            content.DownloadAnimation(() => {
                // アニメーションクリップを再生
                this._myCharacter.SetGesture(content.AnimationClip);
            });
        } else {
            this._myCharacter.SetGesture(content.AnimationClip);
        }
    }
}
```
:::

:::hint{type="danger"}
- カウントは、各タブでダウンロードする最大のジェスチャーの数です。100より大きい数に設定すると、サムネイルのダウンロードプロセス中にエラーが発生する可能性があるため、必要な分だけ設定してください。
:::



スクリプトは次のように進行します：

1\) ZEPETOキャラクターを読み込んだ後、サムネイルを生成するためにContentsRequest()カスタム関数を呼び出します。

- ContentsRequest()関数は、ジェスチャーとポーズをそれぞれ分けてコンテンツ情報を受け取ります。
  - 既存のサムネイルがある場合はスキップされ、そうでない場合はサムネイルが取得されます。
  - 取得したサムネイルデータは、それぞれのリストに保存されます。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-WXIPiOdYeIXNHRY2eiqC_-20240904-102427.png "ジェスチャースクリプト設定画面の例")



## ステップ 2-3 : UIController

- 階層を作成 > 空のオブジェクトを作成し、UIContollerに名前を変更します。
- プロジェクトを作成 > 作成 > ZEPETO > TypeScriptを作成し、UIContollerに名前を変更します。
- 以下のようなサンプルスクリプトを書きます。

:::CodeblockTabs
UIController

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { Button, RawImage, Text, Toggle } from 'UnityEngine.UI';
import { LocalPlayer, ZepetoCharacter, ZepetoPlayers, ZepetoScreenTouchpad } from 'ZEPETO.Character.Controller';
import { OfficialContentType, Content } from 'ZEPETO.World';
import { Object, GameObject, Transform } from 'UnityEngine';
import GestureLoader from './GestureLoader';
import Thumbnail from './Thumbnail';
 
export default class UIController extends ZepetoScriptBehaviour {
     
    @SerializeField() private _closeButton : Button;
    @SerializeField() private _typeToggleGroup : Toggle[];
 
    private _gestureLodaer: GestureLoader;
    private _myCharacter: ZepetoCharacter;
         
    Start() {
        this._gestureLodaer = Object.FindObjectOfType<GestureLoader>();
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._myCharacter = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
 
            // If click the touchpad, cancel the gesture
            Object.FindObjectOfType<ZepetoScreenTouchpad>().OnPointerDownEvent.AddListener(() => {
                this.StopGesture();
            });
 
            // If click the close button, cancel the gesture
            this._closeButton.onClick.AddListener(() => {
                this.StopGesture();
            });
        });
         
        // UI Listener
        this._typeToggleGroup[0].onValueChanged.AddListener(() => {
            this.SetCategoryUI(OfficialContentType.All);
        });
        this._typeToggleGroup[1].onValueChanged.AddListener(() => {
            this.SetCategoryUI(OfficialContentType.Gesture);
        });
        this._typeToggleGroup[2].onValueChanged.AddListener(() => {
            this.SetCategoryUI(OfficialContentType.Pose);
        });
 
    }
 
    // Category Toggle UI Set
    private SetCategoryUI(category: OfficialContentType) {
         
        if (category == OfficialContentType.All) {
            this._gestureLodaer.thumbnails.forEach((Obj) => {
                Obj.SetActive(true);
            });
        }   else {
            for (let i = 0; i < this._gestureLodaer.thumbnails.length; i++) {
                const content = this._gestureLodaer.thumbnails[i].GetComponent<Thumbnail>().content;
                if (content.Keywords.includes(category)) {
                    this._gestureLodaer.thumbnails[i].SetActive(true);
                } else {
                    this._gestureLodaer.thumbnails[i].SetActive(false);
                }
            }
        }
    }
     
    private StopGesture() {
        this._myCharacter.CancelGesture();
    }
}
```
:::

スクリプトの流れは次のとおりです：

1. タッチパッドまたは閉じるボタンをタッチして、CancelGesture()関数を使用して再生をキャンセルします。
2. タブ（トグルボタン）をタップして、SetCategoryUI()カスタム関数を呼び出します。
3. SetCategoryUI()関数は、各サムネイルのジェスチャーコンテンツ情報を使用して、それぞれの対応するカテゴリに設定します。

- 適用可能なタイプであれば有効にし、そうでなければ無効にします。



スクリプトを完了したら、インスペクターに「Close Button」と「TypeToggleGroup」を割り当てます。

- Type Toggle Groupへのエントリーは、ジェスチャーパネル内のToggle Groupの子であるToggleです。

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-8SuVSLSVfcd2bO_GZUPn--20240904-102427.png "例のスクリプト設定画面")



## ステップ 3 : 実行

:::hint{type="danger"}
**❗️ 注意**
プレイする前に、PanelParentを無効にして、プレイ中にオープンボタンだけが表示されるようにしてください。
:::

::Image[]{src="https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-LRFGXgh9k1X9tGdpj1xn3-20240904-102427.png" size="80" width="425" height="320" position="center" showCaption="false"}



## ステップ 4 : マルチプレイジェスチャーの同期

- マルチプレイの場合、特定のプレイヤーが取得したジェスチャー情報値を受信し、ルームにアクセスするすべてのプレイヤーに適用する同期コードを追加する必要があります。
- 重要なのは、どのプレイヤーがどのジェスチャーを行ったかについて、サーバーとクライアント間でルームメッセージを送受信することです。



## ステップ 4-1 : クライアントコード

### サムネイル - マルチプレイ

- シングルプレイのクライアントコードで実装されたのと同じスクリプトを書いてください。

:::CodeblockTabs
サムネイル - マルチプレイ

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { Content } from 'ZEPETO.World';
import { RawImage, Text } from 'UnityEngine.UI';
import { Texture2D } from 'UnityEngine';
 
export default class Thumbnail extends ZepetoScriptBehaviour {
 
    @HideInInspector() public content: Content;
     
    Start() {
        this.GetComponentInChildren<Text>().text = this.content.Title;
        this.GetComponentInChildren<RawImage>().texture = this.content.Thumbnail as Texture2D;
    }
 
}
```
:::



### GestureLoader - マルチプレイ

- デフォルトでは、シングルプレイのクライアントコードで実装されたスクリプトは同じように書かれています。
- さらに、クライアントはPlayerGestureInfoを含むインターフェースを宣言します。
- サーバーに情報を送信する際：SendMyGesture()カスタム関数を参照してください。
  - プレイヤーがサムネイルを押してジェスチャーを作成するとき、ジェスチャーIDをサーバーに送信します room.Send() を使用します。
  - ジェスチャーをキャンセルするときは、キャンセルしたことを送信するために処理します。
- サーバーから別のクライアントからジェスチャー情報を受信するとき："OnChangeGesture"ルームメッセージがこの.room.AddMessageHandler()内のStart()に送信されます。
  - 同期は、"OnChangeGesture"メッセージにセッションIDとジェスチャーIDを持たせ、適切なプレイヤーがジェスチャーを実行することで達成されます。

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { LocalPlayer, SpawnInfo, ZepetoCharacter, ZepetoPlayers } from 'ZEPETO.Character.Controller';
import { OfficialContentType, WorldService, ZepetoWorldContent, Content, ZepetoWorldMultiplay } from 'ZEPETO.World';
import { RawImage, Text, Button } from 'UnityEngine.UI';
import { GameObject, Texture2D, Transform, WaitUntil } from 'UnityEngine';
import Thumbnail from './Thumbnail';
import { Room, RoomData } from 'ZEPETO.Multiplay';
 
interface PlayerGestureInfo {
    sessionId: string,
    gestureId: string
}
 
const CancelMotion = "" as const;
 
export default class GestureLoaderMultiPlay extends ZepetoScriptBehaviour {
 
    public multiplay: ZepetoWorldMultiplay;
     
    @HideInInspector() public contents: Content[] = [];
    @HideInInspector() public thumbnails: GameObject[] = [];
 
    @SerializeField() private _count: number = 50;
    @SerializeField() private _contentsParent: Transform;
    @SerializeField() private _prefThumb: GameObject;
 
    private _myCharacter: ZepetoCharacter;
    private _room: Room;
    private _contentsMap: Map<string, Content> = new Map<string, Content>();
 
    Start() {
        // キャラクターを作成
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._myCharacter = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
            
            // キャラクターが作成された後にコンテンツをリクエストする必要があります。
            this.ContentRequest();
        });
 
        // マルチプレイ用
        this.multiplay.RoomCreated += (room: Room) => {
            this._room = room;
            // サーバーからユーザーのジェスチャー情報を受信
            this._room.AddMessageHandler("OnChangeGesture", (message: PlayerGestureInfo) => {
                let playerGestureInfo: PlayerGestureInfo = {
                    sessionId: message.sessionId,
                    gestureId: message.gestureId
                };
                this.LoadAnimation(playerGestureInfo);
            });
        };
    }
 
 
    // 1. サーバーからコンテンツを受信
    private ContentRequest(){
         
        // すべてのタイプのリクエスト
        ZepetoWorldContent.RequestOfficialContentList(OfficialContentType.All, contents => {
            this.contents = contents;
            for (let i = 0; i < this._count; i++) {
                if (!this.contents[i].IsDownloadedThumbnail) {
                    // 自分のキャラクターを使ってサムネイル写真を撮る
                    this.contents[i].DownloadThumbnail(() =>{
                        this.CreateThumbnailObjcet(this.contents[i]);
                    });
                } else {
                    this.CreateThumbnailObjcet(this.contents[i]);
                }
            }
        });
 
    }
 
    // 2. サムネイルオブジェクトの作成
    private CreateThumbnailObjcet(content: Content) {
        const newThumb: GameObject = GameObject.Instantiate(this._prefThumb, this._contentsParent) as GameObject;
        newThumb.GetComponent<Thumbnail>().content = content;
 
        // コンテンツIDでコンテンツを見つけるための辞書を作成
        this._contentsMap.set(content.Id, content);
         
        // 各サムネイルのボタンリスナー
        newThumb.GetComponent<Button>().onClick.AddListener(() => {
            this.SendMyGesture(content.Id);
        });
 
        // サムネイルリスト
        this.thumbnails.push(newThumb);
 
    }
 
    // マルチプレイ用
    // クリックされたジェスチャー情報をサーバーに送信
    public SendMyGesture(gestureId) {
        const data = new RoomData();
        data.Add("gestureId", gestureId);
        this._room.Send("OnChangeGesture", data.GetObject());
    }
 
    // 3. アニメーションの読み込み
    private LoadAnimation(playerGestureInfo: PlayerGestureInfo){
         
        if (!ZepetoPlayers.instance.HasPlayer(playerGestureInfo.sessionId)) {
            console.log("プレイヤーが存在しません");
            return;
        }
         
        const zepetoPlayer = ZepetoPlayers.instance.GetPlayer(playerGestureInfo.sessionId).character;
 
        if (playerGestureInfo.gestureId == CancelMotion) {
            zepetoPlayer.CancelGesture();
            return;
        }
        else if(!this._contentsMap.has(playerGestureInfo.gestureId)) {
            console.log("リソースがまだ読み込まれていません");
            return;
        }
 
        const content = this._contentsMap.get(playerGestureInfo.gestureId);
 
        // アニメーションの読み込みを確認
        if (!content.IsDownloadedAnimation) {
            // アニメーションがダウンロードされていない場合、ダウンロードします。
            content.DownloadAnimation(() => {
                // アニメーションクリップを再生
                zepetoPlayer.SetGesture(content.AnimationClip);
            });
        } else {
            zepetoPlayer.SetGesture(content.AnimationClip);
        }
    }
}
```



- スクリプトの作成が完了した後、インスペクターはZepeto World Multiplayコンポーネントを使用してMultiplayに追加のオブジェクトを割り当てます。



![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-ErOiXFzjb_CqNuH3v5qGF-20240904-102427.png "スクリプト設定画面の例")

![](https://api.archbee.com/api/optimize/fCt3n1oCa8rgNJ8fw9I2N-9V_vQfOX7KEMXb5QqajfL-20240904-102427.png "Zepeto World Multiplayコンポーネントを持つオブジェクトの例")



### UIController - マルチプレイ

- デフォルトでは、シングルプレイクライアントコードで実装されたスクリプトは同じように書かれています。
- シングルプレイクライアントコードとの違いは、StopGesture()カスタム関数です。
  - GestureLoaderMultiplay内でSendMyGesture()カスタム関数を呼び出します。
  - ジェスチャーがキャンセルされたことを通知する情報を送信するプロセス。

```typescript
import { ZepetoScriptBehaviour } from 'ZEPETO.Script';
import { Button, RawImage, Text, Toggle } from 'UnityEngine.UI';
import { LocalPlayer, ZepetoCharacter, ZepetoPlayers, ZepetoScreenTouchpad } from 'ZEPETO.Character.Controller';
import { OfficialContentType, Content } from 'ZEPETO.World';
import { Object, GameObject, Transform } from 'UnityEngine';
import GestureLoaderMultiPlay from './GestureLoaderMultiPlay';
import Thumbnail from './Thumbnail';
 
const CancelMotion = "" as const;
 
export default class UIController extends ZepetoScriptBehaviour {
 
    @SerializeField() private _closeButton : Button;
    @SerializeField() private _typeToggleGroup : Toggle[];
 
    private _gestureLodaer: GestureLoaderMultiPlay;
    private _myCharacter: ZepetoCharacter;
 
    Start() {
        this._gestureLodaer = Object.FindObjectOfType<GestureLoaderMultiPlay>();
        ZepetoPlayers.instance.OnAddedLocalPlayer.AddListener(() => {
            this._myCharacter = ZepetoPlayers.instance.LocalPlayer.zepetoPlayer.character;
 
            // タッチパッドをクリックした場合、ジェスチャーをキャンセル
            Object.FindObjectOfType<ZepetoScreenTouchpad>().OnPointerDownEvent.AddListener(() => {
                this.StopGesture();
            });
 
            // 閉じるボタンをクリックした場合、ジェスチャーをキャンセル
            this._closeButton.onClick.AddListener(() => {
                this.StopGesture();
            });
        });
 
        // UI リスナー
        this._typeToggleGroup[0].onValueChanged.AddListener(() => {
            this.SetCategoryUI(OfficialContentType.All);
        });
        this._typeToggleGroup[1].onValueChanged.AddListener(() => {
            this.SetCategoryUI(OfficialContentType.Gesture);
        });
        this._typeToggleGroup[2].onValueChanged.AddListener(() => {
            this.SetCategoryUI(OfficialContentType.Pose);
        });
     
    }
 
    // カテゴリ トグル UI セット
    private SetCategoryUI(category: OfficialContentType) {
 
        if (category == OfficialContentType.All) {
            this._gestureLodaer.thumbnails.forEach((Obj) => {
                Obj.SetActive(true);
            });
        }  else {
            for (let i = 0; i < this._gestureLodaer.thumbnails.length; i++) {
                const content = this._gestureLodaer.thumbnails[i].GetComponent<Thumbnail>().content;
                if (content.Keywords.includes(category)) {
                    this._gestureLodaer.thumbnails[i].SetActive(true);
                } else {
                    this._gestureLodaer.thumbnails[i].SetActive(false);
                }
            }
        }
    }
 
    private StopGesture() {
        this._gestureLodaer.SendMyGesture(CancelMotion);
    }
}
```



## ステップ 4-2 : サーバーコード

- サーバーコードは、PlayerGestureInfoを含むインターフェースを同様に宣言します。
  - サーバーコードは、デフォルトでMultiplay Sampleのサーバーコードに基づいています。
- onCreate()内でジェスチャーが変更されると、他のクライアントにジェスチャー情報を送信するonMessage()コールバックを作成します。

```typescript
import { Sandbox, SandboxOptions, SandboxPlayer } from 'ZEPETO.Multiplay';
import { Player, Transform, Vector3 } from 'ZEPETO.Multiplay.Schema';

// プレイヤーのジェスチャー情報を表すインターフェース PlayerGestureInfo を定義します。
interface PlayerGestureInfo {
    sessionId: string,
    gestureId: string
}
 
export default class extends Sandbox {
  
 	 // スクリプトで使用されるメッセージタイプを格納する定数オブジェクト `MESSAGE_TYPE` を定義します。
    MESSAGE_TYPE = {
        OnChangeGesture: "OnChangeGesture"
    }
     
    onCreate(options: SandboxOptions) {
 
        // ルームが作成されたときに呼び出されます。
        // ルームの状態またはデータの初期化を処理します。
 
        this.onMessage("onChangedTransform", (client, message) => {
            const player = this.state.players.get(client.sessionId);
 
            const transform = new Transform();
            transform.position = new Vector3();
            transform.position.x = message.position.x;
            transform.position.y = message.position.y;
            transform.position.z = message.position.z;
 
            transform.rotation = new Vector3();
            transform.rotation.x = message.rotation.x;
            transform.rotation.y = message.rotation.y;
            transform.rotation.z = message.rotation.z;
 
            player.transform = transform;
        });
 
        this.onMessage("onChangedState", (client, message) => {
            const player = this.state.players.get(client.sessionId);
            player.state = message.state;
            player.subState = message.subState; // キャラクターコントローラー V2
        });
 
        // ジェスチャーが変更されたとき、
        this.onMessage<PlayerGestureInfo>(this.MESSAGE_TYPE.OnChangeGesture, (client, message) => {
            let gestureInfo:PlayerGestureInfo = {
                sessionId: client.sessionId,
                gestureId: message.gestureId
            };
            // クライアントを除く他のプレイヤーにジェスチャーを送信します
            this.broadcast(this.MESSAGE_TYPE.OnChangeGesture, gestureInfo);
        });
 
    }
     
    onJoin(client: SandboxPlayer) {
 
        // schemas.json で定義されたプレイヤーオブジェクトを作成した後、初期値を設定します。
        console.log(`[OnJoin] sessionId : ${client.sessionId}, userId : ${client.userId}`)
 
        const player = new Player();
        player.sessionId = client.sessionId;
 

        if (client.userId) {
            player.zepetoUserId = client.userId;
        }
         
        // クライアントオブジェクトの一意のキー値である sessionId を使用してプレイヤーオブジェクトを管理します。
        // クライアントは、プレイヤーオブジェクトに追加された情報を players オブジェクトに add_OnAdd イベントを追加することで確認できます。
        this.state.players.set(client.sessionId, player);
    }
     
    onLeave(client: SandboxPlayer, consented?: boolean) {
 
        // allowReconnection を設定することで、回路の接続を維持できますが、基本ガイドではすぐにクリーンアップします。
        // クライアントは、削除されたプレイヤーオブジェクトに関する情報を players オブジェクトに add_OnRemove イベントを追加することで確認できます。
        this.state.players.delete(client.sessionId);
    }
}
```

