实现类似原神指引目标位置效果
之前在网上查了一下关于目标指引类的文章。有一篇 Unity在屏幕边界追踪目标提示 文章,已经将核心代码提供了。主要是通过目标物体世界坐标转换成屏幕坐标,将此坐标和摄像机的世界坐标转成的屏幕坐标进行连线,然后判断是否和屏幕有交点来给指引的图片进行赋值。如果是对于一个镜头不需要360°旋转的游戏来说已经完全够用了。但是如果类似原神一样的3D游戏的话,还是会有两个问题:
第一个就是如果目标物体无限平行于镜头的时候,会出现转成的屏幕坐标会从一个很大的负数跳到一个很大的整数,这样出现的效果就会变成从图标从左下角直接跳到了右上角。
第二个问题就是如果目标物体在镜头后方,指引图片也会出现在屏幕中间位置。而我们想要的是指引图片还是会在屏幕边缘。那么根据这两个问题,我们就需要改变一下计算的方法。
所以只要计算目标物体处于摄像机的方位,转换成屏幕方向,通过该方向的延长线和屏幕相交,计算交点就可以避免问题一,第二个问题的话就判断一下目标物体转换成屏幕坐标后的Z值的正负就可以了。
下面上代码。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScreenEdgeTips : MonoBehaviour
{
/// <summary> 图片父物体,最好是全屏的Panel </summary>
private RectTransform directContainer;
/// <summary> 玩家自己或者摄像机标志位 </summary>
private GameObject _cameraTarget;
/// <summary> 目标物体</summary>
public GameObject TargetObj;
/// <summary> 主摄像机 </summary>
public Camera mainCamera;
/// <summary> UI摄像机 </summary>
public Camera uicamera;
private List<Line2D> screenLines;
/// <summary> 在画面内的UI </summary>
public Sprite InImage;
/// <summary> 画面外的UI </summary>
public Sprite OutImage;
public InOrOut ImageType = InOrOut.None;
RectTransform rect;
private Image image;
Vector2 finalPos;
private Vector2 lookPos;
public void Init()
{
_cameraTarget = new GameObject("Target");
_cameraTarget.transform.parent = mainCamera.transform;
_cameraTarget.transform.localPosition = new Vector3(0, 0, 0.5f);
directContainer = transform.parent.GetComponent<RectTransform>();
rect = GetComponent<RectTransform>();
image = GetComponent<Image>();
finalPos = new Vector2();
}
private void Update()
{
UpdateImp();
UpdateLookAt();
}
private void Start()
{
Init();
InitWidth(0);
}
private void InitWidth(float width)
{
Vector3 point1 = new Vector3(width, width);
Vector3 point2 = new Vector3(width, Screen.height- width);
Vector3 point3 = new Vector3(Screen.width- width, Screen.height- width); // P2------------P3
Vector3 point4 = new Vector3(Screen.width- width, width); // | |
this.screenLines = new List<Line2D>(); // | |
this.screenLines.Add(new Line2D(point1, point2)); // P1------------ P4
this.screenLines.Add(new Line2D(point2, point3));
this.screenLines.Add(new Line2D(point3, point4));
this.screenLines.Add(new Line2D(point4, point1));
}
/// <summary>
/// 点是否在屏幕内
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
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;
}
public void UpdateImp()
{
if (_cameraTarget != null)
{
Vector3 fromPos = this.WorldToScreenPoint(_cameraTarget.transform.position);
Vector3 toPos = WorldToScreenPoint(TargetObj.transform.position);
if (PointIsInScreen(toPos))//如果目标在屏幕内
{
if (toPos.z<0)
{
ChangeImage(InOrOut.Out);
CacleIntersce(fromPos);
}
else
{
ChangeImage(InOrOut.In);
RectTransformUtility.ScreenPointToLocalPointInRectangle(directContainer, toPos, uicamera, out finalPos);
rect.anchoredPosition = finalPos;//ui的绝对布局
}
}
else
{
ChangeImage(InOrOut.Out);
CacleIntersce(fromPos);
}
}
}
private void CacleIntersce(Vector3 fromPos)
{
Vector2 intersecPos = Vector2.zero;
Vector3 localpos = _cameraTarget.transform.InverseTransformPoint(TargetObj.transform.position);
Vector3 screenpos = new Vector3(localpos.x, localpos.y, 0);//根据场景坐标不同可以调整
lookPos = fromPos + screenpos.normalized * 10000;
Line2D line2 = new Line2D(fromPos, lookPos);
foreach (Line2D l in this.screenLines)
{
if (line2.Intersection(l, out intersecPos) == Line2D.CROSS)
{
break;
}
}
RectTransformUtility.ScreenPointToLocalPointInRectangle(directContainer, intersecPos, uicamera, out finalPos);
rect.anchoredPosition = finalPos;//ui的绝对布局
}
/// <summary>
/// 改变图片
/// </summary>
private void ChangeImage(InOrOut type)
{
if (type == ImageType)
{
return;
}
switch (type)
{
case InOrOut.In:
image.sprite = InImage;
break;
case InOrOut.Out:
image.sprite = OutImage;
break;
case InOrOut.None:
break;
default:
break;
}
ImageType = type;
}
private void UpdateLookAt()
{
if (TargetObj != null)
{
if (ImageType == InOrOut.Out)
{
rect.right = lookPos.normalized;//根据图片轴向改变
}
}
}
}
public enum InOrOut
{
In, Out, None
}
Line2D脚本从上面连接处博客自行copy即可。
有些数值需要根据项目不同进行调整。有些东西可能会有些许偏差,自行调整即可。