アカとブルーの技術話その1【UnityでSTGを作るということ】

お疲れ様です、プログラム担当の藤岡です

今回は技術話ということでUnityでSTGを作る上での話をご紹介しようと思います

(※今回言っているSTGとはいわゆるiOS/Android環境下における2D弾幕系のSTGと定義します)

先に補足しておくと僕はSTGについてはド素人で何もわかってないですし、作ってみたのも今回が初めてです

(関わったことはありますがUIやiOS/Androidライブラリ担当だったので・・・Androidライブラリは日の目を見ませんでしたが)

なので、「コイツSTGをわかってない!」と思われることもあるかと思いますが、実際わかってないことをご理解ください

そして書いてるうちに盛り上がって、ちょっとくどくなってしまいました…

すみません…

さて、気を取り直して続きを…

STGというジャンルはゲームの進化系統図においてほぼ根幹に位置すると僕は考えていて

始祖は、皆さんがご存知のインベーダーになるんじゃないでしょうか

(ビデオゲームそのものとなるとPONGとかの話が出るんでしょうが・・・その辺は話が逸れるので)

そんな根源的であるジャンルにおいて、文字通り死ぬほど重要視されていると思われるのが、

「操作」「動作」ではないでしょうか?

「操作」における重要度

操作における重要度というと、

過去に、某案件で入力遅延が(僕の知らないところで)発生しまして、

取引先からちょいちょい電話で釘を刺され、

上司からドヤされストレスで血便が止まらなくなったぐらいトラウマでして…(実話)

スマホ(タッチパネル)における「操作」については、

未だ納得がいかない人もいるかと思いますが、同ジャンルのアプリを見ていると、

個人的な見解ではありますが、ある程度結論は出ていると思っています

ボタン入力の有無については、個々、人の好き嫌いかなと思ってますし、

3Dタッチが標準になればまた変わるかなとも考えています

入力遅延についてはハードウェアやOS依存の部分が大きいので、

アプリケーションレベルでは解消しきれないのが実情です

正直、操作感覚だけで入力遅延を感知出来ない鈍感な僕は、

コードレベルで遅延が発生しないようにすることしか出来ません

そして今回、

問題になるのが「動作」です

まずメモリ

iPhone3GS時代は100MB、iPhone4/4Sで150MB、iPhone5/5S/6は200MBが

単一のアプリが使える量のデッドラインと考えています

一般的なスマホユーザーは常に複数個のアプリを起動させますので

やはりiPhone6が1GBしかない現状で200MB以上メモリを使用するのは利便性がよろしくないと考えています

(Infinity Bladeのようなハイエンド端末のベンチマークアプリはまた例外と思いますが・・・)

とは言え数字だけ見る人達からすれば使用メモリ量は売り上げにならないことなので、

そこに予算や期間をなかなかさいてもらえないのも難しいところです

特に2DSTGでは、もっぱらコマアニメーションを多用しがちなので、

テクスチャサイズや枚数について何をどの程度使うか厳格に決めねばなりません

ちなみにAndroidは、Javaの仕様上物理メモリが多いほど、

端末全体の動作が安定するとかなんとか・・・(超適当

次に処理負荷

STGのようなアーケードライクなジャンルは、やたらと60FPSを要求されます

STGにおいてはやはり”物量”がものを言いますので、

ビックリするほど大量にオブジェクトを表示させることが一番の課題となります

今回はマルチプラットフォーム対応としてUnityを採用しています

(それだけが理由ではないのですが割愛)

処理負荷を考えるにあたって、Unityを採用することで起きる問題というと、

大きくは以下の2つに分類されます

1.Unity由来の問題

2.C#由来の問題

1については、Unityを正しく理解する学習コストがかかる、

またはUnity側のアップデート対応を待つしかないという問題が発生します

事実、Unity暦4年目ですが未だに全てを把握出来てません

2については、90%がGC(Garbage Collection)の問題です

今まで「UnityでSTGは作れない」とおっしゃるベテランの方が非常に多かった印象ですが、

基本的に、矩形ポリゴンが容易に出せないことが大半の原因を占めていたように思います

当初(Unity4.0時代)は、僕はNGUIで無理矢理2Dゲームを作っていましたが、

NGUIがそういう使い方を想定していないので処理が遅すぎてお話になりませんでした

既にUnityを使用して開発をしている皆さんはご存知かと思いますが、

Unity4.2においてSprieRendererとQuadが実装されました

もう正直これだけで、UnityでSTGが作れない理由の8割は解消しました

残り2割はUIの問題です

Unity4.6からuGUIが実装されましたが、

根本的なパフォーマンス問題はNGUIと大差なく

Unity5.2から大幅改修が入りましたが、

未だにデリケートな場面で採用するかの判断は迷うところです

(※メニュー画面では活躍すると思います)

そんなわけで、結局エネルギーゲージや体力ゲージのような部分は

自前でMeshRendererを作るのですが・・・(不毛な作業です

スプライトを反転させるにも、

マテリアルのカリング設定をOFFにして逆向きに回転する手段しかわかりません

(方法があれば教えてください)

Unity5.5までのロードマップでは、2D関連の更新が多く予定されているので、

その辺りはいずれSpriteRendererで全部対応できるんじゃないでしょうか

コードレベルでのUnityのパフォーマンスを引き出すTIPSについては、

また別途ご紹介したいと思います

C#、というよりは.NET Frameworkと言うべきなのか

いいところはたくさんあるのですが、GCだけはいただけない

他にもUnityC#で明示的なインラインコードの書き方を知っている人がいたら是非教えてください(切実

GCを止めるには、GCが許容出来ない場面でのヒープの確保をなくすことが必要になります

(※抑えるということではない)

GC発生時の処理負荷も確保した量ではなく、

確保した個数によってふくれるという話(検証したわけではない)なので

細かなヒープのつまみ食いこそが危険だと考えます

C#におけるヒープを食うありがちな要因はstringです

stringを使う場合は放射性物質を扱うがごとく、メモリをこぼさないようにしなければなりません

他にも、foreachは内部でヒープを食っている疑惑や、

そもそもforより遅いしでEditor拡張以外では実用的ではありません

delegateのインスタンスも必ずキャッシュするようにしています

(当初気づかずにラムダ式でコールバック設定して毎フレーム食い散らかすという失敗が・・・)

GC以外にも細かな処理速度には気を配っていたりはします

Collection系は参照速度が遅いので固定配列にします

Propertyの{get; set;}も、コール時のオーバーヘッドが気になるので、

極力メンバの宣言を省略せずにメンバの実体から参照するようにしています

そんな不毛な作業を繰り返せばパフォーマンスは上がりますし、

C++だとかC#だとかっていう垣根は言語宗教の域でしかないと思っています

いずれ、検証結果を交えたコードレベルでの

C#におけるパフォーマンスTIPSをご紹介したいと思います

長くなってしまったので実用的な紹介は次回以降の技術話でやっていきます

次回はまた、舞台裏のお話

敵配置のやり方をご紹介しようと思います!

お楽しみに!

2015/12/04 + フジオカ