4-3 Weapon Pickup Points
今天要來介紹撿拾地面的武器的機制~玩家經過放置在地上的武器後,會自動更換成玩家手中的武器。首先,在場景中製作一個空白的GameObject。
取名為Weapon Pickup。
拉進Project視窗中變成Prefab。
新增一個Script名為Weapon Pickup Point,並拉進Weapon Pickup的Prefab中。
接著先向大家介紹WeaponPickPoint中比較特別的程式碼,ExecuteInEditMode,在Class名稱之前加上這段程式碼後,Unity便會在編輯模式中自動執行Update內部的程式碼,執行的時機為編輯中有一些變動時,比如用滑鼠點擊不同的物件就會觸發。
再新增一個Audio Source,用來播放撿拾武器的音效。
再來,我們要修改Player.cs內更換武器的機制,原先於Player.cs中已撰寫了一個方法PutWeaponInHand,只要稍加修改後便能拿來利用。
好的,以下提供Player.cs完整程式碼:
完成!讓我們來看看實際效果如何,於編輯模式中如下圖,會顯示該武器的模型在場景中。
執行遊戲看看,玩家手中拿的還是原本的劍。
經過地面上的鋤頭後,玩家手中的武器成功更換為鋤頭了!
取名為Weapon Pickup。
拉進Project視窗中變成Prefab。
新增一個Script名為Weapon Pickup Point,並拉進Weapon Pickup的Prefab中。
接著先向大家介紹WeaponPickPoint中比較特別的程式碼,ExecuteInEditMode,在Class名稱之前加上這段程式碼後,Unity便會在編輯模式中自動執行Update內部的程式碼,執行的時機為編輯中有一些變動時,比如用滑鼠點擊不同的物件就會觸發。
如下圖,我們撰寫產生武器在場景中的程式。
好的!以下提供大家WeaponPickUp.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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using RPG.Character; namespace RPG.Weapons{ // 在編輯模式執行程式碼 [ExecuteInEditMode] public class WeaponPickupPoint : MonoBehaviour { [SerializeField] Weapon weaponConfig; [SerializeField] AudioClip pickupSFX; AudioSource audioSource; void Start () { audioSource = GetComponent<audiosource> (); } void Update () { // 判斷要在編輯模式下才執行程式 if (!Application.isPlaying) { DestroyChildren (); InstantiateWeapon (); } } void DestroyChildren(){ foreach (Transform child in transform) { DestroyImmediate (child.gameObject); } } // 產生預覽用的武器物件於場景中 void InstantiateWeapon(){ GameObject weapon = weaponConfig.GetWeaponPrefab(); weapon.transform.position = Vector3.zero; Instantiate (weapon, gameObject.transform); } // 觸發Trigger時更換玩家的武器 void OnTriggerEnter(){ FindObjectOfType<player> ().PutWeaponInHand(weaponConfig); audioSource.PlayOneShot (pickupSFX); } } } </player></audiosource> |
撰寫好後,請將拉入素材進相關參數,Weapon Config要放進一個武器,Pickup SFX要放進撿拾武器的音效。接著,再請大家新增一個Box Collider,勾選Is Trigger,然後請大家調整Box Collider的位置與武器對應,當玩家觸發到Trigger時便會更換玩家手中的武器。
再新增一個Audio Source,用來播放撿拾武器的音效。
再來,我們要修改Player.cs內更換武器的機制,原先於Player.cs中已撰寫了一個方法PutWeaponInHand,只要稍加修改後便能拿來利用。
好的,以下提供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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | 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 currentWeaponConfig; [SerializeField] AbilityConfig[] abilities; [SerializeField] AudioClip[] damageSounds; [SerializeField] AudioClip[] deathSounds; [Range(.1f, 1.0f)] [SerializeField] float criticalHitChance = 0.1f; [SerializeField] float criticalHitMultiplier = 2.5f; [SerializeField] ParticleSystem criticalHitParticle; // 定義Trigger的字串常數 const string DEATH_TRIGGER = "Death" ; const string ATTACK_TRIGGER = "Attack" ; const string DEFAULT_ATTACK = "DEFAULT ATTACK" ; float lastHitTime; float currentHealthPoint; Enemy currentEnemy; CameraRaycaster cameraRaycaster; AbilityBehaviour[] abilityBehaviour; AudioSource audioSource; Animator animator; GameObject weaponInUse; public float healthAsPercentage{ get { return currentHealthPoint / maxHealthPoints; } } void Start(){ audioSource = GetComponent<audiosource> (); RegisterForMouseClcik (); SetCurrentMaxHealth (); PutWeaponInHand (currentWeaponConfig); OverrideAnimatorController (); AttachSpecialAbility(); } void Update(){ if (healthAsPercentage > Mathf.Epsilon) { ScanForAbilityKeyDown (); } } private void ScanForAbilityKeyDown(){ for ( int keyIndex = 1; keyIndex <= abilities.Length; keyIndex++) { if (Input.GetKeyDown (keyIndex.ToString())) { AttemptSpecialAbility (keyIndex); } } } private void AttachSpecialAbility(){ abilityBehaviour = new AbilityBehaviour[abilities.Length]; for ( int i = 0; i < abilities.Length; i++) { // 儲存ISpecialAbility物件 abilityBehaviour[i] = abilities [i].AttachAbilityTo (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] = currentWeaponConfig.GetAnimClip (); } public void PutWeaponInHand(Weapon weaponToUse){ currentWeaponConfig = weaponToUse; // 先移除之前手持的武器 Destroy(weaponInUse); GameObject weaponPrefab = weaponToUse.GetWeaponPrefab (); // 生成武器時,將會放置到指定的GameObject之下,此處為WeaponSocket之下 // 故可將WeaponSocket設置成玩家的右手或左手 GameObject weaponSocket = RequestDominantHand(); weaponInUse = Instantiate (weaponPrefab, weaponSocket.transform); // 將武器放置到正確的位置,並有正確的方向 weaponInUse.transform.localPosition = currentWeaponConfig.gripTransform.localPosition; weaponInUse.transform.localRotation = currentWeaponConfig.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; } private void SetCurrentMaxHealth(){ currentHealthPoint = maxHealthPoints; } private void RegisterForMouseClcik(){ cameraRaycaster = FindObjectOfType<cameraraycaster> (); // 註冊滑鼠碰到敵人的事件 cameraRaycaster.onMouseOverEnemy += OnMouseOverEnemy; } void OnMouseOverEnemy(Enemy enemy){ currentEnemy = enemy; if (Input.GetMouseButtonDown (0) && IsTargetInRange (enemy.gameObject)) { AttackTarget (); } else if (Input.GetMouseButtonDown (1)) { // 0為使用第一個技能 AttemptSpecialAbility(0); } } private void AttemptSpecialAbility( int abilityIndex){ Energy energyComponent = GetComponent<energy> (); // 取得技能需要的能量消耗量 float energyCost = abilities [abilityIndex].GetEnergyCost (); if (energyComponent.IsEnergyAvailable (energyCost)) { energyComponent.ConsumeEnergy (energyCost); // 發動技能,並傳入Player的baseDamage AbilityParams abilityParams = new AbilityParams (currentEnemy, baseDamage); abilityBehaviour [abilityIndex].Use(abilityParams); } } private void AttackTarget(){ // 確認本次攻擊時間離上一次攻擊時間須大於minTimeBetweenHits,相當於技能冷卻時間 if ( (Time.time - lastHitTime > currentWeaponConfig.GetMinTimeBetweenHits())) { // 每次攻擊前都重設武器的攻擊動畫 OverrideAnimatorController(); // 呼叫Trigger啟動攻擊動畫 animator.SetTrigger(ATTACK_TRIGGER); // 呼叫攻擊Target的方法 currentEnemy.TakeDamage(CalculateDamage()); // 紀錄目前攻擊的時間 lastHitTime = Time.time; } } private float CalculateDamage(){ // 產生隨機值,比較該值是否小於致命一擊的機率 bool isCriticalHit = Random.Range (0f, 1f) <= criticalHitChance; float damageBeforeCritical = baseDamage + currentWeaponConfig.GetAdditionalDamage (); if (isCriticalHit) { // 播放致命一擊的特效 criticalHitParticle.Play (); // 回傳攻擊力乘上致命一擊的乘率 return damageBeforeCritical * criticalHitMultiplier; } else { return damageBeforeCritical; } } // 確認敵人是否在範圍技的攻擊範圍內 private bool IsTargetInRange(GameObject target){ float distanceToTarget = (target.transform.position - transform.position).magnitude; return distanceToTarget <= currentWeaponConfig.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 ()); } } public void Heal( float points){ currentHealthPoint = Mathf.Clamp (currentHealthPoint + points, 0f, maxHealthPoints); } IEnumerator KillPlayer(){ // 播放死亡音效 audioSource.clip = deathSounds [Random.Range (0, deathSounds.Length)]; audioSource.Play (); // 播放死亡動畫 animator.SetTrigger(DEATH_TRIGGER); // 等待一段時間(依音效長度而定) yield return new WaitForSecondsRealtime(audioSource.clip.length); // 重載關卡 SceneManager.LoadScene(0); } } } </energy></cameraraycaster></dominanthand></animator></audiosource> |
完成!讓我們來看看實際效果如何,於編輯模式中如下圖,會顯示該武器的模型在場景中。
執行遊戲看看,玩家手中拿的還是原本的劍。
經過地面上的鋤頭後,玩家手中的武器成功更換為鋤頭了!
留言
張貼留言