ひらしょー

平山尚が技術のことを書く場所。Unityが多そう。

非Active時にTransformをいじることの罠

長いこと謎の症状に見舞われていた。

 

DOTweenで拡縮のアニメさせていた物をGameObject.SetActive(false)で消した後、

GameObject.SetActive(true)で復活させると、おかしな大きさになるのだ。

復活させた時はアニメはつけていない。ただ静止画として出しただけだ。

しかも、大きさは毎回同じなわけではない。微妙に大きかったり、

微妙に小さかったりする。

 

さらに、明らかに画面上では大きさが変にも関わらず、

Inspectorで見ると値は正しい。例えばどう見てもスケールが1.3くらい

ある絵なのに、1だったりする。

そこで、試しにInspectorで1.0001とかを入れてみると、

大きさが正しくなる。1を入れてもおかしなままで変化はない。

 

Inspectorの表示がおかしいのか?と思い、スクリプト

スケールの現在値をDebug.Logしてみるが、やはり1だ。正しい。

 

実害がさほどなかったので「謎現象」として放っていたのだが、

今日たまたま著しく困るケースがあったので調べてみて、

ようやく原因と対処がわかった。

 

 

Transformの何かをいじると、普通は即時反映される。

例えばlocalScaleをいじると、そこにImageがついていたり、

その子孫にImageがついていたりすれば、その大きさはそのフレームに変更される。

子孫の位置や拡大率の変更計算が行われ、

また、uGUIのImage等があれば頂点を再計算する。

 

しかし、SetActive(false)されたgameObjectの下にあるTransformをいじった場合、

これらの計算が行われないようだ。

見えないのでわからないが、子孫要素の位置や拡大率は変化せず、

頂点もそのままにされる。

とはいえ、SetActive(true)された時にこれらの計算が行われて

正しい絵になってくれれば問題ないわけで、Activeでない時に計算を省くことは

なんら問題ではない。厄介なのは、これにもう一つのおせっかいが加わるからだ。

どうやらTransformの要素に同じ値を入れた時にはスルーする、

という実装になっているようで、これがマズいのである。

非Active状態で何か値を入れ、Activeにしてから同じ値を入れると、

非Activeで入れた値に関する計算は行われず元の絵のままなのに、

Activeにしてから入れた値がスルーされるので元の絵のまま、

という困った状況になる。

 

今回の場合、推測ではあるが例えば以下のような動作と思われる。

 

- DOTweenがスケールを1.3にする。

- SetActive(false)で消す。 

- 次に備えてスケールを1にしておくが、Activeでないので諸々の計算が省略される。

- しばらくしてSetActive(true)して、絵を初期化すべくスケールを1にする。だが、「すでに1が入っている」ので、計算がスルーされる。

- 結果、スケール1.3の絵が出る。

 

今のところこう解釈すると矛盾がない。

実際「一旦スケール2を入れて、次の行で1を入れる」ことで回避できた。

おそらく諸々の計算が2回走って重くなるので、

「SetActiveする前に1を入れる」が最良なのだろうが、

コードの都合上面倒くさいので今回は「一旦2を入れる」で行く。

(SetActiveするgameObjectがアニメするgameObjectのかなり上流にあるので)。

毎度大きさが違ったのも、Activeだった最後のフレームにおけるDoTweenの

値がdeltaTimeの具合によって異なるからで、説明がつく。

 

Unityのバージョンは2017.1.2p3。5.6系の時代にもこの症状は出ていたし、おそらくはさらに前からだろう。

 

こんな根幹の挙動が今更バグるとも思えないので、たぶん仕様なのだろう。

辛い。

アセットバンドルの使い方で悩む

ダウンロード待ちは嫌なものだ。

 

極力少量のダウンロードでゲームを始められるようにしたいし、

まとめてダウンロードしてほしいタイミングでは

極力高速にダウンロードを済ませてほしい。

 

