1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > SRPG游戏开发(七)第三章 绘制地图 - 四 初步完善地图编辑器(Map Graph)

SRPG游戏开发(七)第三章 绘制地图 - 四 初步完善地图编辑器(Map Graph)

时间:2018-12-07 06:45:05

相关推荐

SRPG游戏开发(七)第三章 绘制地图 - 四 初步完善地图编辑器(Map Graph)

返回目录

第三章 绘制地图

四 初步完善地图编辑器(Map Graph)

到目前为止我们可以开心的绘制我们的地图了,但有不少小问题。一直开心忘我的绘制地图,却不知道地图已经绘制了多大,还要去看到底大小有没有超标。像其它地图编辑器都会有相关参数显示。我们也开始创建有些有助于绘制的显示参数,让它更像一个标准的地图编辑器,创建我们的MapGraph.cs。

1地图大小(Map Rect)

第一部要做的是确认地图的大小,我们要先确定我们地图绘制大小,这就可以知道我们绘制的东西是不是已经超越边界。比如一个10x10的地图:

你可以使用确定对角线Position的方式。

public Vector3Int m_LeftDownPosition = Vector3Int.zero;public Vector3Int m_RightUpPosition = new Vector3Int(9, 9, 0);public RectInt mapRect{get{return new RectInt(m_LeftDownPosition.x,m_LeftDownPosition.y,m_RightUpPosition.x - m_LeftDownPosition.x + 1,m_RightUpPosition.y - m_LeftDownPosition.y + 1);}}

也可以直接使用矩形,我们这里直接使用整形的矩形RectInt。

public RectInt m_MapRect = new RectInt(0, 0, 10, 10);public Vector3Int leftDownPosition{get { return new Vector3Int(m_MapRect.xMin, m_MapRect.yMin, 0); }}public Vector3Int rightUpPosition{get { return new Vector3Int(m_MapRect.xMax - 1, m_MapRect.yMax - 1, 0); }}

接下来我们添加一些常用的属性(Property),地图的宽(width)与高(height)。

public int width{get { return m_MapRect.width; }}public int height{get { return m_MapRect.height; }}

2在Scene面板中绘制地图边框(Border)

在Unity中绘制辅助线的方式有多种,比如“Gizmos”和“Handles”。其中Gizmos只能在OnDrawGizmos()与OnDrawGizmosSelected()两个Unity的Callback中使用,而Handles范围就广一些,而且功能更强大。我们需要在Scene中绘制Cell的Position,所以使用两种混用的方法。你还需要知道,Handles是在UnityEditor的命名空间中,在MonoBehaviour中需要在#if UNITY_EDITOR与#endif之间使用

OnDrawGizmos():无论是否被选中,都在Scene面板中渲染;OnDrawGizmosSelected():只有选中状态下,才在Scene面板中渲染。

我们先在MapGraph.cs中创建这一领域。

#if UNITY_EDITORusing UnityEditor;#endif…#if UNITY_EDITORprivate void OnDrawGizmos(){EditorDrawBorderGizmos();}protected void EditorDrawBorderGizmos(){}#endif…

绘制边框,我们采用Gizmos的DrawWireCube(Vector3 center, Vector3 size)方法。从中我们看出,需要中心点与大小。而计算他们主要还是靠Grid组件获取Cell的世界坐标方法(world position),所以我们添加Grid组件。

private Grid m_Grid;public Grid grid{get{if (m_Grid == null){m_Grid = GetComponent<Grid>();}return m_Grid;}}

先来看一张坐标系的图。

图 3 - 17计算Border的Center

你要知道的是,Grid获取Cell的中心世界坐标,所以左下角要减去Cell Size的一半;同样的,右上角要加上Cell Size的一半;而且width与height是指Cell的数量,你还要乘以Cell Size才是他们的真正长与宽。

public Vector3 halfCellSize{get { return grid.cellSize / 2f; }}

完整的EditorDrawBorderGizmos():

protected void EditorDrawBorderGizmos(){Color old = Gizmos.color;GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = m_EditorBorderColor;// 获取边框左下角与右上角的世界坐标Vector3 leftDown = grid.GetCellCenterWorld(leftDownPosition) - halfCellSize;Vector3 rightUp = grid.GetCellCenterWorld(rightUpPosition) + halfCellSize;// 绘制左下角Cell与右上角Cell的PositionHandles.Label(leftDown, (new Vector2Int(leftDownPosition.x, leftDownPosition.y)).ToString(), textStyle);Handles.Label(rightUp, (new Vector2Int(rightUpPosition.x, rightUpPosition.y)).ToString(), textStyle);if (mapRect.width > 0 && mapRect.height > 0){Gizmos.color = m_EditorBorderColor;// 边框的长与宽Vector3 size = Vector3.Scale(new Vector3(width, height), grid.cellSize);// 边框的中心坐标Vector3 center = leftDown + size / 2f;// 绘制边框Gizmos.DrawWireCube(center, size);}Gizmos.color = old;}

其中有一些新出现的东西:

Handles.Label:在Scene面板中绘制文字;m_EditorBorderColor:边框颜色。

这使得我们可以改变边框颜色,不至于让颜色被淹没。再来额外添加一些需要用到的变量,来控制是否需要绘制Gizmos,并修改OnDrawGizmos()。

#if UNITY_EDITOR[Header("Editor Gizmos")]public bool m_EditorDrawGizmos = true;public Color m_EditorBorderColor = Color.white;public Color m_EditorCellColor = Color.green;public Color m_EditorErrorColor = Color.red;private void OnDrawGizmos(){if (m_EditorDrawGizmos){EditorDrawBorderGizmos();}}…#endif

好了,来看看我们的效果。

图 3 - 18绘制Border效果图

需要注意的是(0, 1)不是世界坐标,而是右上角Cell的Position。

这样我们就绘制完成了边框,既能改变颜色,又能在Inspector面板控制是否显示它,绘制的时候也知道是不是越界了。但是,RectInt的宽与高是可以为负数的,这就不对了。接下来,我们来限定它并在Scene面板中绘制一些信息。

3在Scene面板中绘制地图信息(Information)

这部分工作,我们在Editor中进行,所以在Editor文件夹下创建新文件MapGraphEditor.cs。

3.1限定宽与高

这里我们限定宽与高都不能低于2。

public MapGraph map{get { return target as MapGraph; }}public override void OnInspectorGUI(){DrawDefaultInspector();// 检测地图长宽是否正确,如果不正确就修正if (map.mapRect.width < 2 || map.mapRect.height < 2){RectInt fix = map.mapRect;fix.width = Mathf.Max(map.mapRect.width, 2);fix.height = Mathf.Max(map.mapRect.height, 2);map.mapRect = fix;}}

DrawDefaultInspector()是用来绘制本来的Inspector面板。我们暂时并不需要自定义Inspector面板,所以使用它。

3.2绘制地图信息

在Scene面板中绘制GUI是需要在Unity的OnSceneGUI()方法中进行。而且在这之中,要在Handles.BeginGUI()和Handles.EndGUI()中间进行绘制。还要创建GUI块。

// Scene面板左上角显示信息Handles.BeginGUI();{Rect areaRect = new Rect(50, 50, 200, 200);GUILayout.BeginArea(areaRect);{// 你的GUILayout代码}GUILayout.EndArea();}Handles.EndGUI();

再来创建一个绘制两个横向Label方法供我们使用。

protected void DrawHorizontalLabel(string name, string value, GUIStyle style = null, int nameMaxWidth = 80, int valueMaxWdith = 120){EditorGUILayout.BeginHorizontal();if (style == null){EditorGUILayout.LabelField(name, GUILayout.MaxWidth(nameMaxWidth));EditorGUILayout.LabelField(value, GUILayout.MaxWidth(valueMaxWdith));}else{EditorGUILayout.LabelField(name, style, GUILayout.MaxWidth(nameMaxWidth));EditorGUILayout.LabelField(value, style, GUILayout.MaxWidth(valueMaxWdith));}EditorGUILayout.EndHorizontal();}

完成后的OnSceneGUI():

protected virtual void OnSceneGUI(){if (!map.m_EditorDrawGizmos){return;}GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = map.m_EditorCellColor;// Scene面板左上角显示信息Handles.BeginGUI();{Rect areaRect = new Rect(50, 50, 200, 200);GUILayout.BeginArea(areaRect);{// 你的GUILayout代码DrawHorizontalLabel("Object Name:", map.gameObject.name, textStyle);DrawHorizontalLabel("Map Name:", map.mapName, textStyle);DrawHorizontalLabel("Map Size:", map.width + "x" + map.height, textStyle);DrawHorizontalLabel("Cell Size:", map.grid.cellSize.x + "x" + map.grid.cellSize.y, textStyle);}GUILayout.EndArea();}Handles.EndGUI();}

完成后的效果图:

图 3 - 19绘制信息

我们的Scene面板看起来不错。

那么又有新问题来了,如果地图非常的大,比如128*128,那绘制中间的时候,也不知道我们绘制到第几个Cell了,好吧,我们继续改造它。

4在Scene面板中绘制鼠标(Mouse)所在Cell

回到我们的MapGraph.cs这次使用OnDrawGizmosSelected()方法来绘制,只有选中地图时才显示,添加新方法。

private void OnDrawGizmosSelected(){if (m_EditorDrawGizmos){EditorDrawCellGizmos();}}protected void EditorDrawCellGizmos(){}

首先,我们要知道鼠标位置是指Scene面板中的,而不是Game面板中,而且游戏也没有在运行,所以不能用Input.mousePosition,而应该使用Event.current.mousePosition。

Event e = Event.current;Vector2 mousePosition = e.mousePosition;

其次,同样的理由,转换成世界坐标时,不能使用Camera.main(场景中也可能就没有Camera),而应该是Scene面板的Camera。

最后,Event所获取的mousePosition是从屏幕左上角(Left Up)开始的,而Camera是从屏幕左下角(Left Down)开始的。所以转换世界坐标时,不能直接使用。

// 获取当前操作Scene面板SceneView sceneView = SceneView.currentDrawingSceneView;/// 获取鼠标世界坐标:/// Event是从左上角(Left Up)开始,/// 而Camera是从左下角(Left Down),/// 需要转换才能使用Camera的ScreenToWorldPoint方法。Vector2 screenPosition = new Vector2(e.mousePosition.x, sceneView.camera.pixelHeight - e.mousePosition.y);Vector2 worldPosition = sceneView.camera.ScreenToWorldPoint(screenPosition);// 当前鼠标所在Cell的PositionVector3Int cellPostion = grid.WorldToCell(worldPosition);// 当前鼠标所在Cell的Center坐标Vector3 cellCenter = grid.GetCellCenterWorld(cellPostion);

接下来绘制我们选中的Cell,同样使用了Gizmos.DrawWireCube:

GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = m_EditorCellColor;Gizmos.color = m_EditorCellColor;Handles.Label(cellCenter - halfCellSize, (new Vector2Int(cellPostion.x, cellPostion.y)).ToString(), textStyle);Gizmos.DrawWireCube(cellCenter, grid.cellSize);

图 3 - 20绘制Cell

绘制是绘制出来了,可以却又有新问题了,有些时候过好一阵子才会跟着鼠标渲染,这是由于这些方法都不是每帧运行的。接下来,我们来修正这一问题。

5每帧刷新Scene面板(Update Scene)

回到MapGraphEditor.cs中,我们添加一个方法。

/// <summary>/// 立即刷新Scene面板,这保证了每帧都运行(包括Gizmos)。/// 如果在OnSceneGUI或Gizmos里获取鼠标,需要每帧都运行。/// </summary>protected void UpdateSceneGUI(){HandleUtility.Repaint();}

用HandleUtility.Repaint()方法来刷新Scene面板。

我们在OnSceneGUI()调用它:

protected virtual void OnSceneGUI(){if (!map.m_EditorDrawGizmos){return;}GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = map.m_EditorCellColor;// Scene面板左上角显示信息Handles.BeginGUI();{Rect areaRect = new Rect(50, 50, 200, 200);GUILayout.BeginArea(areaRect);{// 你的GUILayout代码DrawHorizontalLabel("Object Name:", map.gameObject.name, textStyle);DrawHorizontalLabel("Map Name:", map.mapName, textStyle);DrawHorizontalLabel("Map Size:", map.width + "x" + map.height, textStyle);DrawHorizontalLabel("Cell Size:", map.grid.cellSize.x + "x" + map.grid.cellSize.y, textStyle);}GUILayout.EndArea();}Handles.EndGUI();// 立即刷新Scene面板UpdateSceneGUI();}

这样,在渲染Scene面板时,同时再次刷新它,就保证了每帧都运行。

6判断Cell是否在地图内与修改绘制Cell

我们已经几乎完成MapGraph.cs的编写,还缺少判断选择的点是不是在地图内,在将来游戏中也是需要的。在RectInt中已经有这个方法。

/// <summary>/// 地图是否包含Position/// </summary>/// <param name="position"></param>/// <returns></returns>public bool Contains(Vector3Int position){return mapRect.Contains(new Vector2Int(position.x, position.y));}

接下来在EditorDrawCellGizmos()方法中,修改绘制Cell,让Scene面板更人性化。

修改前代码:

GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = m_EditorCellColor;Gizmos.color = m_EditorCellColor;Handles.Label(cellCenter - halfCellSize, (new Vector2Int(cellPostion.x, cellPostion.y)).ToString(), textStyle);Gizmos.DrawWireCube(cellCenter, grid.cellSize);

修改后代码:

/// 绘制当前鼠标下的Cell边框与Position/// 如果包含Cell,正常绘制/// 如果不包含Cell,改变颜色,并多绘制一个叉if (Contains(cellPostion)){GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = m_EditorCellColor;Gizmos.color = m_EditorCellColor;Handles.Label(cellCenter - halfCellSize, (new Vector2Int(cellPostion.x, cellPostion.y)).ToString(), textStyle);Gizmos.DrawWireCube(cellCenter, grid.cellSize);}else{GUIStyle textStyle = new GUIStyle();textStyle.normal.textColor = m_EditorErrorColor;Gizmos.color = m_EditorErrorColor;Handles.Label(cellCenter - halfCellSize, (new Vector2Int(cellPostion.x, cellPostion.y)).ToString(), textStyle);Gizmos.DrawWireCube(cellCenter, grid.cellSize);// 绘制Cell对角线Vector3 from = cellCenter - halfCellSize;Vector3 to = cellCenter + halfCellSize;Gizmos.DrawLine(from, to);float tmpX = from.x;from.x = to.x;to.x = tmpX;Gizmos.DrawLine(from, to);}

最后来看一下我们的成果:

图 3 - 21初步完成地图编辑器

这样我们就初步完成了地图编辑器。记得在MapGraph的“Prefab”上添加我们的MapGraph.cs。

7在菜单中创建我们的地图(Createin Menu)

我们虽然已经有Prefab,但如果在其它项目使用,还要重新添加脚本。我们来创建一个选项直接建立MapGraph,打开MapGraphEditor.cs文件,添加方法。

[MenuItem("GameObject/SRPG/Map Graph", priority = -1)]public static MapGraph CreateMapGraphGameObject(){GameObject mapGraph = new GameObject("MapGraph", typeof(Grid));GameObject tilemap = new GameObject("Tilemap", typeof(Tilemap), typeof(TilemapRenderer));tilemap.transform.SetParent(mapGraph.transform, false);Selection.activeObject = mapGraph;return mapGraph.AddComponent<MapGraph>();}

这只是一个例子,你可以自己选择添加的物体与代码,并设置他们。

图 3 - 22菜单中创建MapGraph

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。