3-3 Trigger Player Hit Animation And Audio On Radius
本章要介紹如何播放Player的攻擊動畫,以及當Player進入到某個範圍後會播放音效。因為前半段製作過程都已有文章進行說明,如大家有不懂的地方請先閱讀以下文章:
2-27 Import Mechanim Animation Pack
https://rpgcorecombat.blogspot.tw/2017/12/2-27-import-mechanim-animation-pack.html
3-2 The Animator Override Controller
AudioTrigger的程式碼如下:
再來建立一個Empty GameObject,命名為Audio Trigger,並將Script拉進去。大家可以看見有幾個選項Clip是音效資源、Layer Filter是指觸發音效的Layer、Trigger Radius是觸發範圍、Is One Time Only指是否觸發一次音效後便不再播放、Has Player是用來判斷是否已播放音效的布林變數。
接著,因為我的Player Layer設置在第10個。
所以,Layer Filter設定為10。音效的部分我在這邊下載了一些免費的音效來測試:
接著,Audio Trigger拉入Project中變成prefab。
先執行遊戲測試看看,當玩家進入綠色範圍內時便會播放音效。
好的!那我們將Audio Trigger拉入Healer內。
這樣,當玩家進入Healer的補血範圍內後,就會播放音效了。
2-27 Import Mechanim Animation Pack
https://rpgcorecombat.blogspot.tw/2017/12/2-27-import-mechanim-animation-pack.html
3-2 The Animator Override Controller
主要更改的程式碼為Player.cs,只要在呼叫IDamageable的TakeDamage之前,使用animator呼叫SetTrigger方法即可,如下圖。
我在Player.cs中進行了一些重構讓方法可以自我解釋:
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 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] AnimatorOverrideController animatorOverrideController; [SerializeField] Weapon weaponInUse; Animator animator; float currentHealthPoint; float lastHitTime; CameraRaycaster cameraRaycaster; public float healthAsPercentage{ get { return currentHealthPoint / maxHealthPoints; } } void Start(){ // 初始化血量 currentHealthPoint = maxHealthPoints; cameraRaycaster = FindObjectOfType<cameraraycaster> (); cameraRaycaster.notifyMouseClickObservers += OnMouseClick ; // 初始化武器,放置到手中 PutWeaponInHand (); // 覆寫人物角色中的Animation OverrideAnimatorController (); } private void OverrideAnimatorController(){ // 取得Animator物件 animator = GetComponent<animator> (); // 將人物的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> (); // 計算取得有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 (IsTargetInRange (enemy)) { AttackTarget (enemy); } } } private void AttackTarget(GameObject target){ IDamageable damageable = target.GetComponent<idamageable> (); // 確認本次攻擊時間離上一次攻擊時間須大於minTimeBetweenHits,相當於技能冷卻時間 if ( (Time.time - lastHitTime > weaponInUse.GetMinTimeBetweenHits())) { // 呼叫Trigger啟動攻擊動畫 animator.SetTrigger( "Attack" ); // TODO make const // 呼叫攻擊Target的方法 damageable.TakeDamage(damagePerHit); // 紀錄目前攻擊的時間 lastHitTime = Time.time; } } // 確認敵人是否在範圍技的攻擊範圍內 private bool IsTargetInRange(GameObject target){ float distanceToTarget = (target.transform.position - transform.position).magnitude; return distanceToTarget <= weaponInUse.GetMaxAttackRange(); } public void TakeDamage( float damage){ // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值 currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints); } } } </idamageable></dominanthand></animator></cameraraycaster> |
然後,因為minTimeBetweenHits跟maxAttackRange這兩個變數,我認為要取決於不同的武器,所以將它們拉到Weapon.cs裡面:
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 | 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; [SerializeField] float minTimeBetweenHits = 1f; [SerializeField] float maxAttackRange = 5f; public float GetMinTimeBetweenHits(){ return minTimeBetweenHits; } public float GetMaxAttackRange(){ return maxAttackRange; } public GameObject GetWeaponPrefab(){ return weaponPrefab; } public AnimationClip GetAnimClip(){ RemoveAnimationEvent (); return attackAnimation; } // 避免Asset Pack造成我們的遊戲Crash private void RemoveAnimationEvent(){ attackAnimation.events = new AnimationEvent[0]; } } } |
執行遊戲以後,就會發現玩家可以發動帥帥的動畫了!
動畫的部分,除了之前介紹的免費動畫以外,也推薦大家可以購買付費版的唷!有非常多種不同武器的動畫:
接著,再來介紹如何讓Player進入特定範圍後播放音效。首先請大家建立Script名為AudioTrigger。
AudioTrigger的程式碼如下:
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 50 51 | using UnityEngine; public class AudioTrigger : MonoBehaviour { [SerializeField] AudioClip clip; [SerializeField] int layerFilter = 0; [SerializeField] float triggerRadius = 5f; [SerializeField] bool isOneTimeOnly = true ; [SerializeField] bool hasPlayed = false ; AudioSource audioSource; void Start() { audioSource = gameObject.AddComponent<audiosource>(); audioSource.playOnAwake = false ; audioSource.clip = clip; SphereCollider sphereCollider = gameObject.AddComponent<spherecollider>(); sphereCollider.isTrigger = true ; sphereCollider.radius = triggerRadius; } void OnTriggerEnter(Collider other) { if (other.gameObject.layer == layerFilter) { RequestPlayAudioClip(); } } void RequestPlayAudioClip() { if (isOneTimeOnly && hasPlayed) { return ; } else if (audioSource.isPlaying == false ) { audioSource.Play(); hasPlayed = true ; } } void OnDrawGizmos() { Gizmos.color = new Color(0, 255f, 0, .5f); Gizmos.DrawWireSphere(transform.position, triggerRadius); } } </spherecollider></audiosource> |
再來建立一個Empty GameObject,命名為Audio Trigger,並將Script拉進去。大家可以看見有幾個選項Clip是音效資源、Layer Filter是指觸發音效的Layer、Trigger Radius是觸發範圍、Is One Time Only指是否觸發一次音效後便不再播放、Has Player是用來判斷是否已播放音效的布林變數。
接著,因為我的Player Layer設置在第10個。
所以,Layer Filter設定為10。音效的部分我在這邊下載了一些免費的音效來測試:
接著,Audio Trigger拉入Project中變成prefab。
先執行遊戲測試看看,當玩家進入綠色範圍內時便會播放音效。
好的!那我們將Audio Trigger拉入Healer內。
這樣,當玩家進入Healer的補血範圍內後,就會播放音效了。
留言
張貼留言