Godot Version 4.6
v4.6.dev1.mono.official [8d8041bd4]
Question
I have a setup, where jumping on objects calls a behavior_drop_loot component, that in turn spawns the drops, with an initial velocity to have a effect of items exploding out of the parent. But a lot of times it doesn’t work, and the spawned objects are static on screen.
When inspecting these bugged objects in the runtime inspector, none of them are frozen or sleeping, and they do have velocity, sometimes very high (like 4k px/s), which wouldn’t be possible valuewise given how I set this initial velocity.
I spent the last 5 hours slamming my head against Gemini, Claude and ChatGPT, without avail.
I use resources to initialize generic Object Container that hold platforms, items and all other objects.
The container has an area2d used to trigger behaviours. Before I had it so that the objectdata resource for a big rock would contain the logic for how it’d be destroyed and spawns stones, but I wanted to refactor this and switch to a composition based system, where I now have behaviour scripts, that get loaded and attached to dynamically created nodes in the container tree at runtime.
I use an ObjectSpawner for spawning containers, which then calls the init_object() function on the container, which also initializes the components from the behaviour scripts I assigned.
The container then simply loops though the components and executes their on_collision_behavior().
Sounds straight forward enough, but for some reason it broke my system that was working before I put the logic into components
The fact that the velocity values are so crazy while the objects aren’t moving at all is a mystery to me. I also don’t have any process functions that would hard-override the position… no custom integrator, or anything like that.
Here’s the function in the behaviour component that’s called by the container of a rock to spawn stones:
func on_collision_behavior(body: Node2D) -> void:
if not body.is_in_group("Player"):
return
var inventory:InventoryComponent = body.find_child("InventoryComponent",true)
var failed := false
for tool in required_tools:
if not inventory.has_item_type(tool):
failed = true
if failed:
print("Missing tools")
pass #cancel the function, nothing happens?
else:
call_deferred("deferred_spawn")
#implement calls to ObjectSpawner, in order to spawn small rocks when you break a big one
func deferred_spawn() -> void:
var resource_node_state = parent_container.state as ObjectStateResourceNode
resource_node_state.hits += 1
if resource_node_state.hits >= required_hits:
var count = randi_range(child_spawn_range.x,child_spawn_range.y)
var rng = RandomNumberGenerator.new()
for i in range(count):
var rnd_x = rng.randf_range(-1.0,1.0)
var rnd_y = rng.randf_range(0.5,1.0)
var rnd_vec := Vector2(rnd_x,rnd_y).normalized()*velocity_multiplier
"""ToDo: Right now just a random pick is done. Should also be handled via spawntable eventually"""
var newcont = ObjectSpawner.spawn_world(parent_container.get_parent(),child_spawns.pick_random(),1,parent_container.to_global(parent_container.position),Vector2(1,0))
await get_tree().physics_frame
parent_container.destroy()
And here’s the spawn_world function of the ObjectSpawner
static func spawn_world(parent:Node, object_data:ObjectData, count:int, position:Vector2 = Vector2(0,0), velocity:Vector2 = Vector2(0,0), container:PackedScene = container_scene ) -> ObjectContainer:
var new_state_inst = create_new_object_state(object_data,count,container)
#retrieve objectData from state
#var obj_data = new_state_inst.object_data
#if parent is another container
if parent.get_script().get_global_name() == "ObjectContainer":
#jetzt die data und checken ob es eine platform ist
var parent_platform_size
var parent_cast = parent as ObjectContainer
var parent_obj_data = parent_cast.data
var parent_obj_data_cast = parent_obj_data as ObjectDataPlatforms
print(parent_obj_data_cast.platform_size)
if parent_obj_data_cast: #if it's really a platform
parent_platform_size = parent_obj_data_cast.platform_size # store its size
if parent_platform_size == ObjectDataPlatforms.PlatformSize.SMALL:
#now cast the object_data argument to a ResourceNode, as this class has the can_spawn_on_small var
var obj_data_cast = object_data as ObjectDataResourceNodes
if obj_data_cast:
if obj_data_cast.spawn_on_small == false:
#don't spawn!
return null
var cont:ObjectContainer = new_state_inst.container_scene.instantiate()
# if a velocity is provided, unfreeze the container
if velocity:
cont.freeze = false
cont.sleeping = false
#cont.apply_central_impulse(velocity * cont.mass)
else:
cont.freeze = true
if parent is Node2D:
cont.position = parent.to_local(position)
else:
cont.position = position
parent.add_child(cont)
cont.init_object(new_state_inst)
#initializes the container using the state (and its ref to ObjectData)
#set position
#as spawn_world creates a real object in the world via the ObjectContainer, it's state needs to be "realized", i.E. the current
#container is stored in the state... right now it just can be an ObjectContainer, which is just the realworld representation,
#but maybe it could also be inventory?
new_state_inst.realize_instance(position,cont) #sets position as it was spawned and the container it was spawned "in"
#this way, the container can be deleted along with the state instance if an object is removed from the world
cont.state = new_state_inst # attach unique state copy to the container
return cont
Here’s the ObjectContainer:
extends RigidBody2D
class_name ObjectContainer
@onready var sprite_2d: Sprite2D = $Sprite2D
@onready var collision_shape_container: CollisionShape2D = $CollisionShape2D
@onready var collision_shape_area2d: CollisionShape2D = $Area2D/CollisionShape2D_Area
@onready var area_2d: Area2D = $Area2D
@onready var light_occluder = $LightOccluder2D
@export var data: ObjectData
#@export var pickup_enabled:bool = false
##should get initialized by the spawner. The spawner spawns the Container, and attaches the unique state to it, to be fetched
##later when the object is picked up
@export var state: ObjectState
##If true, an item will get attracted by the player after the follow_player_delay (const)
@export var attracted_by_player:bool = false
@onready var player:CharacterBody2D = get_tree().get_first_node_in_group("Player")
var behaviour_components:Array[Node]
#init an object in the world, needs the particular state
func init_object(state:ObjectState):
var platform_data = data as ObjectDataPlatforms
if platform_data:
light_occluder.occluder.polygon = platform_data.light_occluder
freeze_mode =RigidBody2D.FREEZE_MODE_KINEMATIC #platforms, items etc. should always be animatable
data = state.object_data
assert(data!=null, String(self.name)+" init object failed as ObjectData = null")
sprite_2d.texture = data.texture
sprite_2d.scale *= data.scale
collision_shape_container.scale *= data.scale
collision_shape_area2d.scale *= data.scale
#Collision Configuration
self.collision_layer = 0
self.set_collision_layer_value( data.col_layer,true) #sets the container to the respective collision layer set in the ObjectData
area_2d.collision_layer = 0
area_2d.set_collision_layer_value(data.col_layer,true)#same for the area2d
#Physics Collision Setup
#setup shape
collision_shape_container.shape = data.collision_shape
collision_shape_container.position += data.collision_offset
#setup masks and layers
if not data.col_switch:
collision_shape_container.disabled = true
else: #if collision is activated
if not data.col_player:
#var player = get_tree().get_first_node_in_group("Player")
self.add_collision_exception_with(player)
#Area2D collision setup
collision_shape_area2d.shape = data.collision_shape
collision_shape_area2d.position = data.collision_offset
self.data = data
self.state = state
#var pickup_data = data as ObjectDataPickups
#if pickup_data:
#await get_tree().create_timer(pickup_data.pickup_delay).timeout
#pickup_enabled = true
##Setting up the Behaviour Manager
var behaviour_manager := self.get_node("BehaviourComponentManager") as BehaviourComponentManager
if behaviour_manager:
behaviour_components = behaviour_manager.init_behaviours(data.behaviour_scripts,state.dynamic_behaviours)
func destroy() -> void:
self.queue_free()
func _process(delta: float) -> void:
##ToDo: This stuff should be outsourced to a component as well.
#var pickup_data = data as ObjectDataPickups
#if pickup_data:
#if attracted_by_player:
#if _following:
#pickup_enabled = true #override the timer state
#var vector:Vector2 = player.global_position - self.global_position
#self.linear_velocity = vector.normalized()*follow_player_speed
#var thresh:float = 50
#if vector.length() < thresh:
#data.on_collision_behavior(player,self)
#print("express pickup")
pass
func _on_area_2d_body_entered(body: Node2D) -> void:
for component in behaviour_components:
component.on_collision_behavior(body)
Please help ![]()