编辑器
根据Prefab自动生成Lua代码
副本逻辑框架
概述 一般副本中都会包括各种各样的行为和判断逻辑,下面举一个简单的副本例子,进入副本后,如果组队人数到达三个,则刷出怪物,播放特效,播放对话,与怪物战斗后,弹出结算UI,再播个动画。 如果由策划出完案子后程序负责直接实现的话,每个副本都是定制化的,写起来非常难受,而且维护极不方便,一旦需求改动就要重新写代码 所以需要设计一套副本系统可以灵活配置副本功能,想法是把副本里的每个行为,判断都作为一个节点,类型行为树的概念,从开始节点一直执行到结束节点,这个副本进程就结束了,进入下个副本进程。 编辑器使用连线的方式操作,每个节点的前后置关系,导出lua配置供程序使用,服务端控制执行流程,客户端只需要执行节点逻辑。 例子 节点可以分为执行节点和判断节点,执行节点执行一个行为,流程推进,判断节点在符合条件的时候,推进流程,否则暂停。 端口定义了每个节点间的关系,也就是图中的连线,控制流程走向,比如刷怪战斗节点,输入端口连接“判断组队人数”,有两个输出端口,分别是胜利和失败,连接对应的节点。 把副本流程解构成一个个节点以后,只需要维护节点的行为和端口逻辑即可,组合由策划完成,只有在添加新节点时程序才需要上场。 编辑器 编辑器可以用网上的开源例子xnode组织节点,也可自己实现。 导出的数据包括了节点的类型,参数,前后置节点等一切代码需要的信息。 副本流程 服务端推送当前节点信息 服务端执行/客户端执行 服务端/客户端完成节点,推进流程 直到到达结束节点
场景数据框架
一、概述 一般MMO的游戏由场景和场景中的对象构成,在设计地图时,就会遇到几个问题。怎么有效直观地配置信息,对象都有什么功能,有什么功能是共有的。针对上面的问题,首先需要解决的是抽象出场景对象结构,在此基础上做编辑器,用图形化的界面展示,修改储存数据,供代码使用。 二、场景对象结构 场景本事有数据,如场景资源,组队人数限制等等,这种信息改动不大,也比较直观,一般只要存在excel里面就好,需要的话可以做图形编辑器对excel进行读写和修改。 场景中的对象可以按需分成几类,常见的有npc,采集物,区域,点等等。 NPC NPC主要承载了显示模型的功能,读取NPC表。 采集物 采集物和NPC差不多,只是对话交互改成了点击采集。 区域 区域分成了圆形区域和多边形区域,划分区域可以实现进入和离开区域的逻辑,比如划分了游泳区,飞行区等,进入以后主角切换运动状态。 点 点的功能比较广泛,主要用于定位,也可以添加触发组件以后实现圆形区域的功能。 组件 组件是对象上的属性和功能,公用的组件有缩放,大小,显示隐藏条件等,特殊的组件有巡逻,交互按钮等,按需添加,从而赋予对象特性。 三、场景编辑器 理清了场景对象的结构以后,就可以做编辑器把数据组织起来了,场景编辑器的实现细节不多说,尽量用起来符合直观操作即可,功能如下。 增删改场景信息 直观展示场景中的对象(位置、模型、范围等) 增删改场景中的对象 修改对象的组件属性 导出程序可用数据 四、数据载体 数据可以直接导出lua,也可以导出成json,xml等,只要方便维护,程序方便调用即可。 目前使用的是先储存到excel,再从excel导出到lua中,供程序调用。 使用excel作为中间载体的原因可能是比较直观,但感觉使用json或者Unity序列化的方法也是可行的。
UGUI对齐工具
前言 上半年的大半年时间都在用FGUI,最近又用回了UGUI,发现FGUI的一些工具还是很高效的,比如对齐工具,想着说移植到UGUI里面去,上网搜了一圈还真有,原理并不复杂,就是进行一些数学计算出最终的位置。 不过也没有完全适合项目需求,针对原项目基础上添加了三个功能,下面介绍一下。 一、撤销功能 原项目的工具在修改后,习惯性就按下Ctrl+Z,发现并不能撤销原来的操作,查询了Unity的API后,发现有个Undo的类,可以实现撤销功能,有很多方法,主要用到了RecordObject,代码如下 private static void SetTranPos(Transform tran, Vector3 pos) { Undo.RecordObject(tran, "modify posstion"); tran.position = pos; } 二、Selection.GameObjects不是按选择顺序的 API中的Selection.GameObjects用于获取当前选中的物件,数组的顺序和我选择的顺序无关,所以不能实现灵活的对齐,查询网上文章可以用Selection.Objects替代,里面的第一个元素就是Ctrl选中的第一个元素,以此类推,代码如下 //按选中顺序获取GameObjects private static GameObject[] GetOrderedSelctionObjs() { return Selection.objects.OfType<GameObject>().ToArray(); } 三、添加对父节点对齐 功能就是和标题一样,原项目是没有的,代码如下 //如果只选中一个,对父节点对齐 if (rects.Count == 1) { if (rects[0].parent != null && rects[0].parent.GetComponent<RectTransform>() != null) { rects.Insert(0, rects[0].parent.GetComponent<RectTransform>()); } } 四、代码 UGUIAlign using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; public enum AlignType { Top = 1, Left = 2, Right = 3, Bottom = 4, HorizontalCenter = 5, //水平居中 VerticalCenter = 6, //垂直居中 Horizontal = 7, //横向分布 Vertical = 8, //纵向分布 } public class UGUIAlign : Editor { [MenuItem("GameObject/UI/Align/Left 【左对齐】")] static void AlignLeft() { Align(AlignType.Left); } [MenuItem("GameObject/UI/Align/HorizontalCenter 【水平居中】")] static void AlignHorizontalCenter() { Align(AlignType.HorizontalCenter); } [MenuItem("GameObject/UI/Align/Right 【右对齐】")] static void AlignRight() { Align(AlignType.Right); } [MenuItem("GameObject/UI/Align/Top 【顶端对齐】")] static void AlignTop() { Align(AlignType.Top); } [MenuItem("GameObject/UI/Align/VerticalCenter 【垂直居中】")] static void AlignVerticalCenter() { Align(AlignType.VerticalCenter); } [MenuItem("GameObject/UI/Align/Bottom 【底端对齐】")] static void AlignBottom() { Align(AlignType.Bottom); } [MenuItem("GameObject/UI/Align/Horizontal 【横向分布】")] static void AlignHorizontal() { Align(AlignType.Horizontal); } [MenuItem("GameObject/UI/Align/Vertical 【纵向分布】")] static void AlignVertical() { Align(AlignType.Vertical); } public static void Align(AlignType type) { List<RectTransform> rects = new List<RectTransform>(); GameObject[] objects = GetOrderedSelctionObjs(); if (objects != null && objects.Length > 0) { for (int i = 0; i < objects.Length; i++) { RectTransform rect = objects[i].GetComponent<RectTransform>(); if (rect != null) rects.Add(rect); } } //如果只选中一个,对父节点对齐 if (rects.Count == 1) { if (rects[0].parent != null && rects[0].parent.GetComponent<RectTransform>() != null) { rects.Insert(0, rects[0].parent.GetComponent<RectTransform>()); } } if (rects.Count > 1) { Align(type, rects); } } //按选中顺序获取GameObjects private static GameObject[] GetOrderedSelctionObjs() { return Selection.objects.OfType<GameObject>().ToArray(); } public static void Align(AlignType type, List<RectTransform> rects) { RectTransform tenplate = rects[0]; float w = tenplate.sizeDelta.x * tenplate.lossyScale.x; float h = tenplate.sizeDelta.y * tenplate.lossyScale.y; float x = tenplate.position.x - tenplate.pivot.x * w; float y = tenplate.position.y - tenplate.pivot.y * h; switch (type) { case AlignType.Top: for (int i = 1; i < rects.Count; i++) { RectTransform trans = rects[i]; float th = trans.sizeDelta.y * trans.lossyScale.y; Vector3 pos = trans.position; pos.y = y + h - th + trans.pivot.y * th; SetTranPos(trans, pos); } break; case AlignType.Left: for (int i = 1; i < rects.Count; i++) { RectTransform trans = rects[i]; float tw = trans.sizeDelta.x * trans.lossyScale.x; Vector3 pos = trans.position; pos.x = x + tw * trans.pivot.x; SetTranPos(trans, pos); } break; case AlignType.Right: for (int i = 1; i < rects.Count; i++) { RectTransform trans = rects[i]; float tw = trans.sizeDelta.x * trans.lossyScale.x; Vector3 pos = trans.position; pos.x = x + w - tw + tw * trans.pivot.x; SetTranPos(trans, pos); } break; case AlignType.Bottom: for (int i = 1; i < rects.Count; i++) { RectTransform trans = rects[i]; float th = trans.sizeDelta.y * trans.lossyScale.y; Vector3 pos = trans.position; pos.y = y + th * trans.pivot.y; SetTranPos(trans, pos); } break; case AlignType.HorizontalCenter: for (int i = 1; i < rects.Count; i++) { RectTransform trans = rects[i]; float tw = trans.sizeDelta.x * trans.lossyScale.x; Vector3 pos = trans.position; pos.x = x + 0.5f * w - 0.5f * tw + tw * trans.pivot.x; SetTranPos(trans, pos); } break; case AlignType.VerticalCenter: for (int i = 1; i < rects.Count; i++) { RectTransform trans = rects[i]; float th = trans.sizeDelta.y * trans.lossyScale.y; Vector3 pos = trans.position; pos.y = y + 0.5f * h - 0.5f * th + th * trans.pivot.y; SetTranPos(trans, pos); } break; case AlignType.Horizontal: float minX = GetMinX(rects); float maxX = GetMaxX(rects); rects.Sort(SortListRectTransformByX); float distance = (maxX - minX) / (rects.Count - 1); for (int i = 1; i < rects.Count - 1; i++) { RectTransform trans = rects[i]; Vector3 pos = trans.position; pos.x = minX + i * distance; SetTranPos(trans, pos); } break; case AlignType.Vertical: float minY = GetMinY(rects); float maxY = GetMaxY(rects); rects.Sort(SortListRectTransformByY); float distanceY = (maxY - minY) / (rects.Count - 1); for (int i = 1; i < rects.Count - 1; i++) { RectTransform trans = rects[i]; Vector3 pos = trans.position; pos.y = minY + i * distanceY; SetTranPos(trans, pos); } break; } } private static void SetTranPos(Transform tran, Vector3 pos) { Undo.RecordObject(tran, "modify posstion"); tran.position = pos; } private static int SortListRectTransformByX(RectTransform r1, RectTransform r2) { float w = r1.sizeDelta.x * r1.lossyScale.x; float x1 = r1.position.x - r1.pivot.x * w; w = r2.sizeDelta.x * r2.lossyScale.x; float x2 = r2.position.x - r2.pivot.x * w; if (x1 >= x2) return 1; else return -1; } private static int SortListRectTransformByY(RectTransform r1, RectTransform r2) { float w = r1.sizeDelta.y * r1.lossyScale.y; float y1 = r1.position.y - r1.pivot.y * w; w = r2.sizeDelta.y * r2.lossyScale.y; float y2 = r2.position.y - r2.pivot.y * w; if (y1 >= y2) return 1; else return -1; } private static float GetMinX(List<RectTransform> rects) { if (null == rects || rects.Count == 0) return 0; RectTransform tenplate = rects[0]; float minx = tenplate.position.x; float tempX = 0; for (int i = 1; i < rects.Count; i++) { tempX = rects[i].position.x; if (tempX < minx) minx = tempX; } return minx; } private static float GetMaxX(List<RectTransform> rects) { if (null == rects || rects.Count == 0) return 0; RectTransform tenplate = rects[0]; float maxX = tenplate.position.x; float tempX = 0; for (int i = 1; i < rects.Count; i++) { tempX = rects[i].position.x; if (tempX > maxX) maxX = tempX; } return maxX; } private static float GetMinY(List<RectTransform> rects) { if (null == rects || rects.Count == 0) return 0; RectTransform tenplate = rects[0]; float minY = tenplate.position.y; float tempX = 0; for (int i = 1; i < rects.Count; i++) { tempX = rects[i].position.y; if (tempX < minY) minY = tempX; } return minY; } private static float GetMaxY(List<RectTransform> rects) { if (null == rects || rects.Count == 0) return 0; RectTransform tenplate = rects[0]; float maxY = tenplate.position.y; float tempX = 0; for (int i = 1; i < rects.Count; i++) { tempX = rects[i].position.y; if (tempX > maxY) maxY = tempX; } return maxY; } } 五、代码下载 https://github.com/dandkong/UGUIAlign ...