ひらしょー

平山尚が技術のことを書く場所、にするつもりだったが、ネタを限定するのはやめよう。日記というか思いつきを書く。

CEDEC2018の2日目メモ

https://2018.cedec.cesa.or.jp/session/detail/s5ae163a207655

 

「意識の統合情報理論」。題名に迫力がありすぎて、

失礼ながら地雷を覚悟で聞きに行った。

すごいプレゼンうまい人が出てきてプレゼン力でゴリ押しするような状況を

想像してしまう。のだが、杞憂だった。

地味にフツーにしゃべる人で良かった。

 

で、内容は意識についての話なのだが、

「意識というものが存在することは認めるとして、

それが存在するにはどういう条件が必要か?」

というアプローチで考えるのが面白い。

「弱いロボット」の話と構造は似ている。

「人にそっくりだし意識ありそう」という見掛けを盛るアプローチで行かず、

必要条件から行く引き算アプローチだ。

植物状態の人にテニスの話をしたら、

普通の人と同じように脳の活性化が起こった、という話がある。

全く反応しなくても意識があるのかもしれない。

外から見た目で判断すると誤る可能性がある。

 

そもそも意識は主観的なものなので、

「私の赤と、あいつの赤は同じなのか?」

という問題は絶対に解けない。絶対に解けないとわかっている問題は

問題ではないので、そこには触らない。

そこで「意識のようなものが存在するためには何が必要か」

と考えれば先へ進める。

 

まず、意識は情報を生み出す必要がある。

何かを思い出したり想像したりする時、そこでは情報が生成されている。

単純に言って、脳内で「バカな」と言えば、そこで

「バカな」という文字列の持つ情報量が生成されている。

ダイオードは電流に対応して光るか光らないかで1bitの情報を発生させるので、

「情報を生成する」という機能は持っている、という話があって、

その話の持っていき方はまさしく引き算だと感じた。

もし「情報を生成する」だけをもって意識の条件とすれば、

ダイオードは微小な意識を持っているかもしれない、という話になる。

そういう論法が学者っぽくてすごく参考になる。

 

意識は統合されている。一個しかない。

脳は結構並列で処理してるはずで、自転車乗りながら歌ったりできるわけだが、

それはそれとして意識は一個しかない。2個意識があったりはしない。

脳は左右割れていて、それぞれ右と左の視野がつながっているのだが、

右視野しかない意識と、左視野しかない意識があるわけではない。

くっついてしまって境界がわからない。

これを意識の条件であるとするならば、

デジカメにはたぶん意識がない。

それぞれの画素は情報が混ざらないでそのまま外に取り出される。

デジカメはかなり大きな情報を発生させるので先の条件で見れば

結構意識っぽいのだが、それぞれが独立なので意識っぽくないわけだ。

この論法に痺れる。

 

次は寝ている時と起きている時の比較。

寝ていて意識がない時にも、脳はフツーに活動している。

MRIで見れば脳は動いており、それをもって意識の有無を判定することはできない。

じゃあ何が違うんだ?ということになる。

そこで、「統合情報量」というものを考える。

情報を発生させるネットワークについて、それをテキトーに切った時に

どれくらい情報が失われるか、という量だ。

デジカメはそもそも画素間につながりがないので、減りようがない。

寝ている時には脳の各部位がバラバラに動いていて、

仮にどこかで切れたとしてもあまり影響がないが、

起きている時は一塊のものとして動いているので切れると情報生成量がごっそり減る。

という仮説。細胞の塊と個体の間にある壁、みたいな話だろうか。

 

そして意識の排他性。意識は2個ない。

左脳だけの意識はないし、右脳だけの意識はない。

しかし、左脳と右脳の連絡が切れた人とかでは、二個意識があるかのような

現象が起こることがあるという。

そこで、「統合情報量が最も大きくなる範囲で意識が発生する」という仮説。

左脳と右脳がくっついていれば、個別の統合情報量よりも全体の統合情報量の

方が大きいので、全体で一つ意識が発生する、と説明する。

二人の人の間で意識が一つに融合しないのは、連絡が疎で

統合情報量が少ないからだ。

とすれば、二人の人の脳がもし緊密に連絡するようになったら、意識は統合

されて一つになるかもしれない、という話にもなる。

 

最後に、意識には構造がある。

本とか、風景とか、そういう構造や階層を表現できるデータ構造である必要がある。

デジカメは画素がバラバラなので構造を表現できない。

デジカメには意識がなさそう、というのはここからも言える。

階層や構造が表現できるのであれば、その実装が脳細胞であるか、

電子回路であるかは問題ではないのではなかろうか?

 

という具合で、話が盛り上がってくる。たまらない講演だった。

 

さて、これをどう使うか?

 

引き算路線で、かつファインマンの言う「作れないものは理解できてない」

を信奉する私としては、作ってみるのが良いのだろう。

出てきた条件を満たす最小の何かをソフトウェア実装して、

それがどんな挙動を示すかをゲームの中で見てみたらいいのではなかろうか。

例えばニューラルネットワーク的なデータ構造を作るとして、

統合情報量が大きくなるような接続がどのようなものかはわかっている。

上流と下流がはっきりした接続形ではなく、再帰的に下流から上流へ

情報が流れる形の方が統合情報量は大きい。

そういうものに何かを流して、何かが出てきたとしたら、

そこには意識っぽい何かがあるのだろうか?

さっぱり具体的な想像はできてないが、面白すぎるテーマだと思う。

 

https://2018.cedec.cesa.or.jp/session/detail/s5b14f5a6b9601

 

レイトレ。ラスタライズ時代に最先端から脱落した私としては、

レイトレ時代に一発当てたいという邪な考えがあったりして、

レイトレには微妙に興味がある。

しかし、レイトレ全盛時代を待たずして、

すでにしてレンダリングは一部レイトレ化されているわけで、

ハードが出揃った後にノコノコと参入しても遅いのだろう。

興味あるやらやれよ、と私も思う。

しかし、なにせレンダリングに労力をつっこむのはなかなか割に合わない。

何もしなくてもunityにいろいろ入ってるわけで、

それで出る絵に自分がどれほど価値を加えられるのか?

と考えると、フツーにゲームそのもののコードを書いて客に出す方が

割に合うよなあと思ってしまう。

 

そんな微妙なことを考えつつ、聞きに行った。

「あ、結構行けるじゃんレイトレ」という感じ。

もちろん純粋なレイトレではなくハイブリッドなのだが、

ピクセルあたり2.25レイ、とかで許容できる絵が出るのは驚きだ。

レイのキャッシュ、溜まったレイデータからの空間再構成、

そして輪郭を崩さないフィルタリング。そのあたりがミソなのかと

勉強になった。以前JSでCPU実装した時もちょろっとやったのだが、

なにせ純粋なレイトレだったのでZバッファがなく、

バイラテラルフィルタをかけようがなかった。

どうしても輪郭をまたいでしまってボケボケになっていた。

Zバッファがあればそのあたりはかなり楽になるし、

メインライトからの直接光はフツーにシェーディングしてしまえば、

レイトレにかかる負荷はだいぶ減る。

 

ただ、テンポラルのフィルタにかなり頼っている印象があり、

カメラが激しく動く普通のゲームではなかなか難しいかもしれない。

ドカッと動いてしまった時のテンポラルフィルタの害を

軽減する手法はいろいろあるが、当然その領域ではサンプル数が減って汚なくなる。

もうこれは機械学習じゃないだろうか。

データがない所は生成してしまえばいいのでは?

過去フレームと、今フレームで足されたレイのデータから、

現フレームを生成するようなネットワークに画像を

作らせてしまうことはできないだろうか?

計算量的に可能なのかはさっぱりわからないが、

そういう実験に時間を使えるくらいヒマにならないかなあと思う。

 

レイトレの利点は、ラスタライズ時代に積み上げられた

数々のテクニックが無用になることで技術力競争での不利さが若干緩和されることと、

何よりもデザインの手間が減ることだ。前もって作っておく素材がだいぶ減る。

安くスパイスの効いたゲームを作りたい私としては、

デザインのコストをとにかく下げたい。

今の状況で3Dゲームを真面目に作るとデザイン工数で死ぬし、

真面目に積み上げてきた会社には当然勝てない。

レイトレで多少はマシにならんかなあと思っていたりする。

そのためにも、多少無理をしてでもレイトレを実戦投入してしまいたい。

ゲームのとあるシーンだけで妙に反射屈折してる、みたいなことでもいい。

基礎研と称して製品に入れずに実験していてもさして意味はないので、

稚拙でもなんでも「使えなくもない用途」を無理矢理見つけだして

製品につっこんでしまいたいところだ。それが足がかりになって

用途を拡大していける。

とりあえず、スマホでもコンピュートシェーダ開かないかなあ。

 

なお、「簡単にできる」とセッション名についているが、

実装は結構大変そうだった。楽になるのはデザインだけかもしれない。

最後に関係者の名前が並んでいるスライドがあったが、

数十人だった。「一人で半年で作りました」ですら

こうしてゲームを運用している状況ではしんどいわけで、

あれを自分でやるのはキツいなあ。やりたいけど。

 

https://2018.cedec.cesa.or.jp/session/detail/s5af14318930d4

 

ブレインマシンインターフェイス。すぐにどうこうなる話じゃないだろうと

思いつつ聞きに行った。

そして、すぐにどうこうなる話じゃないなとわかった。

 

脳波はとにかくノイズが乗るし、信号が広がってしまって空間解像度が悪い。

細かいことはできない、ということだ。ただし、時間解像度はいい。

そして、器具が他に比べれば小さく安い。実質娯楽に応用するなら

これしかないので、何かやるならこれについて考えることになる。

 

MRIなら信号の空間解像度はいいが、時間解像度が最悪だ。リアルタイム性がない。

そして、数年以内にMRIが帽子くらいのサイズになって10万円とかになる

ことはありえない。しかし、研究という意味ではこれが一番脳のことがわかっていい。

 

脳に電極刺すアプローチは空間解像度も時間解像度も申し分ないのだが、

いや、脳に電極刺せないでしょ普通、という話になって終わる。

ただし、医学的な応用としてはたぶんこれが一番強力で、

失明しちゃった人をどうにかするとか、聴力を失った人をどうにかするとか、

腕がなくなった人の義手を脳直結で動かすとか、

そういう時には手術して何か埋め込むアプローチもアリだ。

私ももし右聴力を失ったら手術でも何でもしてもらうと思う。

左聴力を失ったプチ障害者である私としては、

この手の技術には結構興味があるのだ。死ぬ前にそういう手術を受けて

「おお、ステレオってのはこういう感じだったのか」とか言いたい。

そして、別にうちはゲーム専業会社ではないので、

そういう医学的な仕事ともコラボできる可能性はなくもない。

私がプログラムを書くことでそういう分野に貢献できるなら、是非ともしたい。

 

https://2018.cedec.cesa.or.jp/session/detail/s5abe4f0cee5c0

 

またまた機械学習。あれ?この人サイゲじゃなかったっけ?

人の移動が激しい業界だなと思う。

 

ドラゴンボールスマホゲームで、ゲームが面白い状況が保てているか

検査する話。単に「この編成でこのステージクリアできるのか?」

みたいな検査よりも高度だ。

「明らかに強い編成が一つあってみんなそれ使う」

みたいな状況に陥るとゲームが死ぬわけで、そうなっていないかを検査する。

流行りの編成をいくつか選んだり、

新キャラを投入する前であれば、それを含んだ編成をゲームデザイナーが

いろいろ用意したりして、総当たり戦をする。

「この編成はこの編成に強い」というようなことをゲームデザイナーは

意識しているわけだが、それがその通りになっているかを検証し、

あからさまに強すぎる編成がないかも検証する。

またこの際、類似の編成というものがあるので、

編成をグループ化しないといけない。似たキャラにはラベルをつけて

同じものとして扱ってグループ化するらしい。

想定される編成や想定されてない編成の使用比率、勝率みたいなものを

監視してるっぽいのだが、何せ素人なのでよくわからない。

 

で、実際の対戦部分のAI化。

人がやると1試合に分単位で時間がかかるし、使える人数にも限度がある。

ここを自動化、高速化することがコストダウンに一番効く。

モンテカルロニューラルネットワークらしい。詳細はよくわからないが、

一対一で行動選択肢が少ないカードゲームであれば、

将棋とおおよそ似た感じに作れるのだろう。

1試合が1秒になったそうだ。

なにせカードゲームはそれぞれのカードに

やけに複雑な効果があるのが常なので、人に匹敵する強さにするのは

大変なのだろうが、ある程度の強さであっても十分役に立つ、

ということなのだろう。

 

対戦環境が健全かをAIで判定する、というのは面白いアイディアだと感じる。

「面白いゲームであるための条件」を数字で評価できる条件に変換するわけだ。

じゃんけんと同様、おおまかには3すくみになるくらいがいいらしい。

ABCと編成があって、AはBに強いので流行り、Aに強いCが発見されて徐々に

移行し、Cに強いBが再評価されてまた流行る、みたいな緩い循環があるといい、

みたいな話をしていた。循環せずどんどん移っていくと古いキャラがゴミになる

わけで、循環してくれた方が開発も助かる。

 

https://2018.cedec.cesa.or.jp/session/detail/s5abc831635896

 

画像認識でリアルのTCGカードを取り込む話。

なんでデータ化するのかは聞きそびれた。

電子化のことなんて何も考えていない時代から蓄積された

9000枚のカードを正しく識別する、という結構大変な話だった。

結構年上っぽい方々で、昔は現場にいたが今は技術サポート部門、

という感じだろうか。

 

テカってたり、色褪せてたり、傷ついてたり、

バーのビニールに入ったままだったりと、

条件はすごく悪い。

それをニューラルネットワークにどうにかさせる。

 

128x128を入力として、VGGというネットワークにつっこむ。

中身的には、128x128x3次元を4096次元に圧縮し、

その空間を9000の領域に分画する問題、と言える。

 

教師データはCGをいじったもので、2枚半透明で合成したら

学習しすぎなくていい感じになったらしい。

説明はいろいろ考えられるが、

やってみたらそうなった、ということなのだろう。

 

さすがに画像認識となるとGPUがないとどうしようもなくて、

最初は収束に3週間とかかかったそうだ。

半透明合成を入れてからは4.5日に短縮したらしい。

画像の処理をする場合は高いGPUを積んだ機械を

何台か並べるのは必須なのだろう。

 

今のところ私が考えているAI利用はゲームの中のAIなので、

たぶんそこまでしなくてもある程度はできるんじゃないかと思うのだが。

 

https://2018.cedec.cesa.or.jp/session/detail/s5aab4104066ad

 

後で追記予定

CEDEC2018の1日目メモ

気がついたら3ヶ月も経っていた。

精神的に余裕がなさすぎて、文章を書くことに時間を使う気に全くならなかった。

 

しかしまあ、仕事にある程度の切れ目はあったし、

そのおかげでCEDECにも行けたので、メモを書いておく。

ここに書いたことに社内特有の事情を加味して会社向けの

レポートにする、という事情もあって、殴り書きの香りがする代物になると思うが、

気にしないことにしよう。

 

なお、セッションの内容については各所に記事が出ているし、

資料自体も配布されているので、それをここに繰り返すことはしない。

思ったこと、考えたことだけ書く。

それと、3日分まとめて書くとたぶん長すぎるので、日ごとに分割する。

 

https://2018.cedec.cesa.or.jp/session/detail/s5b1e23aaede0a

 

宮本さんの話。

 

レベルデザインのツールを企画に渡してプログラマと企画の関係を薄めたら、

企画単独でいろいろできすぎて何がいいのかわからなくなった。

そこで、いくつかのパラメータをプログラム埋め込みにして自由度を削り、

また、企画がプログラマと話をせざるを得ないようにした」

みたいな話があった。時代に逆行しているのかもしれないが、

私もその方がいいと思う方だ。

もし企画やアートにUnityを使わせるのであれば、

UIを制限してできることを減らしたい。

このご時世にその考えは古いのかもしれないし、

異論も多かろうが、前もって「その製品では何をやらないのか」

がはっきりしていないと迷走の危険が増す。

そして「何をやらないのか」は言葉だけでなく、

開発工程によっても表現されるべきだ。

でないと、絞り切れない。ある程度の規模のものを作るなら、

その製品固有の制約を前もって設計しておいた方がいいと私は思う。

 

そういえばスマホの話題が多かった。

DSなどでいろいろやったが、結局世の中はスマホになってしまったし、

今はそういう時代だ、という認識が強かったように思う。

客は嫌々ハードを買う、という言葉を今の時代にあてはめれば、

スマホは買わなくていいわけだから理想そのものだ。

現にハードを作って売っている会社がそこから目を背けていないのはすごいし、

そのことをああいう場ではっきりと話すというのもすごい。

 

全体に「面白いものを作る」と「商売」のバランスがすごいなと感じる。

商売が面白いし、面白いから商売になる。

ゲームは商品である、としてアート扱いしていないのは私は共感が持てる。

ゲームは商品だ。商品であることは何ら恥じることではない。

 

「同じようなものを作ると倍の苦労をする」という台詞にはしびれた。

パクったものを再現した上に、そこに独自の差別化を入れなくてはならない。

そんな苦労をするならオリジナルを作る方が楽だ、と言う。

なかなか言えることじゃない。パクッた方が楽だと普通は思う。

しかし、実際問題、オリジナルは大変な苦労としてゲームを発明しており、

その過程では相当な試行錯誤がなされている。

その過程をすっとばしてパクった所で、パクり切れないのだ。

オリジナルが持つ面白さに及ばない。

その状態で新要素を入れようとするものだから、

「なぜオリジナルがその要素を入れなかったのか」

ということに考えが及ばず、余計なものを足してゲームを崩してしまう

ことになりやすい。人が認識できる大きさには限りがあり、

何かを足すならばその分削らねばならないはずなのだが、

大抵は足すだけになってしまう。

よく考えもしないで新要素を足し、面白くならず、不安なのでまた新要素を足し、

というようなことを繰り返すと、どんどん焦点がぼやけていくし、

出来の悪い要素がゲームを汚していき、

そして当然のことながら時間と人数を浪費することになる。

宮本さんに限らず、多くの人が「足し算より引き算」

というようなことを言っているのだが、

多くの現場では実践されない。その理由を考えることは大切かもしれない。

 

https://2018.cedec.cesa.or.jp/session/detail/s5ab4926f6d350

 

セガのAI利用話。AI話は他にも聞いていたので、

そのことも混ぜつつ書く。なのでこのセッションのことばかり書くわけではない。

 

ゲームでAIを使う用途はいろいろあるが、一つには検査がある。

一人用のステージを作った際に、それが想定する強さで問題なくクリアできるか

検査したい、というようなことだ。

キャラや武器、スキルといったものの構成は無数にあり、

それらでちゃんと想定通りにクリアできるか、あるいは想定以下であれば

ちゃんと負けるか、といったことを検査するのは相当に手間がかかる。

テストプレイという奴はとにかくコストがかかるものだからだ。

なので、そこを自動化したいという欲求は常に出てくる。

 

しかし、これが簡単ではない。

まず、さまざまなキャラから選んでパーティを組む、みたいに

準備に創意工夫があるゲームだと、どのキャラを選ぶか、

どの装備を選ぶか、みたいなところにすでに難しさがある。

企画が想定していない強い組み合わせがないか知りたい、

みたいなことになると、キャラを選ぶところにAIが必要だ。

ランダムで全通り試す、とかいうことは普通は不可能で、

ちゃんとキャラの相性や特性を把握して選ぶ相当賢いAIが必要になる。

 

そこに加えて、実際に操作する余地が大きいゲームであればその操作の

賢さ、上手さも必要になる。ランダム操作では、

ダメージを食らう前に回復するような事が起き、

ちゃんと人が操作するよりも弱くなってしまうので、正しく見積れない。

 

スマホの場合、ゲームで客に要求することが大きすぎると

「難しいゲーム」になってしまって売れないので、

普通はやることを減らして設計する。

例えば、キャラを選んで編成する比重が高いのであれば、

実際の戦いはオートにしてしまう、というようなことだ。

実際の戦いがオートなのであれば、すでにそのオートのプログラムはあるので、

操作に関して別のAIを作る必要はない。

オート操作しかないのであれば、人が介入することは元からできないので、

そのオート操作が上手かどうかを気にすることはできない。

この場合は、編成を自動化するAIさえあれば済む。

しかし、編成も大切で、実際の操作の上手い下手の大切、

というようなことになると、両方のAIが必要だ。

さすがに大変なので「編成はいくつか用意して固定。他の編成は無視」

という選択になることも多かったように思う。

それなら操作だけで済むが、この操作のAI化は結構難しいはずだ。

 

セガスマホメガテンではそこをやっていたとのこと。

ニューラルネット(NN)でAIを構成し、NNを遺伝的アルゴリズム(GA)で鍛えるという。

NNでやるならそのままNNで学習させればいいのにとも思うが、

そのためには、個々の行動について「正解」が必要だ。

人が操作したデータを「正解」として、それに近づける学習はできるが、

それだと予想外の行動をしなくなってしまうので検査が弱くなる。

人が思いつかない選択肢で勝ててしまう、というようなことが起こるかどうかを

知りたいのであれば、正解を前もって与えたくはないだろう。

それでGAが出てきたのだと思う。

テキトーなパラメータでたくさん作って戦わせ、

成績がいいものを残し、その間でパラメータを混ぜて、

また戦わせる。

 

なお、NNの実装は自前とのこと。

画像認識のように何万何十万のデータが入力されるわけではなく、

NNの実行時間自体はさして長くない。入力ノードはたかだか数百だ。

隠れ層一層なので総ノード数も少ない。

GAのためにバトルを回す時間の方が支配的であり、

tensorFlowを使ってGPUで実行する、というようなことは不要だったとのこと。

C#で実装してPCで動くクライアントにつっこんだようだ。

 

https://2018.cedec.cesa.or.jp/session/detail/s5ac0e28614177

 

データ分析の話だが、よくわからなかったので書くことはない。

ただ、

スマホゲーム各社、基本的に水着キャンペーンとかやると思うんですけど」

とおっしゃっていて、「いや、そんな常識みたいに言われても」

と思った。パワポの背景も胸と尻な感じの水着キャラ画像で、

いいのかなあ感。CEDECには女性開発者も多く来るし、

学者さんとかも招待していて結構いる。

あんまりそういうノリは良くない気がする。

 

https://2018.cedec.cesa.or.jp/session/detail/s5a9c9f23a9c0a

 

流体力学つっこんで炎のエフェクトを出す話。

GeForceGTX1080で1msとかかってて、まだ我々には早いなという印象。

スマホに持っていったらそれだけで1フレーム丸ごと持っていかれるだろう。

品質は文句なさそうな感じだが、強いて言えば煤の描画には

まだ工夫の余地がありそうな雰囲気はあった。

 

そういうわけで、また今度かなという思いはあるのだが、 

ああいう技術は無理にでもつっこまないと感じがわからないので、

無理矢理製品につっこめる場所を作る方が良いのかもしれない。

「なんでここの炎だけこんな妙にリアルなんだ?」みたいなことになっても、

まあいいんじゃないだろうか。

流体力学は動的計算できるといろいろと使えることがわかっていて、

使い回し感がないエフェクトをいろんなパラメータのバリエーションで出せる。

パラパラアニメで用意する伝統的なやり方だと、リアル感はないし、

容量は食うし、バリエーションを作ろうとするとそれだけ手間がかかる。

実装は大変でも、一旦動いてしまえばパラメータ設定だけ

でいくらでも生成できるわけで、できればそういう方向に行きたいとは思う。

 

https://2018.cedec.cesa.or.jp/session/detail/s5adda2d837027

 

「弱いロボット」。たぶん、今回一番考えさせられたし、

5年経っても10年経っても覚えていると思う。

 

自動販売機が「アリガトウゴザイマシタ」とか言うことはあるが、

驚くほど心に響かない。そこに「コミュニケーションした感」がない。

銀行のATMも、いちいちキャラ絵入れて動かして、

声まで出してるが、「それ意味あんの?その工数正当化できんの?」

と毎回思って腹を立てるくらい、伝わった感じがない。

あの先生はそれについてずっと考えて、いろいろ実験している人だった。

 

これをどうにかするアプローチには二つある。

一つは人に似せればコミュニケーション感が出るだろ、というアプローチ。

人そっくりにすれば、人っぽくなる。石黒先生のマツコロイドとかで、

足し算のアプローチと呼んでいた。

もう一つは、コミュニケーションの必要条件が何かを見極めて、

そのギリギリを作る。引き算のアプローチ。

 

ゲームデザインでも足すと金がかかるし焦点がぼやけるので、

私は引く方が好きだ。しかし、引き算が滅多に行われないのは難しいからで、

実際問題「何があればコミュニケーションしてる感が出るか?」

という問いはそう簡単に答えられない。

 

いろんな実験があったのだが、説明しやすいものとして、

ゴミ箱のロボットがある。自分で移動するゴミ箱なのだが、

手がないので自分では掃除ができない。

人の近くにヒョコヒョコ寄っていって、揺れているだけの代物だ。

しかし、人の方がつい気になって、ゴミを入れてあげてしまう。

つまり、人間がこのロボットの機能の一部として設計に組み込まれている。

そして人は、なんとなくいい気分になる。

 

ここには「コミュニケーション感」が確実にある。

人は何かをしてあげたくなり、事実して、いい気分になる。

 

もう一つ。目を合わせている間しか情報を出さないスマートスピーカーがある。

カメラを積んでいて、人間の視線が自分を向いていないと「えーとね」「うーんとね」

という具合になって話が進まない。気まずいのでロボットの方を向くと、

「今日は花火大会なんだよ」的な感じに話をしだす。

実用性があるかどうかはさっぱりわからないが、しかし、

「なんか気まずい」と感じた段階で、「コミュニケーション感」

が生じているということだ。合成音声が子供風で無視すると罪悪感が起こりやすい

作りになっている、というようなこともあるが、

それだけではないだろう。

 

「言葉自体の意味を削ぎ落としていくと、関係から意味が出てくる」

と話していたが、なるほどそういうことかもしれない。

無言の時間。仕草。「えーと」「うん」「そっかー」のような

意味の薄い言葉。そういったものが関係を作り、

関係の中から意味が現れてくる。

完結しない言葉に内的な説得力が宿る。

完結した、切り出して意味が伝わる言葉には、内的な説得力がない。

わかる。私に足りない奴だ。

 

さて、ゲームに応用するには?と考えるのがもったいないくらい深いテーマ

だと思うのだが、ゲーム屋なのでゲームに応用することを考える。

わかりやすいのは、AIと対戦なり協力をした時に、

どういう挙動をしたらAIを単なる支配や攻略の対象でないものにできるか?

という課題だ。そしてさらに言えば、

対戦なり協力なりのゲームにおいて、

「人とつながってる感」をより強く感じさせるには何をやればいいのか、

ということにもつながる。ネットの向こうにいる人に「つながり」を感じるには、

ゲームにおいて何が起こればいいのだろうか。

それがわかればAIにもコミュニケーション感を出させることにもつながる。

 

「人の参加する隙間をデザインする」という言語化は深い。

協力ゲームであれば、一人で完結しないようにゲームデザインするのは基本だ。

戦士と僧侶と魔法使いが組んで初めて勝てるように作る。

しかし単なる有利不利を超えて、感情を動かすような何かがそこにあれば、

単に「いいタイミングで連携できた」とか「回復してもらった」

とかいう以上のことが生まれるかもしれない。

 

具体的にどうすればいいのかはまだ何も実験していないのでわからないのだが、

「不完結な言葉」というのは大きなヒントである気がしている。

ゲームにおける行動は何bitかに収まる程度のデータだ。

「ここへ動く」「この魔法を打つ」といったデータは、

言わば「完結した言葉」だ。誤解のしようがなくそこに意味がある。

調整の余地がない。

しかし、移動の軌道が妙に揺れているとか、

行動と行動の間の間隔が変に長いとか、

そういうことで何かが伝わることもあって、

それはどちらかと言えば「不完結な言葉」だ。

そこに内的な説得力が宿るのだとしたら、

そういったものがもっとたくさん伝わったらいいのではなかろうか。

例えばスマホゲームであれば、タップの頻度をなんとなく送信して、

頻度が高いほどモーションが早回しになる、とかだと、

なんとなくテンションが伝わるだろう。

内側カメラで顔認識して視線方向を認識して、それでキャラの顔の向きをいじっても

いいかもしれない。何かしら、それ自体では意味がない情報を、

絶えずやりとりし続けていたら、何かが伝わらないだろうか?

そこに気配が感じられたりはしないだろうか?

 

https://2018.cedec.cesa.or.jp/session/detail/s5aab888dcd265

 

自動パラメータ調整でAIな話。

 

一人用のステージ作って、それが想定通りの強さでクリアできるか調べる、

ということで、セガの話とだいたい似ている。

テストプレイの手間を削りたいというのが動機。

すでに運用して遊んでる人がたくさんいるので、その操作ログを教師にして

機械学習しようとしたが、ログがAIに使うことを考えてなかったりして、

その路線は頓挫したらしい。

元々オートプレイがあるゲームなので、

とりあえずは勝手にゲームをグルグル回す機能を作ることで

手間の問題はおおむね解決できたようだ。先にそれを試すべきでは?と思う。

いくつかキャラや装備の編成を作っておいて、

描画をスキップしながら高速にバトルを回す機能を作れば、

少なくとも手動で試すよりは相当マシになる。

それをやる前に機械学習を試すのは違和感があった。

セガは実プレイデータを教師にせずにGAで学習させていたが、

そのアプローチの方が筋がいい気がする。

 

次に、対人のテスト。

このゲームの対人は、「自軍を編成して置いておくと誰かが戦いに来る」

という放置ゲー(あるいは非同期対戦)で、問題はどの編成にすると強いか、だ。

新キャラや新装備が出てきた時にバランスが崩れないか、

といったことを試すには、自軍を編成して戦闘を繰り返すところを

自動化しないといけない。

まずはその自動化の手段として画像認識でボタンを押すのを試したとのこと。

しかし、メニューが半透明で認識がうまく行かなかったりして、頓挫。

結局、各コマンドをCUIのシェルみたいなものから叩く口を作ったとのことだが、

なぜ画像認識から始めるのかよくわからない。

なお、編成そのものをAIにやらせる、という話ではなく、

どのような編成にするかは手で与えたということでいいんだろうか。

流行っている編成がいくつかあるので、それに関して試すと。

 

最後がボス戦のテスト。各ボスには「こういう編成で来るべき」

的な編成があるらしく、その編成だけテストすればいい一方、

人が操作しないと勝てないように作りたかったので、

操作をAIでやる必要がある。

そこでベイズ最適化でAIを書いた。

お手本の操作があるわけではないので、

ニューラルネットワークというわけにも行かなかったようだ。

セガがやっていたようにGAでの改良という手もあったかもしれない。

どちらがいいかはわからないが、GAは数学全然わかってなくても実装できる

という利点はあるなと感じる。

 

この取り組みはプロジェクトの外にいる専門家が一時的に

チームに入ってやったそうで、

妙に高度な技法を先に試しているのはそのためっぽい。

画像認識とかやってみたかったんだろう。

中の人がやっていたらそういうノリにはならないよなあ。

 

チームをまたいだ分析チームみたいなのがある会社は多いのだろうか。

チームの中には大抵そんな余裕ないし、専門性が高いことは専門チームにして

プロジェクトをまたいでやった方が使い回せて効率がいい。

しかし、専門家が「ドメイン知識」と呼ぶもの、

つまりゲーム固有の知識は無視できないほど大きい。

一人用の難易度が想定通りになっているか?

みたいな話は、「あくまでダメなステージを探す検査」

とわきまえて使えばいいのだが、

それが面白いかはまた別の話だ。

AIがステージをクリアした時に、人があまり取らないやり方で

クリアしていれば、それは新しい攻略が見つかったということで

そこには価値があるのだが、人が自然に思いつかないやり方で

クリアしたのであれば、普通の人にとっての難易度の参考にはならない。

結局ログを見ておかしなことが起こっていないか見ないといけないのだが、

自動で検査できるとなるとどうしてもそこがおざなりになりやすい気がする。

何せ便利な道具でテストプレイの時間を大幅削減できるわけだから、

「最低限クリアできることはわかったしいいよね」

となってしまいそうで怖い。

省力化のためにバンバン取り入れていきたいとは思うが、

いろいろ気をつけないといけないことは多そうだ。

Unite Tokyo 2018

Uniteに参加してきたのでメモをしておく。

さては非同期だなオメー!async/await完全に理解しよう

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session53

 

非同期処理をどう書けばいいかがまだよくわかってないので聞いておいた。

 

関数にasyncとつけた上で、中でawaitの後ろにGetAwaiter()を持った

ものを書くと、そこで待ってくれる。

コンパイラはIEnumerableの時同様に関数をバラバラにして、

ローカル変数を保持するためのクラスをでっちあげてくれるわけだ。

 

C#の標準では勝手にスレッドにブン投げて並列化してくれるが、

unityでやる分にはシングルスレッドになる。

それでもTask.Runを使って明に他のスレッドにブン投げて、

そのハンドルをawaitすれば裏に飛ばせる。

受信したjsonを裏でデシリアライズする、というようなことはそれでできる。

 

さて、async/awaitを使うことがおいしいケースは何か?

 

使ったことがないのではっきりとはわからないが、

おそらくは戻り値だろう。

IEnumeratorやIEnumerableを返す関数は、

戻り値を持てない。やむをえず、

 

class RetVal{ T result; }

IEnumerator SomeFunc(RetVal ret){ ... }

 

みたいなことをして引数に戻り値や完了状態を返すか、

最後に返したIEnumeratorから結果を取れるような凝った細工をするか、

という話になる。私は前者の方がシンプルなので好みだ。

 

しかしasyncをつけた関数は普通にモノを返せるので、

そんな必要はない。そして、

IEnumeratorという本来非同期処理のためにあるわけでもないものを

無理矢理非同期処理に使う必要がなくなり、

IEnumeratorを返せばコレクション、asyncがあれば非同期処理、

と見た瞬間に種別がわかりやすくなる。

 

とはいえ、すでにして大量にyieldで書かれた非同期処理があり、

2018に移行するのはもう少し先だと考えると、

焦って使う必要はたぶんない。

 

なお、Task.Runで実行するとプロファイラでは見えない。

Profiler.BeginThreadProfilingを呼ぶTaskScheduler

自作すれば見えるようになるそうだ。

 

そのうち標準で入るようになるだろうし、

自分でやっても大した手間ではあるまい。

 

 スクリプトによるTimelineがっつり拡張入門

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session56

 

そもそもTimelineという機能を知らないのだが、

Animationの置き換えだろうと推測する。

カーブ定義して出力するパラメータを決めて再生制御する、

という点では同じだが、

Animationには使いにくい点が多々ある。

いちいちAnimatorを用意しないといけないあたり、

UIを動かすために使うような用途には甚だ向かない。

APIを見ていないので確たることは言えないが、

Timelineならそういう用途にもおそらく使えるだろう。

エディタ側のUIの設定に関しても、再生制御にしても、

いじれる余地が大きそうだ。

 

さて、現状私が日常目にするUIその他の動きをつける方法は5つある。

- Animationの利用

- DOTweenの利用

- Updateベタ書き

- AfterEffectsからエクスポートしてスクリプト

- Spine

 

昔はAnimationを使って作ることを模索した時期もあったが、

最近はめっきりやらなくなった。GameObjectとのバインドが

パス文字列なので、一つGameObjectをはさんだだけで全部切れてしまう。

あとassetが増えるのが邪魔くさい。結局なんだかんだ言って、

何か作ればクラスを定義することになるわけで、

Animationだけ作って差せばいいというわけには行かない。

調整もスクリプトだけで完結しない。

そもそも私はそもそも細かくキーをいじって

アニメを調整するようなことはなく、

Animationの利点をあまり得られないのである。

もし細かく調整するならAnimationでやる方が

早く反復できるので、その他の方法より良いだろうと言える。

しかしそれはアーティストがやる方がいいと私は思っている。

 

次はDOTWeenで、これならスクリプトで完結するし、

簡単なものならすぐ書ける。

しかし、簡単なものでないとコードが意味不明に複雑化するし、

多量に発生するものに使うと性能的にヤバい。

画面に同時に10個とか出て、連打するとその度に消えたり出たりする、

というようなものの制御にこれを使うのは悪夢だ。

アニメーションの定義と再生インスタンスが割れてないので、

再生する度にアニメーションの定義コードとメモリ確保が走ることになる。

また、関数が呼ばれる度にSequenceのインスタンスを作るような場合、

前に起動したものを殺し忘れるとひどいことになる。

 

次がベタ書きで、これはUpdateの度にdeltaTimeを使って

手動で動かす方法だ。「0.2秒かけて現れる、消える」

みたいな単純なものならこれで書ける。

ただし、時刻や状態を保持するメンバ変数を用意して

管理する手間がかかり、やるアニメの複雑度が上がると

一瞬にして手間とコード規模がふくれ上がる。

ただし、ガベコレは一切発生させないで書けるし、

Updateでなく自作の更新関数でやればオーバーヘッドもない。

私は結構これもやる。

 

そしてAfterEffectsから吐いたものを自動変換して作ったコードで、

コードなのでできたものは上の「ベタ書き」に近い。

ただしカーブを表現するデータを多量に初期化したり、

カーブとgameObjectをバインドしたりするオーバーヘッドがあるので、

ベタ書きほどは速くないし、初期化ではメモリを食う。

そして、生成されたコードはもはや人が読めるものではなく、

コードなのでAssetBundleにも入れられない。

しかし、AfterEffectsで作ったもので、しかも垂れ流しで良いものであれば、

どんなに複雑なものでも同じ手間で再現できる。

そしてアニメーション定義と再生インスタンスが別なので、

同じアニメを多量に再生する場合の負荷やメモリは軽く済む。

ただし、対応していない機能を使われているとそこだけ手書き対応になって

手間が跳ね上がる。現状マスクやトライトーンは対応しておらず、

とりわけシェイプを使ってマスクをやられるとえらい手間がかかる。

まあそれはAnimationを使っても同じだろうが。

 

最後がSpineだが、これはあんまりUIには使ってない。

再生ランタイムがMeshRendererとして描画するので、

UGUIの中に挟みこめないからだ。

しかし完全にデータ化されていて、アセットバンドルに入れられるので、

こっちを使う手もある。あるいは再生ランタイムを自作したり、

いじったりしてUGUI化することもできるだろう。

 

さて、Timelineの話だった。

これをどこで使いたいかと言えば、当然AfterEffects再現だろう。

現状コードで吐いているものを、Timelineのアセットとプレハブにすれば

AssetBundleにも入れられるし、C++の実装によって高速化することも

期待できるかもしれない。

ただし、

「このノードの下に別のプレハブ差す」

「AE上では画像だが、実際はTextにする」

といった改変は日常茶飯事で、Timelineまで持っていけるケースが

どれくらいあるかはやってみないとわからない。

 

なお、現状私がいる場所ではデザイナーがUnityで作業する、

ということがほぼない。もしそれがあるなら、

最初からTimelineで作られるはずで、

そうなれば話は全く変わってくる。

 

60fpsのその先へ!スマホの物量限界に挑んだSTG「アカとブルー」の開発設計

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session63

 

2000個弾が出る縦シューをどうやってUnityで作るか、という話。

C#の最適化の話はよくわかる。

Normalizeの中でmagnitude呼んでるとか許し難いだろう。

しかし幸いにして私が作っているものは

そういう所を気にしてもほとんど影響がないので、

Vector2やVector3をそのまま使っている。申し訳程度にrefをつける程度だ。

[,]を使うか[ ][ ]を使うか、structでforeachするコスト、

そういったものはほとんど問題にならない。

他にいくらでも重い処理がある。

 

ただし、デバグ描画ライブラリでは千文字以上画面に出たりする関係で、

描画負荷自体は弾が2000個出るのとさほど変わらない状態になるので、

デバグ描画ライブラリの中は結構気を使っている。

例えばVector2の関数は使わない。ベタ書きだ。

refがつけられる時はつけるし、不要にclassを使うこともない。

モノをたくさん出すゲームならまた違うのだろうが、

今はそういう状態だ。

 

