ITweenPathEditorPlus

From Unify Community Wiki
Jump to: navigation, search

Contents

Summary

Moving iTween paths is a pain, since the nodes don't track the position of the game object. To move all the nodes together, you need to write a script. The iTweenPathEditor script is not helpful.

Enter iTweenPathEditorPlus, a modified version of iTweenPathEditor (because it doesn't work well to subclass [CustomEditor(script)] classes.

Functions

Re-center nodes around game object

Clicking this inspector button will shift all the nodes so they are spaced around the transform's position. They keep their position relative to each other.

Multi-select and multi-drag

When viewing the path in the Scene view, pressing Control or Option will connect the selected nodes with a white line to show that the mode has changed, and dragging the handles will move all selected nodes together. Nodes are deselected by clicking the circular button in the center of the handle. It's very fast to move the whole path, or to move most of the path but leave a few nodes anchored.

Code

(Use this to replace the iTweenPathEditor.cs script.)

// Additions copyright (c) 2014 Daniel Zwell
// Copyright (c) 2010 Bob Berkebile
// Please direct any bugs/comments/suggestions to http://www.pixelplacement.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
 
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
 
[CustomEditor(typeof(iTweenPath))]
public class iTweenPathEditor : Editor
{
	iTweenPath _target;
	GUIStyle style = new GUIStyle();
	HashSet<int> m_DeselectedNodes = null;
	public static int count = 0;
 
	void OnEnable(){
		//i like bold handle labels since I'm getting old:
		style.fontStyle = FontStyle.Bold;
		style.normal.textColor = Color.white;
		_target = (iTweenPath)target;
 
		//lock in a default path name:
		if(!_target.initialized){
			_target.initialized = true;
			_target.pathName = "New Path " + ++count;
			_target.initialName = _target.pathName;
		}
	}
 
	public override void OnInspectorGUI(){		
		//draw the path?
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.PrefixLabel("Path Visible");
		_target.pathVisible = EditorGUILayout.Toggle(_target.pathVisible);
		EditorGUILayout.EndHorizontal();
 
		//path name:
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.PrefixLabel("Path Name");
		_target.pathName = EditorGUILayout.TextField(_target.pathName);
		EditorGUILayout.EndHorizontal();
 
		if(_target.pathName == ""){
			_target.pathName = _target.initialName;
		}
 
		//path color:
		EditorGUILayout.BeginHorizontal();
		EditorGUILayout.PrefixLabel("Path Color");
		_target.pathColor = EditorGUILayout.ColorField(_target.pathColor);
		EditorGUILayout.EndHorizontal();
 
		//exploration segment count control:
		EditorGUILayout.BeginHorizontal();
		//EditorGUILayout.PrefixLabel("Node Count");
		_target.nodeCount = Mathf.Max(2, EditorGUILayout.IntField("Node Count", _target.nodeCount));
		//_target.nodeCount =  Mathf.Clamp(EditorGUILayout.IntSlider(_target.nodeCount, 0, 10), 2,100);
		EditorGUILayout.EndHorizontal();
 
		//add node?
		if(_target.nodeCount > _target.nodes.Count){
			for (int i = 0; i < _target.nodeCount - _target.nodes.Count; i++) {
				_target.nodes.Add(Vector3.zero);	
			}
		}
 
		if (GUILayout.Button("Recenter Nodes Around Path Object"))
		{
			Undo.RecordObject(target, "Re-center Path Nodes");
			Vector3 center = Vector3.zero; // the average point of the nodes
			float dividedByCount = 1f / _target.nodes.Count;
			foreach (Vector3 node in _target.nodes)
				center += node * dividedByCount;
			Vector3 difference = center - _target.transform.position; // difference from the game object position
			for (int i = 0; i < _target.nodes.Count; i++)
				_target.nodes[i] -= difference;
			EditorUtility.SetDirty(_target);
		}
 
		//remove node?
		if(_target.nodeCount < _target.nodes.Count){
			if(EditorUtility.DisplayDialog("Remove path node?","Shortening the node list will permantently destory parts of your path. This operation cannot be undone.", "OK", "Cancel")){
				int removeCount = _target.nodes.Count - _target.nodeCount;
				_target.nodes.RemoveRange(_target.nodes.Count-removeCount,removeCount);
			}else{
				_target.nodeCount = _target.nodes.Count;	
			}
		}
 
		//node display:
		EditorGUI.indentLevel = 4;
		for (int i = 0; i < _target.nodes.Count; i++) {
			_target.nodes[i] = EditorGUILayout.Vector3Field("Node " + (i+1), _target.nodes[i]);
		}
 
		//update and redraw:
		if(GUI.changed){
			EditorUtility.SetDirty(_target);			
		}
	}
 
	void OnSceneGUI(){
		if(_target.pathVisible){			
			if(_target.nodes.Count > 0){
				//allow path adjustment undo:
				Undo.SetSnapshotTarget(_target,"Adjust iTween Path");
 
				//path begin and end labels:
				Handles.Label(_target.nodes[0], "'" + _target.pathName + "' Begin", style);
				Handles.Label(_target.nodes[_target.nodes.Count-1], "'" + _target.pathName + "' End", style);
 
				//node handle display:
				if (Event.current.modifiers == EventModifiers.Control || Event.current.modifiers == EventModifiers.Command)
				{
					// pressing "control", so move all nodes together (unless deselected):
 
					// Which nodes are selected?
					if (m_DeselectedNodes == null)
						m_DeselectedNodes = new HashSet<int>();
 
					// Draw lines between selected nodes
					Handles.color = Color.white;
					int prevSelectedNode = -1;
					// wrap around is intentional: element 0 will be handled twice to draw a full loop.
					for (int i = 0; i <= _target.nodes.Count; i++){
						int node = i % _target.nodes.Count;
						if (!m_DeselectedNodes.Contains(node))
						{
							if (prevSelectedNode != -1)
								Handles.DrawLine(_target.nodes[prevSelectedNode], _target.nodes[node]);
							prevSelectedNode = node;
						}
					}
 
					for (int i = 0; i < _target.nodes.Count; i++){
						if (!m_DeselectedNodes.Contains(i)){
							Vector3 newPos = Handles.PositionHandle(_target.nodes[i], Quaternion.identity);
							if (newPos != _target.nodes[i]){
								Vector3 difference = newPos - _target.nodes[i];
								for (int j = 0; j < _target.nodes.Count; j++)
									if (!m_DeselectedNodes.Contains(j))
										_target.nodes[j] += difference;
								EditorUtility.SetDirty(_target);
							}
						}
 
						// Draw different dots, depending on whether this object is selected:
						Handles.color = m_DeselectedNodes.Contains(i) ? Color.white : Color.red;
						if (Handles.Button(_target.nodes[i], Quaternion.identity, 0.2f, 0.3f, Handles.SphereCap)){
							// toggle select/deselect:
							if (!m_DeselectedNodes.Remove(i))
								m_DeselectedNodes.Add(i);
						}
					}
				}
				else {
					m_DeselectedNodes = null;
					for (int i = 0; i < _target.nodes.Count; i++) {
						_target.nodes[i] = Handles.PositionHandle(_target.nodes[i], Quaternion.identity);
					}	
				}
			}	
		}
	}
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox