I can't pick up my loot if the camera doesn't move

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.");
        }
    }
}

It was the mouse filter that intercepted all clicks in some UI.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.