HeliScriptで中間ポイント、ゲームオーバー、ゲームクリアを実装する【コピペで使えるサンプルコード付き】

:::note info
本記事は、[HeliScriptでリッチなアスレチックワールドを作ろう！【コピペで使えるサンプルコード付き】](https://qiita.com/IwahanaFuku/private/63e6bbca2bc1f13ed57e)の記事の一部です

VR法人HIKKYのorganization下のQiita/Zenn両方に投稿しております。
:::

## はじめに
アスレチックを作るためのcomponentは出来てきたので、一度ワールド全体を通しで遊んでみるために、中間ポイントと、ゲームオーバー、ゲームクリアのcomponentを作っていきます。
これらを作ることで、ぐっとゲームらしくなってきます。

## 中間ポイント
中間ポイントはある一定のエリアコライダーに侵入したら中間ポイントが更新され、更新後は落下してもそこからゲームが始まるようにします。

※可視化のため、半透明のマテリアルを適用させています。
![2024-03-29 11-12-34.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/6239203f-9d73-8a6e-f64a-3e52575d1c0d.gif)

### Unity設定画面

**全体構成**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/d30de84b-ceba-09fa-dc99-60639f1f004e.png)

**エリアコライダーを格納するHEO Field**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/c01ddf5d-1e34-5424-305a-35e8e87eabc5.png)

**ワープポイントを設定するためのArea Collider**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/4bb95a6b-7637-4c28-3a2b-4a4792233534.png)

**ワープ先のポイントを設定するためのHEO Object**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/5ddb07fd-d889-1e4b-1a22-9989c43bec69.png)


```C#:HalfwayPointManager.hs
component HalfwayPointManager
{
    Vector3 m_warpPoint; //ワープした後の位置
    float m_rotate; //ワープした後のY軸の向き

    //中間スポーン用のItemの名前をもとに中間ポイントを更新する
    public void SetHalfwayPoint(string halfwayPointName)
    {
        Item warpPointItem = hsItemGet(halfwayPointName);
        m_warpPoint = warpPointItem.GetPos();
        m_rotate = warpPointItem.GetWorldRotate().y;
	}

     //中間ポイントにワープする
	public void WarpHalfwayPoint()
	{
        Player player = hsPlayerGet();
		player.SetPos(m_warpPoint);
        player.SetRotate(m_rotate);
	}
}
```
### コード解説
#### ワープ先の位置の設定
ワープ先の設定には、HEO Objectを使うことで実装していきます。HEO Objectは、アニメーションやギミックに使用するオブジェクトを表示するために使うことが一般的ですが、今までやってきたようにHeliScriptで移動、回転、スケールの値を取得、設定できるという特性を持っており、Unity側での調整を行いやすいマーカーとして扱うことが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/a4903e79-3012-4f88-359b-e3a7460316c3.png)

.heoファイルとして、Emptyという.heoファイルをアタッチしています。
これは、何も設定されていない原点位置に置かれたGameObjectを、VketCloudSDK > フィールドのエクスポートでエクスポートします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/10699718-4682-97c8-4ba3-5cbf8cc319ab.png)


#### ワープポイントの更新
```C#
    //中間スポーン用のItemの名前をもとに中間ポイントを更新する
    public void SetHalfwayPoint(string halfwayPointName)
    {
        Item warpPointItem = hsItemGet(halfwayPointName);
        m_warpPoint = warpPointItem.GetPos();
        m_rotate = warpPointItem.GetWorldRotate().y;
	}
```

ワープポイントの更新の仕組みとして、ワープポイントの位置設定用のHEO Objectの名前をエリアコライダーから設定することで、ワープポイントの位置と回転を取得するといった仕組みを作ります。

HEO Area Colliderコンポーネント（および、クリック判定を行くHEO Action Triggerコンポーネント）に設定できる、アクションの一つとして、[CallScript(HeliScriptを呼び出す)](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/ja/Actions/Programmatic/CallScript.html)があります。
これは、HEO Scriptが設定されている、ゲームオブジェクトの名前と、コンポーネント名、関数、引数を設定することで、Action側からcomponentの中のpublicな関数を実行することが出来る仕組みです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/312c3325-b35a-64db-5bb7-214b92e02500.png)

HEO Area Colliderから、CallScriptが発火されることで、HalfwayPointManager component内の、`SetHalfwayPoint(string halfwayPointName)`を呼び出すことが出来ます。

HEO Area Colliderは、HEO Fieldの配下でないと動作しないため、HelfwayPointManagerには、HEO Fieldを付けておきます。

