Godot Version
Godot 4.3
Question
I’m building a 2D platformer, where a character can grab, carry, and release an object (similar to Chip 'n Dale: Rescue Rangers on the Nintendo Entertainment System). I’m encountering strange behavior though.
In this GIF, you can see the player character has a short red line projecting outwards. The line turns green when an object that can be grabbed is within reach. The object in this case has its CollisionShape2D
visible: a 16 x 16 RectangleShape2D
.
The player character is able to grab and carry the object, then release it. But oddly, even though the CollisionShape2D
appears to move, I don’t think it actually does. The object stays put when released, and then when the player character walks back to where the object originally was, a collision occurs. Notice how the line turns green again, and the player character can stand on seemingly nothing. The player character can then pick the object back up from that original position, and the object will reposition back to being above him (not pictured).
My scene hierarchy is this:
TestScene (Node2D)
↳ Player (CharacterBody2D)
|↳ CollisionShape2D (CollisionShape2D)
|↳ AnimatedSprite2D (AnimatedSprite2D)
|↳ GrabRaycast (Raycast2D)
| ↳ Line2D (Line2D)
↳ GrabbableObject (Rigidbody2D)
↳ Sprite2D (Sprite2D)
↳ CollisionShape2D (CollisionShape2D)
And here’s the code on the Player
:
using Godot;
public partial class CharacterMovement : CharacterBody2D {
private RigidBody2D grabbedObject;
[Export] private RayCast2D grabRaycast; // Raycast object attached to the player
[Export] private int grabbableLayer = 2;
[Export] private Vector2 grabOffset = new Vector2(0, -16);
// Truncated to remove irrelevant properties and methods
#region Grab Objects
// Detect and grab an object — called manually by the player
private void Grab() {
// If nothing is within reach, cancel the grab function
if (!grabRaycast.IsColliding()) {
GD.Print("Nothing within reach.");
return;
}
Node potentialGrab = grabRaycast.GetCollider() as Node;
// Traverse up the node tree to find the RigidBody2D parent
while (potentialGrab != null && !(potentialGrab is RigidBody2D)) {
potentialGrab = potentialGrab.GetParent();
}
if (potentialGrab is not RigidBody2D rigidBody || rigidBody.GetCollisionLayerValue(grabbableLayer) == false) {
GD.Print("Not a grabbable object.");
return; // Not a grabbable RigidBody2D
}
grabbedObject = rigidBody;
GD.Print($"Grabbed {grabbedObject.Name}.");
}
// Release the object — called manually by the player
private void Release() {
grabbedObject = null;
}
// Carry the grabbed object — called during _PhysicsProcess
private void CarryGrabbedObject() {
if (grabbedObject is null) return;
grabbedObject.Position = Position + grabOffset;
}
#endregion Grab Objects
}
The GD.Print
lines confirm that the GrabbableObject
is being grabbed. However, in attempting to debug this, I’m encountering even weirder behavior. I stuck this code into _Process
so I could confirm the positions of the object and its collider.
if (grabbedObject is not null) {
GD.Print($"{grabbedObject.Name} position: {grabbedObject.Position}.");
GD.Print($"Collison shape position: {collisionShape.Position}.");
}
The behaviour remained the same, and the collisionShape
had its local Position
read as 0, 0
, which is expected. But then I altered it to check for its GlobalPosition
.
if (grabbedObject is not null) {
GD.Print($"{grabbedObject.Name} position: {grabbedObject.Position}.");
GD.Print($"Collison shape position: {collisionShape.GlobalPosition}.");
}
The number of the collisionShape.GlobalPosition
matched the grabbedObject.Position
. But that’s not the weird part. Just checking its values somehow changed the behavior of the object. Look:
The object is moving differently, and the collision is occurring in different places. Switching from reading the local to global position and back will alter its behavior.
Is this a Godot bug? I’m really confused now. I would appreciate insight on why this is happening, and how to get this behavior working as intended.