1、首先需要解决两条线段相交的逻辑判定
2、按照方向指示类型的不同,进行不同的判定,以及显示处理
3、按不同类型处理
①需求1:在屏幕边缘,显示屏幕外进攻的敌人的来向
需要敌人点和己方点连线 与 屏幕边缘线段做相交点判定处理
②需求2:在屏幕边缘,提示玩家前进的方向
需要目标点和己方点连线 与 屏幕边缘线段做相交点判定处理
一、单个提示标记和线段相交逻辑文件如下(ScreenEdgeTips.cs)
using System; using UnityEngine; using System.Collections; using System.Collections.Generic; public class Line2D { //共线 public const int COLINE = 0; //相交 public const int CROSS = 1; //平行线 public const int PARALLEL = 2; //无相交 public const int NOT_CROSS = 3; private double EPS = 1e-4; public Vector3 point1; public Vector3 point2; private float A; private float B; private float C; public Line2D(Vector3 point1, Vector3 point2) { this.point1 = point1; this.point2 = point2; this.calcCoefficient(); } private void calcCoefficient() { this.A = this.point2.y - this.point1.y; this.B = this.point1.x - this.point2.x; this.C = this.point2.x * this.point1.y - this.point1.x * this.point2.y; } private bool checkCross(Vector3 sp1, Vector3 ep1, Vector3 sp2, Vector3 ep2) { if (Math.Max(sp1.x, ep1.x) < Math.Min(sp2.x, ep2.x)){ return false; } if (Math.Min(sp1.x, ep1.x) > Math.Max(sp2.x, ep2.x)){ return false; } if (Math.Max(sp1.y, ep1.y) < Math.Min(sp2.y, ep2.y)){ return false; } if (Math.Min(sp1.y, ep1.y) > Math.Max(sp2.y, ep2.y)){ return false; } Vector3 vectorA = sp1 - sp2; Vector3 vectorB = ep2 - sp2; Vector3 vectorC = ep2 - sp2; Vector3 vectorD = ep1 - sp2; double temp1 = (vectorA.x * vectorB.y - vectorA.y * vectorB.x) * (vectorC.x * vectorD.y - vectorC.y * vectorD.x); vectorA = sp2 - sp1; vectorB = ep1 - sp1; vectorC = ep1 - sp1; vectorD = ep2 - sp1; double temp2 = (vectorA.x * vectorB.y - vectorA.y * vectorB.x) * (vectorC.x * vectorD.y - vectorC.y * vectorD.x); if ((temp1 >= 0) && (temp2 >= 0)) { return true; } return false; } private bool isDoubleEqualZero(double data) { if (Math.Abs(data) <= EPS){ return true; }else{ return false; } } public int Intersection(Line2D otherLine, out Vector3 intersectantPoint) { intersectantPoint = Vector3.zero; if (!checkCross(this.point1, this.point2, otherLine.point1, otherLine.point2)) { return Line2D.NOT_CROSS; } if (isDoubleEqualZero(this.A * otherLine.B - this.B * otherLine.A)) { if (isDoubleEqualZero((this.A + this.B) * otherLine.C - (otherLine.A + otherLine.B) * this.C)) { return Line2D.COLINE; } else { return Line2D.PARALLEL; } } else { intersectantPoint.x = (otherLine.B * this.C - this.B * otherLine.C) / (otherLine.A * this.B - this.A * otherLine.B); intersectantPoint.y = (this.A * otherLine.C - otherLine.A * this.C) / (otherLine.A * this.B - this.A * otherLine.B); intersectantPoint.z = 0; return Line2D.CROSS; } } } public class ScreenEdgeTips : MonoBehaviour { //UI public UILabel distanceLabel; public GameObject directContainer; public float prefabWidth; //cameras private Camera mainCamera; private Camera uicamera; //param private bool isIdle = false; private int type = 0; private int fromActorId = 0; private int toActorId = 0; private Vector3 goalPoint; private List<Line2D> screenLines; public ScreenEdgeTips() { this.isIdle = true; } private void InitImp() { float offsetWidth = prefabWidth / 2; float originalPoint = 0 + offsetWidth; float correctionWidth = Screen.width - offsetWidth; float correctionHeight = Screen.height - offsetWidth; Vector3 point1 = new Vector3(offsetWidth, offsetWidth, 0); Vector3 point2 = new Vector3(offsetWidth, correctionHeight, 0); Vector3 point3 = new Vector3(correctionWidth, correctionHeight, 0); Vector3 point4 = new Vector3(correctionWidth, offsetWidth, 0); this.screenLines = new List<Line2D>(); this.screenLines.Add(new Line2D(point1, point2)); this.screenLines.Add(new Line2D(point2, point3)); this.screenLines.Add(new Line2D(point3, point4)); this.screenLines.Add(new Line2D(point4, point1)); this.mainCamera = Camera.main; this.uicamera = NGUITools.FindCameraForLayer(LayersDefine.Ngui); this.isIdle = false; } public void Init(int type, int fromActorId, int toActorId) { this.InitImp(); this.type = type; this.fromActorId = fromActorId; this.toActorId = toActorId; this.UpdateImp(); } public void Init(int type, int fromActorId, Vector3 goalPoint) { this.InitImp(); this.type = type; this.fromActorId = fromActorId; this.goalPoint = goalPoint; this.UpdateImp(); } public bool GetIsIdle() { return this.isIdle; } private void SetIsIdle(bool isIdle) { this.isIdle = isIdle; if(isIdle) { this.type = 0; this.fromActorId = 0; gameObject.SetActive(false); } } public int GetType() { return this.type; } private bool PointIsInScreen(Vector3 pos) { if (pos.x <= this.screenLines[0].point1.x || pos.x >= this.screenLines[1].point2.x || pos.y <= this.screenLines[0].point1.y || pos.y >= this.screenLines[1].point2.y) { return false; } return true; } private Vector3 WorldToScreenPoint(Vector3 pos) { if (null != this.mainCamera) { return mainCamera.WorldToScreenPoint (pos); } return Vector3.zero; } private Vector3 ScreenToUIPoint(Vector3 pos) { if (null != this.uicamera) { Vector3 uiWorldPosInUISpace = this.uicamera.ScreenToWorldPoint(pos); uiWorldPosInUISpace.z = 0f; return uiWorldPosInUISpace; } return Vector3.zero; } public void UpdateImp() { bool isIntersce = false; GameObject fromHero = ActorManager.Ins.GetActor(fromActorId); GameObject toHero = null; if (fromHero != null) { Vector3 intersecPos = new Vector3(); Vector3 fromPos = this.WorldToScreenPoint(fromHero.transform.position); Vector3 toPos = Vector3.zero; if (this.toActorId == 0) { toPos = this.WorldToScreenPoint(this.goalPoint); } else { toHero = ActorManager.Ins.GetActor(toActorId); if (toHero != null) { toPos = this.WorldToScreenPoint(toHero.transform.position); } else { this.SetIsIdle(true); } } if (toPos != Vector3.zero && this.PointIsInScreen(fromPos)) { Line2D line = new Line2D(fromPos, toPos); foreach (Line2D l in this.screenLines) { if (line.Intersection(l, out intersecPos) == Line2D.CROSS) { isIntersce = true; break; } } if (isIntersce) { transform.position = this.ScreenToUIPoint(intersecPos); if (directContainer != null) { if (this.type == 1) { float angle = Vector3.Angle(toPos - fromPos, Vector3.right); directContainer.transform.rotation = new Quaternion(); directContainer.transform.Rotate(Vector3.forward * angle); } else { float angle = Vector3.Angle(fromPos - toPos, Vector3.right); directContainer.transform.rotation = new Quaternion(); directContainer.transform.Rotate(Vector3.back * angle); } } if (this.distanceLabel != null && toHero != null) { this.distanceLabel.text = String.Format("{0}米", Math.Round((toHero.transform.position - fromHero.transform.position).magnitude)); } } } } else { this.SetIsIdle(true); } this.SetIsIdle(false); gameObject.SetActive(isIntersce); } }
二、多个提示的刷新、管理(增删)的逻辑处理文件(ScreenEdgeTipManager.cs)
using UnityEngine; using System.Collections; using System.Collections.Generic; public class ScreenEdgeTipManager : MonoBehaviourX { private Dictionary<int, string> prefabNameMap = new Dictionary<int, string>(); private Dictionary<int, GameObject> prefabMap = new Dictionary<int, GameObject>(); private ScreenEdgeTips ToGoalCtrl; private static ScreenEdgeTipManager instance = null; public static ScreenEdgeTipManager Ins { get { return instance; } } ScreenEdgeTipManager() { instance = this; } void Start() { scope.Listen("BattleOver", OnBattleOver); scope.Listen("GameShutdown", OnBattleOver); prefabNameMap.Add(1, UIDefine.BATTLE_TIPS_GOAL); prefabNameMap.Add(2, UIDefine.BATTLE_TIPS_MONSTER); prefabNameMap.Add(3, UIDefine.BATTLE_TIPS_RAIDER); } private void OnBattleOver(object[] args) { ClearAll(); } private void ClearAll() { gameObject.DestroyAllChildGameObject(); prefabMap.Clear(); NGUITools.SetActiveChildren(gameObject, false); } private GameObject GetPrefab(int type) { if (!prefabMap.ContainsKey(type)) { GameObject tipsTemplate = null; tipsTemplate = (GameObject)ResourceMgr.LoadPrefab(prefabNameMap[type]); if (null != tipsTemplate) { prefabMap.Add(type, tipsTemplate); } } return prefabMap[type]; } private ScreenEdgeTips GetNewTips(int type) { GameObject tipsTemplate = this.GetPrefab(type); GameObject go = GameObject.Instantiate(tipsTemplate) as GameObject; go.transform.parent = transform; go.layer = LayersDefine.Ngui; go.transform.localScale = Vector3.one; return go.GetComponent<ScreenEdgeTips>(); } private ScreenEdgeTips GetTipsCtrl(int type) { if (type == 0) { return null; } //根据type复用 或 创建新的提示 //尝试复用一个已经创建过的UI_BloodUp int count = transform.childCount; ScreenEdgeTips findTips = null; for (int i = 0; i < count; ++i) { GameObject obj = transform.GetChild(i).gameObject; if (!obj.activeSelf) { ScreenEdgeTips et = obj.GetComponent<ScreenEdgeTips>(); if (et != null && et != this.ToGoalCtrl && et.GetType() == type && et.GetIsIdle()) { findTips = et; } break; } } //不存在时,创建新的tips模板 return null != findTips ? findTips : this.GetNewTips(type); } private void _CreateTargetTips(int type, int fromActorId, int toActorId) { ScreenEdgeTips findTips = this.GetTipsCtrl(type); if (findTips != null) { findTips.Init(type, fromActorId, toActorId); } } private ScreenEdgeTips GetToGoalCtrl() { this.ToGoalCtrl = this.ToGoalCtrl != null ? this.ToGoalCtrl : this.GetNewTips(1); return this.ToGoalCtrl; } private void _CreatePointTips(int fromActorId, Vector3 goalPoint) { ScreenEdgeTips findTips = this.GetToGoalCtrl(); if (findTips != null) { findTips.Init(1, fromActorId, goalPoint); } } void Update() { int count = transform.childCount; for (int i = 0; i < count; ++i) { GameObject obj = transform.GetChild(i).gameObject; ScreenEdgeTips et = obj.GetComponent<ScreenEdgeTips>(); if (et != null) { et.UpdateImp(); } } } public static void CreateTargetTips(int type, int fromActorId, int toActorId) { if (instance) { instance._CreateTargetTips(type, fromActorId, toActorId); } } public static void CreatePointTips(int fromActorId, Vector3 goalPoint) { if (instance) { instance._CreatePointTips(fromActorId, goalPoint); } } }
************************************下面是用到到的基础类,不用也没关系*************************************
三、使用到的显示基类(MonoBehaviourX.cs)
using System; using UnityEngine; public class MonoBehaviourX: MonoBehaviour { protected EventScope scope = null; protected MonoBehaviourX() { scope = Eventer.Create(); } protected void OnDestroy() { scope.Destroy(); scope= null; } }
四、使用到的事件处理类(EventScope.cs)
using System; using System.Collections.Generic; public class EventScope { public Dictionary<string, DelegateObjList> eventTable = new Dictionary<string, DelegateObjList>(); private EventScope parent = null; private List<EventScope> childer = new List<EventScope>(); public EventScope(EventScope _parent) { parent = _parent; } public EventScope CreateChild() { EventScope scope = new EventScope(this); childer.Add(scope); return scope; } private void RemoveParentEvents(string name, Delegate deleObject) { if (parent == null) { return; } DelegateObjList list; if (parent.eventTable.TryGetValue(name, out list)) { list.Remove(deleObject); } parent.RemoveParentEvents(name, deleObject); } public void ClearEvent() { foreach (var et in eventTable) { foreach (var _delegate in et.Value.events) { RemoveParentEvents(et.Key, _delegate); } } eventTable.Clear(); } public void Destroy() { ClearEvent(); if (parent != null) { parent.childer.Remove(this); } } public void Listen(string name, CALLBACK handler) { if (handler == null) { return; } DelegateObjList dol; if (!eventTable.TryGetValue(name, out dol)) { dol = new DelegateObjList(); eventTable [name] = dol; } dol.Add(handler); if (parent != null) { parent.Listen(name, handler); } } }
五、使用到的事件广播类(Eventer.cs)
using System; using System.Collections.Generic; public class Eventer { static public EventScope globe = new EventScope(null); static public EventScope Create() { return globe.CreateChild(); } public static void Fire(string name, object[] args) { DelegateObjList dol; if (globe.eventTable.TryGetValue(name, out dol)) { dol.Enter(); int count = dol.events.Count; for (int i=0; i<count; ++i) { CALLBACK callback = dol.events [i] as CALLBACK; callback(args); } dol.Leave(); } } public static void Fire(string name) { object[] args = new object[0] {}; Fire(name, args); } // ---------------- 以下接口提供给Lua调用 ---------------------- public static void _Fire(string name) { object[] args = new object[0] {}; Fire(name, args); } public static void _Fire(string name, object arg1) { object[] args = new object[1] { arg1 }; Fire(name, args); } public static void _Fire(string name, object arg1, object arg2) { object[] args = new object[2] { arg1, arg2 }; Fire(name, args); } public static void _Fire(string name, object arg1, object arg2, object arg3) { object[] args = new object[3] { arg1, arg2, arg3 }; Fire(name, args); } public static void _Fire(string name, object arg1, object arg2, object arg3, object arg4) { object[] args = new object[4] { arg1, arg2, arg3, arg4 }; Fire(name, args); } public static void _Fire(string name, object arg1, object arg2, object arg3, object arg4, object arg5) { object[] args = new object[5] { arg1, arg2, arg3, arg4, arg5 }; Fire(name, args); } public static void _Fire(string name, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6) { object[] args = new object[6] { arg1, arg2, arg3, arg4, arg5, arg6 }; Fire(name, args); } }
六、使用到的回调处理类(DelegateObjList)
using System; using System.Collections.Generic; public delegate void CALLBACK(object[] args); public class DelegateObjList { public class DynamicDelegate { public Delegate callback; public bool append; } public List<Delegate> events = new List<Delegate>(); public List<DynamicDelegate> delayProcesList = null; public bool accessEvent = false; private void AddDynamicDelegate(Delegate dele, bool append) { if (delayProcesList == null) { delayProcesList = new List<DynamicDelegate>(); } DynamicDelegate dd = new DynamicDelegate(); dd.append = append; dd.callback = dele; delayProcesList.Add(dd); } public void Add(Delegate c) { if (accessEvent) { AddDynamicDelegate(c, true); } else { events.Add(c); } } public void Remove(Delegate c) { if (accessEvent) { AddDynamicDelegate(c, false); } else { events.Remove(c); } } public void Enter() { accessEvent = true; } public void Leave() { accessEvent = false; if (delayProcesList == null) { return; } int count = delayProcesList.Count; if (count == 0) { return; } for (int i=0; i<count; ++i) { var dp = delayProcesList [i]; if (dp.append) { events.Add(dp.callback); } else { events.Remove(dp.callback); } } delayProcesList.Clear(); } }