主要技术:Animator Override Contraller,Animator Behavior,判断角色前后关系,粒子系统

制作更多敌人

独立 CharacterStates

  • N 个 Enemy 的 CharacterStates 导入的 CharacterData_SO 数据不应该是一样
1
2
3
4
# CharacterStates
// 模板数据,n只敌人数据不一致
public CharacterData_SO templateData;
public CharacterData_SO characterData;
  • 多增加一个 CharacterData_SO 类型的变量记录当前敌人数据
1
2
3
4
5
6
7
8
9
void Awake()
{
// 两只史莱姆不会互相影响
if (templateData != null)
{
// 复制数据模板,新生成一个 CharacterData_SO 类型的 templateData
characterData = Instantiate(templateData);
}
}

Animator Override Contraller

  • 批量添加动画

Project -> 右键 create -> Animator Override Contraller

选择 Enemy_Slime ,即可按 Enemy_Slime 动画生成对应模板

QQ截图20220805174137


设置兽人士兵

复制 Aimator 可以用 Animator Override Contraller 的方法,修改子动画效果引用动画也会变

另一种方法直接 ctrl+d 其他敌人的Animator,修改对应动画,继承父类Emeny 添加Data,动画添加Event

击飞和眩晕效果

  • Player 被击飞使用 NavMeshAgent 的功能,添加眩晕效果动画速度调整
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
# Grunt
using UnityEngine.AI; // NavMeshAgent

public class Grunt : EnemyController // 继承 EnemyControlle
{
[Header("Skill")]
public float kickForce = 10;
// 击飞
public void KickOff()
{
if (attackTarget != null)
{
// 敌人看向目标再攻击,看向一个三维向量点
transform.LookAt(attackTarget.transform);

// player在enemy面前,player-enemy坐标得到击飞方向
Vector3 direction = attackTarget.transform.position - transform.position;
direction.Normalize(); // 0 1

// 获取player的NavMeshAgent,停止所有动作被击飞
attackTarget.GetComponent<NavMeshAgent>().isStopped = true;
// 用Nav给出击飞力
attackTarget.GetComponent<NavMeshAgent>().velocity = direction * kickForce;
// 眩晕效果
attackTarget.GetComponent<Animator>().SetTrigger("Dizzy");
}
}
}

Animator Behavior

  • 动画添加行为(控制机):编写 Animator Behavior 控制动画播放期间的 Agent

QQ截图20220809114933

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# StopAgent
public class StopAgent : StateMachineBehaviour
{
// 进入动画执行一次
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<NavMeshAgent>().isStopped = true;
}

// 动画执行过程持续执行
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<NavMeshAgent>().isStopped = true;
}

// 动画退出,Stopped之后又回到动画前坐标,可以注释掉
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<NavMeshAgent>().isStopped = false;
}
}
  • 摄像机在人物后方

QQ截图20220809120333


Extention 扩展方法

创建 Transform 的扩展方法,判断攻击目标是否在面前

扩展方法不可以继承其他类,静态方法

QQ截图20220809144102

  • Player 到 Enemy 身后不产生攻击效果,通过 Enemy 面向控制

    Player 和 Enemy 坐标点乘计算相对位置,>= 0.5为前方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ExtensionMethod
public static class ExtensionMethod
{
private const float dotThreshold = 0.5f; // static里面不可以改 要加const

// this 后面是拓展的类,后面是变量,传入目标位置即可
public static bool IsFacingTarget(this Transform transform, Transform target)
{
// 向量点积 1 同方向 0正交 -1反方向 >= 0.5为前面
// (目标位置 - 当前角色位置)向量化
var vecterToTarget = target.position - transform.position;
vecterToTarget.Normalize();
// 当前角色的“前方”(即坐标轴的(0,1)) 点乘 目标角色与当前角色的“差”向量
float dot = Vector3.Dot(transform.forward, vecterToTarget);
return dot >= dotThreshold;
}
}

  • 向量化两种方法
1
2
>var vecterToTarget = target.position - transform.position;
>vecterToTarget.Normalize();
1
>var vecterToTarget = (target.position - transform.position).normolized;
  • 使用扩展方法

    transform.IsFacingTarget(attackTarget.transform)

1
2
3
4
5
6
7
8
9
10
# EnemyController
void Hit()
{
// 正前方才攻击
if (attackTarget != null && transform.IsFacingTarget(attackTarget.transform))
{
var targetstates = attackTarget.GetComponent<CharacterStates>();
targetstates.TakeDamage(characterStates, targetstates);
}
}

设置石头人BOSS

扔石头效果

NavMeshAgent:stopDistance 代理在接近目标停止时的位置 受目标大小影响

  • 石头设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Rock    
// 石头生成时需要的属性
void Start()
{
rb = GetComponent<Rigidbody>();
FlyToTarget();
}
// 石头飞向目标:石头朝目标且有加速度
public void FlyToTarget()
{
// 攻击目标坐标 - 坐标值
direction = (target.transform.position - transform.position + Vector3.up).normalized;
rb.AddForce(direction * force, ForceMode.Impulse);
}
  • 在BOSS手的位置生成石头

    生成 GameObject 方法:

1
Object.Instantiate(Object original, Vector3 position, Quaternion rotation) // 物体 位置 角度
  • 物理撞击效果

QQ截图20220810173657

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
# Golem
// Animation Event
public void KickOff()
{
// 正前方才攻击
if (attackTarget != null && transform.IsFacingTarget(attackTarget.transform))
{
var targetstates = attackTarget.GetComponent<CharacterStates>();
//向量化
Vector3 direction = (attackTarget.transform.position - transform.position).normalized;
//direction.Normalize();
targetstates.GetComponent<NavMeshAgent>().isStopped = true;
targetstates.GetComponent<NavMeshAgent>().velocity = direction * kickForce;
// 击晕
targetstates.GetComponent<Animator>().SetTrigger("Dizzy");
targetstates.TakeDamage(characterStates, targetstates);
}
}
// Animation Event
public void ThrowRock()
{
// 生成石头 rockPrefab 手的位置 旋转角度不变
var rock = Instantiate(rockPrefab, handPos.position, Quaternion.identity);
rock.GetComponent<Rock>().target = attackTarget;
}

玩家反击效果

石头不同状态

不同状态(类型) 想到枚举

  • 石头类
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
# Rock    
// 枚举值 enum NAME { 枚举值1,美枚举值2,枚举值3 }
public enum RockStates { HitPlayer, HitEnemy, HitNothing};
private RockStates rockStates;
void FixedUpdate()
{
// .sqrMagnitude 速度转换为float
if (rb.velocity.sqrMagnitude < 1) // 速度小于1说明落地设置nothing
{
rockStates = RockStates.HitNothing;
}
// Debug.Log(rb.velocity.sqrMagnitude);
}
// 根据碰撞的物体确认石头的状态
private void OnCollisionEnter(Collision collision)
{
switch(rockStates)
{
case RockStates.HitPlayer:
if (collision.gameObject.CompareTag("Player")) // 碰撞player效果
{
collision.gameObject.GetComponent<NavMeshAgent>().isStopped = true;
// 物理效果被撞到
collision.gameObject.GetComponent<NavMeshAgent>().velocity = direction * force;
// 眩晕动画
collision.gameObject.GetComponent<Animator>().SetTrigger("Dizzy");
// 受到伤害值
collision.gameObject.GetComponent<CharacterStates>().TakeDamage(damage, collision.gameObject.GetComponent<CharacterStates>());
rockStates = RockStates.HitNothing;
}
break;
case RockStates.HitEnemy:
// .GetComponent<Golem>(),是否挂载这个脚本,可以做判断语句
if (collision.gameObject.GetComponent<Golem>())
{
var otherStates = collision.gameObject.GetComponent<CharacterStates>();
otherStates.TakeDamage(damage, otherStates);
Instantiate(breakEffect, transform.position, Quaternion.identity); // 击碎消失
Destroy(gameObject);
}
break;
}
}
}
  • 鼠标控制
1
2
3
4
5
6
7
8
9
10
11
# MouseManager
void MouseControl()
{
if (Input.GetMouseButtonDown(0) && hitInfo.collider != null) // 鼠标点击位置 有返回值
{
if (hitInfo.collider.gameObject.CompareTag("Attackable")) // 点击反击物体 触发事件
{
OnEnemyClicked?.Invoke(hitInfo.collider.gameObject);
}
}
}
  • Hit 事件修改:增加石头攻击效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# PlayerController
// Animation Event 武器击打到敌人
void Hit()
{
// 用石头打
if (attackTarget.CompareTag("Attackable"))
{
// 是否石头 是石头触发对应hit事件 对敌人产生伤害
if (attackTarget.GetComponent<Rock>() && attackTarget.GetComponent<Rock>().rockStates == Rock.RockStates.HitNothing)
{
attackTarget.GetComponent<Rock>().rockStates = Rock.RockStates.HitEnemy;
attackTarget.GetComponent<Rigidbody>().velocity = Vector3.one;
attackTarget.GetComponent<Rigidbody>().AddForce(transform.forward * 20, ForceMode.Impulse);
}
}
else
{
// 获取目标数据 用武器打
var targetStates = attackTarget.GetComponent<CharacterStates>();
targetStates.TakeDamage(characterStates, targetStates);
}
}

函数重载

重载TakeDamage(int damage, CharacterStates defener)的另一种有参格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# CharacterStates
// 受伤计算方式
public void TakeDamage(CharacterStates attacker, CharacterStates defener)
// 两个Character类型的参数
{
// 攻击者的攻击-受攻击的防御 防御可能高于其攻击,最小为0
int damage = Math.Max(attacker.CurrentDamage() - defener.CurrentDefence, 0);
// 血量最低为0
CurrentHealth = Math.Max(CurrentHealth - damage, 0);
// attacker
if (attacker.isCritical)
{
defener.GetComponent<Animator>().SetTrigger("Hit");
}
}
// 函数重载 直接写即可
public void TakeDamage(int damage, CharacterStates defener)
{
int currentdamage = Mathf.Max(damage - defener.CurrentDefence, 0);
CurrentHealth = Mathf.Max(CurrentHealth - currentdamage, 0);
}

Partical System 粒子系统

Hirearchy -> Creat -> Effects -> Partical System

  • RockBreak Paritical

QQ截图20220811003620

  • Emission:发射

QQ截图20220811003658

  • Rotation over Lifetime:旋转角度 旋转速度

QQ截图20220811003734

  • Collision:碰撞效果

QQ截图20220811003809

  • Renender:渲染

QQ截图20220811003849