>& STDOUT

主にソフトウェアに関する日々の標準出力+標準エラー出力

Unityで一通りのゲームループを作るのに学んだことメモ

Unityですごく簡易な3Dシューティングを作ってみました。以下のような要素を持っています。

youtu.be

  • タイトル画面、ゲーム画面(インゲーム)、ゲームオーバー(クリア)画面がある
  • プレーヤーはX軸移動と、スペースキーでショットが打てる
  • 画面にはボスキャラと隕石があり、ボスキャラは自機にランダム周期で攻撃してくる
  • 画面内のすべてのオブジェクトを倒すとクリア
  • スコアシステムがある
  • ゲームオーバー(クリア)画面ではスコアが表示され、再挑戦か、終了がキーで選べる

Unityにほぼ触ったことがない状態から、以下のようなことを学びつつ作りました。いったんここまで学んだことをセーブします。

学んだこと

エディタの使い方

何気に3D空間で自由に(見たいように)視点を切り替えて、オブジェクトを操作、編集する部分をしっかりやったのが以降のすべての作業の効率化に繋がったと思います。インスペクタやPrefabの使い方、アタッチやコンポーネントの追加など、基本的な部分は動画で学べました。

キーで自機を動かす

自機のオブジェクト(最初はCube、後でアセットに差し替え)を作成し、Playerスクリプト(Player.cs)を作成してアタッチ。updateで、キー入力を Input.GetKey(KeyCode.RightArrow) で捕まえて transform.position += Vector3.right * 0.02f;で動かす。動かすついでに、transform.rotation = Quaternion.Euler(0, 0, -10);で傾ける。キー入力を捕まえるのはupdateでよいけれど、動かしたり傾けたりするのは、fixedUpdateのほうががよさそう。

自機から弾を撃つ

同じく、updateで、 Instantiate(tama, transform.position + Vector3.forward * 2f, Quaternion.identity); などとする。tama はPrefabで予め用意しておく。 Vector3.forward * 2f で *2f しているのは、自機の場所から弾を出すと自機とのコリジョン判定で弾が消えてしまうから。弾側には、生成されたら一定速度で奥に飛んでいき、何かとぶつかったら自分(自機弾)をDestroyする、という処理が書いてあります。

隕石(お邪魔キャラ)を配置して動かす

自機と同様にオブジェクトとスクリプトを用意した後、Updateで、transform.position = new Vector3(pos.x + Mathf.Sin(Time.time) * moveWidth, pos.y, pos.z); などとして、positionをVector3型のpos.xに正弦波の時間軸を取って、それに幅を掛けて動作させる。Vector3オブジェクトはupdateを抜けると破棄されるので、毎回newしてても大丈夫。

自機の弾にはタグが打ってあり、自機の弾に触れた時のみ、Destoryします(ボスの弾で消えてほしくないので)。また、自分が破壊されたときは、ゲームの状態を管理しているオブジェクト(GameMaster と名付けた空のオブジェクト)に、+10点されたよと、自己申告します。

ボスを設定する

隕石をベースに、さまざまなフィーチャーが追加されています。左右に移動する部分は、隕石と同じです。加えて、ボス自身が自機側(手前)に向けて、不定の間隔で弾を撃ってきます。

弾の発射については、不定の間隔で行いたかったので、その間隔の算出と弾の発射をコルーチン側(非同期処理)で行っています。これに関しては、updateで Random.Range(0.05f, 1.7f); の間射出を休ませる、という書き方でもよかったかもです。

    private IEnumerator bossTamaShot()
    {
        while(true) {
            float shortDuration = Random.Range(0.05f, 1.7f);
            yield return new WaitForSecondsRealtime(shortDuration);
            Instantiate(enemyTama, transform.position + Vector3.back * 10f, Quaternion.identity);
        }
    }

