mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-09 12:45:55 +00:00
Autopickup for player
This commit is contained in:
parent
acc61f9a0e
commit
8d52b49e57
6 changed files with 242 additions and 62 deletions
152
Scripts/Components/FSM/3DPlayer/AutoPickupModule3D.cs
Normal file
152
Scripts/Components/FSM/3DPlayer/AutoPickupModule3D.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cirno.Scripts.Interactables;
|
||||
using Godot;
|
||||
|
||||
namespace Cirno.Scripts.Components.FSM._3DPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Detects nearby <see cref="ItemPickup3D"/> items whose loot table contains at least one
|
||||
/// auto-pickup entry and magnets them toward the player. Items are only formally collected
|
||||
/// (inventory added, node freed) once they physically reach the player's position.
|
||||
///
|
||||
/// Radius can be increased at runtime via equipment or upgrade systems — just write to
|
||||
/// <see cref="Radius"/> and the underlying collision shape is updated automatically.
|
||||
/// </summary>
|
||||
public partial class AutoPickupModule3D : Area3D, IModule<PlayerState, CharacterBody3D>
|
||||
{
|
||||
// Speed at which attracted items move toward the player (units/second).
|
||||
private const float AttractionSpeed = 12f;
|
||||
|
||||
// Squared distance threshold at which an item is considered "collected".
|
||||
private const float CollectDistanceSq = 0.15f * 0.15f;
|
||||
|
||||
[Export]
|
||||
private float _radius = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the auto-pickup detection sphere.
|
||||
/// Assigning a new value resizes the underlying collision shape immediately.
|
||||
/// </summary>
|
||||
public float Radius
|
||||
{
|
||||
get => _radius;
|
||||
set
|
||||
{
|
||||
_radius = value;
|
||||
UpdateCollisionRadius();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
private CollisionShape3D _collisionShape;
|
||||
private AudioStreamPlayer3D _pickupSound;
|
||||
|
||||
// Items currently being attracted toward the player.
|
||||
private readonly HashSet<ItemPickup3D> _trackedItems = [];
|
||||
|
||||
public IStateMachine<PlayerState, CharacterBody3D> StateMachine { get; private set; }
|
||||
|
||||
public void Init(IStateMachine<PlayerState, CharacterBody3D> machine)
|
||||
{
|
||||
StateMachine = machine;
|
||||
_collisionShape = GetNodeOrNull<CollisionShape3D>("CollisionShape3D") ?? CreateCollisionShape();
|
||||
_pickupSound = GetNodeOrNull<AudioStreamPlayer3D>("PickupSound");
|
||||
UpdateCollisionRadius();
|
||||
}
|
||||
|
||||
private CollisionShape3D CreateCollisionShape()
|
||||
{
|
||||
var shape = new CollisionShape3D { Shape = new SphereShape3D { Radius = _radius } };
|
||||
AddChild(shape);
|
||||
return shape;
|
||||
}
|
||||
|
||||
public void EnterState(PlayerState state)
|
||||
{
|
||||
_enabled = true;
|
||||
AreaEntered += OnAreaEntered;
|
||||
AreaExited += OnAreaExited;
|
||||
}
|
||||
|
||||
public void ExitState(PlayerState state)
|
||||
{
|
||||
_enabled = false;
|
||||
AreaEntered -= OnAreaEntered;
|
||||
AreaExited -= OnAreaExited;
|
||||
_trackedItems.Clear();
|
||||
}
|
||||
|
||||
public void Process(double delta) { }
|
||||
|
||||
public void PhysicsProcess(double delta)
|
||||
{
|
||||
if (!_enabled || _trackedItems.Count == 0) return;
|
||||
|
||||
var playerPos = StateMachine.MainObject.GlobalPosition;
|
||||
var toRemove = new List<ItemPickup3D>();
|
||||
|
||||
foreach (var item in _trackedItems)
|
||||
{
|
||||
if (!IsInstanceValid(item))
|
||||
{
|
||||
toRemove.Add(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
var direction = playerPos - item.GlobalPosition;
|
||||
var distanceSq = direction.LengthSquared();
|
||||
|
||||
if (distanceSq <= CollectDistanceSq)
|
||||
{
|
||||
item.Collect();
|
||||
_pickupSound?.Play();
|
||||
toRemove.Add(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move the item toward the player, scaling speed so it feels snappy at any distance.
|
||||
item.GlobalPosition += direction.Normalized() * AttractionSpeed * (float)delta;
|
||||
}
|
||||
|
||||
foreach (var item in toRemove)
|
||||
{
|
||||
_trackedItems.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAreaEntered(Area3D area)
|
||||
{
|
||||
if (!_enabled) return;
|
||||
if (area is not ItemPickup3D pickup) return;
|
||||
if (!pickup.AutoPickup) return;
|
||||
|
||||
// Only attract items whose inventory can still accept them to avoid a
|
||||
// looping autopickup situation when the inventory slot is full.
|
||||
var canAdd = pickup.LootTable.Aggregate(false,
|
||||
(current, item) => current || InventoryManager.Instance.CanAddItem(item.ItemKey));
|
||||
|
||||
if (canAdd)
|
||||
{
|
||||
_trackedItems.Add(pickup);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAreaExited(Area3D area)
|
||||
{
|
||||
if (area is ItemPickup3D pickup)
|
||||
{
|
||||
_trackedItems.Remove(pickup);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCollisionRadius()
|
||||
{
|
||||
if (_collisionShape?.Shape is SphereShape3D sphere)
|
||||
{
|
||||
sphere.Radius = _radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue