ゲームプログラミング研修

こんにちは。技術部平山です。

たぶん15年ぶりくらいに研修の類の講師をやったので、そのことについて書きます。

概要

2D用(github)3D用(github) の2つのUnityプロジェクトをテンプレートとして用意して、 そこに「コードだけで」ゲームを作る研修をしました。

どちらも、Hierarchyに何かを足すことは禁止、 足して良いアセットはC#ファイルのみで、 そのC#ファイル内ではUnityEngineの機能を使用禁止、 というレギュレーションです。

いずれも、IMachineなるインターフェイスが存在し、 これを通してゲームを作ります。 例えば2D用のIMachineの主要部分はこんな感じです。

public interface IMachine
{
    public int Width { get; } // スクリーン横解像度
    public int Height { get; } // スクリーン縦解像度
    public void Draw(int x, int y, byte red, byte green, byte blue); // 点を描画する
}

要するに、Draw()だけで何もかもやれということですね。 UserApplicaionを継承したクラスを実装して、そのUpdate()が毎フレーム呼ばれる、 というだけです。例えば、サンプルとして用意したSampleApplicaionは、

public class SampleApplication : UserApplication
{
    public override void Update(IMachine machine)
    {
        if (machine.Up)
        {
            machine.Draw(30, 70, 0, 255, 0);
        }
        else
        {
            machine.Draw(30, 70, 0, 0, 0);
        }
    }
}

といった感じです。IMachineに上キーが押されているか問い合わせ、 押されていれば(30,70)の点を緑に、押されていなければ黒にします。

これは、私が大昔に書いたゲームプログラマになる前に覚えておきたい技術 の序盤や、プログラムはこうして作られるの 「現代Unity版」の課題と言えます。

「ゲームプログラミング」というスキルは今もあるのか?

さて、なんでこんなことをしたのか、というお話をしましょう。

弊社のゲームはほぼほぼUnity製です。UnrealやGodotを使うこともゼロではありませんが、 それらもまたゲームエンジンであり、コードで何もかも書くわけではありません。

ゲームエンジンを使ったゲーム開発において、コードで作る部分と、 エンジンのエディタ上で作る部分の比率はどれくらいでしょうね。 人によりますが、エディタ上で作る比率が高い人が多いのではないかと思います。 つまり、相対的にコードの重要性が落ちているわけです。 そのため、このような「コード力絶対主義」的な研修は通常行われず、 実際弊社でもこのようなことは今まで行われませんでした。 そんなヒマがあればUnityの機能を覚えた方が、すぐに絵が出て物が動くからです。

しかし、コードを書かないと作れないゲーム、作りにくいゲームというものは 未だに存在します。

例えばテトリスみたいなゲームはその一つです。 マス目単位のパズルゲームをUnityの機能で作ろうとすると、 12x20個のSpriteを持ったGameObjectを用意して云々、 というような話になり、コードで書けば500行で書けるものが ずいぶん複雑な実装になってしまいます。 ぷよぷよやオセロのように再帰的な探索が欲しくなるケースでは、 コライダで接触判定をして云々、などという書き方はとてもできる気がしません。

また、大きなソーシャルゲームのようなものを作るならば、 通信部やリソース管理等々、GameObjectとコンポーネントで エンジンがやってくれない部分を大量に書かねばならなくなります。 機能そのものはエンジンがライブラリとして用意していても、 それを正しく使うにはコードを書く力が必要です。

何故今になってこのような研修をすることになったか

弊社はここ数年、カジュアルゲームを多く作っています。 カジュアルゲームの中でもかなり仕様が小さいものを 多く作っており、分業の余地が少ないのが特徴です。 初期段階ではプログラマは一人しかいません。

そうすると、そのプログラマのコード力によって、 作れるものが制限されることになります。 物理とコライダで形を作り、 コライダで発火するイベントにハンドラを書くことで ゲームを進行させる、といったゲームであれば 比較的初心者でも書きやすいのですが、 コード力が上がらなければ、そういうゲーム ばかり作り続けるような事態に陥りやすいのです。 自分で企画すれば自分で書けるものに抑えてしまいやすいですし、 別の人に企画をもらうとしても、それが力量を超えていれば 形にならなかったり、時間がかかりすぎたりします。

最近はChatGPTに書いてもらうという選択肢もあるので、 部分のアルゴリズムについてはどうにかなるケースが増えましたが、 全体の設計やデータの持ち方となると ChatGPTの力を借りるのは難しくなります。

例えばテトリスのデータの持ち方は複数あり、 「動くマスと動かないマスを別に管理するか一緒に管理するか」や、 「動くマスを4x4の2次元配列で持つか、原点からのオフセットの配列で持つか」 といった選択肢があります。そのどれを選ぶかを ChatGPTに決めてもらうことは、まだ難しいでしょう。 そもそもそれらの選択肢について考えられない人であれば、 ChatGPTが吐いたコードを見ても、「データの持ち方がどうなっているか」を読み取れないはずです。

そこで、「敢えてコードだけで簡単なゲームを作ってみる」 というチャレンジを研修として課してみることにしました。

研修のスケジュールと進行

研修は3日間で、課題は3つ用意し、終わった人から次に行くようにしました。

  • 2Dテトリス
  • 2Dフラッピーバード
  • 3D BallRun2048

3つ目は弊社の製品です。3Dメッシュ生成、カメラ制御、物理、 等々の完全再現をこの時間でやるのは無理ですので、やれる範囲で、 ということになります。

2Dテトリスは長くとも2日までとし、 3日目はフラッピーバードかBallRunを選んでもらうこととしました。 テトリスは段階を踏んでおり、

  • 1マスづつ落ちてきて1行揃ったら消える
  • 2マスつながって落ちてきて回転が可能
  • 3マスつながっている2種類の図形が落ちてくる
  • 4マスの図形が7種類落ちてくる

という4段階で提出することとします。 段階を明らかにすることで迷走しにくくしつつ、 「次の段階を見越した書き方をする」ことも求めます。 そして、2日目の夕方にはプロジェクターで一人づつ製品の動作画面を映し、 次にコードを私が開いてコメントしました。 3日目の夕方も同様に、2つ目の課題について同じようにコードへのコメントを行いました。

Slack上では各自が進行状況をリアルタイムに書き込むスレッドを作り、 スクショを貼ったりしていました。 平山はそれを眺めて助力が必要そうであれば声をかけたり、 何か進行に問題があると感じれば修正します。 また途中経過の確認も重要ですので、 1日目は14時くらいにその時点の作品をgoogle driveに上げて 平山にメンションしてもらい、 平山がコードを見てスレッドにコメントを書きこみました。

スムーズに進行させるための工夫

平山が素早く作品にコメントするためには、 提出が容易で、提出していただいたものを平山の手元で素早く動かせる 環境が必要です。

2Dに関しては「1ファイルで完結し、作者の名前が入ったクラス名/ファイル名とすること」 をレギュレーションとして定めました。 こうすれば、.csファイルを単体で提出してもらえれば、 それをプロジェクトに入れて見られます。 ファイル名やクラス名がかぶっているとコンパイルエラーになったり ファイルを上書きしてしまったりして面倒です。

また、どの作品を実行するかは、 inspectorにスクリプトの.csをドロップすることで切り換えられるようにしました。

作品切り換え

写真ではMainクラスのinspectorにTetris4を指定してありますが、 ここで、例えばFlappyBird.csをドロップすれば、 実行するクラスが切り替わります。 実行中でも切り換えられるため、プレイモードにしたままで 複数の作品を動かせます。

実装は難しくなく、.csファイルはUnityEngine.Object型として 取れるので、そのnameを見て、 現在のアセンブリから同じクラス名を持ったクラスを見つけてきて Activator.CreateInstance するだけです。

このあたりの使い勝手は、かつて「プログラムはこうして作られる」で扱った 独自言語Sunabaの実行環境をできるだけUnity上で再現したものです。 九州大学で10回ほど授業をした経験を活かした形になります。

みなさんもどうぞ!

