VisualScripting奮闘記(駆)


!ご注意!

これはProjectTCC公開に至るまでの間、実装のじの字も知らん奴がVisualScriptingに挑戦し敗北を喫する度に一つずつ増えて行った壁の残骸を纏めた物でござんす。

掃除も整理も洗濯も一切されず、まぁなんか消すのも勿体ないかなと思ったので公開してみた、というくらいの物で、裏路地に積まれた新聞紙くらいの気持ちで見て頂ければ幸い。

今では多分もっとマトモな実装出来ると思うけど、俺はこういうところで躓いたよ、という情報は正直なところどんなTipsよりも役に立つ場合があるのを知っているので、公開するだけしてみようと思った次第。


他人の役に立つかどうかは関係なく、自分が一度調べたり教わったりしたにも関わらず、忘却の果てに同じ事で躓いた問題をここにメモる事で、仏の顔を三度拭かず済ませようって感じnote。


⭐前提

  • ntnyはプログラミングのプの字も出来ない。

  • 実装なんかやった事がないので脳筋がデフォルト設定。

  • 思いつき万歳。行き当たりばったりで作る。

  • 大抵作ったあとの整理で死ぬ。

  • ntnyは掃除ができない。

  • 動けばよかろうなのだ!

  • 一部のノードは開発中の物だよ。

  • 本noteはリファレンスではないので喋り言葉で綴る。

  • 何故ならリファレンスを読める人はリファレンスを読むからだ!

  • ntnyはリファレンスを可能な限り読みたくない人種である。

何となくQA方式で書かれているがQこそ自分が実際に質問した事であり、Aは回答をもとに自分が消化した物である。つまりどっちも自分だ。

OK?

⭐VisualScriptingを使う前に知っておくべき事

🌞VisualScriptingの種類

🔴 ScriptMachineとStateMachineがあるけど最初はどっち使えばいいの?
✅ 敢えて言うならStateMachineかな…
実際のとこ、この二つは初期設定が違うだけで、出来る事は同じなので結論から言うとどっちでも良かったりする。

自分も最初はよく分からなかったので、取っつきやすそうなStateMachineから始めたんだけど、今ではScriptMachineを主体に使う感じになった。
だからと言ってStateMachineを使わなくなったわけではないし、ScriptMachineで始める事を推奨するわけでもない。

StateMachineの利点はビジュアルで今どの処理をしているか、どういう状態にあるかが一目でわかるのと、状態遷移ベースで考えられるのは非常にとっつきやすいと思う。

今は「元気」Stateだけど「悲しい事」が起こると「悲しい」Stateに移動するStateMachine

ScriptMachineはとにかくシンプルで、単純な機能を作るだけならこっちの方がGraphの遷移も少なくてラクチン。

一度実行して終わるタイプの機能ならこっちでいい

で、もし使い分ける指針があるとしたら、そのオブジェクトに「状態変化」が起こるかどうか、または「操作形態」に変化が発生するかどうかで判断すればいいと思う。

最もシンプルな例だと、レバーやスイッチギミックのオン/オフなんかはStateMachineで作って、オン状態とオフ状態をStateで切り替えるとか。

他にも自キャラをVisualScriptingで操作する時「フィールド探索モード」と「バトルモード」があるならStateMachineで始めると入りやすい。
それぞれのモード毎にStateを作っておけば、Stateを切り替えるだけでモードチェンジが出来るようになる。

プレイヤーの状態やモード毎にStateを別けると管理しやすいね

しかしここで新たな刺客が登場する!
State Unitである!

乱用するとあっという間に迷宮が出来上がるState Unit

なんと、これを使えばScriptMachineの中にStateMachineを丸ごと突っ込む事が出来るのだ~!!な、なんだって~!?

そう聞くと「じゃあScriptMachineで始めておけば困ることなくない?」と思う人も居るでしょう。
まぁそうっちゃそうなんですわ。

ただその場合はStateUnitをダブルクリックしないとStateMachine画面にならないので、編集する度に2クリック要する事を良しと出来るかどうかみたいな話にもなるわけです。
それなら最初からStateMachineで良くない?

だってStateMachineだからって必ずしもStateを遷移させないといけないわけじゃないじゃん?

このState一つでStartからEndまでやる事だってできる

StateMachineっていうのは、つまるところ一つのGraphの中で複数のScriptMachineを行ったり来たりさせるシステムなわけで、この最初のState一つが即ちScriptMachineなわけですよ。

