Added UI to Furnance, fixed carry and drag obj and added fish

This commit is contained in:
2026-08-12 12:04:54 +03:00
parent 73069d8242
commit 487e0d72ff
73 changed files with 13209 additions and 15923 deletions
+189 -62
View File
@@ -1,9 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
public class ItemPipe : MonoBehaviour
[RequireComponent(typeof(NetworkObject))]
public sealed class ItemPipe : NetworkBehaviour
{
[Header("Pipe")]
[SerializeField] private Transform spawnPoint;
@@ -11,84 +13,192 @@ public class ItemPipe : MonoBehaviour
[Header("Configuration")]
[SerializeField] private ItemPipeConfig config;
[Header("Temporary testing")]
[SerializeField] private bool spawnOnE = true;
private readonly NetworkVariable<bool> spawnSequenceActive = new(
false,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
private bool isSpawning;
private readonly NetworkVariable<double> cooldownEndsAt = new(
0d,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server);
private Coroutine spawnRoutine;
public float CooldownDuration => config != null ? config.SpawnCooldown : 0f;
public float CooldownRemaining { get; private set; }
public int CooldownSecondsRemaining => Mathf.CeilToInt(CooldownRemaining);
public bool CanSpawnItems => config != null && !isSpawning && CooldownRemaining <= 0f;
private void Update()
public float CooldownRemaining
{
if (CooldownRemaining > 0f)
CooldownRemaining = Mathf.Max(0f, CooldownRemaining - Time.deltaTime);
get
{
double now = NetworkManager != null
? NetworkManager.ServerTime.Time
: Time.timeAsDouble;
return Mathf.Max(0f, (float)(cooldownEndsAt.Value - now));
}
}
public int CooldownSecondsRemaining => Mathf.CeilToInt(CooldownRemaining);
public bool IsSpawning => spawnSequenceActive.Value;
public bool CanSpawnItems =>
IsSpawned &&
config != null &&
!spawnSequenceActive.Value &&
CooldownRemaining <= 0f;
if (!spawnOnE || Keyboard.current == null)
public event Action Changed;
private void Reset() => spawnPoint = transform;
public override void OnNetworkSpawn()
{
spawnSequenceActive.OnValueChanged += HandleSpawnStateChanged;
cooldownEndsAt.OnValueChanged += HandleCooldownChanged;
if (IsServer)
{
spawnSequenceActive.Value = false;
cooldownEndsAt.Value = 0d;
}
Changed?.Invoke();
}
public override void OnNetworkDespawn()
{
spawnSequenceActive.OnValueChanged -= HandleSpawnStateChanged;
cooldownEndsAt.OnValueChanged -= HandleCooldownChanged;
if (spawnRoutine != null)
{
StopCoroutine(spawnRoutine);
spawnRoutine = null;
}
}
/// <summary>
/// Starts one configured batch. Safe to connect directly to a Button or UnityEvent.
/// Calls made on a client are forwarded to the server.
/// </summary>
public void SpawnItems()
{
if (!IsSpawned)
return;
if (Keyboard.current.eKey.wasPressedThisFrame)
if (IsServer)
TryStartSpawnSequenceOnServer();
else
RequestSpawnItemsRpc();
}
/// <summary>
/// UnityEvent&lt;bool&gt; adapter for a lever. Only the true edge starts a batch;
/// returning a momentary lever to false does not cancel the current batch.
/// </summary>
public void SpawnItems(bool activated)
{
if (activated)
SpawnItems();
}
public void SpawnItems()
/// <summary>
/// Starts while true and cancels the current batch while false. Use this when
/// the lever position must control whether the pipe is allowed to keep running.
/// </summary>
public void SetSpawning(bool shouldSpawn)
{
if (!CanSpawnItems)
return;
StartCoroutine(SpawnSequence(config));
if (shouldSpawn)
SpawnItems();
else
StopSpawning();
}
private IEnumerator SpawnSequence(ItemPipeConfig activeConfig)
public void StopSpawning()
{
isSpawning = true;
if (!IsSpawned)
return;
if (IsServer)
StopSpawnSequenceOnServer(applyCooldown: true);
else
RequestStopSpawningRpc();
}
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void RequestSpawnItemsRpc() => TryStartSpawnSequenceOnServer();
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void RequestStopSpawningRpc() =>
StopSpawnSequenceOnServer(applyCooldown: true);
private bool TryStartSpawnSequenceOnServer()
{
if (!IsServer || !CanSpawnItems)
return false;
spawnSequenceActive.Value = true;
spawnRoutine = StartCoroutine(SpawnSequenceOnServer(config));
return true;
}
private IEnumerator SpawnSequenceOnServer(ItemPipeConfig activeConfig)
{
List<GameObject> spawnQueue = BuildSpawnQueue(activeConfig);
for (int i = 0; i < spawnQueue.Count; i++)
for (int index = 0; index < spawnQueue.Count; index++)
{
Spawn(spawnQueue[i], activeConfig);
SpawnNetworkItemOnServer(spawnQueue[index], activeConfig);
if (i < spawnQueue.Count - 1)
if (index < spawnQueue.Count - 1)
yield return new WaitForSeconds(activeConfig.SpawnInterval);
}
isSpawning = false;
CooldownRemaining = activeConfig.SpawnCooldown;
spawnRoutine = null;
cooldownEndsAt.Value =
NetworkManager.ServerTime.Time + activeConfig.SpawnCooldown;
spawnSequenceActive.Value = false;
}
private List<GameObject> BuildSpawnQueue(ItemPipeConfig activeConfig)
private void StopSpawnSequenceOnServer(bool applyCooldown)
{
if (!IsServer || !spawnSequenceActive.Value)
return;
if (spawnRoutine != null)
{
StopCoroutine(spawnRoutine);
spawnRoutine = null;
}
cooldownEndsAt.Value = applyCooldown && config != null
? NetworkManager.ServerTime.Time + config.SpawnCooldown
: 0d;
spawnSequenceActive.Value = false;
}
private static List<GameObject> BuildSpawnQueue(ItemPipeConfig activeConfig)
{
List<GameObject> queue = new();
// Добавляем предметы в точном количестве.
foreach (ItemPipeConfig.GuaranteedItem item in activeConfig.GuaranteedItems)
{
if (item.prefab == null)
continue;
for (int i = 0; i < item.amount; i++)
for (int index = 0; index < item.amount; index++)
queue.Add(item.prefab);
}
// Выбираем указанное количество случайных предметов.
for (int i = 0; i < activeConfig.RandomItemCount; i++)
for (int index = 0; index < activeConfig.RandomItemCount; index++)
{
GameObject selectedPrefab = ChooseWeightedRandom(activeConfig);
if (selectedPrefab != null)
queue.Add(selectedPrefab);
}
// Перемешиваем гарантированные и случайные предметы вместе.
Shuffle(queue);
return queue;
}
private GameObject ChooseWeightedRandom(ItemPipeConfig activeConfig)
private static GameObject ChooseWeightedRandom(ItemPipeConfig activeConfig)
{
float totalWeight = 0f;
@@ -101,64 +211,81 @@ public class ItemPipe : MonoBehaviour
if (totalWeight <= 0f)
return null;
float roll = Random.value * totalWeight;
float currentWeight = 0f;
float roll = UnityEngine.Random.value * totalWeight;
float accumulatedWeight = 0f;
foreach (ItemPipeConfig.RandomItem item in activeConfig.RandomItems)
{
if (item.prefab == null || item.weight <= 0f)
continue;
currentWeight += item.weight;
if (roll < currentWeight)
accumulatedWeight += item.weight;
if (roll < accumulatedWeight)
return item.prefab;
}
return null;
}
private void Shuffle(List<GameObject> queue)
private static void Shuffle(List<GameObject> queue)
{
for (int i = queue.Count - 1; i > 0; i--)
for (int index = queue.Count - 1; index > 0; index--)
{
int randomIndex = Random.Range(0, i + 1);
(queue[i], queue[randomIndex]) =
(queue[randomIndex], queue[i]);
int randomIndex = UnityEngine.Random.Range(0, index + 1);
(queue[index], queue[randomIndex]) =
(queue[randomIndex], queue[index]);
}
}
private void Spawn(GameObject prefab, ItemPipeConfig activeConfig)
private void SpawnNetworkItemOnServer(
GameObject prefab,
ItemPipeConfig activeConfig)
{
if (prefab == null || spawnPoint == null)
Transform activeSpawnPoint = spawnPoint != null ? spawnPoint : transform;
if (prefab == null)
return;
float offsetX = RandomRange(-activeConfig.RandomSpawnX, activeConfig.RandomSpawnX);
float offsetZ = RandomRange(-activeConfig.RandomSpawnZ, activeConfig.RandomSpawnZ);
float offsetX = UnityEngine.Random.Range(
-activeConfig.RandomSpawnX,
activeConfig.RandomSpawnX);
float offsetZ = UnityEngine.Random.Range(
-activeConfig.RandomSpawnZ,
activeConfig.RandomSpawnZ);
Vector3 spawnPosition =
spawnPoint.position +
spawnPoint.right * offsetX +
spawnPoint.forward * offsetZ;
activeSpawnPoint.position +
activeSpawnPoint.right * offsetX +
activeSpawnPoint.forward * offsetZ;
GameObject item = Instantiate(
prefab,
spawnPosition,
Random.rotationUniform
);
UnityEngine.Random.rotationUniform);
if (!item.TryGetComponent(out NetworkObject networkObject))
{
Debug.LogError(
$"{name} cannot spawn '{prefab.name}': the prefab has no NetworkObject.",
this);
Destroy(item);
return;
}
networkObject.Spawn();
if (item.TryGetComponent(out Rigidbody body))
{
Vector3 worldVelocityChange =
spawnPoint.TransformDirection(activeConfig.LocalVelocityChange);
activeSpawnPoint.TransformDirection(activeConfig.LocalVelocityChange);
body.AddForce(worldVelocityChange, ForceMode.VelocityChange);
}
}
private float RandomRange(float min, float max)
{
return Random.Range(min, max);
}
private void HandleSpawnStateChanged(bool _, bool __) => Changed?.Invoke();
private void HandleCooldownChanged(double _, double __) => Changed?.Invoke();
[ContextMenu("Debug/Spawn Batch (Network Play Mode)")]
private void DebugSpawnBatch() => SpawnItems();
[ContextMenu("Debug/Stop Batch (Network Play Mode)")]
private void DebugStopBatch() => StopSpawning();
}