Upload
ryohei-tokimura
View
55.136
Download
0
Embed Size (px)
Citation preview
「 もののけ大戦“陣” 」制作事例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
シーン2
シーン2のロード
シーン2
LoadLevel
シーン1のアンロード
シーン1
シーン1
LoadLevelAddtive
+キャッシュ処理
前のシーンは破棄しない
シーンキャッシュ
シーン2 シーン1
シーン2のロード シーン1のアンロード
全オブジェクトのactiveをfalseにして、残したままにする
シーン2 シーン1
シーンキャッシュ
二回目の以降のシーンロードでは、LoadLevelはいらない。
キャッシュ制御(EnabledのON・OFFを切り替える)
だけ
シーン2 シーン1
シーン2 シーン1
アンロードでされたシーンは、 ルートオブジェクト以下にオブジェクトを移動し、
active状態を、記録したのちfalseに
ロードでされたシーンは、子要素をHierarchy直下に移動し、 active状態を復帰
「もののけ大戦陣」でのシーン管理
シーンキャッシュどうやる
• 加算でシーンを管理する必要がある
• 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!
ロードでされたシーンは、子要素をHierarchy直下に移動し、 active状態を復帰
アンロードでされたシーンは、 ルートオブジェクト以下にオブジェクトを移動し、
active状態を記録したのち、falseに
Commonシーン。常に存在する各オブジェクトにはsingletonを使ってアクセス。
Scene Mergerの下に
各シーンをキャッシュして管理
各シーンのキャッシュ管理をするルートオブジェクト
「もののけ大戦陣」でのシーン管理
ただし、 手軽に作るにはちょっと向かない
「Awake()やStart()などUnityの基本的な仕組みが使えない」
さらに、シーンの初期化・終了は自前で管理する必要がある
シーン切り替え時に、自前でSendMessageするなどして対応した
C++でコンストラクタとデストラクタを書くような面倒さが……
追記 リスクについて
「やってみたけどバグの温床になる!」
という話を聞いてたりします。
特に、メモリの肥大化があるようです。実際、自前でリソースのメモリ管理することになるので、リスクは大きいです。
Unity4による高速化や、最近の端末自体のスペックアップもあるので、もはや迂闊に手を出す手法ではないかもしれません。
高速化と開発効率は反比例するので注意を。
ハイクオリティな2Dエフェクト
もののけ大戦陣のエフェクト
半透明を多用
特に、パーティクルを中心に加算エフェクトを使うことでキラキラした感じを出してる
個人的には加算重要
どうやったか?
NGUI+Particle+Animation
この三つと
努力
NGUI
NGUIとは
Unityの2Dプラグインの
スタンダード
GUI系の機能がメイン。
ただのスプライト描画にも使える
AssetStoreで購入可能
NGUIそのままでは使えなかった
ちょこちょこ拡張
一番の問題
描画順の制御
*追記NGUI3.0以降は
解決済みというウワサ
1 2 3 4 5 6 7 8同一マテリアル(アトラス)なら、Depthの値どおりに描画される
1 2 3 4 5 6 7 8異なるマテリアルが混ざると、
描画順が崩れてしまう
1 3 5 7
2 4 6 8同じマテリアル同士では
Depth値は正しく機能する
マテリアルごとにグループになっているっぽいが、 グループ間の
描画順は制御しづ らい
NGUIのPanel内の描画順
↑この場合もあれば ↑こっちのパターンもある
1 3 5 7 1 3 5 7
2 4 6 8 2 4 6 8
UIDrawCallその2
NGUIの描画の仕組み
1 2 3 4 5 6 7 8
UIDrawCallその1
2 4 6 8
1 3 5 7
実は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)を利用して、指定の範囲に描画をする
アスペクト比固定のフルスクリーン表示
余りは黒枠
「UVスクロール」(最新のNGUIでは追加されてる?)
画面外に大きくはみ出る巨大スプライト描画はAndroid一部端末で負荷が大きかった
UVスクロールに変更
Y座標をアニメーション
画面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性能が上がってるので早い)
フィルレート対応SceneビューでOverdrawを選択
多重に書き込まれているところがわかる第一に
「ピクセルを書きこむ面積を 減らす」
……と、アーティストに伝えてエフェクトを作ってもらう。
特にパーティクルは肥大化しがちなので注意。
UnityのOverdrawで確認もできる
最終的には実機で確認しつつ調整
第二に
「シェーダをMobile用に」
UnityはMobile用に、
処理の軽いシェーダを用意している。
パーティクルもちゃんとMobile用を使うこと。
NGUIであれば、 Clip系の
シェーダは処理が重 いところでは使わない。
フィルレート対応
もろもろの苦労を経て・・・
ワークフローさえ固まれば
アーティストはエフェクト作成に専念でき、エンジニアはトラブルシューティングに専念できる
Unityの強み「動かしながら即座に編集」
が生きてくる
ゲーム制作での強敵「イメージを伝える」という必要がなくなるので
非常に効率的
実際は・・・・・・
パラメーターを反映した
ゲームっぽい複雑なフローは
スクリプト必須
エンジニアも頑張りましょう
複雑なフローの「一部」
・・・playmakerとかで
効率化できたかも
テクスチャ設定
2Dテクスチャのインポート設定はこんな感じ
テクスチャサイズよりも大きい値を設定
圧縮しないならNONEを設定(グレーで選択不可の場合はそのままでOK)
Defaultを設定
チェック外す
特にGenerateMipMapをONにすると画像がボケたあげくメモリ消費が増えます
使わないのでClampとRpeatどちらでも可(とりあえずClamp)
Bilinearを設定 2Dでは
関係ないので一応0を設定
テクスチャフォーマット
Texture TypeをAdvancedにする
(細かい設定が面倒なら、GUIで)
圧縮に関しては、次項に詳細
テクスチャの圧縮(減色)に関して
基本、スマホでは テクスチャをロードすると メインメモリが
消費されます。
テクスチャのサイズは ここに表示される。 pngなどのもとのファ
イルサイズ とは異なるので注意。
2Dは3Dよりも テク
スチャサイズが 肥大化しがちです
テクスチャ一枚で16Mも消費?
サイズ削減のため、圧縮しましょう
16Mが2Mまで縮んだ
16bit系の圧縮なら
とりあえず半分のサイズになります。
ここを切り替える
それ以上の圧縮をする場合
Automatic Compressedこれでいい感じにしてくれる?
とはいえ、 圧縮(減色)は画質が劣化するので、 用途によって細い設定が
必要な場面も多い
圧縮設定詳細
AndroidとiPhoneで
フォーマットが違います( Unityとは関係なく、各端末の仕様)
おまけに、Androidは
機種ごとにフォーマットが違います
iPhoneは「PVRTC」
Androidは「ETC」が唯一の共通フォーマットただし、αチャンネルなしの画像
にしか使えません
DXT、ATCなどは
機種によって未対応の場合あり
圧縮する際は、サイズ制限があり
iPhoneなら、2のべき乗かつ正方形Androidは、2のべき乗かつαなし
ちなみに「パーティクルは正方形が必須」
テクスチャ圧縮形式まとめ
*注2
サイズは2のべき乗かつ正方形が必須。
*注1
サイズは2のべき乗が必須
非圧縮 圧縮(弱)圧縮(強)Android 圧縮(強)・iOS 圧縮(最強)・iOS
α
なし
RGB24 bits
RGB16bits
RGB CompressedETC 4bits*注1
RGB CompressedPVRTC 4bits *注2
RGB CompressedPVRTC 2bits *注2
α
あり
RGBA32 bits
RGBA16bits
共通フォーマット なし
RGBA Compressed PVRTC 4bits *注2
RGBA Compressed PVRTC 2bits *注2
けっこう大変なので、Unityには支援機能がある
細かい設定しないなら、先ほどの Automatic Compressed
Androidの圧縮をRGBA 16bitかETCに 自
動的にしてくれる機能Build Settingsから設定
ローカル設定らしく、AssetSeverで
共有できないので注意
2のべき乗じゃないテクスチャを圧縮したい場合インポート設定の「Non Power Of 2」をいじる
None以外を選ぶと テクスチャを2のべき乗サイズ
にしてくれる 元のテクスチャサイズは失われるので、プログラム 等矛盾がでないように注意
2Dゲームなら、 たいていの
メモリ不足問題は テクスチャの扱い次第
うまく調整しましょう
Asset Bundle
AssetBundleの基本
Assetをサーバーから動的にDLし
デバイス上にキャッシュ 動的にロードできる
ソーシャルゲームではカード画像など
頻繁に追加・更新されるAssetに最適!
AssetBundleの基本
テクスチャやサウンドなどの単純なリソースだけでなく
プレハブやシーンもAssetBundle化できる
AssetBundleの基本
プログラム(スクリプトやシェーダ)はアプリ本体のものが使用される 動
的に更新はできない・・・Appleの規約のため?
バージョン違いにならないように注意
*追記 シェーダはアセットバンドルに含まれる。最適化についての参考ページ→ https://github.com/keijiro/unity-shader-bundle
AssetBundleの使いどころ
カードやバナーなどのTexture
さらにエフェクトもプレハブ化してAssetBundle
こうすると、エフェクトの更新はアプリの更新いらず
AssetBundleの作成
Editor拡張して作成する。
サンプルはあるので問題なく作れる。
ただし、Android・iPhone形式の他にEditor用に、WebPlayer形式でも作成
(α系の画像が壊れるので)
AssetBundleのロード
AssetBundle内に多数のAssetがあると
非同期だとロードが長い(数によっては10秒くらい)
同期処理になるがLoadAllで一気に読んだほうが早い
(2秒くらいになった)
AssetBundleのロード
•サンプルにある
• yield www;
•ではなく
• while( !www.isDone && null == www.error) yield return 0;
プレハブのAssetBundle化
注意点
AssetBundleから リンクされているAssetは すべて自動的にパックされる。
AssetBundle内に含まれるデータ
Assetを選択して 右クリック-> ExpoertPackage
ExportPackageを使うと、
アセットからリンクされてるアセットがわかる。
多重リンク
AssetBundle「A」とAssetBundle「B」の中に
同じアセットがあった場合、別Asset扱いになる
→同時にロードすると、メモリが二倍消費される
AssetBundle「A」
テクスチャA
テクスチャB
テクスチャC
AssetBundle「B」
テクスチャD
テクスチャE
テクスチャC
この場合、読み込まれるテクスチャは テクスチャA,B, D,E, C×2
多重リンク
AssetBundleとアプリの中に
同じアセットがあった場合も同様
AssetBundle「A」
テクスチャA
テクスチャB
UI用テクスチャ
アプリ
テクスチャD
テクスチャE
UI用テクスチャ
UI用のテクスチャなど、アプリが持ってるテクスチャをAssetBundleで使ってしまうと二重化する。 逆に、AssetBundle
のカード画像などを アプリのシーン内のオブジェクトからリンクしている場合も同様
多重リンク回避
AssetBundle「A」
テクスチャA
テクスチャB
ダミー画像
アプリ
テクスチャD
テクスチャE
フォント用テクスチャ
作成時には、8×8などの小サイズのダミー画像を設定し
スクリプトを使って、ロード後にテクスチャを張り替える
ダミー画像を使う
セットしなおす
多重リンク回避
AssetBundle作成時にPopAssetDependencies& PushAssetDependencies で
リンク関係を作っておく。AssetBundle「C」を、「A」「B」より先に読み込むことが必須。 プレハブや、モデルのテクスチャなどの場合に有効。 詳細は、Unity公式サン
プル http://unity3d.com/support/resources/example-projects/assetbundles
AssetBundle同士なら、
PopAssetDependencies& PushAssetDependenciesを使う
AssetBundle「C」 AssetBundle「A」 AssetBundle「B」
テクスチャA テクスチャD
テクスチャB テクスチャE
テクスチャC テクスチャCテクスチャCロード時に自動でリンク
TIPS(よく使ったUnityの便利機能や小技)
TIPS
• 「ExportPackage」は
• アセットのリンク関係を
• 調べるのに便利。
• シーンを対象にもでき、
• 複数選択もできる。
•フォルダを選べば、 フ
ォルダ以下の 全アセッ
トからリンクさ
• れてる一覧になる。
Assetを選択して 右クリック-> ExpoertPackage
TIPSアプリのビルドログには、 アプリ内の各アセットの
サイズ一覧が出力される。
「Window -> Console ->Open Editor Log」
TIPSデュプリケート:Ctrl+D
シーンやプレハブのコピーが簡単にできる。
応用技~日本語コメントつきのソース作成~別エディタでBOMつきUTF16でスクリプト保存→デュプリケート→中身を全クリア(ただし、文字コードはBOMつきUTF16のまま)→改めてソースを書く
右クリックメニューにはない Ctrl+C&Ctrl+Vでは
何も起きない… Ctrl+Dでコピペ可能!
TIPS
・コマ送り
ポーズ後…
・スローモーション再生
コマ送り
TimeScaleを1以下に再生中でも可
TIPS
再生中のシーンからオブジェクトを直接コピー
再生中にコピーして
終了後、 Ctrl+Vすれば再生
中の状態で オブジェクトをコピーできる
おしまい