Files
FORT-BO/Assets/CONTENT/Game/Scripts/Player/PlayerInteractionController.cs
T
2026-08-14 16:54:09 +03:00

150 lines
4.6 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 || Keyboard.current == null)
return;
if (PlayerInteractionSessionRegistry.TryGetControlledSession(
OwnerClientId,
out activeSession))
{
focusedInteractable = null;
if (!Keyboard.current.eKey.isPressed && !activeSessionReleaseRequested)
{
activeSessionReleaseRequested = true;
activeSession.RequestRelease();
}
return;
}
activeSession = null;
activeSessionReleaseRequested = false;
focusedInteractable = FindFocusedInteractable();
if (Keyboard.current.eKey.wasPressedThisFrame &&
focusedInteractable != null &&
focusedInteractable.IsInteractionAvailable)
{
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 prompt = focusedInteractable.IsInteractionAvailable
? "[E] " + 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);
}
}