Squashed commit of the following:
commit428243f4a6Author: AsanoRema <asanorema2036@gmail.com> Date: Sat Aug 29 13:43:13 2026 +0300 Squashed commit of the following: commit68e78fa464Author: AsanoRema <asanorema2036@gmail.com> Date: Sat Aug 29 13:41:49 2026 +0300 Updated .gitignore commit9f87122a34Author: AsanoRema <asanorema2036@gmail.com> Date: Sat Aug 29 13:34:57 2026 +0300 Added Dohya commitf6a7547e84Author: AsanoRema <asanorema2036@gmail.com> Date: Fri Aug 21 17:59:13 2026 +0300 Fixed commitb98901c366Author: AsanoRema <asanorema2036@gmail.com> Date: Tue Aug 18 17:37:22 2026 +0300 Added new particle Package commitbb2f4a3b16Author: AsanoRema <asanorema2036@gmail.com> Date: Tue Aug 18 17:17:48 2026 +0300 Map mehanic ready to multiplayer commitc01ff800f6Author: AsanoRema <asanorema2036@gmail.com> Date: Tue Aug 18 16:16:19 2026 +0300 Squashed commit of the following: commit173bbb165cAuthor: henjkey <henjkey@gmail.com> Date: Fri Aug 14 18:51:23 2026 +0500 Mappp commitd559ab338aAuthor: henjkey <henjkey@gmail.com> Date: Thu Aug 13 18:42:41 2026 +0500 Map Controller Coordinates Target System Crosshair System Radar System
This commit is contained in:
@@ -1,27 +1,39 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public sealed class NetworkButtonInteractable : NetworkInteractable
|
||||
{
|
||||
[SerializeField] private UnityEvent pressed = new();
|
||||
[SerializeField] private UnityEvent<bool> buttonStateChanged;
|
||||
|
||||
private readonly NetworkVariable<bool> isPressed = new(
|
||||
false,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
private readonly NetworkVariable<uint> pressSequence = new(
|
||||
0,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
public bool IsPressed => isPressed.Value;
|
||||
public uint PressSequence => pressSequence.Value;
|
||||
|
||||
public event Action Pressed;
|
||||
public event Action<bool> StateChanged;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
isPressed.OnValueChanged += HandleStateChanged;
|
||||
pressSequence.OnValueChanged += HandlePressSequenceChanged;
|
||||
ApplyState(isPressed.Value);
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
isPressed.OnValueChanged -= HandleStateChanged;
|
||||
pressSequence.OnValueChanged -= HandlePressSequenceChanged;
|
||||
}
|
||||
|
||||
protected override bool CanInteractOnServer(ulong senderClientId) => true;
|
||||
@@ -29,10 +41,23 @@ public sealed class NetworkButtonInteractable : NetworkInteractable
|
||||
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
||||
{
|
||||
isPressed.Value = !isPressed.Value;
|
||||
ApplyState(isPressed.Value);
|
||||
pressSequence.Value++;
|
||||
}
|
||||
|
||||
private void HandleStateChanged(bool _, bool currentValue) => ApplyState(currentValue);
|
||||
|
||||
private void ApplyState(bool currentValue) => buttonStateChanged?.Invoke(currentValue);
|
||||
private void HandlePressSequenceChanged(uint previous, uint current)
|
||||
{
|
||||
if (current == previous)
|
||||
return;
|
||||
|
||||
Pressed?.Invoke();
|
||||
pressed?.Invoke();
|
||||
}
|
||||
|
||||
private void ApplyState(bool currentValue)
|
||||
{
|
||||
StateChanged?.Invoke(currentValue);
|
||||
buttonStateChanged?.Invoke(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,6 +404,12 @@ public sealed class NetworkDraggableInteractable : NetworkInteractable, IWorksta
|
||||
{
|
||||
bool shouldBeKinematic = IsExternallyControlled || !IsServer;
|
||||
|
||||
if (IsExternallyControlled && !physicsBody.isKinematic)
|
||||
{
|
||||
physicsBody.linearVelocity = Vector3.zero;
|
||||
physicsBody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
if (shouldBeKinematic)
|
||||
{
|
||||
physicsBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
@@ -417,11 +423,6 @@ public sealed class NetworkDraggableInteractable : NetworkInteractable, IWorksta
|
||||
physicsBody.collisionDetectionMode = ResolveDynamicCollisionDetectionMode();
|
||||
}
|
||||
|
||||
if (IsExternallyControlled)
|
||||
{
|
||||
physicsBody.linearVelocity = Vector3.zero;
|
||||
physicsBody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (HasAnyDragger)
|
||||
|
||||
@@ -37,8 +37,7 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
||||
if (!TryGetPlayerObject(senderClientId, out NetworkObject playerObject))
|
||||
return;
|
||||
|
||||
float maximumDistanceSquared = maximumInteractionDistance * maximumInteractionDistance;
|
||||
if ((playerObject.transform.position - transform.position).sqrMagnitude > maximumDistanceSquared)
|
||||
if (!IsWithinInteractionDistance(playerObject.transform.position))
|
||||
return;
|
||||
|
||||
if (!CanInteractOnServer(senderClientId))
|
||||
@@ -47,6 +46,28 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
||||
InteractOnServer(senderClientId, playerObject);
|
||||
}
|
||||
|
||||
private bool IsWithinInteractionDistance(Vector3 playerPosition)
|
||||
{
|
||||
float maximumDistanceSquared = maximumInteractionDistance * maximumInteractionDistance;
|
||||
if ((playerPosition - transform.position).sqrMagnitude <= maximumDistanceSquared)
|
||||
return true;
|
||||
|
||||
foreach (Collider interactionCollider in GetComponentsInChildren<Collider>(true))
|
||||
{
|
||||
if (interactionCollider == null || !interactionCollider.enabled ||
|
||||
!interactionCollider.gameObject.activeInHierarchy)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 closestPoint = interactionCollider.ClosestPoint(playerPosition);
|
||||
if ((playerPosition - closestPoint).sqrMagnitude <= maximumDistanceSquared)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool CanInteractOnServer(ulong senderClientId) => false;
|
||||
protected virtual void InteractOnServer(ulong senderClientId, NetworkObject playerObject) { }
|
||||
|
||||
@@ -58,4 +79,3 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
||||
return playerObject != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
@@ -44,6 +45,7 @@ public sealed class NetworkPhysicalLeverInteractable :
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private bool hasSnapTarget;
|
||||
private bool maximumEndpointLatched;
|
||||
private float snapTargetValue;
|
||||
private float automaticReturnTime = -1f;
|
||||
|
||||
@@ -52,7 +54,11 @@ public sealed class NetworkPhysicalLeverInteractable :
|
||||
public bool IsControlled => controllingClientId.Value != NoController;
|
||||
public bool IsControlledByLocalPlayer =>
|
||||
NetworkManager != null && IsControlledBy(NetworkManager.LocalClientId);
|
||||
public string ReleasePrompt => "[Hold E] Pulling lever";
|
||||
public string ReleasePrompt => "[Hold LMB] Pulling lever";
|
||||
|
||||
public event Action<float> ValueChanged;
|
||||
public event Action<bool> StateChanged;
|
||||
public event Action MaximumReached;
|
||||
|
||||
public override string InteractionPrompt => IsControlledByLocalPlayer
|
||||
? ReleasePrompt
|
||||
@@ -92,6 +98,7 @@ public sealed class NetworkPhysicalLeverInteractable :
|
||||
{
|
||||
synchronizedValue.Value = InitialPositionValue;
|
||||
isActivated.Value = initialPositionIsMaximum;
|
||||
maximumEndpointLatched = initialPositionIsMaximum;
|
||||
}
|
||||
|
||||
synchronizedValue.OnValueChanged += HandleValueChanged;
|
||||
@@ -216,9 +223,24 @@ public sealed class NetworkPhysicalLeverInteractable :
|
||||
const float endpointTolerance = 0.001f;
|
||||
|
||||
if (synchronizedValue.Value >= 1f - endpointTolerance)
|
||||
{
|
||||
SetActivation(true);
|
||||
else if (synchronizedValue.Value <= endpointTolerance)
|
||||
if (!maximumEndpointLatched)
|
||||
{
|
||||
maximumEndpointLatched = true;
|
||||
MaximumReached?.Invoke();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
maximumEndpointLatched = false;
|
||||
}
|
||||
|
||||
if (synchronizedValue.Value <= endpointTolerance)
|
||||
{
|
||||
maximumEndpointLatched = false;
|
||||
SetActivation(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActivation(bool activated)
|
||||
@@ -304,11 +326,15 @@ public sealed class NetworkPhysicalLeverInteractable :
|
||||
private void HandleValueChanged(float _, float currentValue)
|
||||
{
|
||||
ApplyVisual(currentValue);
|
||||
ValueChanged?.Invoke(currentValue);
|
||||
leverValueChanged?.Invoke(currentValue);
|
||||
}
|
||||
|
||||
private void HandleActivationChanged(bool _, bool currentState) =>
|
||||
private void HandleActivationChanged(bool _, bool currentState)
|
||||
{
|
||||
StateChanged?.Invoke(currentState);
|
||||
leverStateChanged?.Invoke(currentState);
|
||||
}
|
||||
|
||||
private void ApplyVisual(float value)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkPhysicalWheelInteractable :
|
||||
NetworkInteractable,
|
||||
IPlayerInteractionSession
|
||||
{
|
||||
private const ulong NoController = ulong.MaxValue;
|
||||
|
||||
[Header("Wheel visual")]
|
||||
[SerializeField] private Transform wheelVisual;
|
||||
[SerializeField] private Vector3 neutralLocalEulerAngles;
|
||||
[SerializeField] private Vector3 localRotationAxis = Vector3.right;
|
||||
[SerializeField] private Vector3 localReferenceDirection = Vector3.up;
|
||||
[SerializeField, Range(-720f, 0f)] private float minimumAngle = -180f;
|
||||
[SerializeField, Range(0f, 720f)] private float maximumAngle = 180f;
|
||||
[SerializeField, Range(0f, 1f)] private float initialNormalizedValue = 0.5f;
|
||||
[SerializeField, Min(1f)] private float maximumRotationSpeed = 360f;
|
||||
|
||||
[Header("Control")]
|
||||
[SerializeField, Min(0.5f)] private float maximumControlDistance = 4f;
|
||||
[SerializeField] private UnityEvent<float> normalizedValueChanged;
|
||||
|
||||
private readonly NetworkVariable<ulong> controllingClientId = new(
|
||||
NoController,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private readonly NetworkVariable<float> synchronizedAngle = new(
|
||||
0f,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private float lastPointerAngle;
|
||||
private bool hasPointerAngle;
|
||||
|
||||
public float Angle => synchronizedAngle.Value;
|
||||
public float NormalizedValue => Mathf.InverseLerp(
|
||||
minimumAngle,
|
||||
maximumAngle,
|
||||
synchronizedAngle.Value);
|
||||
public bool IsControlled => controllingClientId.Value != NoController;
|
||||
public bool IsControlledByLocalPlayer =>
|
||||
NetworkManager != null && IsControlledBy(NetworkManager.LocalClientId);
|
||||
public string ReleasePrompt => "[Hold LMB] Turning wheel";
|
||||
|
||||
public override string InteractionPrompt => IsControlledByLocalPlayer
|
||||
? ReleasePrompt
|
||||
: IsControlled
|
||||
? "Wheel in use"
|
||||
: InteractionLabel;
|
||||
|
||||
public override bool IsInteractionAvailable =>
|
||||
base.IsInteractionAvailable && (!IsControlled || IsControlledByLocalPlayer);
|
||||
|
||||
public event Action<float> ValueChanged;
|
||||
|
||||
private Quaternion NeutralLocalRotation => Quaternion.Euler(neutralLocalEulerAngles);
|
||||
private Vector3 RotationAxis => localRotationAxis.sqrMagnitude > 0.0001f
|
||||
? localRotationAxis.normalized
|
||||
: Vector3.right;
|
||||
private Vector3 ReferenceDirection => localReferenceDirection.sqrMagnitude > 0.0001f
|
||||
? localReferenceDirection.normalized
|
||||
: Vector3.up;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
wheelVisual = transform;
|
||||
neutralLocalEulerAngles = wheelVisual.localEulerAngles;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (maximumAngle <= minimumAngle)
|
||||
maximumAngle = minimumAngle + 1f;
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
PlayerInteractionSessionRegistry.Register(this);
|
||||
if (IsServer)
|
||||
{
|
||||
synchronizedAngle.Value = Mathf.Lerp(
|
||||
minimumAngle,
|
||||
maximumAngle,
|
||||
initialNormalizedValue);
|
||||
}
|
||||
|
||||
synchronizedAngle.OnValueChanged += HandleAngleChanged;
|
||||
ApplyVisual(synchronizedAngle.Value);
|
||||
InvokeValueChanged();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
PlayerInteractionSessionRegistry.Unregister(this);
|
||||
synchronizedAngle.OnValueChanged -= HandleAngleChanged;
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!IsServer || !IsSpawned || !IsControlled || wheelVisual == null)
|
||||
return;
|
||||
|
||||
if (!TryGetPlayerObject(controllingClientId.Value, out NetworkObject playerObject) ||
|
||||
!IsPlayerWithinControlDistance(playerObject))
|
||||
{
|
||||
EndControl();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryCalculatePointerAngle(playerObject, out float pointerAngle))
|
||||
{
|
||||
hasPointerAngle = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasPointerAngle)
|
||||
{
|
||||
lastPointerAngle = pointerAngle;
|
||||
hasPointerAngle = true;
|
||||
return;
|
||||
}
|
||||
|
||||
float pointerDelta = Mathf.DeltaAngle(lastPointerAngle, pointerAngle);
|
||||
lastPointerAngle = pointerAngle;
|
||||
float maximumStep = maximumRotationSpeed * Time.fixedDeltaTime;
|
||||
float nextAngle = Mathf.Clamp(
|
||||
synchronizedAngle.Value + Mathf.Clamp(pointerDelta, -maximumStep, maximumStep),
|
||||
minimumAngle,
|
||||
maximumAngle);
|
||||
if (!Mathf.Approximately(nextAngle, synchronizedAngle.Value))
|
||||
synchronizedAngle.Value = nextAngle;
|
||||
}
|
||||
|
||||
public bool IsControlledBy(ulong clientId) =>
|
||||
controllingClientId.Value == clientId;
|
||||
|
||||
public void RequestRelease()
|
||||
{
|
||||
if (IsSpawned && IsControlledByLocalPlayer)
|
||||
RequestReleaseServerRpc();
|
||||
}
|
||||
|
||||
protected override bool CanInteractOnServer(ulong senderClientId) =>
|
||||
!IsControlled || IsControlledBy(senderClientId);
|
||||
|
||||
protected override void InteractOnServer(
|
||||
ulong senderClientId,
|
||||
NetworkObject playerObject)
|
||||
{
|
||||
if (IsControlledBy(senderClientId))
|
||||
EndControl();
|
||||
else
|
||||
BeginControl(senderClientId, playerObject);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestReleaseServerRpc(RpcParams rpcParams = default)
|
||||
{
|
||||
if (IsControlledBy(rpcParams.Receive.SenderClientId))
|
||||
EndControl();
|
||||
}
|
||||
|
||||
private void BeginControl(ulong clientId, NetworkObject playerObject)
|
||||
{
|
||||
controllingClientId.Value = clientId;
|
||||
hasPointerAngle = TryCalculatePointerAngle(playerObject, out lastPointerAngle);
|
||||
}
|
||||
|
||||
private void EndControl()
|
||||
{
|
||||
controllingClientId.Value = NoController;
|
||||
hasPointerAngle = false;
|
||||
}
|
||||
|
||||
private bool IsPlayerWithinControlDistance(NetworkObject playerObject)
|
||||
{
|
||||
float maximumDistanceSquared = maximumControlDistance * maximumControlDistance;
|
||||
return (playerObject.transform.position - wheelVisual.position).sqrMagnitude <=
|
||||
maximumDistanceSquared;
|
||||
}
|
||||
|
||||
private bool TryCalculatePointerAngle(
|
||||
NetworkObject playerObject,
|
||||
out float pointerAngle)
|
||||
{
|
||||
pointerAngle = 0f;
|
||||
FirstPersonCameraController cameraController =
|
||||
playerObject.GetComponent<FirstPersonCameraController>();
|
||||
Transform aimTransform = cameraController != null
|
||||
? cameraController.AimTransform
|
||||
: playerObject.transform;
|
||||
|
||||
Transform wheelParent = wheelVisual.parent;
|
||||
Vector3 axisWorld = wheelParent != null
|
||||
? wheelParent.TransformDirection(NeutralLocalRotation * RotationAxis)
|
||||
: NeutralLocalRotation * RotationAxis;
|
||||
Vector3 referenceWorld = wheelParent != null
|
||||
? wheelParent.TransformDirection(NeutralLocalRotation * ReferenceDirection)
|
||||
: NeutralLocalRotation * ReferenceDirection;
|
||||
|
||||
Ray aimRay = new(aimTransform.position, aimTransform.forward);
|
||||
Plane wheelPlane = new(axisWorld, wheelVisual.position);
|
||||
Vector3 targetPoint;
|
||||
if (wheelPlane.Raycast(aimRay, out float enter) && enter >= 0f)
|
||||
{
|
||||
targetPoint = aimRay.GetPoint(enter);
|
||||
}
|
||||
else
|
||||
{
|
||||
float distanceAlongRay = Mathf.Max(
|
||||
0.25f,
|
||||
Vector3.Dot(wheelVisual.position - aimRay.origin, aimRay.direction));
|
||||
targetPoint = aimRay.GetPoint(distanceAlongRay);
|
||||
}
|
||||
|
||||
Vector3 pointerDirection = Vector3.ProjectOnPlane(
|
||||
targetPoint - wheelVisual.position,
|
||||
axisWorld);
|
||||
if (pointerDirection.sqrMagnitude < 0.0001f)
|
||||
return false;
|
||||
|
||||
pointerAngle = Vector3.SignedAngle(
|
||||
referenceWorld,
|
||||
pointerDirection.normalized,
|
||||
axisWorld);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleAngleChanged(float _, float current) =>
|
||||
ApplyNetworkValue(current);
|
||||
|
||||
private void ApplyNetworkValue(float angle)
|
||||
{
|
||||
ApplyVisual(angle);
|
||||
InvokeValueChanged();
|
||||
}
|
||||
|
||||
private void InvokeValueChanged()
|
||||
{
|
||||
float value = NormalizedValue;
|
||||
ValueChanged?.Invoke(value);
|
||||
normalizedValueChanged?.Invoke(value);
|
||||
}
|
||||
|
||||
private void ApplyVisual(float angle)
|
||||
{
|
||||
if (wheelVisual != null)
|
||||
{
|
||||
wheelVisual.localRotation = NeutralLocalRotation *
|
||||
Quaternion.AngleAxis(angle, RotationAxis);
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Preview/Minimum")]
|
||||
private void PreviewMinimum() => ApplyVisual(minimumAngle);
|
||||
|
||||
[ContextMenu("Preview/Center")]
|
||||
private void PreviewCenter() => ApplyVisual(Mathf.Lerp(
|
||||
minimumAngle,
|
||||
maximumAngle,
|
||||
0.5f));
|
||||
|
||||
[ContextMenu("Preview/Maximum")]
|
||||
private void PreviewMaximum() => ApplyVisual(maximumAngle);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c645663bb9dcbaf43991bd38665923da
|
||||
Reference in New Issue
Block a user