Skip to main content
「 もののけ大戦“陣” 」制作事例
Unityで美麗2Dゲームを作る秘訣

(2012/09/18 加筆、修正)
2012/08/23 株式会社 スパイクチュンソフト
時村 良平
修正内容
•

•

2012/09/18
・シーンキャッシュ全般について、enabledとなっていたのをactiveに修正
・AssetBundleの多重リンクについて追記(p120)
2013/11/27
・シーンキャッシュのリスクを追記(p35)
・NGUIの描画順について 追記(p45)
・AssetBundleとシェーダの関係についての誤りを追記(p112)
自己紹介
コンシューマ系ゲームのころからの
ゲームプログラマ

主にノベル系の製作に参加
アジェンダ
•
•
•
•
•
•

開発環境
高速シーンロード
ハイクオリティな2Dエフェクト
テクスチャ設定
AssetBundle
TIPS
開発環境
開発期間 約7か月
人数 10人前後
スクリプトはC#で統一
バージョン管理は
Team License(Asset Server)
メタファイルやAssetの移動を
スムーズに管理できる

エンジニアじゃなくても簡単
Asset Server
お勧め
高速シーンロード
Unityのシーンの特徴
・「1シーン=1アセット」
・「スマホだと、シーンのロードが遅い」
「1シーン=1アセット」
→複数人数で同じシーンを編集すると衝突する
→シーンを分割しないといけない

しかし
「スマホだと、シーンのロードが遅い」
→シーンを分割してはいけない?
シーンロードを高速化すれば解決!
シーンロードのボトルネックは何か?
UnityのProfilerで確認

どうも「シーンロード」ではなく
「前シーンの破棄」がボトルネック
たぶん
Resources.UnloadUnusedAssets
(未使用のアセットを
オブジェクトを全検索して解放する)
が重いのと同じ原因っぽい?
実際、
LoadLevelAddtiveで 前の
シーンを破棄しないと シ
ーンロードは早い
対策
シーンキャッシュ
コンセプト
一度ロードしたシーンは破棄せずに
メモリ上にキャッシュ 二回目以降は
それを使いまわす
既存のシーン管理
シーン1
LoadLevel

シーン2のロード

シーン1のアンロード

シーン2

シーン1

シーン2
シーンキャッシュ
シーン1
LoadLevelAddtive
+キャッシュ処理
前のシーンは破棄しない
シーン2のロード

シーン1のアンロード
シーン2

シーン1
全オブジェクトのactiveをfalse
にして、残したままにする

シーン2

シーン1
シーンキャッシュ
シーン2

シーン1

二回目の以降のシーンロードでは、
LoadLevelはいらない。
キャッシュ制御
(EnabledのON・OFFを切り替える)
だけ

シーン2

シーン1
「もののけ大戦陣」でのシーン管理

ロードでされたシーンは、子要素を
Hierarchy直下に移動し、 active状態を復帰

アンロードでされたシーンは、 ルートオ
ブジェクト以下にオブジェクトを移動し、
active状態を、記録したのちfalseに
シーンキャッシュどうやる
• 加算でシーンを管理する必要がある
• Unity高橋さんのシーン加算スクリプ ト を改造
共通となるシーン
• さらに全シーンで共通な仕組みがあると便利。
ゲームデータの管理をはじめ、 サウンド
とかの○○マネージャーとか システム
ダイアログとか・・・・
共通となるシーン
• Unity大前さんがGameJam2012で使った考えを拝借
常に加算したいシーン内に、目印になるタグをもつ空のオブジェクトをつくる
シーンロード時に、目印がなければ「目印をもつシーン」を加算シーン。
目印が既にあれば、加算シーンしない。

これが役立つのは主にエディタ上
通常のシーン加算では

ロードする順に、シーンが追加される

エディタ上で編集中の
シーンをいきなりロードしても・・・

読み込んでおきたいシーンが
ロードされずにバグる
Commonシーン

