159 lines
4.6 KiB
C#
159 lines
4.6 KiB
C#
using System.Collections.Generic;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
public abstract class NetworkHeldInteractable : NetworkInteractable
|
|
{
|
|
protected const ulong NoHolder = ulong.MaxValue;
|
|
|
|
[Header("Physics")]
|
|
[SerializeField] private Rigidbody physicsBody;
|
|
[SerializeField] private Collider[] objectColliders;
|
|
[SerializeField, Min(0.05f)] private float collisionRefreshInterval = 0.25f;
|
|
|
|
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; }
|
|
|
|
private bool IsHeld => holderClientId.Value != NoHolder;
|
|
private bool IsHeldByLocalPlayer =>
|
|
NetworkManager != null && holderClientId.Value == NetworkManager.LocalClientId;
|
|
|
|
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()
|
|
{
|
|
holderClientId.OnValueChanged += HandleHolderChanged;
|
|
ApplyHoldingState(IsHeld);
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
holderClientId.OnValueChanged -= HandleHolderChanged;
|
|
ApplyHoldingState(false);
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (!IsSpawned || !IsHeld)
|
|
return;
|
|
|
|
if (!TryGetPlayerObject(holderClientId.Value, out NetworkObject holder))
|
|
{
|
|
if (IsServer)
|
|
StopHolding();
|
|
|
|
return;
|
|
}
|
|
|
|
transform.SetPositionAndRotation(
|
|
holder.transform.TransformPoint(HoldingOffset),
|
|
holder.transform.rotation);
|
|
|
|
if (Time.time >= nextCollisionRefreshTime)
|
|
{
|
|
nextCollisionRefreshTime = Time.time + collisionRefreshInterval;
|
|
IgnoreCollisionsWithPlayers();
|
|
}
|
|
}
|
|
|
|
protected override bool CanInteractOnServer(ulong senderClientId) =>
|
|
!IsHeld || holderClientId.Value == senderClientId;
|
|
|
|
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
|
{
|
|
if (holderClientId.Value == senderClientId)
|
|
StopHolding();
|
|
else if (!IsHeld)
|
|
StartHolding(senderClientId);
|
|
}
|
|
|
|
private void StartHolding(ulong clientId)
|
|
{
|
|
holderClientId.Value = clientId;
|
|
ApplyHoldingState(true);
|
|
}
|
|
|
|
private void StopHolding()
|
|
{
|
|
holderClientId.Value = NoHolder;
|
|
ApplyHoldingState(false);
|
|
}
|
|
|
|
private void HandleHolderChanged(ulong _, ulong currentHolder) =>
|
|
ApplyHoldingState(currentHolder != NoHolder);
|
|
|
|
private void ApplyHoldingState(bool isHeld)
|
|
{
|
|
if (physicsBody != null)
|
|
{
|
|
physicsBody.isKinematic = isHeld;
|
|
if (!isHeld)
|
|
physicsBody.linearVelocity = 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)
|
|
continue;
|
|
|
|
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();
|
|
}
|
|
}
|