:::note info
最初にワープポイントを設定するためのエリアコライダーをスポーン地点にかぶせて実行しています。
これにより、ワールドに入ってから関数が実行される初期化を行うことが出来ます。
これに限らず、エリアコライダーをスポーン地点にかぶせるという仕組みはVket Cloudでギミックを作るときによく使用します。
:::

#### ワープの実行
```C#
     //中間ポイントにワープする
	public void WarpHalfwayPoint()
	{
        Player player = hsPlayerGet();
		player.SetPos(m_warpPoint);
        player.SetRotate(m_rotate);
	}
```
初めて[Playerクラス](https://vrhikky.github.io/VketCloudSDK_Documents/latest/hs/hs_class_player.html)が登場しました。
Playerクラスは、プレイヤーの位置や名前、モーションの情報など、プレイヤーに関わる情報が格納できるクラスです。

:::note warn
Playerクラスはコンストラクタで取得するとマルチプレイ時に動作しなくなる仕様があります。
よって、必要なときにhsPlayerGet()などで取得したり、コンストラクタではなく、ワールド入室後にエリアコライダーや、クリックなどのトリガーを基準に取得するようにします。
:::

:::note info
Playerクラスは、[hsPlayerGet()](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_class_player.html#hsplayerget)で、自分自身プレイヤーを取得することが出来ます。
もう一つの、Playerを取得するための関数として、[hsPlayerGetByID(string PlayerID](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_class_player.html#hsplayergetbyid)がありますが、これは自分自身ではなく、他人のPlayerを取得することが出来るようになります。
これは、マルチプレイギミックに使用することが出来ます。
:::

テストのため、ワープの実行もHEO Area Colliderのアクションから実行することにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/6c864e92-007e-d252-da05-b71e6f8d869d.png)

`SetHalfwayPoint(string halfwayPointName)`で、コンストラクタに設定しておいた、m_warpPointと、m_rotateを設定します。

また、VketCloudSDK標準のDespawnが被ってしまっている場合は、VketCloudSettings > DespawnHeightSettingsのエリアサイズを0にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/90323c61-0c0e-8e1b-b630-9bc54ed32a56.png)

これで中間ポイントの実装は完了です！
[CallScript(HeliScriptを呼び出す)](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/ja/Actions/Programmatic/CallScript.html)を使うことで、プレイヤーの入力との相互作用を生み出すことが出来るようになります。

## ゲームオーバー
ゲームオーバーは、プレイヤーが規定回数マグマに落ちてしまったら再起不能として、ゲームオーバーにしてみましょう。
ゲームオーバーになったら、ゲームオーバー画面を表示させて、リトライがしやすいような仕組みにします。
heliscriptには、[Item.SetCamera()](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/ja/hs/hs_class_item.html#setcamera) [Item.ResetCamera()](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/ja/hs/hs_class_item.html#setcamera)という関数が存在しており、事前にシーン上cameraタイプのItemを追加しておくことで、cameraを切り替えて、簡易的なUIを表示したりすることが出来るようになります。

リトライ後は、どこかをクリックしたらマグマやプレイヤーのスポーン位置を変更するようにします。

また、中間ポイントの実装で、WarpTriggerAreaに侵入すると、中間ポイントへワープするようになっていましたが、Area Colliderは、Item.SetPos()することが出来ないため、マグマのy座標と、プレイヤーのy座標を比べて、プレイヤーのy座標の方が小さくなった場合にワープするような機能も実装します。

![2024-03-26 18-25-06_5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/b3d2e7ce-d897-4d1e-18b4-4343e457c5b5.gif)

### Unity設定画面

**全体構成**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/da1d7ba0-ec56-c255-463c-4f228b3d1706.png)

**Camera**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/07eb5a18-8e24-4f20-715f-4c48f0fd7b19.png)

**TextPlane**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/f11c16ad-2d1b-9459-b946-b9970629a07e.png)

**ボックス**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/aa661769-c398-f8fb-ac4c-048e26203feb.png)

```C#:ItemMove.hs（追記）
	Vector3 m_itemInitialPos; //アイテムが配置された初期地点
 
    public ItemMove()
    {
        m_item = hsItemGetSelf(); //コンポーネントがセットされたItemを取得する

        m_velocity = StrToVector3(m_item.GetProperty("velocity(Vector3)")); //コンポーネントがセットされたItemの(Vector3)というプロパティを取得する
        
        //アイテムが配置された初期地点を取得しておく
		m_itemInitialPos = m_item.GetPos();
    }
    
    //位置をスタート地点にリセットする関数
    public void ResetToStartPosition()
    {
        m_item.SetPos(m_itemInitialPos);
    }
```

