Related Posts Plugin for WordPress, Blogger...

3-7 Adding An Energy Mechanic

本章要來製作Energy Bar,當玩家按下滑鼠右鍵的時候會施放技能,導致Energy被消耗,Energy Bar也必須要有反應。由於我們之前已經製作過Health Bar,原理上相差不多,本次教學也大多是利用之前的素材,如有部分原理尚不了解的話,請先看看下列文章:

2-2 Player Health Bar UI

2-10 Fully Event Based Raycasting 

首先新增一個Energy.cs的Script檔。

Energy.cs的程式碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using RPG.CameraUI;

namespace RPG.Character{
 public class Energy : MonoBehaviour {

  [SerializeField] RawImage energyBarRawImage;
  [SerializeField] float maxEnergyPoints = 100f;
  [SerializeField] float pointPerHits = 10f;

  float currentEnergyPoints;
  CameraRaycaster cameraRaycaster;

  void Start () {
   cameraRaycaster = Camera.main.GetComponent();
   currentEnergyPoints = maxEnergyPoints;

   cameraRaycaster.notifyRightMouseClickObservers += ProcessRightClcik;
  }
  
  private void ProcessRightClcik(RaycastHit raycastHit, int layerHit){
   float newEnergyPoint = currentEnergyPoints - pointPerHits;
   currentEnergyPoints = Mathf.Clamp (newEnergyPoint, 0, maxEnergyPoints);

   float xValue = -(EnergyAsPersent() / 2f) - 0.5f;
   energyBarRawImage.uvRect = new Rect(xValue, 0f, 0.5f, 1f);
  }

  float EnergyAsPersent(){
   return currentEnergyPoints / maxEnergyPoints;
  }
 }
}

大家應該會注意到Energy中註冊了一個委派事件notifyRightMouseClickObservers,該委派事件撰寫在CameraRaycaster中進行呼叫,以下是新的CameraRaycaseter.cs的程式碼,以後會再進行更聰明的重構,現階段就先這樣寫吧:

using UnityEngine;
using UnityEngine.EventSystems;
using System.Linq;
using System.Collections.Generic;

namespace RPG.CameraUI{
 public class CameraRaycaster : MonoBehaviour
 {
  // INSPECTOR PROPERTIES RENDERED BY CUSTOM EDITOR SCRIPT
  [SerializeField] int[] layerPriorities = null;

     float maxRaycastDepth = 100f; // Hard coded value
  int topPriorityLayerLastFrame = -1; // So get ? from start with Default layer terrain

  // Setup delegates for broadcasting layer changes to other classes
  public delegate void OnCursorLayerChange(int newLayer); // 宣告一個新的委派型別
  public event OnCursorLayerChange notifyLayerChangeObservers; //初始化委派的實體

  public delegate void OnClickPriorityLayer(RaycastHit raycastHit, int layerHit); // 宣告一個新的委派型別
  public event OnClickPriorityLayer notifyMouseClickObservers; //初始化委派的實體

  public delegate void OnRightClick(RaycastHit raycastHit, int layerHit); // 宣告一個新的委派型別
  public event OnRightClick notifyRightMouseClickObservers; //初始化委派的實體

     void Update()
  {
   // 確認鼠標是否放在GameObject之上(這個上面在遊戲中亦代表UI層)
   if (EventSystem.current.IsPointerOverGameObject ())
   {
    NotifyObserersIfLayerChanged (5); //5 是UI Layer
    return; // 既然鼠標在UI層,直接跳過以下的判斷事件
   }

   // Raycast to max depth, every frame as things can move under mouse
   Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
   RaycastHit[] raycastHits = Physics.RaycastAll (ray, maxRaycastDepth);

         RaycastHit? priorityHit = FindTopPriorityHit(raycastHits);
         if (!priorityHit.HasValue) // 若碰觸到的Layer不在我們定義的Priority清單中,則預設為Default Layer
   {
    NotifyObserersIfLayerChanged (0); // broadcast default layer
    return;
   }

   // 當鼠標碰觸到的Layer有變換時,通知委派執行事件
   var layerHit = priorityHit.Value.collider.gameObject.layer; //取得鼠標碰到的Layer
   NotifyObserersIfLayerChanged(layerHit);
   
   // 當滑鼠點擊左鍵時,通知委派執行事件
   if (Input.GetMouseButton (0))
   {
    notifyMouseClickObservers (priorityHit.Value, layerHit);
   }

   // 當滑鼠點下去右鍵才會觸發MouseButtonDown,即使一直壓著右鍵也不會觸發多次
   if (Input.GetMouseButtonDown (1))
   {
    notifyRightMouseClickObservers (priorityHit.Value, layerHit);
   }

  }

  void NotifyObserersIfLayerChanged(int newLayer)
  {
   // 確認鼠標碰到的layer是否跟前一個layer不同,不同的話便呼叫委派通知更換Cursor圖標
   if (newLayer != topPriorityLayerLastFrame)
   {
    topPriorityLayerLastFrame = newLayer;
    notifyLayerChangeObservers (newLayer);
   }
  }

  RaycastHit? FindTopPriorityHit (RaycastHit[] raycastHits)
  {
   // Form list of layer numbers hit
   List layersOfHitColliders = new List ();
   foreach (RaycastHit hit in raycastHits)
   {
    layersOfHitColliders.Add (hit.collider.gameObject.layer);
   }

   // 從layerPriorities中依順序偵測
   foreach (int layer in layerPriorities)
   {
    foreach (RaycastHit hit in raycastHits)
    {
     if (hit.collider.gameObject.layer == layer)
     {
      return hit; // stop looking
     }
    }
   }
   return null; // because cannot use GameObject? nullable
  }
 }
}

接著回到場景上,選擇Health Bar跟Health Bar Mask進行複製。


並更名為Energy Bar跟Energy Bar Mask。

並調整Energy Bar的位置,放在Health Bar的上面,如下圖。

然後再來複製原本health bar的圖檔,也更名為energy bar吧。

簡單修改一下顏色,我用Photoshop的顏色取代工具,簡單改成藍色跟灰色。

修改好圖檔以後,再回到Energy Bar的Inspector視窗,將新的圖檔拉入Raw Image/Texture中。

並記得移除在Energy Bar中的Player Health Bar的Script物件,這是因為我們剛剛用複製的,所以這個Script物件跟著複製過來,且也會影響到Energy Bar的顯示。

最後,回到Player的Inspector視窗,將設定好的Energy Bar拉進Energy Bar Raw Image中。

執行遊戲看看吧!目前Energy Bar還未有消耗。

遭遇敵人!趕快按滑鼠右鍵施放技能,看到Energy Bar一直在減少了!施放技能的部分還沒做,以後再做吧。

留言