包含以下内容:
1:随机生成巡逻点 2:判断是否发现敌人 3:巡逻等待 4:敌人死亡 5:敌人UI标记
敌人的逻辑在状态机具体的状态中实现。由于对状态进行了复用,因此在具体状态中声明的变量要上升到敌人类EnemyController中(防止多个敌人状态机对同一ScriptObject状态类进行使用和赋值造成的冲突问题)。
以下是一些敌人中杂项内容:
1:随机生成一个巡逻点 涉及到圆内随机生成一点
要点:生成两个0-1的随机数,一个作为角度a,一个作为距离圆心的距离r
a*=2π r要先取根号 再乘以巡逻半径(确保点在圆内是均匀分布的)
最后根据公式 X=originX + r * cos a Z=originZ + r * sin a Y 高度值默认保持不变
public Vector3 GetRandomPartolPoint()
{
float randomAngle = Random.value;
float randomRadius = Random.value;
randomRadius = Mathf.Sqrt(randomRadius);
randomAngle *= 2 * Mathf.PI;
randomRadius *= patrolRadius;
return new Vector3(orignalPosition.x + randomRadius * Mathf.Cos(randomAngle), transform.position.y, orignalPosition.z + randomRadius * Mathf.Sin(randomAngle));
}
2:判断是否发现敌人
要点:使用 OverlapSphereNonAlloc 判断指定范围内有无Layer为 Player的物体,如果没有,则直接返回。 否则,还需要判断敌人和人物之间有无障碍物(可以使用射线检测),最后还需要判断人物是否是面向敌人的。
private bool CanFindPlayer()
{
int count = Physics.OverlapSphereNonAlloc(transform.position, findPlayerRadius, playerCollider, playerLayer);
if (count == 0) return false;
//从敌人位置向目标位置发射一条射线,判断中间有无障碍物
float distance = Vector3.Distance(transform.position, playerCollider[0].transform.position);
if (!Physics.Raycast(transform.position + transform.up * 0.5f, transform.forward.normalized,distance,barrierLayer))
{
//判断敌人是否是朝向角色的
return transform.IsFacingTarget(player.transform);
}
return false;
}
静态方法:使用人物forward向量以及人物指向敌人的向量 进行点击Dot运算(记得两个向量取normalized变成单位向量),判断的值可以自行决定。(点积计算两个单位向量角度的cos值)
/// <summary>
///
/// </summary>
/// <param name="transform"></param>
/// <param name="目标的transform"></param>
/// <returns></returns>
public static bool IsFacingTarget(this Transform transform, Transform targetTransform)
{
if (Vector3.Dot(transform.forward, (targetTransform.position - transform.position).normalized) > 0.35f)
return true;
return false;
}
3:巡逻等待时间的协程逻辑 :等待时间结束后进行转向,转向完成后才能移动。
//巡逻等待时间的 协程
public IEnumerator WaitPatrolTime(float waitTime, Vector3 targetPos)
{
IsWait = true;
curSpeed = 0;
while (waitTime > 0)
{
waitTime -= Time.deltaTime;
yield return null;
}
yield return RotateToTatgetPos(targetPos);
IsWait = false;
}
4:敌人死亡后的事件
(1)第一个是处理敌人死亡后的事件(主要是如果正在被锁定,则移除玩家对敌人的锁定)。
(2)第二个是处理处决的标志(敌人死亡后,如果有处决标志的话,应该消失)
(3)在Death动画结束后才实际消除物体。
public void AfterDeathAnimation()
{
//TODO:使用对象池管理敌人
GlobalEvent.CallOnEnemyDeath(this);
GlobalEvent.CallEnemyExitWeakState(this);
Destroy(transform.gameObject);
}
5:敌人UI标记(聚焦标记 + 处决标记)
要点:使用 overlay 的Canvas实现 (使用World Space模式的话会被敌人遮挡)
聚焦标记只有一个,用全局事件的方式来响应玩家的聚焦功能。
处决标记可能会出现多个,使用字典<敌人,处决标志>来记录所有的处决标志,同样使用全局事件的方式,在敌人进入虚弱状态时触发,在敌人退出虚弱状态时移除。
标志要跟着敌人走,因此在LateUpdate中更新所有标志的位置。
(使用Camera.main.WorldToScreenPoint(currentLockedEnemy.transform.position)将世界坐标点转化为屏幕坐标点)
public class EnemyPointUI : MonoBehaviour
{
[SerializeField] private GameObject executedPointPrefab;
[SerializeField] private Image FocusPointImage;
[SerializeField] private Transform pointParent;
private Dictionary<EnemyController, GameObject> executedPoints = new();
private EnemyController currentLockedEnemy;
private void OnEnable()
{
GlobalEvent.EnemyEnterWeakState += CreateExecutedPoints;
GlobalEvent.EnemyExitWeakState += DeleteExecutedPoints;
GlobalEvent.EnterFocusOnEnemy += OnEnterFocusOnEnemy;
GlobalEvent.ExitFocusOnEnemy += OnExitFocusOnEnemy;
}
private void OnDisable()
{
GlobalEvent.EnemyEnterWeakState -= CreateExecutedPoints;
GlobalEvent.EnemyExitWeakState -= DeleteExecutedPoints;
GlobalEvent.EnterFocusOnEnemy -= OnEnterFocusOnEnemy;
GlobalEvent.ExitFocusOnEnemy -= OnExitFocusOnEnemy;
}
private void LateUpdate()
{
if (currentLockedEnemy != null)
{
FocusPointImage.transform.position = Camera.main.WorldToScreenPoint(currentLockedEnemy.transform.position);
}
foreach(var redPoint in executedPoints)
{
redPoint.Value.transform.position = Camera.main.WorldToScreenPoint(redPoint.Key.transform.position);
}
}
//锁定敌人的事件
public void OnEnterFocusOnEnemy(EnemyController enemy)
{
currentLockedEnemy = enemy;
FocusPointImage.transform.position = Camera.main.WorldToScreenPoint(currentLockedEnemy.transform.position);
FocusPointImage.gameObject.SetActive(true);
}
//取消锁定的事件
public void OnExitFocusOnEnemy()
{
currentLockedEnemy = null;
FocusPointImage.gameObject.SetActive(false);
}
//生成处决的points
public void CreateExecutedPoints(EnemyController enemy)
{
if (enemy == null) return;
if(!executedPoints.ContainsKey(enemy))
{
executedPoints.Add(enemy, Instantiate(executedPointPrefab, pointParent));
executedPoints[enemy].transform.position = Camera.main.WorldToScreenPoint(enemy.transform.position);
}
}
//删除处决的points
public void DeleteExecutedPoints(EnemyController enemy)
{
if (enemy == null) return;
if(executedPoints.ContainsKey(enemy))
{
Destroy(executedPoints[enemy].gameObject);
executedPoints.Remove(enemy);
}
}
}