じゃあStateMachineでいいじゃん!
となったので、自分は最初StateMachineから始めた、というワケです。

💡 つまり、マジでどっちでもいいんだ。
マジでどっちでもいいってのは、どっちで始めたとしても、取返しが付かなくなる事はない、って事。

Tips

因みにStateMachineはこの画面でもグループ化出来るので、メモがてらグループタイトルに内容を登録しておくと見やすくなるよ。

分かりやすさは大事

🌞Variablesの種類

🔴 ScriptMachineを作ったらVariables?ってのが勝手に出来たんだけど?
✅ それはとても便利な箱です

Variablesってのは、扱いたい要素をしまっておく箱の様な物。
ScriptMachineもStateMachineも、最初に作成した時点で必ずVariablesが一つと、ヒエラルキーの中にVisualScripting SceneVariablesというのが勝手に生成される。
VisualScriptingは基本このVariablesを対象として操作する物なので、例えば自キャラであればHPやスタミナといった要素をVariablesに登録しておくとVisualScriptingでそれらの数値を操作可能になる。

詳しくは本稿以下構造関連の項目で触れるので、ここでは一先ず勝手に作られるコイツ何者なの!?という説明のみにとどめる。

💡 VariablesはTRPGキャラシートにある設定リストみたいなものだよ。
HPとかMPとか、素早さとか、武器とか、攻撃力とか、属性とか、状態異常とか、そういうのを入れて置く場所。

Tips

ScriptMachineを付けたオブジェクトに勝手に作られるVariablesは、そのオブジェクト個人が持っている物で、言わばそいつのステータスやパラメータの様な物だと思ってよい。
それに対してSceneVariablesというのはシーンファイルその物が持っているVariablesで、これはシーン内にあるオブジェクトであれば誰でも自由にアクセスできるパラメータを持たせることができる。

例えばフィールド探索中にダメージを食らう酸性雨が降ってくるとして、自分だけでなく敵も同等のダメージを受ける、といった場合はAcidRainといったパラメータをSceneVariables側にInteger(整数)で15とか持っておくと、15ダメージを受ける酸性雨という要素をプレイヤーも敵も参照できる。

💡 Variablesがキャラシのステータス設定ならSceneVariablesはフィールドのステータス設定くらいの感覚で使い始めると良いかも?
沼地だと足が遅くなるとか、風の谷ではジャンプ力があがる、とかね。

Tips

更にScriptMachineの画面を良く見るとGraphVariablesという物もある。
これは今開いているGraphだけが使う事の出来る小箱。
このVariablesのメリットはInspectorにあるVariablesには登録されない為、無暗に縦長になる事を防げる事。
デメリットはInspectorに表示されないので確認が手間な事…。
ままならないね。

一時的に回転速度を保存しておいたりするのに使う

💡 VariablesはBlackboardの右に行くにつれてスケールが大きくなる。
Graphは今編集しているGraph内でしか機能せず、Objectはオブジェクト単位、Sceneはシーン単位、AppはApp単位でシーンを跨いで参照できる。
Savedは少々特殊で、自分もまだ使う程の物をVisualScriptingで開発していないのでよーわからん。

Tips

🌞グループ化を魂に刻み込め!

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はもう一段隠してくれてもいいと思う…

間違ってもReset to Defaultsを押さないように!!
これは完全にトラップです!配置も近いし頭文字がどっちもRe!

この手順を踏む事で、VisualScriptingが標準では扱えない機能にアクセスできるようになる。なんて面倒くさい!
良く使うところだとTextMeshPro(uGUI)やCursor、他にもCinemachineやImage等も追加しないと扱えない。
面倒くさい?そうでしょうとも!
なので、サンプルプロジェクトから開始するのが手っ取り早いと思います!

💡 この機能追加、結構頻繁に行う事になるんだけど、Resetが怖すぎて可能な限り近寄りたくない。
けど結局やらねばならんので、VisualScriptingで作業していて「あのノード見当たらないぞ」「あの機能は操作出来ないのか?」と思ったらここで探して追加しましょう。
Regenerateの際はどうか、慎重に…

Tips

🌞PlasticSCM(現UnityVersionControl)は神

ひょっとしたらGithubとかは聞いたことがあるかもしれないけど、PlasticSCMも言うたら同じような物。
バージョン管理システムと呼ばれる生命保険の一種だ。

UnityでAssetを直接操作したりすると偶にUndoが利かない事がある。
それが結構なコア要素だったりして、戻せないとなるともうUnityアンインスコするか…ってなるのはとても良くわかる。
これを未然に防ぐ…事は出来ないので、起こってしまった大災害をなかったことにするのが、これらバージョン管理システムである。

こらそこ、違うとか言わない。
使い始めの人間にはSteamのクラウドセーブの様な物と思って始める方が取っつきやすいってもんなの。

というわけで、バージョン管理システムは使えるなら絶対使った方がいい物なわけだが、どういうわけかどれもこれも非常に取っつきにくい。

Github、Turtoise、SourceTree、他にも色々あるが、基本的にそれらは専用のアプリやエクスプローラ、ブラウザ等で操作を行う上に専門用語がとても多いので、自分には全然使いこなせなかったが、PlasticSCMはUnityのEditorに統合されていて、タブから直接コミット(クラウドセーブ)が出来る。

ひと段落したらコミット、何か新しい事や実験をする前にコミット。
これを簡単に出来るようになったのは「バージョン管理よーわからん民」の自分にとって福音以外の何物でもなく、仮にプロジェクトが爆発しても一瞬で元に戻す事が出来る「つよつよアンドゥ」としてめちゃくちゃ便利に使っている。

Editorに統合されたPlasticSCM

PlasticSCMは1~3ユーザー、5GBまで無料なので、是非試してもらいたい。
詳細は公式ページで確認できるよ。

PlasticSCM


⭐本編

ここから先は所謂トラブルシューティング、助けてドラ〇もん式に書いていくよ。
何故なら俺がまさに助けて椿先生~!!と泣きついた記録でもあるからだ。


当たり判定関連


🔵 子にぶら下げたColliderが機能しないが~~!?

✅ 親にRigidBodyを付ける
何故かは知らないが、Unityはルート以外にColliderを設定しても拾ってくれないので、子にしたColliderを動作させたい場合はRigidBodyを付ける必要がある。

だからと言ってじゃあとりあえずつけとけばいいんだね?
というノリでRigidBodyを付けると、つけたオブジェクトが空高く飛んでいく結果になると思う。
これはCharacterControllerを使っている場合に起る。

CharacterController自体がColliderであるため、相互干渉を起こした結果であるが、これはIgnoreLayerCollisionで回避する事が可能。
可能ではあるが、使えるLayerの数には限りがあるのでよく考えて使おう。

そもそも使えるLayer数が少ないと思います!

🔵 ヒットしたColliderが子だった時、その親オブジェクトを取得したい

✅ TransformのGetRootで行ける
アイコンがVector3なのでVector3情報しか取れんのかと思ったらGameObjectとしても取得出来る

Visualを信じすぎてもヨクナイVisualScripting

これによって何ができるかというと、Playerが攻撃したりされたりした対象を現在のターゲットに設定する、といったことができるようになる。
通常、攻撃モーションはPlayerの前方に移動するが、この機能を作ると敵の位置に向かって移動するという事が出来るようになり、遊びやすさに繋がっていく。

因みにGetRootで取得できるのは「最上階の親」で、現在のオブジェクトの一つ上の親を取得したい場合はGetParentを使う。
同様にGetChildで自分の子供を取得する事も出来るので、これらのノードを駆使すればVariablesの使用を減らす事が出来るケースもあると思う。


🔵 武器の当たり判定出した瞬間自分に当たっちゃうんだけど…

✅ レイヤーを使って判定を除外しよう
Ignore Layer Collisionで指定したレイヤー同士は衝突しない設定が出来る。
最初に触れた事であるが、これを使うと色んな事が出来るので便利な反面、作れるレイヤーの数には限りがあるので、乱用は出来ない。

Layer1とLayer2は衝突しない、というノード

逆にタグは無限に作れるので、IfとCompareTagを使ってIgnore Layer Collisionとは手軽さ違うが、特定のタグを持った物が当たった時、という条件を作る事が出来る。

これも非常に便利なノード

これでPlayerにヒットしたオブジェクトがSwordColliderであるか否かで分岐出来るようになる…が、衝突判定は発生するのでIgnoreLayerCollisionよりもコストは高い。


フロー関連


❓ ifとSelectどう使い分けんの?

✅ 数値や中身だけが変わる場合はSelect、進行が分岐する場合はif
極論どちらもifで出来るけど、前者をifでやろうとすると先の処理は同じなのにフローが分岐してややこしい。
ノード全体の総数を減らすより、経由するフローの数を減らしていく方が結果としてグラフは整理されていく(後からでもしやすい)気がする…
因みにifノードは検索窓にbra(nch)と入れても出てくるのでありがたい。

