Related Posts Plugin for WordPress, Blogger...

3-22 Critical Hit And Weapon Bonus

本章要來替Player的武器增加攻擊力的計算,以及增加發動致命一擊的機率。致命一擊是指普通攻擊之中,有機率揮出超強攻擊的一擊,為普通攻擊的好幾倍,又可以稱為會心一擊、爆擊等等。

首先,將我們想要用在致命一擊的粒子效果放到Player之下。

然後讓我們調整一下粒子系統運作的位置、大小。

記得將Looping跟Play On Awake都關掉。

且有些從Asset Store下載的粒子系統同時包含火焰、煙霧等等,所以在子階層的部分還會有其他粒子系統,記得這邊也要關掉Loop。

然後將調整好的Critical Hit Particles拉進Project變成Prefab。

程式碼主要修改在Player跟Weapon,來看看Player主要修改計算攻擊值的部分。使用Random.Range方法計算是否會出現致命一擊,再將傷害值乘以criticalHitMultiplier,並播放特效。

以下提供完整程式碼。
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
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<audiosource> ();
 
   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> ();
   // 將人物的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;
  }
 
  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);
    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);
  }
 }
}
</energy></cameraraycaster></dominanthand></animator></audiosource>

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
39
40
41
42
43
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設定。

 來測試遊戲看看吧!攻擊後會確實發動致命一擊,酷炫的火焰特效也有正確發動。


留言