效果展示:

  • 上述图中,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);
    }
}

点击下载

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