229 lines
7.0 KiB
C#
229 lines
7.0 KiB
C#
using System.Collections.Generic;
|
|
using Unity.Netcode;
|
|
using Unity.Netcode.Components;
|
|
using UnityEngine;
|
|
|
|
[RequireComponent(typeof(Rigidbody), typeof(NetworkRigidbody))]
|
|
public abstract class NetworkHeldInteractable : NetworkInteractable
|
|
{
|
|
protected const ulong NoHolder = ulong.MaxValue;
|
|
private static readonly HashSet<NetworkHeldInteractable> SpawnedHeldObjects = new();
|
|
|
|
[Header("Physics")]
|
|
[SerializeField] private Rigidbody physicsBody;
|
|
[SerializeField] private Collider[] objectColliders;
|
|
[SerializeField, Min(0.05f)] private float collisionRefreshInterval = 0.25f;
|
|
|
|
[Header("Throw")]
|
|
[SerializeField, Min(0f)] private float throwForce = 6f;
|
|
[SerializeField, Min(0f)] private float throwUpwardForce = 0.5f;
|
|
|
|
private readonly NetworkVariable<ulong> holderClientId = new(
|
|
NoHolder,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server);
|
|
|
|
private readonly HashSet<Collider> ignoredPlayerColliders = new();
|
|
private float nextCollisionRefreshTime;
|
|
|
|
protected abstract Vector3 HoldingOffset { get; }
|
|
protected abstract string DropPrompt { get; }
|
|
|
|
public bool IsHeld => holderClientId.Value != NoHolder;
|
|
public bool IsHeldByLocalPlayer =>
|
|
NetworkManager != null && holderClientId.Value == NetworkManager.LocalClientId;
|
|
public string HeldObjectName => InteractionLabel;
|
|
|
|
public override string InteractionPrompt => !IsHeld
|
|
? InteractionLabel
|
|
: IsHeldByLocalPlayer
|
|
? DropPrompt
|
|
: "In use";
|
|
|
|
public override bool IsInteractionAvailable => base.IsInteractionAvailable &&
|
|
(!IsHeld || IsHeldByLocalPlayer);
|
|
|
|
private void Awake()
|
|
{
|
|
if (physicsBody == null)
|
|
physicsBody = GetComponent<Rigidbody>();
|
|
|
|
if (objectColliders == null || objectColliders.Length == 0)
|
|
objectColliders = GetComponentsInChildren<Collider>(true);
|
|
}
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
SpawnedHeldObjects.Add(this);
|
|
holderClientId.OnValueChanged += HandleHolderChanged;
|
|
ApplyHoldingState(IsHeld);
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
holderClientId.OnValueChanged -= HandleHolderChanged;
|
|
SpawnedHeldObjects.Remove(this);
|
|
ApplyHoldingState(false);
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (!IsSpawned || !IsHeld)
|
|
return;
|
|
|
|
if (!TryGetPlayerObject(holderClientId.Value, out NetworkObject holder))
|
|
{
|
|
if (IsServer)
|
|
StopHolding();
|
|
|
|
return;
|
|
}
|
|
|
|
Transform anchor = ResolveHoldingAnchor(holder);
|
|
transform.SetPositionAndRotation(
|
|
anchor.TransformPoint(HoldingOffset),
|
|
anchor.rotation);
|
|
|
|
if (Time.time >= nextCollisionRefreshTime)
|
|
{
|
|
nextCollisionRefreshTime = Time.time + collisionRefreshInterval;
|
|
IgnoreCollisionsWithPlayers();
|
|
}
|
|
}
|
|
|
|
public void RequestThrow()
|
|
{
|
|
if (IsSpawned && IsHeldByLocalPlayer)
|
|
RequestThrowServerRpc();
|
|
}
|
|
|
|
public static bool TryGetHeldObject(
|
|
ulong clientId,
|
|
out NetworkHeldInteractable heldObject)
|
|
{
|
|
foreach (NetworkHeldInteractable candidate in SpawnedHeldObjects)
|
|
{
|
|
if (candidate != null && candidate.holderClientId.Value == clientId)
|
|
{
|
|
heldObject = candidate;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
heldObject = null;
|
|
return false;
|
|
}
|
|
|
|
protected override bool CanInteractOnServer(ulong senderClientId)
|
|
{
|
|
if (holderClientId.Value == senderClientId)
|
|
return true;
|
|
|
|
return !IsHeld &&
|
|
!TryGetHeldObject(senderClientId, out _) &&
|
|
!NetworkHandItemInteractable.TryGetHeldItem(senderClientId, out _);
|
|
}
|
|
|
|
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
|
{
|
|
if (holderClientId.Value == senderClientId)
|
|
StopHolding();
|
|
else if (!IsHeld)
|
|
StartHolding(senderClientId);
|
|
}
|
|
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void RequestThrowServerRpc(RpcParams rpcParams = default)
|
|
{
|
|
ulong senderClientId = rpcParams.Receive.SenderClientId;
|
|
if (holderClientId.Value != senderClientId ||
|
|
!TryGetPlayerObject(senderClientId, out NetworkObject playerObject))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FirstPersonCameraController cameraController =
|
|
playerObject.GetComponent<FirstPersonCameraController>();
|
|
Vector3 throwDirection = cameraController != null
|
|
? cameraController.AimForward
|
|
: playerObject.transform.forward;
|
|
Vector3 throwVelocity = throwDirection * throwForce +
|
|
Vector3.up * throwUpwardForce;
|
|
|
|
StopHolding();
|
|
|
|
if (physicsBody != null)
|
|
physicsBody.linearVelocity = throwVelocity;
|
|
}
|
|
|
|
private void StartHolding(ulong clientId)
|
|
{
|
|
holderClientId.Value = clientId;
|
|
ApplyHoldingState(true);
|
|
}
|
|
|
|
private void StopHolding()
|
|
{
|
|
holderClientId.Value = NoHolder;
|
|
ApplyHoldingState(false);
|
|
}
|
|
|
|
private Transform ResolveHoldingAnchor(NetworkObject holder)
|
|
{
|
|
FirstPersonCameraController cameraController =
|
|
holder.GetComponent<FirstPersonCameraController>();
|
|
return cameraController != null ? cameraController.AimTransform : holder.transform;
|
|
}
|
|
|
|
private void HandleHolderChanged(ulong _, ulong currentHolder) =>
|
|
ApplyHoldingState(currentHolder != NoHolder);
|
|
|
|
private void ApplyHoldingState(bool isHeld)
|
|
{
|
|
if (physicsBody != null)
|
|
{
|
|
physicsBody.isKinematic = isHeld || !IsServer;
|
|
physicsBody.linearVelocity = Vector3.zero;
|
|
physicsBody.angularVelocity = Vector3.zero;
|
|
}
|
|
|
|
if (isHeld)
|
|
IgnoreCollisionsWithPlayers();
|
|
else
|
|
RestorePlayerCollisions();
|
|
}
|
|
|
|
private void IgnoreCollisionsWithPlayers()
|
|
{
|
|
foreach (CharacterController playerCollider in FindObjectsByType<CharacterController>(
|
|
FindObjectsInactive.Exclude,
|
|
FindObjectsSortMode.None))
|
|
{
|
|
foreach (Collider objectCollider in objectColliders)
|
|
{
|
|
if (objectCollider != null && playerCollider != null)
|
|
Physics.IgnoreCollision(objectCollider, playerCollider, true);
|
|
}
|
|
|
|
ignoredPlayerColliders.Add(playerCollider);
|
|
}
|
|
}
|
|
|
|
private void RestorePlayerCollisions()
|
|
{
|
|
foreach (Collider playerCollider in ignoredPlayerColliders)
|
|
{
|
|
if (playerCollider == null)
|
|
continue;
|
|
|
|
foreach (Collider objectCollider in objectColliders)
|
|
{
|
|
if (objectCollider != null)
|
|
Physics.IgnoreCollision(objectCollider, playerCollider, false);
|
|
}
|
|
}
|
|
|
|
ignoredPlayerColliders.Clear();
|
|
}
|
|
}
|