”Unityのため50のヒントとベストプラクティス”の2016年改訂版の意訳をしてみました。
誤訳また記述ミス、その他があるかもしれませんが、それらはコメント欄のほうにお願いします。
■ Unityのため50のヒントとベストプラクティス(2016年版)
Gamasutra:ハーマンTullekenのブログ - Unityのため50のヒントとベストプラクティス(2016年版)
約4年前、私はオリジナルである 50 Tips for working with Unity を公開しました
オリジナルバージョンとは多くの関連がありますが、その後に多数の変更があります:
- Unityは良くなりました。たとえば、私は現在、FPSカウンタを信頼しています。 PropertyDrawersを使用できるため、カスタム・エディターを書く必要が少なくなります。 プレハブ作業を行う方法は、明示的にネストされたプレハブを必要とし ScriptableObjectは相性がいいです。
- Visual Studioの統合性が向上しました。デバッグが非常に簡単になり、ゴリラデバッグが不要になりました。
- サードパーティ製のツールとライブラリが良くなりました。Asset Storeには、視覚的なデバッグやより良いロギングなどの処理を行う多くのアセットが用意されています。。私たち独自の(無料)拡張機能は、プラグインは、元の記事に記載された元の記事に記載されているコードの多くが含まれています。(それの多くは、ここでも説明します)。
- バージョン管理は良くなりました。(あるいは、今、私はそれを効果的に使用する方法を知っています)。例えば、複数のコピーやプレハブのバックアップコピーを持つ必要はありません。
- 私はより多くの経験を得ました。私はこの4年間で、数多くのゲームのプロトタイプ、Father.IOのようなプロダクションゲーム および当社の主力Unityアセットグリッド。などの数多くのUnity・プロジェクトに取り組みました。
この記事は、上記をすべて考慮したオリジナルの改訂版です。