それには、元々のデータが小さいのが理想だ。

ダウンロードも速いし、管理の負担も小さく、

そもそも必要もないものを作らなかったということで開発も安く済む。

技術的には何の工夫もいらない。そうあるべきだ!

しかし、それで済むような小さなゲームでないのなら、

何かしら工夫する必要がある。

 

Unityで起動後ダウンロードする手段といえば、アセットバンドルだ。

サーバに置いておいて、初回はネットで送ってクライアントに保存し、

以降はローカルからロードする。

スマホの場合昔のゲーム機と違って光ディスクのように

遅いわけではないので、ローカルにあればそこそこ

高速にロードできるだろう。

光ディスクはファイルのロード開始までに100ミリ秒

とかかかる悪夢のような機械だったし、HDDですら

10ミリ秒とかかるのでファイルが多いと遅かった。

まあスマホが実際に速いかは測ってはいないのだが。

モノにもよるだろうし。

 

さて、ダウンロードを速くするには、ファイル数は少ない方がいい。

ローカルからのロードでもそうなのだが、ファイルが多いと「ファイルごとに

かかるオーバーヘッド」の影響を強く受ける。

ましてネットならなおさらだ。

「このファイルくれよ」「はいどうぞ」というやりとりをサーバとするだけで

0.5秒とかいう時間がかかってしまう。

キロバイトの小さなファイルごとにそれをやるのは馬鹿馬鹿しい。

そういうわけで、ダウンロードのことだけ考えれば、

巨大なアセットバンドルが一個あるのが一番速いわけだ。

 

ところが一方、更新の問題がある。100MBのアセットバンドルの中身の

テクスチャを1枚だけ更新したとする。お客はまた100MBダウンロード

せねばならない。これはマズい。更新が発生しそうな所に関しては、

バラでダウンロードできる方が良い。ただしダウンロードそのものは遅くなる。

 

結局のところ、それらのバランスを取ってまとめる単位を決めることになり、

それには「代表的なケースでの実際の数字」がないと話にならない。

例えばファイルごとに余計にかかる時間が1ファイル1秒だとしよう。

また、転送速度は1MB/sだとする。

総量が1GBであれば、1ファイルなら1000秒かかる。

10ファイルに増えても、1000秒+10秒で、あんまり変わらない。

それなら更新のことも考えて10ファイルの方が良さそうだ。

そして100ファイルになると、1000秒+100秒で、ちょっと遅くなってくるが、

管理上それくらいに割りたいのであれば、まだ許容できるかもしれない。

ただ、1000ファイルともなると2000秒になり、それはちょっと辛いので、

ファイル数はそんなには増やせないな、という話になる。

実際にはダウンロードを並列させることでファイルごとのオーバーヘッドは

軽減できるのでそのへんも見ないといけないし、

コンテンツ追加や修正の頻度や量も加味する必要があるだろう。

 

さて、落とした後のことも考えないといけない。

 

落とした後はローカルからロードするのだが、もし、

「全部読み込まないと中身が取れない」とすると、

あまり大きな単位でまとめるとマズいことになる。

 

例えば、1GBに1000個の画像が入っているアセットバンドルがあって、

そのうちの1枚の画像が欲しいとしよう。

この時に、1GB読まないといけない状態では困る。

最初の画像を出すための待ち時間がえらいことになるし、

メモリ消費も大変なことになる。

もし音を入れておいた場合、鳴らしたくなってからロードしたのでは

間に合わないだろう。ボタンを押した時の音があまり遅れると辛いことになる。

 

アセットバンドルの場合、LZMAで圧縮しているとこういう状況に陥るようだ。

ビルド時に何も指定しないとそうなる。総容量は減るが、展開が遅いし、

部分だけ取り出すことができない。

もしダウンロード速度を最優先してLZMAにするのであれば、

アセットバンドルはあまり大きくしない方がいいし、

明らかに同時に使うとわかっているもの以外はまとめない方がいいのだろう。

試しに1GBのテキストアセットを入れたアセットバンドルにLoadFromFileを

かけた所、60秒もかかった。どう考えてもファイル全体を処理している。

 

一方、LZ4、つまりChunkBasedCompression入りで

アセットバンドルを作っていれば、

部分だけ取り出すことができるようだ。LoadFromFile(Async)では

目次だけを読み込んでファイル全体は読まず、

LoadAsset~で初めて実際にファイルから読み込むように見える。

であれば、どんなにアセットバンドルが大きくても問題ない。

仮に何もかもが1つのアセットバンドルに入っていたとしても、

その時に使うものだけを読み込める。ロード時間もメモリも無駄にならない。

同じく1GBのテキストアセットを入れたアセットバンドルを作ってみたが、

LZ4をかけておけば1秒でLoadFromFileが終わる。

ファイル全体を見ていたらありえない速度だ。

 

ゲームのデザインや素材の物量によっては、

「とにかくたくさんあって、いつどれが使われるか事前に予測し難い」

という状況はありうる。

例えばエフェクト類とか、短い音声とかだ。

操作や展開次第で「何を鳴らすか」「何を出すか」が違ってくる場合、

使い得る物を全部メモリに展開しておきたくはない。

使わずに終わるデータがあるなら無駄なロードをしたことになるし、

そもそも同時に全部メモリに置くことが許容できないかもしれない。

遅延が許容できる範囲で済むなら、使うとわかってからロードしたいわけだ。

しかし、ファイルがバラだとダウンロード時間が長くなる。

キロバイト量のファイルが数千個、みたいな状態は嫌だ。

もしそういう時に、「1ファイルに数千個入れておけるが、

ロードの時にはその中の必要なものだけを取り出せる」のであれば、

全部まとめて1ファイルにしても問題が出ない。

LZ4のアセットバンドルはそれを可能にしてくれるものと思われる。

昔はそれを可能にするためにファイルを結合するものを自作していたし、

「サーバにはzipして配置、クライアントで展開してアセットバンドルをバラで配置」

といった手も考えられるが、その必要はなさそうだ。

 

さて、もう一つ考えておかねばならないことがある。

「どのアセットがどのファイルに入っているかを誰が知るか」だ。

 

コードを書いている時に考えることは、

「charaAという名前のSpriteが欲しい」

とか、

「bgm_battleという名前のAudioClipが欲しい」

というようなことで、それがどのファイルに入っているかなんて

知りたくもない。

 

ファイルにまとめる単位は上記のように諸々の事情で決まるため、

コードを書いた後でまとめることだってあるだろう。

更新のことを考えて、まとめ方が後から変わることだってある。

であれば、コード中にファイル名は一切出て来ない方がいい。

アセット名だけで済むように仕掛けを作っておく必要がある。

 

しかし、当然ロードするにはファイル名がわからないといけない。

「○○というファイルの中の、××というアセット」と指定しないと

ロードできないからだ。

そこで、「××というアセットは○○というファイルにあるよ」

ということを知っているクラスをどこかに用意して、

そのクラスに「○○くれよ」と言うと、それが入っているファイルを

開けて出してくれる、という感じにする必要がある。

 

AssetBundleの場合、作成時にmanifestなるものが吐かれ、

そこには「何というアセットが入っているか」が書かれている。

これを使うのが良さそうだ。

加工してjsonなりにしてサーバから送信してもいいし、

これをそのままクライアントにダウンロードしてしまって、

落としてから加工して目録を作ってもいいだろう。

そのへんはやったことがないので何とも言えない。

しかし何かしらそういうものが必要になる。

 

ただ、ファイル名を指定したいこともある、というのは注意がいる。

例えばキャラごとにファイルがあって、

それぞれにfaceとかbodyとかいうスプライトが入っている、みたいな時だ。

読むファイルだけ換えて、後のコードは一緒にしたい。

virtualな関数みたいなものである。

なので、ファイル名の指定をできなくしてはいけない。

そもそもアセット名がグローバルに唯一、なんてことは保証し難いわけで、

同じ名前のアセットが複数あるケースはあるものと考えないといけない。

アセットの名前だけでロード要求された場合、それが複数あったらエラーを返す、

というようなことになるだろう。「最初に見つかった奴を返す」

とかだと絶対バグるだろうな。

 

以上まとめると、

 

- アセットバンドルのファイル数が少ないほどダウンロードは速いが、更新や管理と妥協しつつ作る単位を決める感じになるんだろう。

- LZ4(ChunkBasedCompression)でアセットバンドルを作れば一部だけ取り出せるので、同時にロードする確証がないものでもまとめて入れておける(っぽい)。

- 何がどのファイルに入ってるかを気にしないでロードできるようにしたい。

 

という感じだろうか。「一部だけ取り出し」が本当にそのように動作するかは、

実のところまだ確認してない。

1万ファイルくらい入れた巨大アセットバンドルから1個だけ取り出して、

プロファイラで測定する、ということを後でやっておく。

さすがに確認しないと怖くて使えない。

後でここに追記するか、別記事で書くと思う。

テクスチャ圧縮の厄介さ

Unity、というか、スマホ向けにゲームを作るにあたって、

テクスチャ圧縮は悩ましい問題だ。

 

ゲーセン及び家庭用では「とりあえずBC3(DXT5)」

な時代が長かったし、BC4やBC5、A8やL8といった

フォーマットも選択肢としてあった。

対応機種が複数であっても、

手元に全ての機械があったし、仕様もはっきりしていた。

圧縮は自前でやっていたので、

デザイナーが自力でやる、サーバに置いて自動でやる、

みたいな選択肢も自分側にあった。

BC7が現れて圧縮に時間がかかるようになった時にも、

マシン並列やGPU化で高速化する選択肢があった。

 

だがUnityでスマホ向けとなると話が違う。

 

まず、機種が全部手元にない。比率も確実にはわからず、

本当に動くかもわからないとなると、

安全側に振らざるを得ない。

いろんな形式を用意して、機種によって最適なものを

使ってもらう、というのも手間をかけられるなら良かろうが、

手間イコール金と時間なのが難しい。

そういうわけで、「全機種で使えるであろう形式」

に絞れると楽、という事情がある。

あるいは古い機械を相手にしない、という選択もありうるが、

それは技術というよりは商売の問題だろう。

 

もう一つ面倒なのは圧縮がUnityの中で行われ、

その間は操作不能になることだ。

また、複数バージョン持っておくこともできず、

設定を変えれば古いものは消えてしまう。

とにかく圧縮のやり方に関して自由度がない。

別のマシンで並列化とか、自作の高速仮圧縮で

とりあえず体裁だけ整えるとか、

低画質版と高画質版を両方持っておくとか、

そういうことができない。

なおさら、全部の機械で動くと期待できる形式だけを

用意して終わりにしたくなる。

 

Unityで使える形式は、まず、無圧縮32bit。24bitとあっても、

メモリの中では32bit食う機械も多かろう。

そういう機械では24bitでロードすると32bitへの変換が

走って余計に遅くなる。

無圧縮と言えば32bit、と考えて良いのではないだろうか。

 

16bit系は565っぽいもの(RGB16)と、4444っぽいもの(RGBA16)。

5551、つまり色15bitにアルファ1bitのフォーマットは

使えないように見える。

 

8bitの1チャネル系はA8だけ。

シェーダでどう出てくるかは確認していないが、

RGBが1でアルファだけ値が出てくるとすると、

若干使いにくい。

 

もっと減る形式として古い機械でも動くものは、

Android用にETC1、iOS用にPVRTC。

ETC1は透明度を持てず、

ETC1、PVRTC共にかなり劣化の仕方にクセがあって、

