Files
FORT-BO/Assets/CONTENT/Game/Scripts/Player/PlayerInteractionController.cs
T

160 lines
5.1 KiB
C#

using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
public sealed class PlayerInteractionController : NetworkBehaviour, IPlayerSystem
{
[SerializeField, Min(0.1f)] private float interactionDistance = 3f;
[SerializeField] private LayerMask interactionMask = ~0;
private Camera playerCamera;
private IInteractable focusedInteractable;
private IPlayerInteractionSession activeSession;
private readonly RaycastHit[] interactionHits = new RaycastHit[32];
private bool activeSessionReleaseRequested;
private bool isInitialized;
public void Initialize(PlayerFacade facade)
{
playerCamera = GetComponentInChildren<Camera>(true);
isInitialized = playerCamera != null;
}
private void Update()
{
if (!isInitialized || !IsOwner)
return;
if (PlayerInteractionSessionRegistry.TryGetControlledSession(
OwnerClientId,
out activeSession))
{
focusedInteractable = null;
bool isHoldingControl = Mouse.current != null &&
Mouse.current.leftButton.isPressed;
if (!isHoldingControl && !activeSessionReleaseRequested)
{
activeSessionReleaseRequested = true;
activeSession.RequestRelease();
}
return;
}
activeSession = null;
activeSessionReleaseRequested = false;
focusedInteractable = FindFocusedInteractable();
if (focusedInteractable == null || !focusedInteractable.IsInteractionAvailable)
return;
bool startsHoldSession = focusedInteractable is IPlayerInteractionSession &&
Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame;
bool performsRegularInteraction = focusedInteractable is not IPlayerInteractionSession &&
Keyboard.current != null && Keyboard.current.eKey.wasPressedThisFrame;
if (startsHoldSession || performsRegularInteraction)
{
focusedInteractable.RequestInteraction();
}
}
private IInteractable FindFocusedInteractable()
{
Ray ray = new(playerCamera.transform.position, playerCamera.transform.forward);
int hitCount = Physics.RaycastNonAlloc(
ray,
interactionHits,
interactionDistance,
interactionMask,
QueryTriggerInteraction.Collide);
if (hitCount <= 0)
return null;
IInteractable closestAvailable = null;
float closestAvailableDistance = float.PositiveInfinity;
IInteractable closestUnavailable = null;
float closestUnavailableDistance = float.PositiveInfinity;
float closestBlockingDistance = float.PositiveInfinity;
for (int hitIndex = 0; hitIndex < hitCount; hitIndex++)
{
RaycastHit hit = interactionHits[hitIndex];
IInteractable interactable = FindInteractable(hit.collider);
if (interactable == null)
{
closestBlockingDistance = Mathf.Min(closestBlockingDistance, hit.distance);
continue;
}
if (interactable.IsInteractionAvailable)
{
if (hit.distance < closestAvailableDistance)
{
closestAvailable = interactable;
closestAvailableDistance = hit.distance;
}
}
else if (hit.distance < closestUnavailableDistance)
{
closestUnavailable = interactable;
closestUnavailableDistance = hit.distance;
}
}
if (closestAvailable != null && closestAvailableDistance <= closestBlockingDistance)
return closestAvailable;
return closestUnavailable != null && closestUnavailableDistance <= closestBlockingDistance
? closestUnavailable
: null;
}
private static IInteractable FindInteractable(Collider collider)
{
foreach (MonoBehaviour component in collider.GetComponentsInParent<MonoBehaviour>())
{
if (component is IInteractable interactable)
return interactable;
}
return null;
}
private void OnGUI()
{
if (!IsOwner)
return;
if (activeSession != null)
{
DrawPrompt(activeSession.ReleasePrompt);
return;
}
if (focusedInteractable == null)
return;
string inputPrompt = focusedInteractable is IPlayerInteractionSession
? "[Hold LMB] "
: "[E] ";
string prompt = focusedInteractable.IsInteractionAvailable
? inputPrompt + focusedInteractable.InteractionPrompt
: "In use";
DrawPrompt(prompt);
}
private static void DrawPrompt(string prompt)
{
GUIStyle style = new(GUI.skin.label)
{
alignment = TextAnchor.MiddleCenter,
fontSize = 16,
normal = { textColor = Color.white }
};
Rect area = new(0f, Screen.height * 0.62f, Screen.width, 28f);
GUI.Label(area, prompt, style);
}
}