本问转载自http://ghostyii.com/uiarrow/,为什么转载呢,怕以后找不到了! ps:博主写到超级详细,复制粘贴即可使用,超赞的!

0x0.引言

屏幕UI指示箭头,非常常见的游戏UI元素。它是一个动态的,可以帮助玩家在目标脱离屏幕范围时指示出大概方位的UI元素。
它在游戏中可以充当多种功能,如在玩家受到伤害时只是伤害来源位置、如显示任务目标点或游戏资源的位置、如在多人游戏中显示队友的位置等等。下图是游戏COD16的一张截图,此图非常直观的显示出了UI Arrow的作用。  

本文将讲述如何在Unity中实现UI指向箭头的效果。

0x1.基础思路

我们希望指示箭头会一直显示在屏幕中,如果目标物体出现在屏幕中时,指示箭头会显示在目标物体的屏幕坐标位置,而目标物体离开屏幕视野时,指示箭头会在屏幕边缘显示其位置。
那么其对应的基本思路是非常明确且清晰的:将目标物体世界坐标转换为对应的屏幕坐标,然后根据其屏幕坐标限制其的显示位置,若坐标不在UI范围内,则限制其坐标点使其先是在边缘位置。

0x2.编码实现

此处的编码我将会一步一步进行实现直至实现最终效果。

0x2.0.坐标转换

本文使用2D物体作为演示目标。
Unity的坐标转换是开发者遇到的非常常见的问题,转换坐标只需要关注不同坐标轴的对应原点和坐标轴朝向即可,具体的计算可以通过Unity提供的API实现。
此处用到的转换关系为:2D世界坐标轴->2D屏幕坐标轴。本文使用的UI为Unity自带的UGUI系统,用到的API为:

RectTransformUtility.WorldToScreenPoint(Camera cam, Vector3 worldPos)

值得注意的一点是,如果将上述API计算得到的值用于RectTransform的anchoredPosition,则需要将该UI元素的锚点设置为left-bottom。这样做的目的是使得两个坐标轴的原点一致,如果想忽略锚点所带来的影响,则直接把计算得到的值赋值给RectTransform.position即可。

0x2.1.限制范围

目前的效果仅仅只是做出了坐标转换,并没有把UI元素限制在视口范围内。现阶段的效果如下:

上图中,红色菱形方块代表世界物块,白色圆形物体代表UI指向箭头。从图中可以发现白色UI始终在屏幕坐标中的正确位置,但是当右侧红色物体移动至屏幕外时,白色物体也跟着移动到了屏幕外。这显然不是想要的效果。
下一步的工作就是将UI元素的位置限制在玩家的屏幕内。
由于使用的是UGUI,所以此处的“玩家屏幕范围”应该特指挂载了Canvas组件的RectTransform的大小。故在脚本中需要引用一个canvas组件。
单纯的限制范围实现非常简单,下面的代码演示了如何将一个UI物体限制在某个指定的矩形范围内。

private Vector2 GetClampPos(Vector2 pos, Rect area)
{Vector2 safePos = Vector2.zero;safePos.x = Mathf.Clamp(pos.x, area.xMin, area.xMax);safePos.y = Mathf.Clamp(pos.y, area.yMin, area.yMax);return safePos;
}

此处关键信息为Rect结构内的xMin,xMax,yMin,yMax等值的确定。上文提到为了得到范围,我们需要获取到挂载了Canvas组件的RectTransform,那么这些值便可以通过其来确定。
则上述代码中的area参数的确定方法如下:

private Rect CalRectByCanvas(Canvas c, Vector2 uiSize)
{Rect rect = Rect.zero;Vector2 area = c.GetComponent<RectTransform>().sizeDelta;//减去uiSize的一半是为了防止UI元素一般溢出屏幕rect.xMax = area.x - uiSize.x / 2;rect.yMax = area.y - uiSize.y / 2;rect.xMin = uiSize.x / 2;rect.yMin = uiSize.y / 2;return rect;
}

值得注意的一点是,如果UI元素的pivot属性不在中心点(0.5,0.5),则上述代码中的计算也要进行相应的变更使得UI元素不会溢出屏幕。

0x2.2.功能实现

坐标转换与限制范围均实现后,直接将其代码结合起来。最终的实现代码也就下面这一句:

rectTrans.position = GetClampPos(RectTransformUtility.WorldToScreenPoint(Camera.main, target.position), CalRectByCanvas(canvas, rectTrans.sizeDelta));

最终实现效果如下:  
其中最左侧显示为游戏实际效果,中间白色区域为UI限制区域,右侧白色区域为摄像头限制区域。

0x3.拓展功能

有些UI指示标中会显示一个箭头,且箭头方向会指向物体方向。通过本方式去改造成该效果也非常简单。
下面将其代码改造为仅当物体在视野外时显示UI标,且拥有一个箭头指向。
检测物体是否处于视野外有多种方式,在Unity中,可以通过内置回调函数void OnBecameVisible()void OnBecameInvisible()来检测物体是否处于渲染状态,但是在此处不适合。因为此处控制脚本挂载在UI元素上。第二种方式是使用Renderer类中的isVisible属性判断。很明显,使用后者满足场景需求。
值得注意的一点是,上述两种方式仅当所有scene视图与game视图中的物体均不可见才会返回false。
添加一个指示箭头无非多一步用于计算方向,Unity中,如果需要朝向计算,在3D中可以使用LookAt方法实现,但是在2D情况下,LookAt方法的转向轴并非正确,下面的代码实现了2D情况下的LookAt方法:

public Vector3 LookAt2D(Transform from, Vector3 to)
{float dx = to.x - from.transform.position.x;float dy = to.y - from.transform.position.y;float rotationZ = Mathf.Atan2(dy, dx) * 180 / Mathf.PI;rotationZ -= 90;float originRotationZ = from.eulerAngles.z;float addRotationZ = rotationZ - originRotationZ;if (addRotationZ > 180)        addRotationZ -= 360;return new Vector3(0, 0, from.eulerAngles.z + addRotationZ);
}

将得到的角度赋值给箭头对象的eulerAngles属性后就得到了一个朝向标。效果如下: 
 
值得注意一点是,箭头的图片默认应当是朝向上方,否则会出现旋转错误。如果箭头方向无法默认向上,则需要修改LookAt2D方法中的计算使得计算以箭头朝向为基准。

0x4.示例代码

//ORG: Ghostyii & MOONLIGHTGAME
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;[RequireComponent(typeof(RectTransform))]
[ExecuteInEditMode]
public class UIArrow : MonoBehaviour
{//Aim targetpublic Transform target;//Canvas for clamp rect areapublic Canvas canvas;//Arrow pointerpublic RectTransform arrow;private RectTransform rectTrans = null;private SpriteRenderer targetRenderer = null;private Vector2 anchoredPosition = Vector2.zero;private Image arrowImg = null;private void Start(){if (!rectTrans)rectTrans = GetComponent<RectTransform>();if (target)targetRenderer = target.GetComponent<SpriteRenderer>();if (canvas)anchoredPosition = canvas.GetComponent<RectTransform>().anchoredPosition;if (arrow)arrowImg = arrow.GetComponent<Image>();}// Update is called once per framevoid Update(){if (!target)return;if (arrowImg)arrowImg.enabled = !targetRenderer.isVisible;var screenPoint = RectTransformUtility.WorldToScreenPoint(Camera.main, target.position);rectTrans.position = GetClampPos(screenPoint, CalRectByCanvas(canvas, rectTrans.sizeDelta));if (arrow)arrow.eulerAngles = LookAt2D(arrow.transform, screenPoint);}private Vector2 GetClampPos(Vector2 pos, Rect area){Vector2 safePos = Vector2.zero;safePos.x = Mathf.Clamp(pos.x, area.xMin, area.xMax);safePos.y = Mathf.Clamp(pos.y, area.yMin, area.yMax);return safePos;}private Rect CalRectByCanvas(Canvas c, Vector2 uiSize){Rect rect = Rect.zero;Vector2 area = c.GetComponent<RectTransform>().sizeDelta;rect.xMax = area.x - uiSize.x / 2;rect.yMax = area.y - uiSize.y / 2;rect.xMin = uiSize.x / 2;rect.yMin = uiSize.y / 2;return rect;}public Vector3 LookAt2D(Transform from, Vector3 to){float dx = to.x - from.transform.position.x;float dy = to.y - from.transform.position.y;float rotationZ = Mathf.Atan2(dy, dx) * 180 / Mathf.PI;rotationZ -= 90;float originRotationZ = from.eulerAngles.z;float addRotationZ = rotationZ - originRotationZ;if (addRotationZ > 180)addRotationZ -= 360;return new Vector3(0, 0, from.eulerAngles.z + addRotationZ);}}

0x5.使用方法

将脚本挂载在UI元素上,赋值上正确的Target,Canvas和Arrow即可预览效果。

----------------------------------

我项目中,没这么复杂。

1. 箭头位置,常驻中 游戏中 下方中间位置

2. target中相机视野内,隐藏箭头; 否则显示。

下面是脚本:

//ORG: Ghostyii & MOONLIGHTGAME
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;[RequireComponent(typeof(RectTransform))]
[ExecuteInEditMode]
public class UIArrow : MonoBehaviour
{public GameObject target;public RectTransform arrow;Image arrowImg = null;public Camera MainCamera;private Vector2 anchoredPosition = Vector2.zero;private void Start(){MainCamera = Camera.main;if (arrow)arrowImg = ObjectCommon.GetChildComponent<Image>(arrow.gameObject, "arrow");UITools.SetActive(arrow, true);}void Update(){if (!target){arrowImg.enabled = false;return;}bool isinview = IsGameObjectInCameraView(target.gameObject, MainCamera);// 目标是否在事业范围内arrowImg.enabled = isinview == false;if (isinview)return;Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(MainCamera, target.transform.position);if (arrow)arrow.eulerAngles = LookAt2D(arrow.transform, screenPoint);}public Vector3 LookAt2D(Transform from, Vector3 to){float dx = to.x - from.transform.position.x;float dy = to.y - from.transform.position.y;float rotationZ = Mathf.Atan2(dy, dx) * 180 / Mathf.PI;rotationZ -= 90;float originRotationZ = from.eulerAngles.z;float addRotationZ = rotationZ - originRotationZ;if (addRotationZ > 180)addRotationZ -= 360;return new Vector3(0, 0, from.eulerAngles.z + addRotationZ);}public static bool IsGameObjectInCameraView(GameObject targetObj, Camera camera = null){if (camera == null)camera = Camera.main;if (camera == null)return false;Vector3 targetObjViewportCoord = camera.WorldToViewportPoint(targetObj.transform.position);if (targetObjViewportCoord.x > 0 && targetObjViewportCoord.x < 1 && targetObjViewportCoord.y > 0f && targetObjViewportCoord.y < 1 && targetObjViewportCoord.z > camera.nearClipPlane && targetObjViewportCoord.z < camera.farClipPlane)return true;return false;}}

【Unity3D】在Unity中实现UI指向箭头相关推荐

  1. Unity中制作UI光晕效果(发光效果)

    Unity中,我们怎么制作UI物体发光的渐隐渐现的效果呢? 比如说我们有一张月亮光晕的精灵图片 我们可以给它添加一个CanvasGroup组件 我们可以发现,组件上的Alpha值可以控制图片的透明度, ...

  2. Unity中的UI相关组件

    一:Canvas:渲染UI --Overlay:覆盖模式 类似于手机贴膜,所有UI都会显示在场景中2D,3D物体的上层 在同一个Canvas下可以调整Canvas子物体的先后顺序,层级面板中越靠上则先 ...

  3. unity中的UI状态机,用于各界面之间的切换和跳转

    首先感谢姜雪松先生,大家可以去他的博客查看注释以及代码等,http://jxwgame.blog.51cto.com/943299/1613585 言归正传: 1.在开发项目的过程中,总是会遇到这样的 ...

  4. Unity中实现UI描边

    一:效果演示 二:使用 MaterialPath:材质路径(需要自己创建材质并设置Shader为UI/Outline) Outline Color:描边颜色 Outline Width:描边宽度 三: ...

  5. Unity 中实现UI左右滑动效果

    借助DoTween实现功能非常方便哦~,记得导入DoTween插件进项目 1.生成所有UI元素 根据配置面板之间的距离,大小,将UI生成到指定位置,这里是将第一个元素生成到屏幕的正中间 public ...

  6. 【Unity3D编辑器扩展】Unity3D中实现UI界面控制,UI界面的显示和隐藏实现

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 QQ群:1040082875 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有 ...

  7. Unity3D研究院之Unity中连接本地或局域网MySQL数据库

    用户名 Email 游戏蛮牛 手机端 开启辅助访问 腾讯QQ 立即注册 登录 用户名 自动登录  找回密码 密码 登录  注册帐号 [Unity5.X版本开始预售啦!] 扫一扫,访问微社区 </ ...

  8. 【Unity3D小功能】Unity3D中实现UI擦除效果、刮刮卡功能

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦. 一.前言 ...

  9. ES6箭头函数中的this指向

    1箭头函数中的this (1)箭头函数中没有this : 这意味着 call() apply() bind() 无法修改箭头函数中的this (2)箭头函数中的this指向 :访问上一个作用域的thi ...

最新文章

  1. 美团外卖美食知识图谱的迭代及应用
  2. 数据恢复knowledge
  3. 2021年下信息系统项目管理师报考和考试时间
  4. 我用着不舒服的东西, 就TM是不合理的东西!!!
  5. delphi tclientsocket接收不到返回数据_RS—485中教你主站发送报文结构、从站返回报文结构?系列11...
  6. [游戏服务器]第一章:多人聊天室-服务端
  7. qjsonarray 合并_QJsonObject和QJsonArray的巨坑
  8. LINUX下载编译lame
  9. python打印日历_Python怎么打印日历?
  10. 退火算法(Annealing)简介与详解
  11. git查看commit提交记录详情
  12. Python兼职:300-800元/天,各行各业都能运用!
  13. 使用任意波形(或函数)发生器产生想要的任意信号
  14. 设计模式二 单例模式
  15. ROS软路由加eNSP模拟华为交换机模拟环境测试
  16. mysql常见函数面试题_MySql三到常见面试题,整理总结一下
  17. opencv 去除背景
  18. ue4 改变枢轴位置_UE4虚幻引擎学习云笔记(五)-静态网格体编辑器
  19. Windows:忘记本地账户开机密码,但记得住PIN码
  20. CAD怎么快速看图呢?怎么快速的查看建筑设计图纸呢?

热门文章

  1. PAT——A1008Elevator(模拟)
  2. virt-manger创建虚拟机及virtio网卡
  3. 社团部部长工作计划计算机学院,社团部长的工作计划(共9篇).doc
  4. Iphone8 plus系统照片为什么电脑打不开 打开heic文件教程
  5. springboot 根据身份证号计算性别和年龄
  6. MediaRecorder录制视频和录音
  7. 蓝桥杯2019年第十届国赛真题-大胖子走迷宫
  8. Mybatis注解开发指北
  9. python操作excel(二):自动填充
  10. Unity Editor 编辑器扩展 五 EditorGUI