苦手とする画像が異なる。

余計な手間を減らそうと思うと、

Androidではこの画像が汚ないのでどうにかしたい」

みたいなことは極力避けたい。

そもそも実機で確認する、ということ自体が手間なので、

UnityEditor上で劣化の状況まで確認できる方が良いのだ。

とはいえ1画素4bitという圧縮率は捨てるには惜しいので、

ほどよい匙加減が求められる。そのへんが面倒くさい。

 

両方で同じものが動くようになるのは、

ASTCの普及後ということだろう。

人によっては「すでに普及している」と考える人もいるだろうが、

それは商売の問題だ。

個人的には私が持っているスマホが対応していないので、

私は「ASTCはまだ普及してない」と思っている。

 

 

さて、そんな面倒くさいテクスチャ圧縮を

何のためにやるかと言えば、

 

- 実行時のメモリ使用量の削減、

- 実行速度の向上

 

この二つだ。ストレージ容量や、ネットでの通信容量

に関しては、pngやjpgにした方が余程減るわけで、

テクスチャ形式なんて気にする必要はない。

しかしpngやjpgは展開しない限り描画に使えないわけで、

ブラウザだってメモリの中ではjpgやpngは展開されている。

なのでメモリ消費量を減らそうと思えばjpgやpng

役に立たない。GPUがハードウェアとして

対応している形式でなければどうにもならない。

 

となると、jpgなりpngなりで転送、

ストレージに置いておいて、

メモリにロードしてからGPU向けの圧縮形式に変換して使う、

というのが理想なはずなのだが、そうは行かない事情がある。

このGPU向けの圧縮形式への圧縮に、

凄まじく時間がかかるからだ。

とても実行時にできるような代物ではない。

ただし、よほど品質を妥協すれば、

jpgで読んで圧縮し直して使う、ということは実際可能だ。

Unityでもやってできないことはないように見える。

ただ凄まじく面倒くさいことになるし、

品質も論外になるため、

かなり限られた用途でしかやらないだろう。

例えば、莫大にたくさんある画像から実行時に何かしら合成して、

しかもそれを多量にメモリに載せないといけない、とかだろうか。

例えばスポーツ選手が数千人入ったゲーム、

みたいなものだと、顔写真データは極力減らさないと

転送が遅くなりすぎるし、ストレージも辛い。

なのでjpgで持っておいて、ロードしてから展開したくなる。

しかし同時に多数の選手が出てくるなら、

無圧縮だとメモリの消費も痛いだろう。

後述するように性能の問題もある。

そこで品質に妥協してでも圧縮をかけ直す、という選択は

ありえる。実際にやる例があるかどうかは知らないが。

 

そして性能にも影響する。テクスチャをメモリから画像データを

演算器(シェーダ)に運んで、計算し、

それをまたメモリに書き出すのが描画というプロセスなわけだが、

最初にテクスチャをメモリから運ぶところは量が多いほど

時間がかかる。電気も食う。

さらに、演算器から見ればメモリはずいぶんと遠くにあるため、

演算器の近くに小さなメモリ(キャッシュ)を置いておいて、

良く使うものはそこに置いておくようにしているのだが、

データが大きいとすぐにそれが一杯になって、

遠くのメモリまで見に行かねばならなくなる(キャッシュミス)。

結果、テクスチャがデカいと性能も落ち、電気も食う。

 

しかし、これに関しては実際どれくらい遅くなるかは

機械による。そしてその機械が全て手元にあるわけではない。

手元の機械で測っても、違う挙動をする機械はありうる。

メモリが高性能でデカいテクスチャでもビクともしない

こともあるだろうが、安物はそうは行くまい。

実際、メモリと演算器をつなぐ道幅を広くすると

テキメンに機械の値段が上がるので、

安物は道幅を狭くしてあることが多い。

iPhoneは基本高級品なので、iPhoneでばかり見ていると

私のようにymobileで「安いのくれ」と言って買うような