結果は同じだけど、フローやメンテナンス性に違いがある

✅ Integerで分岐するSwitch On Integerノードもある
こちらはGraph Inspectorで分岐数を自由に設定できる。
可能な限りリファレンスを見たくないので、検索中にこういった(似たような)ノードを候補に出してもらいたいが、ままならんもんである…

これも非常に便利なノード

💡 ランダムで足音を変えたり、行動を変化させるのにも使えるよ

Tips

💡 謎のUI…
CustomEventTriggerのように、ノード側でInputやOutputを追加出来る物もあれば、Switchの様にGraph Inspector側で追加するタイプもある。
何をもって別けているのか分からないので、そういう物として覚えるしかないが、Graph Inspectorは意外と見落としやすいので注意が必要。

Tips

整理整頓関連

❓ ナンでもカンでもOnUpdateだと死ぬ?

死ぬ。
解決法としてはコールバックを使ってUnityEventTriggerに置き換える。
コールバックってなんかエンジニア用語っぽくてカッコイイので良くわかってないけど使っていきたい。
いやまぁ単純にそれが呼ばれた時(Call)に返す(Back)メッセージの事だと思うけど、コールバックはUIが他とちょっと違って取っつきにくい。
解ればハチャメチャに便利。

沢山追加出来るけどトリガー用途なので一個で十分。

💡 Callback
VisualScriptingで使う場合は以下の形で覚えればよい。
動作モード(左上)は触らない。
実行者(左下)は自分を設定。
対象(右上)はScriptMachine。
メッセージ(右下)は任意。(この部分がUnityEventTriggerの名前になる)

Tips

💡 動作テストはUpdateでいい
とにかく手軽なので、最初は動作確認の為にUpdateでぶん回すのはヨシ。
ただ作り込みが進んでくるとめちゃくちゃ悪さするので、テストが済んだらUnityEventやCustomEventに切り替えること。はい。

Tips

💡 Animation Modifier BehaviourでState別のキーを設定。
AddKeyノードで「発行されたキーの比較」が出来るので、トリガーは一つでも発行されたキー毎に違う挙動に誘導出来るようになる。

Tips
こうすると斬撃毎のダメージやリアクションを設定できる。

🔵 色々パラメータ作ってたらVariablesがクソ長くなって鬱陶しいよ~~!

✅ Listを使う
ハチャメチャに便利シリーズ第二段。
Variablesに色んなパラメータを一個ずつ突っ込んでるとInspectorがバカみたいに長くなるのでListを使って整理しちゃう!
List君もまたちょっととっつきにくい、というかリスト自体は番号でしか管理できないので何番目が何のパラメータだったか覚えていないといけない。
でも使い始めると便利すぎてヤバイ。そんな奴。

武器リストを登録しておくと、番号一つで武器を切り替えられる

複数のParticleSystemを登録してForEachLoop(後述)で一斉に発射する事も出来る。

SubEmitterだと上手くいかない時に便利

🔵 Listを使ってもInspectorが長くなっちゃうよ~~!

✅ 思い切って管理するGameObjectを分ける
そのパラメータ、本当に必要ですか?
必要だから入れてんじゃい!そうですね。
でもまぁ貴方が持ってる必要はないですよね?っていう物が結構ある。
そういう時は子供を作ってそっちに持たせるとよい。

💡 縛りプレイから得られる知識とその合理性について
例えばSceneVariablesを死んでも使わない縛り、Variablesを可能な限り使わない縛り、などを己に課してVisualScriptingに挑むと意外と未知の便利ノードが見つかったりする。
結果としてSceneVariables使った方が圧倒的に早い事も多いけど、膨大な数のノードを全部把握するなんて到底無理だし、リファレンスなんか死んでも読みたくないので、いっそゲーム感覚でそんな訓練をしたりすると、結構道が開けたりしちゃう。
これはベテランがやるより初心者が少し慣れてきた頃(何でも楽しい期)にやると一気にレベルアップ出来るので暇があったらお試しあれ。

Tips

🔵 同じ処理を何度もするのにノード複製しまくるのどうなの?

✅ ForEachLoopを使う
ForEachLoopを使うと入力(List)に対してBodyから出た内容をListの数分繰り返した後、Exitから抜ける。
めちゃくちゃ取っつきにくいけど、Listを使い始めるとめちゃくちゃ便利になる機能。

リストに登録されたミッションの数分、UIにミッション内容を表示させる処理。

💡 VisualScriptingでは生成するより有効無効を切り替える方が早い?
単純であったり何度も発生するミッションはOnEnableとOnDisableを使うと簡単に再利用できる。
MissionManager等、統括するオブジェクトを用意しておくと管理が楽に。

Tips
こうするとミッションオブジェクトを有効/無効にするだけでミッション発行が可能になる。

💡 CustomEventTriggerは親友である
こいつがとにかく便利なので使いこなしは必須。
ただしVisualScriptingは配置したノードの検索ができないので、己の記憶力を高めるか、分かりやすい色でグループ化してキャプションを追加する等の対策が必要。

Tips
紫の枠がCustomEventTrigger

🔵 一回作った機能を他の場所でも使いまわしたいんだけど

✅ SubGraphを使うのじゃ
SubGraphはグラフをAsset化して使いまわす事が出来る。
一ヵ所修正すれば使いまわしている場所全てが修正されるので、ある程度完成したらSubGraph化すると資産になるのでオススメ。

この図は顔アイコンや立ち絵を出してテキスト会話をする為のノード群
こんな大がかりな物を1セリフ毎に配置してられない
上のノード群をSubGraphに纏めた物
このくらい小さくなれば、使いまわしやすいね

VisualScriptingでは自分がより開発しやすい環境を整えていく為にもSubGraph化による資産形成を忘れてはならない。

💡 Preferenceのノード登録という壁はあるが、SubGraph化しておけば他のプロジェクトでもAssetとして使いまわす事が可能なので、今後ガシガシゲームを作るつもりがあるならば、SubGraphによるプログラムの資産化をお忘れなきように。

Tips

検索関連


🔵 ゲームオブジェクトの名前を取得したい

✅ GameObjectのGetNameを使う
滅茶苦茶シンプルなのに良く忘れる。

FindやGetNameを使って特定の子オブジェクトを取得して操作する

🔵 親なら自分の子供の事くらい知っといて頂きたいんだが~!?

✅ 名前で拾いたいならTransformのFindを使う
✅ GetChildでも拾えるけどこっちはIndex(Integer)なので動的に構造が変化する可能性がある場合は使うと神経すり減る。
✅ Countで総数も拾える。アイテムを10個以上持てないようにしたりするのに使えそうだけどそういうのはVariablesとかでやった方が良さそう?


🔵 自分の子オブジェクトが持ってるコンポーネントをどうにかしたい

✅ Get Component in Childrenを使う

💡 VisualScriptingの検索
ぶっちゃけあんま頭良くないがそれを逆手に取れる
例えば頻繁に使うGet Xなんかはgexで出てくるし、クッソ長いGet Component in Childrenなんかはgecdreでも出る。
左手(キーボード側の手)をホームポジションから動かさなくてもいい検索が出来るようになるとめっちゃ捗るようになる。
逆に順番通りじゃないと検索に出てこない事もある…VS七不思議のひとつ。

Tips
左手ホームから動かさずに拾えるなら名前が長くても許せる…

💡 Favorite昨日はスーパー便利
良く使う奴で名前が長い奴とか偉い深いとこにある奴なんかを登録するとよい。
とはいえ登録しまくったら結局探すのがめんどくさいので、名前が短かい、左手ホームポジションから余裕で打てる、浅いとこにあったりする奴なんかは除外するといいかもしれない。

Tips
CoolとかBoolとか「O」が遠いんじゃ!

🔵 特定の属性を持ったオブジェクトを一括で取得したいんだけど…

✅ タグを使うといいよ
GameObjectのFind Game Object(s) With Tagを使うと、今シーン内にあるそのタグを持った全オブジェクトを取得できる。
これを使うと「全ての敵を倒せ!」のようなミッションの成否判定が簡単に出来るね。

Enemyタグを持ってる奴が0だったら全部倒した事になる

🔵 無い物は無いって言ってよね!

✅ NullCheckは大事
基本的にVisualScriptingのノードや処理は何らかを対象として実行されるので、この「何らかの対象」は原則的に「存在する物」を前提としている。
ノードに最初から「This」と書かれているのはそういうことで、このThisを変更する事で処理対象を任意に変更する事が出来るわけだが、色々処理を組んでいくと、この「This」に入る物が存在しないケースが出てくる。

例えば「箱を取ってこい」というミッションがあって、誤って箱を壊してしまったり、バグで箱が消えてしまった場合「箱」は存在しなくなるので、箱を対象にした処理があった場合は目標を見失ってしまい「目標がないよ~!」とエラーを吐く。

