提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、前置
(1)使用CinemachineFreeLook对操作的角色进行跟随,关于Cinemachine可参考 【游戏开发教程】Unity Cinemachine快速上手,详细案例讲解(虚拟相机系统 | 新发出品 | 良心教程)
(2)相机的LookAt属性都使用角色及目标上挂在的LookRoot,可参考我的偏移

此外还要给每个对象添加CapsuleCollider
提示:以下是本篇文章正文内容,下面案例可供参考
二、在相机前方搜寻一定范围内的目标
在相机观察方向前方添加一个检测盒子,将检测到的目标存到targets里

这里使用协程循环是因为防止当目标不在摄像机检测范围内时依然可以锁定
IEnumerator OnLockTarget()
{
while(true)
{
var viewDir = Camera.main.transform.forward.normalized;
boxCenter = transform.position + viewDir * boxOffset.x + transform.up * boxOffset.y;
targets = Physics.OverlapBox(boxCenter, size, transform.rotation, layer);
yield return findTargetWaitTime;
}
}
这段代码可在scene绘制出检测盒子
#if UNITY_EDITOR
private void OnDrawGizmos()
{
Gizmos.DrawWireCube(boxCenter, size*2);
}
#endif

三、切换锁定目标
可以自定自定义按键,切换目标

void OnSwitchLockTarget(int up=0)
{
if(targets.Length == 0) return;
index += up;
if(index >= targets.Length) index = 0;
else if (index <0) index=targets.Length-1;
targetLootRoot=targets[index].transform.Find("LookRoot");
theLockTarget = targets[index].transform;
//cm.m_LookAt = target;
cm.LookAt = targetLootRoot;
}
四、移动范围限制

有时角色会离开相机,这里我加了一个基于屏幕宽高的比例的判定,在适当时机移动相机,不让角色离开摄像机
void CmClamp()
{
if (Camera.main.WorldToScreenPoint(transform.position).x <= Screen.width / CameraLimit)
cm.m_XAxis.Value += 1f;
if (Camera.main.WorldToScreenPoint(transform.position).x >= Screen.width / CameraLimit * 3)
cm.m_XAxis.Value -= 1f;
}
CameraLimit设置在 3~5 即可
总结
将脚本挂在要操控的角色身上,参数设置如下


using Cinemachine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LockTarget : MonoBehaviour
{
[SerializeField]
CinemachineFreeLook cm;
Transform theLockTarget;//目标
Transform targetLootRoot;//目标的LootRoot
bool isLock;
int index=0;
public float MaxDistance;
Vector3 boxCenter;//检测盒子中心
public LayerMask layer;//检测层级
public Vector2 boxOffset;//盒子产生时的位置偏移
public Vector3 size= new Vector3(2.0f, 2.0f, 2.0f);//盒子的大小
WaitForSeconds findTargetWaitTime;//每次搜索间隔
public float waitTime;//间隔事件
Collider[] targets;//检测到的物体
public Image lookPoint;
public float CameraLimit;
private void Start()
{
findTargetWaitTime=new WaitForSeconds(waitTime);
}
private void Update()
{
InputCheck();
LookPointFlow();
LostTarget();
CmClamp();
}
//开始锁定
void OnLock()
{
lookPoint.gameObject.SetActive(true);
StartCoroutine(OnLockTarget());
StartCoroutine(Delay(0.5f));
OnSwitchLockTarget(0);
isLock = true;
}
//解除锁定
void UnLock()
{
StopCoroutine(OnLockTarget());
cm.m_LookAt=transform.Find("LookRoot");
isLock = false;
lookPoint.gameObject.SetActive(false);
}
//切换目标
void OnSwitchLockTarget(int up=0)
{
if(targets.Length == 0) return;
index += up;
if(index >= targets.Length) index = 0;
else if (index <0) index=targets.Length-1;
targetLootRoot=targets[index].transform.Find("LookRoot");
theLockTarget = targets[index].transform;
//cm.m_LookAt = target;
cm.LookAt = targetLootRoot;
}
void InputCheck()
{
if (Input.GetKeyDown(KeyCode.G))
{
if (isLock)
{
UnLock();
}
else
{
OnLock();
}
}
if (isLock)
{
if (Input.GetKeyDown(KeyCode.LeftArrow)) OnSwitchLockTarget(1);
else if (Input.GetKeyDown(KeyCode.RightArrow)) OnSwitchLockTarget(-1);
}
}
//在摄像机前方范围内搜寻目标
IEnumerator OnLockTarget()
{
while(true)
{
var viewDir = Camera.main.transform.forward.normalized;
boxCenter = transform.position + viewDir * boxOffset.x + transform.up * boxOffset.y;
targets = Physics.OverlapBox(boxCenter, size, transform.rotation, layer);
yield return findTargetWaitTime;
}
}
//延迟执行
IEnumerator Delay(float delayTime)
{
yield return new WaitForSeconds(delayTime);
}
/// <summary>
/// 防止player超出摄像机范围
/// </summary>
void CmClamp()
{
if (Camera.main.WorldToScreenPoint(transform.position).x <= Screen.width / CameraLimit)
cm.m_XAxis.Value += 1f;
if (Camera.main.WorldToScreenPoint(transform.position).x >= Screen.width / CameraLimit * 3)
cm.m_XAxis.Value -= 1f;
}
/// <summary>
/// 锁定图标追随目标
/// </summary>
void LookPointFlow()
{
if (!isLock || targetLootRoot == null) return;
lookPoint.rectTransform.position = Camera.main.WorldToScreenPoint(targetLootRoot.position);
}
/// <summary>
/// 距离过远时取消锁定或者切换目标
/// </summary>
void LostTarget()
{
if (!isLock||targetLootRoot==null) return;
if(Vector3.Distance(transform.position, targetLootRoot.position)>=MaxDistance)
{
if(targets.Length!=0)
OnSwitchLockTarget(1);
else UnLock();
}
}
#if UNITY_EDITOR
private void OnDrawGizmos()
{
Gizmos.DrawWireCube(boxCenter, size*2);
}
#endif
}
补充-遮挡时切换目标-25.5.20
修改离目标过远时的函数,改为使用协程,防止重复检测
判断玩家与目标之间是否有障碍物,有则切换目标
在打开锁定时启用协程
IEnumerator LostOrBlockTarget()
{
while (true)
{
if (!isLock || targetLootRoot == null) yield return null;
Debug.DrawLine(transform.position, targetLootRoot.position,Color.blue);
if (Vector3.Distance(transform.position, targetLootRoot.position) >= MaxDistance||
Physics.Raycast(transform.position, targetLootRoot.position - transform.position,
Vector3.Distance(transform.position, targetLootRoot.position), blockMask))
{
if (targets.Length != 0)
OnSwitchLockTarget(1);
else UnLock();
}
yield return new WaitForSeconds(0.7f);
}
}

37万+

被折叠的 条评论
为什么被折叠?



