[unity learning] RPG Leaning(九)
写这个文章的目的就是为了初学unity,然后更好的掌握unity中的内容【主要是代码】
学习unity的途径是Brackeys 的教程;
接上篇: unity learning RPG Leaning(八).
目的
一、把Sword和Shield的导入和调用
二、拾起Sword和Shield之后,手部动作变化
三、给人物添加攻击状态的转换
四、给人物攻击添加不同动画
一、把Sword和Shield的导入和调用
重新把player的模型导入「包含攻击的动作和Sword/Shield模型」。
由于这里的Shield是放在Armature中的,而且是一个Mesh Render的一个模型。
所以,在这里我们需要把这里Shield/Sword复制到Asset中,且隐藏Hierarchy这里的Shield/Sword(后面要用到)。Asset中的Shield和Sword需要变成Skinned Mesh Render并删除Mesh Render 和Mesh Filter。然后和之前的操作一置,把他放入到一个Item中。
最后修改 EquipmentManager :
加入
public Transform Shield;
public Transform Sword;
然后把刚刚隐藏的Shield/Sword放置到对应的位置
修改Equip()函数变为:
if (newItem != null && newItem.equipsolt == EquipmentSlot.Weapon)
{
newMesh.rootBone = Sword;
}
else if (newItem != null && newItem.equipsolt == EquipmentSlot.Shield)
{
newMesh.rootBone = Shield;
}
else
{
newMesh.transform.parent = targetMesh.transform;
newMesh.bones = targetMesh.bones;
newMesh.rootBone = targetMesh.rootBone;
}
也就是把rootBone放置到对应的位置。
二、拾起Sword和Shield之后,手部动作变化
在Asset/Character 中添加两个Avatar Mask:left Mask和right Mask
只有右手/左手有对应的Mask,在之前创建的Player Controller的Animator/layer中加入两个layer
两个Layer添加刚刚的Avatar Mask
然后在对应的layer添加动画组建
创建一个PlayerAnimator类继承之前我们创建的PlayerAnimation
并修改PlayerAnimation类里面的一些函数和参数变成Protect「类内可以使用,类外不能使用,继承可以使用」和virtual,设置可重写;
在PlayerAnimator中一个武器变换的函数:
void OnEquipmentChanged(Equipment newItem, Equipment oldItem) {
if (newItem != null && newItem.equipsolt == EquipmentSlot.Weapon)
{
animator.SetLayerWeight(1, 1);
}
else if (newItem == null && oldItem != null && oldItem.equipsolt == EquipmentSlot.Weapon)
{
animator.SetLayerWeight(1, 0);
}
if (newItem != null && newItem.equipsolt == EquipmentSlot.Shield)
{
animator.SetLayerWeight(2, 1);
}
else if (newItem == null && oldItem != null && oldItem.equipsolt == EquipmentSlot.Shield)
{
animator.SetLayerWeight(2, 0);
} }
并把他加入到EquipmentManager的委托onEquipmentChanged中
EquipmentManager.instance.onEquipmentChanged += OnEquipmentChanged;
最后需要把控制Player动画PlayerAnimation变成PlayerAnimator;
即可实现人物手拿物体动画的感觉
三、给人物添加攻击状态的转换
就是人物如果在攻击状态的时候,会变成一个攻击状态的idle动作,也就是我们要传入一个bool的变量来判断人物是否在攻击状态;
在BaseLayer层添加动作Combat_idle:
在Parameter四中加入bool类型的InCombat变量
设置动作转换的条件
Locolmotion->Combat_idle : 这里添加了两个条件:
InCombat为true && speedPercent<0.1
注意设置这里的Has Exit Time
Combat_idle -> Locolmotion: 这里添加了两个条件:
InCombat为false || speedPercent>0.1
然后在CharacterCombat中添加
public bool Incombat { get; private set; }
const float combatCooldown = 5f;
float lastAttackTime;
combatCooldown指的是当人物如果停止攻击时间超过5s的话,会把Incombat准换成false。
加入了Update()来实现这个效果
void Update()
{
attackCoolDown -= Time.deltaTime;
if (Time.time - lastAttackTime > combatCooldown)
{
Incombat = false;
}
}
在人物攻击时把 Incombat =true;
public void Attack(CharacterStats targetStats)
{
if (attackCoolDown <= 0f)
{
// targetStats.TakeDamage(myStats.damage.GetValue());
StartCoroutine(DoDamage(targetStats, attackDelay));
attackCoolDown = 1f / attackSpeed;
Incombat = true;
lastAttackTime = Time.time;
if (OnAttack != null)
{
OnAttack();
}
}
else
{
// Debug.Log("CoolDown!");
}
}
被攻击物体死亡时 Incombat = false;
IEnumerator DoDamage(CharacterStats stats, float delay)
{
yield return new WaitForSeconds(delay);
stats.TakeDamage(myStats.damage.GetValue());
if (stats.currentHealth <= 0)
{
Incombat = false;
}
}
在PlayerAnimation中传入这个Incombat;
protected CharacterCombat combat;
protected virtual void Start()
{
combat = GetComponent<CharacterCombat>();
}
protected virtual void Update()
{
animator.SetBool("InCombat", combat.Incombat);
}
四、给人物攻击添加不同动画
添加Punch动画作为基础动画到组件中。
添加Trigger 类型的attack变量。
设置动作变化的触发条件是Trigger
退出动作设置:
这里我们默认的动作是Punch,但是当我们武器变换成Sword后,相应的动作也需要变换:
由于有的武器动作不止一种,所以在PlayerAnimation类中定义了AnimatorOverrideController。
Animator Override Controller是用来配合Animator Controller使用的,它让Animator Controller变得更加实用,可以让不同的使用实例的在同一状态播放不同的动作,但保留原有的结构、参数和逻辑。
unity Animator Override Controller的使用
AnimationClip是什么
简单来说AnimationClip就是一个动作动画。
public AnimationClip replaceableAttackAnim;
public AnimationClip[] defaultAttackAnimSet;
protected AnimationClip[] currentAttackAnimSet;
protected AnimatorOverrideController overrideController;
protected virtual void Start()
{
overrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);
animator.runtimeAnimatorController = overrideController;
currentAttackAnimSet = defaultAttackAnimSet;
}
protected virtual void OnAttack()
{
animator.SetTrigger("attack");
//随机播放currentAttackAnimSet组中的不同动画
int attackIndex = Random.Range(0, currentAttackAnimSet.Length);
overrideController[replaceableAttackAnim.name] = currentAttackAnimSet[attackIndex];
}
Start中设置currentAttackAnimSet变为defaultAttackAnimSet;
**
- 「这里有疑问?是不是replaceableAttackAnim设定了,就能找到对应的这个模块?之后需要好好了解AnimatorOverrideController」
**
然后在PlayerAnimator中定义一个字典来存储不同的武器对应不同的动画:
定义了WeaponAnimations结构,用来外部存储武器和动画
[System.Serializable]
public struct WeaponAnimations
{
public Equipment weapon;
public AnimationClip[] clips;
}
public WeaponAnimations[] weaponAnimations;
Dictionary<Equipment, AnimationClip[]> weaponAnimDict;
protected override void Start()
{
base.Start();
weaponAnimDict = new Dictionary<Equipment, AnimationClip[]>();
//外部的输入 存入字典
foreach (WeaponAnimations a in weaponAnimations)
{
weaponAnimDict.Add(a.weapon, a.clips);
}
}
//改写OnEquipmentChanged,改变currentAttackAnimSet
void OnEquipmentChanged(Equipment newItem, Equipment oldItem)
{
if (newItem != null && newItem.equipsolt == EquipmentSlot.Weapon)
{
animator.SetLayerWeight(1, 1);
if (weaponAnimDict.ContainsKey(newItem))
{
currentAttackAnimSet = weaponAnimDict[newItem];
}
}
else if (newItem == null && oldItem != null && oldItem.equipsolt == EquipmentSlot.Weapon)
{
animator.SetLayerWeight(1, 0);
currentAttackAnimSet = defaultAttackAnimSet;
}
...
}
最后需要把 combat.OnAttack += OnAttack;
加入到PlayerAnimation中,也就是,当触发combat中攻击时候:需要同时调用PlayerAnimation中的OnAttack函数:
public event System.Action OnAttack;//「CharacterCombat中声明的委托」