キー入力などでアイテムのメニューとカーソルを表示してみます。その間はFPSキャラクターやカメラが移動回転しないようにします。
まず、プレイヤーに継承させるインターフェースと、メニュー表示のクラスを作りました。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public interface IPlayer
{
void LockPosition(bool locked);
void LockRotation(bool locked);
void LockCursor(bool locked);
}
public class DisplayMenu : MonoBehaviour
{
[SerializeField] GameObject player;
[SerializeField] Canvas[] canvases;
IPlayer playerScript;
private void Awake()
{
playerScript = player.GetComponent<IPlayer>();
if (playerScript == null) throw new Exception();
CloseAllCanvas();
}
// メニューを開く
protected void OpenMenu(int canvasIndex)
{
if (canvasIndex >= canvases.Length || canvasIndex < 0) return;
// Canvasを表示
canvases[canvasIndex].gameObject.SetActive(true);
playerScript.LockPosition(true); // プレイヤーの位置を固定
playerScript.LockRotation(true); // 回転を固定
playerScript.LockCursor(false); // カーソルロックを解除
}
// メニューを閉じる
protected void CloseMenu(int canvasIndex)
{
if (canvasIndex >= canvases.Length || canvasIndex < 0) return;
// Canvasを非表示
canvases[canvasIndex].gameObject.SetActive(false);
playerScript.LockPosition(false); // プレイヤーの移動を自由にする
playerScript.LockRotation(false); // 回転を自由にする
playerScript.LockCursor(true); // カーソルをロック
}
// CanvasのActiveselfを確認
protected bool GetMenuActiveSelf(int canvasIndex)
{
if (canvasIndex >= canvases.Length || canvasIndex < 0) return false;
return canvases[canvasIndex].gameObject.activeSelf;
}
// すべてのCanvasを非アクティブにする
protected void CloseAllCanvas()
{
foreach (Canvas c in canvases)
{
c.gameObject.SetActive(false);
}
}
}
プレイヤーの位置や回転を制御するときにプレイヤーのクラスにそのメソッドを作るので、メソッド名を統一するためにインターフェースを使います。
Awakeメソッドでインターフェース名を使ってスクリプトを取得します。それがnullのときは例外を出すようにしてみました。
FPSコントローラーを使う場合、クラス名の横のコロンの後にインターフェース名を書きます。他のクラスやインターフェースがすでに継承されている場合は、コンマで区切ります。
もし名前が見つからないと言われたら、Characters > FirstPersonCharacter > Scriptsにあるスクリプトを他のスクリプトと同じ場所に移します。
継承すると、インターフェースで宣言したメソッドの実装がないといわれるので、新しいメソッドを作ります。
public void LockPosition(bool locked)
{
lockPosition = locked;
}
public void LockRotation(bool locked)
{
lockRotation = locked;
}
public void LockCursor(bool locked)
{
m_MouseLook.SetCursorIsLocked(locked);
}
FirstPersonControllerクラスで上の2つのグローバル変数を宣言します。
bool lockPosition;
bool lockRotation;
移動回転はフィールドの値を切り替えて、UpdateやFixedUpdateメソッドの中で、移動や回転をするためのメソッドが呼ばれる時に、これらの値がfalseであることを確認するだけです。
private void Update()
{
//...
if(!lockRotation) RotateView();
//...
}
private void FixedUpdate()
{
if (!lockPosition)
{
float speed;
//...
}
m_MouseLook.UpdateCursorLock();
}
カーソルロックはMouseLookクラスで行います。MouseLookクラスのm_cursorIsLockedという元々あるフィールドの値を変えるメソッドを定義し、InternalLockUpdateメソッドのキー入力を受ける記述をコメントアウトしました。
// MouseLookクラス
public void SetCursorIsLocked(bool locked)
{
m_cursorIsLocked = locked;
}
private void InternalLockUpdate()
{
/*
if(Input.GetKeyUp(KeyCode.Escape))
{
// ...
*/
if (m_cursorIsLocked)
{
//...
}
上のメニュー表示クラスでは、メニューを開閉したり、開いているかどうか確認する、すべてのメニューを閉じるなどの単純な処理を定義して、これを継承したクラスにそれらを組み合わせた処理を書くようにしました。
空のゲームオブジェクトに新しいスクリプトを付けて、このクラスを継承します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
class DisplayMenuOperation : DisplayMenu
{
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.G))
{
if (GetMenuActiveSelf(0))
{
CloseMenu(0);
}
else
{
CloseAllCanvas();
OpenMenu(0);
}
}
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
if (GetMenuActiveSelf(1))
{
CloseMenu(1);
}
else
{
CloseAllCanvas();
OpenMenu(1);
}
}
}
}
このクラスでは、キー入力を受けると操作したいメニューが開いているかどうかによってメニューを開閉します。開くときは一旦すべてのCanvasを非アクティブにしています。そうしないと複数のメニューを開いた後にどれか一つのメニューを閉じると、プレイヤーが動ける状態になります。
はじめのメニュー表示のスクリプトはシーンに置かないようにして、それを継承したものだけにし、プレイヤーとCanvasをアタッチします。はじめのクラスにはabstractキーワードを付けても良いかもしれません。
これでメニューの開閉ができました。
エディタでは、メニューを閉じるときにエスケープキーを使うと、マウスがクリックされるまでカーソルロックが解除されたままになりますが、ビルドすると問題ありません。
「GetMenuActiveSelf」でUIのアクティブ状態を確認する際
「int canvasIndex」には値が無いように思えるんですが
「int canvasIndex」にどうやって値を代入しているのか教えてください
メソッドを呼ぶ時に引数に値を渡しています。