渋谷ほととぎす通信

新しいこと、枯れたこと問わずサムザップ大庭が興味を持ったものを調査、生活の効率を求める完全趣味の技術ブログ。基礎を大事にしています。※あくまで個人ブログであり所属組織とは関係ありません

OpenUPMからDOTweenをインストールする

OpenUPMはおそらく個人で開発のパブリックレジストリを提供しているプロジェクトです(多分)。


今回はこのOpenUPMからDOTweenをインストールして、UnityのPackageManagerで管理することにします。今までだとAssetStoreやgithubからダウンロードしてProject内に入れてから使っていましたが、最近のUnityは外部ライブラリはPackage化の流れがあるので、その波に乗る練習です。


予めOpenUPM CLIをインストールしておきます。

npm install -g openupm-cli

※事前にnodejs、npmをインストールしておく必要がありますが、ここでは割愛

OpenUPMからDOTweenを探してUnityにインストール

searchコマンドでキーワードを入れるとそれっぽいものが候補にあがります。

openupm search tween

f:id:esakun:20200704165820p:plain:w450

キャプチャのようにDOTweenが現れるので、Name部分をキーにして追加します。

Unityプロジェクト直下で、以下のように実行します。

openupm add com.demigiant.dotween

f:id:esakun:20200704170231p:plain:w450

このようにmanifest.jsonが書き換わります。


f:id:esakun:20200704171023p:plain:w450

もろもろインポートが走り、このようにPackageManagerも更新されインストールが完了しています。

環境

  • Unity2019.4.1f1

参考

ShaderGraph テクスチャの参照をシェーダに渡す部分でハマる。_MainTexはどこ?

Unityにて、

  • UniversalRP
  • ShaderGraph

この2つを使った描画の検証をしています。
ShaderGraphで書いたシェーダに外からテクスチャをスクリプトで渡す際に軽くつまづいたので共有します。

作っているもの

カラーテクスチャとマスクテクスチャを渡したマスクシェーダを作っています。
以下の合成後の状態を作るシェーダです。

カラーテクスチャ マスク 合成後
f:id:esakun:20200629085130p:plain:w120 f:id:esakun:20200629085259p:plain:w120 f:id:esakun:20200629085314p:plain:w120

ShaderGraph

右上のBackboardから公開プロパティの設定をします。

f:id:esakun:20200629084140p:plain:w450


拡大すると以下。 f:id:esakun:20200629084616p:plain:w450

+ボタンからプロパティは作れますが、その時につける名前がスクリプトから参照できるキーだと思ってのですが、違いました。

Referenceがスクリプト参照キー

調査した結果、Referenceがスクリプトから参照できるプロパティ名でした。上のキャプチャでは、以下のコードでテクスチャをセットできます。

material.SetTexture("Texture2D_AD98B9C2C", _texture);

f:id:esakun:20200629084448p:plain:w450

Referenceの自動で生成されるっぽい名前がスクリプトから参照できる名前のようです。 ちなみに、最初リネームできることに気づかなくて、_MainTexを定義したいのに定義できない!という事にハマりました。


キャプチャのようにReferenceの文字列を_MainTexにリネームして解決しています。

検証環境

  • Unity2019.4.0f1

余談

今回のシェーダがuGUIのRawImageで使うシェーダだったのですが、_MainTexプロパティが存在しないと以下のエラーが出ます。

Material doesn't have a texture property '_MainTex'
UnityEngine.Canvas:SendWillRenderCanvases()


以上です。

macOS CatalinaとUnityの相性悪いんじゃないか説

f:id:esakun:20200618205326p:plain

macOS Catalinaに先日アップデートしました。
それ自体に問題はありません。


f:id:esakun:20200618205508p:plain

しかしUnityで開発中時々登場するダイアログ↑。

Developer Tools Accessは、デバッグを続行するために別のプロセスを制御する必要があります。これを許可するには、パスワードを入力してください。

IDとパスワードを入れてもUnityはフリーズしてしまうので、Unityを再起動するしかない状態に追い込まれます。

macOS Catalinaにして状況が変わった

Unityの再起動だけならまだ我慢できるのですが、macOS Catalinaになって、Unityのプロセスは切っているはず(強制終了)しているはずなのに、Unityのウィンドウが消えません


macの再起動するしかなくなりました。

ダイアログ表示の回避を探す

ありがたいことに同じことに悩んでいる方がいらっしゃったので、参考に以下のコマンドを叩きました。

$ sudo /usr/sbin/DevToolsSecurity --enable

これで、しばらく様子を見ることにします。

再現環境

  • Unity2019.2.8f1
  • Unity2019.4.0f1 LTS

AndroidがUnityProfilerにつながらない時にチェックした5つの事

環境

  • Windows10
  • Unity2019.3.15f1
  • Pixel3XL

UnityProfilerに実機のAndroidをつないでテストしたいときにつながらない!!っていう時が発生したので、その対応をログっておきます。
※有線(USBケーブルにつないで)テストです

1.Developmentビルド

f:id:esakun:20200607012338p:plain:w500 Development Buildに必ずチェックを入れてビルドします。 また、Development Buildの場合は画面右下にDevelopment Buildという文字が表示されるので確認できます。

2.USBデバッグをON

f:id:esakun:20200607012655p:plain 開発者向けオプションから指定します。

3.ポート転送をセットアップ

adb forwardコマンドを使って、コマンドを使用して任意のポート転送をセットアップしておきます。

adb forward tcp:34999 localabstract:Unity-【アプリのidentifier】

上記をコマンドラインで実行します。 f:id:esakun:20200607013858p:plain 成功するとこのようなログが表示されます。

参考 : Android Debug Bridge(adb)  |  Android デベロッパー  |  Android Developers

'adb' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。

上記のようにadbコマンドが存在しない場合は、SDK Platform Toolsをダウンロードしてadbまでのパスを通ります。
参考 : Windowsでadbコマンドを使う方法! Android SDKを入れてパソコンのコマンドプロンプトから操作しよう

4.UnityProfilerのターゲットを実機へ変更

f:id:esakun:20200607013558p:plain:w500 デフォルトエディタがプロファイルのターゲットになっていますが、プルダウンから実機に変更します。

5.最終兵器Unity再起動

どうしてもだめな場合があります。
そんな時はUnityの再起動をしましょう。

困ったときはTwitterでつぶやくと、このように優しい人が教えてくれるかもしれません。


以上

uGUIのCanvasGroupがしている事とは何か?

全子要素のアルファを一括で操作できるため、uGUIのCanvasGroupコンポーネントはUnityでUIを作る上で重宝しています。

ところCanvasGroupはどのようにしてアルファを適用しているのかを確認しました。 というのもuGUI用のシェーダを開発する上でCanvasGroupが効かないという現象に遭遇しました。 その調査結果となります。


結論から書くとCanvasGroupは頂点カラーを操作しています。


厳密に書くと、自分自身と子階層の頂点を取得し、その頂点カラーのアルファに値を突っ込んでいると思われます。


以下はUI-Default.shaderのフラグメントシェーダの冒頭です。

fixed4 frag(v2f IN) : SV_Target
{
    half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

以下のようにIN.colorという頂点シェーダから送られる頂点カラーの乗算を外してみます。

fixed4 frag(v2f IN) : SV_Target
{
    half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);

するとCanvasGroupが適用されなくなります。
よってCanvasGroupが頂点カラーを操作しているという事が分かります。

環境

  • Unity2019.2.8f1

シェーダバリアントが多く作られるシェーダの最大使用メモリを計測する

Unityでキーワードを追加して処理を分岐させながらシェーダを書いていくと、知らず知らずのうちにシェーダバリアントが大量に増えることがあります。シェーダバリアントを作成するためには#prgama multi_compileまたは#pragma shader_featureを追加だけなのでとても簡単です。


以前multi_compileについて執筆したので参考にどうぞ。

シェーダバリアントが増えることによってコンパイルしたシェーダが占有するメモリが増えていきます。本記事ではシェーダバリアント増加によるメモリ使用量の計測について説明していきいます。

multi_compileとshader_featureの違い

前述した通りmulti_compileとshader_featureはシェーダバリアントを作成するために使用します。

  • shader_featureはビルド時に使用している組み合わせのみコンパイルされる
  • multi_compileは定義した組み合わせ全てをコンパイルされる

という大きな違いがあります。
基本はshader_featureでシェーダバリアントを作成した方が生成される個数は最小限に抑える事ができそうです。


Uniteでのポストモーテムセッションや技術ブログを読むと、最適化する上でmulti_compileからshader_featureに置き換える話を見かけます。


そして、ここからが本題です。

シェーダバリアントが最大でどのくらいのメモリを使用するかを把握する必要性がある

shader_featureで指定されているシェーダバリアントが、本来どのくらい生まれるかは開発、運用してみないと分かりません。


なぜなら前述した通り、ビルド時に使用しているキーワードの組み合わせしか作成されないためです。運用を開始してからシェーダを改修するのは難易度が非常に高くなるため、開発中(せめて中盤頃)にコンパイルしたシェーダが占めるの最大使用メモリは決めておきたいところです。


もう少し詳しく説明すると、集団開発する上でシェーダはエンジニア以外も触ります。Unityエンジニアが想定する範囲内での使用なら問題ないですが、例えばクリエータがそういった暗黙の決まりを知らずにシェーダを使ってしまう場合もあります。
すると、いつの間にかシェーダバリアントが爆増して、メモリ使用量が増えてアプリが落ちたり、またシェーダのコンパイルに依るスパイク(カクツキ)が問題になったりします。

※スパイクについては本記事では割愛


以上の理由からUnityエンジニアはシェーダバリアントの影響範囲を把握しておくべきだと考えます。

一時的にshader_featureをmulti_compileに置き換えてみる

どうやってシェーダバリアントの影響範囲を把握するかですが、一時的にshader_featureをmulti_compileに置き換えて検証してみます。

こうすることで全てのシェーダバリアントを作成することができます。この状態でコンパイルしたシェーダが占めるメモリを計測して、そのシェーダのポテンシャルを測ります。

計測方法

UnityEditor上で実行するプロファイラの値にはノイズが入りすぎててあてにならないので、必ず実機を使って計測するようにしています。具体的にはUnityEditorのProfilerに実機(iPhone6s)を繋ぎ、その中のMemoryProfilerで確認します。

※PackageからインストールするMemoryProfilerではありません

f:id:esakun:20200504163023p:plain:w350 Window > Analysis > Profilerから起動します。

MemoryProfilerのグラフ

シェーダコンパイル前

f:id:esakun:20200504152658p:plain:w300

シェーダコンパイル後

f:id:esakun:20200504152711p:plain:w300 Total Allocatedの値が大きく変化しています。
このビューでは詳しいことは分からないので詳細を見ていきます。

MemoryProfilerのSimpleビュー

シェーダコンパイル前

f:id:esakun:20200504152909p:plain:w450

シェーダコンパイル後

f:id:esakun:20200504152932p:plain:w450

UnityProfiler => MemoryProfilerのSimpleビューで確認すると、Unityが管理するネイティブメモリが40MBほど増えている事が分かります。

Detailビューで更に詳しく見ていきます。

MemoryProfilerのDetailビュー

シェーダコンパイル前

f:id:esakun:20200504153448p:plain:w450

シェーダコンパイル後

f:id:esakun:20200504153301p:plain:w450

Other > Rendering > ShaderLabの値が約40MBほど増加しています。コンパイルしたシェーダはネイティブメモリとして計上されているようです。

f:id:esakun:20200504163802p:plain:w350

Otherが何者なのかについては以下のリファレンスを参考にどうぞ。
Memory プロファイラー - Unity マニュアル

サードパーティMemoryProfilerでは確認できない

ここは余談になります。

UnityのMemoryProfilerに2種類あります。
Profiler付属製とPackageManager経由でインストールするサードパーティ製です。

f:id:esakun:20200504160010p:plain:w450 サードパーティ製のMemoryProfilerはこのようにグラフィカルにメモリ分布を表示してくれてとても見やすいのですが、コンパイルしたシェーダのメモリ使用量は表示してくれないようなので注意です。

念のためにXcodeで確認

UnityEditorでのプロファイリングでほぼほぼ事足りますが、計測した値が正しいかXcodeでのプロファイリングもしてみます。

シェーダコンパイル前

f:id:esakun:20200504164650p:plain:w450

シェーダコンパイル後

f:id:esakun:20200504164632p:plain:w450

約57MB増えています。UnityEditorで計測する値と一致はしませんが、これが実際にこのアプリで使用されているメモリということになります。そこまで大きく値がずれていないという事が確認できました。

Xcode付属のプロファイラInstrumentsを使用すれば更に詳しく確認できると思いますが、今回は時間の関係上調査しません。

f:id:esakun:20200504165313p:plain:w450 UnityEditor上では気づかなかったのですが、シェーダコンパイル時に一瞬ピークメモリが跳ね上がるのも気になります。 こちらも同様Instrumentsで確認すれば何かしら要因が見えてくるかもしれません。

最後に

multi_compile、shader_featureとシェーダバリアントという機能はとても便利です。フルスクラッチでシェーダをこれらのパターン書く事を考えると本当に神機能だと思います。

ただ神機能も調子にのって使いすぎるとシェーダバリアントが大量に作られてしまい、メモリを大量に使用してしまっていたという事も発生します。


最大でどのくらいメモリを消費するかを把握し、アプリ全体のピークメモリを制御する事で安全に開発を進められたらと思います。また日々実機でMemoryProfilerの値をチェックして異常な値になっていないかをウォッチする事も大事。

環境

  • macOS HighSierra 10.13.6
  • Xcode10.1
  • Unity2019.2.8f1
  • 検証実機 iPhone6s ios10.3.1

参考

UnityからiOSビルドしてXcodeで実行したらトラブったのでそのメモ

環境

  • macOS HighSierra 10.13.6
  • Unity2019.3.0f6
  • Xcode10.1
  • iPhone6s iOS10.3.1

はじめに

久々にXcodeの実機デバッグをやりたくなりました。
そこでUnityからiOSビルドしてXcodeで実行すると期待通り?1発では上手くいかなかったので、今回の対応ログを残しておきます。

Failed to create provisioning profile. There are no devices registered in your account on the developer website. Plug in and select a device to have Xcode register it. というエラー

f:id:esakun:20200405003723p:plain:w550 前準備としてXcode > Preferences > AccountsからAppleIDはログイン済みです。


この状態でUnityから書き出したXcodeプロジェクトを開くと以下のようなエラーが出ました。

f:id:esakun:20200404235910p:plain:w550

Failed to create provisioning profile. There are no devices registered in your account on the developer website. Plug in and select a device to have Xcode register it.

iOS実機をMacに繋いだら直った

f:id:esakun:20200405004253p:plain:w450


手元のiPhone6sをMacにUSBケーブルで繋ぐと上図のようにエラーが消え、ビルドできるようになりました。

余談のアラート

f:id:esakun:20200405000240p:plain:w450

Xcodeで実行時にこのようなダイアログが出るときは、実機に構成プロファイルがインストールされていないです。


Verify the Developer App certificate for your account is trusted on your device. Open Settings on "デバイス名" and navigate to General -> Device Management, then select your Developer App certificate to trust it.

構成プロファイルのインストール

設定 > 一般 > デバイス管理 を選択します。


f:id:esakun:20200405005351p:plain:w300

今回追加する構成プロファイルをタップします。


f:id:esakun:20200405005450p:plain:w300

「アカウント名」を信頼をタップすると構成プロファイルはインストールされ、Xcodeから書き出されたアプリが実行できるようになります。


参考 : iPhoneで構成プロファイルをインストールする/削除する - Apple サポート

まとめ

今回のXcodeエラーは単純で実機をMacに繋いでなかったことによるものでした。

ジェンキンスでビルドの自動化してそれに慣れすぎていると、いざローカルPCでビルドする時に手間取ります。Xcodeで実行してInstrumentsでプロファイリングするみたいなことは、発生しやすい案件なので、定期的にビルド環境は整えておきたいところです。

その他Xcode関連のトラブル記事

知っておいた方が良いUnityのProject検索窓の小技

最近ブログを書いていなかったので、リハビリがてら小ネタを投下します。


僕はそこそこの頻度でUnityのProjectの検索窓を使用します。

HogeButtonというファイルを検索する場合

f:id:esakun:20200310010803p:plain
みたいな感じで入力します。

HogeShibuya24Buttonというファイルを検索する場合

HogeShibuya24Buttonというファイルを検索する場合は、HogeButton ではヒットしません。

今回紹介する小技はShibuya24というキーワードが思い出せない....みたいな時に使えます。

スペースを挟んで検索する

Hoge Buttonとすると、

f:id:esakun:20200310011420p:plain

  • HogeBarButton
  • HogeFooButton
  • HogeShibuya24Button

といった感じでOR検索することができます。

OR検索ですのでButton Hogeと入力しても同じ結果になります。

f:id:esakun:20200310011436p:plain


また、大文字小文字は区別されないので、hoge buttonとしても同様の結果になります。

f:id:esakun:20200310011451p:plain

最後に

プロジェクトが大きくなるとお目当てのファイルも探しづらくなります。そんな時に検索窓を有効に使いたいところです。 OR検索がUnityでも出来るということを知っておくと多少は作業効率が上がるのではないかなと思います。


以上

環境

  • Unity2019.2.8f1

TexturePackerで画像を追加する際のちょっとした効率化Tips

Unityでゲーム開発中アトラスを作成する際によくTexturePackerを使用します。 アトラスとは複数の画像を一枚の画像と座標情報をセットにして書き出したもので、CPU負荷削減のために使用されます。

f:id:esakun:20200222231238p:plain:w500

本記事ではTexturePackerで画像追加時の効率化アップTipsを1つ紹介します。


TexturePacker公式サイト

非効率な画像追加の方法

以下のようにtpsファイル(TexturePackerファイル)、1~4の画像を例にします。

f:id:esakun:20200222223012p:plain:w300 これらの画像をTexturePackerでアトラス化するとこうなります。


f:id:esakun:20200222223522p:plain:w500 書き出す前のTexturePackerの状態です。


この状態から5枚目の画像を追加してみます。

f:id:esakun:20200222224318p:plain:w500 追加したい画像を図のようにSpritesエリアにドラッグアンドドロップします。


f:id:esakun:20200222224451p:plain:w500 するとこのように画像が追加されます。


追加はできますが、毎度毎度画像が追加されるたびにドラッグアンドドロップするのは手間です。

またこの例では5種類しか存在しないので良いですが、大量の画像を扱った際に何を追加したいのかが分からなくなる可能性もあります。


以下の方法を使うと多少効率が上がります。

ルート階層をフォルダにする

画像を個別にTexturePackerにドラッグアンドドロップするのではなく、ルート階層にフォルダを追加してそのフォルダに画像を追加するようにします。

f:id:esakun:20200222225825p:plain:w500 TexturePackerにフォルダをドラッグアンドドロップします。今回はSpritesという名前にしました。


f:id:esakun:20200222230042p:plain:w250 Finder上から画像をSpritesフォルダに追加しました。


f:id:esakun:20200222230340p:plain:w500 するとこのように自動でTexturePacker側が更新されます。


いちいち画像をドラッグアンドドロップする必要がなく、あくまでフォルダの中身を更新するだけで良いのと、フォルダの中身は必ず追加されるため、追加の抜け漏れがなくなります。

まとめ

TexturePackerはフォルダをルート階層にすると以下のメリットがあります。

  1. 追加画像をドラッグアンドドロップする作業がなくなり、オペレーション工数が下がる
  2. 画像追加の抜け漏れがなくなり管理コストが下がる

もしルート階層をフォルダにしていない場合は一度試してみると良いかもしれません。


以上

Unityからmicro:bitへ無線通信する方法

以前micro:bitを入力デバイスとしてUnityに無線通信で送信するサンプルを作りました。

今回はUnityを入力デバイスとしてmicro:bitへデータを送信してみます。

f:id:esakun:20191229054510p:plain:w450

準備するもの

  • Unityインストール済みパソコン
  • micro:bit 2個

処理の流れ

  1. C#側からUSBにつながったmicro:bitへ文字列をシリアル通信する
  2. USBにつながった送信micro:bitは文字列を受け取って受信micro:bitへ数字を送信
  3. 受信側micro:bit(電源は電池)は数字を受け取ってLEDを光らせる

準備 : 各micro:bitのソースコードを用意してインストール

以下のソースコードをmicro:bitのオンラインエディタで編集、ダウンロードしてインストールを済ませておきます。

USBでつないでいるmicro:bitのソースコード

// micro:bit間で同じ数字のグループをセットしておく必要あり
radio.setGroup(1)

let sendNum = 0

// Unityから文字列を受け取る
serial.onDataReceived(serial.delimiters(Delimiters.Colon), function () {
    basic.showNumber(sendNum)
    // 受信側micro:bitに数字を送信する
    radio.sendNumber(sendNum)
    sendNum++
    if (sendNum > 2) sendNum = 0
})

受信側micro:bitソースコード

radio.setGroup(1)

// micro:bitから数字を受け取ったらLEDを光らせる
radio.onReceivedNumber(function (n) {
    if (n == 0)
        basic.showIcon(IconNames.Heart)
    else if (n == 1)
        basic.showIcon(IconNames.Asleep)
    else if (n == 2)
        basic.showIcon(IconNames.Butterfly)
})

Unityからmicro:bitへシリアル通信する処理

Unity側の全ソースコードは、全公開していますので、ここでは概要の説明になります。

以前作成したSerialiPortWrapperを使います。

送信ソースコードの抜粋ですが、以下のようにWriteメソッドで任意の文字列をUSBでつながったmicro:bitに送信します。

void Send()
{
    // micro:bitに送信する任意の文字列
    _serialPortWrapper.Write("hoge:");
}

文字列を受け取ったmicro:bitは、無線状態のmicro:bitに数字を送信します。
※数字はあくまで今回のサンプル用で、文字列も送れます。

完成


このような感じでUnity上でマウスクリックするとmicro:bitにシリアル通信するようになりました。

無線状態のmicro:bitのLEDがUnityからの入力で切り替わっています。

Unity側のソースコードはこちらにアップしています。


レイテンシが気になりますが、数行でmicr:bitに無線通信できるのでお手軽です。

環境

  • Unity2019.2.9f1

RiderがUnityの.NET4環境を認識しない場合の対処法

f:id:esakun:20191229044651p:plain

UnityのPlayer Settingsの.NET4.xに設定を変更すると、Rider側が認識せずコンパイルエラー状態になりコード補完が効かなくなりました。

※ただし、Unity側は正常にコンパイルは通ります。

Unityの設定画面で解決

UnityのPreferences > Riderから、Override TargetFramework Versionにチェックを入れて4.6を入力すると正常に動くようになります。

f:id:esakun:20191229044538p:plain


以上です。

環境

  • Unity2019.2.9f1
  • Rider2018.3.5

開発初期に暇を持て余しているUnityエンジニアができる42のTips後編

f:id:esakun:20150730215258g:plain:w450

本記事は、サムザップ Advent Calendar 2019 #1 の12/24の記事です。


株式会社サムザップでUnityエンジニアをしている大庭です。
グラフィックデザイナー、Flashデベロッパーを経て、現在はUnityエンジニアでスマホゲームを作っています。Unity歴は6年くらいです。
今までコマンドバトル、ピンボール、麻雀などを作ってきました。


これから紹介していく内容は、開発初期後回しにされがちな、でも初期にやっておいた方が良いことばかりです。 僕はUnityエンジニアとしてプロジェクトを進める上で以下の事を重要視しています。

  • 動くものをいち早く作ってメンバーに共有
  • ワークフロー構築、開発手法を整える事が最優先
  • いきなり正解は出ないので、不都合が出てきたら修正していくスタイル
  • 修正見直しの数を繰り返して精度を上げていく

この思想も取り入れた形でリストアップしました。

本記事の導入

新規ゲームプロジェクトがスタートし、あなたはUnityエンジニアとして参画する事になったと想定します。しかし、ゲーム仕様はまだ真っ白な状態で、計画しているモック開発の要件も決まっていません。


「あ〜仕様も決まってないし、やる事がなくて暇だな〜」


って、


んな事ぁない!!


仕様が無くてもやる事はいくらでもあります。


今回のタイトルを見て「42Tips!?多すぎじゃない・・・!?」と思ったあなた!ぜひ読んでみてください。
今まで新規開発に携わる事の多かった僕が思いつく「仕様がなくてもUnityエンジニアが開発序盤にやれる事」をツラツラ紹介していきます。


本記事は開発初期に暇を持て余しているUnityエンジニアができる42のTips前編の後編です。

UI系

19.UI解像度を決める(UIを作り出す前に必ずやる)

uGUIを使用する場合は、CanvasScalerに設定するReference Resolutionの値を決めるという話です。 デザイナーの元データにも影響があるので、UIを作り出す前に決めておく必要があります。


解像度毎のパターンを作り、対象となる実機何点かで実際に確認して決めれば良いと思います。 その際、スマホと解像度比率の大きく違うタブレット端末での見え方も注意すると良いでしょう。

20.共通UIパーツを共通のPrefabにする

UI画面にはよく各画面同じようなパーツが出てきます。

  • 戻るボタン
  • ダイアログに表示するOKやキャンセルボタン
  • ゲージ
  • 各種アイコン

などなど。


これらをそれぞれの画面で新規で作成するのは無駄なので、デザインがまだ仮の状態の時から共通のPrefabとして使い、デザインが仕上がってきたら、該当するPrefabの中身を差し替えていくのが良いでしょう。ただし、開発が進む中で無理やり使い続けている感が出てくる場合は、ケースバイケースで作り直した方が良いです。

21.ダイアログなどのシーンをまたいで使用するものはDontDestroy領域へ

以下のようなシーン間、または各シーンで共通して使用したいもの、シーンをまたいで削除されたくないものはDontDestroyOnLoad(gameObject);として、シーン遷移時にDestroyされないようにします。

  • ダイアログUI
  • タップ・スワイプエフェクト
  • ローディング、ダウンロード表示
  • Tips表示
  • UIのブロッキング(画面タップを無効化するオブジェクト)

22.ダイアログシステム作成

ダイアログは大抵どのようなゲームでも必要になるので、先に取り掛かります。

暫定要件

  • ダイアログ以外の部分をタップしても、その後ろのコンテンツは反応しない
  • ダイアログ以外の部分をタップすると、ダイアログを閉じる事ができる
  • 細かい事だけど、ダイアログを開く・閉じる時にはSEが鳴るようにする
  • ダイアログは重なって表示できるようにする
  • OKボタン、キャンセルボタン押した時のコールバック設定
  • OK、キャンセルの表示ON/OFF

などなど。


この中でも「ダイアログは重なって表示できるようにする」は、デザイン次第ではやりたくないという意見も出る場合がありますが、その時に考えて一旦動くものを作って体感を確認しながら進めるのが僕は好きです。経験的にもその方がプロジェクトはより早く前に進みます。

 // hogehogeとメッセージを表示させるダイアログを表示する
 DialogManager.Instance.Open(new AlertDialog(){
    message = "hogehoge", 
    okCallback = ()=> Debug.Log("OK"), 
    cancelCallback = ()=> Debug.Log("cancel")
});

上記のような感じでダイアログを呼べるように一旦実装しています。

23.UI用のCanvas共通化処理

UI用のCanvas、CanvasScalerの設定はプロジェクトで共通設定になる事が多いです。 それを毎度毎度設定するのは非効率のため、以下のような自動化できるコンポーネントを作っておきます。

 using UnityEngine;
 using UnityEngine.UI;
 
 /// <summary>
 /// Canvas初期化
 /// </summary>
 [ExecuteInEditMode]
 [DisallowMultipleComponent]
 public class UICanvasInitializer : MonoBehaviour
 {
     private CanvasScaler _scaler;
 
     void OnEnable()
     {
         Execute();
     }
 #if UNITY_EDITOR
     void OnValidate()
     {
         Execute();
     }
 #endif
     void Reset()
     {
         Execute();
     }
 
     void Execute()
     {
         if (_scaler == null)
         {
             _scaler = GetComponent<CanvasScaler>();
         }
 
        // 内容は適宜
         _scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
         _scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
         _scaler.referenceResolution = new Vector2(1920f, 1080f);
     }
 }

このような共通コンポーネントを作っておくと、修正が入った時に対応しやすいです。

24.UI階層構造のルールを決める

デザインによって大きく変わる可能性がありますが、大抵以下のような構成になるので一旦これで作ってしまいます。

各シーン

  • 最背面レイヤー・・・背景
  • コンテンツレイヤー・・・各シーンのコンテンツ

DontDestroyOnLoad領域

  • 通常ダイアログレイヤー・・・通常ダイアログ
  • オーバーレイレイヤー・・・ローディング、Tipsなど
  • システムダイアログレイヤー・・・システム系ダイアログ
  • 最前面レイヤー・・・・タッエフェクトなど

25.タップ・スワイプエフェクトを実装しておく

スマホゲームを作る場合、必ずと言っていいほどタップした時やスワイプした時のエフェクトが必要になります。 優先度的には後でもよい判断をされるかもしれませんが、先に実装しておく事をおすすめします。


タップ・スワイプエフェクトは最前面に表示されなくてはいけません。
後から実装すると、UIの階層関係を構築する際に見落としてしまって、手戻りをしてしまう可能性があります。 僕は表示階層に関わるものに関してはできるだけ優先的に実装しておく事にしています。


また、先に実装し、prefab化しておけば、クリエーターにブラッシュアップをお願いするのも楽です。

26.UI Environmentの設定

Prefab Editing Environmentsは、Unity2018.3からNestedPrefabが導入され追加された設定です。

必ず設定しておいたほうが良いのは UI Environmentです。 uGUI関連のPrefabをプレファブモードで編集する際、uGUIはCanvas配下に存在しないと表示が崩れます。


UI EnvironmentにCanvasやカメラを配置したシーンを設定しておくとプレファブモード時にそのシーンを使用してくれます。 NestedPrefabを使用する際は必須設定なので序盤に忘れないように設定しておきたいです。


ちなみに、UI Environmentに設定するシーン内のCanvasは23.UI用のCanvas共通化処理で紹介した設定を適用すると良いでしょう。

27.iPhoneXセーフエリア対応

セーフエリア対応はデザインの序盤に思想を決めて動けていないと、後々大きな手戻りが発生する可能性があります。
デザイナーとしっかり相談して、意志を統一していきたいところです。


また、iPhoneX系の実機で日々確認するのも大事です。

28.UIカラー設定コンポーネントを作っておく

デザインが進んでいくとキーとなるカラーが決まってきます。
uGUIのパーツを予め白色で作成しておき、プログラムで色を重ねて作成する場合があります。


その際、指定カラーをUnity上でひとつひとつセットしていくのはとても無駄な作業です。 少しでも楽をするための以下のようなコンポーネントを用意しておきます。

 /// <summary>
 /// 決まったカラーをセットする
 /// </summary>
 public class GraphicColorSetter : MonoBehaviour
 {
     [SerializeField] private ColorType _colorType;
     [SerializeField] private Graphic _graphic;
     // trueにすると実行時に指定色を反映する
     [SerializeField] private bool _isRuntimeApply;
     
     void OnEnable()
     {
        if (isRuntimeApply)
             ApplyColor();
     }
 
     public void SetColorType(ColorType type)
     {
         _colorType = type;
         ApplyColor();
     }
 
     void ApplyColor()
     {
         // ColorResolverにColorTypeに対する色を定義して返却できるようにしておく
         _graphic.color = ColorResolver.Resolve(_colorType);
     }
 
 #if UNITY_EDITOR
     void OnValidate()
     {
         if (Application.isPlaying == false)
         {
             if (_graphic == null)
             {
                 _graphic = GetComponent<Graphic>();
             }
             else
             {
                 ApplyColor();
             }
 
             if (_graphic == null)
             {
                 Debug.LogError("Graphic is not Found", this);
             } 
         }
     }
 #endif
 }

このようにプルダウンでカラーを指定し反映する事ができるので効率的ですし、非エンジニアでも対応可能になります。 また開発序盤中盤はカラーの変更はザラにありますが、その反映もソースコードを変更するだけで対応できるのもメリットです。

作業効率化

29.よく使う拡張メソッドを用意しておく

  • Transform
  • RectTransform
  • List
  • String
  • Dictionary
  • Component
  • GameObject

これらよく使うクラスのよく使う処理は、拡張メソッドとして用意しておくと開発効率が上がります。

 using UnityEngine;
 
 namespace Hoge.Extensions
 {
     public static class GameObjectExtensions
     {
         /// <summary>
         /// 存在しなかった場合はAddComponentする
         /// </summary>
         public static T GetOrAddComponent<T>(this GameObject target) where T : Component
         {
             var comp = target.GetComponent<T>();
             if (comp == null)
             {
                 comp = target.AddComponent<T>();
             }
 
             return comp;
         }
     }
 }

※GameObjectの拡張メソッド例

拡張メソッドの注意点

拡張メソッドを使用しているのかどうかがパッと見、分かりづらくなるためネームスペースにExtensionsをつけておく事をオススメします。 先の例でいうとnamespace Hoge.Extensionsの部分です。

30.よく使いそうなシェーダを用意しておく

乗算、加算(カラー加算、加算ブレンドの両方)、ブラーシェーダ等を用意しておくと良いと思います。
※ググれば出てくるので詳細は割愛します

31.テクスチャインポータの作成

ゲームを作っていると様々な画像アセットが必要になります。
ターゲットプラットフォームごとに指定する圧縮テクスチャの種類やクオリティが変わってきます。


これらを毎度設定するのはオペミスに繋がりますし、ミスをしてても気づきづらいため、AssetPostprocessorを使って所定のディレクトリに格納したら、自動でインポータが実行されるようにしておくと良いです。

32.アセットバンドル名を自動で設定されるようにする

アセットバンドルを利用する場合は、参照用のアセットバンドル名を必ず付ける必要があります。
以下は2.Unityプロジェクトディレクトリ構成を決めるで解説したUnityプロジェクトのディレクトリ構成です。

┌ AssetBundleModule/ ・・・サブモジュール
    ├ AssetBundles/・・・AssetBundleにビルドされるものを格納
        Resources/
            ├ chara
                ├  hoge
                ├ foo

AssetBundlesディレクトリ配下に格納すると、AssetPostprocessorで自動でアセットバンドル名が付与されるようにしています。例えばhogeディレクトリにtest.prefabを格納すると、 chara/hoge/testというアセットバンドル名に自動で追加されます。
16.外部アセットを想定したロード処理で説明していますがアセットバンドル名Resources.Loadのパスを共通化するためにResourcesフォルダ名を抜いたパス名にしています

33.最低限使用するであろうアセット導入

以下のアセットはどのようなプロダクトの開発でも必要な汎用性の高いアセットなので、初期から導入しています(執筆時点)。

TexturePackerの採用理由

Unity純正のSpriteAtlasではなく、TexturePackerを採用している理由ですが、以下に挙げている通り、単純にTexturePackerの方がとても優秀だからという点で採用しています。

  • アトラスに固める前の画像をUnity内部に格納する必要がない
  • TexturePackerアプリ上でスライス設定できる(とても操作しやすい。UnityのSpriteEditorは見づら...ごにょごにょ...)
  • 指定したテクスチャサイズから溢れた時に気づきやすい(自動でテクスチャ数が増えないため)
  • フォルダ分けするとフォルダをSprite名にしてくれるので、命名ミスを減らせる
  • 有料といえど安価

34.ランタイム中のデバッグ機能としてSRDebuggerの導入

33.最低限使用するであろうアセット導入でも紹介しましたが、SRDebuggerは入れておいた方が良いです。

SRDebuggerとはUnityEditor、実機上ともにランタイム中のデバッグをしやすくしてくれる外部アセットです。 AssetStoreから購入可能です。リンクはコチラ

詳細は割愛しますが、これひとつでデバッグツールをまかなえるくらい汎用的な神ツールです。

ワークフロー系

35.ローカルで実機ビルドできる環境の整備

自分のPCで実機ビルドできる環境を作っておくのは、プロダクトのプロファイルをする上で大事です。 Xcodeを利用したプロファイルは強力なのでiOSの実機ビルドはできるようにしておくと良いです。

36.アプリ、アセットバンドルのビルド環境整備

アプリ、アセットバンドルのビルド環境は必ず必要になります。 ジェンキンスを使ったビルドジョブを作り、終了したらSlackに通知するようにしています。


アプリはビルドされたら、AppCenter(旧:HockeyApp)などのサービスから開発メンバーに配信できるようにすると良いです。


ビルドされたアプリはビルド番号をアプリ名に入れておくと便利です。

f:id:esakun:20191223103917p:plain イメージこのような感じです。

37.サウンドアセット作成環境構築

Unity標準サウンド以外を使用する事が最初から決まっている場合は、サウンドアセット作成のワークフロー構築に早めに取り組みたいです。 今回はCRIサウンドを使用すると仮定します。 ※Unity標準サウンドを使用する場合は不要


CRIサウンドを利用する場合は、オーディオファイルをCRI専用ファイルに変換しなければなりません。


変換処理にAtomCraftを利用する場合、そのワークフローを事前に検証、環境構築しておくと良いです。 また、大人数でAtomCraftを使用する場合、どのファイルをgitignoreに入れなければならないかなど、時間に比較的余裕のある開発序盤に潰しておくと時間を効率に使えます。

38.動画アセット作成環境構築

37.サウンドアセット作成環境構築の動画版となりますが、動画はエンコードの仕方で大きくクオリティが変わってくるので、検証できる環境を用意しておくと良いと思います。

39.環境切り替えツールの導入

ゲームを開発する上で最低限以下の2種類は切り替えます。

  • 開発環境
  • リリース環境

その他にもステージング環境、サンドボックス課金環境などプロジェクトによっては様々な環境が必要になります。

環境を切り替えた時には以下の情報を切り替えています。

  • 接続先URL
  • アプリ名
  • アプリID
  • 使用シンボル
 public string ApiBaseUrl =>
   #if DEV
    "http://dev.sample.com/api/";
   #elif RELEASE
    "http://sample.com/api/";
   #elif LOCAL
    "http://localhost:8080/api/";
   #else
    "http://dev.sample.com/api/";
   #endif

※環境毎のAPI接続先切り替えの例です。

これらの情報を切り替えられるようなツールを作っておくと後々楽です。

40.クラッシュレポートの導入

クラッシュレポートは開発序盤から入れるようにしています。 開発序盤はバグも多いためクラッシュの理由をいち早く特定するための材料としてクラッシュレポートは助かります。


僕が今関わっているプロジェクトではSmartBeatを導入し、レポートの概要はSlackに通知するようにして常にウォッチしている状態です。

41.UIパーツのレギュレーションまとめページを作っておく

UIパーツの命名ルールはエンジニアリングを知っている人が決めるべきだと僕は思っています。いわゆるTA的な人です。 僕はTA的な動き方をする事もあるので、皆が見れる場所にUIパーツのレギュレーションページを作っておきます。 今のプロジェクトの場合はConfluenceを利用しています。


最初にひな形を作っておくとメンバーは迷わないので、事前にワークフローを構築しておく事は大事です。

  • 画像サイズ
  • ファイル命名ルール ※例 : キャラID_icon.png
  • 格納フォルダパス
  • サンプル画像

以上の情報を1ページにまとめ、変更が入る度に更新しています。 特に開発序盤は更新頻度が多いので、ある程度落ち着いてからでも問題は無いと思います。


繰り返しになりますが、大事なのはワークフローを事前に作っておく事です。

その他

42.SerializeFieldをつけた時に発生するWarningの除去

最後はとても細かい内容ですが、SerializeFieldアトリビュートをつけるだけで、以下のようなWarningが出てきます。

is never assigned to, and will always have its default value null

Unityを使う身としてはSerializeFieldアトリビュートは必ず使うのでこのWarningは不要です。 初期値を入れれば解決しますが僕は一括で消し去りたいので、以下Assetsフォルダ直下にcsc.rspファイルを作成します。

 -nowarn:0649

すると、該当のWarningは消えます。

最後に

前編、後編と長々と書いてきましたがいかがだったでしょうか。
汎用性の高いものだけを紹介しましたが、関わるプロダクトの種類によってはまだまだやる事はあると思います。


仕様がなくてもUnityエンジニアは忙しいということですね(笑)。

今回紹介した大小含めた42のTipsが皆様の開発のお役に立てれば幸いです。


明日は@chrno001さんの記事です。

Prefabモードのまま再生すると編集中のPrefabも実行されてしまう気がした件の検証

f:id:esakun:20150730215258g:plain:w450

Prefabモードのまま、シーンを実行するとPrefabモードのスクリプトが動いているような気がしたので確認しました。

手順

条件付きで動く時がありました。

  1. Prefabモードにする
  2. [ExecuteInEditMode]をくっつける
  3. シーンを再生する

すると、Prefabモードのスクリプトが動いたままになってしまいます。
(シーンに存在しないのにAwake、Start、Updateなどのイベント関数が動いてしまう)

通常は起きない

Prefab Mode in Play Mode was blocked by the script 'PrefabModeTest' to prevent the script accidentally affecting Play Mode. See the documentation for [ExecuteInEditMode] and [ExecuteAlways] for info on how to make scripts compatible with Prefab Mode during Play Mode.

[ExecuteInEditMode]をくっつけたスクリプトが存在するPrefabをPrefabモードにしたままシーンを再生すると、上記の警告が表示されPrefabモードは解除されてしまいまいPrefabモードのままスクリプトが動くことはありません。

OnValidateは例外

ただしOnValidateメソッドだけは例外でExecuteInEditModeを解除した状態且つ、OnValidate内に処理を書いているとシーン開始時にOnValidate内の処理が走ります。

void OnValidate()
{
    Debug.Log(Application.isPlaying);  // falseと出力される
}

ちなみにApplication.isPlayingはfalseと出力されます。

今回使用したテストコード

最後に

気のせいだと思ったけど、気のせいではなかったようです。

環境

  • Unity2019.2.8f1

DOTween実行時警告 : An error inside a tween callback was silently taken care of ~~► Index was outside the bounds of the array.の対処法

f:id:esakun:20150825162207p:plain


DOTWEEN ► An error inside a tween callback was silently taken care of (Void \<HogehogeMethod>b__21_0(Int32)) ► Index was outside the bounds of the array.

DOTweenでこういう警告が出て困っていたのですが、DOTweenが悪いわけでありませんでした。(大抵悪いのはコードを書いてる自分です)

DOTweenを実行する直前に配列の例外がスローしてた

transform.DOLocalMoveX(1, 1).OnComplete(()=>_array[範囲外Index]);

このような感じでOnComplete関数の中で例外がスローしているとこのような警告が発生するようです。

注意:実機ではクラッシュする

UnityEditorで実行する際は警告のみ出力されるだけなのですが、実機で実行すると問答無用でクラッシュいたします(iPhoneXで確認済み)。ご注意を。


はい、スッキリ。

環境

  • DOTween 1.2.160
  • Unity2019.2.8f1

開発初期に暇を持て余しているUnityエンジニアができる42のTips前編

f:id:esakun:20150730215258g:plain:w450

本記事は、サムザップ Advent Calendar 2019 #2の12/11の記事です。

※後編リリースしました。


株式会社サムザップでUnityエンジニアをしている大庭です。
グラフィックデザイナー、Flashデベロッパーを経て、現在はUnityエンジニアでスマホゲームを作っています。Unity歴は6年程です。


これから紹介していく内容は、開発初期に後回しにされがちな、でも初期にやっておいた方が良い事ばかりです。
僕はUnityエンジニアとしてプロジェクトを進める上で以下の事を重要視しています。

  • 動くものをいち早く作ってメンバーに共有
  • ワークフロー構築、開発手法を整える事が最優先
  • いきなり正解は出ないので、不都合が出てきたら修正していくスタイル
  • 修正見直しの数を繰り返して精度を上げていく

この思想も取り入れた形でリストアップしました。

本記事の導入

新規ゲームプロジェクトがスタートし、あなたはUnityエンジニアとして参画する事になったと想定します。しかし、ゲーム仕様はまだ真っ白な状態で、計画しているモック開発の要件も決まっていません。


「あ〜仕様も決まってないし、やる事がなくて暇だな〜」


って、


んな事ぁない!!


仕様が無くてもやる事はいくらでもあります。


今回のタイトルを見て「42Tips!?多すぎじゃない・・・!?」と思ったあなた!ぜひ読んでみてください。
今まで新規開発に携わる事の多かった僕が思いつく「仕様がなくてもUnityエンジニアが開発序盤にやれる事」をツラツラ紹介していきます。

開発思想・設計系

1.開発環境・開発思想を決める

本記事の読者はUnityエンジニアが多いと思いますので、開発環境はUnityをベースに考えていきます。

開発環境Unityについて

Unityのアップデートに関して、随時追いかけていくのが良いと考えています。バージョンが離れてしまうと、APIのインターフェースに変更が入っていたりして、思わぬ修正が発生する場合があります。ただし、メジャーアップデート直後はバグが多い可能性もあるので、アップデートの通知が来てもすぐにバージョンアップせず少し様子を見る事にしています。


20XX.5、20XX.6辺りまでマイナーバージョンが上がってくると、比較的安心してバージョンを上げる気持ちにさせてくれます。

ソースコードのエディタについて

  • コードフォーマットの共通化に時間をかけたくない
    • 自動でフォーマットする機能がある
  • Unity開発をする上で機能が充実している

ソースコードを書くメンバーは、以上の点を満たすRiderを使うようにしています。 コードフォーマットの設定はカスタマイズする時間がもったいないのでデフォルト状態で使用しています。
ただ、Riderのバージョンによって微妙にデフォルト状態が違うので揃えた方が安全です。

開発思想について

ここはプロジェクト次第で、一概には言えないため、僕がよく採用する開発思想を紹介します。

a.基本はUnityの機能を素直に使うようにする

Unityエンジンの設計思想に反しないように機能を使うようにします。 これはUnityがアップデートした時にトラブルが起き辛いようにする他、後から参画したメンバーが理解しやすくするためでもあります。
※ピーキーにチューニングしたい場合は、置き換え可能な部分に切り出して実装するなど工夫をするのが良いと考えています。

b.巨大なサードパーティフレームワークに依存しないようにする

各メンバーの学習コストが増えて即戦力にならない問題や、採用フレームワークがアップデートされなくなるなどの運用後のリスクを回避するためです。
新しい技術を使った方が便利だったり、一時的な開発効率が上がる場合もあるのですが、開発から運用後の事を想定して採用技術は選定していきたいところです。


新しいものは、その分検証期間が必要ですし、見えていないトラブルもありえます。また、世の中に情報が溜まっていないので余計に時間がかかります。
開発時間に余裕のあるプロジェクトであれば新しい技術の採用を検討する価値はあり、逆に短納期の場合は速度を優先し、枯れた安定した技術を採用するという事も重要だと思っています。

2.Unityプロジェクトディレクトリ構成を決める

何をどこに格納するのが良いかは最初に方針だけでも決めておいた方が良いです。


僕が最近採用する構成は以下です。
※Assetsフォルダ配下の構成です。

┌ AssetBundleModule/ ・・・サブモジュール
    ├ AssetBundles/・・・AssetBundleにビルドされるものを格納
    └ Tools/・・・AssetBundleサブモジュール用のツール(主にアセットのチェッカー、ビューワー、非エンジニア向け機能)
├ Project/・・・アプリに含まれるアセット
    ├ Title/ 
        └ (タイトルシーンのコンテンツが格納される)
    ├ OutGame/
        ├ Common/・・・OutGameの共通アセット
        └ Display/・・・各画面
            ├ Home/ ・・・ホーム画面ソースコード、prefab
            ├ Quest/ ・・・Quest画面ソースコード、prefab
            └ Menu/
        └ Dialog/・・・ダイアログ(ポップアップ)
            ├ Alert・・・アラートダイアログソースコード、prefab
            └ Shop・・・ショップダイアログソースコード、prefab
    ├ Battle/
        └ (バトルシーンのコンテンツが格納される)
    └ 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を作成しておきましょう。

4.AndroidManifest.xmlの設定

初期状態ではファイル自体存在しないので、とりあえず用意はしておきます。
ゲーム開発でAndroidのマルチウィンドウに対応する事は少ないと思うので、マルチウィンドウをオフにする処理だけ追加しておきます。

5.コーディング規約を決める

コーディング規約は、メンバー間で宗教戦争みたいなものに発展する場合もあり、地味に時間を取られがちです。
ただコーディング規約決定に時間を割くのはナンセンスなので、Riderデフォルト設定のコーディング規約に準ずるという事で、今のプロジェクトは進めています。

「Riderさんがそう言ってるから仕方ないよね♥」

※ただしRiderのバージョンごとに微妙に設定内容が異なる可能性があるので注意です

6.MonoBehaviourクラスのAwake、Startメソッドは極力使わない

Unity特有のコード規約みたいな話です。

Awake、Startを無闇に使用してしまうと、、、

  • 実行順がわからない(保証されない)※Script Execution Orderを無闇に変更したくない
  • 知らない所で処理が走られてしまうのが嫌だ(管理できない)

以上の理由からMonoBehaviourが提供するAwakeやStartなどのイベント関数は、極力使わないようにしています。
後から入ったメンバーも処理の流れを理解しやすいというメリットにも繋がります。

7.大人数で開発できるような設計

人数が集まれば開発速度、単純に上がるという事はありません。

  • 開発のフェーズ依存
  • 参画するメンバーのスキル依存

以上の理由の他にもう一つ大きな要因として、大人数で開発できる設計になっているか?です。

  • 疎結合な実装になっているか
  • Viewとロジックが別れているか

ソースコードだと上記のような話になってきます。
Unityの場合は、GameObjectの設計も重要です。


例えばアウトゲームであれば、合計で100画面あるUIが一つのPrefabになっていたりすると、20画面同時に修正箇所が発生しても同時に1人しか作業ができません。
※Prefabは競合するとうまくマージされないため


当たり前かもしれませんが、各画面を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標準機能を個々に書いていっても実現できますが、シーン間に演出、表現をはさみたい場合、このAPIだけでは不足しています。こういう場合はSceneManagerのラッパークラスを作って対応します。

どんな演出が考えられるか?

前述した通り、演出が挟まるのであれば、その分の余白を残して実装する事が重要になります。

  • Tips表示
  • アセットローディングバーの表示
  • 暗転させる

などなど。


それらを仕様が無いながらも、他ゲーム、今までの経験を参考に拡張できるようなシーン遷移管理クラスを作っておくと後々楽になります。

10.画面遷移システムの作成

先程はシーン to シーンの話でしたが、今回は1つのシーン内での画面遷移についてです。


画面遷移は必ず必要になります。デザイン次第で大きく変わりますが、とりあえず動くものを作っておきます。

 // 全画面をEnumで定義する
 public enum DisplayType
 {
     None = 0,
     Home = 1,
     Quest = 2,
     Gacha = 3,
     Menu = 4
 }

画面をEnumで定義します。

https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328771/d586912a-db7d-a3ce-411c-4c33b0444013.png

上の画像のように、Enum値と画面prefabを紐付けておきます。

// 画面遷移開始
DisplayManger.Instance.Goto(DisplayType.Home);

上記のようなコードで画面が遷移するように実装しています。
すごくざっくりな話になっていますが、表示中の画面Prefabを指定したPrefabに入れ替える実装をしています。

11.どのシーンからでも開発できるような仕組みの設計

9.Unityのシーン遷移基盤開発の内容に関連しますが、どのシーンからでも起動できるようにしておくと、開発の効率は上がります。

それを実現するために、各シーンのエントリーポイントとなる共通クラスを継承したクラスをシーンのルートに配置しておくというルールを設けて実装しています。

例えば

  • タイトルシーン
  • アウトゲームシーン
  • バトルシーン

という3シーン構成であれば、それぞれにSceneEntryクラスを継承したのコンポーネント(TitleSceneEntry、OutGameSceneEntry、BattleSceneEntry)を各シーンのルートに配置します。

シーンエントリーポイントクラスの仕事とは?

  • 通常起動とそれ以外の起動の処理における初期化処理の共通化
  • クラス名の通り、そのシーンのエントリーポイントとして動くようにする(そのために、Awake、Startをあえて使わないようにしてます)
    ※参考 : 「6.MonoBehaviourクラスの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.動画再生基盤

動画を再生させる可能性がある場合は、事前に再生検証をしておきたいです。
12.サウンド再生基盤の動画版なので内容は割愛します(考え方は同じです)。

一点、ゲーム内設定のBGMボリュームを動画再生時に反映するというのを忘れやすいので注意です。


実機で再生できるかどうかの検証はもちろんですが、動画は比較的ファイル容量が大きくなるため、ファイルをサーバーに置いて、ダウンロード時間の確認(実機で)を早めにしておくとよいでしょう。

14.ゲームエフェクトの再生と再利用機構

バトル中に大量のゲームエフェクトが表示される事が想定される場合、エフェクトオブジェクトを再利用できる機構を事前に作っておくと良いです。

  • 大量のエフェクトを毎度生成するとGCが発生しやすい
  • Instantiateがそもそも処理的に重いため、スパイクの原因になる

ユーザー体感をより良くするためにできる限りエフェクトを再利用できるように設計しておきます。
またバトル開始前のローディング中にあらかじめ必要なエフェクトをロードする機能を追加しておくと良いかもしれません。

15.ローカルファイル保存の仕組み

ローカルに保存するのはよくあるので、事前に仕組みを用意しておく事ができます。 内部の実装は置き換えられるように一旦ラッパークラスを定義します。よく使いそうなメソッドを用意しておきます。

  • SetInt
  • SetFloat
  • SetString
  • SetBool
  • Set<T>
  • GetInt
  • GetFloat
  • GetString
  • GetBool
  • Get<T>

コード例

LocalStorage.SetString("key", "Sumzap");

var value = LocalStorage.GeString("key");
Debug.Log(value);// output : Sumzap

16.外部アセットを想定したロード処理

アプリ外部からアセットをダウンロード、ロードして使用する場合は、アセットバンドルや、今だとアドレッサブルアセットシステム(以下:AAS)を使用すると思います。
開発序盤ではそのシステムが無い、または何を使用するかを決めていない場合もあると思います。
ただ、決まっていないからといって、実装は後回しにすると手戻りが多く発生する可能性があるため、そうならないような実装を最初から心がけたいところです。

インターフェースを利用して抽象化

 public interface IAssetBundleManager
 {
     void Load<T>(string assetBundleName, System.Action<T> onComplete, System.Action onError = null);
     
     void Release(string assetBundleName);
 }

このような外部アセットをロードと解放を実装したインターフェースを定義しておきます。 開発序盤はResources.Loadで実装しておき、後から実際のAssetBundleまたはAASに置き換えていくと良いかもしれません。


後に出てくる32.アセットバンドル名を自動で設定されるようにするであえてアセットバンドルのパスをAssetBundles/Resources/のようにResouresをわざわざ挟んでいるのは、アセットバンドル名とResources.Loadのパスを合わせる事で、本実装時の手戻りを最小限にするためです。

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までを紹介しました。
まだ半分ですが「うへぇ〜〜」と思ったあなた!これでも暇しなくて済みますね。
後編はサムザップ #1 Advent Calendar 2019 - Qiitaの12/24に公開致します。


そして今回紹介した内容を実装し終えたとしても、それでFIXではありません。
プロジェクトが進む中で見直し、修正して適正な姿に変化させていく必要があります。

まずは手を動かし形にしてみてください。これらのTipsが皆様の開発のお役に立てたなら幸いです。

明日は@kazuhiro1128さんの記事です。