1
0
mirror of https://github.com/Cardidi/dotween-upm-fork.git synced 2025-12-21 01:36:05 +08:00
2020-01-17 00:11:15 +09:00

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
}