というわけで、開発環境となるUnityプロジェクトはgithub ( 2D用、 | 3D用 )に上げておきましたので、 みなさんもやってみてはいかがでしょうか。 その妨げにならないよう、このリポジトリには私が「参考用」として書いた テトリスその他はアップロードしておりません。 研修の際には「他人のコードを一切見ないで実装する」ことが大切です。

2Dの方は、テトリスが半日で作れれば、新人さんとしてはなかなかのものかと思います。 仕様を最小限にすれば、行数は500行未満になるでしょう。

新人のうちにこうした研修をやっておけば、 「マス目単位のゲームにコライダやGameObject使う必要ないな」 という感覚が早くから掴めます。 また、delegateを駆使したコールバック主体の作り方と、 古来の「ゲームループが回ってくる」書き方を相対化して 適切に選択するヒントも得られるでしょう。 今はコールバック式の書き方を主に学ぶ方が多い印象ですが、 原始的な方法を手を動かして知っておくことで、 「新しい書き方がどういう時に良いのか」を俯瞰して考えられるようになります。

また、誰がどれくらい書けるのか、どの段階でどういうつまづき方をしたのか、 どういったクセがあるのか、などがかなりよくわかりますから、 育成の計画も立てやすくなるかと思います。

こういう本が欲しいなあ

平山が過去に書いた「ゲームプログラマになる前に覚えておきたい技術」はもう古びており、 今となってはUnityやUnrealのようなゲームエンジンなしでゲームを作るという選択肢はほぼないかと思います。 昔は本がよく出ていたDirectXも本が滅多に出なくなり、 初学者がいきなり取り組むには厳しい代物になってしまいました。

となると、UnityなりUnrealの上に、エンジン以前のゲームプログラミング の「フレイバー」を持った環境を作り、そこで学習する、 というアプローチは有力だろうと考えています。 学校での教育であれば単位という強制力もありますし独自言語でもいいでしょうが、 言語くらいはプロと同じにしたいという方は多いでしょうから、 言語自体はUnityやUnrealと合わせるのが無難です。 今回はそういったものの一例を提示してみました。

もし、このアプローチで、私が昔書いた2冊の本のような 「コード主体のゲームプログラミングの本」があったら、 役に立つ方もいらっしゃるのではないでしょうか。 特に3Dに関してはエンジンやグラフィクスAPI依存の余計なことを省いて、 「要するに三角形を描けばいいんだよ」ということを伝えるには 今回のような環境を用いて順に説明していくのが良いのではないかと、私は考えています。

どなたか書きませんか?

なお、3Dの環境については、下に軽く仕様を示しておきます。

おまけ: 3D版の仕様

3D版のIMachineの主要部分は以下のような感じです

public interface IMachine
{
    public void SetCamera(
        Vector3 position,
        Quaternion rotation,
        float fieldOfViewYInDegree);

    public void DrawTriangle(
        Vector3 p0,
        Vector3 p1,
        Vector3 p2, 
        Color color); // 三角形を描画する

    // 画面アスペクト比(幅/高さ)
    public float Aspect { get; }

    // ポインタ検出
    public bool PointerDown { get; }
    public Vector2 PointerPosition { get; }
}

要は、カメラを指定して、DrawTriangleで三角形をたくさん描けば 3Dゲームができる、というシンプルな仕様です。 ビュー空間、プロジェクション行列、といった「ややこしい概念」なしでも、 ワールド空間でカメラと頂点を指定すれば絵が出ます。 また、ポインタが取れますので、これでスマホゲームが作れます。

OpenGLを覚えるよりよほど簡単ですし、 UnityのMeshにポリゴンを詰めるよりも覚えることが少ないですから、 メッシュ生成の練習台としても良いのではないでしょうか。 シェーダを知る必要なしにライティングの勉強もできます。

Vector3やQuaternionがありますが、これは平山が用意した偽物です。 インターフェイスはほぼ同じですが、一部の機能しかありません。 using UnityEngine; と書かせたくないのでここまでやっています。

行列はありません。 スケールさえなければ、QuaternionとVector3のペアで行列相当のことができますから、 特にUnityを使っている場合、 玄人以外が行列を使うことはほぼないと思います。