public class EventEditorWindowMgr : Singleton<EventEditorWindowMgr>
{
public List<EventEditorWindow> windowList;
private EventEditorWindow m_currentWindow = null;
public EventEditorWindow CurrentWindow
{
get
{
if (m_currentWindow == null)
{
for (int i = 0, count = windowList.Count; i < count; ++i)
{
if (windowList[i] != null)
{
m_currentWindow = windowList[i];
}
else
{
//Debug.Log("No Window Found!");
}
}
}
return m_currentWindow;
}
set { m_currentWindow = value; }
}
public EventEditorWindowMgr()
{
windowList = new List<EventEditorWindow>();
EditorApplication.update += OnEditorUpdate;
}
~EventEditorWindowMgr()
{
EditorApplication.update -= OnEditorUpdate;
}
public void OnEditorUpdate()
{
int windowCount = windowList.Count;
if (windowCount == 0)
{
return;
}
for (int i = 0; i < windowList.Count; ++i)
{
if (i < 0) break;
if (windowList[i] == EditorWindow.focusedWindow)
{
CurrentWindow = windowList[i];
}
if (windowList[i] == null)
{
windowList.RemoveAt(i);
i--;
}
}
}
#region OpenWindowMenu
[MenuItem("Window/Event Editor/Open Canvas", false, 1000)]
static void OpenMainEventGraph()
{
if (Instance.windowList.Count > 0)
{
EventEditorWindow currentWindow = OpenWindow(true, "Empty", EventEditorUtil.EventIcon);
Instance.CurrentWindow = currentWindow;
currentWindow.CreateNewGraph("Empty");
currentWindow.Show();
}
else
{
EventEditorWindow currentWindow = OpenWindow(false, "Empty", EventEditorUtil.EventIcon);
currentWindow.CreateNewGraph("Empty");
currentWindow.Show();
}
}
public static EventEditorWindow OpenWindow(bool isCreateTab, string title = null, Texture icon = null)
{
EventEditorWindow currentWindow = null;
if (isCreateTab)
{
currentWindow = ScriptableObject.CreateInstance<EventEditorWindow>();
}
else
{
currentWindow = (EventEditorWindow)EditorWindow.GetWindow(typeof(EventEditorWindow), false);
}
currentWindow.minSize = new Vector2((EventEditorDefines.MINIMIZE_WINDOW_LOCK_SIZE - 150), 270);
currentWindow.wantsMouseMove = true;
if (title != null)
currentWindow.titleContent.text = GenerateTabTitle(title);
if (icon != null)
currentWindow.titleContent.image = icon;
return currentWindow;
}
public static string GenerateTabTitle(string original, bool modified = false)
{
GUIContent content = new GUIContent(original);
GUIStyle tabStyle = new GUIStyle("dragtabdropwindow");// GUI.skin.FindStyle( "dragtabdropwindow" );
string finalTitle = string.Empty;
bool addEllipsis = false;
for (int i = 1; i <= original.Length; i++)
{
content.text = original.Substring(0, i);
Vector2 titleSize = tabStyle.CalcSize(content);
int maxSize = modified ? 62 : 69;
if (titleSize.x > maxSize)
{
addEllipsis = true;
break;
}
else
{
finalTitle = content.text;
}
}
if (addEllipsis)
finalTitle += "..";
if (modified)
finalTitle += "*";
return finalTitle;
}
#endregion
}
这个是事件编辑器的入口,创建一个window,并创建一个有一个主节点的graph。
window有一个属性position,就是一个rect,表示的是屏幕坐标系的rect,即窗口左上角的屏幕坐标,以及窗口的屏幕宽高。由于我们的图形画布是可以缩放和拖拽的,因此我们还有一个虚拟相机坐标系,刚开始我们把坐标系原点定为窗口中间,以下是它的换算公式。
public Vector2 TranformedMousePos
{
get { return m_currentMousePos2D * m_cameraZoom - m_cameraOffset; }
}
m_currentMousePos2D 坐标原点在窗口左上角,即我们基于窗口又弄了一个坐标系。
这一段代码是把坐标原点在窗口左上角的鼠标坐标转换为屏幕坐标系,然后判断鼠标是否在窗口内。 Vector2 pos = m_currentEvent.mousePosition;
pos.x += position.x;
pos.y += position.y;
m_insideEditorWindow = position.Contains(pos);
GUI.DrawTextureWithTexCoords(m_graphArea, m_graphBgTexture,
new Rect((-m_cameraOffset.x / m_graphBgTexture.width),
(m_cameraOffset.y / m_graphBgTexture.height) - m_cameraZoom * m_cameraInfo.height / m_graphBgTexture.height,
m_cameraZoom * m_cameraInfo.width / m_graphBgTexture.width,
m_cameraZoom * m_cameraInfo.height / m_graphBgTexture.height));
Color col = GUI.color;
GUI.color = new Color(1, 1, 1, 0.7f);
GUI.DrawTexture(m_graphArea, m_graphFgTexture, ScaleMode.StretchToFill, true);
GUI.color = col;
这段代码是绘制背景的。m_graphArea是Rect(0f,0f,windowwidth,windowheight)表示窗口区域,m_graphBgTexture是要绘制的纹理,最后一个参数rect表示uv坐标系的原点和uv总大小。
m_drawInfo.InvertedZoom = 1 / m_cameraZoom; 这句代码是因为zoom最大值是1,越小zoom越大。
if( drawInfo.CurrentEventType == EventType.Repaint )
OnNodeRepaint( drawInfo );
当在Repaint的情况下去绘制Button,点Button会没反应,。应该是需要其他EventType来处理按钮事件,其他控件类似。
目前编辑器的主要结构是有一个窗口管理器----》窗口实例----》graph----》各个节点(包括事件节点和动作节点)。
创建节点用一个ContextMenu类来实现。
public class ContextMenu : IDisposable
{
protected Rect m_Position;
protected GUIStyle m_Style;
protected UnityAction<Type> m_ButtonCallBack;
protected Dictionary<string, Type> m_ButtonTypeDict;
protected bool m_IsShow;
protected Vector2 m_ScrollPos;
protected const float BUTTON_SPACE = 30f;
public ContextMenu(Rect positon, UnityAction<Type> buttonCallBack)
{
m_Position = positon;
m_ButtonCallBack = buttonCallBack;
m_IsShow = false;
m_ScrollPos = Vector2.zero;
m_ButtonTypeDict = new Dictionary<string, Type>();
var types = System.AppDomain.CurrentDomain.GetAllDerivedTypes(typeof(NodeBase));
foreach(var t in types)
{
if (t == typeof(ActionNode)) continue;
m_ButtonTypeDict.Add(t.Name, t);
}
}
public void ShowHide(bool isShow, Vector2 mousePos)
{
m_IsShow = isShow;
m_Position.x = mousePos.x;
m_Position.y = mousePos.y;
}
public void Draw()
{
if (!m_IsShow || m_ButtonTypeDict == null || m_ButtonTypeDict.Count == 0 || m_ButtonCallBack == null)
return;
if (m_Style == null)
{
m_Style = EditorStyles.textArea;
}
GUI.Label(m_Position, string.Empty, m_Style);
var buttonCount = m_ButtonTypeDict.Count;
m_ScrollPos = GUI.BeginScrollView(m_Position, m_ScrollPos, new Rect(0f, 0f, m_Position.width, buttonCount * BUTTON_SPACE), false, true);
var index = 0;
foreach (var kv in m_ButtonTypeDict)
{
Rect startRect = new Rect(0f, index * BUTTON_SPACE, m_Position.width, BUTTON_SPACE);
if (GUI.Button(startRect, kv.Key))
{
if (m_ButtonCallBack != null)
{
m_ButtonCallBack(kv.Value);
}
ShowHide(false, Vector2.zero);
}
++index;
}
GUI.EndScrollView();
}
public void Dispose()
{
m_IsShow = false;
m_Style = null;
m_ButtonTypeDict = null;
m_ButtonCallBack = null;
}
}
这边用了点小技巧,把所有NodeBase的派生类获取出来,剔除不需要的中间类,就生成了我的节点创建菜单。
我们的绘制主要通过window的OnGUI函数,逐级调用相应的graph,node以及画线等等。画线的接口暂时没懂,有空看看。
public class GLDraw
{
/*
* Clipping code: http://forum.unity3d.com/threads/17066-How-to-draw-a-GUI-2D-quot-line-quot?p=230386#post230386
* Thick line drawing code: http://unifycommunity.com/wiki/index.php?title=VectorLine
*/
public static Material LineMaterial = null;
public static bool MultiLine = false;
private static Shader m_shader = null;
//private static bool m_clippingEnabled;
//private static Rect m_clippingBounds = new Rect();
private static Rect m_boundBox = new Rect();
private static Vector3[] allv3Points = new Vector3[] { };
private static Vector2[] allPerpendiculars = new Vector2[] { };
private static Color[] allColors = new Color[] { };
private static Vector2 startPt = Vector2.zero;
private static Vector2 endPt = Vector2.zero;
private static Vector3 Up = new Vector3( 0, 1, 0 );
private static Vector3 Zero = new Vector3( 0, 0, 0 );
private static Vector2 aux1Vec2 = Vector2.zero;
private static int HigherBoundArray = 0;
public static void CreateMaterial()
{
if ( (object) LineMaterial != null && ( object ) m_shader != null )
return;
Debug.Log(AssetDatabase.GUIDToAssetPath("50fc796413bac8b40aff70fb5a886273"));
m_shader = AssetDatabase.LoadAssetAtPath<Shader>( AssetDatabase.GUIDToAssetPath( "50fc796413bac8b40aff70fb5a886273" ) );
LineMaterial = new Material( m_shader );
LineMaterial.hideFlags = HideFlags.HideAndDontSave;
}
public static void DrawCurve( Vector3[] allPoints, Vector2[] allNormals, Color[] allColors, int pointCount )
{
if (Event.current.type != EventType.Repaint)
return;
CreateMaterial();
LineMaterial.SetPass((MultiLine ? 1 : 0));
GL.Begin(GL.TRIANGLE_STRIP);
for (int i = 0; i < pointCount; i++)
{
GL.Color(allColors[i]);
GL.TexCoord(Zero);
GL.Vertex3(allPoints[i].x - allNormals[i].x, allPoints[i].y - allNormals[i].y, 0);
GL.TexCoord(Up);
GL.Vertex3(allPoints[i].x + allNormals[i].x, allPoints[i].y + allNormals[i].y, 0);
}
GL.End();
}
public static Rect DrawBezier( Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent, Color color, float width, int type = 1 )
{
int segments = Mathf.FloorToInt( ( start - end ).magnitude / 20 ) * 3; // Three segments per distance of 20
return DrawBezier( start, startTangent, end, endTangent, color, width, segments, type );
}
public static Rect DrawBezier( Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent, Color color, float width, int segments, int type = 1 )
{
return DrawBezier( start, startTangent, end, endTangent, color, color, width, segments, type );
}
public static Rect DrawBezier( Vector2 start, Vector2 startTangent, Vector2 end, Vector2 endTangent, Color startColor, Color endColor, float width, int segments, int type = 1 )
{
int pointsCount = segments + 1;
int linesCount = segments;
HigherBoundArray = HigherBoundArray > pointsCount ? HigherBoundArray : pointsCount;
allv3Points = Handles.MakeBezierPoints( start, end, startTangent, endTangent, pointsCount );
if ( allColors.Length < HigherBoundArray )
{
allColors = new Color[ HigherBoundArray ];
allPerpendiculars = new Vector2[ HigherBoundArray ];
}
startColor.a = ( type * 0.25f );
endColor.a = ( type * 0.25f );
allColors[ 0 ] = startColor;
float minX = allv3Points[ 0 ].x;
float minY = allv3Points[ 0 ].y;
float maxX = allv3Points[ 0 ].x;
float maxY = allv3Points[ 0 ].y;
float amount = 1 / ( float ) linesCount;
for ( int i = 1; i < pointsCount; i++ )
{
allColors[ i ] = Color.LerpUnclamped( startColor, endColor, amount * i );
minX = ( allv3Points[ i ].x < minX ) ? allv3Points[ i ].x : minX;
minY = ( allv3Points[ i ].y < minY ) ? allv3Points[ i ].y : minY;
maxX = ( allv3Points[ i ].x > maxX ) ? allv3Points[ i ].x : maxX;
maxY = ( allv3Points[ i ].y > maxY ) ? allv3Points[ i ].y : maxY;
}
for ( int i = 0; i < pointsCount; i++ )
{
if ( i == 0 )
{
startPt.Set( startTangent.y, start.x );
endPt.Set( start.y, startTangent.x );
}
else if ( i == pointsCount - 1 )
{
startPt.Set( end.y, endTangent.x );
endPt.Set( endTangent.y, end.x );
}
else
{
startPt.Set( allv3Points[ i + 1 ].y, allv3Points[ i - 1 ].x );
endPt.Set( allv3Points[ i - 1 ].y, allv3Points[ i + 1 ].x );
}
aux1Vec2.Set( startPt.x - endPt.x, startPt.y - endPt.y );
FastNormalized( ref aux1Vec2 );
aux1Vec2.Set( aux1Vec2.x * width, aux1Vec2.y * width );
allPerpendiculars[ i ] = aux1Vec2;
}
m_boundBox.Set( minX, minY, ( maxX - minX ), ( maxY - minY ) );
DrawCurve( allv3Points, allPerpendiculars, allColors, pointsCount );
return m_boundBox;
}
private static void FastNormalized( ref Vector2 v )
{
float len = Mathf.Sqrt( v.x * v.x + v.y * v.y );
v.Set( v.x / len, v.y / len );
}
public static void Destroy()
{
GameObject.DestroyImmediate( LineMaterial );
LineMaterial = null;
Resources.UnloadAsset( m_shader );
m_shader = null;
}
}
最后重点记录下序列化和反序列化。
public static void SerializeToFile(object data, string filePath)
{
if (data == null) return;
FileStream stream = null;
try
{
stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, data);
}
catch (Exception)
{
stream.Close();
}
finally
{
stream.Close();
}
}
public static object DeserializeFromFile(string filePath)
{
object data = null;
FileStream stream = null;
try
{
stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Read);
BinaryFormatter formatter = new BinaryFormatter();
if (stream.Length > 0)
data = formatter.Deserialize(stream);
}
catch (Exception)
{
stream.Close();
}
finally
{
stream.Close();
}
return data;
}
public static string SerializeToString(object data)
{
if (data == null) return string.Empty;
MemoryStream stream = null;
try
{
stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, data);
stream.Position = 0;
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
stream.Flush();
stream.Close();
return Convert.ToBase64String(buffer);
}
catch (Exception e)
{
Debug.LogException(e);
stream.Close();
return string.Empty;
}
}
public static object DeserializeFromString(string str)
{
object data = null;
MemoryStream stream = null;
try
{
byte[] buffer = Convert.FromBase64String(str);
stream = new MemoryStream(buffer);
BinaryFormatter formatter = new BinaryFormatter();
if (stream.Length > 0)
data = formatter.Deserialize(stream);
}
catch (Exception)
{
stream.Close();
}
finally
{
stream.Close();
}
return data;
}
我们的复制黏贴,以及保存工作文件,都是用这几个核心函数实现。
导出csv,是通过采集csv所需的map数据,然后把他们转换成List<List<object>>类型的对象,最后生成csv文件,代码就不再贴出来了。
用到了Attribute的技术来采集数据
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class NodeDataAttribute : Attribute
{
}
采集数据的代码如下:
var fields = data.type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach(var field in fields)
{
var nodeDataAttris = field.GetCustomAttributes(typeof(NodeDataAttribute), true);
if(nodeDataAttris != null && nodeDataAttris.Length > 0)
{
//Debug.Log(field.Name + " " +field.GetValue(this));
data.otherData.Add(field.Name, field.GetValue(this));
}
}
反序列化数据的代码如下:
public virtual void SetFieldValueFromData(Dictionary<string, object> otherData)
{
var fields = GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
var nodeDataAttris = field.GetCustomAttributes(typeof(NodeDataAttribute), true);
if (nodeDataAttris != null && nodeDataAttris.Length > 0)
{
field.SetValue(this, otherData[field.Name]);
}
}
}
网友评论