VisualScripting奮闘記(駆)
!ご注意!
これはProjectTCC公開に至るまでの間、実装のじの字も知らん奴がVisualScriptingに挑戦し敗北を喫する度に一つずつ増えて行った壁の残骸を纏めた物でござんす。
掃除も整理も洗濯も一切されず、まぁなんか消すのも勿体ないかなと思ったので公開してみた、というくらいの物で、裏路地に積まれた新聞紙くらいの気持ちで見て頂ければ幸い。
今では多分もっとマトモな実装出来ると思うけど、俺はこういうところで躓いたよ、という情報は正直なところどんなTipsよりも役に立つ場合があるのを知っているので、公開するだけしてみようと思った次第。
他人の役に立つかどうかは関係なく、自分が一度調べたり教わったりしたにも関わらず、忘却の果てに同じ事で躓いた問題をここにメモる事で、仏の顔を三度拭かず済ませようって感じnote。
⭐前提
ntnyはプログラミングのプの字も出来ない。
実装なんかやった事がないので脳筋がデフォルト設定。
思いつき万歳。行き当たりばったりで作る。
大抵作ったあとの整理で死ぬ。
ntnyは掃除ができない。
動けばよかろうなのだ!
一部のノードは開発中の物だよ。
本noteはリファレンスではないので喋り言葉で綴る。
何故ならリファレンスを読める人はリファレンスを読むからだ!
ntnyはリファレンスを可能な限り読みたくない人種である。
⭐VisualScriptingを使う前に知っておくべき事
🌞VisualScriptingの種類
🔴 ScriptMachineとStateMachineがあるけど最初はどっち使えばいいの?
✅ 敢えて言うならStateMachineかな…
実際のとこ、この二つは初期設定が違うだけで、出来る事は同じなので結論から言うとどっちでも良かったりする。
自分も最初はよく分からなかったので、取っつきやすそうなStateMachineから始めたんだけど、今ではScriptMachineを主体に使う感じになった。
だからと言ってStateMachineを使わなくなったわけではないし、ScriptMachineで始める事を推奨するわけでもない。
StateMachineの利点はビジュアルで今どの処理をしているか、どういう状態にあるかが一目でわかるのと、状態遷移ベースで考えられるのは非常にとっつきやすいと思う。
ScriptMachineはとにかくシンプルで、単純な機能を作るだけならこっちの方がGraphの遷移も少なくてラクチン。
で、もし使い分ける指針があるとしたら、そのオブジェクトに「状態変化」が起こるかどうか、または「操作形態」に変化が発生するかどうかで判断すればいいと思う。
最もシンプルな例だと、レバーやスイッチギミックのオン/オフなんかはStateMachineで作って、オン状態とオフ状態をStateで切り替えるとか。
他にも自キャラをVisualScriptingで操作する時「フィールド探索モード」と「バトルモード」があるならStateMachineで始めると入りやすい。
それぞれのモード毎にStateを作っておけば、Stateを切り替えるだけでモードチェンジが出来るようになる。
しかしここで新たな刺客が登場する!
State Unitである!
なんと、これを使えばScriptMachineの中にStateMachineを丸ごと突っ込む事が出来るのだ~!!な、なんだって~!?
そう聞くと「じゃあScriptMachineで始めておけば困ることなくない?」と思う人も居るでしょう。
まぁそうっちゃそうなんですわ。
ただその場合はStateUnitをダブルクリックしないとStateMachine画面にならないので、編集する度に2クリック要する事を良しと出来るかどうかみたいな話にもなるわけです。
それなら最初からStateMachineで良くない?
だってStateMachineだからって必ずしもStateを遷移させないといけないわけじゃないじゃん?
StateMachineっていうのは、つまるところ一つのGraphの中で複数のScriptMachineを行ったり来たりさせるシステムなわけで、この最初のState一つが即ちScriptMachineなわけですよ。
じゃあStateMachineでいいじゃん!
となったので、自分は最初StateMachineから始めた、というワケです。
因みにStateMachineはこの画面でもグループ化出来るので、メモがてらグループタイトルに内容を登録しておくと見やすくなるよ。
🌞Variablesの種類
🔴 ScriptMachineを作ったらVariables?ってのが勝手に出来たんだけど?
✅ それはとても便利な箱です
Variablesってのは、扱いたい要素をしまっておく箱の様な物。
ScriptMachineもStateMachineも、最初に作成した時点で必ずVariablesが一つと、ヒエラルキーの中にVisualScripting SceneVariablesというのが勝手に生成される。
VisualScriptingは基本このVariablesを対象として操作する物なので、例えば自キャラであればHPやスタミナといった要素をVariablesに登録しておくとVisualScriptingでそれらの数値を操作可能になる。
詳しくは本稿以下構造関連の項目で触れるので、ここでは一先ず勝手に作られるコイツ何者なの!?という説明のみにとどめる。
ScriptMachineを付けたオブジェクトに勝手に作られるVariablesは、そのオブジェクト個人が持っている物で、言わばそいつのステータスやパラメータの様な物だと思ってよい。
それに対してSceneVariablesというのはシーンファイルその物が持っているVariablesで、これはシーン内にあるオブジェクトであれば誰でも自由にアクセスできるパラメータを持たせることができる。
例えばフィールド探索中にダメージを食らう酸性雨が降ってくるとして、自分だけでなく敵も同等のダメージを受ける、といった場合はAcidRainといったパラメータをSceneVariables側にInteger(整数)で15とか持っておくと、15ダメージを受ける酸性雨という要素をプレイヤーも敵も参照できる。
更にScriptMachineの画面を良く見るとGraphVariablesという物もある。
これは今開いているGraphだけが使う事の出来る小箱。
このVariablesのメリットはInspectorにあるVariablesには登録されない為、無暗に縦長になる事を防げる事。
デメリットはInspectorに表示されないので確認が手間な事…。
ままならないね。
🌞グループ化を魂に刻み込め!
VisualScripting、一日置くと何やってたか忘れがち問題、めっちゃある。
計算処理とか昨日何をどうしてこうなったのか忘れるとか普通。しかしVisualScriptingにはメモ機能が無いのでメモを残す事が出来ない。
なんとVisualScripting ver1.8.0 からSticky Noteが実装された!!!!!
これで気兼ねなくメモを残すことが出来るようになった…よかった…
そこでそれはそれとして役に立つのがこのグループ機能である。
WindowsならCtrlを押しながらドラッグでノードを囲って日本語でタイトルを付けられる。
これはもう息をするように、何かを作ったら必ず括る癖を付けよう。
名前を付けるのが面倒ならデフォルトの「Group」のままでもいい。
関与しあうノード同士を纏めて置くだけでも全然違う。
色はGraph Inspectorで付けることができる。
扱う要素の種類によって色を別けると視認性が一気に上がる。
一般ノードは白、計算処理は青、CustomEvent系は紫、サウンドは黄色。
そんな感じでグラフィカルに、目で見て分かる形に整理していこう。あとUnityはマジでメモ機能を早く実装して!!!! Thank you Unity !!
🌞VisualScriptingの事前準備
🔴 操作したい要素のノードが見つからないよ~~!!
✅ PreferenceのVisualScripting項目をチェックする
Generate Nodesのところにある二つのオプション「Type Options」と「Node Library」が非常に重要。
❶ 先ずNode Libraryをチェック
一番下にある「+」で登録するLibrary枠を増やして、登録したいUnityの機能を検索する。
❷ 次にType Optionsにも同様に「+」で枠を追加して、登録したい機能を検索して追加する。
実際に使うノードは大体こっちの名前。
❸ そして最後にRegenerate Nodesボタンをクリック!
間違ってもReset to Defaultsを押さないように!!
これは完全にトラップです!配置も近いし頭文字がどっちもRe!
この手順を踏む事で、VisualScriptingが標準では扱えない機能にアクセスできるようになる。なんて面倒くさい!
良く使うところだとTextMeshPro(uGUI)やCursor、他にもCinemachineやImage等も追加しないと扱えない。
面倒くさい?そうでしょうとも!
なので、サンプルプロジェクトから開始するのが手っ取り早いと思います!
🌞PlasticSCM(現UnityVersionControl)は神
ひょっとしたらGithubとかは聞いたことがあるかもしれないけど、PlasticSCMも言うたら同じような物。
バージョン管理システムと呼ばれる生命保険の一種だ。
UnityでAssetを直接操作したりすると偶にUndoが利かない事がある。
それが結構なコア要素だったりして、戻せないとなるともうUnityアンインスコするか…ってなるのはとても良くわかる。
これを未然に防ぐ…事は出来ないので、起こってしまった大災害をなかったことにするのが、これらバージョン管理システムである。
こらそこ、違うとか言わない。
使い始めの人間にはSteamのクラウドセーブの様な物と思って始める方が取っつきやすいってもんなの。
というわけで、バージョン管理システムは使えるなら絶対使った方がいい物なわけだが、どういうわけかどれもこれも非常に取っつきにくい。
Github、Turtoise、SourceTree、他にも色々あるが、基本的にそれらは専用のアプリやエクスプローラ、ブラウザ等で操作を行う上に専門用語がとても多いので、自分には全然使いこなせなかったが、PlasticSCMはUnityのEditorに統合されていて、タブから直接コミット(クラウドセーブ)が出来る。
ひと段落したらコミット、何か新しい事や実験をする前にコミット。
これを簡単に出来るようになったのは「バージョン管理よーわからん民」の自分にとって福音以外の何物でもなく、仮にプロジェクトが爆発しても一瞬で元に戻す事が出来る「つよつよアンドゥ」としてめちゃくちゃ便利に使っている。
PlasticSCMは1~3ユーザー、5GBまで無料なので、是非試してもらいたい。
詳細は公式ページで確認できるよ。
⭐本編
ここから先は所謂トラブルシューティング、助けてドラ〇もん式に書いていくよ。
何故なら俺がまさに助けて椿先生~!!と泣きついた記録でもあるからだ。
当たり判定関連
🔵 子にぶら下げたColliderが機能しないが~~!?
✅ 親にRigidBodyを付ける
何故かは知らないが、Unityはルート以外にColliderを設定しても拾ってくれないので、子にしたColliderを動作させたい場合はRigidBodyを付ける必要がある。
だからと言ってじゃあとりあえずつけとけばいいんだね?
というノリでRigidBodyを付けると、つけたオブジェクトが空高く飛んでいく結果になると思う。
これはCharacterControllerを使っている場合に起る。
CharacterController自体がColliderであるため、相互干渉を起こした結果であるが、これはIgnoreLayerCollisionで回避する事が可能。
可能ではあるが、使えるLayerの数には限りがあるのでよく考えて使おう。
🔵 ヒットしたColliderが子だった時、その親オブジェクトを取得したい
✅ TransformのGetRootで行ける
アイコンがVector3なのでVector3情報しか取れんのかと思ったらGameObjectとしても取得出来る
これによって何ができるかというと、Playerが攻撃したりされたりした対象を現在のターゲットに設定する、といったことができるようになる。
通常、攻撃モーションはPlayerの前方に移動するが、この機能を作ると敵の位置に向かって移動するという事が出来るようになり、遊びやすさに繋がっていく。
因みにGetRootで取得できるのは「最上階の親」で、現在のオブジェクトの一つ上の親を取得したい場合はGetParentを使う。
同様にGetChildで自分の子供を取得する事も出来るので、これらのノードを駆使すればVariablesの使用を減らす事が出来るケースもあると思う。
🔵 武器の当たり判定出した瞬間自分に当たっちゃうんだけど…
✅ レイヤーを使って判定を除外しよう
Ignore Layer Collisionで指定したレイヤー同士は衝突しない設定が出来る。
最初に触れた事であるが、これを使うと色んな事が出来るので便利な反面、作れるレイヤーの数には限りがあるので、乱用は出来ない。
逆にタグは無限に作れるので、IfとCompareTagを使ってIgnore Layer Collisionとは手軽さ違うが、特定のタグを持った物が当たった時、という条件を作る事が出来る。
これでPlayerにヒットしたオブジェクトがSwordColliderであるか否かで分岐出来るようになる…が、衝突判定は発生するのでIgnoreLayerCollisionよりもコストは高い。
フロー関連
❓ ifとSelectどう使い分けんの?
✅ 数値や中身だけが変わる場合はSelect、進行が分岐する場合はif
極論どちらもifで出来るけど、前者をifでやろうとすると先の処理は同じなのにフローが分岐してややこしい。
ノード全体の総数を減らすより、経由するフローの数を減らしていく方が結果としてグラフは整理されていく(後からでもしやすい)気がする…
因みにifノードは検索窓にbra(nch)と入れても出てくるのでありがたい。
✅ Integerで分岐するSwitch On Integerノードもある
こちらはGraph Inspectorで分岐数を自由に設定できる。
可能な限りリファレンスを見たくないので、検索中にこういった(似たような)ノードを候補に出してもらいたいが、ままならんもんである…
整理整頓関連
❓ ナンでもカンでもOnUpdateだと死ぬ?
✅ 死ぬ。
解決法としてはコールバックを使ってUnityEventTriggerに置き換える。
コールバックってなんかエンジニア用語っぽくてカッコイイので良くわかってないけど使っていきたい。
いやまぁ単純にそれが呼ばれた時(Call)に返す(Back)メッセージの事だと思うけど、コールバックはUIが他とちょっと違って取っつきにくい。
解ればハチャメチャに便利。
🔵 色々パラメータ作ってたらVariablesがクソ長くなって鬱陶しいよ~~!
✅ Listを使う
ハチャメチャに便利シリーズ第二段。
Variablesに色んなパラメータを一個ずつ突っ込んでるとInspectorがバカみたいに長くなるのでListを使って整理しちゃう!
List君もまたちょっととっつきにくい、というかリスト自体は番号でしか管理できないので何番目が何のパラメータだったか覚えていないといけない。
でも使い始めると便利すぎてヤバイ。そんな奴。
複数のParticleSystemを登録してForEachLoop(後述)で一斉に発射する事も出来る。
🔵 Listを使ってもInspectorが長くなっちゃうよ~~!
✅ 思い切って管理するGameObjectを分ける
そのパラメータ、本当に必要ですか?
必要だから入れてんじゃい!そうですね。
でもまぁ貴方が持ってる必要はないですよね?っていう物が結構ある。
そういう時は子供を作ってそっちに持たせるとよい。
🔵 同じ処理を何度もするのにノード複製しまくるのどうなの?
✅ ForEachLoopを使う
ForEachLoopを使うと入力(List)に対してBodyから出た内容をListの数分繰り返した後、Exitから抜ける。
めちゃくちゃ取っつきにくいけど、Listを使い始めるとめちゃくちゃ便利になる機能。
🔵 一回作った機能を他の場所でも使いまわしたいんだけど
✅ SubGraphを使うのじゃ
SubGraphはグラフをAsset化して使いまわす事が出来る。
一ヵ所修正すれば使いまわしている場所全てが修正されるので、ある程度完成したらSubGraph化すると資産になるのでオススメ。
VisualScriptingでは自分がより開発しやすい環境を整えていく為にもSubGraph化による資産形成を忘れてはならない。
検索関連
🔵 ゲームオブジェクトの名前を取得したい
✅ GameObjectのGetNameを使う
滅茶苦茶シンプルなのに良く忘れる。
🔵 親なら自分の子供の事くらい知っといて頂きたいんだが~!?
✅ 名前で拾いたいならTransformのFindを使う
✅ GetChildでも拾えるけどこっちはIndex(Integer)なので動的に構造が変化する可能性がある場合は使うと神経すり減る。
✅ Countで総数も拾える。アイテムを10個以上持てないようにしたりするのに使えそうだけどそういうのはVariablesとかでやった方が良さそう?
🔵 自分の子オブジェクトが持ってるコンポーネントをどうにかしたい
✅ Get Component in Childrenを使う
🔵 特定の属性を持ったオブジェクトを一括で取得したいんだけど…
✅ タグを使うといいよ
GameObjectのFind Game Object(s) With Tagを使うと、今シーン内にあるそのタグを持った全オブジェクトを取得できる。
これを使うと「全ての敵を倒せ!」のようなミッションの成否判定が簡単に出来るね。
🔵 無い物は無いって言ってよね!
✅ NullCheckは大事
基本的にVisualScriptingのノードや処理は何らかを対象として実行されるので、この「何らかの対象」は原則的に「存在する物」を前提としている。
ノードに最初から「This」と書かれているのはそういうことで、このThisを変更する事で処理対象を任意に変更する事が出来るわけだが、色々処理を組んでいくと、この「This」に入る物が存在しないケースが出てくる。
例えば「箱を取ってこい」というミッションがあって、誤って箱を壊してしまったり、バグで箱が消えてしまった場合「箱」は存在しなくなるので、箱を対象にした処理があった場合は目標を見失ってしまい「目標がないよ~!」とエラーを吐く。
この「対象物が無い」事を「Null」というのだけど、前述の通りノードや処理は原則として「何等か」を対象としているので、対象物が無い場合は前提が崩れてしまい、そこで処理が止まってしまう。
それを防ぐために必要なのが「対象物がちゃんと存在してるかどうか」を調べる「NullCheck」である。
対象物が動的に変化する処理を作る際は、このNullCheckをちゃんと入れておかないとエラーになるリスクが跳ね上がるので、面倒だけどちゃんとやるべし。はい。
素朴な疑問関連
❓ String.Compareの返り値ってなんでInt型なの?Boolで良くない?
✅ 内部処理による解釈不一致だったよ…
そもそもの話だけど、StringのCompareは一致不一致だけを取得する時に使う物ではないらしい。
結論から言うと、このCompareはどちらかと言えばソート目的で使われる物とのことだった。
Compareは内部的にはChar型という文字コードで判定を行っていて、平たく言うと一文字単位で文字コードの一致不一致を文字数分連続して行うのだそうだ。
例えばAの文字コードが65、Bが66、Cが67、だった場合、ABCは65,66,67になり、DEFは68,69,70になる。
Compareはこの数字の大小を比較するノードであった!
例えばA(65)とD(68)の比較はAがDより小さいので-1。
B(66) == E(69)も-1。
ではD(68) == A(65)はというと、1が返ってきて、C(67) == C(67)の場合は一致しているので0が返される。
つまりこのCompareを使うと何が出来るのかというと、比較対象をアルファベット順(文字コード順)に並べ替えることが出来る。
0以外を除外する事でEqualノードと同じ使い方も出来る事に違いはないが、都度上記の処理が走っていると考えると、寧ろ比較対象をどうにかしてEqualで比較出来る形に直してしまった方が処理は軽くなる。
というわけでString.CompareはBoolではなくInt型である…らしい。
❓ Profilerってちゃんと見てなきゃダメなん?
✅ 重さに耐えられなくなった時に見るくらいでいいよ。
というと本業に怒られるかもしれないが、この場に本業はお呼びではない。
そもそもの話としてVisualScriptingのパフォーマンスは非常に悪い。(らしい)絵描きで言うならフリーハンドで描けば早い物をわざわざ定規やコンパスを使って描いている様な状況に近いと思う。
定規は角度を合わせたり、コンパスはサイズを変えたりする手間が毎回挟まる。その分遅い。そんな感じ。
絵の練習をしている、あるいは楽しく絵を描いている相手に対して「普通に描けばいいじゃん」なんていうのは頓珍漢という物であり、お呼びではないのである。
Q:ではプログラミングの勉強をすればいいのでは?
A:死んでもしたかないのである。
良いですか、アナタ。
私が何も考えずキャラクターをモデリングしたりデザインを起こしたりするのに一体どれほどの時間がかかったと思っているのです?
その分野でその域に到達するまであと人生どれくらい使う必要があるのかなんて考えたくもないでしょう。
アートに全振りしてきた人には今更他のスキルツリーに割くポイントなんか残っちゃいないのです。
でも自分が作ってるアートでゲームが作れるんなら作ったっていいじゃないの!!
というわけでVisualScriptingの意義について論ずるつもりはないが、少なくとも自分を始めとするプログラミングのプの字も出来ない奴が重さはさておき動かして遊べるゲームが作れる事は非常に大きな意義である。
それは間違いなくモックとしての機能も果たすでしょうし、最適化や仕上げは我々の様な素人ではなく本業が行うべきなのです。閑話休題。
で、Profilerですが、まぁ、何時かは触らなければならないのは確かです。
…が、前述のとおり我々は素人であります。
素人というのはLv1ということですので、失敗から得られる経験値はLv99の比ではない程大きく、つまるところ、率先して失敗せよ乙女、という事なのであります。サー。
ほんでまぁ使い方とかは正直よーわからんのですが、とりあえずこれだけ知ってればいいよ、と言われたのがこの辺りでした。
Play前にProfilerを起動して、スパイクが出たら一時停止する
スパイクになっているところをクリックする
MainThreadのグラフ付近でFを押してビューを広げる
MainThreadは下から順に積みあがっていて、それぞれが個別の要素ってわけではない
重さの単位はms(ミリセク)で、その処理にどれだけ時間がかかってるか
一番上から一段ずつ下を見て行って、どれが一番負荷ってるかを探す
まぁ探したところで大体はそれが何か分からない
ふーん…つってProfilerを閉じる
❓ CustomEventTriggerとかにもあるArgってナンスカ?
✅ Argumentの略で意味は引数
要はCustomEventTriggerが荷物の伝票であるならArgは備考欄みたいな物。「赤にしといて」とか「100にしといて」「無かったことにしといて」とかをオマケで付けられる。
これによってトリガーを使いまわしてもオマケで入ってくる情報を変えられるので、異なる動作に誘導させることができる。
⭐テキスト関連
🔵 シナリオとかの長文テキスト系をテキストファイルで扱いたいんだけど…
✅ 外部テキストファイルを読み込めるよ!
そりゃあ文字を書く作業の為だけにわざわざUnity起動するこたないでしょ!
使い慣れたテキストエディタでにょきにょき書き進めた方が効率だって良いに決まってる。
というわけで外部テキストを扱うノードがコチラのTextAsset。
🔵 日本語が出ないよ~~!!
✅ TextMeshPro(uGUI)でアセットを生成する事で使えるようになるよ
プロジェクト公開する場合は権利周りに注意しようね!
🔵 TextMeshPro使ってるのにCanvasに反映されないよ~!!
✅ TextMeshProとTextMeshPro(uGUI)は別物なんだ…
これまじでバチクソややこしい。
特になんも設定せずCanvas作ってTextMeshProを使った場合、VisualScriptingで制御するのは(uGUI)が付いてる方です。
🔵 文字を一気に出すんじゃなく一文字ずつ出したいなぁ
✅ TimerとTextMeshPro(UGUI)の連携技で行けるよ~
🔵 クリック待ちってどうやるの?
✅ WaitUntilってのを使うよ
読んで字のごとく「何かをするまで待機」させるのがこのノード。
しかしこのノード、ただ挟むだけでは機能しません。
イベントのトリガーノードにある「コルーチン」とやらを有効化しないとちゃんと動いてくれないのです。
❓ コルーチン?ってなんぞ?
✅ コルーチンってのはまぁ「従来であれば一気に走り抜けてしまう処理を途中で一旦止めたり再開したり出来る権利」みたいな感じの機能。
予めトリガーに「コルーチン」の権利を渡しておくと、その後のフローの途中で一旦止めたり再開したり出来るようになるんだけど、一寸勘違いしやすいのは、例えば2秒待ってから次に行きたい、とかはコルーチンを使わずにCooldownやTimerのCompleteでつなぐ必要がある。
逆にコルーチンを有効にした上でこれらのTimer系を使うと、そこでコルーチンの権利がはく奪されてしまい、その後WaitUntilを差し込んでも機能しなくなってしまうのだ…!!Timer恐ろしい!!なんでかは知らん!!
因みにコルーチンを維持したままTimer系を使う為の回避策もあって、代表的なのは三つ。
❶ CustomEventTriggerで引き継ぐ
❷ Sequenceでフローを別ける
❸ Timerを使う処理をSubGraphの中に突っ込む(コルーチン操作は外でやる)
こうするとTimer処理を途中に挟んでもコルーチンを殺さずにフローを組む事が出来たりする。ややこしい!!でもそういうもんなんだってさ!!
そんな感じのコルーチンくんだけど、まぁ便利に使えるケースは多々出てくるし、多分何度か「コルーチン有効にしてるのに機能しないの何で~~!?」ってやると思うが、とりあえずWaitUntilを使う時に押すボタンくらいの気持ちで覚えてから、他の使い方を知るのが良いのかもしれない。
多分最初にコルーチンが必要になるのってクリック待ちだと思うしね。
知識を入れる順番って大事だとおもいます。
💡 プログラムの速度
これ、自分も最初めちゃめちゃ勘違いしてたので、素人あるあるなのかもしれないけど、例えばAnimationのPlayノードで死亡モーションを再生させて、その後死体を消したいのでDestroyノードに繋いで実行すると、死亡アニメーションが再生されることなく一瞬でモデルが消滅する。
どういう事かと言うと、VisualScriptingは基本的に開始命令しか出さない。
開始命令を出したらさっさと次に行っちゃうので「Playで死亡アニメーション再生させてね!!」って言うだけ言ったら直ぐに「自分をDestroyしてね!!」って言いやがる。しかもその間0.01秒以下。
つまり厳密には死亡アニメーションの1フレーム目は再生されているが、次の瞬間にはもうDestroyが実行されているのである。
奴らは死亡アニメーションが再生しきるまで待つなんてことはしない。
でも我々は死亡アニメーションをちゃんと再生して貰わないと困る。
そこで幾つかの「待機命令」を出す必要がある。
例えばTimerやCooldownでアニメーションが再生し終わるまでの時間を指定して、カウントがCompleteしたらDestroyに流す。
もしくはAnimatorのStateから抜けたらDestroyに流すといった方法でもいい。
この部分はTimeで時間をカウントする必要があるので、WaitUntil…「~するまで待つ」が使えない…というより使う必要がない。
言葉にするとどちらも「何かが達成されるまで待機」ではあるのだけど、この辺りはルール上使い分けなければならない物らしいので、片方試してダメだったらもう片方でやる、くらいの気持ちで挑むといいのかもしれない。
⭐構造関連
🔵 全部一つのオブジェクトにやらせてたらVariablesが大変な事になった
✅ 担当を分けよう
Playerを動かす物はPlayerControllerといった名前にしてPlayerを動かす=ユーザーの入力を拾う事だけをさせる。
同様にEnemyを動かす物はEnemyController、ステージギミックを動かす物はStageControllerといった形で担当を分けると整理しやすい。
🔵 別けたら別けたで個々が持ってるVariablesにアクセス出来なくない?
✅ 参照先を指定すれば出来るよ
操作対象であるPlayerをVariablesに登録して、それをThisになっている部分につなぐとPlayerが持っているObjectVariablesにアクセスできる。
その際、扱いたいVariablesその物はドロップダウンリストに反映されないので、手で直接入力する必要がある。
⭐ Contolする物とControlされる物という構造
VisualScriptingは考え無しに思いつきで作っていくと、後々結構な規模の組み換え作業が発生する。
それ自体はVisualScriptingを習得する上で絶対に経験した方が良いので、善悪でいえば善であるが、いざその段になった時に何の手掛かりもなく整理整頓できる人は天から才能を貰ったと思ってよい。
ここまで順番通りに読む必要はないが、それでも一度触ってみればわかる通り、VisualScriptingの殆どはVariablesとの闘いでもある。
Variablesを綺麗にする事、つまり必要な物を適切な場所に置く事はVisualScriptingを使いこなす上でとても重要な物なので、そのとっかかりの指針も一つくらいは持っておきたい。
というわけでここでは自分が教わった考え方として「操作する物」と「操作される物」を別けて作る、というのを紹介しようと思う。
💡 操作する物=Controllerとは?
Controllerは原則として「入力の受信や指示出し以外は行わない」とする。
分かりやすく言うと、Controllerは「右に動け」という指示だけを出し、Controlされる物が「右に動く」という処理を行う。
💡 操作される物とは?
平たく言うとControllerによって制御される対象の事である。
例えば「ユーザーの入力によって動くPlayer」があった場合、ユーザーの入力を受け付けるのがPlayerControllerで、PlayerControllerから指示を受け取って実際に動くのがPlayerという形。
一見すると間に余計な物を挟んでいるかのように見えるかもしれないが、もし「入力した通りに動かない」という現象が発生した時に何処を調べればいいかがすぐにわかる。
💡 特別な物とそうでない物
ここで言う特別な物というのは、ゲーム全体を通して二つとない物の事を指す。
例えばプレイヤーキャラクターは間違いなく特別な物であるし、一重に敵と言っても沢山出てくるザコと一体しか出てこないボスでは扱いも変わるという物。
こうした扱いの違う物毎にコントローラを作る事で、仕分けを進めていけるわけだ。
例えばMobController、BossController、といった具合。
カテゴリの種類が少ないならEnemyControllerとしてもいいと思う。
💡 Controllerはキャラクター性がある物に限らない
例えば雨や酸素量、日照などの天候が重要なゲームであれば、それはWeatherControllerとして天候制御だけを行う仕組みを作ると、開発を進めて行っても天気関連を弄る場合はここを弄ればよくなる…というか、良くなるように作っておけば、未来の自分が困らない。
💡 一般コントローラを管理するゴッド・コントローラ
そしてこれらはゲームを構成する一要素でしかないので、これらのコントローラを統括するブレインの様な物が必要になる。
例えばゲームスタート時の初期化を行ったり、ゲームオーバーの判定をしたり、イベントの仕切り、フラグの管理といった、ゲームその物の進行を管理するGameControllerを用意する。
各部署にこういったコントローラを作っておくと、あとからザコの種類が増えたり、ボスの数が増えたりしても、末端のやる事と報告する内容がルールに則っている限り、そこから受け取った情報を処理する上層部には殆ど影響が出ないのである。
さて、ここまで読んで3Dアーティストなら過去に似たような何かをやったような…デジャヴの様な感覚を覚える人も居るのではないか。
そう、リギングだ。
💡 ゲームのリギング
この形は言うなればゲームにリグを付けているのだと考えると、途端に色々腑に落ち始めないだろうか?
ゲームを駆動させる様々なボーンやシェイプを制御する為のリグを仕込むのだと考えると、元々3Dアーティストであった自分は非常に腑に落ちる物があったので、今もこのスタイルで開発をしている。
勿論この方法にもデメリットはあって、基本下層から上層への報告ルールが決まっていればいいが、下層の中であっちこっちにデータを参照し始めると、一つ壊れると全部壊れるといったことも起こりうる。
正直こういった部分も非常にリグみがあると思う。
とはいえ、メンテナンス性を高く保つ事は、仮にトラブルが起こっても原因究明に掛かる時間の削減につながる。
各々自分に合った制作スタイルはあると思うが、とっかかりの一つとして覚えておいて損はしないと思う。
🔨 SceneLoaderが使えないんだけど!
✅ AdressableGroupsオプションを有効化する必要あり
🔺この項目の機能は現在開発中の物です🔺
Scene Loaderを使う時、Sceneのところにシーンファイルが設定できない!という事があるが、これは事前に一つ準備が必要らしい。
Window>Asset Management>Addressables>Groupsでウィンドウを開く。
そうすると出てくるAddressables Groupsウィンドウ。
これ自体は開くだけで有効化されるらしいので、とりあえず一度開いたら閉じちゃってもOK。
最終的にビルドする時にまた操作する必要があるので一連を掲載するが、
とりあえずSceneLoaderを使いたい人だけの人はこの先は何となく読むだけでよい…と思う。
🔵 そもそもAdressablesって何?
✅ 外部アセットを使う時、そのファイルがある場所を示す住所みたいな物
ビルド時に
Adressables Groups Windowって毎回打つのが面倒なのでAGウィンドウって言います。
で、ビルドする為の設定がこのAGウィンドウの右上にあるBuildから