Episode 06
Enemy Attacks——敌人攻击
Enemy
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Enemy : LivingEnitity
{
public enum State { Idle, Chasing, Attacking };//定义状态
State currentState;
NavMeshAgent pathfinder;
Transform target;
Material skinmaterial;
Color originalColour;//初始颜色
float attackDistanceThreshold = .5f;//攻击距离阈值
float timeBetweenAttacks = 1;//攻击间隔时间
float nextAttackTime;
float myCollisionRadius;//enemy的碰撞半径
float targetCollisionRadius;//player的碰撞半径
protected override void Start()
{
base.Start();
//追踪Player
pathfinder = GetComponent<NavMeshAgent>();
skinmaterial = GetComponent<Renderer>().material;
originalColour = skinmaterial.color;
currentState = State.Chasing;
target = GameObject.FindGameObjectWithTag("Player").transform;
myCollisionRadius = GetComponent<CapsuleCollider>().radius;
targetCollisionRadius = GetComponent<CapsuleCollider>().radius;
StartCoroutine(UpdatePath());
}
void Update()
{
if (Time.time > nextAttackTime)
{
float sqrDstToTarget = (target.position - transform.position).sqrMagnitude;
//间隔距离小于攻击距离阈值
if (sqrDstToTarget < Mathf.Pow(attackDistanceThreshold + myCollisionRadius + targetCollisionRadius, 2))
{
nextAttackTime = Time.time + timeBetweenAttacks;
StartCoroutine(Attack());
}
}
}
IEnumerator Attack()
{
currentState = State.Attacking;
pathfinder.enabled = false;//取消追逐
Vector3 orginalPosition = transform.position;//初始位置
Vector3 dirToTarget = (target.position - transform.position).normalized;//攻击方向
Vector3 attackPosition = target.position - dirToTarget * myCollisionRadius;//攻击到的位置
float attackSpeed = 3;
float percent = 0;
skinmaterial.color = Color.red;
while (percent <= 1)
{
percent += Time.deltaTime * attackSpeed;
float interpolation = (-Mathf.Pow(percent, 2) + percent) * 4;
//线性位置变换,使攻击动作平滑
transform.position = Vector3.Lerp(orginalPosition, attackPosition, interpolation);
yield return null;
}
skinmaterial.color = originalColour;
currentState = State.Chasing;
pathfinder.enabled = true;
}
//不在Update里面使用SetDestination()防止计算开销过大
IEnumerator UpdatePath()
{
float refreshRate = .25f;//0.25s计算一次
while (target != null)
{
if (currentState == State.Chasing)
{
Vector3 dirToTarget = (target.position - transform.position).normalized;
//追逐player间隔一点距离
Vector3 targetPosition = target.position - dirToTarget * (myCollisionRadius + targetCollisionRadius + attackDistanceThreshold/2);
//防止Die()后继续调用SetDestination()报错
if (!dead)
{
pathfinder.SetDestination(targetPosition);
}
}
yield return new WaitForSeconds(refreshRate);
}
}
}
enum:枚举是直接在命名空间、类或结构中使用 enum 关键字定义的。所有常量名都可以在大括号内声明,并用逗号分隔。如果未将值分配给枚举成员,则编译器将在默认情况下为每个成员分配整数值(从零开始)。枚举的第一个成员将为0,并且每个连续枚举成员的值将增加1。可以为枚举成员分配不同的值。枚举成员的默认值的更改将自动按顺序向其他成员分配增量值。
Vector3 Lerp(Vector3 a, Vector3 b, float t):Linearly interpolates between two points.Interpolates between the points a
and b
by the interpolant t
. The parameter t
is clamped to the range [0, 1]. This is most commonly used to find a point some fraction of the way along a line between two endpoints (e.g. to move an object gradually between those points).The value returned equals a + (b - a) * t (which can also be written a * (1-t) + b*t).
代码逻辑:
Chasing:Enemy追踪Player到离Player的attackDistanceThreshold/2位置。(但在到达attackDistanceThreshold已经开始执行Attack()了)
Attacking:当Enemy与Player距离小于攻击距离阈值(attackDistanceThreshold)时,执行Attack(),以Lerp()插值方式移动Enemy进行攻击动画移动。
拓展:
- 枚举类型 - C# 参考 | Microsoft Learn
- 【 unity3d 】Vector3.Lerp()方法的理解
- [Unity - Scripting API: Vector3.Lerp]