2年目のゲームプログラマがゲーム会社でUnityを使用して
スマートフォン向けのゲームを開発して感じたことをまとめておきます
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 コーディング編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 拡張メソッド編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 デバッグメニュー編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 UI編
- 【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 アセットバンドル編
UI
今回開発したゲームではNGUIを使用してUIを作成しました
その時に行った開発の効率化や遭遇した問題について書き残しておきます
(今回開発したゲームではNGUI 3.6.8を使用しました)
開発の効率化
TexturePackerのバッチを作成する
NGUIではアトラスで1枚のテクスチャを切り分けて使用することが可能です
また、TexturePackerを使用することで簡単にアトラスを作成できます
ただし、テクスチャが追加されたり変更されたりするたびに
デザイナーさんに毎回TexturePackerを開いてPublishして頂くのは大変だったので
アトラスの作成からUnityプロジェクトへの反映までを自動化するために下記のようなバッチファイルを作成しました
set folder_path=【アトラス化するテクスチャが格納されたフォルダのパス】
set out_filename=%1
texturepacker %folder_path% --format unity --disable-rotation --trim-mode None --sheet %out_filename%.png --data %out_filename%.txt
pause
texture_packer_publish.bat 【作成したアトラスの出力先】
テクスチャの設定をインポート時に自動で設定する
UnityではAssetPostprocessorクラスを使用することで
テクスチャやオーディオがインポートされた時にアセットの設定を変更することができます
テクスチャの設定をインポート時に自動で設定したい場合は
AssetPostprocessorを継承したクラスを作成してOnPreprocessTexture関数を定義することで実現可能です
using UnityEditor;
using UnityEngine;
public class TexturePostprocessor : AssetPostprocessor
{
private static readonly string[] ASSET_PATH_LIST =
{
"Assets/Resources/2D",
"Assets/Resources/LocalData/2D",
};
private void OnPreprocessTexture()
{
if ( !ASSET_PATH_LIST.Any( c => assetImporter.assetPath.Contains( c ) ) )
{
return;
}
var importer = assetImporter as TextureImporter;
importer.filterMode = FilterMode.Bilinear;
importer.mipmapEnabled = false;
importer.maxTextureSize = 1024;
importer.textureFormat = TextureImporterFormat.ARGB16;
importer.textureType = TextureImporterType.Advanced;
importer.wrapMode = TextureWrapMode.Clamp;
importer.npotScale = TextureImporterNPOTScale.None;
importer.alphaIsTransparency = true; // http://d.hatena.ne.jp/nakamura001/20130722/1374499269
}
}
上記の様なスクリプトをUnityプロジェクトの「Editor」フォルダに追加しておくことで
UI用のテクスチャがUnityプロジェクトに追加された時に自動で設定を変更するようにしました
SceneビューでUIを配置しやすいようにする
Sceneビューの「2D」をオンにするとUIの配置がしやすくなります
また、「Gizmos」から「Camera」のアイコンを選択してオフにすることで
Sceneビューのカメラのアイコンを非表示にできます
デバッグ用のUI配置
アプリ開発中は所持金や体力などのパラメータを最大値にして動作確認したいことがあったので
デバッグメニューとは別にUI上にチートコマンド実行用のボタンを配置していました
そういったUIがリリース版のアプリで表示されてはいけないので
下記のようなコンポーネントを作成してアタッチしておくことでリリース版のアプリで表示されないようにしました
using UnityEngine;
public class DebugOnly : MonoBehaviour
{
private void Awake()
{
# if RELEASE
Destroy( gameObject );
# endif
}
}
ただし、このやり方だとリリース版のアプリで表示されなくなるだけでUIの情報自体はアプリに含まれてしまうので
完全にリリース版のアプリに含まれないようにしたい場合は、アプリをビルドする前にすべてのシーンファイルから
特定のコンポーネントがアタッチされているオブジェクトを検索して削除する
下記のようなスクリプトを作成すると良いかなと思います
using System.Linq;
using UnityEditor;
using UnityEngine;
public static class DebugOnlyObjectDestroyer
{
[MenuItem( "Tools/Destroy/Debug Only" )]
private static void DestroyDebugOnly()
{
var pathList = EditorBuildSettings.scenes
.Select( c => c.path )
.ToArray();
foreach ( var path in pathList )
{
EditorApplication.OpenScene( path );
var objectList = Object
.FindObjectsOfType<GameObject>()
.Where( c => c.transform.parent == null )
.SelectMany( c => c.GetComponentsInChildren<DebugOnly>( true ) )
.Select( c => c.gameObject )
.ToArray();
foreach ( var obj in objectList )
{
Object.Destroy( obj );
}
EditorApplication.SaveScene();
}
}
}
エディタ上でのみ表示したいUI
エディタ上でのみ表示したいUIに関しては
InspectorでそのオブジェクトのTagをEditorOnlyにしておくことで
実機に含まれないようにすることが可能です
例えば、UIをUnityエディタ上で配置していく時に
UIのレイアウトの一枚絵をUITextureで表示しながら作業することがあったので
そういった作業用のUIはTagをEditorOnlyにすることでリリース版のアプリに含まれないようにしました
禁則処理
NGUIのUILabelには日本語の禁則処理の機能が備わっていないので
行の先頭に「。」「、」「っ」などの文字が来てしまうことがありました
unity_ngui_label - FreeStyleWiki
こちらのサイト様で公開されているスクリプトを使用することで
「。」「、」「っ」などの文字が行の先頭に来ないように補正をかけることができます
改善点
アトラスのテクスチャサイズ
今回開発したアプリでは古いAndroid端末でも画像が正常に表示されるように
1024x1024を最大サイズとしてアトラスのテクスチャをデザイナーさんに作成していただいたのですが
最近のスマートフォンであれば2048x2048の画像も問題なく表示されるようなので
2048x2048のテクスチャでアトラスを作成することでドローコールが削減できるかなと考えています
参考サイト:【Unity】モバイルにおけるテクスチャの最大サイズ
UIの配置
今回はUIの配置をUnityエディタ上で行ったのですが
デザイナーさんにUnityやNGUIの使い方を覚えていただくのが少し大変だったので
Photoshopでレイアウトを設定して、それをNGUIに変換できるようなツールを作成したほうが良かったかなと思っています
有料ですがAsset Storeには既に「PSD Layers to NGUI」というアセットが公開されているので
これを各プロジェクト用にカスタマイズしてみたいなと考えています
「ウチの姫さまがいちばんカワイイ」ではこのアセットを使用しているそうです
また、Cocos2d-xで開発されたアプリですが、「消滅都市」でもPhotoshopでUIを配置できるようにしたようです
参考サイト様
ボタンの同時押し
Unityエディタ上だと同時押しができないため
ボタンを同時押しても問題ないかどうかを確認するときはいつもアプリを実機に書き出して動作確認していました
「Unity Remote 4」を使用すればそういった手間も省くことができたかなと思います
遭遇した問題
表示されないUI
UIPanelのClippingがSoft Clipで、かつオブジェクトのスケールが均一の値になっていない場合、UIが表示されなくなるので
もし表示されないUIがあるときはスケールの値を確認すると良いかもしれません
スケール(0, 0, 0)のUIのドローコール
開閉するウィンドウのUIを作成した時に、スケールを(0, 0, 0)にすることで非表示にしていましたが
NGUIではスケール(0, 0, 0)のオブジェクトもドローコールに加算されてしまっていたので
ウィンドウが開く時にSetActive( true )を呼び出し
ウィンドウが閉じきった時にSetActive( flase )を呼び出すようにしてドローコールを抑えました
入力欄の制限
ユーザー名やコメントの入力欄を実装した際に次のような問題に遭遇しました
- 入力できる文字数を制限していなかった
- 改行可能かどうかを制限できていなかった
- モバイルで表示されるソフトウェアキーボードの種類を設定していなかった
- 空文字列が入力された時に代わりに表示される文字列を設定していなかった
入力できる文字数を制限していなかった
UIInputのCharacter Limitを設定することで入力できる文字数を制限できます
ただ、今回関わったプロジェクトでは
「全角の場合は最大10文字まで入力できるようにする」
「半角の場合は最大20文字まで入力できるようにする」
という仕様で開発していたので、下記のようなスクリプトを作成して入力できる文字数を制限するようにしました
public static class StringExtensions
{
/// <summary>
/// 文字が全角なら true を、半角なら false を返します
/// </summary>
public static bool IsChar2Byte( char c )
{
return !( ( c >= 0x0 && c < 0x81 ) || ( c == 0xf8f0 ) || ( c >= 0xff61 && c < 0xffa0 ) || ( c >= 0xf8f1 && c < 0xf8f4 ) );
}
/// <summary>
/// バイト数を計算して返します
/// </summary>
public static int GetByteCount( this string self )
{
int count = 0;
for ( int i = 0; i < self.Length; i++ )
{
if ( IsChar2Byte( self[ i ] ) )
{
count++;
}
count++;
}
return count;
}
/// <summary>
/// インスタンスからバイト単位で部分文字列を取得します
/// </summary>
public static string SubstringInByte( this string self, int byteCount )
{
string tmp = "";
int count = 0;
for ( int i = 0; i < self.Length; i++ )
{
char c = self[ i ];
if ( IsChar2Byte( c ) )
{
count++;
}
count++;
if ( count > byteCount )
{
break;
}
tmp += c;
}
return tmp;
}
/// <summary>
/// <para>インスタンスからバイト単位で部分文字列を取得します</para>
/// <para>文字数が指定されたバイト数以内の場合はインスタンスをそのまま返します</para>
/// </summary>
public static string SafeSubstringInByte( this string self, int byteCount )
{
return byteCount < self.GetByteCount() ? self.SubstringInByte( byteCount ) : self;
}
}
private UIInput mUIInput;
private void Awake()
{
mUIInput = GetComponent<UIInput>();
EventDelegate.Add( mUIInput.onSubmit, OnSubmit );
}
private void OnSubmit()
{
mUIInput.value = mUIInput.value.SafeSubstringInByte( mMaxByteCount );
mUIInput.label.text = mUIInput.value;
}
改行可能かどうかを制限できていなかった
UIInputのLabelに設定されているUILabelのMax Linesに1を設定することで改行できないように制限できます
ただし、これだけだと改行を含む文字列をコピペした場合に改行文字を入力することが可能でした
それを防ぐために改行できない入力欄では下記のようなスクリプトを適用しました
private UIInput mUIInput;
private void Awake()
{
mUIInput = GetComponent<UIInput>();
EventDelegate.Add( mUIInput.onChange, OnChange );
}
private void OnChange()
{
mUIInput.value = mUIInput.value
.Replace( "\n", "" )
.Replace( "\r", "" );
mUIInput.label.text = mUIInput.value;
}
モバイルで表示されるソフトウェアキーボードの種類を設定していなかった
UIInputのMobile Keyboardを設定することで
実機で表示されるソフトウェアキーボードの種類を変更できます
なので、例えば数値しか入力できない場面ではNumberPadを設定しておくことで
ユーザーの方がストレスなく数値を入力できるようになるかなと思います
空文字列が入力された時に代わりに表示される文字列を設定していなかった
「ここにあなたの名前を入力してください」のような文言を入力欄の中に表示したい場合
UIInputのLabelに設定されているUILabelのTextにあらかじめその文字列を設定しておくことで
空文字列が入力された時に代わりに表示できるようになります
表示するテキストが動的に変わるUILabel
キャラクターやアイテムの説明文のように、マスタで設定する文言をUILabel表示する時に
デザイナーさんが想定していた文字数を超えて文言が設定されており、
UIの枠を超えて文言が表示されてしまうことがありました
これを防ぐために、マスタに入力された文言が
想定文字数を超えていないかどうかを確認するチェッカーツールを作成すると共に
表示するテキストが動的に変わるUILabelではOverflowをShrinkContentにするようにしました
その他
他のスマホゲームでどのようにUIが作られているのかを確認する
こちらのサイト様で多くのスマホゲームのUIを見ることができるので、ゲーム開発中は参考にさせていただきました










