I’m working on a system that sets the limits of the camera based on the position of the player.
I have implemented this system using the nodes Panel and Area2D: I set the position and the size of the Panel node in the 2d viewport and a script then creates the CollisionShape2D for the Area2D based on the rectangle of the Panel node.
To handle overlapping Panels, each Panel has a priority. The Panel with the highest priority sets the limit for the camera.
I keep track of the CameraLimiters that contain the player using the
variable contains_player. This bool gets set in the _on_area_body_entered and _on_area_body_exited methods. Here’s the problem: I can’t rely on the order of the signals body_entered and body_exited. For example, when the player exits one Area2D and enters another, the body_entered signal of one Area2D fires before the body_exited of the other Area2D. Thus, the script wrongly concludes that there are two Area2Ds where contains_player is true.
How can I fix this behaviour? I want the _on_area_body_exited function of all CameraLimiter nodes to execute before the _on_area_body_entered of any CameraLimter.
class_name CameraLimiter
extends Panel
@export var priority := 0
var contains_player: bool
func _ready() -> void:
area.body_entered.connect(_on_area_body_entered)
area.body_exited.connect(_on_area_body_exited)
var rectangle_shape := RectangleShape2D.new()
rectangle_shape.size = size
collision_shape.shape = rectangle_shape
collision_shape.position = size / 2
func _on_area_body_entered(body: Node2D) -> void:
contains_player = true
for camera_limiter: CameraLimiter in get_tree().get_nodes_in_group("camera_limiter"):
if not camera_limiter.contains_player:
continue
if camera_limiter.priority > max_priority:
max_priority = camera_limiter.priority
camera_limiter_with_max_priority = camera_limiter
func _on_area_body_exited(body: Node2D) -> void:
contains_player = false
Thanks a lot for your reply but this solution does not work for me. It doesn’t pdate the camera bounds as I would like. The smaller rect doesn’t get enabled when I spawn the player in it…
I guess I have to call the update_camera_limits function every frame for now…
func _process(delta: float) -> void:
update_camera_limits()
func update_camera_limits() -> void:
var max_priority := -INF
var camera_limiter_with_max_priority: CameraLimiter
var overlapping_areas_count := 0
for camera_limiter: CameraLimiter in get_tree().get_nodes_in_group("camera_limiter"):
var overlapping_bodies := camera_limiter.area.get_overlapping_bodies()
if overlapping_bodies.is_empty():
continue
if camera_limiter.priority > max_priority:
max_priority = camera_limiter.priority
camera_limiter_with_max_priority = camera_limiter
var game_camera := get_tree().get_first_node_in_group("game_camera") as GameCamera
if game_camera == null:
return
game_camera.update_limits(camera_limiter_with_max_priority.get_rect2())
I thought about this some more. The image you posted is good info. I see now that you have areas within areas.
Here’s another idea: use a priority queue that updates when a new area has entered.
This is similar to what you originally posted. But control is inverted. Instead of having the camera limiter itself choose who is in control, we have an external object that keeps a sorted queue of all the areas that the body is currently inside.
The “current” area will be the area with the highest priority and will remain current until an area with a higher priority has been entered or the current area has been exited.
Areas with the same priority are sorted with the earliest added towards the front.
Try adapting this to your CameraLimiter use case.
signal current_changed(area)
var queue = []
func get_current():
return queue[0] if queue.size() > 0 else null
func _ready() -> void:
for area: Area2D in get_tree().get_nodes_in_group('area'):
area.body_entered.connect(update_queue.bind(true, area))
area.body_exited.connect(update_queue.bind(false, area))
func sort_queue(a: Area2D, b: Area2D) -> bool:
if a.priority == b.priority:
return false # oldest first, stable for sample size.
return a.priority > b.priority
func update_queue(body: Node2D, entered: bool, area: Area2D) -> void:
var previous = get_current()
if entered:
queue.append(area)
queue.sort_custom(sort_queue)
else: # exited
queue.erase(area)
if previous != get_current():
current_changed.emit(get_current())
Notes
Array.sorts algorithm as described in the docs is not stable. You will most likely need to customize the sorting to get the desired behavior. The sort_queue comparator has stable results, but if you change return false to return true i.e. sort the same priority by most recent first, is not stable.