6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】GUIで簡単なグラフを表示する

Last updated at Posted at 2024-07-30

エディタ拡張で簡単なグラフを描画する方法に関するメモ.

image.png

背景の描画

Handles.DrawSolidRectangleWithOutline()で背景と枠線をまとめて表示できる.(※GUI.GroupScopeを使用する場合は,そちらでGUIStyleを渡して背景描画することも)

デモコード
DemoEditorWindow.cs
// デモ表示用のエディタウインドウ
public class DemoEditorWindow : EditorWindow {

        private void OnGUI() {
            EditorGUILayout.Space();
            GUILayout.Label("-------------");
            { // グラフ描画
                var rect = GUILayoutUtility.GetRect(300, 200).Padding(10);
                Handles.DrawSolidRectangleWithOutline(rect, Colors.backdropColor, Colors.gridLineColor);
            }
            GUILayout.Label("-------------");
        }

        /// <summary>
        /// 背景と枠線を描画する
        /// </summary>
        public void DrawBackDrop(Rect rect, Color backdropColor, Color outlineColor) {
            Handles.DrawSolidRectangleWithOutline(rect, backdropColor, outlineColor);
        }

        private static class Colors {
            public static readonly Color backdropColor = new Color(0.8f, 0.8f, 0.8f, 0.1f);
            public static readonly Color gridLineColor = new Color(0.9f, 0.9f, 0.9f, 0.5f);
        }
    }

【結果】

image.png

グリッド線の描画

Handles.Line()でGUIに線を描く場合、EditorWindow左上を原点とした座標を渡す必要がある.そのためグラフ範囲のRect内に描画するように変換を行っている.

デモコード
DemoEditorWindow.cs
        private void OnGUI() {
			EditorGUILayout.Space();
			GUILayout.Label("-------------");
			{ // グラフ描画
				var settings = new AxisSettings(xRange: (0, 10), yRange: (0, 10), xStep: 1, yStep: 2);

				var rect = GUILayoutUtility.GetRect(300, 200).Padding(10);
				DrawBackDrop(rect, Colors.backdropColor, Colors.gridLineColor);
				DrawGrid(rect, settings, Colors.gridLineColor);
			}
			GUILayout.Label("-------------");
		}

        public void DrawGrid(Rect rect, AxisSettings settings, Color gridColor) {
			using (new Handles.DrawingScope(gridColor)) {
				var graphOrigin = rect.position;

				// X軸
				var xStart = Mathf.Ceil(settings.xRange.min / settings.xStep) * settings.xStep;
				var xTicks = EnumerableUtil.LinspaceWithStep(xStart, settings.xRange.max, settings.xStep);
				foreach (var x in xTicks) {
					var value1 = new Vector2(x, settings.yRange.min);
					var value2 = new Vector2(x, settings.yRange.max);

					// GUI座標に変換して描画
					var p1 = Convertor.ValueToGraphPoint(value1, rect.size, settings) + graphOrigin;
					var p2 = Convertor.ValueToGraphPoint(value2, rect.size, settings) + graphOrigin;
					Handles.DrawLine(p1, p2);
				}

				// Y軸
				var yStart = Mathf.Ceil(settings.yRange.min / settings.yStep) * settings.yStep;
				var yTicks = EnumerableUtil.LinspaceWithStep(yStart, settings.yRange.max, settings.yStep);
				foreach (var y in yTicks) {
					var value1 = new Vector2(settings.xRange.min, y);
					var value2 = new Vector2(settings.xRange.max, y);

					// GUI座標に変換して描画
					var p1 = Convertor.ValueToGraphPoint(value1, rect.size, settings) + graphOrigin;
					var p2 = Convertor.ValueToGraphPoint(value2, rect.size, settings) + graphOrigin;
					Handles.DrawLine(p1, p2);
				}
			}
		}

        public struct AxisSettings {
			// グラフの値域
			public (float min, float max) xRange;
			public (float min, float max) yRange;

			// グリッド幅
			public float xStep;
			public float yStep;

			public AxisSettings((float min, float max) xRange, (float min, float max) yRange, float xStep, float yStep) {
				this.xRange = xRange;
				this.yRange = yRange;
				this.xStep = xStep;
				this.yStep = yStep;
			}
		}

		private static class Convertor {

			// グラフの値域
			public static (float min, float max) xRange = (0, 10);
			public static (float min, float max) yRange = (0, 10);

			/// <summary>
			/// 
			/// </summary>
			public static Vector2 ValueToGraphPoint(Vector2 value, Vector2 size, AxisSettings settings) {
				var xRange = settings.xRange;
				var yRange = settings.yRange;
				float x = Mathf.Lerp(0, size.x, (value.x - xRange.min) / (xRange.max - xRange.min));
				float y = Mathf.Lerp(size.y, 0, (value.y - yRange.min) / (yRange.max - yRange.min));
				return new Vector2(x, y);
			}

		}
util.cs
Util.cs
 public static class EnumerableUtil {

        /// <summary>
        /// ステップサイズで線形に配置されたデータを作成する
        /// </summary>
        public static IEnumerable<float> LinspaceWithStep(float start, float end, float step) {
            if (step <= 0) throw new InvalidOperationException("Step must be positive and non-zero.");

            int count = (int)((end - start) / step) + 1;
            return Enumerable
                .Range(0, count)
                .Select(i => start + i * step)
                .TakeWhile(value => value <= end);
        }
}

【結果】

image.png

データの描画

GUI.ClipScope、GUI.GroupScopeのスコープ内では指定したRectのpositionを基準とした相対座標でGUIメソッドを実行できる.また描画される図形も指定Rect内に制限される(Rectからはみ出た部分は描画されない).

デモコード
DemoEditorWindow.cs
        private void OnGUI() {
			EditorGUILayout.Space();
			GUILayout.Label("-------------");
			{ // グラフ描画
				var settings = new AxisSettings(xRange: (0, 10), yRange: (0, 10), xStep: 1, yStep: 2);
				var dummyData = EnumerableUtil.LinspaceWithStep(-2, 12, 0.5f)
					.Select(x => new Vector2(x, Mathf.Sin(x) + x))
					.ToArray();

				var rect = GUILayoutUtility.GetRect(300, 200).Padding(10);
				DrawBackDrop(rect, Colors.backdropColor, Colors.gridLineColor);
				DrawGrid(rect, settings, Colors.gridLineColor);
				DrawData(rect, settings, dummyData, Colors.dataColor);
			}
			GUILayout.Label("-------------");
		}

		/// <summary>
		/// 点列を描画する
		/// </summary>
		public void DrawData(Rect rect, AxisSettings settings, Vector2[] values, Color color) {
			using (new GUI.ClipScope(rect)) // ※スコープ内のGUIメソッドは指定Rectを基準として処理される
			using (new Handles.DrawingScope(color)) {
				for (int i = 0; i < values.Length - 1; i++) {
					Vector2 p1 = Convertor.ValueToGraphPoint(values[i], rect.size, settings);
					Vector2 p2 = Convertor.ValueToGraphPoint(values[i + 1], rect.size, settings);
					Handles.DrawLine(p1, p2);
				}

				foreach (var value in values) {
					Vector2 p = Convertor.ValueToGraphPoint(value, rect.size, settings);
					Handles.DrawSolidDisc(p, Vector3.forward, 2f);
				}
			}
		}

【結果】

image.png

参考

https://tips.hecomi.com/entry/2014/06/23/222805
https://qiita.com/hinagawa/items/4388f2f61cb33b9975e8

デモコード全文
.cs
#if UNITY_EDITOR
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

namespace nitou.EditorScripts {

	// デモ表示用のエディタウインドウ
	public class DemoEditorWindow : EditorWindow {

		[MenuItem(MenuItemName.Prefix.Develop + "Test/Demo Graph")]

		public static void Open() => GetWindow<DemoEditorWindow>("Demo Graph");

		private void OnGUI() {
			EditorGUILayout.Space();
			GUILayout.Label("-------------");
			{ // グラフ描画
				var settings = new AxisSettings(xRange: (0, 10), yRange: (0, 10), xStep: 1, yStep: 2);
				var dummyData = EnumerableUtil.LinspaceWithStep(-2, 12, 0.5f)
					.Select(x => new Vector2(x, Mathf.Sin(x) + x))
					.ToArray();

				var rect = GUILayoutUtility.GetRect(300, 200).Padding(10);
				DrawBackDrop(rect, Colors.backdropColor, Colors.gridLineColor);
				DrawGrid(rect, settings, Colors.gridLineColor);
				DrawData(rect, settings, dummyData, Colors.dataColor);
			}
			GUILayout.Label("-------------");
		}

