こんにちは、Unityエンジニアのオオバです。
Unityでゲームづくり開始直後って ひま ですよね。
とくにUnityエンジニアは。
- ゲーム企画が決まらない
- UIもない
- イラストもアニメーションもない
- 3Dキャラクタもない
つまり 作りたくても作るものが見つからない んですよね。
でも探せばやることはたくさんあります。
むしろ、開発初期だからこそやっておくべきこと があるんです。
新規のゲームを5本立ち上げた経験から
開発初期にやれることを紹介したいと思います。
退屈しているUnityエンジニアのみなさんにお届けしたい。
ちなみにチーム開発前提のTipsですが、
個人開発でも当てはまることは多いです。
ぜひ読んでみて下さい。
本記事では、42のTipsと多いため前後編に分けました。
※今回は前編です。
開発初期に暇を持て余しているUnityエンジニアができる42のTips後編
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
- 1.開発環境と思想を決める
- 2.Unityプロジェクトディレクトリ構成を決める
- 3..gitignoreの設定をする
- 4.AndroidManifest.xmlの設定をする
- 5.コーディング規約を決める
- 6.MonoBehaviourクラスのAwake、Startメソッドは極力使わない
- 7.多人数で開発できるような設計
- 8.文字列をソースコードやPrefabへの直書き回避とローカライズ対応
- 9.Unityのシーン遷移基盤開発
- 10.画面遷移システムの作成
- 11.どのシーンからでも開発できるような仕組みの設計
- 12.サウンド再生基盤の開発
- 13.動画再生基盤の開発
- 14.ゲームエフェクトの再生と再利用機構の開発
- 15.ローカルファイル保存の仕組みを用意しておく
- 16.外部アセットを想定したロード処理を作る
- 17.Androidバックキー設計をする
- 18.ローカルPush通知の実装をしておく
1.開発環境と思想を決める
開発環境について、この2つの影響が大きいです。
- ゲームエンジン
- ソースコードエディタ
本記事の読者はUnityエンジニアが多いため、
ゲームエンジンUnity一択です。
ソースコードエディタは色々ありますよね。
- Rider
- VSCode
- Visual Studio
など。
これらの個人的なオススメと設定について解説します。
開発中は随時、リリース直前からはLTSごと
結論以下の2つの基準を設けています。
- 開発中 : 随時アップデート
- リリース直前から : LTSごとのアップデート
Unityは日々進化しています。
アップデートに対しての思想を決めましょう。
随時新しいUnityにアップデートするのが理想です。
ただし、Unityが安定している場合は。
出たばかりの新しいUnityはバグが多いのです。
暫く経つと安定します。
新機能による開発効率アップは見逃せないため、
開発中はリスク高めでも随時アップデート。
リリース半年前くらいからはLTSごとのアップデートが良いでしょう。
UnityのアップデートはUnityHubの利用を強くオススメします。
LTSも出たばかりは不安定
経験上の話ですが、
Unity LTSも出たばかりは不安定です。
マイナーバージョンが6 くらいまで上がったら、
アップデートを検討するくらいの気持ちで良いかと。
ソースコードエディタはRiderが最もオススメ
- 自動フォーマットをでコードの共通化したい
- Unity開発をする上で機能が充実している
- ソフトとして安定している
以上の条件を満たすRiderを
Unityのソースコードエディタとして採用しています。
基本的にチームメンバー全員Riderを使うようにしています。
フォーマット設定はRiderデフォルト
- カスタマイズする時間がもったいない
- 人それぞれの好みを考慮が大変
- フォーマットの浸透と管理が大変
以上の理由からコードフォーマットの設定は
Riderデフォルト状態 です。
注意点として、
Riderのバージョンによってデフォルト状態が異なる ため、
揃えたほうがよいでしょう。
開発思想について
具体的なアクションは以下2点です。
- Unityの機能を素直に使う
- 巨大なサードパーティフレームワークの使用禁止
時の経過でプロジェクトが破綻しないための対策です。
具体的に解説していきます。
Unityの機能を素直に使う
Unityのアップデートで死ぬ。
Unityが勧めていない使い方をすると、
アップデートでトラブルが起きます。
- Obsoluteの機能の使用
- Previewパッケージの使用
- Unityが提供する機能を独自で作る
具体的には上記のような使い方です。
どうしても特殊な使い方をしたい場合もあります。
そんな時は被害が最小限になるよう
いつでも切り離せるような設計 を心がけましょう。
巨大なサードパーティフレームワークの使用禁止
以下の理由からオオバが声を大にして伝えたい思想です。
- メンバーの学習コストアップ
- プロジェクトの生殺与奪を他人に依存
メンバーの学習コストアップ
新しいフレームワークを導入する際、
正しい使い方の理解が必須です。
そして、正しい使い方をメンバーに伝え、
育成するコストはかなりのストレスを生みます。
もちろん育成コストがその後の開発スピードでペイできるなら良いですが。
開発メンバーはコロコロ入れ替わります。
その度に育成するのはしんどいです。
まずそのフレームワークを知らないメンバーの育成から始まります。
プロジェクトの生殺与奪を他人に依存
よくわからない個人がGitHubにアップしている
フレームワークに生殺与奪を握られるのはいかがなものか。
会社が運営、導入実績が多い、ノウハウも蓄積されている。
といったバックグラウンドがあるなら採用を検討します。
「私が死んでも代わりはいるもの」
こういうフレームワークなら問題ないでしょう。
代わりの誰かが意志を受けつぎ開発は継続されます。
フレームワーク導入時に大事なことは以下です。
- ただ流行りに乗らない
- 十分な時間をかけた検証する
話題になっているものってすごそうに感じます。
わかります。
しかし、そのプロジェクトで本当に求められていることを見つめましょう。
それを実現する最短の道のりが新しいフレームワークの導入なのか。
ただ、自分が使ってみたいだけなのか。
安定した枯れた技術のほうが良かった ということも多いですね。
2.Unityプロジェクトディレクトリ構成を決める
プロジェクト構成は、
Unity開発で揉めるがちポイントの1つです。
好みが入るところは大抵揉めます。
本章ではオオバはこうしているという
事実をお送りし、お役に立てればと思っています。
まず 何をどこに格納するのか 方針だけ決めましょう。
最近採用する構成は以下です。
※Assetsフォルダ配下の構成です。
┌ AssetBundleModule/ ・・・サブモジュール
├ AssetBundles/・・・AssetBundleにビルドされるものを格納
└ Tools/・・・AssetBundleサブモジュール用のツール(主にアセットのチェッカー、ビューワー、非エンジニア向け機能)
├ Project/・・・アプリに含まれるアセット
├ Title/
└ (タイトルシーンのコンテンツが格納される)
├ OutGame/
├ Common/・・・OutGameの共通アセット
└ Display/・・・各画面
├ Home/ ・・・ホーム画面ソースコード、prefab
├ Quest/ ・・・Quest画面ソースコード、prefab
└ Menu/
└ Dialog/・・・ダイアログ(ポップアップ)
├ Alert・・・アラートダイアログソースコード、prefab
└ Shop・・・ショップダイアログソースコード、prefab
├ Battle/
└ (バトルシーンのコンテンツが格納される)
├ Scenes/・・・シーン格納
└ Resources/ ・・・ Projectで唯一のResourcesフォルダ。
├ ExternalAssets/ ・・・外部アセット(アセットストアから落としたものとか)
├ Modules/・・・サブモジュール群
├ Preset/・・・プリセットデータ
├ ScriptTemplates/・・・スクリプトテンプレート
├ StreamingAssets/
├ Tools/・・・開発ツール
└ Sandbox/ ・・・テストコード格納
└ ohba_shunsuke/ ・・・各開発者ごとにディレクトリを切ったテストコード
無闇なResourcesフォルダの作成がゴミを増やす
どのようなリソースがアプリに含まれるか分からなくなるため、
無闇にResourcesフォルダは作らない事にしています。
アセットストアからダウンロードしたものをまとめる
アセットストアからダウンロードしたものは、
基本的にAssets配下に散らばってしまいフォルダ構成を汚し、
視認性を悪くしてしまうため、ExternalAssets
にまとめています。
ソースコードとprefabを無理やり分けない
よく見かける以下のような区分けはしません。
フォルダ名 | 内容 |
---|---|
Scripts | ソースコードを格納 |
Prefabs | Prefabを格納 |
視認性が悪くなるため、
スクリプトとPrefabを別々に分けません。
同じディレクトリに入れる事が多いです。
機能単位でディレクトリを切って
そこにまとめるのが良いのです。
3..gitignoreの設定をする
.gitignoreを設定していなかったプロジェクトにぶち当たった事もあるので一応言及しておきます。
最初から.gitignoreを作成しておきましょう。
↑UnityであればGithubのテンプレートで存在します。
そちらを使うとほぼ間違いないです。
4.AndroidManifest.xmlの設定をする
AndroidManifest.xml
はAndroid向け開発で必要です。
ビルドの際に使われ、
Androidアプリの設定に使われます。
初期状態ではファイル自体存在しません。
後々必要になるため用意しておきましょう。
配置場所はコチラ。
Assets/Plugins/Android/AndroidManifest.xml
ただ作るだけではなく必要最低限の設定を入れておきます。
ゲーム開発でAndroidのマルチウィンドウに
対応する事はほぼないため、
マルチウィンドウをオフにしておきましょう。
💻ソースコード : マルチウィンドウをオフにするサンプル
<?xml version="1.0" encoding="utf-8"?>
<manifest
~~~~ 略 ~~~
<application ~~~~ 略 ~~~ android:resizeableActivity="false">
~~~~ 略 ~~~
</application>
</manifest>
5.コーディング規約を決める
コーディング規約は揉めます。
エンジニアが揉める第一候補です。
メンバー間の宗教戦争に発展する場合もあり。
地味に時間を取られてしまうため、解決方法を提案します。
Riderに任せる
Riderデフォルト設定のコーディング規約に準ずる。
つまりRiderが指摘したら修正するということです。
コーディング規約決定に時間を割くのはナンセンス。
規約にいくら時間をつかっても
ゲームは完成しません。
今のプロジェクトはこの方針で進めています。
「Riderさんがそう言ってるから仕方ないよね♥」
前述の通りただしRiderのバージョンごとに
微妙に異なるためバージョンを揃えることをオススメします。
6.MonoBehaviourクラスのAwake、Startメソッドは極力使わない
- Aawke
- OnEnable
- Start
- Update
などなど。
Unityのライフサイクルイベントは基本使用禁止です。
- 実行順がわからない(保証されない)
- 処理の実行を検知しづらい
以上の理由から MonoBehaviourが提供するイベント関数は
極力使わない ようにしています。
後から入ったメンバーも処理の流れを
理解しやすいというメリットにも繋がります。
7.多人数で開発できるような設計
人数が集まれば開発速度が上がるという事はありません。
- 開発フェーズのタイミング
- 参画するメンバーのスキル依存
人員を投入して量産するフェーズであれば
開発速度の上昇が見込めます。
逆に設計フェーズでは
お荷物になる可能性があります。
またもう1つ大きな原因として、
多人数で開発できる設計になっているか?です。
- 疎結合な実装になっているか
- Viewとロジックが別れているか
ソースコードだと上記のような話です。
Unityの場合は、GameObjectの構成も重要です。
- 100画面あるUI
- 1画面の開発に1日かかる
こういうシステムがあったとします。
100画面を1つのPrefabで作成したとします。
仕様変更で20画面同時に修正が発生したらどうなるでしょうか。
1つのPrefabは1人しか作業できないため、
合計20日かかります。
逆に1画面1Prefabで分割されていれば、
4人で対応したとして5日、
20人ヘルプしてくれれば1日で完了します。
なにがいいたいかというと、
お金の力でどうにかできるようにしておく
ということが大事なのです。
それがリスク回避です。
前者の場合、お金ではどうにもなりません。
1人でがんばるしかないのです。
各画面を1Prefabずつ分けておくと良いです。
また、その画面を構成するパーツ要素も細かく
prefab化できていると多人数で開発をしやすくなります。
8.文字列をソースコードやPrefabへの直書き回避とローカライズ対応
任意の文字列をソースコードや
Prefab(Textコンポーネント等)に直書きすると、
以下の状況で困ります。
- 文字列を一括置き換えができない
- 多言語対応(ローカライズ)する時に文字の使用箇所がわからない
そんな時は、キーを元に文字列を取得する
ヘルパークラスを用意すると良いです。
lang,JP,EN
yes,はい,YES
no,いいえ,NO
cancel,キャンセル,CANCEL
hp,HP,HP
上記のようなcsvを実行時にロードできるようにしておきます。
// 日本語をセット
Localization.SetLanguage("JP");
var str = Localization.Get("yes");
Debug.Log(str); // output : はい
このような感じでキーを引数にして
文字列を取得するように実装しておくと、
文字列がソースコードに散らばらなくなって良いです。
Localization.SetLanguage("EN");
とすれば、
機械的に英語に文字列が置き換わるようになるため、
ローカライズ対応の手助けになるかもしれません。
9.Unityのシーン遷移基盤開発
ある程度大きなUnityプロジェクトになってくると、
シーンを分ける事があります。
分けた際に、そのシーン間を
遷移させる仕組みを設計、実装しておくと良いです。
SceneManager.LoadSceneAsync("シーン名", LoadSceneMode.Single);
上記のUnity標準のSceneManager
使っても実現できます。
しかしシーン間に演出、表現をはさみたい場合、機能不足です。
こういう場合はSceneManagerのラッパークラスを作って対応します。
どんな演出が考えられるか?
前述した通り演出が挟まるのであれば、
その分の余白を残して実装する事が重要になります。
- Tips表示
- アセットローディングバーの表示
- 暗転させる
などなど。
それらを仕様が無いながらも、
他ゲーム、今までの経験を参考に拡張できるような
シーン遷移管理クラスを作っておくと楽になります。
10.画面遷移システムの作成
先程はシーン to シーン
の話でしたが、
今回は1つのシーン内での画面遷移についてです。
画面遷移は必ず必要になります。
デザイン次第で大きく変わりますが、
とりあえず動くものを作っておきましょう。
// 全画面をEnumで定義する
public enum DisplayType
{
None = 0,
Home = 1,
Quest = 2,
Gacha = 3,
Menu = 4
}
画面をEnumで定義します。
上の画像のように、Enum値と画面prefabを紐付けておきます。
// 画面遷移開始
DisplayManger.Instance.Goto(DisplayType.Home);
上記のようなコードで画面が遷移するように実装しています。
すごくざっくりな話になっていますが、
表示中の画面Prefabを指定した
Prefabに入れ替える実装をしています。
11.どのシーンからでも開発できるような仕組みの設計
どのシーンからでも起動できるようにしておくと、開発の効率は上がります。
それを実現するために、各シーンのエントリーポイントとなる共通クラスを継承したクラスをシーンのルートに配置しておくというルールを設けて実装しています。
例えば
- タイトルシーン
- アウトゲームシーン
- バトルシーン
という3シーン構成であれば、
それぞれにSceneEntry
クラスを
継承したのコンポーネント
- TitleSceneEntry
- OutGameSceneEntry
- BattleSceneEntry
これらを各シーンのルートに配置します。
シーンエントリーポイントクラスの仕事とは?
- 通常起動とそれ以外の起動の処理における初期化処理の共通化
- クラス名の通り、そのシーンのエントリーポイントとして動くようにする(そのために、Awake、Startをあえて使わないようにしてます)
以下各シーン毎の起動例を紹介します。
タイトルシーンから起動した場合(通常起動)
- ユーザーが存在しなければ利用規約のダイアログを出す(ユーザーが存在すれば省略してログイン)
- 利用規約を許可したらユーザーを作成
- マスターデータの取得
- アセットのダウンロード
タイトルシーン以外から起動した場合(非通常起動)
- ユーザーが存在しなければ、利用規約など出さずにユーザー作成。存在すればログインする。
- マスターデータの取得
- アセットのダウンロード
という処理フローになり、通常起動と違って色々省略されています。
これらの処理の分岐をシーンエントリーポイントクラスが担っており、
このクラスを使う事でどのシーンからでも起動できるようにしています。
開発効率がとても良くなるためオススメです。
しっかり保守していきたいところです。
12.サウンド再生基盤の開発
ゲーム開発する上ではサウンド再生は必ず必要になります。
開発序盤ではどんな技術を採用するかは決まらない可能性が高いです。
- Unity標準サウンド
- CRIなどのミドルウェア
など。
そこで何を採用しても大丈夫なように
再生部分を以下のようなインターフェースにしておきます。
public interface ISoundPlayer
{
int Play(string soundName);
void Stop(int id);
void SetVolume(float volume);
void SetMute(bool isMute);
}
開発初期はUnity標準サウンドで実装しておいて、仕様が決まってきたら必要な技術に内部実装を置き換えていく想定です。
13.動画再生基盤の開発
動画を再生させる可能性がある場合は、
事前に再生検証をしておきたいです。
前述したサウンド再生基盤動画版なので内容は割愛します(考え方は同じです)。
一点、ゲーム内設定のBGMボリュームを動画再生時に反映するというのを忘れやすいので注意です。
実機で再生できるかどうかの検証はもちろんですが、
動画は比較的ファイル容量が大きくなるため、
ファイルをサーバーに置いて、
ダウンロード時間の確認(実機で)を早めにしておくとよいでしょう。
14.ゲームエフェクトの再生と再利用機構の開発
バトル中に大量のゲームエフェクトが表示される事が想定される場合、
エフェクトオブジェクトを再利用できる機構を事前に作っておくと良いです。
- 大量のエフェクトを毎度生成するとGCが発生しやすい
- Instantiateがそもそも処理的に重いため、スパイクの原因になる
ユーザー体感をより良くするために
できる限りエフェクトを再利用できるように設計しておきます。
またバトル開始前のローディング中に
あらかじめ必要なエフェクトをロードする機能を
追加しておくと良いでしょう。
15.ローカルファイル保存の仕組みを用意しておく
ローカルに保存するのはよくあるので、
事前に仕組みを用意しておく事ができます。
内部の実装は置き換えられるように一旦ラッパークラスを定義します。
よく使いそうなメソッドを用意しておきます。
- SetInt
- SetFloat
- SetString
- SetBool
- Set
- GetInt
- GetFloat
- GetString
- GetBool
- Get
💻ソースコード : ローカル保存の例
LocalStorage.SetString("key", "Sumzap");
var value = LocalStorage.GeString("key");
Debug.Log(value);// output : Sumzap
16.外部アセットを想定したロード処理を作る
アプリ外部からアセットをダウンロードすることは
今のスマホゲームでは必須機能です。
Unityが提供するアセットバンドル、
Addressable Asset System(以下:AAS)これらが該当します。
ただそれらを使うにしても
機能を作らないといけません。
開発序盤にアセットシステムが無い、
または何を使用するかを決めていない場合もあると思います。
決まっていないからといって、
実装は後回しにするのはオススメしません。
ゲーム全体の作り直しのリスクが発生します。
そうならないような実装を最初から心がけたいところです。
インターフェースを利用して抽象化
public interface IAssetBundleManager
{
void Load(string assetBundleName, System.Action onComplete, System.Action onError = null);
void Release(string assetBundleName);
}
このような外部アセットのロードと解放を
実装したインターフェースを定義しておきます。
開発序盤はResources.Load
で実装しておき、
後から実際のAssetBundleまたはAASに
置き換えていくと良いかもしれません。
17.Androidバックキー設計をする
後に回すと面倒なのがAndroidバックキー対応です。
バトル中やバトルのリザルトでは不要な場合がほとんどなので、Androidバックキーが必要になるのは主にアウトゲーム側です。
Androidバックキーを実行した時は以下の処理を想定して実装しておく
- ダイアログ表示中だったらダイアログを閉じる(閉じると不都合なダイアログが存在するので、分岐できるように設計しておく)
- 画面遷移システム側で履歴を保持して前の画面に戻る
- ホーム画面でバックキーが押されたらタイトルへ戻す(遷移履歴を消す)
- タイトルではアプリを終了する
Androidバックキーの連打対応
画面遷移演出中、API実行中などAndroidバックキーが動いては不都合な状態が存在するので、演出中、API実行中などAndroidバックキーが動いてはいけない状態をフラグとして取得できるようにして、trueだったらAndroidバックキーを押しても動かないようにしておきます。
18.ローカルPush通知の実装をしておく
ローカルPush通知もスマホゲーム開発なら
必ず実装する機能なので、先に手を付けておきます。
以下のような要件が想定されます。
- アプリを終了した時、サスペンドした時にローカルPushを指定の時間後に予約する
- アプリを起動した時、ローカルPushの予約を全て消す
- 設定からON/OFFできる事が多いので、設定値を取得できるようにする
- Push通知の許可タイミングを任意のタイミングにできるようにしておく
サクッと無料で実装しておきたい場合は、
Unity公式のMobile Notifications
という
パッケージがPackage Managerからインストールできます。
今回は42のTipsのうち18までを紹介しました。
まだ半分ですが「うへぇ〜〜」と思ったあなた!
これでもひましなくて済みますね。
※後編公開しました
開発初期に暇を持て余しているUnityエンジニアができる42のTips後編
そして今回紹介した内容を実装し終えたとしても、
これで終わりではありません。
プロジェクトが進む中で見直し、
修正して適正な姿に変化させていく 必要があります。
まずは手を動かし形にしてみてください。
これらのTipsが皆様の開発のお役に立てたなら幸いです。
本記事はサムザップ Advent Calendar 2019 #2の12/11の記事でした。
明日は@kazuhiro1128さんの「Next.js + FirebaseAuthでサインイン機能を実装してみた」です。
この記事が気に入ったらフォローしよう
「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
ぜひチャンネル登録をお願いします!
最後まで読んでいただきありがとうございました!
すばらしいUnityライフをお過ごしください。