この「対象物が無い」事を「Null」というのだけど、前述の通りノードや処理は原則として「何等か」を対象としているので、対象物が無い場合は前提が崩れてしまい、そこで処理が止まってしまう。
それを防ぐために必要なのが「対象物がちゃんと存在してるかどうか」を調べる「NullCheck」である。
対象物が動的に変化する処理を作る際は、このNullCheckをちゃんと入れておかないとエラーになるリスクが跳ね上がるので、面倒だけどちゃんとやるべし。はい。


素朴な疑問関連


❓ String.Compareの返り値ってなんでInt型なの?Boolで良くない?

✅ 内部処理による解釈不一致だったよ…
そもそもの話だけど、StringのCompareは一致不一致だけを取得する時に使う物ではないらしい。
結論から言うと、このCompareはどちらかと言えばソート目的で使われる物とのことだった。

特定のアイテム情報をStringで比較するのに使っていたが
間違いだったようだ…

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型である…らしい。

💡 因みに一文字目が不一致だった場合はそれ以降チェックされない。
なのでString.Compareで文字列の一致を取得しようとする時、「ABCDEFG」と「ABCDEFH」のCompareみたいなパターンが最もめんどくさがられるんじゃないかと思う。コンピュータ君に。知らんけど。

Tips

❓ Profilerってちゃんと見てなきゃダメなん?

✅ 重さに耐えられなくなった時に見るくらいでいいよ。
というと本業に怒られるかもしれないが、この場に本業はお呼びではない。

そもそもの話としてVisualScriptingのパフォーマンスは非常に悪い。(らしい)絵描きで言うならフリーハンドで描けば早い物をわざわざ定規やコンパスを使って描いている様な状況に近いと思う。
定規は角度を合わせたり、コンパスはサイズを変えたりする手間が毎回挟まる。その分遅い。そんな感じ。
絵の練習をしている、あるいは楽しく絵を描いている相手に対して「普通に描けばいいじゃん」なんていうのは頓珍漢という物であり、お呼びではないのである。

Q:ではプログラミングの勉強をすればいいのでは?
A:死んでもしたかないのである。


良いですか、アナタ。
私が何も考えずキャラクターをモデリングしたりデザインを起こしたりするのに一体どれほどの時間がかかったと思っているのです?
その分野でその域に到達するまであと人生どれくらい使う必要があるのかなんて考えたくもないでしょう。

アートに全振りしてきた人には今更他のスキルツリーに割くポイントなんか残っちゃいないのです。
でも自分が作ってるアートでゲームが作れるんなら作ったっていいじゃないの!!

というわけでVisualScriptingの意義について論ずるつもりはないが、少なくとも自分を始めとするプログラミングのプの字も出来ない奴が重さはさておき動かして遊べるゲームが作れる事は非常に大きな意義である。
それは間違いなくモックとしての機能も果たすでしょうし、最適化や仕上げは我々の様な素人ではなく本業が行うべきなのです。閑話休題。

で、Profilerですが、まぁ、何時かは触らなければならないのは確かです。
…が、前述のとおり我々は素人であります。
素人というのはLv1ということですので、失敗から得られる経験値はLv99の比ではない程大きく、つまるところ、率先して失敗せよ乙女、という事なのであります。サー。

ほんでまぁ使い方とかは正直よーわからんのですが、とりあえずこれだけ知ってればいいよ、と言われたのがこの辺りでした。

MainThredの一番上が総合処理負荷(ms)らしい。
一番上は以下の合算らしく、重い処理を探すのに一役買ってくれるっぽい。
  • Play前にProfilerを起動して、スパイクが出たら一時停止する

  • スパイクになっているところをクリックする

  • MainThreadのグラフ付近でFを押してビューを広げる

  • MainThreadは下から順に積みあがっていて、それぞれが個別の要素ってわけではない

  • 重さの単位はms(ミリセク)で、その処理にどれだけ時間がかかってるか

  • 一番上から一段ずつ下を見て行って、どれが一番負荷ってるかを探す

  • まぁ探したところで大体はそれが何か分からない

  • ふーん…つってProfilerを閉じる


❓ CustomEventTriggerとかにもあるArgってナンスカ?

✅ Argumentの略で意味は引数
要はCustomEventTriggerが荷物の伝票であるならArgは備考欄みたいな物。「赤にしといて」とか「100にしといて」「無かったことにしといて」とかをオマケで付けられる。
これによってトリガーを使いまわしてもオマケで入ってくる情報を変えられるので、異なる動作に誘導させることができる。

