【Unity】VisualScriptingチュートリアル4
第4回
概要
前回と同じく
こちらの動画を参考としたチュートリアルコードを書いていきます
前準備
前回はキューブオブジェクトを動かし、カプセルオブジェクトに接触した際に頭上のオブジェクトのアクティブ/非アクティブを切り替えるVisualScriptを記述しました
今回は「カプセルオブジェクトに接触した際に画面にテキストメッセージを表示する」をやっていこうと思います
メッセージウィンドウのように配置
配置した TextMeshPro
では日本語に対応したフォントを取り付ける必要があります
今回は 以下のサイト様のフォントを使用することにしました
画面下部のダウンロードからフォントをダウンロードし、中にあるいずれかの xx.ttf ファイルをUnityに配置→読み込ませる
Window > TextMeshPro > Font Asset Creator
からフォントアトラスを生成する
RenderModeはSDFAAで CharacterSetはCustomCharactersで Generate Font Atlas > 保存
生成したフォントアセットの GenerationSettings
を開いて AtlasPopulationMode
をDynamicに,そして ClearDyanmicData
にチェックを入れておきましょう
Staticのままではパフォーマンスが良いが使用する文字を自分で登録しなければならないので面倒くさい。
Dynamicの場合は実行中にフォントテクスチャを生成してくれるので楽
これでTextMeshProに作成したフォントを取り付けると日本語が表示できるようになったと思います
ScriptMachineを取り付ける
MessageBoxにScriptMachineコンポーネントを取り付けます。
今回は条件がいらないのでScriptMachine。
ScriptMachine上からテキストを書き換えるために TextMeshPro のオブジェクトをScriptMachineに教える必要があります
なので Variables
コンポーネントに Messageという名前で TextMeshPro を登録しておきます
Editor Graph
を押して実際にテキストを配置していきます。
しかし、このまま 右クリック > Add Node
から TextMeshPro
を探しても出てきません
VisualScriptingに登録されていないコンポーネントを使用するためには、TextMeshPro
コンポーネントをVisualScriptingに登録する必要があります
Edit > ProjectSettings > Visual Scripting
を開いて Node Library > +ボタン
で TextMeshPro の DLL を登録。
次に Type Options > +ボタン
で TextMeshPro UGUI を登録
TextMeshProのDLLをまずは登録して、その中にある TextMeshPro UGUI を登録します。
これで下にある Regenerate Nodes
を押すと VisualScriptingでTextMeshProが使用できるようになります
TextMeshProのテキストを設定するのに必要なのは TExtMeshProUGUI
の SetText
ユニット
全体図はこんな感じ
- OnStart と SetTextをつなげる
- GetVariable の Object から Vairables に登録した Message を取得
- MessageとSetTextをつなげる
- String ユニットを貼り付けて表示したいテキストを入力してSetTextと結びつける
以上で、実行するとVisualScriptingに配置したテキストがデフォルトで登録されてあってテキストを書き換えて表示されます
条件を入れていないため起動時に速攻書き換えて終わり。
【Unity】VisualScriptingチュートリアル3
第三回
概要
先日、Unity公式のYoutubeチャンネルで素晴らしい動画が上がっていました
今回はこちらの動画を参考に、少しシンプルにしたものを手元で再現してみようと思います
実装
キューブとカプセルが配置されている世界で、キューブを手動で動かしカプセルに衝突したときに
カプセルの上に配置されたオブジェクトがアクティブ化。
衝突から離れたときに非アクティブ化。
を実現していきます
1. GameObjectの準備
Sceneに以下のオブジェクトを配置します
- Cube : 動かす対象
- Capsule : 衝突先のオブジェクト
- Caution : カプセルの頭上に配置されたオブジェクト。衝突されたらアクティブ化される
Cautionは最初非アクティブにしておきます
衝突判定に必要なため、
- Cubeには BoxCollider と Rigidbody (UseGravityはOff)。そして判定に必要なため
Player
というタグを付ける - CapsuleにはCapsuleCollider (IsTriggerがON)
を付けておいてください
2. Cubeを動かせるようにする
キーボードの十字キーでCubeオブジェクトを前後左右動かします
public class CubeController : MonoBehaviour { void Update() { if (Input.GetKey (KeyCode.UpArrow)) { transform.Translate(0.0f, 0.0f, 0.1f); } if (Input.GetKey (KeyCode.DownArrow)) { transform.Translate(0.0f, 0.0f, -0.1f); } if (Input.GetKey (KeyCode.LeftArrow)) { transform.Translate(-0.1f, 0.0f, 0.0f); } if (Input.GetKey (KeyCode.RightArrow)) { transform.Translate(0.1f, 0.0f, 0.0f); } } } }
このような適当なクラスを作成してCubeに貼り付けちゃいましょう
3. Capsuleに衝突したことを検知するGraphを構築する
Capsule に AddComponent から 「StateMachine」を付けます
New
ボタンから適当にアセットを保存し、Edit Graph
ボタンで編集に入ります
何もない場所で
右クリック > Create Script State
を押して「接触状態の時の動き」を配置するユニットを作成します
左上のタイトルを編集し
という名前をつけました。
接触状態と非接触状態が交互に切り替わるため、お互い Make Transition
で相互につなげます
非接触状態 → 接触状態 の遷移条件
非接触状態から接触状態になるためには「キューブがカプセルに接触した」つまり OnTriggerEnter
が呼び出さればいいわけで
そのような状態を「非接触状態→接触状態」の条件に記載します (黄色の部分をダブルクリック)
- OnTriggerEnterユニットを置く
- if ユニットを置いて true と Triggeer Transition をつなげる
上記のみでは「Cube以外にあたったときも反応してしまう」ので、CompareTag
を取り付けます。
これは Tag に記載したオブジェクトのみ反応してくれるユニットです。
今回はCubeにはPlayer
というタグを付けました。
なので Compare Tag には Player
というタグで反応してもらうようにします
接触状態 → 非接触状態の遷移条件
先ほどとほぼ一緒ですが、 OnTriggerEnter
ユニットではなく OnTriggerExit
ユニットを取り付けます。
これで 「カプセルからキューブが離れたときに反応する」ようにします
これでキューブからカプセルに接触したときに 接触状態の Script State に遷移するようになりました!
接触状態のScriptStateにSetAcriveの処理を書く
これでオブジェクト同士の当たり判定で赤丸の接続状態に遷移するようになりました。
次は
「 Caution : カプセルの頭上に配置されたオブジェクト。衝突されたらアクティブ化される」
を ScriptStateに記述していきたいと思います
赤丸の接続状態をダブルクリックして...
中に
「接触中はCautionをアクティブに。非接触中はCautionを非アクティブに」
するユニットを配置していきます
その前に
Cautionオブジェクトのアクティブを切り替えるためには、Cautionオブジェクトを知っておく必要があります
カプセルオブジェクトにStateGraphを追加した時、合わせてVariablesというコンポーネントも追加されています。
簡単に説明すると、ここにオブジェクトを登録するとカプセルのStateMachineで参照できるようになる
ということです。
なので Variables に CautionCube という名前で Cautionオブジェクトをアタッチします
これでStateMachine内でCautionオブジェクトのアクティブが切り替えられるようになりました
そして次の通りにユニットを配置します
- 接触状態になった時を知るために
OnEnterState
ユニットを配置 - 非接触状態になった時を知るために
OnExitState
ユニットを配置 - それぞれ、
SetActive
ユニットを配置して、 Value に チェックあり、チェックなし版をつなげる - GetVariableユニットを配置して、今回は「Object」からCautionCubeを選択してそれぞれ
SetActive
ユニットにつなぎ合わせる
これで 接触、非接触状態になったときにSetActiveが切り替えられるようになりました。
GetVariable
はその名の通り変数、オブジェクトを取得するユニットです。
Variableで登録したオブジェクトがここで参照できるようになります。
今回はカプセルのVariableコンポーネントにCautionを登録したため、Objectから選択するようになっています
(スコープ範囲による。今はスルー)
これで実行すると、キューブがカプセルに衝突すると、カプセルの頭上のCautionオブジェクトが表示されるようになります
以上!
【Unity】VisualScriptingチュートリアル2
概要
以前の記事の続きです
「Xが4移動したらCubeが動かなる」という実装をしてみたいと思います
実装
- ScriptGraphをダブルクリップして開く
- AddNodeから
GetLocalPosition
,If
,LessOrEqual
を配置 - GetLocalPositionから GetX を配置して LessOrEqual と結びつける
- LessOrEqualに4を入れる
- 各ユニットを結びつける
(途中からの動画ですが)
各種ユニットの役目ですが
- GetLocalPosition
CubeのLocalPositionを取得するユニット
- LessOrEqual
Aの入力がBの値以下の場合 trueを返す
つまり、CubeのLocalPositionが4以上になったら If ユニットが false を返すようになるので Translate が働かなくなる
ということになります。
クライアントコードで書くと
void Update() { var pos = cube.transform.GetLocalPosition(); if (pos.x <= 4) { // 0.01動かす } }
ということをやっているわけです
この記述は、「4動かしたら止まる」ではなく「4まで動かす」ですね(結果は同じですが)
以上あっさりとしたものになりますが、これだけでも掴めるものがあると思います
【Unity】VisualScripting で "GUI Window tried to begin rendering " エラーが発生する
環境
- M1 Mac
- Unity 2021.2.0b12
- VisualScripting v1.7.3
概要
VisualScripting の ScriptGraph上で 右クリック > AddNode
を選択した際に
GUI Window tried to begin rendering while something else had not finished rendering! Either you have a recursive OnGUI rendering, or the previous OnGUI did not clean up properly.
が発生しました。
AddNode自体が失敗するので何もできない状態になりました
原因
DisplayLink を利用してデュアルディスプレイ化をして開発していたのですが、
ディスプレイ1 → UnityEditor ディスプレイ2 → State Graph Editor
を配置しているときに、StateGraphEditor上で右クリックすると発生するようでした
つまり UnityEditor本体とGraphEditorを映しているディスプレイが違う場合にエラーが発生するようです。
同じ環境で作業される方は少ないと思いますが、2時間以上手間取って見つけたエラーなので同じことで困ってる方の手助けになれば..
VisualScriptingだけではなく他のEditorWindowでも同じことになる可能性はある為心に留めて開発していく
【Unity】VisualScriptingチュートリアル1
手っ取り早く動かしてみることを目標に。
「3秒後にxを2動かして止まる」
という挙動をとってみます
前準備
まず適当なオブジェクトをシーンに作成。
このCubeに
- StateMachine コンポーネント
を取り付けます
次に今から作成するビジュアルスクリプトを保存する先を作成します
Inspecor上に New というボタンを押すと保存先を選択する流れになります
適当なフォルダに保存しちゃいましょう
このファイルはScriptableObjectとなっており、内部に情報を持ちます
編集
次に Edit Graph
を押すと実際にビジュアルスクリプトを編集する画面が表示されます
空きスペースを右クリックすると操作パネルが開きます。
- ScriptState を作成
- Startを右クリックして Make Transition
- StartからScript Stateをつなげる
Startは開始地点
ScriptStateユニットは実際の動きを行う場所、例えばオブジェクトを回転させたり、移動させたりなど。
そして途中の黄色い部分は遷移するための条件を書く場所です
つまり「3秒後にxを2動かして止まる」を分解すると
3秒後に というのは「黄色の部分」
xを2動かす というのは「ScriptStateユニット」
が担当します
まずは「xを動かす」部分を記述します
- ScriptStateユニットをダブルクリック
- なにもない部分を右クリックして検索窓に「Translate」と入力して配置
- Xに0.01を入れる
- OnUpdateユニットとTranslateユニットをつなぎ合わせる
この状態で再生しても何も起きません
開始していることをScriptStateに教えてないためです
なので次はScriptStateに問題なく遷移するための処理を追加します
- StartとScriptStateの中間にあったマークをダブルクリック
- 右クリックから「Start」といれて検索
- StartとScriptStateをつなぎ合わせる
その後PlayModeに移行するとCubeがX移動します
これはMonobehaviourで言うところの「 Startメソッドで、UpdateのたびにX移動するスクリプトをアタッチした」と同じ意味になります
次に「3秒後」という条件を追加します
- OnStartを削除してOnUpdateをつける
- if と検索して ifユニットを間に取り付ける
- GetTime と検索して 時間を取得するユニットを取り付ける
- GreaterOrEqual ユニットを取り付けて 「GetTimeした結果が3以上であれば if true を流す」ようにする
実行してグラフを見ると GetTime から値が流れ、3以上になった瞬間に TriggerTransition が起動する絵が見れます
ここまでの実装で ある条件により動きの実行を制御する 事ができました
次はすでに動いている状態から「2動いて止まる」という事後条件を実装していこうと思います
UniRx UniTaskを利用してIObservableで通知が来るまでawaitする
UniRxで作成したIObservableを UniTaskを利用してとある演出シーケンスなどの一連の流れの中で使いたい。
async UniTask 何かしらの演出() { // 演出A // UniRxから通知が来るまで待機したい // 演出B }
UniTaskはIObservableをawaitできますが、待機終了する条件は 『OnComplete が呼ばれたとき。』
元々『UniTaskにSetNextが呼ばれるまでawaitする機能があれば出来そう。』と思っていましたが、「OnCompleteが呼ばれるまで」という条件でした
であればOnCompleteが呼ばれるようにすればいいわけで
このときFirstオペレーターを利用できました
async UniTask 何かしらの演出() { // 演出A // hoge先で OnNext() が呼ばれるまで await する await hoge.FugaObservable.First(); // 演出B }
Firstははじめの一度だけ通知を呼び出し、その後OnCompleteします。 つまり OnNext が呼ばれたあと First を通ることで await が終了するということ
でした。終わり
もしとある条件の通知が来た時のみ先に進ませたいのであればWhereを併用できます
async UniTask 何かしらの演出() { // 演出A // hoge先で OnNext() が呼ばれるまで await する await hoge.FugaObservable .Where(x => x.IsOk) // IsOK がtrueの時のみ進む .First(); // 演出B }
【C#】List<Value> からDictionary<Key, List<Value>> を作る方法
あるデータ構造クラス
class Data { public HogeType Type; public int Value; }
そのクラスのリストがあるとき
List<Data> list;
このリストから
Dictionary<HogeType, List<Data>> dict
このような辞書クラスを作りたいとき。
ポイントはValue部分がListになっているところ。 つまり同じタイプを持つDataクラスをListにまとめたい。
Linqを使えばスマートにかけました
var dict = list .GroupBy(data => data.Type) .ToDictionary(group => group.Key, group => group.ToList());
listに対してGroupByでTypeでまとめてからToDictionary
あまりこういう書き方したことなかったので今更ですが。
Linq便利だなと再認識
全文
using System; using System.Linq; using System.Collections.Generic; public static class C { public enum HogeType { A, B, } class Data { public HogeType Type; public int Value; } public static void Main() { var list = new List<Data>() { new Data { Type = HogeType.A, Value = 1 }, new Data { Type = HogeType.A, Value = 2 }, new Data { Type = HogeType.B, Value = 1 }, new Data { Type = HogeType.B, Value = 2 }, new Data { Type = HogeType.B, Value = 3 }, }; var dict = list .GroupBy(data => data.Type) .ToDictionary(group => group.Key, group => group.ToList()); foreach (var pair in dict) { foreach (var data in pair.Value) { Console.WriteLine($"{pair.Key}, {data.Value}"); } } } }