Added leveler
This commit is contained in:
@@ -1,9 +1,282 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using Unity.Netcode.Components;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class NetworkDraggableInteractable : NetworkHeldInteractable
|
||||
[RequireComponent(typeof(Rigidbody), typeof(NetworkRigidbody))]
|
||||
public sealed class NetworkDraggableInteractable : NetworkInteractable
|
||||
{
|
||||
[SerializeField] private Vector3 dragOffset = new(0f, 0.35f, 1.8f);
|
||||
private const ulong NoDragger = ulong.MaxValue;
|
||||
|
||||
protected override Vector3 HoldingOffset => dragOffset;
|
||||
protected override string DropPrompt => "Release";
|
||||
[Header("Weight")]
|
||||
[SerializeField, Min(0.1f)] private float weight = 35f;
|
||||
[SerializeField, Min(0.1f)] private float pullStrengthPerPlayer = 50f;
|
||||
[SerializeField, Range(1, 2)] private int maximumDraggers = 2;
|
||||
|
||||
[Header("Dragging")]
|
||||
[SerializeField] private Vector3 dragOffset = new(0f, 1f, 1.8f);
|
||||
[SerializeField, Min(0.1f)] private float maximumDragSpeed = 3f;
|
||||
[SerializeField, Range(0.05f, 1f)] private float minimumSpeedMultiplier = 0.35f;
|
||||
[SerializeField, Min(0.5f)] private float maximumDraggerDistance = 4.5f;
|
||||
[SerializeField] private bool keepCurrentHeight;
|
||||
|
||||
[Header("Physics")]
|
||||
[SerializeField] private Rigidbody physicsBody;
|
||||
[SerializeField] private Collider[] objectColliders;
|
||||
[SerializeField, Min(0.05f)] private float collisionRefreshInterval = 0.25f;
|
||||
|
||||
private readonly NetworkVariable<ulong> firstDraggerClientId = new(
|
||||
NoDragger,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private readonly NetworkVariable<ulong> secondDraggerClientId = new(
|
||||
NoDragger,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private readonly HashSet<Collider> ignoredPlayerColliders = new();
|
||||
private float nextCollisionRefreshTime;
|
||||
|
||||
public float Weight => weight;
|
||||
public int ActiveDraggerCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = firstDraggerClientId.Value == NoDragger ? 0 : 1;
|
||||
if (secondDraggerClientId.Value != NoDragger)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
public int RequiredDraggerCount => Mathf.Max(1, Mathf.CeilToInt(weight / pullStrengthPerPlayer));
|
||||
public bool HasEnoughPullStrength => ActiveDraggerCount >= RequiredDraggerCount;
|
||||
|
||||
private bool HasAnyDragger => ActiveDraggerCount > 0;
|
||||
private bool IsDraggedByLocalPlayer =>
|
||||
NetworkManager != null && IsDragger(NetworkManager.LocalClientId);
|
||||
|
||||
public override string InteractionPrompt
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDraggedByLocalPlayer)
|
||||
return "Release";
|
||||
|
||||
int activeDraggers = ActiveDraggerCount;
|
||||
if (activeDraggers == 0)
|
||||
return $"{InteractionLabel} ({weight:0.#} kg)";
|
||||
|
||||
if (activeDraggers < maximumDraggers)
|
||||
return !HasEnoughPullStrength
|
||||
? $"Help drag ({activeDraggers}/{RequiredDraggerCount})"
|
||||
: $"Join drag ({activeDraggers}/{maximumDraggers})";
|
||||
|
||||
return HasEnoughPullStrength ? "In use" : "Too heavy";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsInteractionAvailable => base.IsInteractionAvailable &&
|
||||
(IsDraggedByLocalPlayer || ActiveDraggerCount < maximumDraggers);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolvePhysicsReferences();
|
||||
ApplyWeight();
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ResolvePhysicsReferences();
|
||||
ApplyWeight();
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
firstDraggerClientId.OnValueChanged += HandleDraggerChanged;
|
||||
secondDraggerClientId.OnValueChanged += HandleDraggerChanged;
|
||||
ApplyDraggingState();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
firstDraggerClientId.OnValueChanged -= HandleDraggerChanged;
|
||||
secondDraggerClientId.OnValueChanged -= HandleDraggerChanged;
|
||||
RestorePlayerCollisions();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!HasAnyDragger || Time.time < nextCollisionRefreshTime)
|
||||
return;
|
||||
|
||||
nextCollisionRefreshTime = Time.time + collisionRefreshInterval;
|
||||
IgnoreCollisionsWithPlayers();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!IsServer || !IsSpawned || physicsBody == null)
|
||||
return;
|
||||
|
||||
RemoveInvalidDraggers();
|
||||
if (!HasEnoughPullStrength || !TryGetCombinedDragTarget(out Vector3 target))
|
||||
return;
|
||||
|
||||
if (keepCurrentHeight)
|
||||
target.y = physicsBody.position.y;
|
||||
|
||||
float totalStrength = ActiveDraggerCount * pullStrengthPerPlayer;
|
||||
float loadRatio = Mathf.Clamp01(weight / totalStrength);
|
||||
float speedMultiplier = Mathf.Lerp(1f, minimumSpeedMultiplier, loadRatio);
|
||||
float movementStep = maximumDragSpeed * speedMultiplier * Time.fixedDeltaTime;
|
||||
Vector3 nextPosition = Vector3.MoveTowards(physicsBody.position, target, movementStep);
|
||||
physicsBody.MovePosition(nextPosition);
|
||||
}
|
||||
|
||||
protected override bool CanInteractOnServer(ulong senderClientId) =>
|
||||
IsDragger(senderClientId) ||
|
||||
ActiveDraggerCount < maximumDraggers;
|
||||
|
||||
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
||||
{
|
||||
if (IsDragger(senderClientId))
|
||||
RemoveDragger(senderClientId);
|
||||
else
|
||||
AddDragger(senderClientId);
|
||||
}
|
||||
|
||||
private void AddDragger(ulong clientId)
|
||||
{
|
||||
if (firstDraggerClientId.Value == NoDragger)
|
||||
firstDraggerClientId.Value = clientId;
|
||||
else if (maximumDraggers > 1 && secondDraggerClientId.Value == NoDragger)
|
||||
secondDraggerClientId.Value = clientId;
|
||||
}
|
||||
|
||||
private void RemoveDragger(ulong clientId)
|
||||
{
|
||||
if (firstDraggerClientId.Value == clientId)
|
||||
{
|
||||
firstDraggerClientId.Value = secondDraggerClientId.Value;
|
||||
secondDraggerClientId.Value = NoDragger;
|
||||
}
|
||||
else if (secondDraggerClientId.Value == clientId)
|
||||
{
|
||||
secondDraggerClientId.Value = NoDragger;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveInvalidDraggers()
|
||||
{
|
||||
ulong first = firstDraggerClientId.Value;
|
||||
ulong second = secondDraggerClientId.Value;
|
||||
|
||||
if (first != NoDragger && !IsDraggerValid(first))
|
||||
RemoveDragger(first);
|
||||
|
||||
if (second != NoDragger && !IsDraggerValid(second))
|
||||
RemoveDragger(second);
|
||||
}
|
||||
|
||||
private bool IsDraggerValid(ulong clientId)
|
||||
{
|
||||
if (!TryGetPlayerObject(clientId, out NetworkObject playerObject))
|
||||
return false;
|
||||
|
||||
float maximumDistanceSquared = maximumDraggerDistance * maximumDraggerDistance;
|
||||
return (playerObject.transform.position - physicsBody.position).sqrMagnitude <= maximumDistanceSquared;
|
||||
}
|
||||
|
||||
private bool TryGetCombinedDragTarget(out Vector3 target)
|
||||
{
|
||||
target = Vector3.zero;
|
||||
int validTargets = 0;
|
||||
|
||||
AddDragTarget(firstDraggerClientId.Value, ref target, ref validTargets);
|
||||
AddDragTarget(secondDraggerClientId.Value, ref target, ref validTargets);
|
||||
|
||||
if (validTargets == 0)
|
||||
return false;
|
||||
|
||||
target /= validTargets;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddDragTarget(ulong clientId, ref Vector3 target, ref int validTargets)
|
||||
{
|
||||
if (clientId == NoDragger || !TryGetPlayerObject(clientId, out NetworkObject playerObject))
|
||||
return;
|
||||
|
||||
target += playerObject.transform.TransformPoint(dragOffset);
|
||||
validTargets++;
|
||||
}
|
||||
|
||||
private bool IsDragger(ulong clientId) =>
|
||||
firstDraggerClientId.Value == clientId || secondDraggerClientId.Value == clientId;
|
||||
|
||||
private void HandleDraggerChanged(ulong _, ulong __) => ApplyDraggingState();
|
||||
|
||||
private void ApplyDraggingState()
|
||||
{
|
||||
if (physicsBody != null)
|
||||
{
|
||||
physicsBody.isKinematic = HasAnyDragger || !IsServer;
|
||||
physicsBody.linearVelocity = Vector3.zero;
|
||||
physicsBody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
if (HasAnyDragger)
|
||||
IgnoreCollisionsWithPlayers();
|
||||
else
|
||||
RestorePlayerCollisions();
|
||||
}
|
||||
|
||||
private void ResolvePhysicsReferences()
|
||||
{
|
||||
if (physicsBody == null)
|
||||
physicsBody = GetComponent<Rigidbody>();
|
||||
|
||||
if (objectColliders == null || objectColliders.Length == 0)
|
||||
objectColliders = GetComponentsInChildren<Collider>(true);
|
||||
}
|
||||
|
||||
private void ApplyWeight()
|
||||
{
|
||||
if (physicsBody != null)
|
||||
physicsBody.mass = Mathf.Max(0.1f, weight);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user