また、1発で破壊される隕石と異なり、ボスにはヒットポイントが設定されています。自機弾がヒットするたびに減っていき、0になったら爆発のエフェクトと一緒にDestoryされ、GameMasterに得点を自己申告します。シューティングゲームでは、1発で倒せない敵については、自機弾が確かにヒットしていることを何らかの形で示す必要があり、本作ではよくある「ヒットしたときに一瞬全体が赤くなる」という処理を、これもコルーチンで実装しています。ベースのマテリアルを取っておいたあと、赤のマテリアルに差し替えて、0.08秒待って元のマテリアルに戻します。

    private IEnumerator flashColorRedOnCollisionEnter()
    {
        Color baseColor = GetComponent<Renderer>().material.color;
        GetComponent<Renderer>().material.color = Color.red;
        yield return new WaitForSecondsRealtime(0.08f);
        GetComponent<Renderer>().material.color = baseColor;
    }

最近のシューティングゲームではボスの体力ゲージが表示されていることが多いです。今回は Slider 画面上部に配置して、ボスのヒットポイントと連動する形で実装しました。Start で slider.maxValue = hitPoint;として初期化して、Playerの弾が当たったときにslider.valueを現在のヒットポイントに書き換える形で実装しています。

各種効果音を鳴らす

3D空間でオブジェクトの場所で音を鳴らすだけなら割と簡単でした。効果音を発生させたいオブジェクトにアタッチしているスクリプトpublic AudioClip playerShotSe; としてインスペクタで鳴らしたい音を指定。あとは鳴らしたいタイミングで、AudioSource.PlayClipAtPoint(playerShotSe, transform.position); とするだけです。ただ、この方法だとボスのように奥にいるキャラの爆発音が小さく聞こえてしまう問題が発生しました。そこで、transform.position + Vector3.back * 40f として、ボスの破壊音だけはだいぶ手前で鳴るようにしてみました。物理的には不自然ですが、ゲーム的には自然になったと思います。

ゲームの状態をコントロールする

これまで何度か出てきましたが、ゲームのメイン画面には、GameMasterという不可視の空オブジェクトがいて、この人がインゲームの状態を管理しています。例えば、ボスと隕石がすべて無くなればゲームクリア画面に遷移、プレーヤーが被弾したらゲームオーバー画面に遷移する、スコアを集計し、スコア表示のオブジェクトに渡したりといった役割を担っています。

うっかり?GameMasterという名前にしてしまいましたが、Unityのお作法的には GameManager という名前が適切なようです。この名前にすると、アイコンが自動的に歯車になります。

シーンをまたいでゲームのデータを受け渡す

ScriptableObject という仕組みを利用しました。ゲームクリア画面とゲームオーバー画面はシーン自体は同じで、GameMasterがどちらかの画面に遷移させるときにテキストだけ変えています。また、いずれの画面でもスコアが表示されるため、スコア情報もGameMasterが常にScriptableObjectに保存しています。

余談ですが、スコアのデータをCanvas以下のTMPに代入しようとして、そのTMPオブジェクトが取れておらずNullReferenceになるというエラーを、ScriptableObjectが適切に受け渡されていないエラーと勘違いして、結構な時間ハマりました。

ゲームオーバー(クリア)画面で、再挑戦か終了かをUIで選ぶ

上下のキー操作を取得し、buttons[currentButtonIndex].Select();を発火します。onClickに、シーンの再ロードや、アプリケーションのQuitを行う関数が設定されています。メニューの数が少ないことから、かなりネイティブなつくりをしてしまった感があります。これはちゃんとライブラリを学んだ方がよさそうです。

まとめ

Unityにはビジュアル開発環境もありますが、今回はC#でゴリゴリ書く方でやってみたところ、他言語の経験があれば、Unityの中で扱う分にはほぼ言語系で困ることはありませんでした。規模が小さいからというのもありますが、それでも13個ぐらいのスクリプトファイルから構成されています。すごく小さなゲームですが、ありそうな要素を概ねカバーして、ゲームループを1周させてみるのは開発環境を学ぶのに非常に役に立ちました。

参考

ここまでの内容は、以下、ITeens Labのこば先生の動画の15回までをベースにしています。本当にむちゃくちゃ分かりやすいです。
超初心者向けUnityの使い方① 〜Unity(ユニティ)とは? インストールと準備〜 - YouTube https://www.youtube.com/watch?v=8h2JqLg_oVI

効果音は、効果音工房様の素材を利用させていただきました。ありがとうございます。
効果音工房 | 自由に使える無料の効果音を配布中! https://umipla.com/