Created
November 15, 2011 08:25
-
-
Save N-Carter/1366457 to your computer and use it in GitHub Desktop.
Custom inspector for Path objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEditor; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
[CustomEditor(typeof(Path))] | |
public class PathEditor : Editor | |
{ | |
protected Path m_Path; | |
protected Vector2 m_ScrollPosition; | |
protected HashSet<Path.Waypoint> m_SelectedWaypoints; | |
protected Path.Waypoint m_WaypointProperties; // FIXME: for multiple selection editing, if I get there | |
protected GUISkin m_InspectorSkin; | |
protected GUISkin m_SceneSkin; | |
protected const int kWaypointHandleSize = 16; | |
protected Color selectionColour = new Color(0.5f, 0.7f, 1.0f, 1.0f); | |
protected Texture m_EventTexture; | |
[MenuItem("Toolkit/Path/Create Path")] | |
protected static void CreatePath() | |
{ | |
new GameObject("Path").AddComponent<Path>(); | |
} | |
protected void OnEnable() | |
{ | |
m_Path = (Path)target; | |
m_InspectorSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector); | |
m_SceneSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene); | |
m_EventTexture = (Texture2D)EditorGUIUtility.Load("Path Editor/Textures/Path Event.tif"); | |
// m_EventTexture = (Texture2D)EditorGUIUtility.FindTexture("Path Event.tif"); | |
m_SelectedWaypoints = new HashSet<Path.Waypoint>(); | |
Repaint(); | |
} | |
#region Inspector GUI | |
public override void OnInspectorGUI() | |
{ | |
if(m_Path.m_Waypoints.Count == 0) | |
m_Path.m_Waypoints.Add(new Path.Waypoint("", m_Path.transform.position)); | |
// base.OnInspectorGUI(); | |
EditorGUIUtility.LookLikeControls(); | |
m_Path.m_Loop = EditorGUILayout.Toggle("Loop", m_Path.m_Loop); | |
m_Path.m_Subdivisions = EditorGUILayout.IntField("Subdivisions", m_Path.m_Subdivisions); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
EditorGUILayout.PrefixLabel("Next path"); | |
m_Path.m_NextPath = (Path)EditorGUILayout.ObjectField(m_Path.m_NextPath, typeof(Path), true, GUILayout.Height(22)); | |
} | |
m_Path.m_Colour = EditorGUILayout.ColorField("Colour", m_Path.m_Colour); | |
m_Path.m_ShowVertices = EditorGUILayout.Toggle("Show vertices", m_Path.m_ShowVertices); | |
m_Path.m_ProjectedDownwards = EditorGUILayout.Toggle("Project down", m_Path.m_ProjectedDownwards); | |
// Waypoint list: | |
EditorGUILayout.Separator(); | |
GUILayout.Label("Waypoints", EditorStyles.boldLabel); | |
SelectionButtons(); | |
int numSelectedWaypoints = m_SelectedWaypoints.Count; | |
if(numSelectedWaypoints == 0) | |
{ | |
GUILayout.Label("Nothing selected.", "Box", GUILayout.ExpandWidth(true)); | |
AlignButtons("Align all waypoints"); | |
} | |
else if(numSelectedWaypoints == 1) | |
WaypointGUI(m_SelectedWaypoints.First()); | |
else | |
{ | |
// FIXME: add support for setting properties of multiple waypoints. | |
MultipleWaypointsGUI(); | |
AlignButtons("Align selected waypoints"); | |
} | |
// Event list: | |
EditorGUILayout.Separator(); | |
GUILayout.Label("Events", EditorStyles.boldLabel); | |
EventScrollViewGUI(); | |
// Debugging log: | |
// if(GUILayout.Button("Dump Data to Log")) | |
// { | |
// System.Console.WriteLine("Path {0}", path.name); | |
// | |
// foreach(Path.Segment segment in m_Path) | |
// { | |
// System.Console.WriteLine("{4}: {0} -> {1} ({2}) - diff {3}", segment.vertex, segment.nextVertex, | |
// (segment.isCurve ? "Curve" : "Line"), segment.nextVertex - segment.vertex, segment.t); | |
// } | |
// } | |
if(GUI.changed) | |
EditorUtility.SetDirty(m_Path); | |
} | |
protected void SelectionButtons() | |
{ | |
GUILayout.Label("Select"); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
// Annoyingly, you have to call SetDirty on the path, otherwise the scene view doesn't get redrawn. That means | |
// it keeps setting the "scene needs saving" flag. | |
if(GUILayout.Button("All")) | |
{ | |
SelectAllWaypoints(); | |
SceneView.RepaintAll(); | |
} | |
if(GUILayout.Button("None")) | |
{ | |
DeselectAllWaypoints(); | |
SceneView.RepaintAll(); | |
} | |
if(GUILayout.Button("First")) | |
{ | |
DeselectAllWaypoints(); | |
m_SelectedWaypoints.Add(m_Path.m_Waypoints.First()); | |
SceneView.RepaintAll(); | |
} | |
if(GUILayout.Button("Last")) | |
{ | |
DeselectAllWaypoints(); | |
m_SelectedWaypoints.Add(m_Path.m_Waypoints.Last()); | |
SceneView.RepaintAll(); | |
} | |
GUI.enabled = (m_SelectedWaypoints.Count == 1); | |
if(GUILayout.Button("<-")) | |
{ | |
SelectAdjacentWaypoint(Direction.Previous); | |
SceneView.RepaintAll(); | |
} | |
if(GUILayout.Button("->")) | |
{ | |
SelectAdjacentWaypoint(Direction.Next); | |
SceneView.RepaintAll(); | |
} | |
GUI.enabled = true; | |
} | |
} | |
protected void SelectAllWaypoints() | |
{ | |
m_SelectedWaypoints.Clear(); | |
m_Path.m_Waypoints.ForEach(waypoint => m_SelectedWaypoints.Add(waypoint)); | |
} | |
protected void DeselectAllWaypoints() | |
{ | |
m_SelectedWaypoints.Clear(); | |
m_WaypointProperties = null; | |
} | |
protected void SelectWaypoint(Path.Waypoint waypoint) | |
{ | |
m_SelectedWaypoints.Add(waypoint); | |
} | |
protected void DeselectWaypoint(Path.Waypoint waypoint) | |
{ | |
m_SelectedWaypoints.Remove(waypoint); | |
} | |
protected enum Direction {Previous = -1, Next = 1} | |
protected void SelectAdjacentWaypoint(Direction direction) | |
{ | |
int index = FindWaypointIndex(m_SelectedWaypoints.First()); | |
m_SelectedWaypoints.Clear(); | |
int numWaypoints = m_Path.m_Waypoints.Count; | |
m_SelectedWaypoints.Add(m_Path.m_Waypoints[(index + (int)direction + numWaypoints) % numWaypoints]); | |
} | |
protected void AlignButtons(string label) | |
{ | |
GUILayout.Label(label); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
if(GUILayout.Button("X")) | |
AlignWaypoints(0); | |
if(GUILayout.Button("Y")) | |
AlignWaypoints(1); | |
if(GUILayout.Button("Z")) | |
AlignWaypoints(2); | |
} | |
} | |
protected void WaypointGUI(Path.Waypoint waypoint) | |
{ | |
m_WaypointProperties = null; | |
using(new EditorGUIGroupVertical("Box")) | |
{ | |
waypoint.name = EditorGUILayout.TextField("Name", waypoint.name); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
EditorGUILayout.PrefixLabel("Curve factors"); | |
waypoint.factorBefore = GUILayout.HorizontalSlider(waypoint.factorBefore, 0.0f, 0.5f); | |
waypoint.factorBefore = Mathf.Clamp(EditorGUILayout.FloatField(waypoint.factorBefore, GUILayout.Width(37)), 0.0f, 0.5f); | |
waypoint.factorAfter = GUILayout.HorizontalSlider(waypoint.factorAfter, 0.0f, 0.5f); | |
waypoint.factorAfter = Mathf.Clamp(EditorGUILayout.FloatField(waypoint.factorAfter, GUILayout.Width(37)), 0.0f, 0.5f); | |
} | |
EditorGUILayout.Separator(); | |
waypoint.position = EditorGUILayout.Vector3Field("Position", waypoint.position); | |
EditorGUILayout.Separator(); | |
// Insert and remove: | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
if(GUILayout.Button("Insert Waypoint After")) | |
InsertWaypointAfter(FindWaypointIndex(waypoint)); | |
GUI.enabled = (m_Path.m_Waypoints.Count > 1); | |
if(GUILayout.Button("Remove Waypoint")) | |
RemoveWaypoint(FindWaypointIndex(waypoint)); | |
GUI.enabled = true; | |
} | |
} | |
} | |
protected void MultipleWaypointsGUI() | |
{ | |
if(m_WaypointProperties == null) | |
m_WaypointProperties = new Path.Waypoint(); | |
using(new EditorGUIGroupVertical("Box")) | |
{ | |
GUILayout.Label(string.Format("{0} waypoints selected.", m_SelectedWaypoints.Count), GUILayout.ExpandWidth(true)); | |
EditorGUILayout.Separator(); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
m_WaypointProperties.name = EditorGUILayout.TextField("Name", m_WaypointProperties.name); | |
if(GUILayout.Button("Apply", GUILayout.Width(50))) | |
{ | |
foreach(var waypoint in m_SelectedWaypoints) | |
waypoint.name = m_WaypointProperties.name; | |
SceneView.RepaintAll(); | |
} | |
} | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
EditorGUILayout.PrefixLabel("Factor before"); | |
m_WaypointProperties.factorBefore = GUILayout.HorizontalSlider(m_WaypointProperties.factorBefore, 0.0f, 0.5f); | |
m_WaypointProperties.factorBefore = Mathf.Clamp(EditorGUILayout.FloatField(m_WaypointProperties.factorBefore, GUILayout.Width(37)), 0.0f, 0.5f); | |
if(GUILayout.Button("Apply", GUILayout.Width(50))) | |
{ | |
foreach(var waypoint in m_SelectedWaypoints) | |
waypoint.factorBefore = m_WaypointProperties.factorBefore; | |
SceneView.RepaintAll(); | |
} | |
} | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
EditorGUILayout.PrefixLabel("Factor after"); | |
m_WaypointProperties.factorAfter = GUILayout.HorizontalSlider(m_WaypointProperties.factorAfter, 0.0f, 0.5f); | |
m_WaypointProperties.factorAfter = Mathf.Clamp(EditorGUILayout.FloatField(m_WaypointProperties.factorAfter, GUILayout.Width(37)), 0.0f, 0.5f); | |
if(GUILayout.Button("Apply", GUILayout.Width(50))) | |
{ | |
foreach(var waypoint in m_SelectedWaypoints) | |
waypoint.factorAfter = m_WaypointProperties.factorAfter; | |
SceneView.RepaintAll(); | |
} | |
} | |
if(GUILayout.Button("Remove Waypoints")) | |
RemoveSelectedWaypoints(); | |
} | |
} | |
protected void EventScrollViewGUI() | |
{ | |
Path.Event selectedPathEvent = null; | |
int numSelectedPathEvents = 0; | |
float rowHeight = GUI.skin.textArea.CalcHeight(new GUIContent(""), 100) + GUI.skin.textField.margin.vertical; | |
var minHeight = GUILayout.MinHeight(Mathf.Min(m_Path.m_Events.Count, 4) * rowHeight); | |
m_ScrollPosition = EditorGUILayout.BeginScrollView(m_ScrollPosition, minHeight, GUILayout.ExpandHeight(false)); | |
{ | |
foreach(var pathEvent in m_Path.m_Events) | |
{ | |
GUI.backgroundColor = (pathEvent.selected ? Color.grey : Color.white); | |
bool selected = GUILayout.Toggle(pathEvent.selected, | |
string.Format("t: {0}, {1}({2}) -> {3}{4}", pathEvent.t, | |
(pathEvent.name != "" ? pathEvent.name : "Unnamed"), | |
pathEvent.parameter, | |
(pathEvent.eventResponder != null ? pathEvent.eventResponder.name : "None"), | |
(pathEvent.notifyPathFollowers ? ", Notify" : "")), "TextArea"); | |
if(selected != pathEvent.selected) | |
{ | |
if((Event.current.modifiers & (EventModifiers.Shift | EventModifiers.Command)) == 0) | |
{ | |
DeselectAllPathEvents(); | |
pathEvent.selected = true; | |
} | |
else | |
pathEvent.selected = selected; | |
} | |
if(pathEvent.selected) | |
{ | |
if(++numSelectedPathEvents == 1) | |
selectedPathEvent = pathEvent; | |
} | |
} | |
GUI.backgroundColor = Color.white; | |
} | |
EditorGUILayout.EndScrollView(); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
if(GUILayout.Button("Add Event")) | |
{ | |
m_Path.m_Events.Add(new Path.Event(0.0f, "PathEvent", 0.0f, null, false)); | |
SortEvents(); | |
} | |
} | |
EditorGUILayout.Separator(); | |
// Event editor: | |
if(numSelectedPathEvents == 0) | |
{ | |
using(new EditorGUIGroupVertical("Box")) | |
GUILayout.Label("No events selected.", GUILayout.ExpandWidth(true)); | |
} | |
else if(numSelectedPathEvents == 1) | |
EventGUI(selectedPathEvent); | |
else | |
{ | |
using(new EditorGUIGroupVertical("Box")) | |
{ | |
GUILayout.Label(string.Format("{0} events selected.", numSelectedPathEvents), GUILayout.ExpandWidth(true)); | |
if(GUILayout.Button("Remove Selected Events")) | |
RemoveSelectedEvents(); | |
} | |
} | |
} | |
protected void EventGUI(Path.Event pathEvent) | |
{ | |
using(new EditorGUIGroupVertical("Box")) | |
{ | |
float t = EditorGUILayout.FloatField("t", pathEvent.t); | |
if(t != pathEvent.t) | |
{ | |
pathEvent.t = t; | |
SortEvents(); | |
} | |
pathEvent.name = EditorGUILayout.TextField("Name", pathEvent.name); | |
pathEvent.parameter = EditorGUILayout.FloatField("Parameter", pathEvent.parameter); | |
// The GetComponent call ensures that scripts that don't implement the interface are rejected: | |
var responder = EditorGUILayout.ObjectField("Responder", pathEvent.eventResponder, typeof(MonoBehaviour), true) as MonoBehaviour; | |
if(responder != null) | |
pathEvent.eventResponder = (MonoBehaviour)responder.GetComponent(typeof(Path.IEventResponder)); | |
pathEvent.notifyPathFollowers = EditorGUILayout.Toggle("Notify followers", pathEvent.notifyPathFollowers); | |
using(new EditorGUIGroupHorizontal()) | |
{ | |
if(GUILayout.Button("Duplicate")) | |
{ | |
m_Path.m_Events.Add(pathEvent.Clone()); | |
SortEvents(); | |
} | |
if(GUILayout.Button("Remove")) | |
m_Path.m_Events.Remove(pathEvent); | |
} | |
} | |
} | |
#endregion | |
#region Scene GUI | |
protected void OnSceneGUI() | |
{ | |
Event currentEvent = Event.current; | |
Camera sceneCamera = Camera.current; | |
Undo.SetSnapshotTarget(m_Path, "Edit Path"); | |
// Selection dragging: | |
if(m_SelectedWaypoints.Count > 0) | |
{ | |
Vector3 averagePosition = m_SelectedWaypoints.First().position; | |
foreach(var waypoint in m_SelectedWaypoints.Skip(1)) | |
averagePosition += waypoint.position; | |
averagePosition /= m_SelectedWaypoints.Count; | |
Vector3 newPosition = m_Path.transform.InverseTransformPoint( | |
Handles.PositionHandle(m_Path.transform.TransformPoint(averagePosition), Tools.handleRotation)); | |
var offset = newPosition - averagePosition; | |
if(offset.sqrMagnitude > 0.00001f) // FIXME: is this fuzzy enough? | |
{ | |
foreach(var waypoint in m_SelectedWaypoints) | |
waypoint.position += offset; | |
EditorUtility.SetDirty(m_Path); | |
} | |
} | |
// Draw waypoint selection buttons: | |
for(var i = 0; i < m_Path.m_Waypoints.Count; ++i) | |
{ | |
var waypoint = m_Path.m_Waypoints[i]; | |
Vector3 position = m_Path.transform.TransformPoint(waypoint.position); | |
if(m_Path.m_ProjectedDownwards) | |
{ | |
object hit = HandleUtility.RaySnap(new Ray(position, -Vector3.up)); | |
if(hit != null) | |
{ | |
Handles.color = Color.blue; | |
Handles.DrawLine(position, ((RaycastHit)hit).point); | |
} | |
} | |
Handles.color = (i > 0 ? m_Path.m_Colour * 0.5f : m_Path.m_Colour); | |
Vector3 screenPoint = sceneCamera.WorldToScreenPoint(position); | |
screenPoint.y = sceneCamera.pixelHeight - screenPoint.y; | |
if(screenPoint.z > 0.0f) | |
{ | |
Handles.BeginGUI(); | |
GUI.skin = m_InspectorSkin; | |
bool selected = m_SelectedWaypoints.Contains(waypoint); | |
if(selected) | |
GUI.color = selectionColour; | |
if(GUI.Button(new Rect(screenPoint.x - kWaypointHandleSize / 2, screenPoint.y - kWaypointHandleSize / 2, | |
kWaypointHandleSize, kWaypointHandleSize), "")) | |
{ | |
// Handle multiple selection: | |
if((currentEvent.modifiers & (EventModifiers.Shift | EventModifiers.Command)) == 0) | |
{ | |
DeselectAllWaypoints(); | |
selected = false; | |
} | |
if(selected) | |
DeselectWaypoint(waypoint); | |
else | |
SelectWaypoint(waypoint); | |
Repaint(); | |
} | |
GUI.color = Color.white; | |
// Using this instead of Handles.Label because that can still be seen when it's behind you! | |
string name = (waypoint.name != null && waypoint.name != "" ? waypoint.name : i.ToString()); | |
Rect labelRect = new Rect(screenPoint.x + 4, screenPoint.y + 4, 200, 20); | |
string labelText = string.Format(" {0}", name); | |
GUI.Label(labelRect, labelText, EditorStyles.whiteBoldLabel); | |
GUI.skin = m_SceneSkin; | |
Handles.EndGUI(); | |
} | |
} | |
// Draw events and vertex dots: | |
Handles.BeginGUI(); | |
int currentEventIndex = 0; | |
foreach(var segment in m_Path) | |
{ | |
Vector3 screenPoint = sceneCamera.WorldToScreenPoint(segment.vertex); | |
screenPoint.y = sceneCamera.pixelHeight - screenPoint.y; | |
if(screenPoint.z > 0.0f) | |
{ | |
if(m_Path.m_ShowVertices) | |
{ | |
GUI.DrawTexture(new Rect(screenPoint.x, screenPoint.y, 2.0f, 2.0f), EditorGUIUtility.whiteTexture); | |
// GUI.Label(new Rect(screenPoint.x, screenPoint.y, 200, 20), segment.totalT.ToString("0.00"), EditorStyles.miniLabel); | |
} | |
while(currentEventIndex < m_Path.m_Events.Count) | |
{ | |
var pathEvent = m_Path.m_Events[currentEventIndex]; | |
float endT = segment.totalT + 1.0f / m_Path.m_Subdivisions; | |
if(pathEvent.t >= segment.totalT && pathEvent.t < endT) | |
{ | |
GUI.DrawTexture(new Rect(screenPoint.x - m_EventTexture.width * 0.5f, screenPoint.y - m_EventTexture.height * 0.5f, | |
m_EventTexture.width, m_EventTexture.height), m_EventTexture); | |
GUI.Label(new Rect(screenPoint.x, screenPoint.y, 200, 20), pathEvent.name, EditorStyles.miniLabel); | |
} | |
else if(pathEvent.t >= endT) | |
break; | |
++currentEventIndex; | |
} | |
} | |
} | |
Handles.EndGUI(); | |
} | |
#endregion | |
#region Waypoint tools | |
protected int FindWaypointIndex(Path.Waypoint waypoint) | |
{ | |
return m_Path.m_Waypoints.IndexOf(waypoint); // Returns -1 if not found | |
} | |
protected void InsertWaypointAfter(int index) | |
{ | |
m_SelectedWaypoints.Clear(); | |
Undo.RegisterUndo(m_Path, "Insert Waypoint"); | |
var waypointToMove = m_Path.m_Waypoints[index]; | |
m_Path.m_Waypoints.Insert(index, waypointToMove); // Insert the existing waypoint, then insert the new one after it | |
var newWaypoint = waypointToMove.Clone(); | |
newWaypoint.position += Vector3.one; | |
m_Path.m_Waypoints[index + 1] = newWaypoint; | |
EditorUtility.SetDirty(m_Path); | |
m_SelectedWaypoints.Add(newWaypoint); | |
} | |
protected void RemoveWaypoint(int index) | |
{ | |
Undo.RegisterUndo(m_Path, "Delete Waypoint"); | |
m_SelectedWaypoints.Remove(m_Path.m_Waypoints[index]); | |
m_Path.m_Waypoints.RemoveAt(index); | |
EditorUtility.SetDirty(m_Path); | |
} | |
protected void RemoveSelectedWaypoints() | |
{ | |
var waypoints = m_Path.m_Waypoints.Where(waypoint => !m_SelectedWaypoints.Contains(waypoint)).ToList(); | |
if(waypoints.Count == 0) | |
waypoints.Add(new Path.Waypoint()); | |
m_SelectedWaypoints.Clear(); | |
m_Path.m_Waypoints = waypoints; | |
EditorUtility.SetDirty(m_Path); | |
} | |
protected void AlignWaypoints(int axis) | |
{ | |
// TODO: this could take a float for when you want to set the value explicitly instead of averaging it. | |
ICollection<Path.Waypoint> collection = null; | |
int numSelectedWaypoints = m_SelectedWaypoints.Count; | |
if(numSelectedWaypoints == 0) | |
{ | |
collection = m_Path.m_Waypoints; | |
numSelectedWaypoints = collection.Count; | |
} | |
else if(numSelectedWaypoints > 1) | |
collection = m_SelectedWaypoints; | |
// TODO: try copying this stuff into a new array, then transforming the array into world space if | |
// Tools.pivotRotation is set to Global, doing the operation, then setting it back again. | |
float average = collection.First().position[axis]; | |
foreach(var waypoint in collection.Skip(1)) | |
average += waypoint.position[axis]; | |
average /= numSelectedWaypoints; | |
foreach(var waypoint in collection) | |
waypoint.position[axis] = average; | |
EditorUtility.SetDirty(m_Path); | |
} | |
#endregion | |
#region Event tools | |
protected void DeselectAllPathEvents() | |
{ | |
foreach(var pathEvent in m_Path.m_Events) | |
pathEvent.selected = false; | |
} | |
protected void RemoveSelectedEvents() | |
{ | |
m_Path.m_Events.RemoveAll(pathEvent => pathEvent.selected); | |
} | |
protected void SortEvents() | |
{ | |
m_Path.m_Events.Sort((x, y) => x.t.CompareTo(y.t)); | |
} | |
#endregion | |
#region Other events | |
protected Bounds OnGetFrameBounds() | |
{ | |
Bounds bounds; | |
if(m_SelectedWaypoints.Count > 0) | |
{ | |
// Focus the selected waypoints: | |
bounds = new Bounds(m_Path.transform.TransformPoint(m_SelectedWaypoints.First().position), Vector3.zero); | |
foreach(var waypoint in m_SelectedWaypoints.Skip(1)) | |
bounds.Encapsulate(m_Path.transform.TransformPoint(waypoint.position)); | |
} | |
else | |
{ | |
// Focus all waypoints and the path's transform.position: | |
bounds = new Bounds(m_Path.transform.position, Vector3.zero); | |
foreach(var waypoint in m_Path.m_Waypoints) | |
bounds.Encapsulate(m_Path.transform.TransformPoint(waypoint.position)); | |
} | |
return bounds; | |
} | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment