Godot Version
4.5
Question
I currently have the problem that I can only pick up my loot lying on the ground when I zoom the camera in or out. As soon as the camera moves automatically because I control the character with WASD, or when it doesn’t move at all, I can’t loot anything.
I can hardly find anything about this on the internet, except that Godot’s raycast for MouseEntered and MouseExited in my Area3D isn’t continuously active.
Does anyone have an idea how I can solve this problem properly?
using Godot;
using Runarsokari.Core.Autoloads;
namespace Runarsokari.Game;
public partial class CameraFollow : Camera3D
{
[ExportGroup("Target")]
[Export] public NodePath TargetPath { get; set; }
[ExportGroup("Camera Settings")]
[Export] public Vector3 Offset { get; set; } = new(0, 10, 10);
[Export] public float SmoothSpeed { get; set; } = 5.0f;
[ExportGroup("Zoom")]
[Export] public bool EnableZoom { get; set; } = true;
[Export(PropertyHint.Range, "0.1, 5.0")] public float ZoomSensitivity { get; set; } = 0.2f;
[Export(PropertyHint.Range, "0.5, 3.0")] public float MinZoomFactor { get; set; } = 0.6f;
[Export(PropertyHint.Range, "0.5, 3.0")] public float MaxZoomFactor { get; set; } = 1.2f;
private Node3D _target;
private float _zoomFactor = 1.0f;
public override void _Ready()
{
if (!string.IsNullOrEmpty(TargetPath))
{
_target = GetNode<Node3D>(TargetPath);
}
SetPhysicsProcess(_target != null);
SetProcessInput(EnableZoom && _target != null);
SignalBus.Instance.PlayerChanged += OnPlayerSpawned;
SignalBus.Instance.PlayerDied += OnPlayerDeath;
}
public override void _Input(InputEvent @event)
{
if (!EnableZoom || _target == null)
{
return;
}
if (@event is InputEventMouseButton { Pressed: true } mouseButton)
{
float deltaZoom = 0;
if (mouseButton.ButtonIndex == MouseButton.WheelUp)
{
deltaZoom = -ZoomSensitivity;
}
else if (mouseButton.ButtonIndex == MouseButton.WheelDown)
{
deltaZoom = ZoomSensitivity;
}
if (!Mathf.IsZeroApprox(deltaZoom))
{
_zoomFactor = Mathf.Clamp(_zoomFactor + deltaZoom, MinZoomFactor, MaxZoomFactor);
@event.Dispose();
}
}
}
public override void _PhysicsProcess(double delta)
{
if (_target?.GlobalTransform.Origin == null)
{
return;
}
Vector3 desiredPosition = _target.GlobalTransform.Origin + Offset * _zoomFactor;
Vector3 newOrigin = GlobalTransform.Origin.Lerp(desiredPosition, 1f - Mathf.Exp(-SmoothSpeed * (float)delta));
if (!newOrigin.IsEqualApprox(GlobalTransform.Origin))
{
GlobalTransform = new Transform3D(GlobalTransform.Basis, newOrigin);
}
}
private void OnPlayerSpawned()
{
var player = Global.Instance.CurrentPlayer;
if (player != null)
{
_target = player;
SetPhysicsProcess(_target != null);
SetProcessInput(EnableZoom && _target != null);
}
}
private void OnPlayerDeath()
{
_target = null;
SetPhysicsProcess(false);
SetProcessInput(false);
}
}
Loot:
using Godot;
using Godot.Collections;
using Runarsokari.Core.Logging;
using Runarsokari.Game.Component;
using Runarsokari.Game.Item;
using Runarsokari.Game.Visual;
using System.Linq;
using Runarsokari.Core.Autoloads;
namespace Runarsokari.Game.Loot;
[GlobalClass]
public partial class Loot : RigidBody3D
{
[Export] public NodePath PhysicsColliderPath { get; set; }
[Export] public NodePath VisualsMountPath { get; set; }
[Export] public NodePath ClickAreaPath { get; set; }
[Export] public NodePath ClickShapePath { get; set; }
public ItemInstance InstanceData { get; set; }
public Vector3 InitialGlobalPosition { get; set; } = Vector3.Zero;
private Node3D _visualsMountNode;
private CollisionShape3D _physicsColliderNode;
private Area3D _clickAreaNode;
private CollisionShape3D _clickAreaShape;
private readonly Array<Node> _instantiatedVisuals = [];
public override void _Ready()
{
LoadAndValidateRequiredNodes();
if (InstanceData?.BaseDefinition == null)
{
GameLogger.PushError($"DroppedItem '{Name}': Wurde ohne gültige ItemInstance oder BaseDefinition zum Baum hinzugefügt. Item wird entfernt.");
QueueFree();
return;
}
Name = $"Dropped_{InstanceData.BaseDefinition.ItemId}_{GetInstanceId()}";
GlobalPosition = InitialGlobalPosition;
SetupVisuals(InstanceData.BaseDefinition);
InitializePhysics();
InitializeClickInteraction();
Freeze = false;
//QueueFreeAfterDelay(10);
}
#region Init
private void LoadAndValidateRequiredNodes()
{
_visualsMountNode = GetNode<Node3D>(VisualsMountPath);
if (_visualsMountNode == null)
{
GameLogger.PushError($"DroppedItem '{Name}': VisualsMountPath '{VisualsMountPath}' nicht korrekt zugewiesen oder Node nicht gefunden.");
QueueFree();
return;
}
_physicsColliderNode = GetNode<CollisionShape3D>(PhysicsColliderPath);
if (_physicsColliderNode == null)
{
GameLogger.PushError($"DroppedItem '{Name}': PhysicsColliderPath '{PhysicsColliderPath}' nicht korrekt zugewiesen oder Node nicht gefunden.");
QueueFree();
return;
}
_clickAreaNode = GetNode<Area3D>(ClickAreaPath);
if (_clickAreaNode == null)
{
GameLogger.PushWarn($"DroppedItem '{Name}': ClickAreaPath '{ClickAreaPath}' nicht korrekt zugewiesen oder Node nicht gefunden. Klick-Interaktion wird nicht funktionieren.");
QueueFree();
}
}
private void InitializePhysics()
{
if (_physicsColliderNode != null)
{
_physicsColliderNode.Disabled = false;
}
}
private void InitializeClickInteraction()
{
if (_clickAreaNode == null)
{
return;
}
_clickAreaNode.MouseEntered += OnMouseEnteredInteractionArea;
_clickAreaNode.MouseExited += OnMouseExitedInteractionArea;
_clickAreaNode.ProcessMode = ProcessModeEnum.Inherit;
_clickAreaNode.SetProcessInput(true);
_clickAreaShape = _clickAreaNode.GetNodeOrNull<CollisionShape3D>("ClickShape");
if (_clickAreaShape == null)
{
Node untypedClickShape = _clickAreaNode.GetNodeOrNull("ClickShape");
GameLogger.PushWarn(untypedClickShape != null
? $"DroppedItem '{Name}': Ein Node namens 'ClickShape' wurde als Kind von '{_clickAreaNode.Name}' gefunden, aber es ist vom Typ '{untypedClickShape.GetType().Name}' anstatt CollisionShape3D. Mouse hover funktioniert eventuell nicht."
: $"DroppedItem '{Name}': Keine CollisionShape3D namens 'ClickShape' als Kind von '{_clickAreaNode.Name}' gefunden. Mouse hover funktioniert eventuell nicht.");
}
else
{
_clickAreaShape.Disabled = false;
}
}
#endregion
#region Visuals
private void SetupVisuals(ItemDefinition itemDef)
{
if (_visualsMountNode == null)
{
return;
}
foreach (Node visual in _instantiatedVisuals)
{
visual.QueueFree();
}
_instantiatedVisuals.Clear();
if (itemDef.AffectedVisualParts == null || itemDef.AffectedVisualParts.Count == 0)
{
var placeholder = new MeshInstance3D
{
Mesh = new BoxMesh
{
Size = Vector3.One * 0.3f
},
Name = "PlaceholderVisual"
};
var material = new StandardMaterial3D
{
AlbedoColor = Colors.DarkOrchid
};
placeholder.SetSurfaceOverrideMaterial(0, material);
_visualsMountNode.AddChild(placeholder);
_instantiatedVisuals.Add(placeholder);
GameLogger.PushWarn($"DroppedItem '{Name}': Item '{itemDef.ItemId}' hat keine AffectedVisualPartsOnCharacter. Verwende Placeholder.");
return;
}
float horizontalOffsetStep = 0.2f;
int numParts = itemDef.AffectedVisualParts.Count;
float currentXOffset = (numParts > 1) ? -horizontalOffsetStep * (numParts - 1) / 2.0f : 0f;
foreach (VisualAttachmentInfo visualInfo in itemDef.AffectedVisualParts)
{
if (visualInfo?.ItemScene != null)
{
Node visualNode = visualInfo.ItemScene.Instantiate();
_visualsMountNode.AddChild(visualNode);
_instantiatedVisuals.Add(visualNode);
if (visualNode is Node3D visualNode3D && numParts > 1)
{
visualNode3D.Position = new Vector3(currentXOffset, 0, 0);
}
DeactivateSubPhysicsAndInteraction(visualNode);
currentXOffset += horizontalOffsetStep;
}
}
_visualsMountNode.RotateY(Mathf.DegToRad(GD.RandRange(-25, 25)));
}
private void DeactivateSubPhysicsAndInteraction(Node itemPartSceneInstance)
{
var subRb = itemPartSceneInstance.FindChild("PhysicsBody", true, false) as RigidBody3D ?? itemPartSceneInstance as RigidBody3D;
if (subRb != null && subRb != this)
{
subRb.SetProcessMode(ProcessModeEnum.Disabled);
foreach (var shapeOwner in subRb.GetChildren().OfType<CollisionShape3D>())
{
shapeOwner.Disabled = true;
}
}
}
#endregion
#region Signals
private void OnMouseEnteredInteractionArea()
{
var playerNodes = GetTree().GetNodesInGroup("Player");
if (playerNodes.Count > 0 && playerNodes[0] is Player.Player playerScript)
{
playerScript.SetHoveredItem(this);
}
// TODO: Visual Feedback on Item (Highlight)
}
private void OnMouseExitedInteractionArea()
{
var playerNodes = GetTree().GetNodesInGroup("Player");
if (playerNodes.Count > 0 && playerNodes[0] is Player.Player playerScript)
{
playerScript.ClearHoveredItem(this);
}
// TODO: Visual Feedback on Item (Highlight)
}
#endregion
public bool AttemptPickupBy(Player.Player interactingPlayer)
{
if (InstanceData == null || interactingPlayer == null)
{
return false;
}
InventoryComponent playerInventory = interactingPlayer.GetNodeOrNull<InventoryComponent>("InventoryComponent");
if (playerInventory != null && playerInventory.AddItem(Refs.Instance.DefaultContainerId, InstanceData))
{
interactingPlayer.ClearHoveredItem(this, true);
QueueFree();
return true;
}
return false;
}
private async void QueueFreeAfterDelay(float delay = 0.1f)
{
try
{
await ToSignal(GetTree().CreateTimer(delay), Timer.SignalName.Timeout);
if (IsInstanceValid(this))
{
QueueFree();
}
}
catch
{
GameLogger.PushError("ERROR: QueueFreeAfterDelay failed.");
}
}
}


