3-13 Player Damage & Death Sound
本章要教大家播放受傷跟死亡的音效。首先請大家下載角色的音效,花了很多時間才找到滿意的免費音效:
http://www.affordableaudio4everyone.com/Affordable%20SFX%204%20Everyone/Samples/ActionRPG_Characters/ActionRPG_Char_Samples.zip
另外這是該音效的官網,介紹是說有4290個音效,只要約600元的樣子,大家有興趣可以參考看看:
http://www.affordableaudio4everyone.com/Affordable%20SFX%204%20Everyone/ActionRPG_Characters.html
但我還沒買,在目前的階段中,我們使用免費音效來測試程式是否能正常運作即可。往後要優化遊戲體驗時,再找更好的資源檔進行更換。
首先,先開啟File/Build Settings選項。
按下「Add Open Scenes」將目前的開啟的Scene加入,因為我們要製作當玩家死亡後會重新讀取關卡的功能,必須先將Scene登記在Build Settings中。
然後,在Player中加入Audio Source,這是用來播放音效的組件。
將Play On Awake選項關掉。
接著,請將剛剛下載的音效檔匯入,或者匯入自己預先準備的音效檔。
主要修改Player.cs及Enemy.cs。於Player.cs的TakeDamage方法中新增條件式判斷血量,血量不足時呼叫KillPlayer方法,這邊使用StartCoroutine的呼叫方式,是為了讓程式等待死亡音效播放完畢後,再重新載入場景。播放音效的部分,則用Random隨機函數挑選其中一個音效檔。
Enemy.cs的Update中,則增加了判斷玩家是否已死的條件式,當玩家死了以後就不要再攻擊玩家了。不然會發生Player喊著死亡音效的時候,Enemy還在拚命鞭屍的窘境。(ˊˋ)
最後,將音效拉入Damage Sounds跟Death Sounds即可。
以下提供本章修改的完整程式碼。
Player.cs:
Enemy.cs:
http://www.affordableaudio4everyone.com/Affordable%20SFX%204%20Everyone/Samples/ActionRPG_Characters/ActionRPG_Char_Samples.zip
另外這是該音效的官網,介紹是說有4290個音效,只要約600元的樣子,大家有興趣可以參考看看:
http://www.affordableaudio4everyone.com/Affordable%20SFX%204%20Everyone/ActionRPG_Characters.html
但我還沒買,在目前的階段中,我們使用免費音效來測試程式是否能正常運作即可。往後要優化遊戲體驗時,再找更好的資源檔進行更換。
首先,先開啟File/Build Settings選項。
按下「Add Open Scenes」將目前的開啟的Scene加入,因為我們要製作當玩家死亡後會重新讀取關卡的功能,必須先將Scene登記在Build Settings中。
然後,在Player中加入Audio Source,這是用來播放音效的組件。
將Play On Awake選項關掉。
接著,請將剛剛下載的音效檔匯入,或者匯入自己預先準備的音效檔。
主要修改Player.cs及Enemy.cs。於Player.cs的TakeDamage方法中新增條件式判斷血量,血量不足時呼叫KillPlayer方法,這邊使用StartCoroutine的呼叫方式,是為了讓程式等待死亡音效播放完畢後,再重新載入場景。播放音效的部分,則用Random隨機函數挑選其中一個音效檔。
Enemy.cs的Update中,則增加了判斷玩家是否已死的條件式,當玩家死了以後就不要再攻擊玩家了。不然會發生Player喊著死亡音效的時候,Enemy還在拚命鞭屍的窘境。(ˊˋ)
最後,將音效拉入Damage Sounds跟Death Sounds即可。
以下提供本章修改的完整程式碼。
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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.SceneManagement; 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 baseDamage = 50f; [SerializeField] AnimatorOverrideController animatorOverrideController; [SerializeField] Weapon weaponInUse; [SerializeField] SpecialAbilityConfig[] abilities; [SerializeField] AudioClip[] damageSounds; [SerializeField] AudioClip[] deathSounds; float lastHitTime; float currentHealthPoint; CameraRaycaster cameraRaycaster; ISpecialAbility[] specialAbility; AudioSource audioSource; Animator animator; public float healthAsPercentage{ get { return currentHealthPoint / maxHealthPoints; } } void Start(){ // 初始化血量 currentHealthPoint = maxHealthPoints; cameraRaycaster = FindObjectOfType<cameraraycaster> (); // 註冊滑鼠碰到敵人的事件 cameraRaycaster.onMouseOverEnemy += OnMouseOverEnemy; // 初始化武器,放置到手中 PutWeaponInHand (); // 覆寫人物角色中的Animation OverrideAnimatorController (); // 替Player增加技能的Behaviour,並回傳ISpecialAbility物件 AttachSpecialAbility(); audioSource = GetComponent<audiosource> (); } private void AttachSpecialAbility(){ specialAbility = new ISpecialAbility[abilities.Length]; for ( int i = 0; i < abilities.Length; i++) { // 儲存ISpecialAbility物件 specialAbility[i] = abilities [i].AddComponent (gameObject); } } 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 OnMouseOverEnemy(Enemy enemy){ if (Input.GetMouseButtonDown (0) && IsTargetInRange (enemy)) { AttackTarget (enemy); } else if (Input.GetMouseButtonDown (1)) { // 0為使用第一個技能 AttemptSpecialAbility(0, enemy); } } private void AttemptSpecialAbility( int abilityIndex, Enemy enemy){ Energy energyComponent = GetComponent<energy> (); // 取得技能需要的能量消耗量 float energyCost = abilities [abilityIndex].GetEnergyCost (); if (energyComponent.IsEnergyAvailable (energyCost)) { energyComponent.ConsumeEnergy (energyCost); // 發動技能,並傳入Player的baseDamage AbilityParams abilityParams = new AbilityParams (enemy, baseDamage); specialAbility [abilityIndex].Use(abilityParams); } } private void AttackTarget(Enemy enemy){ // 確認本次攻擊時間離上一次攻擊時間須大於minTimeBetweenHits,相當於技能冷卻時間 if ( (Time.time - lastHitTime > weaponInUse.GetMinTimeBetweenHits())) { // 呼叫Trigger啟動攻擊動畫 animator.SetTrigger( "Attack" ); // TODO make const // 呼叫攻擊Target的方法 enemy.TakeDamage(baseDamage); // 紀錄目前攻擊的時間 lastHitTime = Time.time; } } // 確認敵人是否在範圍技的攻擊範圍內 private bool IsTargetInRange(Enemy enemy){ float distanceToTarget = (enemy.transform.position - transform.position).magnitude; return distanceToTarget <= weaponInUse.GetMaxAttackRange(); } public void TakeDamage( float damage){ // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值 currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints); // 隨機播放受傷的音效 audioSource.clip = damageSounds [Random.Range (0, damageSounds.Length)]; audioSource.Play (); bool playerDies = currentHealthPoint <= 0; if (playerDies) { StartCoroutine (KillPlayer ()); } } IEnumerator KillPlayer(){ // 播放死亡音效 audioSource.clip = deathSounds [Random.Range (0, deathSounds.Length)]; audioSource.Play (); // 播放死亡動畫 // 等待一段時間(依音效長度而定) yield return new WaitForSecondsRealtime(audioSource.clip.length); // 重載關卡 SceneManager.LoadScene(0); } } } </energy></dominanthand></animator></audiosource></cameraraycaster> |
Enemy.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 108 109 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; using UnityStandardAssets.Characters.ThirdPerson; using RPG.Core; using RPG.Weapons; namespace RPG.Character{ public class Enemy : MonoBehaviour, IDamageable { [SerializeField] float maxHealthPoints = 100f; [SerializeField] float attackRadius = 3.0f; [SerializeField] float chaseRadius = 10.0f; [SerializeField] float damagePerShot = 9f; [SerializeField] float secondsBetweenShots = 0.5f; [SerializeField] GameObject projectileToUse; [SerializeField] GameObject projectileSocket; [SerializeField] Vector3 aimOffset = new Vector3(0, 1f, 0); bool isAttacking = false ; float currentHealthPoint; AICharacterControl aiCharacterControl = null ; Player player; IEnumerator coroutine; WaitForSeconds waitForShots; public float healthAsPercentage{ get { return currentHealthPoint / maxHealthPoints; } } void Start(){ currentHealthPoint = maxHealthPoints; aiCharacterControl = GetComponent<aicharactercontrol> (); player = GameObject.FindObjectOfType<player>(); waitForShots = new WaitForSeconds (secondsBetweenShots); } void Update(){ // 判斷Player是否已經死了 if (player.healthAsPercentage <= Mathf.Epsilon) { StopAllCoroutines (); // 阻止Enemy在Player已經死亡的狀態下仍繼續攻擊 Destroy ( this ); } // 計算Player跟Enemy的距離 float distanceToPlayer = Vector3.Distance (player.transform.position, transform.position); // 若彼此之間的距離小於attackRadius,就讓Enemy開始攻擊Player if (distanceToPlayer <= attackRadius && !isAttacking) { isAttacking = true ; coroutine = SpawnProjectile (); StartCoroutine (coroutine); } if (distanceToPlayer > attackRadius && isAttacking) { isAttacking = false ; StopCoroutine (coroutine); } // 若彼此之間的距離小於chaseRadius,就讓Enemy開始追蹤Player if (distanceToPlayer <= chaseRadius) { aiCharacterControl.SetTarget (player.transform); } else { aiCharacterControl.SetTarget (transform); } } IEnumerator SpawnProjectile(){ while ( true ) { yield return waitForShots; // Instantiate可以生成GameObject,Quaternion.identity為方向不進行任何轉向 GameObject newProjectile = Instantiate(projectileToUse, projectileSocket.transform.position, Quaternion.identity); // 取得Projectile Projectile projectileComponent = newProjectile.GetComponent<projectile> (); // 設定Projectile的攻擊威力 projectileComponent.SetDamage(damagePerShot); // 設定Projectile的Shooter為自己 projectileComponent.SetShooter (gameObject); // 計算發射Projectile到Player之間的單位向量 Vector3 unitVectorToPlayer = (player.transform.position + aimOffset - projectileSocket.transform.position).normalized; float projectileSpeed = projectileComponent.GetDefaultLaunchSpeed(); // 將單位向量乘以發射速度,透過velocity將Projectile發射出去吧! newProjectile.GetComponent<rigidbody> ().velocity = unitVectorToPlayer * projectileSpeed; } } public void TakeDamage( float damage){ // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值 currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints); // 敵人血量低於0就會消失 if (currentHealthPoint <= 0) { Destroy (gameObject); } } void OnDrawGizmos(){ //繪製攻擊範圍 Gizmos.color = new Color(255f, 0f, 0f, 0.5f); Gizmos.DrawWireSphere (transform.position, attackRadius); //繪製移動範圍 Gizmos.color = new Color(0f, 0f, 255f, 0.5f); Gizmos.DrawWireSphere (transform.position, chaseRadius); } } } </rigidbody></projectile></player></aicharactercontrol> |
留言
張貼留言