最终效果:
当我们对准场景中的一个物体时,可以显示这个物体的名称或者对这个物体的描述。如图所示,我们对准了场景中的一个桌子,对这个桌子的描述: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");
}
}