feat(shop): add box pickup/carry, pour-into-shelf, and package flight visuals
- Carryable: generic pickup/carry component, cosmetic per-peer follow of the carrier's HandSocket instead of NGO reparenting (camera pitch isn't networked, so root-level reparenting wouldn't tilt with the view) - PlayerCarry: mutual exclusion with PlayerHands, server-timed hold-to-pour tick (client only signals start/stop, never per-unit requests) - IPourSource/IPourTarget: decouple PlayerInteractor from concrete Shop types, same pattern as IInteractable - Lieferkarton: gains product data (productId/quantity), two-stage Interact() (open, then pick up), IPourSource - ShelfSlot: implements IPourTarget, fills the previous RefreshVisuals TODO - PackageDisplay: renders quantity as a proper column x row grid that stacks into further layers as space runs out, reused by both containers - FlyingPackage: small non-networked cosmetic visual that arcs a package from the carried box to its exact landing slot in the shelf grid on every successful pour tick, broadcast via Rpc(SendTo.Everyone) - StorageCrate: rejects pickup while the player is carrying a box Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AfterHours.Shop
|
||||
{
|
||||
/// <summary>
|
||||
/// Rein kosmetisches, nicht genetzwertes Flug-Visual für ein Stück Ware,
|
||||
/// das vom Karton ins Regal wandert. Jeder Peer erzeugt seine eigene
|
||||
/// Instanz (ausgelöst über PlayerCarry.PlayFlightRpc) – keine
|
||||
/// Server-Autorität nötig, der eigentliche Bestand ist längst per
|
||||
/// NetworkVariable synchron.
|
||||
/// </summary>
|
||||
public sealed class FlyingPackage : MonoBehaviour
|
||||
{
|
||||
private const float ArcHeight = 0.35f;
|
||||
|
||||
private Vector3 _start;
|
||||
private Vector3 _end;
|
||||
private Quaternion _startRotation;
|
||||
private Quaternion _endRotation;
|
||||
private float _duration;
|
||||
private float _elapsed;
|
||||
|
||||
public static void Spawn(GameObject prefab, Vector3 start, Vector3 end, Quaternion endRotation, float duration)
|
||||
{
|
||||
if (prefab == null) return;
|
||||
|
||||
var instance = Instantiate(prefab, start, Quaternion.identity);
|
||||
var flight = instance.AddComponent<FlyingPackage>();
|
||||
flight.Init(start, end, endRotation, duration);
|
||||
}
|
||||
|
||||
private void Init(Vector3 start, Vector3 end, Quaternion endRotation, float duration)
|
||||
{
|
||||
_start = start;
|
||||
_end = end;
|
||||
_startRotation = transform.rotation;
|
||||
_endRotation = endRotation;
|
||||
_duration = Mathf.Max(0.05f, duration);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(_elapsed / _duration);
|
||||
|
||||
Vector3 flat = Vector3.Lerp(_start, _end, t);
|
||||
float arc = ArcHeight * Mathf.Sin(t * Mathf.PI);
|
||||
transform.position = flat + Vector3.up * arc;
|
||||
transform.rotation = Quaternion.Slerp(_startRotation, _endRotation, t);
|
||||
|
||||
if (t >= 1f) Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user