エディタからどのシーンを起動しても
読み込んでおきたいシーンは 自動
的に即座に加算する
「もののけ陣」では、これをCommonシーンとした。
他のシーンからアクセスされるようなオブジェクトはCommonシーンに起き、
各コンポーネントにはSingletonを使ってアクセスすると便利
Singletonのサンプル
public class Hoge : MonoBehaviour {
static Hoge instance;
public static Hoge GetInstance(){
return instance;
}
void Awake(){
instance = this;
}
}
シーンキャッシュの結果
シーンロード時間はほぼゼロに!
キャッシュ前(最初の一回)でも、1~2秒
(ただし、通信による待ち時間は普通にかかります)

各画面ごとにシーンを分割して分担。作業効率UP!
「もののけ大戦陣」でのシーン管理
Commonシーン。常に存在する
各オブジェクトにはsingletonを使ってアクセス。

Scene Mergerの下に
各シーンをキャッシュして管理

各シーンのキャッシュ管理をする
ルートオブジェクト

ロードでされたシーンは、子要素を
Hierarchy直下に移動し、 active状態を復帰

アンロードでされたシーンは、 ルートオ
ブジェクト以下にオブジェクトを移動し、
active状態を記録したのち、falseに
ただし、 手軽に作るにはちょっと
向かない

「Awake()やStart()など
Unityの基本的な仕組みが使えない」
さらに、シーンの初期化・終了は
自前で管理する必要がある

シーン切り替え時に、自前で
SendMessageするなどして対応した
C++でコンストラクタとデストラクタを
書くような面倒さが……
追記 リスクについて
「やってみたけどバグの温床になる!」
という話を聞いてたりします。
特に、メモリの肥大化があるようです。実際、自前
でリソースのメモリ管理することになるので、リスク
は大きいです。
Unity4による高速化や、最近の端末自体のスペッ
クアップもあるので、もはや迂闊に手を出す手法で
はないかもしれません。
高速化と開発効率は反比例するので注意を。
ハイクオリティな2Dエフェクト
もののけ大戦陣のエフェクト
半透明を多用
特に、パーティクルを中心に
加算エフェクトを使うことで
キラキラした感じを出してる
個人的には加算重要
どうやったか?
NGUI+Particle+Animation
この三つと
努力
NGUI
NGUIとは
Unityの2Dプラグインの
スタンダード
GUI系の機能がメイン。
ただのスプライト描画
にも使える
AssetStoreで購入可能
NGUIそのままでは使えなかった
ちょこちょこ拡張
一番の問題
描画順の制御
*追記
NGUI3.0以降は
解決済みというウワサ
NGUIのPanel内の描画順
同一マテリアル(アトラス)なら、
Depthの値どおりに描画される

1

2

3

4

5

6

7

8

異なるマテリアルが混ざると、
描画順が崩れてしまう

1

2

3

4

5

6

7

8

1

3

5

7

2

4

6

8

1

3

5

7

1

3

5

7

2

4

6

8

2

4

6

8

同じマテリアル同士では
Depth値は正しく機能する

マテリアルごとにグループになっ
ているっぽいが、 グループ間の
描画順は制御しづ らい

↑この場合もあれば

↑こっちのパターンもある
NGUIの描画の仕組み
1

UIDrawCall その1

UIDrawCall その2

2

1

3

4

3

2

5

6

5

4

7

8

7

6

8

実はNGUIの各オブジェクト自体は描画処理をしない。 描画を担当する
UIDrawCallというオブジェクトを裏で自動生成している。 UIDrawCallはマ
テリアルごとに作られるため 描画順はマテリアルごとに区切らざるを得
ない
NGUIの描画の仕組み

Geometryにすると
NUGIが自動生成する
UIDrawCallオブジェクト
が表示される
いろいろ解析すると、 同一
Panel内で マテリアルが入り混
じる描画は NGUIの仕様外

つまり、
・フォント専用マテリアルを混ぜる
・加算シェーダのマテリアルを混ぜる
とかすると、描画順が制御しづらい!
Panelを逐一つけて、Z値をうまく使えば解決はするが・・・
違うマテリアル同士でもDepthの値で
描画順が制御できるように
NGUIのソースコードを修正
UIPanelのソースを中心に… マテリアルが違ったら
UIDrawCallオブジェクトを増やす 各widetのZ値は0固定にし、Depthに比例した値を
UIDrawCallオブジェクトのZ値にoffsetする Unityは半透明描画物はZソートされるのでそれを
描画順制御に利用 Panelが違うものの描画順はPanelのZ値で制御

・・・といった感じで改造
詳細は省略・・・けっこうややこしい
正直、これをしないと
2Dゲームは厳しいと思う
NGUIはここが非常にもったいない
その他、NGUIの機能拡張
タイルド+スライスド機能
最初に拡張しようとした部分

英語ができずに、結局製作
者への依頼を諦めた
英語できるといいね
アスペクト比固定のフルスクリーン表示
アスペクト比が崩れない
最大の大きさで描画される

アスペクト比(タテヨコ比)が
バラバラのAndroidの対応
カメラをうまく使うのがコツ
カメラ1:黒でクリアするだけ
カメラ2:ビューポート
(camera.rect)を利用して、指
定の範囲に描画をする

余りは
黒枠
Y座標をアニ
メーション

「UVスクロール」
(最新のNGUIでは追加されてる?)
UV値をアニ
メーション

画面

画面外に大きくはみ出る
巨大スプライト描画は
Android一部端末で
負荷が大きかった

UVスクロールに変更
「A8bitテクスチャでのフォント描画」
(すでに最新のNGUIでは似たような機能があるので不要かも)

A8描画用のシェーダを作成
メモリサイズは1/4に

2048×2048のテクスチャなら フォント
サイズを22~24くらいにすれば メモリ
消費4Mで8000文字近く入る フォント
ワークス製フォントで 全文字入ったの
で 日本語表示は実質解決
NGUIの高速化
newを減らそう
たとえば、頂点データを一時作成する配列を
毎回newしてる
計算のための一時バッファなら、
staticなバッファを作って それを
使いまわしてもOK
時間によって
処理回数が増えるところを調整
前フレームとの時間差ぶん
ループ回数が増えるところがあったり
処理が落ちるほど、さらに重くなるという
拡張による問題点
最新バージョンのNGUIとの
マージを諦めた

この辺は今後の課題
Animationエディタ
UnityデフォルトのAnimation
• 3Dモーションだけではなく、Flashのような
2Dのキーフレームアニメにも使える。
• EventキーでSEつけたりと柔軟に拡張可能
Animationエディタには いく
つか問題が
• Hierarchyで名前や構造を変えても、キーは変
更されない
• boolとfloatにしかキーが打てない(intは無理)
• カラーの値などを変えても描画に反映されな
い?
• キーのコピーができない!
と、なかなか手ごわいです
「Hierarchyで名前や構造を変えても、
キーは変更されない」
↓
後から構造を変更するとキーを打ちなおしになる
↓
事前に構造を決める!
ある程度の慣れが必要
既にキーを打ったものを変更すると壊れるが
「追加」はOK
「boolとfloatにしかキーが打てない(intは無理)」

↓
intをfloatに書き換える
が、結局intのパラメーターに
キーを打つ必要が生じなかった
「カラーの値を変えても描画に反映されない?」
NGUIの拡張が必要。カラーに限らない話。
Unityのアニメーションは、メンバ変数を直接書き換えるっぽい
→setやエディター拡張による定義は無視される

↑ こういうの
mColorの値だけ書き換えられて、mChangedのフラグが立たない
毎フレームカラーの値の変化を監視する処理を組み込んだ。
「キーのコピーができない」
↓
諦めた
AssetStoreにもサポートプラグインはない模様。
Editor拡張のスクリプト書くしかない?

Unityへのリクエストはけっこう前からある模様……
4.0に期待!!
その他、細かいバグっぽいものの対処
・回転のConstant変化にバグ
→Scriptで解決。
のちにUnityから対 応策発表 。

・Eventでセットしたアセットは、キーを消しても
リンクに残ってしまう
→NULLをセットした後キーを消す
一度長くしたキーフレームの最大長を
短くできないバグ
↓クリーンナップスクリプトを書いて解決
けっこう問題は多いですが
ゲームの見た目そのままにアニメーション編集
できるのは便利!
うまく使えば、十分に使い物になる
Particle
Particle
Shuriken未使用
(リリースが
開発途中だったため)

Legacy Particleで割と安定。
そのままで使える感じ。
省テクスチャで豊かな表
現が可能な反面、
速度的な負荷が高くなり
がちなので注意。
描画パフォーマンスについて
Unityというかスマホの
2D描画パフォーマンスについて
• GPU処理で意識すべきは
ドローコール
フィルレート

• CPU負荷になりがちなのは サイズの大きい
newやPrefabのInstantiateなど
・その他 描画に使うリソースのロードは事前に
しておく
半透明使うなら
特にフィルレート重要
・平たく言うと「ピクセルを画面に描く速さ」
画面の解像度が高い=たくさん描きこむ=遅い
このせいで、iPhone4はiPhone3GSよりも遅い
(iPhone4sはGPU性能が上がってるので早い)
フィルレート対応
第一に
「ピクセルを書きこむ面積
を 減らす」
……と、アーティストに伝えて
エフェクトを作ってもらう。
特にパーティクルは肥大化
しがちなので注意。
UnityのOverdrawで確認もできる
最終的には実機で確認しつつ調整

SceneビューでOverdrawを選択
多重に書き込まれているところがわかる
フィルレート対応
第二に
「シェーダをMobile用に」
UnityはMobile用に、
処理の軽いシェーダを用
意している。
パーティクルもちゃんと
Mobile用を使うこと。

NGUIであれば、 Clip系の
シェーダは処理が重 いとこ
ろでは使わない。
もろもろの苦労を経て・・・
ワークフローさえ固まれば
アーティストはエフェクト作成に専念でき、
エンジニアはトラブルシューティングに専念できる
Unityの強み「動かしながら即座に編集」
が生きてくる
ゲーム制作での強敵
「イメージを伝える」という必要がなくなるので
非常に効率的
実際は・・・・・・
パラメーターを反映した
ゲームっぽい複雑なフローは
スクリプト必須
エンジニアも頑張りましょう
複雑なフローの「一部」
・・・playmakerとかで
効率化できたかも
テクスチャ設定
2Dテクスチャの
インポート設定は
こんな感じ
Texture Type を
Advancedにする
(細かい設定が面倒なら、
GUIで)
テクスチャフォーマット

圧縮しないならNONEを設定
(グレーで選択不可の場合はそのままでOK)
Defaultを設定

チェック外す
特にGenerateMipMapをONにすると
画像がボケたあげくメモリ消費が増えます

使わないのでClampとRpeatどちらでも可
(とりあえずClamp)
Bilinearを設定 2Dでは

関係ないので一応0を設定
圧縮に関しては、次項に詳細

テクスチャサイズよりも大きい値を設定
テクスチャの圧縮(減色)に関して
基本、スマホでは テクスチャ
をロードすると メインメモリが
消費されます。
テクスチャのサイズは ここに表
示される。 pngなどのもとのファ
イルサイズ とは異なるので注
意。
2Dは3Dよりも テク
スチャサイズが 肥
大化しがちです
テクスチャ一枚で16Mも消費?
サイズ削減のため、
圧縮しましょう

16Mが2Mまで縮んだ
16bit系の圧縮なら
とりあえず半分のサイズ
になります。
ここを切り替える
それ以上の圧縮をする場合
Automatic Compressed
これでいい感じにしてくれる?
とはいえ、 圧縮(減色)は画質が劣化
するので、 用途によって細い設定が
必要な場面も多い
圧縮設定詳細
AndroidとiPhoneで
フォーマットが違います
( Unityとは関係なく、各端末の仕様)
おまけに、Androidは
機種ごとにフォーマットが
違います
iPhoneは 「PVRTC」