Related Posts Plugin for WordPress, Blogger...

3-20 Triggering Special Abilities From Keys

本章要來製作快捷鍵發動技能嘍!首先先讓我們修改一下介面吧,選擇GameObject/UI/Image來新增圖片資源。

將之前提供的圖片檔案Icon Special Ability Power Attack放置上去,如果不知道怎麼指定圖片資源以及排版,可以先參考以前的文章:
3-16 Using Image Fill In UI

接著將剛剛新增好的Image多複製幾份,分別取名為Special Slot (0)、Special Slot (1)、Special Slot (2)。

然後將圖片資源都放進去,排列整齊,如下圖的樣子。

接著,在Game Canvas按右鍵,Create Empty,取名為Keyboard Affordances。

於Keyboard Affordances按右鍵選擇UI/Text,新增文字框。

將文字框移動到技能圖片上面,文字寫1,自己調整Font Size跟Color,Alignment可以如下圖那樣設定,數字就會在技能圖片的右下角。

然後將文字框再複製一份。

將複製的文字框移動到旁邊的技能圖片上,文字改成2。這樣,我們就把UI的部分都設定好了!

主要修改Player.cs,如下圖,我們在Update撰寫方法來實時感應鍵盤的事件,先是撰寫條件式確保Player的血量不為0,以免Player死了以後還能透過快捷鍵發動技能。再來是撰寫ScanForAbilityKeyDown方法,當使用者按下的數字鍵為已存在技能中的其中一項,就呼叫AttemptSpecialAbility發動技能。

以下提供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
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;
 
  // 定義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.AdjustHealth(baseDamage);
    // 紀錄目前攻擊的時間
    lastHitTime = Time.time;
   }
  }
 
  // 確認敵人是否在範圍技的攻擊範圍內
  private bool IsTargetInRange(GameObject target){
   float distanceToTarget = (target.transform.position - transform.position).magnitude;
   return distanceToTarget <= weaponInUse.GetMaxAttackRange();
  }
 
  public void AdjustHealth(float changePoints){
   // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值
   currentHealthPoint = Mathf.Clamp (currentHealthPoint - changePoints, 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 ();
   // 播放死亡動畫
   animator.SetTrigger(DEATH_TRIGGER);
   // 等待一段時間(依音效長度而定)
   yield return new WaitForSecondsRealtime(audioSource.clip.length);
   // 重載關卡
   SceneManager.LoadScene(0);
  }
 }
}
</energy></cameraraycaster></dominanthand></animator></audiosource>

調整一下Player的技能順序,如下圖調整後,按下滑鼠右鍵發動Power Attack L1,按下數字鍵1發動AOE L1,按下數字鍵2發動Self Heal L1。

來執行遊戲看看吧!按下數字鍵1以後,就能夠發動範圍技攻擊敵人了。

留言