タイプの人を見逃すことになる。

スマホが出始めの頃は毎年買い換えるようなタイプの人が

多数いて、それに応えるように

毎年性能が倍々になっていく世界だったが、

今はもはやそうでもない。

「頑丈で防水」とか「デザインがいい」とか、

そういう理由で買われる機械の性能を高める理由はあまりない。

実際、私が2017年後半に買ったS2という機械は、

2012年発売のiPhone5にも大きく劣る。

「新しい機械は速い」はもはや成り立たないのだ。

要求性能を低くできるなら、それに越したことはないだろう。

テクスチャ圧縮はそのための武器になる。

 

ただ、逆に言えば、メモリ消費量と性能の問題がないなら、

こんなに面倒なテクスチャ圧縮なんてしなくてもいい、

ということではある。テクスチャ圧縮なしで問題ない

サイズや性能要求に収めて開発する、という選択はアリで、

たぶんそれはコストを結構大きく下げる。

「メモリ4倍だぞ?圧縮しないとかありえんだろ」

と私なんかはすぐ思ってしまうが、それが持ちこむ

面倒の大きさは結構バカにならない。

ましてUnityの場合それを解決する手段がなく、

インポート時間やビルド時間の増加はボディブローのように

ゲーム開発を蝕むのだ。jenkinsに出したから大丈夫、

などという単純な話ではない。

圧縮済みのテクスチャをインポートできたらいいのにと

本気で思うが、今できないということは

そういう要望はあんまりないということなんだろうな。

高速化なあ...

技術ブログらしく、ちゃんと構成を考えて、

いらんことを書かずに情報価値の高いものを書こう、

と思っていたのだが、その結果、一度も書かないまま

数ヶ月が過ぎるという状態になった。

 

本を書いてすら脱線と雑談まみれのものになるのだから、

本ほどの労力をかけられるはずもないブログが

まともなものになるわけがない。

 

あきらめた。それよりはメモでも何でも書いた方がいい。

そもそも、情報それ自体で勝負するスタイルでは私は不利なのだ。

 

さて、最近は高速化をやっている。もちろんUnityでの話だ。

 

過去、高速化仕事をせずに終われたプロジェクトはなかった。

しかしUnityならば違った展開もあるだろう、と期待していた。

エンジンの思想に沿った範囲で無理なく作り、

それで浮いたコストをより価値のある、お客の喜ぶところに

つっこむ、というのが皆が幸せになる道であるはずだ。

スマホの処理速度はおおむねPS VITAを越えており、

あまり無理をして高速化せずとも十分綺麗なものができる

公算は高い。

 

でもまあ、やっぱりそうは行かないよなあ。

 

速いマシンはより遅いコードを書くために使われる。

UnityのオーバーヘッドとC#のオーバーヘッド、

それに数々の新しい書き方のオーバーヘッドが

重なると、とてもVITAより速い機械とは思えない状況になる。

 

では、Unityを2Dメインで使っている場合の負荷はどこに来るか。

 

- シーンの初期化スパイク

- 動的Instantiateによるスパイク

- ガベコレによるスパイク

- DrawCall関連負荷

- 通信関連負荷

- 2Dゲーム特有の重ね塗りの激しさ

- Canvasの頂点詰め直し関連負荷

 

このあたりだろう。

 

シーンの初期化は悩ましい問題だ。

そんなの別スレッドで初期化まで済ませてくれるんでしょ?

くらいに思っていたが、ユーザスクリプトは基本メインスレッド

でしか動かないわけで、AwakeやらOnEnableが

大量についているとメインスレッドが遅くならざるを得ない。

昔ながらのやり方で、画面が止まってる時にガッツリ

初期化してしまうのがたぶん一番いいのだろうし、

Unityはそういう前提で作られているように見える。

 

しかし、それでは待ち時間とメモリ消費が増える。

多少ゲームがガタついても、必要になるまで初期化を遅らせれば、

最初に画面が出るまでの待ち時間は減るし、

そもそも使わずに済むものを無駄に作ることもなくなるから、

合計の待ち時間は減るのだ。

スパイクを削るために、エフェクト全種類を最大数生成する、

みたいなアプローチは、必ずしも良いとは言い難い。

メモリの消費量の問題もある。

 

なので、シーンの中にはあまりモノを置かず、

極力プレハブから動的初期化、ということにしたいのだが、

これがまた重い。作りやすさを重視すると、

行列乗算の必要がなくても「単にまとめるためにgameObject置く」

みたいなことになりがちで、どうしても無駄に重くなる。

匙加減としか言いようがない。

 

ガベコレも面倒だ。ガベコレは完全には抑制できないし、

C#的な普通の書き方をしていれば毎フレームキロバイト

量のnewがされるのはやむをえない。

だいたい通信が来れば、そのデシリアライズ

避け難くnewが走るわけで、エフェクトの類をいくら

事前生成+使い回ししてもゼロにはできない。

foreachも配列以外では全てnewが走る。

となれば「気にしたら負け」くらいに思いたいところなのだが、

さすがに限度がある。数十ms止まる、みたいな状態は

さすがに許容し難い。ガベコレの負荷は使用するメモリブロックの

数に比例するはずなので、とにかく数を減らしたいのだが、

なにせnewは見えにくい。boxingはとりわけ厄介で、

デシリアライザの類でやむをえずobjectを経由する、

みたいなのはなかなかに避け難い。

 

DrawCallも重い。よくSetPassが問題でDrawCallは

問題ではないというようなことが言われるが、

GLES2でもそうなのかは確認していない。

DX11やGL4はスマホからは遠い世界であり、

PC向けインディーを作っている人達の知見が

そのまま使えるとは限らない。

GLES2はよほどうまく実装してもそう速くできないだろう。

ドライバ層が厚すぎる。

metalやGLES3、vulkanなどもあるが、

そういうAPIが使えるのは高い機械で、

高い機械は放っておいても速いのだ。

「いい機械を持っている人にはいい体験をしてほしい」

という気持ちはあるし、やれる範囲ではやるが、

手元にない機種に多数対応せねばならない状況では、

できるだけ同じプログラムを通したい。

GLES2を捨てられないなら全部GLES2の方が

問題は起こりにくかろう。

 

通信関連負荷も馬鹿にならない。

文字列処理は元来重い処理で、ゲーム中に動的にやるのは

避けたいものだ。しかし、作りやすさを考えると、

文字列は扱いやすく、仕様変更にも強い。

そうなると、JSON的なものが中核に入ってくる。

しかしこれがまた重い。まして自動でクラスインスタンス

生成する現代的な形でのデシリアライズをやると、

リクレクションまみれのコードになる。

データ設計が現代的だと、クラスが多数ネストするので、

newも増える。

こういう状況で、通信を無視できる負荷

に抑えるのは結構しんどい。

「無視できる負荷」というのは、

想定する一番ショボい機械で1ms未満だ。

今のところの感覚として、エディタの3倍くらい

は時間がかかる感じなので、エディタで0.3ms

に収まっていればいいのだろうが。

 

あとは2Dゲームの重ね塗りのエグさも如何ともし難い。

画面を暗くするのに上に黒い透けた板を重ねれば、

それで一画面プラスだ。

不透明なものがほとんどないので、奥から描かざるを得ず、

Zで削ることもできない。

画像を不透明部と半透明部に割って先に不透明部を

手前から順にZテスト付きで描く、

という最適化は理屈上可能だが、

それを内部で自動でやるように仕組みを作るのは至難だ。

内部自動でできなければ開発コストが増すわけで、

ただでもかさむUI実装負荷を増すようなマネはできない。

UIは仕様変更が多く、組む手間が大きくコストに響く。

極力「Unity的にフツーの組み方」そのままでやれないとキツい。