とはいえ、スパイクについては考えさせられた。

GCに関してはやれる範囲でとしか言いようがないが、

シェーダコンパイルとテクスチャのVRAM転送に関しては

確かに手を打っておいても良い気がする。

OpenGLでは実際に画面に絵が出ない限りテクスチャがVRAMに

送られず、初めて絵が出るフレームにスパイクが出る、

というのは知っていたが、まさか2018年になってそんな話を聞くとは

思わなかった。今起こっていることが

Unityのせいなのか下のGLのせいなのかは知らないが、

確実な方法が「一回描画すること」しかないことに違いはない。

各アトラスについて、それを貼ったポリゴンを何かしら

描画したらいいんだろう。

しかし、動的にロードされるエフェクトの類では打てる手はなさそうだ。

全種類前もってメモリに乗せるわけにも行かない時もある。

 

そういえば、移動経路の曲線データを事前に分割した直線データに

変換していたのは面白かった。

おおまかに言って、時代が進むほど、計算と転送は計算の方が安くなる。

昔は事前計算して読み込んでいた方が速かったものが、

その場で計算する方が速くなる、というようなことだ。

なにせ2018年なので、スマホはストレージやメモリ

からの転送の方が計算よりも高くつく公算が大きいハードだろうと

勝手に思っていた。それだけに、ベジェの計算程度でも事前計算の方が速い、

というのはちょっとショックだ。そんなにCPU遅いのか。

 

ScriptableObjectの利用について。

会話シーンの類はUnity上に編集環境作って

ScriptableObjectにしてるらしい。

企画にjsonやらxmlやらエクセルやらを作らせて、

それを何かに変換してプログラムにロード、

という作りだと、どうにもイテレーションが遅くなる。

Unity上でやってもらってその場で保存してもらうのは自然だろう。

しかし、企画やアートにとってUnityが手に馴染む良いツールになるのか?

というのは未だに確信が持てない。

エクセル編集でも実行したまま再ロードができる仕組みがあれば、

それはそれでアリだ。もちろん実行中の再ロードができなかったり、

サーバの再起動が必要だったりするフローは論外だが。

 

P-MAPというものを使うと、加算と通常合成が交互に来ても

1パスで書けるらしい。どうもテクスチャを事前に加工してるらしい。

同じシェーダと同じブレンドモードで行けるということで、

一体何をしているのだろうか。

 

通常合成は、D' = S*Sa + D(1-Sa)

加算はD' = S*Sa + D

だ。

ブレンドモードがSa, 1-Saであれば前者、Sa, 1であれば後者になる。

これを同じにしたいと。ここでSにSSa=S*Saを入れておき、

通常合成はSaをそのままにし、加算ではSaを0にし、

ブレンドモードを1, 1-Saにすれば、

加算合成も加算もD' = SSa*1 + D(1-Sa)

となって同じブレンドモードで扱える。

シェーダ内で分岐して吐くアルファを0にするかSaにするかを決めてもいいが、

それではシェーダに分岐が入って重くなるしマテリアルが割れるから意味がない。

その情報を入れるなら頂点かテクスチャかだ。

アトラス化も兼ねてテクスチャでやるアプローチの方がおそらくは普通だろう。

しかしテクスチャに触りたくないケースでは頂点という可能性も残る。

おおよそ話はわかった。

Unityが透明部分に勝手な細工をしないようにAlphaIsTransparencyは

切った方がいいだろう。

 

 運営中コンテンツにおける大型アップデート成功のための考え方とUnity最適化手法 

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session66

 

テクスチャに影描きこんで照明計算なしで出したゲームを、

後から照明計算を足して豪華な絵にしよう、という話。

 

解像度を上げて、フルスクリーンアンチエイリアス(MSAA)をかけ、ポスプロを足し、

テクスチャに含まれていない成分である環境光と鏡面反射を足している。

影が描いてあるならディフューズ項はいじれない。

とはいえ、場所依存の環境ディフューズの成分は描かれていないだろうから

足しても問題は少ないだろうし、

描いてある影のいくぶんかはアンビエント遮蔽だろうから、

それはそのまま乗算してもそれっぽくはなる。

 

ただ法線が問題だ。元々アウトライン用のウソ法線しか入れていなかったそうで、

本物の法線を入れねばならないのだが、FBXには法線が2つ入らない。

UVのフリをして入れて、インポート時にタンジェントにつっこんだとのこと。

なかなか手間だ。

 

ポスプロはZバッファ生成パスを用意してそれを使ったそうだが、

よくわからない。通常描画で描いたZは使えないのだろうか?

もしそうなら、私だったらZを使うポスプロはあきらめたくなる。

ライトシャフトだろうか。

 

豪華版は影描画も追加。

Unityの標準だとモバイルはハードシャドウになって許容できないので

自作したとのこと。物を見ていないのでわからないが、

普通のデプス影でソフトにするには結構な計算量がかかる。

プロジェクションシャドウと言っていたが、

地形だけに落とす奴だろうか?

 

出してしまったゲームの画質を後から上げる、

ということはスマホの場合には結構あるだろうし、

そもそも出した当初から機械の性能には天地の開きがあり、

機械によって使う技法を切り換えるのはありうることだ。

良い機械を持っている人にはより良い体験を、

というのは作り手の精神として共感できる。

ただ、それを割に合う手間で、というのがなかなかに厄介なのだ。

 

まず、解像度とフレームレートに関してはほぼタダでできる。

良い機械では解像度が上がり、フレームレートが上がる。

これは自然にできる。

性能の割に解像度が低い端末であれば、

例えば2倍解像度で描画して縮めたっていい。

単純に2倍重いが、MSAAなどより遥かに綺麗だ。

MSAAは機械によって負荷がどうなるかが読めないところがあるし、

綺麗になるのはポリゴンエッジだけなので、

安く出来るチップでない限りは割に合わない印象がある。

そのチップがどんな特性のチップかがわからない状態でMSAAは

あまりやりたくない気がするが、

多数の機械で試しているのであれば問題ないだろうし、

なにせスイッチを入れるだけで有効化できるので手間では圧倒的に有利だ。

ただ、centroidサンプリングを選択できないと、

おかしなテクセルを拾って絵がおかしくなる。

実際そうなっていたらしく、テクスチャのアトラス境界を広げたそうだ。

モバイル向けのUnityではcentroidサンプリングはできないのだろう。

ますますMSAAを使いたくなくなる。

コンピュートシェーダが使えるマシンでだけそれを使ってAAをかける、

というのではダメだろうか。

 

元の状態でスペキュラがなかったのであれば、

人間は物の形や動きをスペキュラで把握する傾向があり、

スペキュラを足すのは効果が大きい。

しかし、スペキュラをまともに足すにはマテリアル設定が必要で、

何をどう考えても手間が増す。

元のモデルがマテリアル境界で分割されているとは思えないわけで、

粗さや反射率をテクスチャにつっこむことになるのだろう。

これも結構な手間だ。

そして、スペキュラは法線マップとセットでないと効果が薄い。

頂点が少ないとディティール感が出ない。

だが法線マップを貼るにはタンジェントが必要だ。

さらに手間が増す。これは頭が痛いな。やりたくない。

 

テクスチャはETC2にしたらしい。

どうせ低性能機種で読めないならASTCでもいいんじゃないか?

と思うが、単に「高級機種だけ綺麗にしたい」

というわけではなく、より多くのマシンで改善したかったのだろう。

ETC1に比してマッハバンドが改善しているそうだ。

どうも圧縮のQuality設定を上げればETC1圧縮でもマッハバンドは

消えるようなのだが、べらぼうに圧縮が遅くなって

パイプラインに悪影響があるので、ETC2だとそこそこのQualityでも

マッハバンドが消える、ということを重く見たとのこと。

更新頻度と物量次第だろうとは思うが、

ローカルではQuality0でまずインポートし、

土日に自動でQuality0のものを100に変えてインポートしなおせば

我慢できなくもない気はする。

キャッシュサーバも助けになる。 

それで済むならETC2のためにUnityのバージョンを上げる必要もなかっただろう。

おそらくはそれで済まない理由があったのだろうと思う。

 

では、そのクラスの大型更新をどうやって確実に遂行するか。

まずチームは割り、運営チームの邪魔をしないことを最優先する。