CustomEventはTriggerとArgの数が一致してないと怒られる

⭐テキスト関連


🔵 シナリオとかの長文テキスト系をテキストファイルで扱いたいんだけど…

✅ 外部テキストファイルを読み込めるよ!
そりゃあ文字を書く作業の為だけにわざわざUnity起動するこたないでしょ!
使い慣れたテキストエディタでにょきにょき書き進めた方が効率だって良いに決まってる。
というわけで外部テキストを扱うノードがコチラのTextAsset。

TextとTextAssetは別物だよ

💡 自分の環境だけかもしれないが、TextMeshPro(uGUI)では何故か改行コードが使えず代わりにHTMLタグが使えたので改行する時は<BR>を入れたりしている。

Tips

🔵 日本語が出ないよ~~!!

✅ TextMeshPro(uGUI)でアセットを生成する事で使えるようになるよ
プロジェクト公開する場合は権利周りに注意しようね!

FontAssetCreatorの使い方については各々ググって欲しい

🔵 TextMeshPro使ってるのにCanvasに反映されないよ~!!

✅ TextMeshProとTextMeshPro(uGUI)は別物なんだ…
これまじでバチクソややこしい。

tmpとだけ打つと先に出てくるのは無印の方

特になんも設定せずCanvas作ってTextMeshProを使った場合、VisualScriptingで制御するのは(uGUI)が付いてる方です。

なんならuguiと打った方が早い

🔵 文字を一気に出すんじゃなく一文字ずつ出したいなぁ

✅ TimerとTextMeshPro(UGUI)の連携技で行けるよ~


🔵 クリック待ちってどうやるの?

✅ WaitUntilってのを使うよ
読んで字のごとく「何かをするまで待機」させるのがこのノード。

Bool検問所

しかしこのノード、ただ挟むだけでは機能しません。
イベントのトリガーノードにある「コルーチン」とやらを有効化しないとちゃんと動いてくれないのです。


❓ コルーチン?ってなんぞ?

✅ コルーチンってのはまぁ「従来であれば一気に走り抜けてしまう処理を途中で一旦止めたり再開したり出来る権利」みたいな感じの機能。

EventノードのGraph Inspectorにチェックボックスがある

予めトリガーに「コルーチン」の権利を渡しておくと、その後のフローの途中で一旦止めたり再開したり出来るようになるんだけど、一寸勘違いしやすいのは、例えば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といった形で担当を分けると整理しやすい。

💡 規模によってはシーンレベルで分けるとよい
例えばランダムに湧く敵や、プラットフォーマーのステージなんかはシーン単位で別けてしまってもよい。
Unityは複数のシーンを動的に読み込んだり破棄したりできるので、集中的に作り込みをする場合はシーンを分けておくと管理がしやすい事もある。

Tips

🔵 別けたら別けたで個々が持ってるVariablesにアクセス出来なくない?

✅ 参照先を指定すれば出来るよ
操作対象であるPlayerをVariablesに登録して、それをThisになっている部分につなぐとPlayerが持っているObjectVariablesにアクセスできる。
その際、扱いたいVariablesその物はドロップダウンリストに反映されないので、手で直接入力する必要がある。

こういった構造は頻繁に作る事になるので覚えておこう

⭐ Contolする物とControlされる物という構造

VisualScriptingは考え無しに思いつきで作っていくと、後々結構な規模の組み換え作業が発生する。
それ自体はVisualScriptingを習得する上で絶対に経験した方が良いので、善悪でいえば善であるが、いざその段になった時に何の手掛かりもなく整理整頓できる人は天から才能を貰ったと思ってよい。

ここまで順番通りに読む必要はないが、それでも一度触ってみればわかる通り、VisualScriptingの殆どはVariablesとの闘いでもある。
Variablesを綺麗にする事、つまり必要な物を適切な場所に置く事はVisualScriptingを使いこなす上でとても重要な物なので、そのとっかかりの指針も一つくらいは持っておきたい。

というわけでここでは自分が教わった考え方として「操作する物」と「操作される物」を別けて作る、というのを紹介しようと思う。

💡 操作する物=Controllerとは?
Controllerは原則として「入力の受信や指示出し以外は行わない」とする。
分かりやすく言うと、Controllerは「右に動け」という指示だけを出し、Controlされる物が「右に動く」という処理を行う。

💡 VisualScriptingは全ての処理を一つのオブジェクトで行う事も出来るわけだが、そうなると当然Variablesは膨れ上がるしGraphも複雑を極める。
自分だけ分かればいい、というスタンスであったとしても、ぶっちゃけ自分で見てもサッパリわからん事になる。マジでなる。