かといって謎のスクリプトで最適化をかけてからビルド、

のようなアプローチは、「ビルドした時だけ絵が変なんだけど」

みたいなバグの原因になって、それはそれでコストになる。

 

しかも、スマホの機械はおおよそメモリ帯域が狭いだろうから、

なんぼシェーダが単純でもテクスチャフェッチと塗り

でごっそり持っていくだろう。

透明な部分をポリゴンを切ってくれれば軽減はできるが、

標準のUI.Imageはそんなことはしてくれない。

透明部を切り落とすものを自作することはできるが、

それで頂点が増えればCanvasの詰め直し負荷が上がる。

 

canvasの詰め直しの負荷は結構深刻で、

頂点数が数百でも無視できない負荷になる。

とりわけ、無駄にgameObjectの階層を重ねて

Transformが多数ある場合は重いっぽい。

それだけ行列演算のコストがかさんでくるということだろう。

 

動かないものと動くものを分けろ、と言うが、

そんな面倒くさいことはできればやりたくないし、

下手にやればDrawが増えて逆効果になりかねない。

その意味で、よほどはっきりと割れない限りは上策とは言い難い。

それに現代的なゲームでは大抵のものは動くのだ。

 

頂点数が増えがちなのは文字描画だ。

最低でも文字数×4の頂点が必要になり、

標準のOutlineをつけるとそれが7.5倍にふくれ上がる。

リッチテキスト有効だとタグ分も頂点も加わり、

それはもうひどいことになる。

面積ゼロの三角形を削るフィルタをかましてタグ

分は削れるが、その負荷もタダじゃない。

また品質が許容できれば軽量版のOutlineを用意したりするのだが、

それでも頂点が多い。

レンダーテクスチャに焼くなどせねばならないのだろう。

面倒なことだ。

 

ロクにまとめないままズラズラと書いてきたが、

本当、容易じゃないなあ。

技術ブログをやろうと思う

私は20年以上前から

http://www.page.sannet.ne.jp/hirasho/

に日記を書いてきたわけだが、内容はごちゃまぜで質もバラバラだ。

技術についてきちんと文章にまとめる機会がある方が腕が上がると

思っているので何か書きたいのだが、

転職してからというもの仕事が忙しいのでとても本など書いているヒマはない。

だがブログの形であれば小出しにできる。

そこでここにアカウントを作ってみた。技術者の間ではここがメジャーらしい。

 

さて、今の私に人様に伝えられるような技術があるのか?

なにせUnityでモノを作るようになってもうすぐ1年という素人だ。

 

しかし、最近はどうやら普通に仕事ができている気もするし、

UnityだろうがC#だろうがプログラムを書くことに変わりはなく、

C++でやっていたことはほとんどそのまま活きている気がする。

職場でも私以外があまりやらないことがあったりするので、

そういうことを書くのは誰かにとって面白いかもしれない。

 

さて今日は内容はないが、漠然と考えているネタを列挙しておく。

  • update+switch vs コルーチン
  • delegate vs 直接呼び出し
  • 動きをつけるのに使える「数学的な意味での」関数について(expとかlogとか)
  • IEnumeratorそしてコルーチンというのはつまり何なのか
  • 3DオブジェクトとUGUIを協調させる(3D空間内の物体をUIに向かって飛ばすとか)
  • よくある隠れたメモリ確保について
  • プログラムでタップを発生させてボタンを押しまくる話(EventSystemでのやり方)
  • 全てのcanvasについて、マテリアル数、頂点生成が走るか否か、その頂点数の概算、を得て性能解析に役立てる話。実装と用法。
  • 敢えておおまかに性能を測ることのすすめ。理屈と実践。
  • AfterEffectsからUnityにアニメを持っていく話

たぶん他にもいろいろある気がするし、実際問題いつ書けるのかわからないが、

今日のところはともかくも「ブログを始めた」ということで。