Skip to content

Instantly share code, notes, and snippets.

@Monsoonexe
Created April 20, 2023 14:17
Show Gist options
  • Save Monsoonexe/958214c720f4079d6284e49d933707f8 to your computer and use it in GitHub Desktop.
Save Monsoonexe/958214c720f4079d6284e49d933707f8 to your computer and use it in GitHub Desktop.
PlayerController
using UnityEngine;
using UnityEngine.InputSystem;
using System.Collections;
using UnityEngine.XR.Interaction.Toolkit;
using Sirenix.OdinInspector;
using ScriptableObjectArchitecture;
using HouseOfCards.Player;
using RichPackage;
using CommonUsages = UnityEngine.XR.CommonUsages;
//TODO - add this to Player namespace
//TODO - add ETurnType Variable similar to locomotion type
/// <summary>
///
/// </summary>
public class PlayerController : RichMonoBehaviour
{
private static PlayerController instance;
public static PlayerController Instance => instance;
#region Prefab Properties
[Title("Prefab Refs")]
[SerializeField, Required]
private CharacterController characterController;
[Title("Locomotion")]
[SerializeField, Required]
private ContinuousMoveProviderBase movementProvider;
[Title("Turners")]
[SerializeField, Required]
private SnapTurnProviderBase snapTurnProvider;
[SerializeField, Required]
private ContinuousTurnProviderBase continuousTurnProvider;
[Title("Teleporters")]
[SerializeField, Required]
private XRController leftRayTeleporter;
[SerializeField, Required]
private XRController rightRayTeleporter;
[Title("Grab Interactors")]
[SerializeField, Required]
private XRController leftHandController;
[SerializeField, Required]
private XRController rightHandController;
[Title("Ray Interactors")]
[SerializeField, Required]
private XRController leftRayInteractor;
[SerializeField, Required]
private XRController rightRayInteractor;
#endregion
#region Option Properties
/// <summary>
/// How should the Player handle turning.
/// </summary>
[FoldoutGroup("Locomotion Options")]
[Tooltip("How should the Player handle turning.")]
[SerializeField, Required]
private ETurnTypeVariable TurnType;
/// <summary>
/// How should the Player handle moving.
/// </summary>
[FoldoutGroup("Locomotion Options")]
[SerializeField, Required]
[Tooltip("How should the Player handle moving.")]
private ELocomotionTypeVariable locomotionType;
/// <summary>
/// Is this teleporter allowed to be used?
/// </summary>
[FoldoutGroup("Locomotion Options")]
[ShowInInspector]
[PropertyTooltip("Is this teleporter allowed to be used?")]
public bool LeftTeleporterEnabled { get; set; } = true;
/// <summary>
/// Is this teleporter allowed to be used?
/// </summary>
[FoldoutGroup("Locomotion Options")]
[ShowInInspector]
[PropertyTooltip("Is this teleporter allowed to be used?")]
public bool RightTeleporterEnabled { get; set; } = true;
#endregion
[Title("Resources")]
[SerializeField, Required]
private GameEvent toggleDashboardEvent;
#region Runtime State
//runtime data
private LocomotionProvider activeTurnProvider;
private bool leftTeleportRayActiveState; //state for "edge" control
private bool rightTeleportRayActiveState;//state for "edge" control
/// <summary>
/// Property backer. Do not interact with this field, rather
/// interact with <see cref="TurningEnabled"/>.
/// </summary>
private bool _turningEnabled = true; //backing state
//button states (grosssssss)
private bool lastMenuButtonState = false; //TODO button class
#endregion
private void Reset()
{
SetDevDescription("I help control the player!");
characterController = GetComponent<CharacterController>();
movementProvider = GetComponent<ContinuousMoveProviderBase>();
continuousTurnProvider = GetComponent<ContinuousTurnProviderBase>();
snapTurnProvider = GetComponent<SnapTurnProviderBase>();
}
protected override void Awake()
{
base.Awake();
InitSingleton(this, ref instance,
dontDestroyOnLoad: false);
//subscribe to events
locomotionType.AddListener(SetLocomotionTypeInternal);
TurnType.AddListener(SetTurningTypeInternal);
//set default states
SetLocomotionTypeInternal(locomotionType);
continuousTurnProvider.enabled = false;
snapTurnProvider.enabled = false;
SetTurningTypeInternal(TurnType);
}
private IEnumerator Start()
{
yield return null; //wait a frame to force teleporters off to start with
//hide visuals of non-connected controllers
TeleportersEnabled = false; //start with inactive.
//InitStaticLocomotion();
}
private void OnDestroy()
{
//unsubscribe from events
locomotionType.RemoveListener(SetLocomotionTypeInternal);
TurnType.RemoveListener(SetTurningTypeInternal);
}
private void Update()
{
//CheckLocomotionInput(); // Disable in favor of static locomotion system
CheckDashboardInput();
}
private void CheckLocomotionInput()
{
//enable teleport ray behaviour when teleporting is set
//as preferred method of locomotion
if (locomotionType.Value == ELocomotionType.TELEPORT)
{
//handle left stick
var input = leftHandController.ReadPrimaryJoystickInput();
var magnitude = input.magnitude;
var adjustedMagnitude = GetDeadzoneAdjustedValue(magnitude);
//check if teleport is activated
if (adjustedMagnitude > 0)
{
if (!leftTeleportRayActiveState)
{
//update state after edge
leftTeleportRayActiveState = true;
//disable interact ray
leftRayInteractor.gameObject.SetActive(false);
//enable teleport ray
LeftTeleportRayActive = true;
}
}
else if (leftTeleportRayActiveState)
{
//update state after edge
leftTeleportRayActiveState = false;
//enable teleport ray
LeftTeleportRayActive = false;
//disable interact ray
leftRayInteractor.gameObject.SetActive(true);
}
//handle right stick
input = rightHandController.ReadPrimaryJoystickInput();
magnitude = input.magnitude;
adjustedMagnitude = GetDeadzoneAdjustedValue(magnitude);
//check if teleport is activated
if (adjustedMagnitude > 0)
{
if (!rightTeleportRayActiveState)
{
//update state after edge
rightTeleportRayActiveState = true;
//disable interact ray
rightRayInteractor.gameObject.SetActive(false);
//enable teleport ray
RightTeleportRayActive = true;
}
}
else if (rightTeleportRayActiveState)
{
//update state after edge
rightTeleportRayActiveState = false;
//enable teleport ray
RightTeleportRayActive = false;
//disable interact ray
rightRayInteractor.gameObject.SetActive(true);
}
}
}
public void InitStaticLocomotion()
{
// Enable Left Teleport
//leftTeleportRayActiveState = true; //update state after edge
leftRayInteractor.gameObject.SetActive(false); //disable interact ray
LeftTeleportRayActive = true; //enable teleport ray
// Enable Right Interact
//rightTeleportRayActiveState = false; //update state after edge
RightTeleportRayActive = false; //enable teleport ray
rightRayInteractor.gameObject.SetActive(true); //disable interact ray
}
private void CheckDashboardInput()
{
//handle specific button presses
bool hasLeftMenu = leftHandController.inputDevice.TryGetFeatureValue(
CommonUsages.menuButton, out bool menuButtonState);
if (hasLeftMenu && menuButtonState && !lastMenuButtonState)
{
RequestToggleMainMenu(default);
}
lastMenuButtonState = menuButtonState;
}
#region Turning
/// <summary>
/// Is turning allowed.
/// </summary>
[FoldoutGroup("Locomotion Options")]
[ShowInInspector]
[PropertyTooltip("Is turning allowed.")]
public bool TurningEnabled
{
get => _turningEnabled;
set
{
_turningEnabled = value;
if (activeTurnProvider != null)
activeTurnProvider.enabled = _turningEnabled;
}
}
/// <exception cref="System.NotImplementedException"></exception>
private void SetTurningTypeInternal(ETurnType turnType)
{
//disable current method
if (activeTurnProvider != null)
{
activeTurnProvider.enabled = false;
activeTurnProvider = null;
}
//determine new method
switch (turnType)
{
case ETurnType.None:
//do nothing as already disabled
break;
case ETurnType.Snap:
activeTurnProvider = snapTurnProvider;
activeTurnProvider.enabled = TurningEnabled; //setting the type doesn't imply enabling
break;
case ETurnType.Continuous:
activeTurnProvider = continuousTurnProvider;
activeTurnProvider.enabled = TurningEnabled; //setting the type doesn't imply enabling
break;
default:
throw new System.NotImplementedException($"{turnType} is not implemented!");
}
}
public void SetTurningType(ETurnType turnType)
=> TurnType.Value = turnType; //triggers Internal call
public void SetTurningType(ETurnType turnType, bool enabled)
{
TurningEnabled = enabled;
SetTurningType(turnType);
}
#endregion
public bool MoveEnabled
{
get => movementProvider.enabled;
set => movementProvider.enabled = value;
}
private void RequestToggleMainMenu(InputAction.CallbackContext context)
{
Debug.Log("Menu button pressed");
toggleDashboardEvent.Raise();
}
public void SetLocomotionType(ELocomotionType newType)
=> locomotionType.Value = newType; //trigger event
private void SetLocomotionTypeInternal(ELocomotionType locomotionType)
{ //disable all, then set what is active
DisableLocomotion();
switch (locomotionType)
{
case ELocomotionType.NONE:
//nada, already disabled
break;
case ELocomotionType.TELEPORT:
TeleportersEnabled = true;
break;
case ELocomotionType.CONTINUOUS:
MoveEnabled = true;
break;
default:
Debug.LogError("Case not accounted for: " + locomotionType);
break;
}
}
/// <summary>
/// Enable turning, moving, and teleporting.
/// </summary>
public void EnableLocomotion()
=> MoveEnabled = TeleportersEnabled = true;
/// <summary>
/// Disable turning, moving, and teleporting.
/// </summary>
public void DisableLocomotion()
=> MoveEnabled = TeleportersEnabled = false;
#region Teleportation State Controllers
/// <summary>
/// Set turning, moving, and teleporting.
/// </summary>
public bool TeleportersEnabled
{
get => LeftTeleportRayActive;// = rightRayTeleporter.enabled;
set
{
LeftTeleportRayActive = value;
RightTeleportRayActive = value;
}
}
public bool LeftTeleportRayActive
{
get => leftRayTeleporter.gameObject.activeSelf && LeftTeleporterEnabled;
set
{
value = value && LeftTeleporterEnabled;
leftRayTeleporter.gameObject.SetActive(value);
//force state off if on, otherwise keep same
leftTeleportRayActiveState = leftTeleportRayActiveState && value;
}
}
public bool RightTeleportRayActive
{
get => rightRayTeleporter.gameObject.activeSelf && RightTeleporterEnabled;
set
{
value = value && RightTeleporterEnabled;
rightRayTeleporter.gameObject.SetActive(value);
//force state off if on, otherwise keep same
rightTeleportRayActiveState = rightTeleportRayActiveState && value;
}
}
public void EnableTeleportation()
=> TeleportersEnabled = true;
public void DisableTeleportation()
=> TeleportersEnabled = false;
#endregion
public void TeleportTo(Transform place)
=> TeleportTo(place.position);
public void TeleportTo(in Vector3 point)
{
myTransform.position = point;
}
/// <summary>
/// Step given amount.
/// </summary>
/// <param name="motion"></param>
public void Move(in Vector3 motion)
=> characterController.Move(motion); //forward call
/// <summary>
/// Teleport to position.
/// </summary>
/// <param name="worldPosition"></param>
public void MoveTo(in Vector3 worldPosition)
=> myTransform.position = worldPosition;
public static Vector2 GetDeadzoneAdjustedValue(Vector2 value)
{
var magnitude = value.magnitude;
var newMagnitude = GetDeadzoneAdjustedValue(magnitude);
if (Mathf.Approximately(newMagnitude, 0f))
value = Vector2.zero;
else
value *= newMagnitude / magnitude;
return value;
}
public static float GetDeadzoneAdjustedValue(float value)
{
var min = 0.125f;
var max = 0.925f;
var absValue = RichMath.AbsoluteValue(value);
if (absValue < min)
return 0f;
if (absValue > max)
return Mathf.Sign(value);
return Mathf.Sign(value) * ((absValue - min) / (max - min));
}
}
public static class Controller_Extensions
{
public static Vector2 ReadPrimaryJoystickInput(
this XRController device)
{
Vector2 input = Vector2.zero;
if (device.enableInputActions)
device.inputDevice.TryGetFeatureValue(
CommonUsages.primary2DAxis, out input);
return input;
}
public static Vector2 ReadSecondaryJoystickInput(
this XRController device)
{
Vector2 input = Vector2.zero;
if (device.enableInputActions)
device.inputDevice.TryGetFeatureValue(
CommonUsages.secondary2DAxis, out input);
return input;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment