【Unity】Bit演算覚書【C#】
はじめに
執筆日は20222/11/14になります。
Unityのバージョンは2021.3.1f1を使用しています。
よく忘れて調べ直すので、自分でまとめ
自己紹介
活動名はHiruko、もしくはHirukoTimeで活動してます。
個人でSteamでゲームを販売したり、定期的にゲームジャムなどに参加をしています。
基本的な処理
初期化
//0で初期化[32bit] int bit0 = 0 //[00000000000000000000000000000000] //1で初期化[32bit] int bit1 = -1 //[11111111111111111111111111111111]
左シフト
//[8]1000 var bit = 1 << 3 //ゲームなどでフラグ管理で使う場合はconstやEnumで使う const int Flag_A = 1 << 3; [System.Flags] public enum Status { None = 0, All = -1, //0001 Attack = 1 << 0, //0010 Jump = 1 << 1, //0100 Crouch = 1 << 2, //1000 Dead = 1 << 3, }
Bitを左にずらす
UnityのInspectorでは自動でEverythingが追加される。
右シフト
var bit = 256; var rightShift = bit >> 1; //[100000000]bit //-------------- //[010000000]rightShift
Bitを右にずらす。
OR演算
//左シフトの際のenum Statusを例として使う var state = (Status.Attack | Status.Jump); //0001 Attack //0010 Jump //----- //0011 stateに代入される値
どちらかのBitが1なら1、両方0なら0
[ | ]OR演算子 (縦棒、vertical bar)
[shifr] + [ ¥ ]キー([ \ ]キー)
AND演算
//左シフトの際のenum Statusを例として使う var state = (Status.Attack | Status.Jump); //[0011] state = state & Status.Attack; //0011 現在のstate値 //0001 Attack //----- //0001 計算後の値
どちらのBitも1なら1、それ以外なら0
[ & ]AND演算子
[shifr] + [ 6 ]キー
XOR演算(排他的論理和)
var a = 0b0101; var b = 0b1001; var flag = a ^ b; //0101 a //1001 b //----- //1100 計算結果 flag
ビット同士が同じなら0、違うなら1
[ ^ ]XOR演算子 (ハット、キャレット)
[ ^ ]キー ([ ¥ ]キーの左)
NOT演算(補数、反転)
//左シフトの際のenum Statusを例として使う var state = (Status.Attack | Status.Jump); //[0011] state = ~state; //0000-0000-0000-0000-0000-0000-0000-0011 現在のstate値 //----- //1111-1111-1111-1111-1111-1111-1111-1100 計算後の値
0と1を入れ替える。
全部のビットを入れ替える。
[ ~ ]NOT演算子 (チルダ)
[shifr] + [ ^ ]キー
フラグを追加する。
//左シフトの際のenum Statusを例として使う //初期化 var state = (Status.Attack | Status.Jump); //OR演算を用いる // state = (status | Status.Crouch); と同義 state |= Status.Crouch; //0011 stateの現在値 //0100 Crouch //----- //0111 計算後のstate
フラグを削除する。
//左シフトの際のenum Statusを例として使う //初期化 var state = (Status.Attack | Status.Jump); //AND演算とNOT演算を用いる。 // state = (status & ~Status.Jump); と同義 state &= ~Status.Jump; //0011 stateの現在値 //1101 ~Status.Jump //----- //0001 計算後のstate
指定のフラグが立っているか
例1
//左シフトの際のenum Statusを例として使う //初期化 var state = (Status.Attack | Status.Jump); //AND演算を用いる。 if( (state & Status.Crouch) == Status.Crouch) { //何かの処理 } //0011 stateの現在値 //0100 Crouch //----- //0000 (state & Status.Crouch) //(state & Status.Crouch)[0000] == Crouch[0100] 一致しないのでfalse
例2
var state = (Status.Attack | Status.Jump | Status.Crouch ); var CrouchAttack = (Status.Attack | Status.Crouch ); if( (state & CrouchAttack ) == CrouchAttack ) { //しゃがみ攻撃!! } //0111 state //0101 CrouchAttack //----- //0101 (state & CrouchAttack ) //(state & CrouchAttack )[0101] == CrouchAttack[0101] 一致するのでtrue
BitCount
int BitCount(int bits) { bits = (bits & 0x55555555) + (bits >> 1 & 0x55555555); bits = (bits & 0x33333333) + (bits >> 2 & 0x33333333); bits = (bits & 0x0f0f0f0f) + (bits >> 4 & 0x0f0f0f0f); bits = (bits & 0x00ff00ff) + (bits >> 8 & 0x00ff00ff); return (bits & 0x0000ffff) + (bits >>16 & 0x0000ffff); }
有名なアルゴリズム。
ログで出力する際の変換
public static string LogBit(int flag , int digit = 4 , bool isForce = false , char padding = '0') { //ToString( 変換元 , 進数指定 ) 第二引数に[2]を入れると2進数で表現する。 //※[8]8進数 [16]16進数と言う感じ //左詰めする処理:PadLeft( 桁数 , 空埋する文字 ) //PadLeft(4,'0')だと、ToStringの変換を4文字で埋めて、空の場合は左に0を埋める。 //[flag = 3]の場合[11] => [0011] //Consoleの出力で確認する場合桁が揃った方が見やすいので必要だと思う。 //digitより桁数が多い場合は表示がずれるので、Maskを掛ける。 //デバッグ上問題があるのでisForceでmaskを無視する。 var mask = !isForce ? ((1 << digit) - 1) : -1; return Convert.ToString(flag & mask, 2).PadLeft(digit,padding); //digit = 4 //[10000 = 32] 1 << digit //[01111 = 31] (1 << digit) - 1は(32) - 1 //flag & maskで下位4bit以外を全部0にする //XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX flag //0000-0000-0000-0000-0000-0000-0000-1111 mask //------------------------------------------------------- //0000-0000-0000-0000-0000-0000-0000-XXXX 計算結果 }
【Unity】GraphViewで遊んでみた。【第三回】
はじめに
この記事は第三回になります。
執筆日は20222/11/14になります。
Unityのバージョンは2021.3.1f1を使用しています。
第一回:Windowの作成方法
第二回:Nodeの作成方法
自己紹介
活動名はHiruko、もしくはHirukoTimeで活動してます。
個人でSteamでゲームを販売したり、定期的にゲームジャムなどに参加をしています。
今回のお題
グラフの保存機能を作る。
GraphViewに保存に関するものは無いので頑張って実装する。
まずは保存用のボタンを作成する。
ファイル構成
- Editor
- Node
- BaseNode.cs
- FloatNode
- TestWindow.cs
- TestGraphView.cs
- SearchMenu.cs
- Node
- Resources
- UXMLファイル
- USSファイル
- Runtime
- GraphData.cs
ブログ用でアクセサーは適当にpublic。
見た目の基本方針としては、ShaderGraphっぽくする。
アセットのダブルクリックでGraphが開く。
開いた時に自動ロード
保存と新規保存。
GraphData.cs
using System; using System.Collections.Generic; using UnityEngine; public class GraphData : ScriptableObject { public List<NodeData> Nodes = new List<NodeData>(); public List<EdgeData> Edges = new List<EdgeData>(); public void Clear() { nodeDates.Clear(); edgeDates.Clear(); } } [Serializable] public class NodeData { public string guid; public string typeName; public string parameterJson; public Rect position; public NodeData(string targetGuid,string typeFullName,string paramJson,Rect pos) { guid = targetGuid; typeName = typeFullName; parameterJson = paramJson; position = pos; } } [Serializable] public class EdgeData { public string from; public string to; public EdgeData(string fromGuid, string toGuid) { from = fromGuid; to = toGuid; } }
TestWindow.cs
public class TestWindow : GraphViewEditorWindow { [SerializeField] private GraphData data; private TestGraphView _graphView; private const string DefaultFileName = "NewGraphData"; private const string Extension = "asset"; //Attributeはなくして、アセットファイルから開くようにする。 private static void ShowWindow(GraphData data) { if (data == null) return; //呼び出した段階でOnEnableが走る var window = GetWindow<TestWindow>(data.name); window.Initialize(data); window.Show(); } //アセットを開く(ダブルクリックする)処理。 [OnOpenAsset(0)] public static bool OnOpenAsset(int instanceId, int line) { var asset = EditorUtility.InstanceIDToObject(instanceId) as GraphData; if (asset == null) return false; ShowWindow(asset); return true; } //タイミング的にOnEnableの後になる。 private void Initialize(GraphData graphData) { data = graphData; OnLoad(); } private void OnEnable() { _graphView = new TestGraphView(); _graphView.InitializeMenuWindow(this); rootVisualElement.Add(_graphView); var toolbar = new Toolbar(); toolbar.styleSheets.Add(Resources.Load<StyleSheet>("TestToolBarStyle")); var saveButton = new ToolbarButton(OnSave){text = "Save",name = "save-button"}; var saveAsButton = new ToolbarButton(CreateFile){text = "SaveAs",name = "save-as-button"}; toolbar.Add(saveButton); toolbar.Add(saveAsButton); rootVisualElement.Add(toolbar); //ここのタイミングはPlayなどでリロードが入った場合に再読み込みする。 OnLoad(); } //このタイミングでSaveしないと編集内容が破棄されて保存前のデータがリロードされる。 //今回は何もしてないが、ダイアログなどだして、注意メッセージ後に保存でもいいかもしれない private void OnDisable() { OnSave(); } private void CreateFile() { var path = EditorUtility .SaveFilePanelInProject("Save Graph Data", DefaultFileName , Extension,""); //Panelをキャンセルした時は空文字列になる。 if (string.IsNullOrEmpty(path)) return; var fileName = System.IO.Path.GetFileNameWithoutExtension(path); var newAsset = CreateInstance<GraphData>(); newAsset.name = fileName; data = newAsset; OnSave(); var oldAsset = AssetDatabase.LoadAssetAtPath<GraphData>(path); if (oldAsset == null) { AssetDatabase.CreateAsset(newAsset, path); } else { EditorUtility.CopySerializedIfDifferent(newAsset, oldAsset); AssetDatabase.SaveAssets(); } } private void OnSave() { if (data.IsUnityNull()) return; data.Clear(); foreach (var edge in _graphView.edges) { var to = edge.input.node as BaseNode; var from = edge.output.node as BaseNode; if (from == null || to == null) continue; var edgeData = new EdgeData(from.Guid, to.Guid); data.Edges.Add(edgeData); } foreach (var node in _graphView.nodes) { if (!(node is BaseNode n)) continue; var nodeData = new NodeData( n.Guid, n.GetType().FullName, n.ToJson(), n.GetPosition()); data.Nodes.Add(nodeData); } } private void OnLoad() { if (data.IsUnityNull()) return; _graphView.nodes.ForEach(x => x.RemoveFromHierarchy()); _graphView.edges.ForEach(x => x.RemoveFromHierarchy()); foreach (var n in data.Nodes) { var t = Type.GetType(n.typeName); if (t == null) continue; var instance = Activator.CreateInstance(t) as BaseNode; if (instance == null) continue; _graphView.AddElement(instance); instance.Initialize(n.guid,n.parameterJson,n.position); } var nodes = _graphView.nodes; foreach (var e in data.Edges) { var from = nodes.Select(x => x as BaseNode).FirstOrDefault(x => x?.Guid == e.from); var to = nodes.Select(x => x as BaseNode).FirstOrDefault(x => x?.Guid == e.to); if (from == null || to == null) continue; var input = to.inputContainer.Children().FirstOrDefault(x => x is Port) as Port; var output = from.outputContainer.Children().FirstOrDefault(x => x is Port) as Port; if (input == null || output == null) continue; var edge = new Edge() {input = input, output = output}; edge.input.Connect(edge); edge.output.Connect(edge); _graphView.Add(edge); } } }
TestToolBarStyle.uss
.unity-toolbar-button{ -unity-text-align: middle-center; margin: 0px 5px; }
BaseNode.cs
public abstract class BaseNode : Node { public string Guid { get; protected set; } protected BaseNode() : base() {} protected BaseNode(string uxmlPath) : base(uxmlPath){} public virtual void Initialize(string guid,string json,Rect pos) { Guid = guid; //JsonUtility.FromJson(json); SetPosition(pos); } public abstract string ToJson(); }
FloatNode.cs
public class FloatNode : BaseNode { public FloatField Value { get; private set; } public FloatNode() : base() { Guid = System.Guid.NewGuid().ToString("N"); UseDefaultStyling(); base.title = "Float"; var input = base.InstantiatePort(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi,typeof(float)); input.portName = "X(1)"; inputContainer.Add(input); var output = base.InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi,typeof(float)); output.portName = "Out(1)"; outputContainer.Add(output); Value = new FloatField(){name = "Value",label = "Value"}; extensionContainer.Add(Value); base.ToggleCollapse(); base.ToggleCollapse(); } public override string ToJson() { return JsonUtility.ToJson(Value); } public override void Initialize(string guid, string json, Rect pos) { Guid = guid; var field = JsonUtility.FromJson<FloatField>(json); Value.value = field.value; SetPosition(pos); } }
テスト動画(GIF)
一応、保存はできているが、不安が残る実装。
他にいい方法がないか検討。
変更箇所が多いので記載漏れがないか不安。
今後の予定
- ブラックボード
- ミニマップ
- 他何か思いついたら
【Unity】結構前に作ったUnityでEasingを使う時のクラス【Easing】
はじめに
執筆日は20222/11/14になります。
Unityのバージョンは2021.3.1f1を使用しています。
自己紹介
活動名はHiruko、もしくはHirukoTimeで活動してます。
個人でSteamでゲームを販売したり、定期的にゲームジャムなどに参加をしています。
参考サイト
実際のScript
Easing.cs
public static class Easing { //定数 private const float C1 = 1.70158f; private const float C2 = C1 * 1.525f; private const float C3 = C1 + 1; private const float C4 = (2 * Mathf.PI) / 3f; private const float C5 = (2 * Mathf.PI) / 4.5f; private const float N1 = 7.5625f; private const float D1 = 2.75f; //実際に使う関数 public static float Value(float current, float min, float max , EasingPattern ease = EasingPattern.Linear) { if (current <= min) return 0; if (max <= current) return 1; var diff = max - min; var time = current - min; var x = time / diff; return ease switch { EasingPattern.Linear => Linear(x), EasingPattern.EaseInSin => EaseInSin(x), EasingPattern.EaseOutSin => EaseOutSin(x), EasingPattern.EaseInOutSin => EaseInOutSin(x), EasingPattern.EaseInQuad => EaseInQuad(x), EasingPattern.EaseOutQuad => EaseOutQuad(x), EasingPattern.EaseInOutQuad => EaseInOutQuad(x), EasingPattern.EaseInCubic => EaseInCubic(x), EasingPattern.EaseOutCubic => EaseOutCubic(x), EasingPattern.EaseInOutCubic => EaseInOutCubic(x), EasingPattern.EaseInQuart => EaseInQuart(x), EasingPattern.EaseOutQuart => EaseOutQuart(x), EasingPattern.EaseInOutQuart => EaseInOutQuart(x), EasingPattern.EaseInQuint => EaseInQuint(x), EasingPattern.EaseOutQuint => EaseOutQuint(x), EasingPattern.EaseInOutQuint => EaseInOutQuint(x), EasingPattern.EaseInExpo => EaseInExpo(x), EasingPattern.EaseOutExpo => EaseOutExpo(x), EasingPattern.EaseInOutExpo => EaseInOutExpo(x), EasingPattern.EaseInCirc => EaseInCirc(x), EasingPattern.EaseOutCirc => EaseOutCirc(x), EasingPattern.EaseInOutCirc => EaseInOutCirc(x), EasingPattern.EaseInBack => EaseInBack(x), EasingPattern.EaseOutBack => EaseOutBack(x), EasingPattern.EaseInOutBack => EaseInOutBack(x), EasingPattern.EaseInBounce => EaseInBounce(x), EasingPattern.EaseOutBounce => EaseOutBounce(x), EasingPattern.EaseInOutBounce => EaseInOutBounce(x), EasingPattern.EaseInZeroOne => EaseInZeroOne(x), EasingPattern.EaseOutZeroOne => EaseOutZeroOne(x), EasingPattern.EaseInOutZeroOne => EaseInOutZeroOne(x), _ => throw new ArgumentOutOfRangeException(nameof(ease), ease, null) }; } private static float Linear(float x) => x; //EaseSign系 private static float EaseInSin(float x) => 1 - Mathf.Cos((x * Mathf.PI) / 2); private static float EaseOutSin(float x) => Mathf.Sin((x * Mathf.PI) / 2); private static float EaseInOutSin(float x) => -(Mathf.Cos(Mathf.PI * x) - 1) / 2; //EaseQuad系 private static float EaseInQuad(float x) => x * x; private static float EaseOutQuad(float x) => 1 - (1 - x) * (1 - x); private static float EaseInOutQuad(float x) => x < 0.5 ? 2 * x * x : 1 - Mathf.Pow(-2 * x + 2, 2) / 2; //EaseCubic系 private static float EaseInCubic(float x) => x * x * x; private static float EaseOutCubic(float x) => 1 - Mathf.Pow(1 - x, 3); private static float EaseInOutCubic(float x) => x < 0.5 ? 4 * x * x * x : 1 - Mathf.Pow(-2 * x + 2, 3) / 2; //EaseQuart系 private static float EaseInQuart(float x) => x * x * x * x; private static float EaseOutQuart(float x) => 1 - Mathf.Pow(1 - x, 4); private static float EaseInOutQuart(float x) => x < 0.5 ? 8 * x * x * x * x : 1 - Mathf.Pow(-2 * x + 2, 4) / 2; //EaseQuint系 private static float EaseInQuint(float x) => x * x * x * x * x; private static float EaseOutQuint(float x) => 1 - Mathf.Pow(1 - x, 5); private static float EaseInOutQuint(float x) => x < 0.5 ? 16 * x * x * x * x * x : 1 - Mathf.Pow(-2 * x + 2, 5) / 2; //EaseExpo系 private static float EaseInExpo(float x) => x == 0 ? 0 : Mathf.Pow(2, 10 * x - 10); private static float EaseOutExpo(float x) => Mathf.Approximately(x,1) ? 1 : 1 - Mathf.Pow(2, -10 * x); private static float EaseInOutExpo(float x) => x == 0 ? 0 : Mathf.Approximately(x,1) ? 1 : x < 0.5 ? Mathf.Pow(2, 20 * x - 10) / 2 : (2 - Mathf.Pow(2, -20 * x + 10)) / 2; //EaseCirc系 private static float EaseInCirc(float x) => 1 - Mathf.Sqrt(1 - Mathf.Pow(x, 2)); private static float EaseOutCirc(float x) => Mathf.Sqrt(1 - Mathf.Pow(x - 1, 2)); private static float EaseInOutCirc(float x) { return x < 0.5 ? (1 - Mathf.Sqrt(1 - Mathf.Pow(2 * x, 2))) / 2 : (Mathf.Sqrt(1 - Mathf.Pow(-2 * x + 2, 2)) + 1) / 2; } //EaseBack系 private static float EaseInBack(float x) => C3 * x * x * x - C1 * x * x; private static float EaseOutBack(float x) => 1 + C3 * Mathf.Pow(x - 1, 3) + C1 * Mathf.Pow(x - 1, 2); private static float EaseInOutBack(float x) { return x < 0.5 ? (Mathf.Pow(2 * x, 2) * ((C2 + 1) * 2 * x - C2)) / 2 : (Mathf.Pow(2 * x - 2, 2) * ((C2 + 1) * (x * 2 - 2) + C2) + 2) / 2; } // //EaseElastic系 private static float EaseInElastic(float x) => x == 0 ? 0 : Mathf.Approximately(x,1) ? 1 : -Mathf.Pow(2, 10 * x - 10) * Mathf.Sin((x * 10f - 10.75f) * C4); private static float EaseOutElastic(float x) => x == 0 ? 0 : Mathf.Approximately(x,1) ? 1 : Mathf.Pow(2, -10 * x) * Mathf.Sin((x * 10f - 0.75f) * C4) + 1; private static float EaseInOutElastic(float x) { return x == 0 ? 0 : Mathf.Approximately(x,1) ? 1 : x < 0.5 ? -(Mathf.Pow(2f, 20f * x - 10f) * Mathf.Sin((20f * x - 11.125f) * C5)) / 2f : (Mathf.Pow(2f, -20f * x + 10f) * Mathf.Sin((20f * x - 11.125f) * C5)) / 2f + 1f; } //EaseBounce系 private static float EaseInBounce(float x) => 1 - EaseOutBounce(1 - x); private static float EaseOutBounce(float x) { return x switch { < 1 / D1 => N1 * x * x, < 2 / D1 => N1 * (x -= 1.5f / D1) * x + 0.75f, < 2.5f / D1 => N1 * (x -= 2.25f / D1) * x + 0.9375f, _ => N1 * (x -= 2.625f / D1) * x + 0.984375f }; } private static float EaseInOutBounce(float x) => x < 0.5f ? (1 - EaseOutBounce(1 - 2 * x)) / 2f : (1 + EaseOutBounce(2 * x - 1)) / 2f; private static float EaseInZeroOne(float x) => 1; private static float EaseOutZeroOne(float x) => x < 1 ? 0 : 1; private static float EaseInOutZeroOne(float x) => x < 0.5f ? 0 : 1; // //Template Ease系 // public static float EaseIn(this float x) // => ; // // public static float EaseOut(this float x) // => ; // // public static float EaseInOut(this float x) // => ; }
public static class EaseExtension { public static void Ease(ref this float value ,float current, float min, float max, float pow = 1 , EasingPattern ease = EasingPattern.Linear) { value = Easing.Value(current, min, max, ease) * pow; } public static void Ease(ref this Vector3 value, float current, float min, float max , float pow = 1 , EasingPattern ease = EasingPattern.Linear) { value = Vector3.one * (Easing.Value(current, min, max, ease) * pow); } public static void EaseScale(this Transform value, float current, float min, float max, float pow = 1 , EasingPattern ease = EasingPattern.Linear) { var scale = value.localScale; scale.Ease(current, min, max, pow, ease); value.localScale = scale; } public static void EasePosition(this Transform value, float current, float min, float max, float pow = 1 , EasingPattern ease = EasingPattern.Linear) { var pos = value.position; pos.Ease(current, min, max, pow, ease); value.position = pos; } }
[Serializable] public enum EasingPattern { Linear, EaseInSin, EaseOutSin, EaseInOutSin, EaseInQuad, EaseOutQuad, EaseInOutQuad, EaseInCubic, EaseOutCubic, EaseInOutCubic, EaseInQuart, EaseOutQuart, EaseInOutQuart, EaseInQuint, EaseOutQuint, EaseInOutQuint, EaseInExpo, EaseOutExpo, EaseInOutExpo, EaseInCirc, EaseOutCirc, EaseInOutCirc, EaseInBack, EaseOutBack, EaseInOutBack, EaseInBounce, EaseOutBounce, EaseInOutBounce, EaseInElastic, EaseOutElastic, EaseInOutElastic, EaseInZeroOne, EaseOutZeroOne, EaseInOutZeroOne, }
- 価格: 5060 円
- 楽天で詳細を見る
【Unity】GraphViewで遊んでみた。【第二回】
- はじめに
- 自己紹介
- Nodeの作成
- Nodeの状態とContainerの位置の情報
- Port同士を繋げられるようにする。
- ノードを作成するメニューを表示させる。
- Nodeの形状や配置を変える。
- 少し形状に手を加えた例。
- ここまでの成果
はじめに
この記事は第二回になります。Windowの作成方法などは第一回をご覧ください。
執筆日は20222/11/09になります。
Unityのバージョンは2021.3.1f1を使用しています。
自己紹介
活動名はHiruko、もしくはHirukoTimeで活動してます。
個人でSteamでゲームを販売したり、定期的にゲームジャムなどに参加をしています。
Nodeの作成
ベースとなる抽象クラスの作成
BaseNode.cs
using UnityEditor.Experimental.GraphView; using UnityEditor.UIElements; public abstract class BaseNode : Node { //DefaultのUXMLでいい場合はこちらを使う protected BaseNode() : base() {} //OriginalのUXMLにしたい場合はこちらを使う protected BaseNode(string path) : base(path){} }
適当にShaderGraphにあるNodeを参考に作る
FloatNode.cs
using UnityEditor.Experimental.GraphView; using UnityEditor.UIElements; public class FloatNode : BaseNode { public FloatField Value { get; private set; } public FloatNode() : base() { base.title = "Float"; //Orientation:ポートからでる線(Edge)の向き。Vertical、Horizontal //Direction:ポートの入出力の向き。Input、Output //Capacity:ポートに繋げられるEdgeの数。Multiは制限なし、Singleは1つだけ //Type:ポートのType //base.InstantiatePortは //Port.Create<Edge>(orientation, direction, capacity, type)を使っても同じ //内部で同じことしてる。 var input = base.InstantiatePort( Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(float)); //Portの横に出るラベルの設定。 //Port.titleではなくPort.portNameを使う。前者はエラーが出るので注意 input.portName = "X(1)"; //Direction.InputのPortはinputContainer、 //Direction.OutputのPortはoutputContainerに入れる。 inputContainer.Add(input); var output = base.InstantiatePort(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi,typeof(float)); output.portName = "Out(1)"; outputContainer.Add(output); //一応、Floatの入力フィールドを作って置く。 //extensionContainerはパラメーターなどのVisualElementを配置するのに使うっぽい。 Value = new FloatField(){name = "Value",label = "Value"}; extensionContainer.Add(Value); //extensionContainerはデフォルトの仕様だと閉じた状態でノードが生成される。 //開いた状態にしたい場合は↓を2回呼ぶ。1回だとPortのContainerも閉じた状態になる。 //もっと綺麗な方法がありそうではあるが見つかりませんでした。 //base.ToggleCollapse(); //base.ToggleCollapse(); } }
ShaderGraphのノード 今作ったノード 比べると見た目だけでも色々違いますが今はこれで保留。
Nodeの状態とContainerの位置の情報
- Defaultの状態
- ToggleCollapse()が1回の状態(ノード上の【V】みたいなボタンを1回押した状態)
- ToggleCollapse()が2回の状態(ノード上の【V】みたいなボタンを2回押した状態)
Defaultの状態は一度開閉するとその状態にならない。
Port同士を繋げられるようにする。
現状の状態だとPort同士を繋げられないので、それを繋げられるようにする。
TestGraphView.cs
//省略 using System.Collections.Generic;//追加 using System.Linq;//追加 public class TestGraphView : GraphView { public TestGraphView(){}//前回の最後に表示したNode追加部分は消してある。 //ドラッグしてるPortと繋げられるPortを探す処理。どこからか呼ばれてる。 public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) { var compatibleParts = new List<Port>(); compatibleParts.AddRange( ports.ToList().Where(port => startPort.node != port.node && port.direction != startPort.direction && port.portType == startPort.portType)); return compatibleParts; } }
GetCompatiblePortsがどかからか呼ばれて、Portをドラッグすると繋げられるようになります。
ノードを作成するメニューを表示させる。
メニュー作成するScripatableObjectを作成する。
SearchMenu.cs
using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.Experimental.GraphView; using UnityEngine; using UnityEngine.UIElements; public class SearchMenu : ScriptableObject , ISearchWindowProvider { private GraphView _graphView; private EditorWindow _window; private readonly List<SearchTreeEntry> _entries = new List<SearchTreeEntry>(); public void Initialize(GraphView graphView, EditorWindow editorWindow, params Type[] notInserts) { _graphView = graphView; _window = editorWindow; _entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node"))); _entries.Add(new SearchTreeGroupEntry(new GUIContent("Input")){level = 1}); _entries.Add(new SearchTreeEntry(new GUIContent(nameof(FloatNode))) { level = 2, userData = typeof(FloatNode) }); } public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context) => _entries; public bool OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context) { var type = searchTreeEntry.userData as Type; if (type == null || !(Activator.CreateInstance(type) is BaseNode node)) return false; var worldMousePos = _window.rootVisualElement.ChangeCoordinatesTo( _window.rootVisualElement.parent, context.screenMousePosition - _window.position.position); var localMousePos = _graphView.contentViewContainer.WorldToLocal(worldMousePos); node.SetPosition(new Rect(localMousePos, new Vector2(100, 100))); _graphView.AddElement(node); return true; } }
ScriptableObjectである必要性に疑問を感じたが、呼び出しにScriptableObjectであることが必要っぽい。
TestGraphView.cs
//省略 using UnityEditor;//追加 public class TestGraphView : GraphView { public TestGraphView(){}//省略 public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter){}//省略 //追加 public void InitializeMenuWindow(EditorWindow window) { var menuWindow = ScriptableObject.CreateInstance<SearchMenu>(); menuWindow.Initialize(this, window ); nodeCreationRequest += context => { SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), menuWindow); }; } }
TestWindow.cs
//省略 public class TestWindow : GraphViewEditorWindow { [MenuItem("Window/Test")] private static void ShowWindow(){}//省略 private void OnEnable() { var graphView = new TestGraphView(); graphView.InitializeMenuWindow(this);//追加 rootVisualElement.Add(graphView); } }
entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node")))
右クリックメニューの一番上にCreate Nodeが追加される
entries.Add(new SearchTreeGroupEntry(new GUIContent("Input")){level = 1});
右クリック>[Create Node]でこのメニューが出る。
_entries.Add(new SearchTreeEntry(new GUIContent(nameof(FloatNode)))
{ level = 2, userData = typeof(FloatNode) });
右クリック>[Create Node]>[Input]でこのメニュー。
FloatNodeをクリックすると、FloatNodeを生成できる。
各項目のlevelは適切に設定しないとその項目が行方不明になるので注意。
Nodeの形状や配置を変える。
元になるNodeのConstructorで以下のUXML要素が呼ばれているので、独自のUXMLを適用する場合は最低限必要になる。
例:"UXML内Name"[変数名]
"node-border" [mainContainer] public get ; private set;
"selection-border" [変数名無し]
"input" [inputContainer] public get ; private set;
"output" [outputContainer] public get ; private set;
"collapsible-area" [m_CollapsibleArea] private
"extension" [exntensionContainer] public get ; private set;
"title" [titleContainer] public get ; private set;
"title-label" [m_TitleLabel] private
"title-button-container" [titleButtonContainer]public get ; private set;
"collapse-button" [m_CollapseButton]private
MyNode.uxml(デフォルトの構成)
<?xml version="1.0" encoding="utf-8"?> <engine:UXML xmlns:engine="UnityEngine.UIElements" xmlns:ui="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" > <ui:VisualElement name="node-border"> <ui:VisualElement name="title" > <ui:Label name="title-label" /> <ui:VisualElement name="title-button-container"> <ui:VisualElement name="collapse-button"> <ui:VisualElement name="icon" /> </ui:VisualElement> </ui:VisualElement> </ui:VisualElement> <ui:VisualElement name="contents"> <ui:VisualElement name="divider" class="horizontal" /> <ui:VisualElement name="top"> <ui:VisualElement name="input" /> <ui:VisualElement name="divider" class="vertical" /> <ui:VisualElement name="output" /> </ui:VisualElement> <ui:VisualElement name="collapsible-area"> <ui:VisualElement name="divider" class="horizontal" /> <ui:VisualElement name="extension"/> </ui:VisualElement> </ui:VisualElement> </ui:VisualElement> <ui:VisualElement name="selection-border" picking-mode="Ignore"/> </engine:UXML>
MyUniqueNode.cs
using UnityEditor.Experimental.GraphView; using UnityEditor.UIElements; public class MyUniqueNode : BaseNode { public MyUniqueNode() : base("Assets/GraphViewTest/Resources/MyNode.uxml") { //Defaultのスタイルを設定する //"selection-border"の色が変わるStyleとなどが含まれるため、 //適用しないと選択時に色が変わらなくなる。 UseDefaultStyling(); //なんか独自の要素とか処理 } }
少し形状に手を加えた例。
ここまでの成果
ScriptのリロードやEditorのPlayをするGraphがなくなる(保存されてない)
右クリック>[Delete]やDeleteキーから削除はできるが、Paste、Duplicateはできない。
第三回の予定
Graphの保存
【Unity】GraphViewで遊んでみた。【第一回】
はじめに
執筆日は20222/11/09になります。
Unityのバージョンは2021.3.1f1を使用しています。
自己紹介
活動名はHiruko、もしくはHirukoTimeで活動してます。
個人でSteamでゲームを販売したり、定期的にゲームジャムなどに参加をしています。
GraphViewについて
ShaderGraphみたいな機能を自作できるAPIです。
Runtimeな使用するAPIではなくEditor拡張のAPIです。
あくまで、データなどを作成する視覚的なツールを作るためのものになります。
現在はExperimentalなので、破壊的な変更が来る可能性はありますが、内部的にShaderGraphやVFXGraphなどで使用されているみたいなのでなくなることはないと思います。
GraphViewの要素について
主な要素
- GraphView
- Node
- Port
- Edge
補助、拡張的な要素
- Blackboard
- MiniMap
- GraphViewToolWindowなど
知っておいた方がいい要素
- USS
- UXML
- UIElements
- Editor拡張系のAPIやワード
実際にいじってみる
まずはWindowの作成
TestWindow.cs
using UnityEditor; using UnityEditor.Experimental.GraphView; public class TestWindow : GraphViewEditorWindow { [MenuItem("Window/Test")] private static void ShowWindow() { var window = GetWindow<TestWindow>("TestWindow"); window.Show(); } private void OnEnable() { rootVisualElement.Add(new BaseGraphView()); } }
今回はEditorWindowを直接継承せず、試したいこともあったのでGraphViewEditorWindowを継承します。GraphViewEditorWindowがEditorWindowを継承しています。
TestGraphView.cs
using UnityEditor.Experimental.GraphView; using UnityEngine; using UnityEngine.UIElements; public class TestGraphView : GraphView { public TestGraphView() { //形状や内容の設定 SetupZoom(ContentZoomer.DefaultMinScale,ContentZoomer.DefaultMaxScale); this.StretchToParentSize(); var backGround = new GridBackground(); backGround.styleSheets.Add(Resources.Load<StyleSheet>("BackGround")); Insert(0,backGround); //操作を受け付けるようにする為 this.AddManipulator(new ContentDragger()); this.AddManipulator(new SelectionDragger()); this.AddManipulator(new RectangleSelector()); } }
SetupZoom()はマウスホイールでのズームの設定 StretchToParentSize()はそのままの意味で親のサイズに合わせてくれる。 GridBackGroundはParameterの設定が面倒なためUSSを使用した方が楽。またGridBackGroundは内部でStretchToParentSize()を呼び出しているので、使用する時にしなくても大丈夫。
BackGround.uss
GridBackground { --grid-background-color:rgb(90,90,90); --line-color:rgba(80,80,80,255); --thick-line-color:rgba(40,40,40,153); --spacing:10; /*--thick-lines: 0 このパラメータは存在するが変更しても変化がない。*/ }
ちょっと躓いた点としては、UI Toolkit Debugger上での表示はrgba(float,float,float,float)のfloat[0-1]になっているが、USSで指定する場合はParameterをrgba(int,int,int,int)でint[0-255]で指定する。
ここまでの段階で上部のメニューから[Window>Test]でウィンドウを表示すると↑の感じになる。 この段階で、ドラッグででグリッドを動かしたり、ホイールでズームや右クリックのメニューがでるようになります。
次はNodeを表示してみる。
TestGraphView.cs
//省略 public class TestGraphView : GraphView { public TestGraphView() { //省略 AddElement(new Node()); } }
とりあえず表示するだけならこれで表示される。
注意点としては、GraphViewにはGraphView.AddとGraphView.AddElementがあるが、NodeとかのGraph要素はAddElement。GridBackgroundの様な要素はAddと使い分ける必要がある。
何も指定せず生成すると左上にNodeがでる。
この状態でドラッグで動かしたり、右クリックのメニューからDeleteしたり、コピーしたりできる。
試しに表示しただけなので、AddElement(new Node());の部分は削除する。
第二回の予定
NodeにPortやParameterを配置する。
動的なNodeの作成。
【Unity】A* Pathfinding Project Pro 試してみた
・GridGraphのグリッド間の障害物で接続のブロック
グリッド配置のSRPG系のゲームで障害物をグリッド上ではなく、グリッドとグリッドの間に障害物を配置して、その間の接続をブロックする方法。
基本的にアセットの仕様として、移動阻害をする場合はグリッド自体を無効にする処理になるので、障害物としてオブジェクトを配置をする場合は、このようにグリッド(Node)自体が無効になる。
※赤い四角は無効になってるNodeの表示。緑のラインはNode-Nodeの接続
こんな感じでNodeは無効にせずに、接続だけをなくしたい時の方法。
公式のドキュメントにはやり方が見つからなかったのですが、フォーラムの方を探していたら数年前のスレッドだが、参考になるスレッドがありました。
スレッド自体がかなり前なので、修正すべきスクリプトの位置が大分違ったので苦労しました。
GridGenerator.cs/Class::GridGraph
public virtual bool IsValidConnection (GridNodeBase node1, GridNodeBase node2) { if (!node1.Walkable || !node2.Walkable) { return false; } //追加部分↓ if (Physics.Linecast((Vector3) node1.position, (Vector3) node2.position)) { return false; } //追加部分↑ if (maxClimb <= 0 || collision.use2D) return true; //変更がないので省略 }
Graph生成時?Scan時?にNode間にLinecastし、障害物がある場合はConnectionを生成しないようにしている。実際に使用する場合はLinecastの第三引数にLayermaskを設定した方がいい。
GridGraphクラスは専用のEditorScriptが存在するのでLayermask変数を用意してInspectorから設定したい場合は
Assets/AstarPathfindingProject/Editor/GraphEditors/GridGeneratorEditor.cs
を編集する必要があるので少し手間がかかる。
- 価格: 5060 円
- 楽天で詳細を見る