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完成程式碼:
調整一下Player的技能順序,如下圖調整後,按下滑鼠右鍵發動Power Attack L1,按下數字鍵1發動AOE L1,按下數字鍵2發動Self Heal L1。
來執行遊戲看看吧!按下數字鍵1以後,就能夠發動範圍技攻擊敵人了。
將之前提供的圖片檔案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完成程式碼:
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 ();
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.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);
}
}
}
調整一下Player的技能順序,如下圖調整後,按下滑鼠右鍵發動Power Attack L1,按下數字鍵1發動AOE L1,按下數字鍵2發動Self Heal L1。
來執行遊戲看看吧!按下數字鍵1以後,就能夠發動範圍技攻擊敵人了。












留言
張貼留言