以下が私のObserverパターンについての認識です。
前回までのコードでは、CursorAffordanceクラスが秒間数十回currentLayerHitの値を確認し、カーソルの画像を再セットし続けていました。しかし、currentLayerHitの値はユーザの操作によって変化するもので、実際には0.5~10秒に1度程度の変更に抑えられるはずなので、値を確認し続けるのはとても無駄が多いです。
なのでCursorAffordanceクラスが定期的にcurrentLayerHitを確認するのではなく、currentLayerHitが変化するタイミングでCameraRaycasterクラスがCursorAffordanceに変更を通知し、そのタイミングでのみ処理を行うほうが効率的です。
また、CameraRaycasterクラスは適切なタイミングですべての通知先に通知することだけを意識し、そのタイミングで行いたい処理は通知先に任せきることで、もし仮にcurrentLayerHitの値を見て行う処理が新たに増えたとしても、既存のCameraRaycasterクラスやCursorAffordanceクラスに対して修正を行う必要はありません。
実際に現時点での知識でObserverパターンへの変更を試してみました。
問題なく動作しているように見えます!
CameraRaycaster クラス
using System;
using System.Collections.Generic;
using UnityEngine;
public class CameraRaycaster : MonoBehaviour
{
public Layer[] layerPriorities = {
Layer.Enemy,
Layer.Walkable
};
[SerializeField] float distanceToBackground = 100f;
Camera viewCamera;
RaycastHit raycastHit;
public RaycastHit hit
{
get { return raycastHit; }
}
Layer layerHit;
public Layer currentLayerHit
{
get { return layerHit; }
// currentLayerHit に値を代入する際に呼び出される
set
{
// Layerが同じならばなにもしない
if (value == layerHit) return;
// Layerが変わったならば、値を layerHit に代入し、OnLayerChanged メソッドを呼び出す
layerHit = value;
OnLayerChanged();
}
}
void Start()
{
viewCamera = Camera.main;
}
void Update()
{
// Look for and return priority layer hit
foreach (Layer layer in layerPriorities)
{
var hit = RaycastForLayer(layer);
if (hit.HasValue)
{
raycastHit = hit.Value;
//layerHit = layer;
currentLayerHit = layer; // ←代入先を currentLayerHit に変更
return;
}
}
// Otherwise return background hit
raycastHit.distance = distanceToBackground;
//layerHit = Layer.RaycastEndStop;
currentLayerHit = Layer.RaycastEndStop; // ←代入先を currentLayerHit に変更
}
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;
}
// レイヤー変更時に行いたい処理(メソッド)を、ここに追加する。
// publicにしてあるため、別クラスからも追加できる。
// 追加できるメソッドは Action<Layer> という形のデリゲードであり、
// これは要するに 引数が Layer で、戻り値が void のメソッドを追加できるということ。
// 引数が2個のメソッドならば Action<Layer, RaycastHit> 、戻り値にintを返すメソッドならば Func<Layer, int> 等に変更する必要がある。
public List<Action<Layer>> LayerChangedEvent = new List<Action<Layer>>();
// 実際に currentLayerHit が変化したときに、CameraRaycasterがこのメソッドを呼び出す。
// LayerChangedEvent に登録されているメソッドをすべて実行する。
// 登録メソッド数がいくつあっても気にせずすべて実行するし、登録数が0ならばなにもしない。
private void OnLayerChanged()
{
foreach (var del in LayerChangedEvent)
{
del(currentLayerHit);
}
}
}
CursorAffordanceクラス
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(CameraRaycaster))]
public class CursorAffordance : MonoBehaviour
{
[SerializeField] Texture2D walkCursor;
[SerializeField] Texture2D targetCursor;
[SerializeField] Texture2D unknownCursor;
[SerializeField] Vector2 cursorHotspot = new Vector2(0, 0);
CameraRaycaster cameraRaycaster;
private void Awake()
{
cameraRaycaster = GetComponent<CameraRaycaster>();
}
private void Start()
{
// CameraRaycaster の LayerChangedEvent に CursorChange メソッドを登録
// これにより、Layerが変更されたタイミングで CursorChange が呼び出されるようになる
cameraRaycaster.LayerChangedEvent.Add(CursorChange);
}
// LateUpdate から 抜き出し。
// LateUpdateでは呼び出さず、cameraRaycaster の LayerChangedEvent で呼び出されるのを待つ。
private void CursorChange(Layer layer) {
switch (cameraRaycaster.currentLayerHit)
{
case Layer.Walkable:
Cursor.SetCursor(walkCursor, cursorHotspot, CursorMode.Auto);
break;
case Layer.Enemy:
Cursor.SetCursor(targetCursor, cursorHotspot, CursorMode.Auto);
break;
case Layer.RaycastEndStop:
Cursor.SetCursor(unknownCursor, cursorHotspot, CursorMode.Auto);
break;
default:
Debug.LogError("Don't know wath cursor to show");
break;
}
}
}