0Neji
0Neji

Reputation: 1128

Why does my Godot RigidBody3D not keep it's frozen position relative to parent?

I'm putting together a football/soccer match engine in Godot 4.3 as a learning exercise and I've run into a problem trying to freeze/attach a the ball to one of the players. Here's what happens:

  1. Players move to their position
  2. Once players are in position one of them takes the kick off and passes the ball to another player behind them
  3. The player is a CharacterBody3d with an Area3D around their feet. The ball enters the area and the idea is that it "sticks" to the player in the exact position it enters the Area3d.
  4. The player should then run forward with the ball which should be frozen at point of entering Area3D.

What actually happens is that the ball gets frozen as expected but as soon as the player moves forward, the ball shifts position - it doesn't stay in front of the player.

I've debugged this, even with a sleep timer and the freeze looks to work correctly. I had half expected the physics to not have been complete so it shifted but the sleep timer discounts that theory - I think.

To demonstrate the problem here are a couple of images:

1.The point the ball enters the Area3D and "freezes" The point the ball enters the Area3D and "freezes"

  1. GIF showing the ball "shift" once the player moves. Please view at Imgur: https://imgur.com/DrSZstZ

The on body entered method looks like this:

public void OnFeetEntered(Node3D body)
{
    if(CurrentState == PlayerState.KickingOff || CurrentState == PlayerState.MovingToPosition)
    {
        return;
    }

    if (body is Ball ball)
    {
        HasBall = true;
        CurrentState = PlayerState.JustTakenBall;

        ball.LinearVelocity = Vector3.Zero;
        ball.AngularVelocity = Vector3.Zero;
        ball.FreezeMode = RigidBody3D.FreezeModeEnum.Kinematic;
        ball.Freeze = true;

        ball.GlobalPosition = ball.GlobalPosition;
        var localPosition = ToLocal(ball.GlobalPosition);

        AddChild(ball);
        
        ball.Position = localPosition;
    }
}

And this is how the player is told to move (target position is hard-coded to Vector3(-10.0f, 0.252f, -8.0f) for testing purposes):

private void MoveToTarget(Vector3 targetPosition)
{
    Vector3 direction = GlobalPosition.DirectionTo(targetPosition);

    float distance = GlobalPosition.DistanceTo(targetPosition);

    if (distance > stopThreshold)
    {
        Velocity = direction * Speed;

        MoveAndSlide();
    }
    else
    {
        Velocity = Vector3.Zero; // Stop movement

        CurrentState = PlayerState.Idle;

        Look();
    }
}

So what am I doing wrong here? I just want that ball to stay at the position it enters the player when that player moves forward or rotates.

EDIT

Thanks to @liggiorgio for pointing out that I need to remove the parent before calling AddChild.

I've done this now but I get another problem. Now when I run the game and try to move the player forward, the game crashes with a stack overflow error:

Exception of type 'System.StackOverflowException' was thrown.

There's no more information present, no inner exception or anything very helpful.

Image of error

What might be causing this? I'm at a bit of a loss!

EDIT 2

The second issue I ran into was simple recursion, I didn't appreciate that the on entered method would be called repeatedly. This was causing the parent to get removed and added in an infinite loop causing the stack overflow exception. My player is now happily moving forward with the ball in the correct position!

Upvotes: 1

Views: 63

Answers (1)

liggiorgio
liggiorgio

Reputation: 325

You can't use AddChild() on nodes that already have a parent, otherwise the method will fail. Instead, you should reparent the ball Node to your player Node:

public void OnFeetEntered(Node3D body)
{
    //...
    if (body is Ball ball)
    {
        //...
        ball.Reparent(self);
        //...
    }
}

And revert the parenting relationship when a player throws the ball away:

public void KickBall(...)
{
    if (!HasBall) return;

    // Assuming `ball` and `ball_parent` exist
    ball.Reparent(ball_parent);
}

The optional argument keep_global_transform of function Node.Reparent() defaults to true, which is what you want: the ball changes parent but its absolute position stays the same.

Upvotes: 3

Related Questions