mirror of
https://github.com/Cardidi/dotween-upm-fork.git
synced 2025-12-20 09:16:02 +08:00
505 lines
14 KiB
C#
505 lines
14 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
|
|
|
|
|
|
[CustomEditor( typeof( GoDummyPath ) )]
|
|
public class GoDummyPathEditor : Editor
|
|
{
|
|
private GoDummyPath _target;
|
|
private GUIStyle _labelStyle;
|
|
private GUIStyle _indexStyle;
|
|
|
|
private int _insertIndex = 0;
|
|
private float _snapDistance = 5f;
|
|
private bool _showNodeDetails;
|
|
private bool _fileLoadSaveDetails;
|
|
private int _selectedNodeIndex = -1;
|
|
|
|
|
|
#region Monobehaviour and Editor
|
|
|
|
void OnEnable()
|
|
{
|
|
// setup the font for the 'begin' 'end' text
|
|
_labelStyle = new GUIStyle();
|
|
_labelStyle.fontStyle = FontStyle.Bold;
|
|
_labelStyle.normal.textColor = Color.white;
|
|
_labelStyle.fontSize = 16;
|
|
|
|
_indexStyle = new GUIStyle();
|
|
_indexStyle.fontStyle = FontStyle.Bold;
|
|
_indexStyle.normal.textColor = Color.white;
|
|
_indexStyle.fontSize = 12;
|
|
|
|
_target = (GoDummyPath)target;
|
|
}
|
|
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
// what kind of handles shall we use?
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Use Standard Handles" );
|
|
_target.useStandardHandles = EditorGUILayout.Toggle( _target.useStandardHandles );
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
// path name:
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Route Name" );
|
|
_target.pathName = EditorGUILayout.TextField( _target.pathName );
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if( _target.pathName == string.Empty )
|
|
_target.pathName = "route" + Random.Range( 1, 100000 );
|
|
|
|
|
|
// path color:
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Route Color" );
|
|
_target.pathColor = EditorGUILayout.ColorField( _target.pathColor );
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
// force straight lines:
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Force Straight Line Path" );
|
|
_target.forceStraightLinePath = EditorGUILayout.Toggle( _target.forceStraightLinePath );
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
// resolution
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Editor Drawing Resolution" );
|
|
_target.pathResolution = EditorGUILayout.IntSlider( _target.pathResolution, 2, 100 );
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
EditorGUILayout.Separator();
|
|
|
|
|
|
// insert node - we need 3 or more nodes for insert to make sense
|
|
if( _target.nodes.Count > 2 )
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Insert Node" );
|
|
_insertIndex = EditorGUILayout.IntField( _insertIndex );
|
|
if( GUILayout.Button( "Insert" ) )
|
|
{
|
|
// validate the index
|
|
if( _insertIndex >= 0 && _insertIndex < _target.nodes.Count )
|
|
{
|
|
// insert the node offsetting it a bit from the previous node
|
|
var copyNodeIndex = _insertIndex == 0 ? 0 : _insertIndex;
|
|
var copyNode = _target.nodes[copyNodeIndex];
|
|
copyNode.x += 10;
|
|
copyNode.z += 10;
|
|
|
|
insertNodeAtIndex( copyNode, _insertIndex );
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
|
|
|
|
// close route?
|
|
if( GUILayout.Button( "Close Path" ) )
|
|
{
|
|
Undo.RecordObject( _target, "Path Vector Changed" );
|
|
closeRoute();
|
|
GUI.changed = true;
|
|
}
|
|
|
|
|
|
// shift the start point to the origin
|
|
if( GUILayout.Button( "Shift Path to Start at Origin" ) )
|
|
{
|
|
Undo.RecordObject( _target, "Path Vector Changed" );
|
|
|
|
var offset = Vector3.zero;
|
|
|
|
// see what kind of path we are. the simplest case is just a straight line
|
|
var path = new GoSpline( _target.nodes, _target.forceStraightLinePath );
|
|
if( path.splineType == GoSplineType.StraightLine || _target.nodes.Count < 5 )
|
|
offset = Vector3.zero - _target.nodes[0];
|
|
else
|
|
offset = Vector3.zero - _target.nodes[1];
|
|
|
|
for( var i = 0; i < _target.nodes.Count; i++ )
|
|
_target.nodes[i] += offset;
|
|
|
|
GUI.changed = true;
|
|
}
|
|
|
|
|
|
// reverse
|
|
if( GUILayout.Button( "Reverse Path" ) )
|
|
{
|
|
Undo.RecordObject( _target, "Path Vector Changed" );
|
|
_target.nodes.Reverse();
|
|
GUI.changed = true;
|
|
}
|
|
|
|
|
|
// persist to disk
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField( "Save to/Read from Disk" );
|
|
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Serialize and Save Path" );
|
|
if( GUILayout.Button( "Save" ) )
|
|
{
|
|
var path = EditorUtility.SaveFilePanel( "Save path", Application.dataPath + "/StreamingAssets", _target.pathName + ".asset", "asset" );
|
|
if( path != string.Empty )
|
|
{
|
|
persistRouteToDisk( path );
|
|
|
|
// fetch the filename and set it as the routeName
|
|
_target.pathName = Path.GetFileName( path ).Replace( ".asset", string.Empty );
|
|
GUI.changed = true;
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
// load from disk
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel( "Load saved path" );
|
|
if( GUILayout.Button( "Load" ) )
|
|
{
|
|
var path = EditorUtility.OpenFilePanel( "Choose path to load", Path.Combine( Application.dataPath, "StreamingAssets" ), "asset" );
|
|
if( path != string.Empty )
|
|
{
|
|
if( !File.Exists( path ) )
|
|
{
|
|
EditorUtility.DisplayDialog( "File does not exist", "Path couldn't find the file you specified", "Close" );
|
|
}
|
|
else
|
|
{
|
|
_target.nodes = GoSpline.bytesToVector3List( File.ReadAllBytes( path ) );
|
|
_target.pathName = Path.GetFileName( path ).Replace( ".asset", string.Empty );
|
|
GUI.changed = true;
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
// node display
|
|
EditorGUILayout.Space();
|
|
_showNodeDetails = EditorGUILayout.Foldout( _showNodeDetails, "Show Node Values" );
|
|
if( _showNodeDetails )
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
for( int i = 0; i < _target.nodes.Count; i++ )
|
|
_target.nodes[i] = EditorGUILayout.Vector3Field( "Node " + ( i + 1 ), _target.nodes[i] );
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
|
|
|
|
// instructions
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.HelpBox( "While dragging a node, hold down Ctrl and slowly move the cursor to snap to a nearby point\n\n" +
|
|
"Click the 'Close Path' button to add a new node that will close out the current path.\n\n" +
|
|
"Hold Command while dragging a node to snap in 5 point increments\n\n" +
|
|
"Double click to add a new node at the end of the path\n\n" +
|
|
"Hold down alt while adding a node to prepend the new node at the front of the route\n\n" +
|
|
"Press delete or backspace to delete the selected node\n\n" +
|
|
"NOTE: make sure you have the pan tool selected while editing paths", MessageType.None );
|
|
|
|
|
|
// update and redraw:
|
|
if( GUI.changed )
|
|
{
|
|
EditorUtility.SetDirty( _target );
|
|
Repaint();
|
|
}
|
|
}
|
|
|
|
|
|
void OnSceneGUI()
|
|
{
|
|
if( !_target.gameObject.activeSelf )
|
|
return;
|
|
|
|
// handle current selection and node addition via double click or ctrl click
|
|
if( Event.current.type == EventType.mouseDown )
|
|
{
|
|
var nearestIndex = getNearestNodeForMousePosition( Event.current.mousePosition );
|
|
_selectedNodeIndex = nearestIndex;
|
|
|
|
// double click to add
|
|
if( Event.current.clickCount > 1 )
|
|
{
|
|
var translatedPoint = HandleUtility.GUIPointToWorldRay( Event.current.mousePosition )
|
|
.GetPoint( ( _target.transform.position - Camera.current.transform.position ).magnitude );
|
|
|
|
Undo.RecordObject( _target, "Path Node Added" );
|
|
|
|
// if alt is down then prepend the node to the beginning
|
|
if( Event.current.alt )
|
|
insertNodeAtIndex( translatedPoint, 0 );
|
|
else
|
|
appendNodeAtPoint( translatedPoint );
|
|
}
|
|
}
|
|
|
|
|
|
if( _selectedNodeIndex >= 0 )
|
|
{
|
|
// shall we delete the selected node?
|
|
if( Event.current.keyCode == KeyCode.Delete || Event.current.keyCode == KeyCode.Backspace )
|
|
{
|
|
if (_target.nodes.Count > 2) {
|
|
Undo.RecordObject( _target, "Path Node Deleted" );
|
|
Event.current.Use();
|
|
removeNodeAtIndex( _selectedNodeIndex );
|
|
_selectedNodeIndex = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( _target.nodes.Count > 1 )
|
|
{
|
|
// allow path adjustment undo:
|
|
Undo.RecordObject( _target, "Path Vector Changed" );
|
|
|
|
// path begin and end labels or just one if the path is closed
|
|
if( Vector3.Distance( _target.nodes[0], _target.nodes[_target.nodes.Count - 1] ) == 0 )
|
|
{
|
|
Handles.Label( _target.nodes[0], " Begin and End", _labelStyle );
|
|
}
|
|
else
|
|
{
|
|
Handles.Label( _target.nodes[0], " Begin", _labelStyle );
|
|
Handles.Label( _target.nodes[_target.nodes.Count - 1], " End", _labelStyle );
|
|
}
|
|
|
|
// draw the handles, arrows and lines
|
|
drawRoute();
|
|
|
|
for( var i = 0; i < _target.nodes.Count; i++ )
|
|
{
|
|
Handles.color = _target.pathColor;
|
|
|
|
// dont label the first and last nodes
|
|
if( i > 0 && i < _target.nodes.Count - 1 )
|
|
Handles.Label( _target.nodes[i] + new Vector3( 3f, 0, 1.5f ), i.ToString(), _indexStyle );
|
|
|
|
Handles.color = Color.white;
|
|
if( _target.useStandardHandles )
|
|
{
|
|
_target.nodes[i] = Handles.PositionHandle( _target.nodes[i], Quaternion.identity );
|
|
}
|
|
else
|
|
{
|
|
// how big shall we draw the handles?
|
|
var distanceToTarget = Vector3.Distance( SceneView.lastActiveSceneView.camera.transform.position, _target.transform.position );
|
|
distanceToTarget = Mathf.Abs( distanceToTarget );
|
|
var handleSize = Mathf.Ceil( distanceToTarget / 75 );
|
|
|
|
_target.nodes[i] = Handles.FreeMoveHandle( _target.nodes[i],
|
|
Quaternion.identity,
|
|
handleSize,
|
|
new Vector3( 5, 0, 5 ),
|
|
Handles.SphereCap );
|
|
}
|
|
|
|
|
|
// should we snap? we need at least 4 nodes because we dont snap to the previous and next nodes
|
|
if( Event.current.control && _target.nodes.Count > 3 )
|
|
{
|
|
// dont even bother checking for snapping to the previous/next nodes
|
|
var index = getNearestNode( _target.nodes[i], i, i + 1, i - 1 );
|
|
var nearest = _target.nodes[index];
|
|
var distanceToNearestNode = Vector3.Distance( nearest, _target.nodes[i] );
|
|
|
|
// is it close enough to snap?
|
|
if( distanceToNearestNode <= _snapDistance )
|
|
{
|
|
GUI.changed = true;
|
|
_target.nodes[i] = nearest;
|
|
}
|
|
else if( distanceToNearestNode <= _snapDistance * 2 )
|
|
{
|
|
// show which nodes are getting close enough to snap to
|
|
var color = Color.red;
|
|
color.a = 0.3f;
|
|
Handles.color = color;
|
|
Handles.SphereCap( 0, _target.nodes[i], Quaternion.identity, _snapDistance * 2 );
|
|
//Handles.DrawWireDisc( _target.nodes[i], Vector3.up, _snapDistance );
|
|
Handles.color = Color.white;
|
|
}
|
|
}
|
|
} // end for
|
|
|
|
|
|
if( GUI.changed )
|
|
{
|
|
Repaint();
|
|
EditorUtility.SetDirty( _target );
|
|
}
|
|
} // end if
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private methods
|
|
|
|
private void appendNodeAtPoint( Vector3 node )
|
|
{
|
|
_target.nodes.Add( node );
|
|
|
|
GUI.changed = true;
|
|
}
|
|
|
|
|
|
private void removeNodeAtIndex( int index )
|
|
{
|
|
if( index >= _target.nodes.Count || index < 0 )
|
|
return;
|
|
|
|
_target.nodes.RemoveAt( index );
|
|
|
|
GUI.changed = true;
|
|
}
|
|
|
|
|
|
private void insertNodeAtIndex( Vector3 node, int index )
|
|
{
|
|
// validate the index
|
|
if( index >= 0 && index < _target.nodes.Count )
|
|
{
|
|
_target.nodes.Insert( index, node );
|
|
|
|
GUI.changed = true;
|
|
}
|
|
}
|
|
|
|
|
|
private void drawArrowBetweenPoints( Vector3 point1, Vector3 point2 )
|
|
{
|
|
// no need to draw arrows for tiny segments
|
|
var distance = Vector3.Distance( point1, point2 );
|
|
if( distance < 40 )
|
|
return;
|
|
|
|
// we dont want to be exactly in the middle so we offset the length of the arrow
|
|
var lerpModifier = ( distance * 0.5f - 25 ) / distance;
|
|
|
|
Handles.color = _target.pathColor;
|
|
|
|
// get the midpoint between the 2 points
|
|
var dir = Vector3.Lerp( point1, point2, lerpModifier );
|
|
var quat = Quaternion.LookRotation( point2 - point1 );
|
|
Handles.ArrowCap( 0, dir, quat, 25 );
|
|
|
|
Handles.color = Color.white;
|
|
}
|
|
|
|
|
|
private int getNearestNode( Vector3 pos, params int[] excludeNodes )
|
|
{
|
|
var excludeNodesList = new System.Collections.Generic.List<int>( excludeNodes );
|
|
var bestDistance = float.MaxValue;
|
|
var index = -1;
|
|
|
|
var distance = float.MaxValue;
|
|
for( var i = _target.nodes.Count - 1; i >= 0; i-- )
|
|
{
|
|
if( excludeNodesList.Contains( i ) )
|
|
continue;
|
|
|
|
distance = Vector3.Distance( pos, _target.nodes[i] );
|
|
if( distance < bestDistance )
|
|
{
|
|
bestDistance = distance;
|
|
index = i;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
|
|
private int getNearestNodeForMousePosition( Vector3 mousePos )
|
|
{
|
|
var bestDistance = float.MaxValue;
|
|
var index = -1;
|
|
|
|
var distance = float.MaxValue;
|
|
for( var i = _target.nodes.Count - 1; i >= 0; i-- )
|
|
{
|
|
var nodeToGui = HandleUtility.WorldToGUIPoint( _target.nodes[i] );
|
|
distance = Vector2.Distance( nodeToGui, mousePos );
|
|
|
|
if( distance < bestDistance )
|
|
{
|
|
bestDistance = distance;
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
// make sure we are close enough to a node
|
|
if( bestDistance < 10 )
|
|
return index;
|
|
return -1;
|
|
}
|
|
|
|
|
|
private void closeRoute()
|
|
{
|
|
// we will use the GoSpline class to handle the dirtywork of closing the path
|
|
var path = new GoSpline( _target.nodes, _target.forceStraightLinePath );
|
|
path.closePath();
|
|
|
|
_target.nodes = path.nodes;
|
|
|
|
GUI.changed = true;
|
|
}
|
|
|
|
|
|
private void persistRouteToDisk( string path )
|
|
{
|
|
var bytes = new List<byte>();
|
|
|
|
foreach( var vec in _target.nodes )
|
|
{
|
|
bytes.AddRange( System.BitConverter.GetBytes( vec.x ) );
|
|
bytes.AddRange( System.BitConverter.GetBytes( vec.y ) );
|
|
bytes.AddRange( System.BitConverter.GetBytes( vec.z ) );
|
|
}
|
|
|
|
File.WriteAllBytes( path, bytes.ToArray() );
|
|
}
|
|
|
|
|
|
private void drawRoute()
|
|
{
|
|
// if we are forcing straight lines just use this setup
|
|
if( _target.forceStraightLinePath )
|
|
{
|
|
// draw just the route here and optional arrows
|
|
for( var i = 0; i < _target.nodes.Count; i++ )
|
|
{
|
|
Handles.color = _target.pathColor;
|
|
if( i < _target.nodes.Count - 1 )
|
|
{
|
|
Handles.DrawLine( _target.nodes[i], _target.nodes[i + 1] );
|
|
drawArrowBetweenPoints( _target.nodes[i], _target.nodes[i + 1] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|