主要技术:Animator,Shader Graph,敌人状态切换及动画,玩家攻击控制

Animator 动画控制器

新建 Animator Controller,控制角色各种动画效果的切换

Blend Tree

通过float类型速度判断停止-走路-跑步用Blend Tree是比较方便的

右键新建 Blend Tree

QQ截图20220904021705

1
2
3
4
5
private void SwitchAnimation()
{
// 直接用agent可以获得速度 返回浮点值
anim.SetFloat("Speed", agent.velocity.sqrMagnitude);
}

Animator

  • 切换状态时不需要退出动画,从一个状态切换回默认状态需要播放完动画,Exit设置为1

  • 设置多层Animator,权重越大越优先执行

    QQ截图20220905130900


Shader Graph

可视化Shader:这里用于控制遮挡显示

修改物体表面,就是修改物体的 Material,也可以通过写Shader,创建对应的Material,即可将Shader效果赋给物体

  • 创建Shader

    Project -> creat -> Shader -> Universal Render Pipeline -> Unlit Shader Graph

  • 基于Shader创建材质

    选中创建的 Shader Graph ,右键creat -> Materials

  • Shader Graph

    QQ截图20220904172746

  • 遮挡设置

    UniversalRenderPipelineAsset_Renderer

    QQ截图20220904174709


敌人控制

  • 组件自动挂载:在定义组件变量后,自动将组件挂载到物体身上

    [RequireComponent(typeof(NavMeshAgent))]

    1
    2
    3
    4
    5
    [RequireComponent(typeof(NavMeshAgent))]        // 自动添加该组件
    public class EnemyController : MonoBehaviour
    {
    private NavMeshAgent agent;
    }

状态切换

状态:站桩,巡逻,跟随,死亡

多类型自然而然想到枚举 enum Enemystates { GUARD, PATROL, CHASE, DEAD }

  • 状态机

  • 动画效果

    • 基础:站立 -> 走动

      QQ截图20220905131751

    • 攻击:

      QQ截图20220905131801

    • 死亡:

      QQ截图20220905131808

    • 胜利:

      QQ截图20220905131815

  • Switch

    • 首先玩家出现在敌人攻击范围内:

      当前点的半径范围的碰撞体:Physics.OverlapSphere(transform.position, sightRadius)

      碰撞体的标签对比:target.CompareTag("Player")

    • 追击

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      # EnemyController
      // 追击
      case EnemyStates.CHASE:
      isWalk = false;
      isChase = true;
      //追player 拉脱回到上一个状态 执行动画 攻击范围内侧攻击 配合动画
      agent.speed = speed;
      if (!FoundPlayer())
      {
      // 拉脱回到上一范围
      isFollow = false;
      if (remainLookAtTime > 0)
      {
      agent.destination = transform.position;
      remainLookAtTime -= Time.deltaTime;
      }
      else if(isGuard)
      enemystates = EnemyStates.GUARD;
      else
      enemystates = EnemyStates.PATROL;
      }
      else
      {
      isFollow = true;
      agent.isStopped = false;
      agent.destination = attackTarget.transform.position;
      }
      // 攻击范围则攻击
      if (TargetInAttackRange() || TargetInSkillRange())
      {
      isFollow = false;
      agent.isStopped = true;
      if (lastAttackTime < 0)
      {
      lastAttackTime = characterStates.attackData.coolDown;
      //暴击判断
      characterStates.isCritical = Random.value < characterStates.attackData.criticalChance;
      //执行攻击
      Attack();
      }
      }
      break;
    • 巡逻

      巡逻范围:绘制球体

      1
      2
      3
      4
      5
      6
      public float sightRadius;   // 可视范围    
      void OnDrawGizmosSelected()
      {
      Gizmos.color = Color.blue;
      Gizmos.DrawWireSphere(transform.position, sightRadius);
      }

      随机巡逻:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 自由移动范围
      void GetNewWayPoint()
      {
      // 停下来看一看
      remainLookAtTime = lookAtTime;
      // 随机点
      float randomX = Random.Range(-patrolRange, patrolRange);
      float randomZ = Random.Range(-patrolRange, patrolRange);
      // 在出生点周围运动 Y是为了高度差
      Vector3 randomPoint = new Vector3(guardPose.x + randomX, transform.position.y, guardPose.z + randomZ);
      // 可能出现问题 撞到碰撞体卡住 避开障碍物
      NavMeshHit hit;
      // 先判断随机移动点是否是障碍 是的话随机选择另一个
      wayPoint = NavMesh.SamplePosition(randomPoint, out hit, patrolRange, 1) ? hit.position : transform.position;
      //wayPoint = randomPoint;
      }

      巡逻:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      // 巡逻
      case EnemyStates.PATROL:
      isChase = false;
      agent.speed = speed * 0.5f;
      // 判断是否到了随机巡逻点
      if (Vector3.Distance(wayPoint, transform.position) <= agent.stoppingDistance)
      {
      isWalk = false;
      if(remainLookAtTime > 0)
      {
      remainLookAtTime -= Time.deltaTime;
      }
      else
      GetNewWayPoint();
      }
      // 拉脱返回原状态
      else
      {
      isWalk = true;
      agent.destination = wayPoint;
      }
      break;
    • 守卫

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      // 站桩
      case EnemyStates.GUARD:
      // 脱战以后不在初始位置就要自己回去
      if (transform.position != guardPose)
      {
      isWalk = true;
      agent.isStopped = false;
      agent.destination = guardPose;
      // 计算两个三维向量点的差别
      if (Vector3.SqrMagnitude(guardPose - transform.position) <= agent.stoppingDistance)
      {
      isWalk = false;
      // 面向缓慢转回原来的面向:transform.rotation -> guardRotation
      transform.rotation = Quaternion.Lerp(transform.rotation, guardRotation, 0.01f);
      }
      }
      isChase = false;
      break;
    • 死亡

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // box或者别的collider都是继承于这个类
      private Collider coll;
      // 死亡
      case EnemyStates.DEAD:
      coll.enabled = false; // 防止死亡还能攻击
      // agent.enabled = false; //compent里面的对钩
      agent.radius = 0;
      Destroy(gameObject, 2f);
      break;

玩家控制

位移并攻击敌人:协程

  • 距离远,先面向敌人

    transform.LookAt(attackTarget.transform)

  • 通过距离判断远程还是近战

    Vector3.Distance(attackTarget.transform.position, transform.position)

  • 通过协程移动到攻击目标

    StopAllCoroutines():停止协程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    # PlayerController	
    // 移动到目标点
    public void MoveToTarget(Vector3 target)
    {
    // 攻击途中点击其他地方取消攻击
    StopAllCoroutines();
    // 每个动作都要判断人物是否死亡
    if (isDead) return;
    agent.stoppingDistance = stopDistance;
    agent.isStopped = false;
    agent.destination = target;
    }
    // 攻击事件
    private void EventAttack(GameObject target)
    {
    if (isDead) return; // 每个动作都要判断人物是否死亡
    if (target != null) // 有目标才能移动过去
    {
    attackTarget = target;
    characterStates.isCritical = UnityEngine.Random.value < characterStates.attackData.criticalChance;
    StartCoroutine(MoveToAttackTarget());
    }
    }
    // 协程:不断循环位移到目标点直到达到攻击距离
    IEnumerator MoveToAttackTarget()
    {
    // 攻击时状态是停止
    agent.isStopped = false;
    // 修改攻击距离为characterStatesRange,针对攻击目标较大
    agent.stoppingDistance = characterStates.attackData.attackRange;
    transform.LookAt(attackTarget.transform);
    //修改攻击范围参数,持续不断前进直到如果>攻击范围
    while (Vector3.Distance(attackTarget.transform.position, transform.position) > characterStates.attackData.attackRange)
    {
    // 点击目标
    agent.destination = attackTarget.transform.position;
    yield return null;
    }
    // 攻击之后再点击地面还可以移动
    agent.isStopped = true;
    // 攻击
    if (lastAttackTime < 0)
    {
    anim.SetBool("Critical", characterStates.isCritical);
    anim.SetTrigger("Attack");
    // 重置冷却时间
    lastAttackTime = characterStates.attackData.coolDown;
    }
    }