Area3D's as Hitboxes Registering Late

Godot Version

v4.5.stable.official

Question

In my journey to make a platform fighter with rollback using Godot (novel idea I know), I have encountered the issue of physics bodies registering late. With code (shown at the end) I am spawning hitboxes using call_deferred() and then connecting their signals directly to a hitbox collision function.

The issue lies in how the hitboxes (Area3D’s) register a hit one frame after colliding with the opponent’s hurtbox (Area3D’s). I am aware that it takes one whole physics step to reach the internal server and update collision, but I am really unsure of how to handle that. I thought perhaps call_deferred() would fix the issue by spawning the Area3D before the next physics step, but that hasn’t changed the outcome of late hitbox registration.

Am I approaching this wrong? Is there anyone who has made a fighting game with Godot that has found a way around these issues?

(P.S. If you’re going to comment about Shapecast3D’s, I have tried that as well. They don’t even allign with the player on the first frame! At least the Area3D’s were in the right place. And forcing an update with their built in function doesn’t seem to work the way I’d expect)

Going to organize the code tree by what I feel is most relevant first:

Some final notes about my code structure: I have my custom State Machine class running inside physics_process(). Each character will extend this class with their own custom implementation of a State Machine. State Machine is just a node I attached underneath the parent node of the character so it can access its parent’s attributes (speed, frame data, etc.) to perform the right actions within each state.



If you made it this far, thank you very much for taking the time to read. I’m sure you saw a couple funny comments inside my code (gotta keep everything well documented!) I want to make this post before I give up on using the built in functionality of Godot. I’m not opposed to coding my own AABB system, but it feels like reinventing the wheel especially when Godot has these built in features. Let me know what you think!

1 Like

call_deferred would add a frame of delay (more like process at the end of current frame) to what ever the called function is.

You may have faster results using PhysicsDirectSpaceState, such as get_rest_info, though it can be more complicated to use physics RIDs, it’s probably close to how your are creating hitboxe shapes on the fly.


Make sure to paste code instead of screenshots

1 Like

Thank you for the recommendation!! I’ll give this a shot as soon as I’m off work and see.
Also yeah pasting the code would be more elegant sorry this is my first post

So… It works. The output is long, and it is going to be hard to parse, but it works on the same frame the areas are spawned in (detects both the hitboxes and hurtboxes).

This is the output:

[{ "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 1 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 3 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 5 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 6 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 7 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 8 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 9 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 0 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 1 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 0 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 2 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 3 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 4 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 5 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 6 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 7 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 8 }, { "rid": RID(4973572128770), "collider_id": 47445968469, "collider": Hurtbox:<Area3D#47445968469>, "shape": 9 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 2 }, { "rid": RID(4621384810496), "collider_id": 46758102582, "collider": Hurtbox:<Area3D#46758102582>, "shape": 4 }, { "rid": RID(38654705664009), "collider_id": 91033175751, "collider": Hitbox1:<Area3D#91033175751>, "shape": 0 }, { "rid": RID(38684770435082), "collider_id": 91083507402, "collider": Hitbox2:<Area3D#91083507402>, "shape": 0 }]

I am severely concerned with the overlapping of dictionary entries though. Take a look at how {“shape” : index } repeats the same numbers for different entries inside the field. I was planning on using their RID’s anyway to identify the original Area and parse what should happen from there, but my concern remains.

It almost feels like PhysicsShapeQueryParameters is a forgotten and abandoned part of the engine. It has exclusively rudimentary setters and getters, little documentation, and I had to change the layer masks with a 32bit 0xbbbbbbbb etc. binary value instead of set_collision_mask_value()) But it works. And I might just try it out. Thank you for the pointer (heh) in this direction!

Returning with more details for the sake of being thorough.
This is the function for creating a PhysicsShapeQueryParameters3D and checking its collisions later on in the code:

@onready var world = get_world_3d()
@onready var directSpaceState = world.get_direct_space_state()

var physicsShape = PhysicsShapeQueryParameters3D.new()


func createPhysicsShapeQueryParameters3D():

	var shape = CapsuleShape3D.new()
	shape.height = 15
	shape.radius = 7
	
	
	physicsShape.set_shape(shape)
#Self is alligned with the player's 0x,0y,0z position here
	physicsShape.set_transform(Transform3D(Vector3(1,0,0),Vector3(0,1,0), Vector3(0,0,1), Vector3(self.position.x, self.position.y, self.position.z)))
	physicsShape.collide_with_areas = true
#I have my hitboxes set to mask layer 5, and hurtboxes set to mask layer 6
	physicsShape.set_collision_mask(0b00000000_00000000_00000000_00110000)

#Later in the script
func buttonPressed():

#This is how I'm getting the results of the above post. NOT using get_rest_info(), but instead intersect_shape() with my physicsShape and my object collision limit being 200
	var print = directSpaceState.intersect_shape(physicsShape, 200)
	print(print)

I am actually now moving on to a 2D physics system that will render in 3D, so this code will be a bit obsolete (mainly the Transforms) but still a useful resource for anyone unfamiliar with the PhysicsShapeQueryParameters3D type. I wouldn’t blame you! It’s confusing!