秒速でプロトタイプを作成する方法

を翻訳した記事となります。

━━━━━━━━━━━━━━━━

2013年11月、私と2人の同僚は30ものゲームを作成しました。私は以前にもいくつかのゲームのプロトタイピングを作っていますが、このような短期間で非常に多くのゲームを作ったことは、私に新しい洞察を与えました。

この記事では、TIPSという形式で、これらの観察を集めました。

プロトタイプを記述するより一般的な視点については、

こちらの記事をすでに読まれていると仮定して話を進めます。ある意味、その記事で提示されたアイデアを、ここではプログラムに特化した内容したもの、と言えるでしょう。

(なお2人の同僚の名は、ジョナサン・ベイリーとエドゥアルド・ベウケスです。もし私達のプロトタイピングの詳細を読みたい場合は、

を参照してください。

■A.決定を迅速に行う

非常に素早く何かを作ることはたくさんの楽しさを得られます。ゲーム開発は、最終的な完成に近づくまでは、ゆったりとしか進まないことがあります。その間にアイデアを検証しても、ゲームの形が見えてこないため、結果は曖昧なものにしかなりません。

迅速なプロトタイピングを実践すると、あたなはすぐに満足感を得ることができます。それは特定の問題にエネルギーを集中させ、ゲーム開発を加速させるでしょう。

それをするには、あなたは開発スタイルを工夫する必要があります。余計な作業を放り投げる方法を知っている必要があります。いくつかの努力を無駄にすることを知っている必要があります。

あなたは「コア(核心)」をつかみ取らなければなりません。

▼ゴールを知る。ゴールを忘れない

プロトタイプ構築の理由が変更される例としては、以下のものがあります

  • 一定期間の終わりでゲームを完成させる
  • 楽しいと思わせるゲームのコアを持っている
  • 選択肢のセットから、最高のアイデアを選択する
  • アイデア(グラフィック技術やAIアルゴリズムなど)の技術的な実現可能性をテストする

作業と意思決定を行うための最善の方法は、これらのゴールに依存します。ゴールが何であるかについて明確にし、頻繁にそれを思い出させる必要があります。もし、そのタスクをこなしても、それがあなたのゴールに近づかない場合は、「それをしない」ことを選択すべきです。

▼アイデアの本質を見つけて、それのための充分な時間をスケジューリングする

本質は、ゲームメカニクス、スタイル、テーマ、設定、感情を揺るがす体験をさせる、です。それを見つけて、補助的な要素から切り離して考えなければなりません。

スケジューリングは、本質に合わせて時間を割り当てるようにします。もしあなたが求める本質が、新しいゲームメカニクスであれば、それを探求するための時間を充分に持つようにしてください。決してその時間を、レベルの設計や環境シェーダの実験などに使わないでください。

▼創造的なリスクを取り、技術的なリスクを回避する

ゲームのプロトタイプに失敗するのは、技術的な問題ではなく退屈だからです(ただしゴールが技術的なプロトタイピングでない場合)。プロトタイプを完成させるには、あなたとあなたのチームに以下のことを伝える必要があります。

  • トリッキーな洗練されたデータ構造とアルゴリズムを避ける
  • 洗練されたシステムとアーキテクチャを避ける
  • 急ぎすぎて作ろうとすることを避ける
  • ゲームプレイを改善しないような技術的な偉業を避ける

■計画とプロセス

▼コンテンツについて考える

あなた自身に、あなたのゲームによって得られる感触がどのような範囲なのか、質問をしてください。

  • それを作るのにどのくらい必要ですか?
  • どれくらいのバリエーションを持たせる必要がありますか?
  • 開発をどの段階まで進めるべき?
  • どのようなフォールバック(代替案)を使用することができますか? もしあなたの時間が不足した場合、本質的なコンテンツを入れられるのか? 単純なピンクバージョンのボスでいいのか? キャラクターの代わりに箱でもいいのか?
▼計画
  • ラフな計画を立て、そしてその工数を見積ってください
  • ゴールに対して計画を確認し、ゴールへと近づくために不要なものを削除してください
  • あなたのアイデアの本質を実現するための計画を確認し、あなたが本質を探求するために充分な時間が割り当てられていることを確認して下さい
  • あなたの見積りで予定からはみ出そうなところを確認します。常にフォールバック(代替案)を意識してください。考慮すべきは「影響度」「役割」「複雑さ」です。これによりプレースホルダ(仮の実装)の戦略を考えます(下記参照)
▼実装戦略に従い、衰退に従うように適合させ、創造的なプロセスの流れを作る

あなたが従うべき提案の順序です。典型的な依存関係を念頭に置いて、最初にもっとも重要なことを置きます。

  1. 画面上にゲームオブジェクトを配置する
  2. トップレベルの基本ゲームシステムを実装する
  3. プレイヤーがゲームオブジェクトを操作できるようにする
  4. 最小限のレベルを実装する
  5. ギミックのロジックを実装する
  6. ゲームのゴールを実装する
  7. AIを実装する
  8. ユーザからのフィードバックを受けて、改修を行う
  9. リファイン

プロトタイプは仕様を必要としません。仕様は間違った始まりであり、適合させていくことを知っておく必要があります。大切なのは、計画は変化し、スケジュールは更新し続ける必要があるということです。

▼「タイム・ドレイン(時間の吸い上げ)」を避ける方法を知る

特定の事象が不均衡な時間を吸い上げます。これらを認識し、充分な時間をスケジューリングし、フォールバック(代替案)を用意します(典型的なタイム・ドレインはゲームの種類に依存します。以下の例はあなたに私が何を意味するのかの感覚を与えます)。

  • コントロール:アクションゲームでは、プレイできるまで長い時間がかかる(そしてその作業は楽しい)
  • バランス:シミュレーションゲームでは、ゲームバランスの均衡の調整に時間がかかります。システムの数を低く抑え、バリエーションやパラメータの数を少なくします。そして残った要素でバランスを取るための充分な時間を用意します
  • GUI:情報量の多いゲームではGUIにすべての時間を奪われることがあります。またお気に入りのBGMを見つけるのに長い時間をかけたりもします。それらは必要不可欠でない限り、それを追加しない、またはあなたが必要とする音がどこにあるかあなたはすでに知っておく必要があります
  • アニメーション:アニメーションは、多くの時間を吸い取ります。アニメーションしない静止画を使うことを考慮するようにします
  • プロシージャルなコンテンツ生成:コンテンツ生成のためのアルゴリズムは調整に時間のかかる可能性があります。他のアルゴリズムとは異なり、コンテンツ生成アルゴリズムの仕様はゴールを作ることが難しいです
  • シリアル開発の導入を検討してください(プログラミング・パイプライン):

通常、複数人のプログラマーでの開発は、コンポーネント(機能)を分配して作業を進め、平行して作業を行います。別の方法として、一度か二度、それを試して、あなたのチームのために働くかどうか試す価値のある方法があります。

このアプローチでは、コードの大部分はすべてのプログラマーを千鳥状に通過することとなります。まず最初のプログラマーはプロジェクトの広範な範囲を一通り実装します。続けて詳細な部分の実装を進めます。これにより、各プログラマは、コードの一部を所有するのではなく、プロジェクト全体をタイムスライス(全員が全体をシェアする)ことができます。(これらのスライスは多少の重複が発生します)

これは、プレースホルダ(仮の実装)をする際、特に適している手法です。

このアプローチの良いところは、インターフェースについての議論の削減が可能となる点です。あなたの作業時間において、インターフェイスの変更が必要な場合は、あなたが唯一のプログラマーであるため、他のプログラマのコードは中断されません。(それはまだスライスがオーバーラップして動作しますが、あなたはより多くのケア、計画を必要としています。)

デメリットとしては、非常に悪い設計で終わるかもしれない点です。しかしそれは重要ではありません。というのも設計をエレガントにするのは決して重要ではないはずです。何人かのプログラマーが、プログラムしていない時間がある方が、よっぽど無駄であると考えています。

(もちろん、これはプログラミングのタスクを分離するためのものです。あなたは他の創作活動でこれを行う場合、それはみっともないことでしょう。ですが、多くのプロトタイピングのプロジェクトでは、プログラマーはゲームデザイナーとなります)

▼迷う時間を削減する

いくつかのことはあなたを迷わせます。バグ、トリッキーに実装したコード、使用不能や手間のかかるコントール、膨大なコンテンツ、レベルの設計、などなど……。

あなたは、それらの泥沼にいるとき、そこから抜け出すべきです。そのときにできることは次の通りです。

  • ゲームプレイ・ゲームバランスの崩壊とはならないような、トリッキーなバグは無視します
  • 問題の原因となる特徴をごっそり削ります。もしくは機能を削ります
  • 代替可能なアルゴリズムに置き換えます。もしくは近似するアルゴリズムを使用します
  • ときには強引に進めることも必要です。正しい設計や便利なテクニック、高速アルゴリズム、省メモリ設計、ゲームループ外(ファイルロードなど)の実行速度を犠牲にします。
  • ごまかします。プレイヤーの操作に対して反応するモンスターは、そうでないモンスターよりも賢く見えます。プレイヤーは内部でどんなAIが使われているかを気にしたりしません
  • ハードコーディングをします。洗練された波の生成アルゴリズムよりも、長い配列を使ったシーケンスを使うほうが、はるかに実装が簡単です
  • 隠しましょう

■C.コードの設計と実装

▼プレースホルダコードを使う

プレースホルダコードを使うことで、ゲームのコアを素早く作ることができるでしょう。うまく実装が進められないのであれば、体系的な代替コード(フレームワークなど)に置き換えると、よりより仕事ができます。このアプローチの良いところは、すべてのゲームシステムを一通りプレイできるので、ゲームプレイへの影響に応じたタスクの優先順位をつけることが簡単になります。

ここで、プレースホルダの典型的な例を示します。

  • コントロール:いくつかのゲームではコントロールが非常に重要です。しかしながら、あなたがベストとしている操作体系を、必ずしも最初から実装する必要はありません。例えば、多くのスライドパズルゲームにおけるベストの実装は、オブジェクトをマウスや指タッチでオブジェクトを動かすようにすることです。これは実現が困難なスキームです。ですが一方、矢印キーやボタンでのオブジェクト操作を実現することはそれほど難しくなく、迅速に実装でき、それによるゲームプレイのフィードバックを素早く得ることができます。
  • フィードバック:フィードバックは重要で、ゲーム情報を提供し、エクスペリエンスを向上させます。しかし最初から完璧を目指すのではなく、ダイアログやメッセージや色の変化、点滅や音などは簡単にできるもので、まずは実装します。
  • AI:AIはとても難しいです。これは任意の戦略が機能するかどうかを予測するのが難しいためです。あなたは最終的なAIアルゴリズムの実装を開始する前に、以下のAIプレースホルダを使用することができます。
    • ランダムAI:乱数を使用した完全にランダムなAI
    • ヒューリスティックAI:最善の解ではないAI。先読みまたはその逆
    • グリードAI:ひたすらプレイヤーを追いかける。評価値の高い戦略を選ぶのみのAI
    • AIはすべての可能な動きをすることはできない
  • プロシージャル(動的)なコンテンツ生成:プロシージャルコンテンツに依存している場合、以下のプレースホルダを使用できます。
    • 固定のコンテンツ
    • 純粋にランダムな内容
  • 近似:近似の処理を使用することは、多くの場合で非常に便利です。いくつかの例を示します
    • メッシュでの衝突の代わりに、スフィア、ボックス、点で当たり判定を行います
    • 曲線に近似した直線を使う
▼設計のショートカットを使う

※※注意して使ってください※※

製品用のコードとしては神のような全知全能なクラスは避けるべきですが、プロトタイプ段階ではこれを使うことで多くの時間を節約することができます。Actorクラスを継承したPlayerクラスやMonsterクラスを実装する代わりに、Actorクラスにすべてのコードを組み込みます。

▼間違った設計のショートカットを避ける
  • 命名規則とプロトタイプの組織は変更しません
  • 不必要なデータの露出(不必要なpublicフィールドの使用)によるショートカットは、1日2日であなたをバグが襲うでしょう。モンスターを殺すには、HPを0にするのか、それとも外部から破棄するのか、後から見て思い出すのは決して簡単ではありません
  • ステートマシン(または状態を追跡する何か)はほとんど常に、あなたが考えるより多くの状態を必要とします。もし代わりに不適切な抽象化のショートカット(こんがらがったif-else)を使用すると、バグだらけのコードとなってしまいます。
▼ゲームパラメータの調整を管理する
  • 調整用のパラメータ設定は、1つのファイルや場所にまとめておくとゲームの調整が容易になります
  • すべての値が調整用のパラメータとはなりません。しかし中心となるパラメータは定数にしておきます。マジックナンバーは使用してはいけません
  • 調整用パラメータの名前は慎重に決定します
  • 微調整可能なシステムを設計する場合、公開する変数は明確で、線形で変化する分かりやすいパラメータにします
▼読み取れるコードを書く

使い捨てのコードは、可能な限りの最高水準のものに設計するべきではありません。

しかしながら、チーム開発をする場合は特に、あなたの意図を伝えることを目標にサービスを提供する必要があります。コードの混乱は多くの時間を無駄にすることとなります。迅速に開発するためのコードを書く際に、最も有用であるのは以下のことです。

  • 適切な名前をつけることです
  • 組織的な作りにします
  • 深い階層や複雑な依存関係を避けます
  • 関数を短く記述するようにします
  • “スマートクラス”の数を制限します。スマートクラスとは、ゲームロジックを処理するものです。小さく揮発性のあるプロジェクトでは、プレゼンテーションのためのロジックと予備のヘルパークラスに集中することをおすすめします。そうすることで簡単に大幅な変更を加えることができます。もちろん、関数を短くする限りは、ですけれども
▼実装パターンを活用する

標準的な実装パターンを活用すると、容易に堅牢なシステムを構築できます。ここではメイン処理のパターン例を記載します。

// ゲーム開始
public void Start()
{
   Reset()
}
 
// ゲームリスタート
public void ResetGame()
{
   currentLevel = 0; // レベルを初期値に戻す
   ResetLevel();     // レベルを初期化
}
 
// レベルを初期化
public void ResetLevel()
{
   DestroyObjectsFromPreviousPlay(); // 前回のプレイでのオブジェクトを破棄
   InitialiseVariables();            // 変数を初期化
   BuildLevel();                     // レベルを構築
   InitGameState();                  // ゲーム状態を初期化
}
 
// 更新
public void Update()
{
   ProcessGameFlowInput(); // リセット or 次のレベル
 
   if(gameOver) return;
 
   ProcessGamplayInput();       // プレイヤー操作
   UpdateContinuousGameState(); // 移動、AIなど
 
   gameState = GetGameState();
 
   if(gameState == GameState.Win) WinGame();
   if(gameState == GameState.Loose) LooseGame();
}
 
// 次のレベル
private void NextLevel()
{
   currentLevel++; // レベルアップ
   ResetLevel();   // レベルの初期化
}
 
// ゲームに勝利
private WinGame()
{
   ShowMessage("You win"); // あなたの勝ちです!
   EndGame();              // ゲーム終了
}
 
// ゲームに敗北
private LooseGame()
{
   ShowMessage("You loose"); // あなたの負けです
   EndGame();                // ゲーム終了
}
 
// 終了処理
private EndGame()
{
   gameOver = true; // ゲームオーバーフラグを立てる
}

標準的な方法を用いることで、あなたが思考に費やす時間を短縮し、迷う時間を削減します。実装パターンは製品用コードにもとても有益です。

しかしプロトタイプ用のコードはパターンが異なります。プロトタイプではシンプルさと明快さを強調し、再利用できるオブジェクト指向ではなく、効率を重視します。プロトタイプのテンプレートとして利用できるパターンはどんどん使います。(あなたはプロトタイプの限られた期間で、これらのパターンを集め、設計します。下記参照)

別の例として、ここでは時間の経過とともに滑らかに変数の値を変更する処理の、実装パターンを紹介します。

// 何かに変更する
private IEnumerator ChangeSomething(thingToChange, initialState, finalState)
{
   float timePassed = 0; // 時間の経過
   Set(thingToChange, originalState);
   bool finalStateReached = false;
 
   // 開始処理を行う
   DoThingsAtStartOfChange();
 
   // 変数の値を変更するループ処理
   while(!finalStateReached)
   {
       // デルタタイムを足し込む
       timePassed += Time.deltaTime;
 
       if(timePassed >= time)
       {
          // 目標となる時間が経過した
          timePassed = time;
           
          // 処理を終了する
          finalStateReached = true;
       }
 
       // 元の状態と最後の状態を時間の経過で線形補間する
       Set(thingToChange, Lerp(originalState, finalState, 
          timePassed / totalTime);
 
       // yieldで処理を中断する
       yield return null;       
   }
 
   // 終了処理を行う
   DoThingsAtEndOfChange();
}

■D.前・後ろ・中間

もしあなたが定期的にプロトタイプを構築するのであれば、中間のプロトタイピングの材料を確保することで、大きな利点を得ることができます。

▼反省材料を次に反映する

あなたはプロトタイプを作り終えた後、それについて良かった点、悪かった点の反省会を行います。そして次のプロトタイプのプロジェクトの前に短い注意事項をレビューします。

▼迅速なプロトタイピングを可能にするコードのライブラリを構築する

これは製品用のプロジェクトのためのライブラリとは異なります。速度的には遅い処理となり、プラットフォームは限定的なものとし、製品用のコードとしては使用できないものです。

例として、グリッドの描画に、テクスチャピクセルを使用するのではなく、スプライトを使用します。この方法はプロトタイピングには問題ありませんが、最終的な製品としては遅すぎます。

▼実装パターン、バクのパターン、時間泥棒の識別

いくつかのプロトタイプの後、あなたはいくつかのパターンを見出すことができます。そうしたら、それについてリストを作成し、あなたのライブラリに入れてその情報を活用するようにします。

あなたはライブラリのテンプレートとして実装パターンを体系化することはできますか?

あなたにはいくつかのバクのパターンを避けるために、より良いデータ構造を設計できますか?

あなたはライブラリにそれを追加することで、時間泥棒を退け有利なスタートを切ることができますか?

■参考文献