Unityのビジュアルスクリプティングで作るシンプルな横スクロールゲーム


# はじめに
この記事ではUnity 2021から標準パッケージになった**Visual Scripting**で、Chromeの恐竜（chrome://dino/）ライクなシンプルな横スクロールゲームを作成する方法を解説します。
![恐竜ゲーム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/0411ea91-3dd7-ba4b-68bb-ec16af110065.png)
この記事の対象としては、以下のような方を想定しています。

- Unityを少しでも触ったことがある方
- Visual Scriptingに興味がある方

# 環境
Unity 2021.2.6f1

# Visual Scriptingとは

Visual Scriptingとは、コーディングの代わりにグラフを作成してプログラムを記述可能なシステムです。
![Visual Scripting](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/5cada60a-b634-e0e9-3ec1-7acfe9888fd7.png)

これには以下のような利点があります

- 「なんとなく」で使えるため非エンジニアでも触りやすい
- C#で記述したプログラムと共存可能
- Play中にも処理を変更できるためプロトタイプ作成などにおいて素早くトライアンドエラーをまわせる
- 実行中の処理が可視化されるため、簡単な処理ならデバッグがしやすい

（↓実行中のグラフで、処理の流れが可視化されている&線上にそのフレームの値が表示されている）
![Visual Scripting](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/fc815a59-15dc-bf75-b311-3be796c5b853.gif)

この機能はUnity 2020まではBoltと呼称されていたパッケージでしたが、Unity2021から名前を変え標準パッケージとなりました。
そのため、Unity2021をインストールするだけで利用が可能です。

# 作るもの
今回の記事では、Visual Scriptingを使用して↓のような簡略版恐竜ゲームを作ります。
（白丸が恐竜、黄色長方形がサボテンだと読み替えてください。）
![作るもの.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/4a2bb888-122d-0d4e-716b-c001b48f2d75.gif)

## 必要な機能
これを作る上で、まず最低限必要な機能を書き出してみます。

1. 障害物が左に動く
1. ボタン押下に応じてプレイヤーがジャンプ
1. 数秒おきに障害物を生成
1. スコア表示
1. 死亡処理（スコアなどの停止）

なんとなく似たゲームができそうですね。
ただ、全て実装すると記事が長くなりすぎるので、今回は基礎として1と2を作るところを目標にします。
では、作っていきましょう。

# 実践
## プロジェクトの作成

まずはプロジェクトの作成です。

1. Unity Hubの新規作成から`Unity 2021.2.6f1`を選択
1. テンプレートに`2D`を選択して`作成`を選択

![プロジェクト作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/fb96ab9c-6136-29ea-17d5-0de9da182830.png)

## GameObjectの配置

ここで、先にGameObjectをある程度まとめて配置してしまいましょう。
今回のゲームの登場人物はプレイヤー、床、障害物の3つです。
今回は~~面倒なので~~工数削減のため、プレイヤーは白い丸、床は白い長方形、障害物は黄色の長方形で代用します。

### プレイヤーの配置
1. Hierarchyで右クリック -> 2D Object -> Sprites -> Circle をクリック
1. GameObjectの名前をPlayerに変更
1. Add ComponentでCircle Collider 2Dを選択
1. Add ComponentでRigid Body 2Dを選択

### 床の配置
1. Hierarchyで右クリック -> 2D Object -> Sprites -> Square をクリック
1. GameObjectの名前をFloorに変更
1. Add ComponentでBox Collider 2Dを選択

### 障害物の配置
1. Hierarchyで右クリック -> 2D Object -> Sprites -> Square をクリック
1. GameObjectの名前をTreeに変更
1. Add ComponentでBox Collider 2Dを選択
1. Spriteの色を黄色に変更（任意）

以上が出来たら、大体で良いので以下のように配置します。
![GameObjectの配置](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/2dc700c7-a617-2bcd-cc70-1577169d695b.png)

## 障害物の動きの作成
今回のゲームでは右から左に障害物が線形に移動していきます。
この動きをVisual Scriptingを用いて作成してみます。

### コンポーネントのアタッチ
まず、TreeにAdd ComponentでVisual Scripting->Script Machineを選択してください。
（Boltを使ったことがある方はとりあえずScript Machine = Flow Machineだと思って大丈夫です。）
![Add Graph Component](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/782d9af3-32b4-4868-8a39-c36d5a902109.png)
すると以下のような2つのコンポーネントがアタッチされます。
![Script Machine Component](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/eaf9ae51-d39b-b639-6ca2-361ba9ce116b.png)
ここで、Script MachineコンポーネントのSourceという項の選択により、Script Machineコンポーネントは`Graph`と`Embed`どちらかのモードを選択できます。
それぞれの違いは以下の通りです。

- Graph : グラフをファイルとして保存する。再利用可。
- Embed : グラフがコンポーネントに埋め込まれる。（基本的には）再利用不可。コンポーネントをRemoveするとグラフも消える。