```C#:GameOverManager.hs
component GameOverManager
{
    Item m_item;
    Item m_despawnHeightItem; //デスポーンの高さ用のアイテム
    Item m_camera; //カメラ用のアイテムを取得する
    Item m_halfwayPointManager;

    int m_remain; //プレイヤーが落下できる数
    bool m_isGameOver; //ゲームオーバー画面かどうか

    public GameOverManager()
    {
        m_item = hsItemGetSelf();
        m_despawnHeightItem = hsItemGet("Magma");
        m_camera = hsItemGet("Camera");
        m_halfwayPointManager = hsItemGet("HalfwayPointManager");

        m_remain = m_item.GetProperty("remain(int)").ToInt();
    }

    public void Update()
    {
        if(! m_isGameOver)
        {
            if(m_despawnHeightItem.GetPos().y >= hsPlayerGet().GetPos().y)
            {
                //HalfwayPointManagerコンポーネントの、WarpHalfwayPointメソッドを実行する
                m_halfwayPointManager.CallComponentMethod("HalfwayPointManager", "WarpHalfwayPoint", "param");
                SetRmain(-1);
            }
        }
    }

    //残機を減らす関数
    void SetRmain(int amount)
    {
        m_remain += amount;
        CheckGameOver();
    }

    //残機が0以下になっていないか確認する関数
    void CheckGameOver()
    {
        if(m_remain <= 0)
        {
            SetGameOver();
        }
    }

    //ゲームオーバー画面に遷移する関数
    void SetGameOver()
    {
        m_isGameOver = true;

        //カメラをゲームオーバー画面のカメラに移す
        m_camera.SetCamera();
    }

    //ボックスのどこかをクリックしたらリトライを行うための関数
    public void OnClickNode(int nodeIndex)
    {
       SetRetry();
    } 

    //リトライ処理
    void SetRetry()
    {
        m_isGameOver = false;

        //リトライ回数を初期値に戻す
        m_remain = m_item.GetProperty("remain(int)").ToInt();

        //カメラをプレイヤーに戻す
        m_camera.ResetCamera(); 

        //プレイヤーをHalfwayPoint_0（初期位置に戻す）
        hsPlayerGet().SetPos(hsItemGet("HalfwayPoint_0").GetPos());

        //プレイヤーの中間地点をHalfwayPoint_0に設定する
        m_halfwayPointManager.CallComponentMethod("HalfwayPointManager", "SetHalfwayPoint", "HalfwayPoint_0");

        //ItemMoveコンポーネントのResetToStartPosition関数を実行する
        m_despawnHeightItem.CallComponentMethod("ItemMove", "ResetToStartPosition", "param");
    }
}
```
### コード解説
#### ほかのコンポーネントのメソッドを実行する。
heliscriptには、Unity C#のGetComponent()といった関数は用意されていませんが、他のコンポーネントのメソッドを実行することは出来るようになっています。

[CallComponentMethod(string ComponentName, string MethodName, string Params)](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_class_item.html#callcomponentmethod)

引数は多いですが、要するに、Actionの[CallScript(HeliScriptを呼び出す)](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/ja/Actions/Programmatic/CallScript.html)と同じような使用感で使用することが出来ます。

このメソッドを使用して、マグマに当たったらHalfwayPointManager componentについている、`WarpHalfwayPoint()`関数を実行するようにします。

また、ゲームオーバーになった後、リトライしたのちに、スタート地点に戻す関数を[HeliScriptでアイテムをまっすぐ動かす【コピペで使えるサンプルコード付き】](https://qiita.com/IwahanaFuku/private/f171b7af2a9e15dc2139)で作成した、ItemMove.hsに関数に追加して、コンストラクタで初期地点の保存を行うようにしておきます。

```C#:ItemMove.hs（追記）

	Vector3 m_itemInitialPos; //アイテムが配置された初期地点
 
    public ItemMove()
    {
        m_item = hsItemGetSelf(); //コンポーネントがセットされたItemを取得する

        m_velocity = StrToVector3(m_item.GetProperty("velocity(Vector3)")); //コンポーネントがセットされたItemの(Vector3)というプロパティを取得する
        
        //アイテムが配置された初期地点を取得しておく
		m_itemInitialPos = m_item.GetPos();
    }
    
    //位置をスタート地点にリセットする関数
    public void ResetToStartPosition()
    {
        m_item.SetPos(m_itemInitialPos);
    }
```

これによって、リトライを行おうとしたら、マグマを動かしているItemMove componentについている`ResetToStartPosition()`関数を呼び出して、初期位置に戻す関数を実行できるようになりました。

### ゲームオーバー画面の設定
```C#
    //ゲームオーバー画面に遷移する関数
    void SetGameOver()
    {
        m_isGameOver = true;

        //カメラをゲームオーバー画面のカメラに移す
        m_camera.SetCamera();
    }

    //リトライ処理
    void SetRetry()
    {
        m_isGameOver = false;

        //リトライ回数を初期値に戻す
        m_remain = m_item.GetProperty("remain(int)").ToInt();

        //カメラをプレイヤーに戻す
        m_camera.ResetCamera(); 

        //プレイヤーをHalfwayPoint_0（初期位置に戻す）
        hsPlayerGet().SetPos(hsItemGet("HalfwayPoint_0").GetPos());

        //プレイヤーの中間地点をHalfwayPoint_0に設定する
        m_halfwayPointManager.CallComponentMethod("HalfwayPointManager", "SetHalfwayPoint", "HalfwayPoint_0");

        //ItemMoveコンポーネントのResetToStartPosition関数を実行する
        m_despawnHeightItem.CallComponentMethod("ItemMove", "ResetToStartPosition", "param");
    }
```
#### ゲームオーバー画面のカメラを映す
HeliScriptには、事前に配置しておいたカメラをにプレイヤーのカメラを設定する関数が用意されています。

[組み込み関数 - カメラ](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_system_function_camera.html)

カメラに視点を移すには、
* HEO Cameraコンポーネントがついたゲームオブジェクトをシーン上に配置する
* カメラアイテムを取得する
* Item.SetCamera()を実行する

だけで、そのカメラに移すことが出来ます。

今回、ゲームオーバー画面用のボックスを事前に用意して、カメラをそのボックスだけ表示するように調整し、そこをカメラで映すことによって、ゲームオーバー画面を実装します。

ゲームオーバー画面上にあるオブジェクトをクリックすることで、初期位置に戻すようにしたいため、[OnClickNode(int NodeIndex)](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_component.html#-_4) コールバックを使用して、画面のどこをクリックしてもリトライ処理が走るようになっています。

#### ワープして最初の中間地点に戻る
中間ポイント作成時、侵入時アクションにアクションを設定しましたが、`Player.SetPos()`でエリアコライダー内に侵入すると、アクションが実行されないという特徴があります。

その場合、退出時アクションで、アクションを設定すると、退出時にアクションが呼ばれるようになるので、ここで追加の設定を行っておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/ba307570-f80b-ad30-e8e4-c983af8fdb37.png)



## ゲームクリア
ゲームクリアの演出は最後に行うとして、ひとまずはマグマを止めることを目的とします。

![2024-03-26 18-25-06_6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/4d0bbfaa-2162-9702-d903-ddd964497b66.gif)

### Unity設定画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629942/4c7641ae-65fb-e543-65f2-a7721ff75bbb.png)

```C#:ItemMove.hs（追記）
    //プロパティの変更を検知して、設定する
    public void OnChangedProperty(string key, string value)
    {
        if(key == "velocity(Vector3)")
        {
            m_velocity = StrToVector3(m_item.GetProperty("velocity(Vector3)"));
        }
    }
```

```C#:GameClearManager.hs
component GameClearManager
{
    public void OnClickNode(int nodeIndex)
    {
       GameClear();
    } 

    public void GameClear()
    {
        Item despawnHeightItem = hsItemGet(m_item.GetProperty("despawnHeight(Item)"));
        despawnHeightItem.SetProperty("velocity(Vector3)", "0,0,0");
    }
}

```
### コード解説
#### Propertyを変更する
マグマを止める方法についていろいろな実装がありますが、今回はPropertyを変更できる機能があるので、そちらを使って実装していきましょう。

[Item.SetProperty()](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_class_item.html#setproperty)は、Unity上で、Propatyの設定をしていたように、HeliScriptからPropertyの設定ができる関数です。
この関数を使用して、Propertyを更新すると、[コールバック - プロパティ](https://vrhikky.github.io/VketCloudSDK_Documents/12.3/hs/hs_component.html#-_14)が呼ばれます。

ItemMove.hsではコンストラクタでPropertyの取得を行っているため、変更したPropertyを更新するために、このコールバックを使用します。

これで、アスレチックに必要な機能な最低限の機能は実装完了しました！　次回は、作ったcomponentを使用して、一通りワールドが遊べるようにします。

 **前 :** [HeliScriptでアイテムを反復して動かす【コピペで使えるサンプルコード付き】](https://qiita.com/IwahanaFuku/private/dfc38371ad0ed7cc034d) 
 **次 :** [ワールドを一通り遊べるようにする](https://qiita.com/IwahanaFuku/private/f45059a11c83a367cb6c)