有时后敌人或者传送门等位置超出屏幕外,为了指示这些物体的位置,需要有UI标识这些物体的位置。
这里采用怪物离屏幕边缘最近的点。另一种方式是怪物与玩家的位置连线与屏幕边框的交点,这里暂时不考虑。
using JetBrains.Annotations; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using System.Security.AccessControl; using UnityEngine; using UnityEngine.Animations; /// <summary> /// 图标类型 /// </summary> public enum IconType { OpendDoor, //正在打开的门 EliteDoor, //精英怪门 NpcDoor, //NPC门 BossDoor, //Boss门 RandomDoor, //问号门 HomeDoor } /// <summary> /// 预制物体和图标类型的对应 /// </summary> [System.Serializable] public class PointIconTypePair { public IconType iconType; public GameObject prefab; } /// <summary> /// 图标与目标物体 /// </summary> public class PointIconPair { public Transform icon; public Transform target; public Transform cycleFigure;//圆圈指向,用于指向目标位置 public PointIconPair(Transform icon,Transform target) { this.icon = icon; this.target = target; cycleFigure = icon.Find("CycleFigure_Image"); } /// <summary> /// 按照固定屏幕空间计算设置图标的位置 /// </summary> /// <param name="distanceToEdge"></param> public void UpdateWithScreenSpaceOverlay(float distanceToEdge) { if (!icon || !target) return; Vector3 iPos; Vector3 tPos; bool isAlive = SetAlive(out iPos, distanceToEdge,out tPos); if (!isAlive) return; icon.position = iPos; CycleFigureTarget(tPos); } /// <summary> /// 按照固定屏幕空间计算设置图标的位置 /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <param name="up"></param> /// <param name="down"></param> public void UpdateWithScreenSpaceOverlay(float left,float right,float up,float down) { if (!icon || !target) return; Vector3 iPos; Vector3 tPos; bool isAlive = SetAlive(out iPos,out tPos,left,right,up,down); if (!isAlive) return; icon.position = iPos; CycleFigureTarget(tPos); } /// <summary> /// 按照相机屏幕空间设置图标的位置 /// </summary> /// <param name="distanceToEdge"></param> /// <param name="rect"></param> /// <param name="cam"></param> public void UpdateWithScreenSpaceCamera(float distanceToEdge, RectTransform rect, Camera cam) { if (!icon || !target) return; //////////////////////////// Vector3 iPos; Vector3 tPos; bool isAlive = SetAlive(out iPos, distanceToEdge,out tPos); if (!isAlive) return; var t = Vector2.zero; RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, iPos, cam, out t); icon.GetComponent<RectTransform>().anchoredPosition = t; RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, tPos, cam, out t); CycleFigureTarget(t); } /// <summary> /// 相机空间设置设置图标位置 /// </summary> /// <param name="distanceToEdge"></param> /// <param name="rect"></param> /// <param name="cam"></param> /// <param name="left"></param> /// <param name="right"></param> /// <param name="up"></param> /// <param name="down"></param> public void UpdateWithScreenSpaceCamera(RectTransform rect, Camera cam, float left, float right, float up, float down) { if (!icon || !target) return; //////////////////////////// Vector3 iPos; Vector3 tPos; bool isAlive = SetAlive(out iPos, out tPos, left, right, up, down); if (!isAlive) return; var t = Vector2.zero; RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, iPos, cam, out t); icon.GetComponent<RectTransform>().anchoredPosition = t; RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, tPos, cam, out t); CycleFigureTarget(t); } /// <summary> /// 设置激活状态,根据是否在屏幕内,激活,返回的值就是激活状态 /// </summary> public bool SetAlive(out Vector3 camSpacePos,float distanceToEdge,out Vector3 targetCamSpacePos) { return SetAlive(out camSpacePos,out targetCamSpacePos,distanceToEdge,distanceToEdge,distanceToEdge,distanceToEdge); } /// <summary> /// 获取目标物体与icon的转换在屏幕空间内的位置 /// </summary> /// <param name="camSpacePos"></param> /// <param name="targetCamSpacePos"></param> /// <param name="left">离屏幕左边缘的间隔</param> /// <param name="right">离屏幕右边缘的间隔</param> /// <param name="up">离屏幕上边缘的间隔</param> /// <param name="down">离屏幕下边缘的间隔</param> /// <returns></returns> public bool SetAlive(out Vector3 camSpacePos,out Vector3 targetCamSpacePos, float left,float right,float up,float down) { camSpacePos = new Vector3(); targetCamSpacePos = new Vector3(); Vector3 canvasPos = Camera.main.WorldToScreenPoint(target.position); float width = UnityEngine.Screen.width; float height = UnityEngine.Screen.height; Vector3 iPos = canvasPos; if (iPos.x > 0 && iPos.x < width && iPos.y > 0 && iPos.y < height)//如果在屏幕区域内,那么隐藏物体,不做位置更新 { icon.gameObject.SetActive(false); return false; } /*if (!iconPair.icon.gameObject.activeSelf) */ icon.gameObject.SetActive(true); if (iPos.x > width - right) iPos.x = width - right; else if (iPos.x < left) iPos.x = 0 + left; if (iPos.y > height - up) iPos.y = height - up; else if (iPos.y < down) iPos.y = 0 + down; camSpacePos = iPos; targetCamSpacePos = canvasPos; return true; } void CycleFigureTarget(Vector3 targetPos) { if (!cycleFigure) return; Vector3 dir = targetPos - cycleFigure.position;dir.z = 0; float angle = Vector3.SignedAngle(Vector3.right, dir, Vector3.forward); Quaternion rotation = Quaternion.Euler(0, 0, angle); cycleFigure.rotation = rotation; } } /// <summary> /// 动态图标,指示物体的位置 /// </summary> public class PointIconUI : MonoBehaviour { public List<PointIconTypePair> icons=new List<PointIconTypePair>(); public Dictionary<IconType,List<PointIconPair>> iconPairs; //public float distanceToEdge;//距离边缘的宽度 [Header("icon离四个边缘的空隙大小")] public float left; public float right; public float up; public float down; [Space] [HideInInspector] public Canvas canvas; //public RectTransform canvas; private void Awake() { iconPairs = new Dictionary<IconType, List<PointIconPair>>(); foreach(PointIconTypePair typePair in icons) { if (iconPairs.ContainsKey(typePair.iconType)) continue; iconPairs.Add(typePair.iconType, new List<PointIconPair>()); } Debug.Log(UnityEngine.Screen.width + " ******** " + UnityEngine.Screen.height); //Debug.Log(iconPairs.Count); } /// <summary> /// 更新图标的位置,如果目标物体在屏幕外,那么显示图标在对应的边缘 /// 如果目标物体在屏幕内,那么隐藏图标 /// </summary> private void Update() { if(canvas.renderMode==RenderMode.ScreenSpaceOverlay) UpdateWithScreenSpaceOverlay(); else UpdateWithScreenSpaceCamera(); } void UpdateWithScreenSpaceOverlay() { foreach (List<PointIconPair> list in iconPairs.Values) { foreach (PointIconPair iconPair in list) { //iconPair.UpdateWithScreenSpaceOverlay(distanceToEdge); iconPair.UpdateWithScreenSpaceOverlay(left, right, up, down); } } } void UpdateWithScreenSpaceCamera() { foreach (List<PointIconPair> list in iconPairs.Values) { foreach (PointIconPair iconPair in list) { //iconPair.UpdateWithScreenSpaceCamera(distanceToEdge, transform.GetComponent<RectTransform>(), canvas.worldCamera); iconPair.UpdateWithScreenSpaceCamera(transform.GetComponent<RectTransform>(), canvas.worldCamera, left, right, up, down); } } } /// <summary> /// 添加一系列图标 /// </summary> /// <param name="type"></param> /// <param name="targets"></param> public void CreateIcon(IconType type,List<Transform> targets) { foreach(Transform trans in targets) { CreateIcon(type,trans); } } //添加一个图标 public bool CreateIcon(IconType type,Transform target) { if (!iconPairs.ContainsKey(type)||!target) return false; //获取一个type对应的iconTypePair PointIconTypePair typePair = icons.Find(item => item.iconType == type); if (typePair == null|| !typePair.prefab) return false; //实例化一个iconPair下的预制物体 GameObject icon = Instantiate(typePair.prefab, transform); icon.gameObject.SetActive(true); //将预制物体添加到字典下对应的类型的列表中 iconPairs[type].Add(new PointIconPair(icon.transform,target)); return true; } //清除所有图标 public void Clears() { foreach(List<PointIconPair> list in iconPairs.Values) { if (list == null) continue; foreach(PointIconPair iconPair in list) { Destroy(iconPair.icon.gameObject); Destroy(iconPair.target.gameObject); } list.Clear(); } } }