一直在学习,几乎边学边忘,再也不想打王者农药了,现在打卡学习持续写博客,现在在开发学习保卫萝卜,这篇文章关于地图编辑器的开发,在项目中不能是无限个场景,用读取xml文件的方法可以说是能实现无限关卡,同时节省很多开发时间缩短周期
一.目录结构
二.创建数据类(放置在Data目录)
首先看一下XML文件内容
根据xml文件节点创建一些数据类,首先创建Point.cs脚本,放置在Data目录中
using UnityEngine;
using System.Collections;
//格子坐标
public class Point
{
public int X;
public int Y;
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}
3.创建Tile.cs脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//格子信息
public class Tile
{
public int X;
public int Y;
public bool CanHold; //是否可以放置塔
public object Data; //格子所保存的数据
public Tile(int x, int y)
{
this.X = x;
this.Y = y;
}
public override string ToString()
{
return string.Format("[X:{0},Y:{1},CanHold:{2}]",
this.X,
this.Y,
this.CanHold
);
}
}
4.创建Round.cs脚本
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class Round
{
public int Monster; //怪物类型ID
public int Count; //怪物数量
public Round(int monster, int count)
{
this.Monster = monster;
this.Count = count;
}
}
5.根据xml文件信息创建关卡类Level.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Level
{
//名字
public string Name;
//背景
public string Background;
//路径
public string Road;
//金币
public int InitScore;
//炮塔可放置的位置
public List<Point> Holder = new List<Point>();
//怪物行走的路径
public List<Point> Path = new List<Point>();
//出怪回合信息
public List<Round> Rounds = new List<Round>();
}
三.创建读写XML工具类
using UnityEngine;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
public class Tools
{
//读取关卡列表
public static List<FileInfo> GetLevelFiles()
{
string[] files = Directory.GetFiles(Consts.LevelDir, "*.xml");
List<FileInfo> list = new List<FileInfo>();
for (int i = 0; i < files.Length; i++)
{
FileInfo file = new FileInfo(files[i]);
list.Add(file);
}
return list;
}
//填充Level类数据
public static void FillLevel(string fileName, ref Level level)
{
FileInfo file = new FileInfo(fileName);
StreamReader sr = new StreamReader(file.OpenRead(), Encoding.UTF8);
XmlDocument doc = new XmlDocument();
doc.Load(sr);
level.Name = doc.SelectSingleNode("/Level/Name").InnerText;
level.Background = doc.SelectSingleNode("/Level/Background").InnerText;
level.Road = doc.SelectSingleNode("/Level/Road").InnerText;
level.InitScore = int.Parse(doc.SelectSingleNode("/Level/InitScore").InnerText);
XmlNodeList nodes;
nodes = doc.SelectNodes("/Level/Holder/Point");
for (int i = 0; i < nodes.Count; i++)
{
XmlNode node = nodes[i];
Point p = new Point(
int.Parse(node.Attributes["X"].Value),
int.Parse(node.Attributes["Y"].Value));
level.Holder.Add(p);
}
nodes = doc.SelectNodes("/Level/Path/Point");
for (int i = 0; i < nodes.Count; i++)
{
XmlNode node = nodes[i];
Point p = new Point(
int.Parse(node.Attributes["X"].Value),
int.Parse(node.Attributes["Y"].Value));
level.Path.Add(p);
}
nodes = doc.SelectNodes("/Level/Rounds/Round");
for (int i = 0; i < nodes.Count; i++)
{
XmlNode node = nodes[i];
Round r = new Round(
int.Parse(node.Attributes["Monster"].Value),
int.Parse(node.Attributes["Count"].Value)
);
level.Rounds.Add(r);
}
sr.Close();
sr.Dispose();
}
//保存关卡
public static void SaveLevel(string fileName, Level level)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
sb.AppendLine("<Level>");
sb.AppendLine(string.Format("<Name>{0}</Name>", level.Name));
sb.AppendLine(string.Format("<Background>{0}</Background>", level.Background));
sb.AppendLine(string.Format("<Road>{0}</Road>", level.Road));
sb.AppendLine(string.Format("<InitScore>{0}</InitScore>", level.InitScore));
sb.AppendLine("<Holder>");
for (int i = 0; i < level.Holder.Count; i++)
{
sb.AppendLine(string.Format("<Point X=\"{0}\" Y=\"{1}\"/>", level.Holder[i].X, level.Holder[i].Y));
}
sb.AppendLine("</Holder>");
sb.AppendLine("<Path>");
for (int i = 0; i < level.Path.Count; i++)
{
sb.AppendLine(string.Format("<Point X=\"{0}\" Y=\"{1}\"/>", level.Path[i].X, level.Path[i].Y));
}
sb.AppendLine("</Path>");
sb.AppendLine("<Rounds>");
for (int i = 0; i < level.Rounds.Count; i++)
{
sb.AppendLine(string.Format("<Round Monster=\"{0}\" Count=\"{1}\"/>", level.Rounds[i].Monster, level.Rounds[i].Count));
}
sb.AppendLine("</Rounds>");
sb.AppendLine("</Level>");
string content = sb.ToString();
StreamWriter sw = new StreamWriter(fileName, false, Encoding.UTF8);
sw.Write(content);
sw.Flush();
sw.Dispose();
}
//加载图片
public static IEnumerator LoadImage(string url, SpriteRenderer render)
{
WWW www = new WWW(url);
while (!www.isDone)
yield return www;
Texture2D texture = www.texture;
Sprite sp = Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f));
render.sprite = sp;
}
}
三.创建编辑器Editor类
1.创建常量类Consts.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
public static class Consts
{
//目录
public static readonly string LevelDir = Application.dataPath + @"\Game\Res\Levels";
public static readonly string MapDir = Application.dataPath + @"\Game\Res\Maps";
}
2.创建Map.cs,此脚本挂在层级试图hierarchy中的Map上
先定义一些字段,属性,和方法
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class Map : MonoBehaviour
{
#region 常量
public const int RowCount = 8; //行数
public const int ColumnCount = 12; //列数
#endregion
#region 字段
float MapWidth;//地图宽
float MapHeight;//地图高
float TileWidth;//格子宽
float TileHeight;//格子高
List<Tile> m_grid = new List<Tile>(); //格子集合
List<Tile> m_road = new List<Tile>(); //路径集合
Level m_level; //关卡数据
public bool DrawGizmos = true; //是否绘制网格
#endregion
#region 属性
public Level Level
{
get { return m_level; }
}
public string BackgroundImage
{
set
{
SpriteRenderer render = transform.Find("Background").GetComponent<SpriteRenderer>();
StartCoroutine(Tools.LoadImage(value, render));
}
}
public string RoadImage
{
set
{
SpriteRenderer render = transform.Find("Road").GetComponent<SpriteRenderer>();
StartCoroutine(Tools.LoadImage(value, render));
}
}
public List<Tile> Grid
{
get { return m_grid; }
}
public List<Tile> Road
{
get { return m_road; }
}
//怪物的寻路路径
public Vector3[] Path
{
get
{
List<Vector3> m_path = new List<Vector3>();
for (int i = 0; i < m_road.Count; i++)
{
Tile t = m_road[i];
Vector3 point = GetPosition(t);
m_path.Add(point);
}
return m_path.ToArray();
}
}
#endregion
#region 方法
public void LoadLevel(Level level)
{
//清除当前状态
Clear();
//保存
this.m_level = level;
//加载图片
this.BackgroundImage = "file://" + Consts.MapDir + "/" + level.Background;
this.RoadImage = "file://" + Consts.MapDir + "/" + level.Road;
//寻路点
for (int i = 0; i < level.Path.Count; i++)
{
Point p = level.Path[i];
Tile t = GetTile(p.X, p.Y);
m_road.Add(t);
}
//炮塔点
for (int i = 0; i < level.Holder.Count; i++)
{
Point p = level.Holder[i];
Tile t = GetTile(p.X, p.Y);
t.CanHold = true;
}
}
//清除塔位信息
public void ClearHolder()
{
foreach (Tile t in m_grid)
{
if(t.CanHold)
t.CanHold = false;
}
}
//清除寻路格子集合
public void ClearRoad()
{
m_road.Clear();
}
//清除所有信息
public void Clear()
{
m_level = null;
ClearHolder();
ClearRoad();
}
#endregion
#region Unity回调
//只在运行期起作用
void Awake()
{
//计算地图和格子大小
CalculateSize();
//创建所有的格子
for (int i = 0; i < RowCount; i++)
for (int j = 0; j < ColumnCount; j++)
m_grid.Add(new Tile(j, i));
}
//只在编辑器里起作用
void OnDrawGizmos()
{
if (!DrawGizmos)
return;
//计算地图和格子大小
CalculateSize();
//绘制格子
Gizmos.color = Color.green;
//绘制行
for (int row = 0; row <= RowCount; row++)
{
Vector2 from = new Vector2(-MapWidth / 2, -MapHeight / 2 + row * TileHeight);
Vector2 to = new Vector2(-MapWidth / 2 + MapWidth, -MapHeight / 2 + row * TileHeight);
Gizmos.DrawLine(from, to);
}
//绘制列
for (int col = 0; col <= ColumnCount; col++)
{
Vector2 from = new Vector2(-MapWidth / 2 + col * TileWidth, MapHeight / 2);
Vector2 to = new Vector2(-MapWidth / 2 + col * TileWidth, -MapHeight / 2);
Gizmos.DrawLine(from, to);
}
foreach (Tile t in m_grid)
{
if (t.CanHold)
{
Vector3 pos = GetPosition(t);
Gizmos.DrawIcon(pos, "holder.png", true);
}
}
Gizmos.color = Color.red;
for (int i = 0; i < m_road.Count; i++)
{
//起点
if (i == 0)
{
Gizmos.DrawIcon(GetPosition(m_road[i]), "start.png", true);
}
//终点
if (m_road.Count > 1 && i == m_road.Count - 1)
{
Gizmos.DrawIcon(GetPosition(m_road[i]), "end.png", true);
}
//红色的连线
if (m_road.Count > 1 && i != 0)
{
Vector3 from = GetPosition(m_road[i - 1]);
Vector3 to = GetPosition(m_road[i]);
Gizmos.DrawLine(from, to);
}
}
}
#endregion
#region 帮助方法
//计算地图大小,格子大小
void CalculateSize()
{
Vector3 leftDown = new Vector3(0, 0);
Vector3 rightUp = new Vector3(1, 1);
Vector3 p1 = Camera.main.ViewportToWorldPoint(leftDown);
Vector3 p2 = Camera.main.ViewportToWorldPoint(rightUp);
MapWidth = (p2.x - p1.x);
MapHeight = (p2.y - p1.y);
TileWidth = MapWidth / ColumnCount;
TileHeight = MapHeight / RowCount;
}
//获取格子中心点所在的世界坐标
Vector3 GetPosition(Tile t)
{
return new Vector3(
-MapWidth / 2 + (t.X + 0.5f) * TileWidth,
-MapHeight / 2 + (t.Y + 0.5f) * TileHeight,
0
);
}
//根据格子索引号获得格子
Tile GetTile(int tileX, int tileY)
{
int index = tileX + tileY * ColumnCount;
if (index < 0 || index >= m_grid.Count)
return null;
return m_grid[index];
}
//获取鼠标下面的格子
Tile GetTileUnderMouse()
{
Vector2 wordPos = GetWorldPosition();
int col = (int)((wordPos.x + MapWidth / 2) / TileWidth);
int row = (int)((wordPos.y + MapHeight / 2) / TileHeight);
return GetTile(col, row);
}
//获取鼠标所在位置的世界坐标
Vector3 GetWorldPosition()
{
Vector3 viewPos = Camera.main.ScreenToViewportPoint(Input.mousePosition);
Vector3 worldPos = Camera.main.ViewportToWorldPoint(viewPos);
return worldPos;
}
#endregion
}
3.在Editor文件夹下创建编辑器类MapEditor.cs
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections;
using System.Collections.Generic;
[CustomEditor(typeof(Map))]
public class MapEditor : Editor
{
[HideInInspector]
public Map Map = null;
//关卡列表
List<FileInfo> m_files = new List<FileInfo>();
//当前编辑的关卡索引号
int m_selectIndex = -1;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if(Application.isPlaying)
{
//关联的Mono脚本组件
Map = target as Map;
EditorGUILayout.BeginHorizontal();
int currentIndex = EditorGUILayout.Popup(m_selectIndex, GetNames(m_files));
if (currentIndex != m_selectIndex)
{
m_selectIndex = currentIndex;
//加载关卡
LoadLevel();
}
if (GUILayout.Button("读取列表"))
{
//读取关卡列表
LoadLevelFiles();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("清除塔点"))
{
Map.ClearHolder();
}
if (GUILayout.Button("清除路径"))
{
Map.ClearRoad();
}
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("保存数据"))
{
//保存关卡
SaveLevel();
}
}
if (GUI.changed)
EditorUtility.SetDirty(target);
}
void LoadLevelFiles()
{
//清除状态
Clear();
//加载列表
m_files = Tools.GetLevelFiles();
//默认加载第一个关卡
if (m_files.Count > 0)
{
m_selectIndex = 0;
LoadLevel();
}
}
void LoadLevel()
{
FileInfo file = m_files[m_selectIndex];
Level level = new Level();
Tools.FillLevel(file.FullName, ref level);
Map.LoadLevel(level);
}
void SaveLevel()
{
//获取当前加载的关卡
Level level = Map.Level;
//临时索引点
List<Point> list = null;
//收集放塔点
list = new List<Point>();
for (int i = 0; i < Map.Grid.Count; i++)
{
Tile t = Map.Grid[i];
if (t.CanHold)
{
Point p = new Point(t.X, t.Y);
list.Add(p);
}
}
level.Holder = list;
//收集寻路点
list = new List<Point>();
for (int i = 0; i < Map.Road.Count; i++)
{
Tile t = Map.Road[i];
Point p = new Point(t.X, t.Y);
list.Add(p);
}
level.Path = list;
//路径
string fileName = m_files[m_selectIndex].FullName;
//保存关卡
Tools.SaveLevel(fileName, level);
//弹框提示
EditorUtility.DisplayDialog("保存关卡数据", "保存成功", "确定");
}
void Clear()
{
m_files.Clear();
m_selectIndex = -1;
}
string[] GetNames(List<FileInfo> files)
{
List<string> names = new List<string>();
foreach (FileInfo file in files)
{
names.Add(file.Name);
}
return names.ToArray();
}
}
4.自此完成了读取XML文件,显示地图和放塔点,接着要做的是完成在编辑器模式下鼠标左键添加或清除放塔点,右键添加或清除路径点
在map.cs脚本中添加一个事件类TileClickEventArgs,继承EventArgs
//鼠标点击参数类
public class TileClickEventArgs : EventArgs
{
public int MouseButton; //0左键,1右键
public Tile Tile;
public TileClickEventArgs(int mouseButton, Tile tile)
{
this.MouseButton = mouseButton;
this.Tile = tile;
}
}
定义事件
#region 事件
public event EventHandler<TileClickEventArgs> OnTileClick;
#endregion
接着在map类中添加事件回调代码
#region 事件回调
void Map_OnTileClick(object sender, TileClickEventArgs e)
{
if (Level == null)
return;
//处理放塔操作
if (e.MouseButton == 0 && !m_road.Contains(e.Tile))
{
e.Tile.CanHold = !e.Tile.CanHold;
}
//处理寻路点操作
if (e.MouseButton == 1 && !e.Tile.CanHold)
{
if (m_road.Contains(e.Tile))
m_road.Remove(e.Tile);
else
m_road.Add(e.Tile);
}
else { }
}
#endregion
在Awake()方法中监听鼠标点击事件
监听鼠标点击事件
OnTileClick += Map_OnTileClick;
然后在Update中检测触发
void Update()
{
//鼠标左键检测
if (Input.GetMouseButtonDown(0))
{
Tile t = GetTileUnderMouse();
if (t != null)
{
//触发鼠标左键点击事件
TileClickEventArgs e = new TileClickEventArgs(0, t);
if (OnTileClick != null)
{
OnTileClick(this, e);
}
}
}
//鼠标右键检测
if (Input.GetMouseButtonDown(1))
{
Tile t = GetTileUnderMouse();
if (t != null)
{
//触发鼠标右键点击事件
TileClickEventArgs e = new TileClickEventArgs(1, t);
if (OnTileClick != null)
{
OnTileClick(this, e);
}
}
}
}
地图编辑器到这里就完成了。第一次写博客完全不知道怎么写,只知道贴代码,就当是自己的学习笔记鼓励自己继续学下去,总比沉迷王者农药要好得多,农药伤身不益智 。mmp—-