150 lines
4.6 KiB
C#
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);
|
|
}
|
|
}
|