This is a follow up to what I posted earlier here https://community.gamedev.tv/t/nasty-bug-with-disabling-playercontroller/232421/7.
In our game we now have multiple things that can enable or disable the PlayerController often around the same time including:
- PauseWindowUI
- CinematicControlRemover
I noticed that these can interfere with eachother. For example when one script removes removes control but then another script enables control before the first script was ready to re-enable control.
In my case, I also need to keep the player controller disabled during dialogue (but keep the game unpaused) because I have Cinematics running during dialogue.
I must have tried a half dozen solutions on my own each one with its own unique quirks. Here is the latest I came up with inspired by PauseWhenActive - Can I get some feedback on it?
PlayerController.cs edits
public class PlayerController : MonoBehaviour
{
private static readonly Dictionary<MonoBehaviour, bool> ActiveInstances = new();
// If you're wondering why this exits it's keeping UI in sync.
// In my case I have a virtual joystick and buttons on my UI
// these need to be turned off when the player controller is disabled.
public event Action ControllerStateChanged;
private void OnEnable()
{
ControllerStateChanged?.Invoke();
}
private void OnDisable()
{
ControllerStateChanged?.Invoke();
}
/// This is necessary because you could have stale
/// references after a scene load
private void OnDestroy()
{
ActiveInstances.Clear();
}
void Update()
{
if (!IsInputAllowed()) return;
}
public bool IsInputAllowed()
{
return ActiveInstances.Count == 0;
}
public void RequestInputSuspend(MonoBehaviour requester)
{
ActiveInstances[requester] = true;
ControllerStateChanged?.Invoke();
}
public void RequestInputEnable(MonoBehaviour requester)
{
if (ActiveInstances.ContainsKey(requester))
{
ActiveInstances.Remove(requester);
ControllerStateChanged?.Invoke();
}
}
}
SuspendInputWhenActive.cs
/// <summary>
/// Class to disable player controller when a certain component is active. For
/// example you may put on UI if you want the control to be disabled when that UI
/// is active.
/// </summary>
public class SuspendInputWhenActive : MonoBehaviour
{
private PlayerController playerController;
private void Awake()
{
playerController = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
}
private void OnEnable()
{
// need to check for null since PlayerController can be destroyed while this is
// still alive
if (playerController)
{
playerController.RequestInputSuspend(this);
}
}
private void OnDisable()
{
// need to check for null since PlayerController can be destroyed while this is
// still alive
if (playerController)
{
playerController.RequestInputEnable(this);
}
}
}
CinematicControlRemover.cs edits
private void DisableControl(PlayableDirector aDirector)
{
player.GetComponent<ActionScheduler>().CancelCurrentAction();
player.GetComponent<Mover>().StartMoveAction(player.transform.position, 1f);
player.GetComponent<PlayerController>().RequestInputSuspend(this);
}
private void EnableControl(PlayableDirector aDirector)
{
// Need to check for null because .stopped can trigger as a result of scene
// load. Player may be destroyed as a result.
if (player)
{
player.GetComponent<PlayerController>().RequestInputEnable(this);
}
}
}