AimSystem Unity瞄准场景中的物体并进行交互的系统

最终效果: 当我们对准场景中的一个物体时,可以显示这个物体的名称或者对这个物体的描述。如图所示,我们对准了场景中的一个桌子,对这个桌子的描述:is a aimable gameobject. 会显示在一旁。除此之外,我们可以添加瞄准进入事件,瞄准离开事件,瞄准停留事件,

最终效果:

当我们对准场景中的一个物体时,可以显示这个物体的名称或者对这个物体的描述。如图所示,我们对准了场景中的一个桌子,对这个桌子的描述:is a aimable gameobject. 会显示在一旁。除此之外,我们可以添加瞄准进入事件,瞄准离开事件,瞄准停留事件,鼠标点击事件。

思路:

实现该系统所需要编写的几个内容:

        1.AimSystem 其实就是一个射线检测系统,不停的去检测场景中可以进行瞄准的物体。

        2.IAimableObject 可以瞄准的物体所继承的接口。

        3.AimSystemPanel  UI界面 用来显示瞄准物体的名称或者描述。

AimSystem:

using KFramework;
using UnityEngine;

/// <summary>
/// 瞄准系统
/// </summary>
public class AimSystem : MonoBehaviour
{
    public static AimSystem Instance { get; private set; }

    private void Awake() => Instance = this;

    //瞄准距离 (即射线检测的距离、检测范围)
    readonly float mAimDistance = 5f;
    /// <summary>
    /// 当前所瞄准的物体
    /// </summary>
    public IAimableObject CurrentAimableObject { get; private set; }
    //射线
    Ray ray;
    //是否检测到碰撞
    bool isHit;
    //碰撞信息
    RaycastHit hit;

    /*  射线起点
     *  从屏幕正中央发出射线
     *  (0.5f,0.5f) 
     */
    Vector2 mCentralPoint = Vector2.one * 0.5f;

    //UI界面
    AimSystemPanel panel;

    private void Start()
    {
        //打开界面
        panel = UIMgr.OpenPanel<AimSystemPanel>(prefabName: "Resources/AimSystemPanel");
    }

    public void AimEnter(string str) => panel.AimEnter(str);

    public void AimExit() => panel.AimExit();

    private void Update()
    {
        //从主相机视角中央发出射线
        ray = Camera.main.ViewportPointToRay(mCentralPoint);
        //是否检测到碰撞
        isHit = Physics.Raycast(ray, out hit, mAimDistance);
        if (isHit)
        {
            /*  所检测到的物体是否继承 IAimableObject 接口
             *  即是否为可瞄准物体   
             */
            GameObject aimTarget = hit.collider.gameObject;
            IAimableObject aimObject = aimTarget.GetComponent<IAimableObject>();

            //如果可瞄准物体所设定的可被检测距离 小于 当前实际距离 将其置为空 表示没有检测到
            (null != aimObject && aimObject.AimDistance < hit.distance).Invoke(() => aimObject = null);

            //如果与当前可瞄准物体不一致 表示检测到新的可瞄准物体
            if (aimObject != CurrentAimableObject)
            {
                /* 
                 * 如果上一个可瞄准物体不为空  执行其瞄准离开事件
                 * 将当前可瞄准物体 赋值为新检测到的
                 * 执行当前可瞄准物体的 瞄准进入事件
                 */
                CurrentAimableObject?.OnAimExit();
                CurrentAimableObject = aimObject;
                CurrentAimableObject?.OnAimEnter();
            }
            //如果点击鼠标左键且当前可瞄准物体不为空  执行其鼠标点击事件
            Input.GetMouseButtonDown(0).Invoke(() => CurrentAimableObject?.DoInteract());
        }
        /* 
         * 此处表示没有碰撞信息
         * 如果当前可瞄准物体不为空
         * 执行其瞄准离开事件 并将其置为空
         */
        else
        {
            if (null != CurrentAimableObject)
            {
                CurrentAimableObject.OnAimExit();
                CurrentAimableObject = null;
            }
        }
        //如果当前可瞄准物体不为空 不停执行其瞄准停留事件
        CurrentAimableObject?.OnAimStay();
    }

    private void OnDestroy()
    {
        Instance = null;
        //卸载UI界面
        if(Application.isPlaying)
            UIMgr.ClosePanel<AimSystemPanel>();
    }
}

IAimableObject:

/// <summary>
/// 可瞄准物体接口
/// </summary>
public interface IAimableObject
{
    /// <summary>
    /// 瞄准距离(可被检测的距离)
    /// </summary>
    float AimDistance { get; }
    /// <summary>
    /// 物体的名称或对其描述
    /// </summary>
    string AimDescription { get; }
    /// <summary>
    /// 瞄准进入事件
    /// </summary>
    void OnAimEnter();
    /// <summary>
    /// 瞄准离开事件
    /// </summary>
    void OnAimExit();
    /// <summary>
    /// 瞄准停留事件
    /// </summary>
    void OnAimStay();
    /// <summary>
    /// 鼠标点击事件
    /// </summary>
    void DoInteract();
}

AimSystemPanel 不再赘述,只需要一个Text文本框,当瞄准进入时显示其AimDescription,当瞄准离开时将Text文本框内容清空。

下面是继承接口的一个实现:

using System;
using UnityEngine;

/// <summary>
/// 可瞄准物体基类
/// </summary>
public class AimableObject : MonoBehaviour, IAimableObject
{
    //瞄准进入事件 可以进行事件的追加和删除
    private Action mOnEnterEvent;
    //瞄准离开事件 可以进行事件的追加和删除
    private Action mOnExitEvent;

    //瞄准距离(可被检测的距离)  子类去重写
    protected virtual float AimDistance => 5f;
    //物体的名称或对其描述  子类去重写
    protected virtual string AimDescription => name;

    /// <summary>
    /// 瞄准进入事件
    /// </summary>
    protected virtual void OnAimEnter()
    {
        mOnEnterEvent?.Invoke();
        AimSystem.Instance.AimEnter(AimDescription);
    }
    /// <summary>
    /// 瞄准离开事件
    /// </summary>
    protected virtual void OnAimExit()
    {
        mOnExitEvent?.Invoke();
        AimSystem.Instance.AimExit();
    }
    /// <summary>
    /// 瞄准停留事件
    /// </summary>
    protected virtual void OnAimStay() { }
    /// <summary>
    /// 鼠标点击事件
    /// </summary>
    protected virtual void DoInteract() { }

    float IAimableObject.AimDistance => AimDistance;
    string IAimableObject.AimDescription => AimDescription;
    void IAimableObject.OnAimEnter() => OnAimEnter();
    void IAimableObject.OnAimExit() => OnAimExit();
    void IAimableObject.OnAimStay() => OnAimStay();
    void IAimableObject.DoInteract() => DoInteract();

    protected virtual void OnDestroy()
    {
        mOnEnterEvent = null;
        mOnExitEvent = null;
    }

    /// <summary>
    /// 对瞄准进入事件进行追加
    /// </summary>
    /// <param name="action"></param>
    public void AppendAimEnterEvent(Action action)
    {
        if (null == mOnEnterEvent) mOnEnterEvent = action;
        else
        {
            Delegate[] delegates = mOnEnterEvent.GetInvocationList();
            if (!Array.Exists(delegates, v => v == (Delegate)action))
                mOnEnterEvent += action;
        }
    }
    /// <summary>
    /// 对瞄准离开事件进行追加
    /// </summary>
    /// <param name="action"></param>
    public void AppendAimExitEvent(Action action)
    {
        if (null == mOnExitEvent) mOnExitEvent = action;
        else
        {
            Delegate[] delegates = mOnExitEvent.GetInvocationList();
            if (!Array.Exists(delegates, v => v == (Delegate)action))
                mOnExitEvent += action;
        }
    }
    /// <summary>
    /// 对瞄准进入事件进行删除
    /// </summary>
    /// <param name="action"></param>
    public void DeleteAimEnterEvent(Action action)
    {
        if (null == mOnEnterEvent) return;
        Delegate[] delegates = mOnEnterEvent.GetInvocationList();
        if (Array.Exists(delegates, v => v == (Delegate)action))
            mOnEnterEvent -= action;
    }
    /// <summary>
    /// 对瞄准离开事件进行删除
    /// </summary>
    /// <param name="action"></param>
    public void DeleteAimExitEvent(Action action)
    {
        if (null == mOnExitEvent) return;
        Delegate[] delegates = mOnExitEvent.GetInvocationList();
        if (Array.Exists(delegates, v => v == (Delegate)action))
            mOnExitEvent -= action;
    }
}

Example:

using UnityEngine;

public class AimableObjectExample : AimableObject
{
    protected override string AimDescription => "Is a aimable gameobject.";

    protected override float AimDistance => 4.5f;

    protected override void OnAimEnter()
    {
        base.OnAimEnter();
        Debug.Log("OnAimEnter");
    }

    protected override void OnAimExit()
    {
        base.OnAimExit();
        Debug.Log("OnAimExit");
    }

    protected override void DoInteract()
    {
        Debug.Log("DoInteract");
    }

    protected override void OnAimStay()
    {
        Debug.Log("OnAimStay");
    }
}

知秋君
上一篇 2024-08-30 07:12
下一篇 2024-08-29 22:48

相关推荐