Related Posts Plugin for WordPress, Blogger...

3-2 The Animator Override Controller

本章要讓Player使用不同的武器時會播放不同的Animation,使用的方式為Animator Override Controller,先來引用官方的介紹:

https://docs.unity3d.com/Manual/AnimatorOverrideController.html
The Animator Override Controller is a type of asset which allows you to extend an existing Animator Controller, replacing the specific animations used but otherwise retaining the original’s structure, parameters and logic.

This allows you to create multiple variants of the same basic state machine, but with each using different sets of animations. For example, your game may have a variety of NPC types living in the world, but each type (goblin, ogre, elf, etc) has their own unique animations for walking, idling, sitting, etc.

By creating one “base” Animator Controller containing the logic for all NPC types, you could then create an override for each type and drop in their respective animation files.

簡單說明上述的介紹,Animator Override Controller能在不改變原有State Machine框架下,動態變更Animation clip。比方說,你的遊戲可能有不同類型的NPC都套用相同的Animator,亦即套用相同的走路、待命、坐下等State Machine,但是其中的Animation卻需要依據妖精、哥布林等不同類型的NPC去更換。

同理,今天要介紹的遊戲功能便需要依據不同武器動態更換不同的Animation。

首先,我們需要先新增一個預設的Animation。

取名為DEFAULT ATTACK,這個Animation並沒有真實的動畫效果,只是為了讓我們在遊戲運作時,依據武器系統更換動畫的標記物。

再來選擇Player的Animator,選擇我們設定的Attack。如看見這一步看不懂的話,請先瀏覽以前的文章:
2-27 Import Mechanim Animation Pack

將Motion改成DEFAULT ATTACK。

再來新增一個Animator Override Controller。

取名為Player Animator Override。

點開Player Animator Override後,在Controller的部分選擇ThirdPersonAnimatorController,這是我們的Player目前使用的Controller。大家可以看見如下圖的樣子,有一個名為DEFAULT ATTACK的Animation,待會我們便需要透過程式碼動態更換該Animation。

然後在DEFAULT ATTACK的Inspector視窗下,可以見到沒有任何動畫。

大家可以將我們的Player拉進去,看起來比較有趣。

再來開始修改程式碼了!首先修改Weapon.cs,增加一個GetAnimClip方法回傳該武器的Animation:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Weapons{
 [CreateAssetMenu(menuName="RPG/Weapon")]
 public class Weapon : ScriptableObject {

  public Transform gripTransform;

  [SerializeField] GameObject weaponPrefab;
  [SerializeField] AnimationClip attackAnimation;

  public GameObject GetWeaponPrefab(){
   return weaponPrefab;
  }

  public AnimationClip GetAnimClip(){
   RemoveAnimationEvent ();
   return attackAnimation;
  }

  // 避免Asset Pack造成我們的遊戲Crash
  private void RemoveAnimationEvent(){
   attackAnimation.events = new AnimationEvent[0];
  }
 }
}


再來是Player.cs,這邊需要動態更換Animation,註解寫在程式碼中:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using RPG.CameraUI; // TODO consider re-wiring
using RPG.Core;
using RPG.Weapons;

namespace RPG.Character{
 public class Player : MonoBehaviour, IDamageable {

  [SerializeField] float maxHealthPoints = 100f;
  [SerializeField] float damagePerHit = 50f;
  [SerializeField] float minTimeBetweenHits = 0.5f;
  [SerializeField] float maxAttackRange = 5f;
  [SerializeField] AnimatorOverrideController animatorOverrideController;

  [SerializeField] Weapon weaponInUse;

  float currentHealthPoint;
  float lastHitTime;
  CameraRaycaster cameraRaycaster;

  public float healthAsPercentage{
   get{ return currentHealthPoint / maxHealthPoints; }
  }

  void Start(){
   // 初始化血量
   currentHealthPoint = maxHealthPoints;

   cameraRaycaster = FindObjectOfType ();
   cameraRaycaster.notifyMouseClickObservers += OnMouseClick ;
   // 初始化武器,放置到手中
   PutWeaponInHand ();
   // 覆寫人物角色中的Animation
   OverrideAnimatorController ();
  }

  private void OverrideAnimatorController(){
   // 取得Animator物件
   Animator animator = GetComponent ();
   // 將人物的Animator取代為我們剛剛新增的Player Animator Override
   animator.runtimeAnimatorController = animatorOverrideController;
   // Player Animator Override中有一預設的DEFAULT ATTACK,將其取代成Weapon物件中的Animation
   // 亦即人物的Animation將會依據不同的Weapon物件,而使用有不同的動畫
   animatorOverrideController ["DEFAULT ATTACK"] = weaponInUse.GetAnimClip ();
  }

  private void PutWeaponInHand(){
   GameObject weaponPrefab = weaponInUse.GetWeaponPrefab ();
   // 生成武器時,將會放置到指定的GameObject之下,此處為WeaponSocket之下
   // 故可將WeaponSocket設置成玩家的右手或左手
   GameObject weaponSocket = RequestDominantHand();
   GameObject weapon = Instantiate (weaponPrefab, weaponSocket.transform);
   // 將武器放置到正確的位置,並有正確的方向
   weapon.transform.localPosition = weaponInUse.gripTransform.localPosition;
   weapon.transform.localRotation = weaponInUse.gripTransform.localRotation;
  }

  private GameObject RequestDominantHand(){
   // 從Children中尋找包含DominantHand的物件
   DominantHand[] dominantHand = GetComponentsInChildren ();
   // 計算取得有DominantHand的物件的數量
   int numberOfDominantHands = dominantHand.Length;
   // 確保該數量不為0
   Assert.AreNotEqual (numberOfDominantHands, 0, "No DominantHand found on player, please add one");
   // 確保該數量不要大於1
   Assert.IsFalse (numberOfDominantHands > 1, "Multiple Dominant script on Player, please remove one");
   // 傳回屬於該DominantHand的GameObject
   return dominantHand [0].gameObject;
  }

  void OnMouseClick(RaycastHit raycastHit, int layerHit){
   const int enemyLayer = 9;
   if (layerHit == enemyLayer) {
    GameObject enemy = raycastHit.collider.gameObject;

    // 確認敵人是否在範圍技的攻擊範圍內
    if ((enemy.transform.position - transform.position).magnitude > maxAttackRange) {
     return; // 超過攻擊範圍就跳出
    }

    // 控制攻擊頻率,若本次攻擊時間離上一次攻擊時間大於minTimeBetweenHits,才允許攻擊
    // 相當於範圍技的CD時間,不能一直發動技能!
    if (Time.time - lastHitTime > minTimeBetweenHits) {
     IDamageable enemyDamageable = enemy.GetComponent (typeof(IDamageable)) as IDamageable;
     // 讓敵人損血
     enemyDamageable.TakeDamage (damagePerHit);
     // 紀錄目前攻擊的時間
     lastHitTime = Time.time;
    }

   }
  }

  public void TakeDamage(float damage){
   // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值
   currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints);
  }
 }
}

接著,請大家將剛剛新增的Player Animator Override拉進Player的Animator Override Controller。

如下圖,請打開Animation視窗,大家可以看見最下面有一個DEFAULT ATTACK的Animation。

再來要替Weapon的ScriptableObject指定需要的Animation。如果大家不懂什麼是ScriptableObject請看以前的文章:
2-25 Introducing Scriptable Objects

此處我選擇的範例是Unarmed-Attack-Kick-L1。

最後,執行遊戲看看!當遊戲運作後,就會發現原本最下方的DEFAULT ATTACK被自動更換成Unarmed-Attack-Kick_L1。

假設我換成另外一個武器Dirty Old Hoe,並將Animation設定成Unarmed-Attack-Kick-R1。

在Player的Weapon In Use中設定Dirty Old Hoe更換武器。

 再度執行遊戲,就會發現Animation這是被更換成Unarmed-Attack-Kick-R1了!

留言