基本的にはGraphを選択して一つ一つ保存した方が良いと思われますが、今回は説明の簡略化のためEmbedを使用します。
SourceにEmbedを選択するとTitleを付けられるようになるので、今回の機能として`TreeMovement`と入力してください。
![Embed Component](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/e77e115d-ada5-e96d-165d-6d51ad067526.png)
ちなみに、Convertを選択することでEmbedで作成したグラフを後からファイルに書き出すことも可能です。
（詳しくは[こちら](https://docs.unity3d.com/Packages/com.unity.visualscripting@1.5/manual/vs-graphs-machines-macros.html)を参照してください）

### グラフの作成
次に、Script MachineコンポーネントのEdit GraphをクリックしてScript Graphウィンドウを開きましょう。
![Script Graphウィンドウ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/fca006cf-4a38-59bc-1c0b-479e7b61dc0f.png)
最初に開いた時はウィンドウが小さく肝心のグラフが見えない場合があるので、その時は横幅を広げましょう。
このウィンドウの見方をざっくり解説すると以下のような感じです。

- 左上 ： Graph Inspector
    - 選択したグラフの詳細を表示
- 左下 ： Blackboard
    - 変数の管理
- 右 ： グラフエディタ
    - 実際に処理を記述する場所

最初は、基本右側を使うんだなくらいの認識で大丈夫です。

### ノードの配置
では、実際にグラフを作成していきましょう。
まず、グラフ上で右クリックをしましょう。すると、ノードの作成ポップアップが開くと思います。
ここでいうノードとは、処理のひとかたまりだと考えて下さい。（C#でいう関数のようなもの）
↓ ノードの一例： Rigidbody.AddForce()
![Add Force](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/fb2cbfb7-1a9a-742c-7f9b-de48fd25d4cf.png)

今回は左に移動してほしい、つまり障害物のポジションを移動させたいので、`translate`と入力し、`Transform: Translate(X,Y,Z)`を選択してください。(C#でいう`transform.Translate()`と考えて大丈夫です)
![translate](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/28b24c07-d069-37b3-6dd4-6616eebb79d6.png)
すると以下のようになると思います。

![no edge](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/103aed5b-9e1e-82c4-a78d-e218b6904bd2.png)

<!--
次に、`Vector 3: Create Vector3(X,Y)`、`Float Literal`を選択してください
![vector3 create](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/1528039d-ada0-9552-1a0e-b3ac5eff53ee.png)
![float literal](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/8baa0bc4-db1c-fbf1-13b8-587ab15a0622.png)
すると以下のようになると思います。
![create node](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/96c40226-9422-5b84-0f5b-8bdcc0d0b783.png)
-->

この状態ではノードが何にもつながっていないため、処理が走りません。
次はこのノードを線でつないで処理を記述していきます。

### 処理の記述
ここでやりたいことは右から左へ位置を移動させる、つまり「毎フレーム -x 方向にポジションを移動させる」ことです。
毎フレーム行う処理を記述する場合、`On Update`イベントを用います。（C#でいうUpdate()です。）
今回は、`On Update`イベントから`Translate`ノードを繋ぎます。

こうすることで毎フレーム`Translate`ノードの処理が呼ばれることになります。
また、左方向に移動させたいので`X`にマイナスの数値を入力しましょう。
![graph](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/c4cbef20-3bfc-29ba-7c6e-12bfe3f7ce43.png)
この状態でPlayすれば、Treeというオブジェクトがひとまず左に動いてくれるはずです。

（上記の状態だとフレームレートによってTreeが動く速度が変わってしまうため、最終的には以下のようにdeltaTimeを乗算しましょう。）
![multiply delta time](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/f71ca778-9505-6f4c-da72-bbdac5faafe9.png)

## プレイヤーのジャンプ
障害物の動きが出来たところで、キーの押下に応じてプレイヤーがジャンプする動きを実装します。
ここで、プレイヤーは**接地状態**、**ジャンプ状態**という2つの状態を行き来する、とも考えられます。
それぞれの状態は以下の通りです

- 接地状態 : キーを押下でジャンプ状態へ移行する。
- ジャンプ中 : この状態に入った瞬間上方向に飛び上がる。床に触れた時接地状態へ移行する。

この状態遷移を簡単に実現できる機能が**State Machine**です。
今回はこのState Machineを用いてジャンプを実装します。

### コンポーネントのアタッチ
ではさっそくState Machineを作成しましょう。
Playerオブジェクトを選択して、Add ComponentからState Machineを選択してください。
ここでもSourceにGraphかEmbedを選べますが、今回は例によってEmbedを選択し、TitleにJumpableと入力してください。
![state machine component](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/d4780d34-c834-db4b-fbe1-0454399d58c0.png)

### State Machineについて
ここでEdit Graphを押すと以下のような画面が出るはずです。
![state machine](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/db02c08e-c3b1-823c-9349-eea0d87d4d54.png)
画面構成は基本Script Machineと同じですが、右側が**状態遷移図**となっています。
この状態遷移図の"状態"とは、Script Machineのことだと考えてもらって大丈夫です。
ここで、中央の`Start`をクリックすると先程のScript Machineの編集画面と似たものが出てきたのがわかると思います（下図）。
![スクリーンショット 2021-12-14 1.21.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/6f0b38be-1f71-614d-94fa-777029a96509.png)

### 状態の作成
それでは、状態を作っていきましょう。
まず、右クリック->`Create Script State` を選択して、Titleに`Jumping`と入力します。
また、初期状態であるStartの名前を変更して`Grounded`とします。
![スクリーンショット 2021-12-14 1.26.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/82be21d5-1d21-3d82-154d-0cfeb9bccd64.png)
これで、ジャンプ中の状態`Jumping`と接地中の状態`Grounded`が作成できました。

### 遷移の作成
次に、遷移を作成していきます。
`Grounded`を右クリック->`Make Transition`で矢印を`Jumping`につなげます。
同様に`Jumping`から`Grounded`へも矢印をつなげます。
![transition](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/b8e610e1-779e-1b02-0a5d-2ac693427ff7.png)
しかし、線をつないだだけでは”なにがあったら状態が変わるか”が記述できていません。
次は、この遷移条件を記述します。

### 遷移条件
まず、`Grounded`から`Jumping`への遷移を作成しましょう。
今回は`Grounded`から`Jumping`への遷移条件は、「何かのキーが押された」こととします。
`Grounded`から`Jumping`への遷移の間にある黄色の6角形をダブルクリックしましょう。
すると以下のような画面が出るはずです。
![スクリーンショット 2021-12-14 1.36.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/75954793-c978-df0e-743e-736e7509cfee.png)

<!-- ここでは、`State Trigger Transition`ノード（イベント）が発火したら状態が遷移する -->

次に`On Update`ノード、`Input Get Any Key`ノード、`If`ノードを作成し、以下のようにつないでください。
![grounded to jumping](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/4754df6e-1036-5007-6318-d1c9f6e3a0f3.png)
これで、「`Grounded`状態で何かのキーが押されていたら`Jumping`状態へ遷移する」という処理になります。


次は、`Jumping`から`Grounded`への遷移を作成しましょう。
今回は`Jumping`から`Grounded`への遷移条件は、「Floorという名前のオブジェクトと接触した」こととします。
上と同様に今度は`Jumping`から`Grounded`の遷移の間にある黄色の6角形をダブルクリックし、以下のようなグラフを作成してください。
![jumping to grounded](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/1ee0f819-53ee-c283-bbc4-cbfaeed6b2ae.png)
これで、「`Jumping`状態でFloorという名前のオブジェクトと接触したら`Grounded`状態へ遷移する」という処理になります。


### ジャンプ処理
遷移も記述できたところで、肝心のジャンプ処理を記述していきましょう。
ジャンプ処理は、今回「`Jumping`状態に入った瞬間、上方向に力をかける」ことで実現します。
では、早速`Jumping`をダブルクリックして以下のように処理を記述しましょう。
ここで、`On Enter State`は「この状態に入った直後に発火するイベント」、`On Exit State`は「この状態から出る瞬間に発火するイベント」です。
![スクリーンショット 2021-12-14 2.05.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/c390dd81-a12c-31a0-0681-7f1ec1a452fd.png)
なお、このままでは上に飛ぶ力が強すぎるかもしれません。
その場合、Add ForceノードのForce値や、Rigidbody2DのGravity Scaleを調整しましょう。
（記事の上の方で貼った動作例ではRigidBody2DのGravity Scaleを10にしています。）

これで、プレイヤーのジャンプは実現できました。
今回の例ではState Machineをそれほど活用できていませんが、最終的には各状態でAnimationの管理を行ったり、状態を増やしてより多彩な処理を行ったりということが可能です。

# まとめ
今回はVisual Scriptingを用いたシンプルな横スクロールゲームの制作方法を紹介しました。
Visual Scriptingを用いてノードをつないだグラフを作成することで、わかりやすく簡単に、障害物を動かしたりプレイヤーをジャンプさせたりできました。

Visual Scriptingでは、処理をグラフィカルに見ることができるため非エンジニアでもわかりやすい点や、素早くトライアンドエラーがまわしやすい点などがメリットとして挙げられます。
また、今回紹介したState Machineはゲーム制作の上で色々な応用が考えられる強力な機能だと思います。

今回使用していない機能（カスタムイベント・サブグラフなど）も沢山あるので、興味があれば調べてみて下さい。
「Visual Scripting使ったことないけどどういうものか気になる！」と思っている方がこの記事を読むことで「Visual Scriptingなんとなくわかったぜ！」となっていれば幸いです。

# 最後に
この記事は サムザップ [Advent Calendar 2021](https://qiita.com/advent-calendar/2021/sumzap) の12/16の記事です。
昨日の記事は @ozaki_shinya さんによる[Unityでシーン遷移時にパラメーターを渡す方法](https://qiita.com/ozaki_shinya/items/0834a635d32585839c23)でした。
明日は@higuchi_yutaさんの「【Unity】ATBシステムにおける新ルールの実装コストを削減した設計」です。