Unity3d开发——保卫萝卜学习笔记一地图编辑器开发

来源:互联网 发布:深圳赛维网络招聘 编辑:程序博客网 时间:2024/04/29 17:42
   一直在学习,几乎边学边忘,再也不想打王者农药了,现在打卡学习持续写博客,现在在开发学习保卫萝卜,这篇文章关于地图编辑器的开发,在项目中不能是无限个场景,用读取xml文件的方法可以说是能实现无限关卡,同时节省很多开发时间缩短周期

一.目录结构

这里写图片描述

二.创建数据类(放置在Data目录)

  1. 首先看一下XML文件内容
    这里写图片描述

  2. 根据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—-