Amazon CodeWhisperer を使って Unity で「スイカゲーム」風ゲーム制作 RTA

この記事は [AWS for Games Advent Calendar 2023](https://qiita.com/advent-calendar/2023/aws-for-games) の 6 日目の記事です。

# プロローグ
「AWS for Games アドベントカレンダー 6 日目の記事を書くつもりでいたのに、気づいたら 12/5 の午後！何か簡単なプロダクトを作れたら嬉しいけど、Unity なんかは普段触っていないからこんな短時間で開発できるわけがない！」
皆さんも、1 度や 2 度はこんなことがあるんじゃないでしょうか。そんなときのプロダクト制作 RTA に役立つのが、[Amazon CodeWhisperer](https://aws.amazon.com/jp/codewhisperer/) です。

# この記事を読むと何が分かるの？
この記事では、リアルタイムでコードの提案をしてくれる AWS のサービス「Amazon CodeWhisperer」を Unity で動かしてみます。「スイカゲーム」の基本ロジックを実装しながら、Amazon CodeWhisperer のコード生成がどんなものか確認しようという目論見です。Unity での開発を高速化したい方は、是非ゆっくりしていってください。

この記事に書かれているのは下記です。
- Amazon CodeWhisperer の概要
- Amazon CodeWhisperer の Visual Studio 2022 での始め方
- Amazon CodeWhisperer が Unity のクライアントサイド開発にどれほど使えるのか

一方で、下記のような方はこの記事の対象外です。ごめんなさい。
- Unity の見識を深めたい！→ この記事では扱いません。しかし、Amazon CodeWhisperer は、Unity 初学者の方が爆速コーディングするためのお手伝いに役立つと思います。
- スイカゲームを厳密に実装したい！→ 他の記事を当たってください……。
- Visual Studio for Mac を使ってます。→ 現状 Visual Studio for Mac は Amazon CodeWhisperer が対応しておりません！Visual Studio Code なら使えます！

# はじめに
皆さんは「スイカゲーム」をご存知でしょうか？スイカゲームは 2023 年に流行した、果物を落として合体させるパズルゲームです。スイカゲームに関する説明は調べたらたくさん出てくるので、この記事では割愛します。
さらに、「スイカゲーム風のゲームを自分で作ってみよう」という記事も調べたら複数出てきます。個人でも作れる程度のシンプルなロジックでありながら、多くの人々を魅了してやまないのがこのスイカゲームなのです。

この記事では、そんな「スイカゲーム」風のロジックを Unity で実装してみます。もちろん Unity でのスイカゲーム風ロジックの実装方法も世の中に出回っているのですが、今回は Amazon CodeWhisperer によるリアルタイムコード生成を活用しながら実装していきます。次の章では、Amazon CodeWhisperer がどんなサービスなのかご紹介していきます。

# Amazon CodeWhisperer とは

Amazon CodeWhisperer は、リアルタイムコード生成をサポートしてくれるサービスです。例えば、「操作 A を始める関数」とコメントで書くと、Amazon CodeWhisperer が操作 A をする関数を書いてくれます。場合によっては、「ついでにこんな関数もほしいよね？」と言わんばかりに、「操作 A を終える関数」も同時に生成してくれることもあります。実際の使用感は本記事の後半をご覧ください。
あまり書き慣れていない言語やロジック・API などを扱うときには書き方を調べながら書くと思いますが、そんなときに調べなくても自動でコードを生成してくれるのが Amazon CodeWhisperer です。

現状 Amazon CodeWhisperer は、Unity で使われる C# を含め、複数の言語に対応しています。また、Visual Studio Code など複数の IDE をサポートしています。今回コーディングに用いる Visual Studio 2022 も、現在プレビューとして利用できます。プレビューのため今後内容が変わる可能性はありますが、現在 Visual Studio 2022 では C# のみが Amazon CodeWhisperer を利用できる言語となっています。
なお、注意点として、Amazon CodeWhisperer が公式に対応している自然言語は英語のみです。実は日本語で書いてもある程度動くのですが、英語でコメントを書いた方が精度は高いと思われます。[公式ページ](https://aws.amazon.com/jp/codewhisperer/faqs/)には、「トレーニングデータセットにはさまざまな例が含まれているため、CodeWhisperer は、英語以外の言語で記述されたコメントからコードの提案を提供できる可能性がありますが、これはサポートされていないユースケースです。」と書かれています。したがって、本記事中でもコメントは英語に統一します。

# 環境設定
本記事では下記環境を利用します。
- Unity 2022.3.14f1
- Visual Studio 2022

Visual Studio for Mac は現在 Amazon CodeWhisperer が対応していません。また、Visual Studio 2019 など、2022 以外のバージョンの Visual Studio も Amazon CodeWhisperer の対象外です。

そして、Visual Studio 2022 に Amazon CodeWhisperer を導入します。導入方法は[こちらの公式ページ](https://docs.aws.amazon.com/ja_jp/codewhisperer/latest/userguide/visual-studio-getting-started.html)を参考にしてください。10 分もかからずに導入できます。手順中では IAM Identity Center か AWS Builder ID かの選択が求められますが、手っ取り早く始める場合は AWS Builder ID を作成しておけば大丈夫です。この場合料金はかかりません。

なお、前回利用から一定時間があくと、Visual Studio 内で AWS Builder ID でのサインインが求められます。このサインインも 1 分ほどで終わります。画面の上部の Sign in をクリックして確認するだけです。
![スクリーンショット 2023-12-06 004425.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/1027ccfd-6651-0095-a763-bb3affd97960.png)



# ゲーム作成の前準備
それではいよいよ、「スイカゲーム」風のゲームを作っていきましょう。ここからは、スイカゲームがどんなものかある程度知っている前提で話を進めます。

まずは適当にオブジェクトを生成します。最低限必要なのは、周りの枠と果物ですね。残念ながら、GUI で設定する部分において Amazon CodeWhisperer ができることはありません。おとなしく作ってしまいましょう。

周りの枠は Square Sprites を組み合わせて作り、BoxCollider2D をアタッチしています。果物は Circle Sprites を 5 種類の大きさで作り、CircleCollider2D と Rigidbody2D をアタッチしています。同じ果物がぶつかると 1 つ大きいサイズの果物になり、最大の果物がぶつかると消滅するというロジックです。完成イメージは下図です。枠の中に果物に見立てた円を落としていきます。

![スクリーンショット 2023-12-06 004543.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/f7cf0d86-d4b7-dc9c-8b79-438c5df89692.png)

果物にアタッチするスクリプト `fruit.cs` と、ゲーム全体を管理するスクリプト `GameManager.cs` を導入します。シーン内に空の GameManager オブジェクトを配置して、そこに `GameManager.cs` をアタッチします。シーンは下図のようになっています。

![スクリーンショット 2023-12-05 211039.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/eb55cf47-ff04-e29a-5c14-409fb3c2ad4a.png)


# いよいよコーディング
ここからはコードを書いていきます。下記のロジックを順に実装していきます。ゲームの核となるロジックの実装にとどめますので、ゲームオーバーや BGM・効果音の実装は今回は含みません。

- 果物を落とす
- 新たな果物を発生させる
- ぶつかった果物を合体・消滅させる
- 点数を計算する

## 果物を落とす
まずは、左右矢印キーで果物を落とす位置を決め、スペースキーで果物を落とす実装をします。下図は実装中の様子を示した画像です。
![スクリーンショット (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/e9680c15-25fc-19ee-8848-0697a512f0a2.png)
薄い文字で書かれているところが、Amazon CodeWhisperer の生成したコード候補です。「右矢印キーを押した場合は右に移動する」という内容から、オブジェクトを移動するコードを生成しているわけです。この状態で Tab キーを押せば、候補が適用されます。

そして、最終的に果物を落とす実装を書きあげたのが下図です。深緑の下線部が自力で書いた箇所、マゼンタの下線部が Amazon CodeWhisperer により生成された箇所です。ただし、`3.0f * Time.deltaTime` や `4.2f` といったコード中の具体的な数値は、後から手動で書き換えています。
![スクリーンショット 2023-12-05 215511.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/13134ffe-5d25-5139-b4d3-3b5f8ad54ef8.png)
見ると分かる通り、コメントや行の途中までの内容をベースに、続きが生成されています。スペースキーを押した場合の挙動の実装部分を見ると、コメントの後半から実際の挙動まですべて Amazon CodeWhisperer が書いてくれています。

ちなみに、コードの前後の文脈によって、Amazon CodeWhisperer が生成するコードは大きく変わります。試しに、`Update()` 関数以外はデフォルトのままで、キー入力の挙動のみをコメントで書くと、あっという間に下図のような場合分けと移動のコードを次々と出力してくれます。
![スクリーンショット (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/6fdb2fa9-cca7-2ce3-8be4-305b6849fb36.png)
![スクリーンショット (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/c5397836-38b5-44b5-30a5-41b59daff11d.png)

ここまでで、果物を左右に移動して好きな場所で落とすことができるようになりました。
![スクリーンショット 2023-12-06 005404.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/7eebedba-08e8-b6d2-81f5-392fcfc4a610.png)
![スクリーンショット 2023-12-06 005412.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/5010c0df-2610-bddb-e017-e9d8ab2cb4c1.png)


## 新たな果物を発生させる
続いて、果物を落としたあとは新たな果物を発生させてみましょう。この操作は `GameManager.cs` で実装します。とりあえず、下記画像のように必要な変数の実装をします。`isEmpty` は、落とすべき果物を持っていない状態のときに `true` になる変数です。
![スクリーンショット 2023-12-05 221415.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/dd6053ca-e4b8-9866-8537-6d976ccf6fc4.png)
そして、新しい果物を作る関数を定義すると、Amazon CodeWhisperer は、関数名から類推してランダムに果物を生成するコードを作成します。
![スクリーンショット (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/9b51500a-18b0-f951-243d-890ba797937e.png)

この `CreateNewFruit()` 関数を呼び出す `Update()` 関数も、コメントを書いたらそれ以降は Amazon CodeWhisperer が生成してくれました。
![スクリーンショット 2023-12-05 222455.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/c6fb4314-385a-dde1-050e-7cf07c3f93cc.png)

このあと、`Fruit.cs` と `GameManager.cs` を少しずつ変更しました。この時点でのコードを記載しておきます。
```csharp:GameManager.cs
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    [SerializeField] private GameObject fruit1;
    [SerializeField] private GameObject fruit2;
    [SerializeField] private GameObject fruit3;
    [SerializeField] private GameObject fruit4;
    [SerializeField] private GameObject fruit5;

    public bool isEmpty;


    // Start is called before the first frame update
    void Start()
    {
        isEmpty = true;
    }

    // Update is called once per frame
    void Update()
    {
        // If isEmpty is true, create a new fruit after 1 second.
        if (isEmpty)
        {
            Invoke("CreateNewFruit", 1.0f);
            isEmpty = false;
        }
        
    }

    void CreateNewFruit()
    {
        int random = Random.Range(1, 6);
        switch (random)
        {
            case 1:
                Instantiate(fruit1, new Vector3(0, 7.0f, 0), Quaternion.identity);
                break;
            case 2:
                Instantiate(fruit2, new Vector3(0, 7.0f, 0), Quaternion.identity);
                break;
            case 3:
                Instantiate(fruit3, new Vector3(0, 7.0f, 0), Quaternion.identity);
                break;
            case 4:
                Instantiate(fruit4, new Vector3(0, 7.0f, 0), Quaternion.identity);
                break;
            case 5:
                Instantiate(fruit5, new Vector3(0, 7.0f, 0), Quaternion.identity);
                break;
        }
    }
}

```
```csharp:Fruit.cs
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading;
using UnityEngine;

public class Fruit : MonoBehaviour
{
    private Rigidbody2D rb;
    private bool dropped = false;
    private GameObject gameManager;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        gameManager = GameObject.Find("GameManager");
    }

    // Update is called once per frame
    void Update()
    {
        if (!dropped)
        {
            // During the right arrow key is pressed, move the fruit to the right.
            if (Input.GetKey(KeyCode.RightArrow))
            {
                if (transform.position.x < 4.2f)
                {
                    transform.position += new Vector3(3.0f * Time.deltaTime, 0, 0);
                }
            }
            else if (Input.GetKey(KeyCode.LeftArrow))
            {
                if (transform.position.x > -4.2f)
                {
                    transform.position += new Vector3(-3.0f * Time.deltaTime, 0, 0);
                }
            }
            // If the space bar is pressed, drop the fruit.
            if (Input.GetKeyDown(KeyCode.Space))
            {
                dropped = true;
                rb.gravityScale = 1;
                gameManager.GetComponent<GameManager>().isEmpty = true;
            }
        }
    }
}

```
ここまでで、次々と果物が出現するようになります。
![スクリーンショット 2023-12-06 005430.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/7a483624-19df-ffaf-f479-1a3237b8dfc8.png)


## ぶつかった果物を合体・消滅させる
続いて、果物の合体・消滅を実装します。同じ種類の果物が合体したらより大きな果物が生成されます。生成位置は既存の 2 つの果物の中間地点とします。ただし、最も大きい果物どうしが合体した場合は、新しい果物は生成せず単に 2 つの果物が消滅します。下記に `OnCollisionEnter2D` の内容を記します。
この関数は、逐次コメントを書きながら Amazon CodeWhisperer でコードを生成し、自分で少し書き換えるのを繰り返しました。ところどころ挟まっているコメントが自身で書いたものです。行を途中まで書くと Amazon CodeWhisperer が残りを生成してくれる場合も多くあります。汎用的なロジックでもないのですべてまとめて生成というわけにはいきませんが、書き方のヒントを次々と生成してくれるだけでも作業のお供として強力です。

```csharp:Fruit.cs
void OnCollisionEnter2D(Collision2D col)
{ 
    // get the opponent of collision
    GameObject opponent = col.gameObject;
    if (opponent != null)
    {
        if (opponent.tag == "Fruit" && n == opponent.GetComponent<Fruit>().n && !this.collided && !opponent.GetComponent<Fruit>().collided)
        {
            collided = true;
            opponent.GetComponent<Fruit>().collided = true;

            // get the positions of itself and opponent
            Vector3 pos = this.transform.position;
            Vector3 posopp = opponent.transform.position;
            // get the midpoint of pos and posopp
            Vector3 mid = (pos + posopp) / 2;

            Destroy(this.gameObject);
            Destroy(opponent);

            // instantiate a new fruit at midpoint
            if (nextFruit != null) {
                GameObject f = Instantiate(nextFruit, mid, Quaternion.identity);
                f.GetComponent<Fruit>().dropped = true;
                f.GetComponent<Fruit>().GetComponent<Rigidbody2D>().gravityScale = 1;
            }

        }
    } 
}
```

## 点数を計算する
ここは正直おまけです。合体・消滅のたびに 1 点ずつ追加します。テキストを画面に追加して `GameManager.cs` に点数を更新するロジックを追加します。下に、変数と関数を追加した箇所のみ記しておきます。この `updatePoint()` 関数は、`Fruit.cs` の衝突処理で呼んでいます。
```csharp:GameManager.cs

[SerializeField] private TextMeshProUGUI text;

private int point = 0;

// 中略

public void updatePoint()
{
    point += 1;
    text.text = point.ToString();
}
```
些細な追加ですが、ここでも Amazon CodeWhisperer はちゃんと役に立っています。
![スクリーンショット 2023-12-06 000602.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/64841de8-8d01-4920-90ac-4cd9b6599998.png)

そういうわけで、ここまでで今回作るゲームロジックは完成です！
![スクリーンショット 2023-12-06 000905.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311582/c0385e5d-4776-474e-e503-433a0fecc696.png)

# まとめと所感
本記事では、Amazon CodeWhiserer を利用して「スイカゲーム」風のゲームを作ってみました。この記事を通して、Amazon CodeWhisperer のリアルタイムコード生成は Unity のゲームロジックでも利用できることが分かりました。汎用的で基本的なコードはまとめて生成されるので、Unity を普段から触っているわけではない人間にとって非常に有益でした。そこまで汎用的でないコードにおいても、コメントからコードを生成したり行の後半を書いたりといったかたちで、細かく分割した作業の高速化に大きく寄与しました。実際に、「ここってどうやって書くんだっけ」と調べる時間はほとんどありませんでした。今回は「スイカゲーム」を通してそこまで複雑でないコーディングにおける Amazon CodeWhisperer の恩恵を確認しましたが、より高度な開発においても、同様に作業の細かい部分で役立つと考えられます。この記事で気になった方は、是非一度 Amazon CodeWhisperer を試してみてはいかがでしょうか。