""" Asset: Godot Simple Portal System File: collision_disable_area.gd Description: An area in which collisions are disabled for physics bodies based on the "disabled_collision_masks" metadata array. Instructions: For detailed documentation, see the README or visit: https://github.com/Donitzo/godot-simple-portal-system Repository: https://github.com/Donitzo/godot-simple-portal-system License: CC0 License """ extends Area3D class_name CollisionDisableArea ## Seconds until collisions are re-enabled after the body leaves the area. @export var re_enable_delay_seconds:float = 0.1 # Info about the disabled bodies var _disables:Array = [] func _ready() -> void: connect("body_entered", _on_body_entered) connect("body_exited", _on_body_exited) func _process(delta:float) -> void: for i in range(_disables.size() - 1, -1, -1): var disable_info:Dictionary = _disables[i] if not is_instance_valid(disable_info.body): # Body has been freed, remove disable info _disables.remove_at(i) continue if not disable_info.left: # The body has yet to leave the area continue # The body left the area, so reduce its timeout before removal disable_info.seconds_until_enable -= delta if disable_info.seconds_until_enable > 0: continue _disables.remove_at(i) # Re-enable collision masks for layer_number in disable_info.disabled_layers: # Only consider layers which were enabled to begin with if disable_info.disable_count.has(layer_number): # Decrement disables so only the final area actually disables the mask disable_info.disable_count[layer_number] -= 1 if disable_info.disable_count[layer_number] == 0: # Final disable, so re-enable the collision mask disable_info.body.set_collision_mask_value(layer_number, true) func _on_body_entered(body:PhysicsBody3D) -> void: if not body.has_meta("disabled_collision_masks"): return # Is the body already disabled by this area? for disable_info in _disables: if disable_info.body == body: # Reset left and timeout disable_info.left = false disable_info.seconds_until_enable = re_enable_delay_seconds return # Keep track of the number of times each collision mask is disabled in a meta field if not body.has_meta("collision_disable_count"): body.set_meta("collision_disable_count", {}) var disable_count:Dictionary = body.get_meta("collision_disable_count") # Disable the collision masks specified in the "disabled_collision_masks" metadata array var disabled_layers:Array = body.get_meta("disabled_collision_masks") for layer_number in disabled_layers: # Only consider layers which were enabled to begin with if disable_count.has(layer_number) or body.get_collision_mask_value(layer_number): # Increment disables so only the first area actually disables the mask disable_count[layer_number] = 1 if not disable_count.has(layer_number) else disable_count[layer_number] + 1 if disable_count[layer_number] == 1: # First disable, so disable the collision mask body.set_collision_mask_value(layer_number, false) # Keep a reference to all current disables in the area _disables.push_back({ "body": body, "disabled_layers": disabled_layers, "disable_count": disable_count, "seconds_until_enable": re_enable_delay_seconds, "left": false, }) func _on_body_exited(body:PhysicsBody3D) -> void: if body.has_meta("disabled_collision_masks"): for disable_info in _disables: if disable_info.body == body: # Mark the body as having left the area disable_info.left = true