实习期间要求让我尝试一下家园系统的前端开发实现。目前做的进度如下:
TMX(XML)解析,创造点图,编辑建筑。
TMXtxt打开就是XML格式,Xml是典型的树形结构,解析xml可以用C#自带的xml类的类方法,解析的过程也就是,树的深度遍历,然后截取关健信息就行了。类方法文章很多,可以瞧瞧,甚至有superTiled2unity这种插件,不过2017版本解析出来有点问题。
解析出的数据一般都是二维数组的形式,根据数组信息生成附带图片的位图就可以了。
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class CreateMap : MonoBehaviour
{
private List<List<int>> bitmap;
public Texture2D tex;
private int row = 10;
private int column = 10;
private float height = 1 ;//菱形内宽
private float length = 1 ;//菱形内高
public Vector3 originalPos;
//public GameObject pos;
// Use this for initialization
private void Awake()
{
originalPos = this.transform.position;
}
void Start () {
if(this.transform.childCount==0)
{
for (int i = 0; i < row; ++i)
{
for (int j = 0; j < column; ++j)
{
GameObject go = new GameObject();
go.transform.parent = this.transform;
go.transform.position =
new Vector3(originalPos.x + j * length - i*length, originalPos.y + j * height+i*height, originalPos.z);
go.name = "mapPos(" + (i+1) + ',' + (j +1)+ ')';//生成菱形的地图,每一个生成的地片以菱形两条对角线的长和高在x和y轴上做偏移。如果是生成六边形的地图,要对奇数列和偶数列分开做偏移操作,大致思路相同
//gird初始化
GridCell gridCell = go.AddComponent<GridCell>();//为地砖加上GridCell类型
gridCell.currentState = GridCell.GridType.Empty;//初始化为空
SpriteRenderer s = go.AddComponent<SpriteRenderer>();//加上精灵渲染器
s.sprite= Sprite.Create(tex,new Rect(0.0f,0.0f,tex.width,tex.height),new Vector2(0.5f,0.5f));
BoxCollider2D boxColl = go.AddComponent<BoxCollider2D>();
// boxColl.isTrigger = true;
}
}
}
}
}
我们可以生成一个大致如下的点图,每个点有collider,和girdCell类
using UnityEngine;
[ExecuteInEditMode]
public class GridCell : MonoBehaviour
{
private SpriteRenderer _spriteRenderer;
private bool change = false;
public enum GridType//地片的状态 可建筑的(空的),已经建筑的,障碍物(不可交互),杂草(需要清理)
{
Empty=0,
Construction=1,
Barrier =2,
Weed=3,
}
public GridType currentState;
public GridType CurrentState
{
set
{
}
get
{
return currentState;
}
}
private void Awake()
{
_spriteRenderer = GetComponent<SpriteRenderer>();
}
private void Update()
{
if(change&&Input.GetMouseButton(0))
colorchange();
}
private void OnTriggerEnter2D(Collider2D a)
{
if (a.CompareTag("construction"))
{
change = true;
}
}
private void colorchange()//可放置时变颜色,提示
{
_spriteRenderer.color = new Color(0f, 1f, 0f, Mathf.Abs(Mathf.Sin(Time.time*3)));
}
private void OnTriggerExit2D(Collider2D other)
{
_spriteRenderer.color = Color.white;
change = false;
}
}
编辑方法
然后分别在mouseManager 和 UImanager中添加方法,以及操作面板的UI
思路 MouseManager 管理鼠标点击事件,传参给 UImanager 在打开ui中的编辑建筑面板。
难点,建筑多点碰撞,
建筑的collider太大 会一次性触碰多个点,这样不是我们想要的效果,所以我们给建筑双collider
一大一小,当拖拽时,就切换成小的collider,避免多碰撞
using UnityEngine;
public class UImanager : MonoBehaviour
{
public static UImanager Instance;
public GameObject ConstructOperationPanel;
// Use this for initialization
private void Awake()
{
singletonMode();
ConstructOperationPanel = this.transform.GetChild(0).gameObject;
}
void Start () {
}
// Update is called once per frame
void Update () {
}
public void ConstructOperationOpen(GameObject go)//建筑操作面板打开
{
if(!ConstructOperationPanel.activeInHierarchy)
{ ConstructOperationPanel.SetActive(true);
ConstructOperationPanel.GetComponent<ConstructionOperation>().construction = go;
ConstructOperationPanel.GetComponent<ConstructionOperation>().oripos = go.transform.position;}
}
public void ConstrucOperationClose()//建筑操作面板关闭暂时没用
{
ConstructOperationPanel.SetActive(false);
ConstructOperationPanel.GetComponent<ConstructionOperation>().construction = null;
}
void singletonMode()//单简单例模式
{
if (Instance == null)
Instance = this;
else
{
Destroy(this);
}
}
}
using UnityEngine;
public class MouseManager : MonoBehaviour
{
public static MouseManager Instance;
public GameObject ConstructionGo;//正在操作的建筑
private Camera mainCam;
public Vector2 mousePoint;
private void Awake()
{
if (Instance == null)
Instance = this;
else
{
Destroy(Instance);
}
}
void Start()
{
}
void Update()
{
OnMouseDragConstruction2D();
}
public Vector3 magneticPos;
private void OnMouseDragConstruction2D() //鼠標拖拽建筑物体移动,要求设置tag为“construction”。
{
//TODO 拖拽物体移动会有一两帧延后。
RaycastHit2D hit;
if (Input.GetMouseButtonDown(0))
{
// Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition),
Vector2.zero)) //精灵检测方法 和 3D不相同。
{
if (hit.collider.CompareTag("construction")) //检测到手误写个分号完全不同,拿到
{
ConstructionGo = hit.collider.gameObject;
UImanager.Instance.ConstructOperationOpen(ConstructionGo);//传值
magneticPos = ConstructionGo.transform.position;
BoxCollider2D[] s2d=ConstructionGo.GetComponents<BoxCollider2D>();//切换物体的collider , 目的是避免碰撞体过大碰撞到多个坐标点,当选中物体时,切换为小一号的碰撞体。
s2d[0].enabled = false;
s2d[1].enabled = true;
}
}
}
else if (Input.GetMouseButton(0) && ConstructionGo)
{
//TODO 吸附效果
//TODO 可放置地区绿色高亮。
mousePoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
ConstructionGo.transform.position =
new Vector3(mousePoint.x, mousePoint.y-0.5f, ConstructionGo.transform.position.z);
if ( hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero))//抓取到地片就跟新吸附点。
{
if ( hit.collider.gameObject.GetComponent<GridCell>().currentState == GridCell.GridType.Empty)
{
magneticPos = hit.transform.position;
}
}
}
else if (Input.GetMouseButtonUp(0))
{
if (ConstructionGo) //有就释放
{
ConstructionGo.transform.position = magneticPos;
BoxCollider2D[] s2d=ConstructionGo.GetComponents<BoxCollider2D>();
s2d[0].enabled = true;
s2d[1].enabled = false;
ConstructionGo = null;
}
}
}
}
using UnityEngine;
using UnityEngine.UI;
public class ConstructionOperation : MonoBehaviour
{
public Button conf;
public Button rev;
public Button rot;
public Button del;
public GameObject construction;
private Vector2 pos;
private RectTransform rectTra;
private Camera cam;
public Vector3 oripos;
//public Vector3 magneticPos;
// Use this for initialization
void Start ()
{
rectTra = this.GetComponent<RectTransform>();
cam = Camera.main;
conf.onClick.AddListener(Confirm);
rev.onClick.AddListener(revacation);
rot.onClick.AddListener(rotation);
del.onClick.AddListener(delete);
}
// Update is called once per frame
void Update ()
{
follow();
}
void follow()
{
if (construction)//如果抓取到了建筑,那么ui跟随建筑移动
{
pos = cam.WorldToScreenPoint(construction.transform.position);//从时间空间转换到屏幕空间
rectTra.position = new Vector2(pos.x, pos.y-20f);//更新Rect Transfrom
// print(pos);
// print(rectTra.position);
}
else
{
return;
}
}
void Confirm()
{
construction = null;
this.gameObject.SetActive(false);
}
void revacation()//返回编辑器最初地点,而不是吸附点
{
construction.transform.position = oripos;
construction = null;
this.gameObject.SetActive(false);
}
void rotation()//2D的旋转可以通过更改UV。offset来实现偏移来实现,或者资源加载新的sprite,这里只做个简单反转示范
{
bool x = construction.GetComponent<SpriteRenderer>().flipX;
if (x)
construction.GetComponent<SpriteRenderer>().flipX = false;
else
{
construction.GetComponent<SpriteRenderer>().flipX = true;
}
}
void delete()
{
Destroy(construction);
construction = null;
this.gameObject.SetActive(false);
}
}
吸附效果如下
还有两个BUG没有完善,
1,地片方法是通过ontriggerenter和exit的方法来控制颜色转换,当点击ui中确定按钮是,直接终止
编辑操作,结束操作后脚下的落脚点颜色还是触发时的绿色。
解决思路:到时候加入编辑管理器到时候做统筹管理和扫尾工作。
2,拖拽吸附编辑效果不够丝滑,如果点太小有时候颜色变绿也没有更新吸附点,大小双collider感觉仍然不是最优解。
解决思路,取消双collider,取消cell中碰撞方法,在MouseManager中解析碰撞体,如果点到地片,改变地片颜色。
!!但是仍然有个问题,鼠标离开后仍然不能复原。或许可以尝试双指针思路用一个慢指针来扫尾?
系统中值得注意的点,
1,当前主程告诉我,对于大型系统的制作就是manager传值传引用和打开方法,在上述系统中进行了尝试,进行单一原则和解耦,但是感觉系统再大些,manger会不会过于庞杂。不知道这是否是业界标准做法。
2,Ontrriger, OnCollsion方法均需要组件 rigidbady