Unity目标点距离计算+屏幕追踪+路线指引
·
效果展示:

- 上述图中,Secne视图显示的为各目标点及Player俯视坐标;
- Game视图左上角显示的为当前目标点与Player的直线距离长度;
- Console面板打印了开启路径、到达指引点、到达最终目标点三种情况,分别用于调用此方法、执行到达每个指引点触发的事件及到达最终目标点事件;
各功能原理:
-
主逻辑:目标点阈值判定、目标点距离计算、画箭头指引路径、屏幕追踪STK
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScreenTracking : MonoBehaviour
{
[Header("玩家")]
public GameObject Player;
[Header("导航箭头3D对象Quad的预制体")]
public GameObject meshRendererPrefab;
private GameObject currentArrow;//当前导航箭头
[Header("箭头的缩放比例")]
public float xscale = 0.5f;
public float yscale = 2f;
[Header("指引点")]
public List<Transform> targets = new List<Transform>();
private int CurrentIndex = 0; //当前目标索引
private bool isActive = false; //限定条件
private GameObject CurrentTargets; //当前指引点
[Header("屏幕追踪预制体")]
public TargetTipWithoutWindow STK;
//玩家与指引点距离
float dis;
private void Update()
{
if (isActive == true) TSDraw();
if (Input.GetKeyDown(KeyCode.Space)) DrawPath(); //测试方法,正式使用时注释掉,调用DrawPath()方法
}
private void TSDraw()
{
//更新导航箭头的路径
DrawPath(new Vector3[] { Player.transform.position, targets[CurrentIndex].position });
//检查玩家是否到达当前目标
float distanceToTarget = Vector3.Distance(Player.transform.position, targets[CurrentIndex].position);
//假设1是到达目标的阈值
if (distanceToTarget < 1f)
{
//到达目标后隐藏路径
currentArrow.SetActive(false);
//关闭当前指引点
targets[CurrentIndex].gameObject.SetActive(false);
//判断是否到达最终目标点
if (CurrentIndex + 1 < targets.Count)
{
Debug.Log(string.Format("<color=yellow>{0}</color>", "到达指引点"));
//开启下一个指引点
targets[CurrentIndex + 1].gameObject.SetActive(true);
CurrentTargets = targets[CurrentIndex + 1].gameObject;
//切换到下一个目标
CurrentIndex = (CurrentIndex + 1) % targets.Count;
//更新导航箭头的路径
DrawPath(new Vector3[] { Player.transform.position, targets[CurrentIndex].position });
}
else
{
isActive = false;
//关闭屏幕追踪
STK.TargetTransform = null;
Debug.Log(string.Format("<color=cyan>{0}</color>", "到达最终目标点"));
return;
}
}
//屏幕追踪
STK.TargetTransform = CurrentTargets.transform;
//距离
dis = (Player.transform.position - CurrentTargets.transform.position).magnitude;
}
void OnGUI()
{
GUIStyle fontStyle = new GUIStyle();
fontStyle.alignment = TextAnchor.UpperLeft;
fontStyle.fontSize = 60;
fontStyle.fontStyle = FontStyle.Bold;
fontStyle.normal.textColor = new Color(50 / 255f, 50 / 255f, 50 / 255f, 1);
if (isActive == false)
{
GUI.Label(new Rect(20, 20, 60, 60), "按下空格键开始导航指引", fontStyle);
}
if (isActive == true)
{
GUI.Label(new Rect(20, 20, 60, 60), "鼠标右键旋转", fontStyle);
GUI.Label(new Rect(20, 100, 60, 60), "键盘WASD移动", fontStyle);
GUI.Label(new Rect(20, 180, 60, 60), string.Format("距离:{0:0.00}米", dis), fontStyle);
}
}
/// <summary>
/// 划路径外部调用方法
/// </summary>
public void DrawPath()
{
isActive = true;
//设置索引为0
CurrentIndex = 0;
//激活第一个指引点
targets[CurrentIndex].gameObject.SetActive(true);
//设置屏幕追踪的物体为第一个指引点
CurrentTargets = targets[CurrentIndex].gameObject;
//创建箭头
currentArrow = Instantiate(meshRendererPrefab, Vector3.zero, Quaternion.identity);
//初始时隐藏导航箭头
currentArrow.SetActive(false);
//画路径
DrawPath(new Vector3[] { Player.transform.position, targets[CurrentIndex].position });
//屏幕追踪
STK.TargetTransform = targets[CurrentIndex].transform;
Debug.Log(string.Format("<color=cyan>{0}</color>", "开始指引"));
}
/// <summary>
/// 画路径的实现
/// </summary>
private void DrawPath(Vector3[] points)
{
if (points == null || points.Length <= 1)
return;
var start = points[0];
var end = points[1];
var length = Vector3.Distance(start, end);
currentArrow.transform.localScale = new Vector3(xscale, length, 1);
currentArrow.transform.position = (start + end) / 2;
//指向end
currentArrow.transform.LookAt(end);
//旋转偏移
currentArrow.transform.Rotate(90, 0, 0);
currentArrow.GetComponent<Renderer>().sharedMaterial.mainTextureScale = new Vector2(1, length * yscale);
currentArrow.SetActive(true);
}
}
-
屏幕追踪C#代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TargetTipWithoutWindow : MonoBehaviour
{
public Transform TargetTransform;
public Image img;
public float offsetRight, offsetLeft, offsetUp, offsetDown;
private void LateUpdate()
{
Method2();
}
private void Method2()
{
Transform camTransform = Camera.main.transform;
var vFov = Camera.main.fieldOfView;
var radHFov = 2 * Mathf.Atan(Mathf.Tan(vFov * Mathf.Deg2Rad / 2) * Camera.main.aspect);
var hFov = Mathf.Rad2Deg * radHFov;
if (TargetTransform==null)
{
return;
}
Vector3 deltaUnitVec = (TargetTransform.position - camTransform.position).normalized;
float vdegobj = Vector3.Angle(Vector3.up, deltaUnitVec) - 90f;
float vdegcam = Vector3.SignedAngle(Vector3.up, camTransform.forward, camTransform.right) - 90f;
float vdeg = vdegobj - vdegcam;
float hdeg = Vector3.SignedAngle(Vector3.ProjectOnPlane(camTransform.forward, Vector3.up), Vector3.ProjectOnPlane(deltaUnitVec, Vector3.up), Vector3.up);
vdeg = Mathf.Clamp(vdeg, -89f, 89f);
hdeg = Mathf.Clamp(hdeg, hFov * -0.5f, hFov * 0.5f);
Vector3 projectedPos = Quaternion.AngleAxis(vdeg, camTransform.right) * Quaternion.AngleAxis(hdeg, camTransform.up) * camTransform.forward;
Debug.DrawLine(camTransform.position, camTransform.position + projectedPos, Color.red);
Vector3 newPos = Camera.main.WorldToScreenPoint(camTransform.position + projectedPos);
if (newPos.x > Screen.width - offsetRight || newPos.x < offsetLeft || newPos.y > Screen.height - offsetUp || newPos.y < offsetDown)
newPos = KClamp(newPos);
else
{
img.gameObject.SetActive(false);
}
img.transform.position = newPos;
}
private Vector3 KClamp(Vector3 newPos)
{
img.gameObject.SetActive(true);
Vector2 center = new Vector2(Screen.width / 2, Screen.height / 2);
float k = (newPos.y - center.y) / (newPos.x - center.x);
if (newPos.y - center.y > 0)
{
newPos.y = Screen.height - offsetUp;
newPos.x = center.x + (newPos.y - center.y) / k;
}
else
{
newPos.y = offsetDown;
newPos.x = center.x + (newPos.y - center.y) / k;
}
if (newPos.x > Screen.width - offsetRight)
{
newPos.x = Screen.width - offsetRight;
newPos.y = center.y + (newPos.x - center.x) * k;
}
else if (newPos.x < offsetLeft)
{
newPos.x = offsetLeft;
newPos.y = center.y + (newPos.x - center.x) * k;
}
return newPos;
}
}
-
Player控制C#代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player_Controller : MonoBehaviour
{
[Header("相机挂载此脚本,Player为相机父物体")]
public Transform Player;
[Header("相机旋转灵敏度调节值")]
public float sensitivity=5f;
[Header("移动速度")]
public float movespeed=0.02F;
[Header("相机上下旋转角度")]
public float AngleUP = 50f;
public float AngleDown = 50f;
//鼠标XY
private float mouseX, mouseY;
//数值缓存
private float tempFloat;
private void Update()
{
if (Input.GetMouseButton(1))
{
mouseX = Input.GetAxis("Mouse X") * sensitivity;
mouseY = Input.GetAxis("Mouse Y") * sensitivity;
//使用插值减少晃动
tempFloat -= mouseY;
//限制上下旋转角度
tempFloat = Mathf.Clamp(tempFloat, -AngleUP, AngleDown);
//父物体旋转
Player.Rotate(Vector3.up * mouseX);
//相机自身上下旋转
transform.localRotation = Quaternion.Euler(tempFloat, 0, 0);
}
if (Input.GetKey(KeyCode.A))
{
Player.transform.position += Player.transform.right * -movespeed;
}
else if (Input.GetKey(KeyCode.D))
{
Player.transform.position += Player.transform.right * movespeed;
}
else if (Input.GetKey(KeyCode.W))
{
Player.transform.position += Player.transform.forward * movespeed;
}
else if (Input.GetKey(KeyCode.S))
{
Player.transform.position += Player.transform.forward * -movespeed;
}
}
}
-
动态箭头Shader
Shader "Custom/NavPathArrow"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_ScrollYSpeed("Y Scroll Speed", Range(-20, 20)) = 2
}
SubShader
{
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
LOD 100
//双面渲染
Cull Off
//Alpha混合
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _ScrollYSpeed;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed2 uv = i.uv;
uv.y += _ScrollYSpeed * _Time;
fixed4 col = tex2D(_MainTex, uv);
return col;
}
ENDCG
}
}
}
-
动态光圈C#代码
using UnityEngine;
public class BeamRotator : MonoBehaviour
{
[Tooltip("Amount of degrees to rotate around the rotation axis per second.")]
public float degreesPerSecond = 60.0f;
[Tooltip("The rotation axis to rotate the object around.")]
public Vector3 rotationAxis = Vector3.up;
protected virtual void OnEnable()
{
rotationAxis.Normalize();
}
protected virtual void Update()
{
transform.Rotate(rotationAxis, degreesPerSecond * Time.deltaTime);
}
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)