そこで担当を分ける為の指針の一つが「操作する物」と「操作される物」に分けるという物。

Tips

💡 操作される物とは?
平たく言うとControllerによって制御される対象の事である。
例えば「ユーザーの入力によって動くPlayer」があった場合、ユーザーの入力を受け付けるのがPlayerControllerで、PlayerControllerから指示を受け取って実際に動くのがPlayerという形。

一見すると間に余計な物を挟んでいるかのように見えるかもしれないが、もし「入力した通りに動かない」という現象が発生した時に何処を調べればいいかがすぐにわかる。

💡 まず始めに入力がちゃんと受け取られているかを確認し、もし正常ならControllerに異常はないので、Playerに問題があるというのが分かる。

Tips

💡 特別な物とそうでない物
ここで言う特別な物というのは、ゲーム全体を通して二つとない物の事を指す。
例えばプレイヤーキャラクターは間違いなく特別な物であるし、一重に敵と言っても沢山出てくるザコと一体しか出てこないボスでは扱いも変わるという物。

こうした扱いの違う物毎にコントローラを作る事で、仕分けを進めていけるわけだ。
例えばMobController、BossController、といった具合。
カテゴリの種類が少ないならEnemyControllerとしてもいいと思う。

💡 Controllerはキャラクター性がある物に限らない
例えば雨や酸素量、日照などの天候が重要なゲームであれば、それはWeatherControllerとして天候制御だけを行う仕組みを作ると、開発を進めて行っても天気関連を弄る場合はここを弄ればよくなる…というか、良くなるように作っておけば、未来の自分が困らない

💡 一般コントローラを管理するゴッド・コントローラ
そしてこれらはゲームを構成する一要素でしかないので、これらのコントローラを統括するブレインの様な物が必要になる。
例えばゲームスタート時の初期化を行ったり、ゲームオーバーの判定をしたり、イベントの仕切り、フラグの管理といった、ゲームその物の進行を管理するGameControllerを用意する。

各部署にこういったコントローラを作っておくと、あとからザコの種類が増えたり、ボスの数が増えたりしても、末端のやる事と報告する内容がルールに則っている限り、そこから受け取った情報を処理する上層部には殆ど影響が出ないのである。

さて、ここまで読んで3Dアーティストなら過去に似たような何かをやったような…デジャヴの様な感覚を覚える人も居るのではないか。

そう、リギングだ。

💡 ゲームのリギング
この形は言うなればゲームにリグを付けているのだと考えると、途端に色々腑に落ち始めないだろうか?

ゲームを駆動させる様々なボーンやシェイプを制御する為のリグを仕込むのだと考えると、元々3Dアーティストであった自分は非常に腑に落ちる物があったので、今もこのスタイルで開発をしている。

勿論この方法にもデメリットはあって、基本下層から上層への報告ルールが決まっていればいいが、下層の中であっちこっちにデータを参照し始めると、一つ壊れると全部壊れるといったことも起こりうる。
正直こういった部分も非常にリグみがあると思う。

とはいえ、メンテナンス性を高く保つ事は、仮にトラブルが起こっても原因究明に掛かる時間の削減につながる。
各々自分に合った制作スタイルはあると思うが、とっかかりの一つとして覚えておいて損はしないと思う。


🔨 SceneLoaderが使えないんだけど!

✅ AdressableGroupsオプションを有効化する必要あり
🔺この項目の機能は現在開発中の物です🔺

Scene Loaderを使う時、Sceneのところにシーンファイルが設定できない!という事があるが、これは事前に一つ準備が必要らしい。

Sceneに登録できない

Window>Asset Management>Addressables>Groupsでウィンドウを開く。

ちょっと見つけにくい

そうすると出てくるAddressables Groupsウィンドウ。
これ自体は開くだけで有効化されるらしいので、とりあえず一度開いたら閉じちゃってもOK。

Adressables Groups

最終的にビルドする時にまた操作する必要があるので一連を掲載するが、
とりあえずSceneLoaderを使いたい人だけの人はこの先は何となく読むだけでよい…と思う。

🔵 そもそもAdressablesって何?
✅ 外部アセットを使う時、そのファイルがある場所を示す住所みたいな物

ビルド時に

Adressables Groups Windowって毎回打つのが面倒なのでAGウィンドウって言います。
で、ビルドする為の設定がこのAGウィンドウの右上にあるBuildから