3-22 Critical Hit And Weapon Bonus
本章要來替Player的武器增加攻擊力的計算,以及增加發動致命一擊的機率。致命一擊是指普通攻擊之中,有機率揮出超強攻擊的一擊,為普通攻擊的好幾倍,又可以稱為會心一擊、爆擊等等。
首先,將我們想要用在致命一擊的粒子效果放到Player之下。
然後讓我們調整一下粒子系統運作的位置、大小。
記得將Looping跟Play On Awake都關掉。
且有些從Asset Store下載的粒子系統同時包含火焰、煙霧等等,所以在子階層的部分還會有其他粒子系統,記得這邊也要關掉Loop。
然後將調整好的Critical Hit Particles拉進Project變成Prefab。
以下提供完整程式碼。
Player.cs:
Weapon.cs:
撰寫好以後,回到Player的設定視窗,讓我們先將Critical Hit Chance調整成1,方便我們測試攻擊時發動致命一擊的效果,並將Player底下的Critical Hit Particles拉進Critical Hit Particle設定。
來測試遊戲看看吧!攻擊後會確實發動致命一擊,酷炫的火焰特效也有正確發動。
首先,將我們想要用在致命一擊的粒子效果放到Player之下。
然後讓我們調整一下粒子系統運作的位置、大小。
記得將Looping跟Play On Awake都關掉。
程式碼主要修改在Player跟Weapon,來看看Player主要修改計算攻擊值的部分。使用Random.Range方法計算是否會出現致命一擊,再將傷害值乘以criticalHitMultiplier,並播放特效。
Player.cs:
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; [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"; float lastHitTime; float currentHealthPoint; Enemy currentEnemy; CameraRaycaster cameraRaycaster; ISpecialAbility[] specialAbility; AudioSource audioSource; Animator animator; public float healthAsPercentage{ get{ return currentHealthPoint / maxHealthPoints; } } void Start(){ audioSource = GetComponent(); RegisterForMouseClcik (); SetCurrentMaxHealth (); PutWeaponInHand (); 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(){ 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取代為我們剛剛新增的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; } private void SetCurrentMaxHealth(){ currentHealthPoint = maxHealthPoints; } private void RegisterForMouseClcik(){ cameraRaycaster = FindObjectOfType (); // 註冊滑鼠碰到敵人的事件 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 (); // 取得技能需要的能量消耗量 float energyCost = abilities [abilityIndex].GetEnergyCost (); if (energyComponent.IsEnergyAvailable (energyCost)) { energyComponent.ConsumeEnergy (energyCost); // 發動技能,並傳入Player的baseDamage AbilityParams abilityParams = new AbilityParams (currentEnemy, baseDamage); specialAbility [abilityIndex].Use(abilityParams); } } private void AttackTarget(){ // 確認本次攻擊時間離上一次攻擊時間須大於minTimeBetweenHits,相當於技能冷卻時間 if ( (Time.time - lastHitTime > weaponInUse.GetMinTimeBetweenHits())) { // 呼叫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 + weaponInUse.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 <= 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 ()); } } 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); } } }
Weapon.cs:
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; [SerializeField] float additionalDamage = 10f; public float GetMinTimeBetweenHits(){ return minTimeBetweenHits; } public float GetMaxAttackRange(){ return maxAttackRange; } public GameObject GetWeaponPrefab(){ return weaponPrefab; } public AnimationClip GetAnimClip(){ RemoveAnimationEvent (); return attackAnimation; } public float GetAdditionalDamage(){ return additionalDamage; } // 避免Asset Pack造成我們的遊戲Crash private void RemoveAnimationEvent(){ attackAnimation.events = new AnimationEvent[0]; } } }
撰寫好以後,回到Player的設定視窗,讓我們先將Critical Hit Chance調整成1,方便我們測試攻擊時發動致命一擊的效果,並將Player底下的Critical Hit Particles拉進Critical Hit Particle設定。
留言
張貼留言