3DRPG游戏开发核心功能03-敌人及玩家控制
主要技术:Animator,Shader Graph,敌人状态切换及动画,玩家攻击控制
Animator 动画控制器
新建
Animator Controller
,控制角色各种动画效果的切换
Blend Tree
通过float类型速度判断停止-走路-跑步用Blend Tree是比较方便的
右键新建 Blend Tree
1 | private void SwitchAnimation() |
Animator
切换状态时不需要退出动画,从一个状态切换回默认状态需要播放完动画,
Exit
设置为1设置多层Animator,权重越大越优先执行
Shader Graph
可视化Shader:这里用于控制遮挡显示
修改物体表面,就是修改物体的 Material,也可以通过写Shader,创建对应的Material,即可将Shader效果赋给物体
创建Shader
Project -> creat -> Shader -> Universal Render Pipeline -> Unlit Shader Graph
基于Shader创建材质
选中创建的 Shader Graph ,右键
creat -> Materials
Shader Graph
遮挡设置
UniversalRenderPipelineAsset_Renderer
敌人控制
组件自动挂载:在定义组件变量后,自动将组件挂载到物体身上
[RequireComponent(typeof(NavMeshAgent))]
1
2
3
4
5[// 自动添加该组件 ]
public class EnemyController : MonoBehaviour
{
private NavMeshAgent agent;
}
状态切换
状态:站桩,巡逻,跟随,死亡
多类型自然而然想到枚举
enum Enemystates { GUARD, PATROL, CHASE, DEAD }
状态机
动画效果
基础:站立 -> 走动
攻击:
死亡:
胜利:
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
// 追击
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
6public 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
// 移动到目标点
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;
}
}