完全に作業が終わるまでは運営側のdevelopには一切入れない。

更新用のdevelopにちょくちょく運営のdevelopをマージしつつ開発を進める。

#if で古いUnityと新しいUnityを分岐させる時はシェーダは注意せよとのこと。

自動でシェーダを新しいUnity用に描き換える機能があるらしく、

それが#ifを無視するそうだ。切っておかねばならない。

 

さらに、AssetBundleにシェーダが入ると互換性がない。

古いUnity用のシェーダが入っていると新しい方で読めず、その逆もある。

ABにシェーダが入っているか調べるツールを書いたそうだ。

これは他人事ではない。

シェーダが足りなくて紫になる事故はたまに起こる。

 

ABビルドは並列ビルドしてるらしい。

ビルド済みのManifestがあればビルドしないので、

Manifestをいい具合にコピーしておいてビルドを抑制すると。

manifestがなければ、それに対応するABがビルドできると。

後から別の人に聞いた話では、

実際やってみるといろいろ罠があるらしく簡単ではないそうだが、

しかしやってできないほど難しいことではないだろう。

ABのビルド時間が問題になるなら試してみても良さそうだ。

 

 

『CARAVAN STORIES』のアセットバンドル事例

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session72

 

正直衝撃の連続だったのだが、それは私の経験が浅いからだろうか。

 

まず「アプリが2GB越えしてからAB化した」というのにド肝を抜かれた。

ビルドは3時間だそうだ。

AB化にはビルド時間の分散と短縮という意味もある。

ビルドの度に全アセットの変換を行うよりは、

こまめにAB化して、その後は触らずに済む方が合計時間は短くて済む。

 

一体日々のテストプレイはどうしていたのだろうか。

2GBのapkを皆に配っていたのだろうか。

実機でのテストは一体どれくらいしていたのだろうか。

実機でないと感じがわからないことは多々あるだろう。

その度に3時間待たされたのだろうか。

どのようなフローで開発しているのか正直想像がつかない。

 

また、同期ロードを非同期ロードに変える工数を嫌った、

というような話があったが、

正直私の感覚だと「同期でファイルIOをやって許されるのはデバグ時だけ」

なので、それも衝撃だった。

最初から全てが非同期であれば何ら手間などかからないし、

いずれアセットバンドル化することは明らかなのだから、

Resourcesから読むかABから読むかは最初から抽象化しておけば良い

ように思える。ベータに突入してから、

極力影響が及ばないようにその作業をする、

というのは一体何が起こっているのだろうか。

 

しかし、私はMMOなんて作ったことはないし、

スマホで物を売ったこともない。

おそらくは、私に見えていない何かがあって、

そこに妥当性があるのだろうと思う。

 

それにしても、先にResourcesを見に行って、

なかったらABを読む、というのはレイテンシが心配だ。

ファイルの存在非存在だけでもシステムコールを叩くわけで、

多少はかかりそうに思える。

 

データのunload2秒待ってからにしてるらしい。

次に同じものを使う時に再度ロードのレイテンシがかかるのを

防ぐためらしいが、それなら適当なキャッシュシステムを

用意する方が確実な気はする。

例えば50MB分キャッシュすると決めておいて、リストにつなぎ、

使った時にリストの先頭に動かす。容量があふれたら、

末尾から順に削る。

こんな簡単な処理でも、即座に再利用した場合にはロードが走らなくなる。

しかしそれが面倒だったということだろうか。

 

ABにしたらアニメーションのロードが速くなった、

という話は意味がわからなかった。

Resourcesから31個のAnimationClipを読むのに6秒かかっていたが、

それが一瞬になったという。6秒もかかること自体ありえないと思うのだが、

テキストとかバイナリとか言っていた気がする。

まさか、Animationの保存形式にテキストとバイナリを選択でき、

テキストにしていたが、ABにビルドする際にはテキストの選択肢がないので

バイナリになって高速化した、ということだろうか。

知識がなくて意味が取れなかったのでこのあたりの話はよくわからない。

 

最終的にアプリのビルド時間は3時間から5分になったという。

アプリサイズは45MBだそうだ。5分はうらやましい。

アプリ埋め込みのリソースを減らしてABに叩き出せば速くなるのだろう。

専用スクリプトが不要なシーンがあるならば、シーンごとABに

叩き出すこともできるのだろうし、それができれば拡張の点から有利なのだが、

現状そういう作り方をできる目処は立っていない。

いずれ考えたいところだ。

 

ABは4600ファイル。元のアセットは10万個。

4並列でダウンロードする。4並列もやれば、

それくらいのファイル数でもオーバーヘッドが気にならないのだろう。

であるならば更新時の差分を小さくする意味でも、

あまりまとめすぎない方が良いのかもしれない。

ただ、ABのバージョンチェックにはファイル数に比例した時間がかかる

印象があるのだが、そのあたりはどうだったのだろうか。

 

一つ気になる話があった。

音が鳴らないことがあり、UnloadUnusedAssetsで再生中のが破棄された

のではないか?とのこと。

UnloadUnusedAssets後再生が止まったら再度再生して回避しているらしい。

音が鳴らない、という不具合が出てきたらこのことを思い出そうと思う。

 

カスタムシェーダーでモバイルでも最先端グラフィックスな格闘ゲームを!

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session72

 

大手で社内ライブラリを作っていた人がUnityの台頭で

チームを渡り歩いて最適化をやる仕事をしている、

ということには何となく時代を感じる。

私は下層を作ることをやめてゲームの上層を作る側に転じたが、

この講演の人は過去の経験をそのままUnityで活かしていて、

いろいろな生き方があるなと思う。

 

グラフィクス系の講演であるにも関わらず

一度も絵が出てこなかったことに度肝を抜かれた。

論文への参照があるあたりもUniteの他の講演と違う臭いを感じる。

このような言い方が良いかはわからないが、

「あ、オレも以前あそこにいたんだったな」的な郷愁がこみ上げた。

 

内容としては、

Unityに任せると遅いので昔PS3あたりでやっていたことをUnityでまたやった、

という話と言って良いかと思う。

 

影を描くためにカメラを用意すると諸々遅いので、

メインカメラのスクリプトでコマンドバッファへの描画を発行して影を描く。

標準シェーダの影は遅いので昔ながらのユニフォームシャドウマップを自作。

行列は自前生成するからカメラは不要。

カメラのカリング処理などが全部不要で速い。

 

テクスチャの焼きはPlatformをモバイルに設定するとLDRで焼かれて

話にならないので、Standaloneにして焼いて、

出てきたテクスチャをReinhardでLDR化して吐き出し。使用時に逆変換。

ライトマップはLightmapSettingsから抜ける。RT用意してBlitしてReadPixels。

 

デコードは lightmap / (1h - min(0.9h, lightmap))

最大10で切って無限に飛ぶのを抑制する。

モバイルでもdepth shadowmapはハードウェア支援があるので、

影はdepth shadow。

 

影描画は、影を受けるオブジェクトをリスト化しておいて、

MaterialPropertyBlockで影テクスチャと影フェッチ行列をつっこむ。

こうすればマテリアルを生成せずに済む。

これらの処理を行うのはメインカメラのCamera.OnPreRender。

 

透視変換行列はGL.GetGPUProjectionMatrixで取れるので、

フェッチの際にはこれを使う。Vが上プラスか下プラスかは下層のAPI依存なので

これが必要。

 

照明計算はリニア空間HDR。forwardでMRTなし。

テクスチャはsRGBなので、フェッチ後リニアに変換。

RTへの描き込み前にトーンマッピング

ガンマ変換してsRGBにする。

HDRバッファがあればHDRかつリニアのまま書き込んで、

描画終了後にポスプロでトーンマッピングとガンマ変換を

やる所だが、HDRバッファがないので

それぞれのモデルのレンダリングでバラバラでやらねばならない。

 

トーンマッピング時のEV値はアーティストが設定。

Filmic Tonemapping近似を使用(Hejl 2010)。

 

BRDFの詳細は語られなかったが、

「物理ベース」と言っていたので、おそらくはUEの計算モデルだろう。

私がやるとしても似たようなことをやるのだろうな、と思う講演だったが、

もしそういうプロジェクトがあってもやるのは私ではあるまいな、とも思う。

シェーダをやりたい人は他にいくらでもいるだろう。

 

それにしても会場にいた人は皆BRDFと言われてそれがなんだかわかったのだろうか。

普段Unityを使っていればまず出てこない単語だと思うのだが。

 

Unityの医療と教育への応用 ~ちょっと人を助けてみませんか?~

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session82

 

脳神経外科の現役バリバリの外科医が、

Unityを使って可視化やシミュレーションを行って、

日々の手術に役立てている、という話。

 

CTのビューアは私も書こうとしたことがある。JSでdicom(医療画像フォーマット)

の解釈をして、webGLにつっこんで描画まではできた。

しかし今の時代それはUnityの方が良いのだろう。

ネイティブになるので速度も出るし、AppStoreその他で配布もできるし、

物理シミュレーションなどもついてくる。

腫瘍が血管を圧迫して血管が本来と違う位置にある、

ということをシミュレートするのにUnityの標準の物理を使っていたりして、

専用に用意しなくても実用になるものが作れるのかと驚いた。

 

医療用のソフトウェアの市場規模は1400億と小さいが、

年率15%で成長しているそうだ。

以前は診断に使われるソフトウェアには厳しい基準があり、

なかなか認可されなくなったが、そのあたりで規制緩和があって

認可されやすくなったとのこと。

確かにフリーで配布されていたCTのビューアも「診断には使えません」

とただし書きがついていた。もしかしたら今は不要になっているのかもしれない。

 

全体的に、患者の固有データをつっこんでシミュレーションしたり、

治療戦略を立てる助けになるようなソフトが足りていないそうだ。

患者個人の脳を立体化したり、変形を加えたりできないと、

治療戦略を立てる役には立たないという。

 

細かい話はいろいろあったが、医療分野を知らない会社との

コラボが良い成果を上げているという話は印象的だった。

それはつまり、プログラマとして優秀な人間が医療分野になかなか行かない、

ということでもあるのだろう。もしかしたらそこにチャンスがあるのかもしれない。

例えば、分析の待ち時間が長くて困る、というような話は、

アルゴリズムと望む結果がある程度わかっていれば、

高速化を得意とする技術者が当たった方が良い結果が出るかもしれない。

患者のCTを撮って3日後に手術、という時に24時間かかるようでは使い物に

ならないわけだ。

優秀なプログラマgoogleやらゲーム屋やらにたくさんいて金を稼いでいるわけで、

医療や学問の世界でプログラマをやろうとは思わないのかもしれない。

 

現状特に時間がかかるのはセグメンテーション処理、

つまり、CTやMRIから立体化する際に、これが脳で、これが骨、これが皮膚、

のように領域分けをする処理だ。そもそも正しく領域分けをすること自体が

困難なのだが、現状のアルゴリズムでも時間がかかりすぎるという。

機械学習を応用して精度を上げた話なども出てきた。

普段実際に頭を開けて手術している医者からこういう話が出てくるというのは

本当にすごい。一緒に何かできる機会があったら面白いのだが。

身内に二人も癌患者が出たこともあって、医療にはかなり関心もあるし。

 

そういえば、一つ現状の用途で役に立っていることとして、

どのクリップを使うと丁度いいかを推測するのに使っている、というのは面白かった。

血管を止めるクリップは使い捨てで数万円する。使ってみてダメだといきなり

万単位でドブに捨てることになるので、前もってシミュレーションで

種類を限定しておきたいそうだ。実際に効果があるのだという。

 

Unityにおける疎結合設計 ~UIへの適用事例から学ぶ、テクニックとメリット~

http://events.unity3d.jp/unitetokyo2018/session-lineup.html#session84

 

疎結合、なんて言葉を聞くと、コードの設計のことかと思うが、

全然違った。いや、全然違う、と言うと正確ではないのだが、

おそらくは「全然違う」と思った人は多かろうと思う。

しかし私にとっては、そんな「疎結合」の話をされるよりずっと面白かった。

というかこのドイツの人、話がおもろい。

 

まず結合という言葉を「片方が変更されるともう片方にも変更が必要になること」

と定義し、疎結合はそのような結合がないこと、としている。

「疎」は「まばら」であって「無」ではないはずなのだが、話としてはわかりやすい。

例えばデザイナーが素材をいじった時に、プログラマに作業が発生すれば

疎結合ではない。密結合だ。

 

どれくらい疎であるべきか、というのは主にプロジェクトの規模によって変わる。

人が多く、期間が長く、仕様が大きいほど、疎にしないと辛くなる。

なるほどそうだ。

 

そして、「何が疎か」ということに関してはレベルがあり、

ワークフローやデータ、そしてコードのレベルがある。

ワークフローにおいて関係が疎であれば、それはデータにおいても

疎であることを要求し、それはさらにコードにおいても疎であることを要求する。

 

デザイナーが直してもプログラマに仕事が発生しないためには、

デザイナーが直したデータをプログラマが受け取る必要がない状態でなくてはならず、

それはそのようなコードによって実現される。

コードにアニメ書いてあるとか、Unityの参照に画像をプログラマが差してるとか、

そういう状態では疎にはなりえない。

 

言われてみれば当たり前なのだが、そのようなことを「疎結合

なる言葉で説明することには意義がある。別に他の言葉でもいいのだが、

大きな概念に名前がつくと応用範囲が広い。

 

ではこの概念上の道具を使ってうちらはどうすべきか?

 

うちのフローは、デザイナーが素材を作ってプログラマが実装する、

この人が言う「ダメなフロー」だ。画像が変わればプログラマ

受け取って差し換えねばならないし、位置が変われば

プログラマがシーン中で置き直さねばならない。

また、デザイナーが仕様を書いて渡すコスト、

プログラマが仕様を受け取って理解するコスト、

といった無駄なコストも発生する。

こういった害は、デザイナーが直す回数が多いほど大きくなる。

直すコストが大きければ直さなくなるので、試行錯誤の回数は減り、

普通に考えて品質に悪い影響が出る。

 

つまり、理屈の上では、デザイナーがUnity上で組んでしまうのが最も良い。

配置も動きもUnity上でデザイナーがつける。

そして、ゲームロジック側とのやりとりは何らかのインターフェイスを設けて

行う。scriptableオブジェクト一個用意して、

そこにsetがあるプロパティを置けば、それはアニメからロジックへの送信になり、

getがあるプロパティを置けば、それはロジックからアニメへの通知になる。

setには「ボタンが押された」のようなイベントや「今のHPゲージの値」

などが入るだろうし、getには「時間が来たからボタンを無効化しろ」

のような制御が入るだろう。いずれもdelegateなりeventなりを置けば、

pullでなくpushにできる。

 

このあたりの作りをこの講演ではメッセージバス、と呼んでいた。

バス、つまり通信チャネル的なものに情報を流し、

相手が誰かもわからないまま送信、送信元が誰かもわからないまま受信する。

こういう作りであれば、UIとゲームロジックを分離でき、

UI単体テストなども可能になる。ゲームを起動しないとテストができないのは

密結合でよろしくない作りだ。

 

実にもっともで、考えさせられることは多い。

 

しかし、それはそれとして、単体テストが不要で、担当者が分かれていない、

というような状況であれば、ゲームエンジンとUIが密結合した

昔の形でも許容できることはあるだろう。

大した回数直さないとか、そもそもデザイナー資源が極端に少なくて

アニメまでつけないとか、アニメをつけるのがうまいプログラマがいるとか、

そういう条件があれば現状のフローも正当化される。

コード的にも切り離せば抽象化層を作る手間はあるし、その分だけ長くなる。

決め打ちであれば不要だった検査類も、分離するなら書いた方がいいかもしれない。

私はここのところ疎結合だったものを密結合に直すという

逆行したことをやっているが、

一旦くっつけ終わったらまた分離を再度考えてもいい気がしてきた。

 

とりあえずはAfterEffectsからTimelineのassetに変換するものは作りたい。

それがあればAEを多少いじってもプログラマが一切知らないまま

ABが更新されて勝手に出る、という状態にできる。

デザイナーが手持ちの実機で確認できるところまで

整備しておかねばならない。

非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を用意したりするのだが、

それでも頂点が多い。

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

面倒なことだ。

 

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

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