		/// <summary>
		/// 点列を描画する
		/// </summary>
		public void DrawData(Rect rect, AxisSettings settings, Vector2[] values, Color color) {
			using (new GUI.ClipScope(rect)) // ※スコープ内のGUIメソッドは指定Rectを基準として処理される
			using (new Handles.DrawingScope(color)) {
				for (int i = 0; i < values.Length - 1; i++) {
					Vector2 p1 = Convertor.ValueToGraphPoint(values[i], rect.size, settings);
					Vector2 p2 = Convertor.ValueToGraphPoint(values[i + 1], rect.size, settings);
					Handles.DrawLine(p1, p2);
				}

				foreach (var value in values) {
					Vector2 p = Convertor.ValueToGraphPoint(value, rect.size, settings);
					Handles.DrawSolidDisc(p, Vector3.forward, 2f);
				}
			}
		}


		public void DrawGrid(Rect rect, AxisSettings settings, Color gridColor) {
			using (new Handles.DrawingScope(gridColor)) {
				var graphOrigin = rect.position;

				// X軸
				var xStart = Mathf.Ceil(settings.xRange.min / settings.xStep) * settings.xStep;
				var xTicks = EnumerableUtil.LinspaceWithStep(xStart, settings.xRange.max, settings.xStep);
				foreach (var x in xTicks) {
					var value1 = new Vector2(x, settings.yRange.min);
					var value2 = new Vector2(x, settings.yRange.max);

					// GUI座標に変換して描画
					var p1 = Convertor.ValueToGraphPoint(value1, rect.size, settings) + graphOrigin;
					var p2 = Convertor.ValueToGraphPoint(value2, rect.size, settings) + graphOrigin;
					Handles.DrawLine(p1, p2);
				}

				// Y軸
				var yStart = Mathf.Ceil(settings.yRange.min / settings.yStep) * settings.yStep;
				var yTicks = EnumerableUtil.LinspaceWithStep(yStart, settings.yRange.max, settings.yStep);
				foreach (var y in yTicks) {
					var value1 = new Vector2(settings.xRange.min, y);
					var value2 = new Vector2(settings.xRange.max, y);

					// GUI座標に変換して描画
					var p1 = Convertor.ValueToGraphPoint(value1, rect.size, settings) + graphOrigin;
					var p2 = Convertor.ValueToGraphPoint(value2, rect.size, settings) + graphOrigin;
					Handles.DrawLine(p1, p2);
				}
			}
		}


		/// <summary>
		/// 背景と枠線を描画する
		/// </summary>
		public void DrawBackDrop(Rect rect, Color backdropColor, Color outlineColor) {
			Handles.DrawSolidRectangleWithOutline(rect, backdropColor, outlineColor);
		}

		private static class Colors {
			public static readonly Color backdropColor = new Color(0.8f, 0.8f, 0.8f, 0.1f);
			public static readonly Color gridLineColor = new Color(0.9f, 0.9f, 0.9f, 0.5f);
			public static readonly Color dataColor = Color.green;
		}


		public struct AxisSettings {
			// グラフの値域
			public (float min, float max) xRange;
			public (float min, float max) yRange;

			// グリッド幅
			public float xStep;
			public float yStep;

			public AxisSettings((float min, float max) xRange, (float min, float max) yRange, float xStep, float yStep) {
				this.xRange = xRange;
				this.yRange = yRange;
				this.xStep = xStep;
				this.yStep = yStep;
			}
		}

		private static class Convertor {
			/// <summary>
			/// グラフ範囲の座標に変換する
			/// </summary>
			public static Vector2 ValueToGraphPoint(Vector2 value, Vector2 size, AxisSettings settings) {
				var xRange = settings.xRange;
				var yRange = settings.yRange;
				float x = Mathf.Lerp(0, size.x, (value.x - xRange.min) / (xRange.max - xRange.min));
				float y = Mathf.Lerp(size.y, 0, (value.y - yRange.min) / (yRange.max - yRange.min));
				return new Vector2(x, y);
			}

		}
	}
}
#endif
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?