1-9 Bug Fixes - Using Delegate in C#
今天要來修正目前專案已有的一些Bug。
1. 從滑鼠模式切換到Gamepad後,於Gamepad模式中移動一段距離再切換回滑鼠模式,人物會自動移動到上次滑鼠模式點擊的位置。
2. 滑鼠的Cursor圖標在快速切換的過程中,若即時將滑鼠停下來,會發現鼠標仍舊朝一個方向自動移動。
1. 第一個問題很好修正,首先PlayerMovement.cs中看ProcessMouseMovement方法,可見滑鼠點擊後,會將點擊位置紀錄在變數currentClickTarget。
但是,切換到Gamepad模式後,直接用鍵盤移動人物位置,故變數currentClickTarget維持在舊的位置資料。此時,若切換回滑鼠模式,人物會自動移動到上一個位置。
修正方式如下,我們只要在切換模式的時候,將人物的目前位置記錄到currentClickTarget即可。
2. 第二個問題,我們要使用C#的委派(Delegate)來解決。
首先,Bug的發生原因,我們可以關閉Cursor Affordance,然後再來快速移動滑鼠,發現鼠標不會自己移動了。
再來重新把Cursor Affordance打開,並將下列其中一行程式碼註解。我註解的是設定Enemy的圖標,結果發現在Walkable圖標的時候還是會有Bug發生。
以下完整程式碼上菜!
CameraRaycaster.cs
CursorAffordance.cs
1. 從滑鼠模式切換到Gamepad後,於Gamepad模式中移動一段距離再切換回滑鼠模式,人物會自動移動到上次滑鼠模式點擊的位置。
2. 滑鼠的Cursor圖標在快速切換的過程中,若即時將滑鼠停下來,會發現鼠標仍舊朝一個方向自動移動。
1. 第一個問題很好修正,首先PlayerMovement.cs中看ProcessMouseMovement方法,可見滑鼠點擊後,會將點擊位置紀錄在變數currentClickTarget。
if (Input.GetMouseButton(0))
{
//print("Cursor raycast hit" + cameraRaycaster.currentLayerHit);
switch (cameraRaycaster.currentLayerHit) {
case Layer.Walkable:
//取得滑鼠點擊到的物件的位置
currentClickTarget = cameraRaycaster.hit.point;
break;
case Layer.Enemy:
print ("Not moving to enemy.");
break;
default:
print ("Unexpected layer found.");
break;
}
}
但是,切換到Gamepad模式後,直接用鍵盤移動人物位置,故變數currentClickTarget維持在舊的位置資料。此時,若切換回滑鼠模式,人物會自動移動到上一個位置。
修正方式如下,我們只要在切換模式的時候,將人物的目前位置記錄到currentClickTarget即可。
private void FixedUpdate()
{
if(Input.GetKeyDown(KeyCode.G)){
//切換GamePad Mode
isInDircetMode = !isInDircetMode;
//解決Gamepad模式切換時,人物角色會自動回到上一個滑鼠點擊位置的Bug
currentClickTarget = transform.position;
}
if (isInDircetMode) {
ProcessDirectMovement ();
} else {
ProcessMouseMovement ();
}
}
2. 第二個問題,我們要使用C#的委派(Delegate)來解決。
首先,Bug的發生原因,我們可以關閉Cursor Affordance,然後再來快速移動滑鼠,發現鼠標不會自己移動了。
由此可以假設這個Bug是因為在Update一直呼叫Cursor.SetCursor導致的,所以我們要讓設定Cursor的執行頻率不要如此頻繁,最好是在Layer切換的時候再更換圖標就好。
循此解決方式,我們就要使用Observer Pattern的概念來實作,亦即當CameraRaycast執行時發現滑鼠指向的Layer變了,這時必須通知負責的Observer去執行Cursor.SetCursor。
如不懂Observer Pattern和C#的委派(Delegate),可循下列教學看看:
[Design Pattern] 觀察者模式 (Observer Pattern) 我也能夠辦報社
重新整理Observer Pattern
[C#.NET] 如何 使用 委派 Delegate / 事件 event
C#的委派與事件的使用
簡言之,Observer Pattern中會有一個觀察者,並且需要向觀察者註冊執行事件。當觀察者於程式運作中發現必須執行時,就會執行所有向他註冊的事件。委派便是一個能方便實作Observer Pattern的好用語法。
首先,我們先在CameraRaycaster宣告委派事件:
接著,再到CursorAffordance修改LateUpdate方法為OnLayerChange,並於Start註冊該方法為委派的事件。
最後,回到CameraRaycaster原本判斷Layer的地方,撰寫判斷式,何時需要通知委派執行事件。
以下完整程式碼上菜!
CameraRaycaster.cs
using UnityEngine;
public class CameraRaycaster : MonoBehaviour
{
//設定偵測Layer的順序,亦即先偵測Enemy,若沒有Enemy才偵測Walkable
public Layer[] layerPriorities = {
Layer.Enemy,
Layer.Walkable
};
//使用SerializeField屬性,該變數會出現於Inspector中,且同時為private
[SerializeField] float distanceToBackground = 100f;
Camera viewCamera;
RaycastHit raycastHit;
public RaycastHit hit
{
get { return raycastHit; }
}
Layer layerHit;
public Layer currentLayerHit
{
get { return layerHit; }
}
public delegate void OnLayerChange(Layer newLayer); // 宣告一個新的委派型別
public event OnLayerChange layerChangeObservers; //初始化委派的實體
void Start()
{
viewCamera = Camera.main;
}
void Update()
{
// 從layerPriorities中依順序偵測
foreach (Layer layer in layerPriorities)
{
var hit = RaycastForLayer(layer);
if (hit.HasValue)
{
raycastHit = hit.Value;
// 確認滑鼠點中的layer是否跟前一個layer不同,不同的話便呼叫委派通知更換Cursor圖標
if (layerHit != layer) {
layerHit = layer;
layerChangeObservers (layerHit); // 呼叫委派事件
}
return;
}
}
// 都沒偵測到,回傳RaycastEndStop
raycastHit.distance = distanceToBackground;
if (layerHit != Layer.RaycastEndStop) {
layerHit = Layer.RaycastEndStop;
layerChangeObservers (layerHit); // 呼叫委派事件
}
}
RaycastHit? RaycastForLayer(Layer layer)
{
int layerMask = 1 << (int)layer; // See Unity docs for mask formation
Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit; // used as an out parameter
bool hasHit = Physics.Raycast(ray, out hit, distanceToBackground, layerMask);
if (hasHit)
{
return hit;
}
return null;
}
}
CursorAffordance.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent (typeof(CameraRaycaster))]
public class CursorAffordance : MonoBehaviour {
[SerializeField] Texture2D walkCursor = null;
[SerializeField] Texture2D targetCursor = null;
[SerializeField] Texture2D unknownCursor = null;
[SerializeField] Vector2 cursorHotspot = new Vector2 (0, 0);
CameraRaycaster cameraRaycaster;
void Start () {
//取得Camera Arm中的Camera Raycaster,因為CursorAffordance.cs也是放在Camera Arm之下,故可以直接用GetComponent取得。
cameraRaycaster = GetComponent ();
//向委派註冊執行的事件
cameraRaycaster.layerChangeObservers += OnLayerChange ;
}
void OnLayerChange (Layer newLayer) {
//於Console中顯示滑鼠點擊到的Layer
print (cameraRaycaster.currentLayerHit);
switch (newLayer) {
case Layer.Enemy:
Cursor.SetCursor(targetCursor, cursorHotspot, CursorMode.Auto);
break;
case Layer.Walkable:
Cursor.SetCursor (walkCursor, cursorHotspot, CursorMode.Auto);
break;
case Layer.RaycastEndStop:
Cursor.SetCursor(unknownCursor, cursorHotspot, CursorMode.Auto);
break;
default:
Debug.LogError ("Don't know what cursor to show.");
break;
}
}
}





留言
張貼留言