Tipsのディスカッションを続ける前に、私たちはここに次の免責条項を発行します(初期バージョンと基本的に同じです):
これらのヒントは、すべてのUnityプロジェクトに適用されるものではありません。
- これらは、3?20人からの小さなチームのプロジェクトでの私の経験に基づいています。
- チームのサイズ、プロジェクトの規模、およびプロジェクトの目標により その代価が支払われるべきかどうかを決定します 。構造、再利用性、透明性、などの評価値があります 例えば あなたはゲームジャム中にすべてのこれらを使用することはありません、。
- 多くのヒントは好みの問題です(ここに記載されているTipsに匹敵する技術があるかもしれません)。
以下の記事にもいくつかのUnityのベストプラクティス(パフォーマンスの観点からのものが、ほとんどですが)が存在します:
- http://unity3d.com/learn/tutorials/topics/best-practices
- Best practices for physically based content creation https://youtu.be/OeEYEUCa4tI
- 2D Best practices in Unity https://youtu.be/HM17mAmLd7k
- Internal Unity tips and tricks https://youtu.be/Ozc_hXzp_KU
- Unity Tips and Tricks https://youtu.be/2S6Ygq58QF8
- http://docs.unity3d.com/Manual/HOWTO-ArtAssetBestPracticeGuide.html
ワークフロー(開発プロセス)
1.最初に決定した規模ですべてのプロトタイプを構築します。
そうしないと(たとえば、あなたが常に正しくアニメーションをスケーリングすることはできません)、その後のアセットをやり直す必要があるかもしれません。1Unity・ユニットを使用して3Dゲームの場合、= 1メートル、通常は最高です。照明や物理学を使用していない2Dゲームのために、(「デザイン」の解像度で)1Unity単位= 1ピクセルは通常は良いです。UI(および2Dゲーム)について、すべての資産がその解像度にスケーリングするために設計解像度(私たちはHDまたは2xHDを使用)とデザインを選択します。
2. すべてのシーンが実行可能にしてください。
これによりシーンを切り替えてゲームを実行する必要がなくなり、より速くテストできるようになります。すべてのシーンで必要なシーン読み込みの間にオブジェクトが残っている場合、これは難しいことです。これを実行する1つの方法は、彼らがシーンに存在しないときに自分自身をロードする永続オブジェクトのシングルトンを作る使用することです。シングルトンは別のTipsでより詳細に記載されています。
3. ソースコード管理と効果的にそれを使用する方法を学びます。
- アセットをテキストとしてシリアル化します。
シーンとプレハブをより合併させることは実際にはありませんが、変更されたものをより見やすくします。
- シーンやプレハブ共有戦略を採用します。
一般的には、複数の人がシングルトンは別のチップでより詳細に記載されています。同じシーンやプレハブでは動作しないようにしてください。小さな制作チームのために、誰もがシーンやプレハブを作ることができないことを保証するために作業を開始する直前に。所有権のシーンを代表する取引所の物理的なマークは、(デスクトップ上のシーンがある場合は、あなただけの1シーンで作業することができます)有用である可能性があります。
- ブックマークなどのタグを使用してください。
- 分岐戦略を決めて固執する。 シーンとプレハブをスムーズにマージすることはできないので、分岐はやや複雑です。 あなたがブランチを使うことに決めた場合は、あなたのシーンとプレパブの共有戦略にそって動作しなければなりません。
- サブモジュールを使用する場合は注意してください。
サブモジュールは、再利用可能なコードを維持するための素晴らしい方法ですが、いくつかの注意事項があります。
- メタファイルは、一般的に複数のプロジェクトにわたって一貫していません。非Monobehaviourまたは非Scriptableオブジェクトコードでは一般的に問題ではありませんが、サブモジュールを使用するMonoBehavioursおよびScriptableオブジェクトでは、コードが失われる可能性があります。
- 多くのプロジェクト(サブモジュールで1つ以上を含む)で作業している場合、すべてのプロジェクトでコードを安定化させるために、さまざまなプロジェクトをプル・マージ・コミット・プッシュする必要がある場合には、アップデート・アバランシェを得ることがあります これが起こっている間に他の誰かが変更を加えている場合、それは持続的な雪崩に変わる可能性があります)。この影響を最小限に抑える1つの方法は、専用のプロジェクトからサブモジュールを常に変更することです。このように、サブモジュールを使用するプロジェクトは、常にプルする必要があります。それらはプッシュバックする必要はありません。
4.テストシーンやコード別々に保管してください。
リポジトリに一時的なアセットやスクリプトをコミットし 編集しているプロジェクトから削除します。
5.ツール(特にUnity)をアップグレードする場合は同時にインストールしてください。
Unityは、以前とは異なるバージョンのプロジェクトを開いたときにリンクを保持することに大変優れていますが、各メンバーが異なるバージョンで作業しているときにリンクが失われることがあります。
6. サードパーティ製のアセットはクリーンなプロジェクトにインポートし独自に使用するための新しいリソースパッケージをエクスポートしましょう。
プロジェクトに直接リソースをインポートすると、それらは時々問題を引き起こす可能性があります:
- 競合(ファイルまたは名前)、特に、プラグインフォルダのルートにファイルがあるか、サンプルの標準アセットのアセットを使用するアセットが存在する可能性があります。
- あなた自身のプロジェクト全体にファイルを置くときに組織化されていないかもしれません。。これは特に、使用しないで削除する場合に問題になります。
より安全にアセットをインポートするには、以下の手順に従ってください:
- 新しいプロジェクトを作成し アセットをインポートします。
- examples を実行し それらが動作することを確認してください。
- アセットは、より適切なディレクトリ構造に配置されています。 (私は通常、独自のディレクトリ構造のリソースの強制的なアライメントないんだけど、私は、任意のプロジェクトのないディレクトリ内のすべてのファイルは、既存のファイルは重要な位置に存在する上書きされる可能性がありますことを確認しています。)
- examplesを実行して動作確認をします。(リソースを移動したときに時々 アセットの損傷を引き起こす可能性がありますが、一般的に問題になることはありません)。
- 必要のないものを全て削除します(たとえばexamplesのような)。
- アセットがコンパイルされているか そしてプレハブが全てリンクされているか確認します。なにか残っているかを実行してテストします
- すべてのアセットを選択して、パッケージをエクスポートします。
- プロジェクトにインポートします。
7. ビルドプロセスの自動化。
これは小規模なプロジェクトでも役に立ちますが、以下の場合に特に便利です:
- 多くの異なったゲームのバージョンでビルドが必要な場合。
- チーム内の技術知識の差異があるメンバーでビルドの必要がある場合。
- プロジェクトのビルド前に微調整をする必要がある場合。
※ Unity Builds Scripting: Basic and advanced possibilities 詳しくはリンク先を参照してください。
8. ドキュメントセットアップ。
ほとんどのドキュメントはコード内になければなりませんが、特定のことは、コードの外にドキュメント化する必要があります。設計者がセットアップ用のコードを取捨選択することは時間の浪費です。(ドキュメントが最新のものである場合)ドキュメント化されたセットアップの効率を改善しました。
以下のようなことをドキュメント化してください:
- 使用しているタグ。
- レイヤー用途(コリジョン、カリングおよびレイキャスト 本質的には、各レイヤに対応する使用)
- レイヤーのためのGUIの深さ(各レイヤーに対応する表示)
- シーンのセットアップ設定。
- 複雑なプレハブのプレハブ構造。
- 共通言語設定。
- ビルドのセットアップ設定。
一般的なコーディング
9.すべてのコードを名前空間(ネームスペース)内に格納する。
これにより、独自のライブラリとサードパーティのコード間のコードの衝突を回避できます。しかし重要なクラスとの衝突を避けるために、名前空間に依存していません 別の名前空間を使用するとしてもクラス名として、"Object"、 "Action" 、 "Event" は使用しないでください。
10. アサーションの使用
アサーションは、コード内の不変量をテストし論理バグをフラッシュするのに役立ちます。アサーションで利用可能なのはUnity.Assertions.Assertのクラスです。これらですべての条件をテストし 条件が満たされない場合、コンソールにエラーメッセージを書き込みます。アサーションをどのように活用できるかを熟知していない場合はリンク先を参照アサーションを使用したプログラミングの利点を(別名文をアサート)。
11. 表示されるテキスト以外の文字列は使用しないでください。
特に、オブジェクトやプレハブを識別するために文字列を使用しないでください。例外があります(Unityで名前でしかアクセスできないものがいくつかあります)。このような場合には、そのようなAnimationNamesやAudioModuleNamesなどのファイルの定数としてこれらの文字列を定義します。これらのクラスは管理不能になった場合はあなたのような何かを言うことができるように、入れ子になったクラスを使用しますAnimationNames.Player.Run。
12. Invoke とSendMessageを使用しないでください。
これらのMonoBehaviourのメソッドは名前で他のメソッドを呼び出します。コード内で名前で呼び出す方法は、追跡するのが困難です(あなたは用途を見つけることが出来ません。そしてSendMessageのスコープは範囲が広いため追跡することはより困難です)
コルーチンとC#アクションを使用して独自のInvokeを簡単にロールアウト(運用)することができます:
|
次に、あなたのmonoBehaviourでこのようにこれを使用することができます:
this.Invoke(ShootEnemy); //ShootEnemy は引数を取らないvoidメソッドです.
独自のベースMonoBehaviourを実装する場合は、独自のInvokeを追加することができます。
別の安全なSendMessageを実装するのはより困難です。そのかわりに、私はたいてい 親オブジェクト、カレントのゲームオブジェクトや子オブジェクトの現在のコンポーネントを取得し 各種のGetComponent 変数を使用して直接呼び出しを実行します。
(編集:誰かがUnityのEvent Systemの一部であるExecuteEventを代わりに提案しました、これまではまだ分かりませんでしたが、それ以上調べる価値はあります)
13. ゲームが実行中に階層を混乱させるオブジェクトの生成をしないでください。
シーンオブジェクトに親を設定すると、ゲームが実行されているときに簡単に見つけられるようになります。 空のゲームオブジェクト、またはシングルトン(この記事の後半参照)を使用して、コードから簡単にアクセスできるようにすることもできます。 このオブジェクトをDynamicObjectsと呼びます。
14. 正規な値としてnullを使用することについては具体的に記述し 可能な限り避けてください。
nullは不正なコードを検出するのに役立ちます。 しかしnullを暗黙的に渡す習慣があれば、間違ったコードがうまく実行されてしまいずっと後までそのバグに気付かないでしょう。 さらに各層がnull変数の上を通過するときにコードの深部に現れることがあります。 私は合法的な値としてnullを使うのをやめようとします。
私の好みのイディオムは、nullチェックを行わずコードが問題のある場所で失敗するようにすることです。 より深いレベルへのインタフェースとして機能するメソッドでは、nullの変数をチェックし失敗する可能性のある他のメソッドに渡す代わりに例外をスローします。
場合によっては、値が正当にnullになる可能性があり、別の方法で処理する必要があります。このような場合、何がいつ そしてなぜnullになるのかを説明するコメントを追加してください。
一般的にインスペクタで設定された値によく使用されます。ユーザーは値を指定できますが、値が指定されていない場合はデフォルト値が使用されます。これを行うより良い方法は、Tの値をラップする、クラスOptional
(残念なことに、ジェネリッククラスを直接使用することはできません.Tの特定の値のクラスを拡張する必要があります。)
[Serializable] public class Optional
|
コードでは、このように記述できます:
| health = healthMax.useCustomValue ? healthMax.Value : DefaultHealthMax; |
編集:多くの人から構造体を使用する方が良いと指摘されました(ガベージを生成せず、nullにすることはできません)。 ただし 非ジェネリッククラスのベースクラスとしては使用できないため、インスペクタで実際に使用できるフィールドには使用できません。
15. コルーチンの効果的な使用方法を学びましょう
コルーチンは、多くの問題を解決する強力な方法です。 しかしデバッグが難しく誰も理解できない、あなた自信でさえ理解することが出来ません。
知っておくべきこと:
- コルーチンを並行して実行する方法。。
- コルーチンを順番に実行する方法。
- 既存のものから新しくコルーチンを作成する方法。
- CustomYieldInstructionを使用するcustom coroutines(カスタムコルーチン)を作成する方法。
|
16. インターフェイスを共有するコンポーネントを操作するには、拡張メソッドを使用します。
(編集:どうやらGetComponentなどはインターフェイス用に機能し、このチップを冗長にしています)。 特定のインタフェースを実装するコンポーネントを取得したり、そのようなコンポーネントを持つオブジェクトを見つけると便利なことがあります.
次の例では、代わりにこれらの関数のtypeofのジェネリック版を使用しています。ジェネリック版はインターフェイスで動作しますが、typeofをしないでください。以下の方法は、一般的な方法できちんとこれをラップします。
|
17. 拡張メソッドの構文をより便利にする。
例えば:
|
18. 守備型のGetComponent代替を使用する
場合によって他のクラスのGetComponentを呼び出すときに、RequiredComponentを介してコンポーネントの依存関係を強制することが常に可能であるとは限りません。 そして、たとえあなたがRequiredComponentを使用しているとしても、それがどこにあるかを期待するコンポーネントをどこで取得し そうでない場合にはセットアップエラーをコード内に示すことは有用です。 これを行うには、エラーメッセージを出力する拡張メソッドを使用するか、見つからないときに、以下のようなより有用な例外をスローします:
|
19. 同じことをするために異なる慣用イディオムを使用しないでください。
多くの場合、物事を行うために複数の慣習的な方法があります。 そのような場合は、プロジェクト全体で使用するものを1つ選択します。
理由は次のように:
1)いくつかのイディオムはうまく機能しません。 1つのイディオムを使用すると別のイディオムには適さない一方向のデザインになります。
2)同じイディオムを使用すると、チームメンバーは何が起こっているのかを簡単に理解することができます。 構造とコードを理解しやすくし 間違いにくくなります。
イディオム基の例:
- ステートマシンvsコルーチン。
- ネストされたプレハブvsリンクプレハブvsgodプレハブ。
- データ分離戦略。
- 2Dのゲームで状態ステートのスプライトを使用する方法。
- プレハブ構造。
- スポーン戦略。
- タイプによって参照(「リンク」)配列vsレイヤーvsタグvsネームvs:オブジェクトを検索する方法。
- オブジェクトをグループ化する方法:レイヤー参照vs配列(「リンク」)vsタグvsネームvsタイプによって。
- 他のコンポーネントのメソッドを呼び出すための方法。
- 自己登録vsオブジェクトのグループの検索。
- 実行順序の制御(Unityのexecution order設定vsyield logic vsAwake / Start and Update / Late Update 更新の信頼vs手動による方法と任意順序アーキテクチャの使用)
- ゲーム中のマウスでオブジェクト/ポジション/ターゲットを選択する:選択マネージャとローカル自己管理。
- シーンの変化との間でデータを保持:を通じて PlayerPrefs新しいシーンがロードされるときに破壊されない、またはオブジェクト。
- (アディングおよびレイヤリング、ブレンディング)アニメーションの組み合わせの方法。
- 入力処理(ローカルvs集中管理)
20. 簡単に一時停止するために、あなた自身の時間のクラスを維持してください。Time.DeltaTimeとTime.TimeSinceLevelLoadをラップすると、一時停止と時間スケールが考慮されます。 それを使用するには調整が必要ですが異なるクロック(インターフェース・アニメーションやゲーム・アニメーションなど)を実行する場合は、作業が非常に簡単になります。
編集:UnityはunscaledTimeとunscaledDeltaTimeをサポートしています。これにより、多くの状況で独自のTimeクラスが冗長化されます。 グローバル時間をスケーリングすると、あなたが望ましくない方法で書き込まなかったコンポーネントに影響を与える場合には、これは依然として有効です。
21. 更新が必要なカスタムクラスは、グローバル静的時間にアクセスすべきではありません。 代わりに、Updateメソッドのパラメータとしてデルタ時間を取る必要があります。これにより、上で説明したように一時停止システムを実装するときや、カスタムクラスの動作を高速化または低速化したいときに、これらのクラスを使用できるようになります。
22. WWWを呼び出す一般的な構造を用います。
サーバー通信する多くのゲームでは、一般的に数十のWWWコールを含みます。あなたはUnityの標準のWWWクラスまたはプラグインを使用して定型文として上に薄い層を書くことで恩恵を得ることができます。
私は通常、すなわちCallImplのコルーチンとMakeHandler(GETとPOSTのために、それぞれ)メソッドの呼び出しを定義します。基本的に、スーパーハンドラを構築するために、プロセッサのパーサ、成功と失敗からMakeHandlerメソッドを使用してメソッドを呼び出します。また、それは、スーパーハンドラを呼び出し 完了するまで待って、URLを作成し CallImplのコルーチンを呼び出します。
これは おおよそ次のとおりです。:
|
これには、いくつかの利点があります。
- 定型コードをたくさん書く必要がなくなります。
- 特定の処理(UIコンポーネントのロードの表示や特定の一般的なエラーの処理など)を中枢部分で処理できます。
23. あなたが大量のテキストを持っている場合は、同じファイル内に配置します。
インスペクタで編集のためにフィールドに入力しないでください。 Unityのエディタを開くことなく変更することが容易になり、特にシーンを保存する必要はありません。
24. ローカライズする予定がある場合は、すべての文字列を1つの場所に分けてください。
これを行うには多くの方法があります。 1つの方法は、文字列ごとにパブリックの文字列フィールドを持つTextクラスを定義することです(デフォルトは英語に設定されています)。 他の言語ではこれをサブクラス化して、対応する言語でフィールドを再初期化します。
より洗練されたテクニック(テキストの本文が大きく、言語の数が多い場合に適しています)は、スプレッドシートで読み込まれ、選択された言語に基づいて正しい文字列を選択するロジックを提供します。
クラス設計
25.inspectable フィールドの実装方法を決定し それを標準にします
フィールドをパブリックにするか、プライベートにして[SerializeField]としてマークします。 後者は「より正確」ですが、あまり便利ではありません(もちろん、Unity自身が普及している方法ではありません)。 どの方法を選んでも、チームの開発者が公開フィールドをどのように解釈するかを知ることができるように標準にしてください。
- inspectable なフィールドは公開されている場合。このシナリオでは、publicとは、「実行時に変数が設計者によって変更されるのは安全です。コード内に値を設定しないでください」という意味です。
- inspectable なフィールドはプライベートでマークされたシリアライズ可能なフィールドの場合。 このシナリオでは、publicは「コード内でこの変数を変更することは安全です」(したがって、あまり多くは見られず、MonoBehavioursおよびScriptableObjectsにはパブリックフィールドは存在しないはずです)。
26.コンポーネントの場合、インスペクタで調整しないでください。
それ以外の場合は、デザイナーが微調整します。 いくつかのまれなケースでは避けられないものがあります(たとえば、エディタスクリプトによってはそれを保持する必要がある場合など)。 その場合、HideInInspector属性を使用してインスペクタで非表示にすることができます。
27.PropertyDrawersを使用して、フィールドをよりユーザーフレンドリーにする
PropertyDrawersは、インスペクター内のコントロールをカスタマイズするために使用できます。これにより、データの性質に適したコントロールを作成し 特定のセーフガードを適所に置くことができます(変数の範囲の制限など)。 ヘッダー属性を使用してフィールドを整理し ツールチップ属性を使用してデザイナーに余分なドキュメントを提供します。
28. カスタムエディタに比べてPropertyDrawersの使用が好ましい。
PropertyDrawersは、フィールド・タイプごとに実装されるため、実装する作業がはるかに少なくなります。 それらはまた より再利用可能です - 一度型のために実装され、それらはどのクラスでもその型のために使用することができます。 カスタムエディタはMonoBehaviourごとに実装されているため、再利用性と作業性が低下します。
29. 既定ではMonoBehavioursはシールされています。
一般に、UnityのMonoBehavioursはあまり継承されていません:
- UnityがStartやUpdateのようなメッセージメソッドを呼び出す方法は、サブクラスでこれらのメソッドを扱うのが難しいです。気をつけなければ、間違った箇所が呼び出されるか、基本メソッドを呼び出すことを忘れてしまいます。
- カスタムエディタを使用する場合は、通常、エディタの継承階層を複製する必要があります。あなたのクラスを拡張したい人は、自分のエディタを用意するか、またはあなたが提供したもので何でもしなければなりません
継承が必要な場合は、それを避けることができれば、Unityメッセージメソッドを提供しないでください。 そうした場合、それらを仮想化しないでください。 必要に応じて、メッセージメソッドメソッドから呼び出される空の仮想関数を定義して、子クラスがオーバーライドして追加の作業を実行することができます。
//このクラスは独自のアップデートを行う前に呼び出されます //独自の更新コードにフックするオーバーライドします。
//カスタム操作を実行
|
これは、誤ったコードの上書きからクラスを防ぎ、それでもそれをUnityのメッセージにフックすることができます。私がこのパターンが好きではない一つの理由は、物事の順序が問題になることです。上の例では、クラスが独自の更新を行った直後に子プロセスが直接操作を行いたい場合があります。
30.インターフェイスをゲームロジックから分離する。
インタフェースコンポーネントは、一般にそれらが使用されているゲームについて何も知らないはずです。ビジュアル化するために必要なデータを提供し イベントに登録して、ユーザーがそれらとやり取りするときを見つけます。インターフェイスコンポーネントはゲームロジックをおこなう必要がありません 。それが有効であることを確認するために入力をフィルタリングすることができますが、メインルールの処理は他の場所で行われるべきです。多くのパズルゲームでは、作品はインターフェイスの拡張であり、ルールを含むべきではありません。(例えば、チェスの駒はそれ自体のルール上の動きを計算すべきではないです)
同様に、入力はその入力に作用するロジックから分離する必要があります。 あなたのアクターに動く意図を知らせる入力コントローラーを使用してください。 アクターは実際に移動するかどうかを処理します。
ここでは、ユーザーが選択肢のリストから武器を選択できるようにする、UIコンポーネントの抜粋された例を示します。これらのクラスがゲームについて知っている唯一のものは、武器クラスです(武器は、このコンテナが表示する必要があるデータの有用なソースであるためです)ゲームはコンテナについても何も知りません。それが関係しているすべてはOnWeaponSelectイベントに登録するだけです。
// GameManagerは、このイベントのために登録することができます
|
31. 個別のコンフィグレーション、状態ステート、ブックキーピングを分離する。
- 設定変数(コンフィギュレーション)は、プロパティを使ってオブジェクトを定義するためにインスペクタで調整される変数です。 たとえば、maxHealthです。
- 状態変数(ステート)は、オブジェクトの現在の状態を完全に決定する変数であり、ゲームが保存をサポートしている場合は保存する必要がある変数です。 たとえば、currentHealthです。
- 簿記変数(ブックキーピング)は、速度、利便性、過渡的な状態に使用されます。 それらは常に状態変数から完全に決定することができます。 たとえば、previousHealthです。
これらのタイプの変数を分離することで、変更可能なもの、保存する必要のあるもの、ネットワークを介して送信/取得する必要があるもの、そしてこれをある程度強化することが容易になります。
ここでは、この設定の簡単な例を示します:
|
32. パブリックインデックス結合配列の使用を避けてください
例えば、武器の配列、弾丸の配列、およびパーティクルの配列を定義しないでください。コードは次のようになります:
|
これはコード内ではあまり問題になりませんが、きちんとインスペクタで設定してください。 いっそのこと3つの変数をカプセル化するクラスを定義し その配列を作成してください
|
コードはきれいに見えますが、最も重要なことはインスペクタでデータを設定するのは間違いということです。
33. 配列以外の構造体には配列を使用しないでください。
例えば、プレイヤーは3種類の攻撃を受ける可能性があります。 それぞれは現在の武器を使用しますが、異なる弾と異なる動作を生成します。
3つの弾丸を配列にダンプして、この種のロジックを使用したくなるかもしれません:
|
別々の変数を使用して、どの名前を入れるべきかを示す名前を付ける方が良いでしょう。クラスを使ってきれいにします
|
これは、他に火災、氷、風のデータがないことを前提としています。
34. インスペクタで細かくするためのシリアライズ可能なクラスのデータをグループ化する。
いくつかのエンティティには数十の調整機能があるので インスペクタで正しい変数を見つけることは悪夢になることがあります。
作業を簡単にするには、以下の手順に従ってください:
- 変数のグループに対して別々のクラスを定義します。 それらをパブリックかつシリアライズ可能にします。
- 主要クラスでは、上記のように定義された各型のパブリック変数を定義します。
- AwakeまたはStartでこれらの変数を初期化しないでください。 シリアライズ可能なので、Unityはそれを処理します。
- 定義に値を割り当てることで、以前と同じようにデフォルトを指定することができます。
これにより、インスペクタ内の折りたたみ可能な単位で変数がグループ化され、管理が容易になります。
|
35. 公開フィールドに使用されていない場合でも、MonoBehaviours Serializableではないクラスをシリアライズ可能にする。
これにより、Inspectorがデバッグモードのときにインスペクタのクラスフィールドを表示することができます。 これはネストされたクラス(プライベートまたはパブリック)に対してもです。
36. インスペクタの変更をコードで修正することは避けてください。
インスペクタで調整可能な変数は構成変数であり、ランタイム定数として扱われ、状態変数の2倍ではありません。 このプラクティスに従うことで、コンポーネントの状態を初期状態にリセットするメソッドを簡単に記述でき、変数の動作をより明確にすることができます。
|
パターン
パターンは、頻繁に発生する問題を標準的な方法で解決する方法です。 Bob Nystromの「Game Programming Patterns(無料のオンラインで読むことができます)http://gameprogrammingpatterns.com/」は、ゲームプログラミングで発生する問題にどのようにパターンが当てはまるかを見るのに役立つリソースです。 Unity自身は、これらのパターンを多く使用しています。Instantiateはプロトタイプパターンの例です。 MonoBehavioursはテンプレートパターンのバージョンに従い、UIとアニメーションはオブザーバーパターンを使用し 新しいアニメーションエンジンはステートマシンを使用しています。
これらのヒントは、具体的にはUnityとパターンの使用に関連します。
37. 利便性のためにシングルトン(Singleton)パターンを使用してください。
次のクラスは、それを継承するクラスを自動的にシングルトンにします:
|
シングルトンは ParticleManager 、AudioManager または GUIManager。などの管理に有用です。
(多くのプログラマーは、XManagerという名前のクラスに対して警告を出します。なぜならクラスの名前が不十分であるか、無関係なタスクが多すぎるように設計されているからです。 概ね私はこれに同意します。 しかし すべてのゲームには少数のマネージャーが存在しますし すべてのゲームで同じことをするので、実際にはこれらのクラスは慣用表現です。)
- (プレーヤーなど)マネージャーではない固有のプレハブのインスタンスのためにシングルトンを使用しないでください。この原則を遵守しないと、継承階層が複雑になり、特定の種類の変更が困難になります。GameManager(または他の適切なGodクラス)でこれらの参照を維持してください。
- クラス外から頻繁に使用されるパブリック変数やメソッドの静的プロパティとメソッドを定義します。これにより、GameManager.Instance.playerの代わりにGameManager.Playerを記述することができます。
他のヒントで説明したように、シングルトンは、グローバルデータを追跡するシーンロード間で持続するデフォルトのスポーンポイントとオブジェクトを作成する場合にも有用です。
38. ステートマシンを使用して、異なるステートで異なる動作を取得したり、ステート遷移でコードを実行したりします
軽量のステートマシンは、複数の状態を有し各状態は、指定または状態の実行操作の有無を入力して、アクションを更新することができます。これは、コードをよりクリーンにしてエラーが起こりにくくします。Updateメソッドに、それが何をするかを変更するifステートメントやswitchステートメント、またはhasShownGameOverMessageなどの変数の有無を記述することで状態マシンからの符号を得ることが出来ます。
|
より多くの状態ステートがある場合 このタイプのコードは非常に乱雑になる可能性があります。 ステートマシンはそれを非常にクリーンにすることができます。
39. UnityEvent型のフィールドを使用して、インスペクタでObserver パターンを設定する
※Observer パターン(オブザーバ・パターン)とは、プログラム内のオブジェクトの状態を観察(observe)するようなプログラムで使われるデザインパターンです。
UnityEventのクラスでは、ボタン上のイベントと同じUIインターフェイスを使用して、インスペクタの4つのパラメータを取る方法にリンクすることができます。これは入力に対処するために特に有用です。
40.フィールド値の変化を検出するためにオブザーバーパターンを使用してください。
変数が変更された場合にのみコードを実行する問題は、ゲームで頻繁に発生します。 私たちは汎用クラスでこれを包括的に解決しました。これにより、値が変わるたびにイベントに登録することができます。 ここでは、healthの例での構築の方法です:
/*値の監視*/health = new ObservedValue(100);
|
これを確認する場所を確認することなく、どこでも変更できます。たとえば次のように。
if(hit) health.Value -= 10;
健康値healthが0を下回ると、Dieメソッドが呼び出されます。 さらなる議論と実装については、この記事ポストを参照してください。
41.プレハブにアクターパターンを使用してください。(これは、「標準」パターンではありません。基本的な考え方は、Kieran Lordのこのプレゼンテーションを参考にしてください)
プレハブの主なコンポーネントはアクターです。 通常はプレハブの「同一性」を提供するコンポーネントであり、上位の1つのコードは多くの場合相互作用します。 アクターは、他のコンポーネント(ヘルパー)を同じオブジェクト(および場合によっては子供)上で使用して作業を行います。
メニューからボタンオブジェクトを1つにまとめると、SpriteおよびButtonコンポーネント(およびTextコンポーネントを持つ子)を持つゲームオブジェクトが作成されます。 この場合、Buttonはアクターコンポーネントです。 同様に、メインカメラは、通常、付属のカメラコンポーネントに加えて、いくつかのコンポーネント(GUIレイヤ、フレアレイヤ、オーディオリスナ)を備えています。 カメラはアクターです。
アクタは、他のコンポーネントが正しく動作するように要求することがあります。 アクタコンポーネントで次の属性を使用すると、プレハブをより強固で便利にすることができます。
-
あなたのアクターが同じゲームオブジェクトに必要とするすべてのコンポーネントを示すには、RequiredComponentを使用します。 (あなたのアクターは、返された値がnullかどうかをチェックする必要はなく、常にGetComponentを安全に呼び出すことができます)。
-
DisallowMultipleComponentを使用して、同じコンポーネントの複数インスタンスをアタッチすることを防ぎます。 したがって、アクターは、複数のコンポーネントが接続されているときのビヘイビアーを心配することなく、常にGetComponentを呼び出すことができます。
-
アクタオブジェクトに子がある場合はSelectionBaseを使用します。 これにより、シーンビューでの選択が容易になります。
|
[RequiredComponent(typeof(HelperComponent))] [DisallowMultipleComponent] [SelectionBase] public class Actor : MonoBehaviour { ...// } |
42. ジェネレータを用いたランダムパターンのデータストリーム。(これは標準モードではありませんが、それが非常に有用であることが判明しました)
ジェネレータは乱数ジェネレータと似ています。特定のタイプの新しいアイテムを取得するために呼び出すことができるNextメソッドを持つオブジェクトです。 ジェネレータは、さまざまなパターンやさまざまな種類の乱数を生成するために、その構築中に操作することができそれらは便利です。なぜなら新しい項目を生成するロジックをアイテムの必要な場所とは別にして、コードがはるかにクリーンになるようにするからです。
ここではいくつかの例を示します:
var generator = Generator
|
私たちは、障害物の生成、背景色の変更、プロシージャルな音楽の作成、単語ゲームで単語を作るときに可能性の高い文字列の生成などに使用しています。ジェネレータはまた、コンストラクトを使って一定でない間隔で繰り返しコルーチンを制御するためにうまく機能します:
|
ジェネレータの議論についての詳細は、この投稿を参照してください
プレハブとScriptableObject参照
43.すべてにプレハブを使用してください。
プレハブ(またはプレハブの一部)ではないシーン内の唯一のゲームオブジェクトは、フォルダーでなければなりません。 一度だけ使用されるユニークなオブジェクトであっても、プレハブでなければなりません。 これにより、シーンを変更する必要のない変更を簡単に行うことができます。
44.プレハブをプレハブにリンクする。 インスタンスをインスタンスにリンクしない
プレハブへのリンクは、プレハブをシーンにドロップするときに維持されます。 インスタンスへのリンクはありません。 可能な限りプレハブにリンクするとシーン設定が減少し シーンを変更する必要性が減ります。
可能な限り、インスタンス間のリンクを自動的に確立します。 インスタンスをリンクする必要がある場合は、プログラムでリンクを確立します。 例えばプレイヤープレハブはゲームマネージャーが起動時にそれをGameManagerに登録することができます。または、GameManagerは、プレイヤープレハブインスタンスが開始時にそれを見つけることができます。
45.あなたが他のスクリプトを追加したい場合。プレハブのルートにメッシュを入れないでください
メッシュからプレハブを作るときは、最初にメッシュを空のゲームオブジェクトの親とし それをルートにします。 メッシュノードではなく、ルート上にスクリプトを置く。 そうすれば、インスペクタで設定した値を失うことなく、メッシュを別のメッシュに置き換えるほうがずっと容易です。
46.プレハブの代わりに、共有コンフィギュレーションデータにScriptableObjectを使用します。
あなたがこれを行う場合:
- シーンはより小さくなります
- 誤って1つのシーン(プレハブインスタンス上)に変更を加えることはできません。
47. レベルのデータにScriptableObjectを使用する。
レベルデータは、多くの場合、XMLまたはJSONに保存されているが、代わりにScriptableObjectを使用することで、いくつか利点があります。
- ScriptableObjectはエディタで編集することができます。簡単にデータの検証が可能になることで技術面に暗いデザイナーにも親しみやすくなります。また編集をさらに容易にするためにカスタムエディタを使用することができます。
- データの読み出し/書き込みと解析を心配する必要はありません。
- 結果のアセットを分割してネストして管理する方が容易であり 大規模な構成ではなく、ビルディングブロックからレベルを作成する方が簡単になります。
48.ScriptableObjectを使用して、インスペクタで動作を設定する。ScriptableObjectは通常、データの設定に関連付けられますが、データとして「メソッド」を使用することもできます
敵のタイプがあり、各敵にスーパーパワーの束があるシナリオを考えてみましょう。 あなたはこれらの通常のクラスを作ってEnemyクラスのリストを持つことができますが、カスタムエディタを使わなければインスペクタ内に別のSuperPowerのリスト(それぞれが独自のプロパティを持つ)を設定することはできません。 しかし これらのSuperPowerアセットを(ScriptableObjectとして実装する)ことができればよいのです。
このように設定:
|
このパターンに従うとき、次の点に注意してください:
- ScriptableObjectを確実に抽象化することはできません。 代わりに具象基底クラスを使用し 抽象型であるメソッドにNotImplementedExceptionsをスローします。 抽象属性を定義して、抽象クラスである必要があるクラスとメソッドをマークすることもできます。。
- ジェネリックであるScriptableObjectはシリアライズできません。 ただし ジェネリックベースクラスを使用して、すべてのジェネリックを指定するサブクラスをシリアライズするだけで済みます。
49.ScriptableObjectを使用してプレハブを特殊化します。
2つのオブジェクトの構成が一部のプロパティでのみ異なる場合は、2つのインスタンスをシーンに配置し インスタンスのプロパティを調整するのが一般的です。 通常は、2つのタイプの間で異なるプロパティの別のクラスを別々のScriptableObjectクラスにする方が優れています。
これにより、柔軟性が向上します:
- 特殊化クラスの継承を使用して、異なるタイプのオブジェクトにさらに固有のプロパティを与えることができます。
- シーンの設定ははるかに安全です(単に目的のタイプのオブジェクトを作るためにすべてのプロパティを調整する代わりに、ScriptableObjectを選択するだけです)
- コードの実行時にこれらのオブジェクトを操作する方が簡単です。
- あなたは2タイプの複数のインスタンスがある場合は、変更を行うときはそのプロパティは常に一貫性があることを知っています。
- コンフィギュレーション変数のセットを混在および一致させることができるセットに分割できます。
以下はセットアップの簡単な例です:
|
特殊化の数が多い場合は、特殊化を通常のクラスとして定義し それを保持できる適切な場所(GameManagerなど)にリンクされたScriptableObjectでこれらのリストを使用することができます。 それを安全に、より速く便利にするために必要な融合方法がもう少しあります。
最小限の例を以下に示します:
|
50. CreateAssetMenuを使用することで属性アトリビュートは自動的にScriptableObjectが作成したメニューが作成/アセットに追加されます。
デバッギング
51. 効果的なUnityのデバッグ機能を使用する方法を学びます。
- Debug.Logステートメントにコンテキストオブジェクトを追加して、それらの生成場所を確認します。
- Debug.Breakを使用してエディタでゲームを一時停止します(たとえば、エラー状態が発生し そのフレームのコンポーネントプロパティを調べたい場合など)。
- ビジュアルデバッグのためにDebug.DrawRay関数とDebug.DrawLine関数を使用します(たとえば、レイキャストがヒットしなかった理由をデバッグする場合、DrawRayは非常に便利です)。
- ビジュアルデバッグには、Gizmosを使用します。 また、DrawGizmo属性を使用してモノ動作以外のギズモレンダリングを提供することもできます。
- デバッグインスペクタビューを使用して(インスペクタを使用してUnityで実行時にプライベートフィールドの値を確認してください)。
52. 効果的にIDEのデバッガを使用する方法を学びます。
Visual StudioでのUnityゲームのデバッグ。を参照してください。
53. 時間の経過とともに値をグラフに描画するビジュアルデバッガを使用する
これは物理学、アニメーション、その他の動的プロセス、特に散発的な障害をデバッグするのに非常に役立ちます。 グラフのゆらぎを見ることができ、同時にどの他の変数が変化するかを見ることができます。 目視検査では、あまりにも頻繁に変化する値や明白な原因なしにドリフトするような特定の種類の奇妙な行動も明確になります。 私たちはモニターコンポーネントを使用しますが、それにはいくつかの方法があります。
54. 改良されたコンソールログを使用します。
カテゴリに応じた出力の色分けを可能にするエディタ拡張を使用し それらのカテゴリに従って出力をフィルタリングすることができます。私たちは Editor Console Proを使用していますが、いくつかが利用可能です
55. Unityのテストツールを使用して、特にアルゴリズムと数学的コードをテストする。
チュートリアルを参照するか またはUnit testing at the speed of light with Unity Test Tools.の投稿、またUnity Test Tools を参照してください。
56. Unityのテストツールを使用して、「スクラッチパッド」のテストを実行します。
Unityのテストツールは、正式なテストには適していません。 シーンを実行することなくエディタで実行できる便利なスクラッチパッドテストにも利用できます。
57.スクリーンショットを撮影するためのショートカットを実装します。
多くのバグは視覚的であり、スナップショットを撮ることができるときに報告する方がずっと簡単です。 理想的なシステムでは、連続するスクリーンショットが上書きされないように、PlayerPrefsにカウンタを保持する必要があります。 スクリーンショットは、プロジェクトフォルダ外に保存して、誤ってリポジトリにコミットすることを避ける必要があります。
58. 重要な変数のスナップショットを印刷するためのショートカットを実装します。
これにより、ゲーム中に何か予期せぬ事態が発生したときに、情報を簡単に記録することができます。 もちろん、どの変数がゲームに依存しているか。 あなたはゲームで発生する典型的なバグに導かれます。 例は、プレイヤーと敵の位置、またはAIアクター(「それを追いかけようとしているパス」などの「思考状態」)です。
59.簡単にテストさせるためのデバッグオプションを実装します。
いくつかの例:
- すべての項目のロックを解除します。
- 敵を無効にします。
- GUIを無効にします。
- プレイヤーを無敵にします。
- すべてのゲームプレイを無効にします。
誤ってデバッグオプションをコミットしないように注意してください。デバッグオプションを変更することでチーム内の他の開発者を混乱させる可能性があります。
60. デバッグのショートカットキーの定数を定義し 一か所に保存します。
デバッグ・キーは、ゲーム入力の残りの部分と同じように単一の場所で通常(または都合よく)処理されません。 ショートカットキーの衝突を回避するには、中枢の場所に定数を定義します。 代わりに、デバッグ機能であるかどうかにかかわらず、すべてのキーを1か所で処理することもできます。 (欠点は、このクラスがこれのためだけにオブジェクトへの余分な参照を必要とするかもしれないということです)。
61.プロシージャルなメッシュ生成を行う際に頂点に小さな球を描画するか生成します。
メッシュを表示するために三角形やUVを使い始める前に、頂点が正しい位置にあることを確認し メッシュが正しいサイズであることを確認するのに役立ちます。
パフォーマンス
62. パフォーマンス上の理由におけるデザインと設計に関する一般的なアドバイスには細心の注意を払ってください。
- このようなアドバイスはしばしば作り話に基づいており、テストによって裏打ちされていません。
- 時にはアドバイスは、テストに裏打ちされているが、テスト自体が破綻しています。
- 時にはアドバイスは正しいテストに裏打ちされているが、それらは非現実的または異なる状況にあります。(例えば、それを使用して配列はジェネリックリストよりも高速であるかを示すのは簡単です。しかし 実際のゲームの文脈でこの差はほとんど常に無視できる程度です。同様に、テストはあなたのターゲットデバイスとは異なるハードウェアに適用されていた場合、その結果はあなたにとっては意味がありません。)
- 時にはアドバイスは懸命ですが、古くなっている事があります。
- 時には、アドバイスが適用されますが、トレードオフが存在します。スローゲームその船は時々ない高速のものよりも優れています。そして、頻繁に最適化されたゲームはシッピング(発送)を遅らせる厄介ななコードが含まれている可能性が高くなります。
パフォーマンスのアドバイスは、以下で概説するプロセスを使用して、実際の問題の原因を迅速に追跡するのに役立つことを覚えておくと便利です。
63. 初期の段階からターゲットデバイス上で定期的にテストをします。
デバイスは、非常に異なるパフォーマンス特性を持っていますが それを驚かないでください。早期から問題について知っていることでより効果的に対処することができます。
64.パフォーマンスの問題の原因を追跡するために効果的にプロファイラを使用する方法を学びます。
- あなたがプロファイリングを初めて使用する場合は、プロファイラの紹介を参照してください。
- ファイングレイン分析のための独自のフレーム(Profiler.BeginFrameとProfiler.EndFrameを)使用して定義する方法を学びます。
- iOS用の組み込みのプロファイラなどのプラットフォーム固有のプロファイルを使用する方法を学ぶ
- 内蔵プレーヤーでファイルにプロファイルし プロファイラでデータを表示することを学びます。
65. 必要に応じてより正確なプロファイリングを行うには、カスタムプロファイラを使用します。
Unityのプロファイラでは、何が起きているのかを明確に把握することはできません。プロファイルフレームが不足したり、プロファイリングが深すぎるとテストが意味をなさないほどゲームが遅くなることがあります。 私たちはこれに独自のカスタムプロファイラーを使用していますが、アセットストアで代替案を見つけることができるはずです。
66. パフォーマンス向上の影響を測定する
パフォーマンスを向上させるために変更を加えるときは、その変更が実際に改善されていることを確認するために測定します。 変更が測定可能または過失でない場合は、元に戻します。
67. より良いパフォーマンスのために読みにくいコードを書かないでください。
次のいずれかの状況がない限り:
- プロファイラを使用して問題を抱えているソースを特定し変更後のパフォーマンスを測定しパフォーマンスの向上が保守性の損失に比べて十分に高い場合。
OR
- あなたが自分は何をしているか解っている場合。
基本ネーミングおよびディレクトリ構造
68. ネーミングは、慣例やフォルダ構造に従います
一貫した命名およびフォルダ構造は、対象を見つけやすく、対象が何であるかを把握しやすくなります。
おそらく、独自の命名規則とフォルダ構造を作成したいと思うでしょう。 ここでは一例を示します。
命名の一般原則
- それが何であるかを示すような呼び方をします。鳥は鳥と呼ばれるべきです。
- 発音したり記憶したりできる名前を選択します。 あなたがマヤのゲームを製作したとして レベルをQuetzalcoatisReturn(ケツアルコアトルの復活)と名付けてはいけません。
- 一貫して。あなたが名前を選択すると、それに固執。別の内の1つの場所とbuttonContainerで何かbuttonHolderを呼び出さないでください。
- ComplicatedVerySpecificObject:このように、パスカルケースを使用してください。スペース、アンダースコア、またはハイフンを使用しないでください (例外的に同じものの異なる性質を命名を参照してください)。
- 進捗状況(WIP、 final)を示すために、バージョン番号、または単語を使用しないでください。
- 略語は使用しないでください:DVamp@ WはDarkVampire@Walkでなければなりません。
- 設計ドキュメント内の用語を使用してください:ドキュメントがdieのアニメーションを呼び出した場合、DarkVampire@DeathではなくDarkVampire@Dieとします。
- 最も具体的な記述子は、VampireDarkではなくDarkVampireのままにしておきます。 PauseButtonではなく、ButtonPausedです。 たとえば、すべてのボタンが「Button」という単語で始まらない場合、インスペクターで一時停止ボタンを見つける方が簡単です。 [多くの人がそれを好む傾向があります。なぜならグループ分けを視覚的により明白にするからです。 名前はグループ化するためのものではなく、フォルダもあります。 名前は同じタイプのオブジェクトを区別して、信頼性が高く高速に配置することができます。
- いくつかの名前は、配列を形成します。PathNode0、PathNode1、例えば、これらの名前の数字を使用してください。常に0ではなく1から開始します。
- シーケンスを形成しないもののために数字を使用しないでください。例えば、Bird0、Bird1、Bird2はフラミンゴ、イーグル、ツバメにする必要があります。
- 二重下線__Player_Backupとプレフィックス一時オブジェクト。
同じものを異なる性質(特徴)から命名
基本の名前をアンダーバーで区切って「性質(特徴)」を記述します。
例えば:
- GUIボタンステート EnterButton_Active、EnterButton_Inactive
- テクスチャ DarkVampire_Diffuse、DarkVampire_Normalmap
- スカイ JungleSky_Top、JungleSky_North
- LODグループ DarkVampire_LOD0、DarkVampire_LOD1
例えばRock_Small、Rock_LargeはSmallRock、LargeRockにする必要があり、アイテムの異なるタイプを区別するだけにこの規則を使用しないでください。
構造
シーン、プロジェクトフォルダ、およびスクリプトフォルダの構成は同様の形式に従ってください。ここでは、取り掛かりにいくつかの要約例を示します。
フォルダー構成
|
シーンの構造
|
スクリプトのフォルダ構造
|