#[Solved] "Click Radius" for a clicker-type game. get_overlapping_areas() issue

7 messages · Page 1 of 1 (latest)

modern river
#

I'm working on a learning project, and am trying to make a simple click-based game. Enemies spawn, move from the right to left side of the screen, and you have to Left Click on them to defeat them.

Originally, I was using _on_input_event() on the scripts attached to enemies, so if you click on an enemy sprite, it would get destroyed. Now I want to try to implement a Click Radius around the cursor that could be used instead.

To do this, I create a CollisionShape2D at the left-click location using _input_event() This seems to work okay, as I can see the Collision Area moving on Left Click..

However, it seems like you can't update the collider position and use get_overlapping_areas() in the same step. The First click I do moves the area, and the Second Click I do will destroy the enemies that are in the area before it moves..

The documentation on Area2D says :
this list is modified once during the physics step, not immediately after objects are moved

Area2D[] get_overlapping_areas ( ) const

Returns a list of intersecting Area2Ds. The overlapping area's CollisionObject2D.collision_layer must be part of this area's CollisionObject2D.collision_mask in order to be detected.

For performance reasons (collisions are all processed at the same time) this list is modified once during the physics step, not immediately after objects are moved. Consider using signals instead.

How can I get around this? Is there a better implementation for a "ClickRadius" that I'm missing? I thought about calculating the distances from enemies to the mouse location, but this means I have to calculate the closest sprite boundary etc..

dawn pivot
#

You can use yield for Godot 3 or await for Godot 4 to wait for the next physics frame. Do that right before the call to get_overlapping_areas().

But are you enemies just Area2D's?? You can register clicks from that Area2D instead and then expand its collision shape (probably a circle) to be the desired click radius.

If the enemies aren't Area2D's (though your code suggests otherwise) you can add one to the enemy with a circle collision shape of the desire click radius and register the clicks that way.

modern river
#

Understanding Collision Updates in Godot with CircleShape2D and ShapeCast2D

The Challenge:
I was initially using an Area2D node with a CollisionShape2D to create a dynamic click radius. The plan was straightforward: detect left-clicks using _input(event), move the Area2D to the click position, and use get_overlapping_areas() to apply damage with take_damage() on any overlapping enemies.

However, I encountered a peculiar behavior where the first click wouldn't register any damage, but subsequent clicks would apply damage based on the Area2D's previous position.

The Root Cause:
It turned out that collision overlaps in Godot are not immediately updated upon the position change of Area2D. This is a performance optimization; the physics engine updates collision information on the next physics frame, not the current one.

The Solution:
After some research and experimentation, I found that ShapeCast2D could be used to effectively solve this problem. By using force_shapecast_update(), I was able to immediately update the collision information without waiting for the physics process, allowing for instantaneous detection and response to collisions within the same frame.

I found plenty of other references to the same issue, and even a proposed feature change in the godot engine

#
extends Node2D  # Assuming this is your main scene node

# Reference to the ShapeCast2D node
@onready var shape_cast = $"."

func _input(event):
    if event is InputEventMouseButton and event.pressed:
        shape_cast.shape.radius = PlayerController.get_click_radius()
        shape_cast.position = event.position
        shape_cast.force_shapecast_update()
        handle_collisions()

func handle_collisions():
    if shape_cast.is_colliding():
        var collision_count = shape_cast.get_collision_count()
        for i in range(collision_count):
            var collider = shape_cast.get_collider(i)
            if collider and collider.is_in_group("enemies"):
                handle_enemy(collider)

func handle_enemy(enemy):
    if not enemy.isDeathAnimating:
        enemy.ghost_click_sound.play()
    var click_damage = PlayerController.get_click_damage()
    enemy.take_damage(click_damage)
#

.
Takeaway:
If you need immediate collision detection after moving a shape, ShapeCast2D with force_shapecast_update() might be your answer. It's especially useful when dealing with instant area effects or similar mechanics where waiting for the next physics frame is not an option.

Happy to share this learning experience, and I hope it helps anyone who might be facing similar challenges!

#

[Solved] "Click Radius" for a clicker-type game. (get_overlapping_areas() issue

#

[Solved] "Click Radius" for a clicker-type game. get_overlapping_areas() issue