<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joeri's blog]]></title><description><![CDATA[Software Developer, learning Godot]]></description><link>https://joeridamme.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 30 Aug 2026 19:03:22 GMT</lastBuildDate><atom:link href="https://joeridamme.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Godot 4: Detect body collision before adding Node to scene in a 2D game (part 2).]]></title><description><![CDATA[Introduction
In my previous blog post, I explained how we can detect two overlapping objects before making it visible in the scene. I achieved this with an Area2D and a CollisionShape2D. However, this was not the ideal solution I was aiming for, as i...]]></description><link>https://joeridamme.hashnode.dev/godot-4-detect-body-collision-before-adding-node-to-scene-part-2</link><guid isPermaLink="true">https://joeridamme.hashnode.dev/godot-4-detect-body-collision-before-adding-node-to-scene-part-2</guid><category><![CDATA[Godot]]></category><category><![CDATA[Godot 4]]></category><category><![CDATA[gdscript]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Joeri Damme]]></dc:creator><pubDate>Fri, 08 Dec 2023 14:45:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702048723492/f6b97fec-80df-4acf-9373-1218e6cb5dc2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>In my <a target="_blank" href="https://joeridamme.hashnode.dev/godot-4-detect-body-collision-before-adding-node-to-scene-part-1">previous blog post</a>, I explained how we can detect two overlapping objects before making it visible in the scene. I achieved this with an <code>Area2D</code> and a <code>CollisionShape2D</code>. However, this was not the ideal solution I was aiming for, as it still required adding an invisible object to the scene and working with signals and <code>await</code> to wait for the first frame to be rendered in order to detect a collision.</p>
<p>After some Googling, I found a different (and in my opinion, a better) solution by making use of the <code>PhysicsDirectSpaceState2D</code> class and performing a query with the <code>PhysicsShapeQueryParameters2D</code> class.</p>
<p>The <a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_physicsdirectspacestate2d.html">PhysicsDirectSpace2D</a> class has the following description:</p>
<blockquote>
<p>Provides direct access to a physics space in the PhysicsServer2D. It's used mainly to do queries against objects and areas residing in a given space.</p>
</blockquote>
<p>So we can do queries on a 2D space. That's interesting. So how do we actually do queries? The PhysicsDirectSpace2D class has a function <code>intersect_shape</code> (<a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_physicsdirectspacestate2d.html#class-physicsdirectspacestate2d-method-intersect-shape">docs</a>), which requires as first parameters an instance of the class <a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_physicsshapequeryparameters2d.html">PhysicsShapeQueryParameters2D</a> :</p>
<blockquote>
<p>By changing various properties of this object, such as the shape, you can configure the parameters for <a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_physicsdirectspacestate2d.html#class-physicsdirectspacestate2d-method-intersect-shape">PhysicsDirectSpaceState2D.intersect_shape</a>.</p>
</blockquote>
<p>Ok, let's see how this works.</p>
<h1 id="heading-the-good-solution-2-making-queries-on-the-physicsdirectspace2d">The good solution 2: Making queries on the PhysicsDirectSpace2D</h1>
<p>Let's clean up the project first by removing unnecessary code and go back to a state where we programmatically add a planet again. When we run the game, we will once again see two planets, but without the <code>SpawnBoundary</code> nodes from the previous solution:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701702184064/2adb3152-c681-4b0b-b0a0-fe02dbca243d.png" alt class="image--center mx-auto" /></p>
<p>Let's attempt to use the <code>PhysicsDirectSpaceState2D</code> class in the Game scene script:</p>
<ol>
<li>First get the Space state by using <code>var space_state = get_world_2d().direct_space_state</code>. This returns an instance of <code>PhysicsDirectSpaceState2D</code>:</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

    <span class="hljs-comment"># Detect other objects in Space</span>

    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state
</code></pre>
<ol>
<li>Now, we want to perform a query on the space state using the <code>intersect_shape</code> function. Remember that the first parameter is an instance of the <code>PhysicsShapeQueryParameters2D</code> class. So, let's create that first:</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

    <span class="hljs-comment"># Detect other objects in Space</span>

    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># 2. Create the PhysicsShapeQueryParameters2D instance</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()
</code></pre>
<ol>
<li>Now, we want to define two things: the shape [<a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_physicsshapequeryparameters2d.html#class-physicsshapequeryparameters2d-property-shape">docs</a>] we want to use to query the space state—in our case, a circle—and also the origin of the shape, indicating where we want to place the circle to determine what is inside it. We can use the transform [<a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_physicsshapequeryparameters2d.html#class-physicsshapequeryparameters2d-property-transform">docs</a>] property for this. The transform property is a <code>Transform2D</code> object, which contains the <code>origin</code> property [<a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_transform2d.html#class-transform2d-property-origin">docs</a>]. Let's start by creating the shape. I'm using a radius of 200 pixels::</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

    <span class="hljs-comment"># Detect other objects in Space</span>

    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># 2. Create the PhysicsShapeQueryParameters2D instance</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()

    <span class="hljs-comment"># 3. Create the Circle shape</span>
    var shape = CircleShape2D.new()
    shape.radius = <span class="hljs-number">200</span>
</code></pre>
<ol>
<li>Now we are gonna set the properties for the <code>PhysicsShapeQueryParameters2D</code> instance:</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

    <span class="hljs-comment"># Detect other objects in Space</span>

    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># 2. Create the PhysicsShapeQueryParameters2D instance</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()

    <span class="hljs-comment"># 3. Create the Circle shape</span>
    var shape = CircleShape2D.new()
    shape.radius = <span class="hljs-number">200</span>

    <span class="hljs-comment"># 4. Set the shape and origin</span>
    shape_query_params.shape = shape
    shape_query_params.transform.origin = Vector2(<span class="hljs-number">500</span>, <span class="hljs-number">300</span>)
</code></pre>
<ol>
<li>Let's now execute the query on the space state and print the results:</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

    <span class="hljs-comment"># Detect other objects in Space</span>

    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># 2. Create the PhysicsShapeQueryParameters2D instance</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()

    <span class="hljs-comment"># 3. Create the Circle shape</span>
    var shape = CircleShape2D.new()
    shape.radius = <span class="hljs-number">200</span>

    <span class="hljs-comment"># 4. Set the shape and origin</span>
    shape_query_params.shape = shape
    shape_query_params.transform.origin = Vector2(<span class="hljs-number">500</span>, <span class="hljs-number">300</span>)

    <span class="hljs-comment"># 5. Perform the query</span>
    var results = space_state.intersect_shape(shape_query_params)
    print(results)
</code></pre>
<ol>
<li>For debugging purposes, let's draw an <code>Area2D</code> node with a <code>CollisionShape2D</code> child node on top of the CircleShape2D:</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

    <span class="hljs-comment"># Detect other objects in Space</span>

    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># 2. Create the PhysicsShapeQueryParameters2D instance</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()

    <span class="hljs-comment"># 3. Create the Circle shape</span>
    var shape = CircleShape2D.new()
    shape.radius = <span class="hljs-number">200</span>

    <span class="hljs-comment"># 4. Set the shape and origin</span>
    shape_query_params.shape = shape
    shape_query_params.transform.origin = Vector2(<span class="hljs-number">500</span>, <span class="hljs-number">300</span>)

    <span class="hljs-comment"># 5. Perform the query</span>
    var results = space_state.intersect_shape(shape_query_params)
    print(results)

    <span class="hljs-comment"># 6. Debug</span>
    var area_2d = Area2D.new()
    area_2d.position = shape_query_params.transform.origin

    var collision_shape_2d = CollisionShape2D.new()
    var collision_shape = CircleShape2D.new()
    collision_shape.radius = shape.radius
    collision_shape_2d.shape = collision_shape

    area_2d.add_child(collision_shape_2d)
    add_child(area_2d)
</code></pre>
<ol>
<li>Be sure that in the debug menu you have enabled 'Visible Collision Shapes'. Now let's run the game!</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701811402271/c357d1bd-5617-4914-b671-5c329a76ad62.png" alt class="image--center mx-auto" /></p>
<p>We will observe two planets intersecting with the query that we performed on the space state. Remember that the <code>CollisionShape2D</code> is just for demonstration purposes to indicate the part where we execute the query. If we examine the output, we observe the following result (in a more organized format):</p>
<pre><code class="lang-python">[
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2675764625408</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26508002450</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Planet"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26508002450&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   },
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2774548873217</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26675774619</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"@CharacterBody2D@2"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26675774619&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   }
]
</code></pre>
<p>Exactly as expected, we see that the query returned two results. Now let's move the second planet a bit more to the right by changing the <code>Vector2</code> coordinates to 800, 300:</p>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">800</span>, <span class="hljs-number">300</span>)

    add_child(second_planet)

<span class="hljs-comment"># ...other code</span>
</code></pre>
<p>Now let's run the game again:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701814428649/d7a8d9a9-8d72-4c3c-ae29-bd13ea070f40.png" alt class="image--center mx-auto" /></p>
<p>The query is intersecting now only one planet. We also see this in the output:</p>
<pre><code class="lang-python">[
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2675764625408</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26508002450</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Planet"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26508002450&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   }
]
</code></pre>
<p>Great! It seems to work.</p>
<h1 id="heading-filtering-the-collisions">Filtering the collisions</h1>
<p>For my game, I want to detect if there is a planet nearby. However, with the current script, it will also detect other areas or bodies. Let's demonstrate that by creating a new scene, "Player," which is just a basic setup of a <code>CharacterBody2D</code>, a <code>Sprite2D</code>, and a <code>CollisionShape2D</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702024566972/55439f27-94bf-4ab6-9f34-55569967769a.png" alt class="image--center mx-auto" /></p>
<p>Now let's link the new Player scene to the game scene and place it close to the planet:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702024944124/0f46ac33-8e4c-4221-99b4-b922b394f36e.png" alt class="image--center mx-auto" /></p>
<p>Let's run the game and see what the output will say:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702024970379/d8d66bca-4914-4373-b365-3351be2936c7.png" alt class="image--center mx-auto" /></p>
<p>Output:</p>
<pre><code class="lang-python">[
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2692944494592</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26860323986</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Planet"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26860323986&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   },
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2710124363777</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26910655639</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Player"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26910655639&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   }
]
</code></pre>
<p>As you can see, it now also collides with the Player node. Therefore, we need to filter this array and only return the planets. The result is, in fact, an array with dictionaries containing a <code>collider</code> key, representing the colliding object. All 2D nodes inherit the <code>Node</code> class, allowing us to utilize <a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_node.html#class-node">all methods</a> within that class. The method we are going to use is <code>is_in_group()</code>, which checks if an object belongs to a group. First, we need to ensure that the planets belong to a group. Afterward, we <a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_array.html#class-array-method-filter">filter</a> the array based on the group.</p>
<ol>
<li>Let's open the Planet scene and add the scene to the "Planets" group:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702026118790/da3393ce-57eb-4506-beca-3a2c00660080.png" alt class="image--center mx-auto" /></p>
<ol>
<li>Open the Game script. Change the following code at step #5:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment"># ...</span>

<span class="hljs-comment"># 5. Perform the query</span>
var results = space_state.intersect_shape(shape_query_params)
print(results)

var filtered_array = results.filter(
    func(collision_object):
        <span class="hljs-keyword">return</span> collision_object.collider.is_in_group(<span class="hljs-string">"Planets"</span>)
)
print(filtered_array)
<span class="hljs-comment"># ...</span>
</code></pre>
<ol>
<li>Run the game...</li>
</ol>
<p>We observe two distinct outputs: one before and one after filtering the array:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Before filtering...</span>
[
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2692944494592</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26860323986</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Planet"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26860323986&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   },
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2710124363777</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26910655639</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Player"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26910655639&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   }
]

<span class="hljs-comment"># After filtering...</span>
[
   {
      <span class="hljs-string">"rid"</span>:RID(<span class="hljs-number">2692944494592</span>),
      <span class="hljs-string">"collider_id"</span>:<span class="hljs-number">26860323986</span>,
      <span class="hljs-string">"collider"</span>:<span class="hljs-string">"Planet"</span>:&lt;CharacterBody2D<span class="hljs-comment">#26860323986&gt;,</span>
      <span class="hljs-string">"shape"</span>:<span class="hljs-number">0</span>
   }
]
</code></pre>
<p>In the event that no planets are within the vicinity, an empty array [] will be returned. To determine the array's length, we can employ the size() method.</p>
<p>Armed with this understanding, let's bring everything together. I've reorganized some elements and introduced the Vector2 variable check_position. In instances where no planets are detected at that position, a new planet will be added to the scene:</p>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># 0. Location of new planet:</span>
    var check_position: Vector2 = Vector2(<span class="hljs-number">800</span>, <span class="hljs-number">300</span>)

    <span class="hljs-comment"># Detect other objects in Space</span>
    <span class="hljs-comment"># 1. Get the PhysicsDirectSpaceState2D</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># 2. Create the PhysicsShapeQueryParameters2D instance</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()

    <span class="hljs-comment"># 3. Create the Circle shape</span>
    var shape = CircleShape2D.new()
    shape.radius = <span class="hljs-number">200</span>

    <span class="hljs-comment"># 4. Set the shape and origin</span>
    shape_query_params.shape = shape

    <span class="hljs-comment"># Check space around location of new planet</span>
    shape_query_params.transform.origin = check_position

    <span class="hljs-comment"># 5. Perform the query</span>
    var results = space_state.intersect_shape(shape_query_params)

    var filtered_array = results.filter(
        func(collision_object):
            <span class="hljs-keyword">return</span> collision_object.collider.is_in_group(<span class="hljs-string">"Planets"</span>)
    )

    <span class="hljs-comment"># 6. Programmatically add planet if nothing is in range</span>
    <span class="hljs-keyword">if</span> filtered_array.size() == <span class="hljs-number">0</span>:
        var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
        second_planet.position = check_position
        add_child(second_planet)

    <span class="hljs-comment"># 7. Debug</span>
    var area_2d = Area2D.new()

    <span class="hljs-comment"># Draw debug circle around location of new planet</span>
    area_2d.position = check_position

    var collision_shape_2d = CollisionShape2D.new()
    var collision_shape = CircleShape2D.new()
    collision_shape.radius = shape.radius
    collision_shape_2d.shape = collision_shape

    area_2d.add_child(collision_shape_2d)
    add_child(area_2d)
</code></pre>
<p>And the result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702036982221/21c520af-4a27-4058-a5ce-b274c35a6452.png" alt class="image--center mx-auto" /></p>
<p>Great. Now let's change the <code>check_position</code> vector to 400,300:</p>
<pre><code class="lang-python"><span class="hljs-comment"># ...</span>

func _ready() -&gt; void:
    <span class="hljs-comment"># 0. Location of new planet:</span>
    var check_position: Vector2 = Vector2(<span class="hljs-number">400</span>, <span class="hljs-number">300</span>)

<span class="hljs-comment"># ...</span>
</code></pre>
<p>Result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702037123612/7769b3ae-0068-4e60-91a9-23b25adb84cf.png" alt class="image--center mx-auto" /></p>
<p>No planet! Last test: it is still allowed to spawn near the player. Set the <code>check_position</code> vector to 700, 300:</p>
<pre><code class="lang-python"><span class="hljs-comment"># ...</span>

func _ready() -&gt; void:
    <span class="hljs-comment"># 0. Location of new planet:</span>
    var check_position: Vector2 = Vector2(<span class="hljs-number">700</span>, <span class="hljs-number">300</span>)

<span class="hljs-comment"># ...</span>
</code></pre>
<p>Result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1702037269283/0d71b962-1ec5-48f0-b0df-ac86a8febf3e.png" alt class="image--center mx-auto" /></p>
<p>Awesome :)</p>
<h1 id="heading-cleaning-up">Cleaning up</h1>
<p>The code is not really reusable, so let's create a more abstract method that can be used for other groups as well. Let's also make the radius configurable:</p>
<pre><code class="lang-python">func detect_body_in_radius(radius: int, pos: Vector2, group_name: String) -&gt; bool:
    <span class="hljs-comment"># Get the 2D physics space state</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># Create a CircleShape2D and set its radius</span>
    var shape = CircleShape2D.new()
    shape.radius = radius

    <span class="hljs-comment"># Create PhysicsShapeQueryParameters2D and set its shape and transform</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()
    shape_query_params.shape = shape
    shape_query_params.transform.origin = pos

    <span class="hljs-comment"># Perform a shape intersection query in the physics space</span>
    var results = space_state.intersect_shape(shape_query_params)

    <span class="hljs-comment"># Filter the results to include only objects in the specified group</span>
    var filtered_array = results.filter(func(collision_object): <span class="hljs-keyword">return</span> collision_object.collider.is_in_group(group_name))

    <span class="hljs-comment"># Check if there are any objects in the filtered array</span>
    <span class="hljs-keyword">return</span> filtered_array.size() &gt; <span class="hljs-number">0</span>
</code></pre>
<p>And finally the complete code:</p>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    var check_position: Vector2 = Vector2(<span class="hljs-number">650</span>, <span class="hljs-number">300</span>)
    var planet_nearby = detect_body_in_radius(<span class="hljs-number">200</span>, check_position, <span class="hljs-string">"Planets"</span>)

    <span class="hljs-keyword">if</span> !planet_nearby:
        var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
        second_planet.position = check_position
        add_child(second_planet)


func detect_body_in_radius(radius: int, pos: Vector2, group_name: String) -&gt; bool:
    <span class="hljs-comment"># Get the 2D physics space state</span>
    var space_state = get_world_2d().direct_space_state

    <span class="hljs-comment"># Create a CircleShape2D and set its radius</span>
    var shape = CircleShape2D.new()
    shape.radius = radius

    <span class="hljs-comment"># Create PhysicsShapeQueryParameters2D and set its shape and transform</span>
    var shape_query_params = PhysicsShapeQueryParameters2D.new()
    shape_query_params.shape = shape
    shape_query_params.transform.origin = pos

    <span class="hljs-comment"># Perform a shape intersection query in the physics space</span>
    var results = space_state.intersect_shape(shape_query_params)

    <span class="hljs-comment"># Filter the results to include only objects in the specified group</span>
    var filtered_array = results.filter(func(collision_object): <span class="hljs-keyword">return</span> collision_object.collider.is_in_group(group_name))

    <span class="hljs-comment"># Check if there are any objects in the filtered array</span>
    <span class="hljs-keyword">return</span> filtered_array.size() &gt; <span class="hljs-number">0</span>
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>The initial solution I attempted turned out to be suboptimal—quite frankly, it was far from ideal. Fortunately, the PhysicsDirectSpaceState2D class provides a convenient method for querying 2D space without the need to create a node beforehand. I found this approach not only more efficient but also considerably beneficial. I hope you find it as helpful as I did!</p>
<p>Happy coding :)</p>
]]></content:encoded></item><item><title><![CDATA[Godot 4: Detect a collision before adding node in a 2D game (part 1).]]></title><description><![CDATA[Introduction
Over the past month, I've been immersing myself in the world of Godot 4, and it's truly an exceptional game engine. For quite some time, I've had an idea brewing for a 2D exploration game set in space. I'm currently engaged in procedural...]]></description><link>https://joeridamme.hashnode.dev/godot-4-detect-body-collision-before-adding-node-to-scene-part-1</link><guid isPermaLink="true">https://joeridamme.hashnode.dev/godot-4-detect-body-collision-before-adding-node-to-scene-part-1</guid><category><![CDATA[Godot]]></category><category><![CDATA[Godot 4]]></category><category><![CDATA[gdscript]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Joeri Damme]]></dc:creator><pubDate>Fri, 08 Dec 2023 14:41:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1702048505416/f6963888-4e34-40f0-b4cd-b6ecab74a5d3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Over the past month, I've been immersing myself in the world of Godot 4, and it's truly an exceptional game engine. For quite some time, I've had an idea brewing for a 2D exploration game set in space. I'm currently engaged in procedurally generating a map using the TileMap node and the <a target="_blank" href="https://docs.godotengine.org/en/stable/classes/class_fastnoiselite.html">FastNoiseLite</a> library.</p>
<p>Based on the noise at specific coordinates, I place a visible tile (which will eventually become invisible) with a value ranging from 0 to 7. When the value reaches 6 or higher, I aim to position a planet. However, here lies the challenge: I want to avoid spawning planets too closely together. Below is a screenshot of the tile map:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701610164953/41284252-e05f-4527-828a-0670abcb0171.png" alt="Procedural generated map with the FastNoiseLite library" class="image--center mx-auto" /></p>
<p>As you can see, there are clusters of tiles and I don't want to generate on each tile with the number 6 a planet. So we need to detect if there is another planet close by. I decided to setup a test project and see how I can detect a planet near by.</p>
<h2 id="heading-setting-up-the-test-project">Setting up the test project</h2>
<p>I've initiated a new project in Godot 4. Within this project, I've crafted a <code>Game</code> scene and introduced a specialized <code>Planet</code> scene. The <code>Planet</code> scene is straightforward, featuring a <code>CharacterBody2D</code> node as the root, accompanied by two child nodes—a <code>CollisionShape2D</code> and a <code>Sprite2D</code>. The <code>Sprite2D</code> node showcases a circular pink image, symbolizing a planet:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701610949111/9364135e-c8ad-4e9a-97ec-beb477bdcb01.png" alt="CharacterBody2D node basic setup with a Sprite2D and CollisionShape2D" class="image--center mx-auto" /></p>
<p>In the <code>Game</code> scene, I incorporated a <code>Node2D</code> as the root node. I then connected the <code>Planet</code> node once and adjusted its position within the viewport to <code>x: 550, y: 300</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701614321675/397ce44a-4fa4-47d5-803f-b1170d65cbfe.png" alt="The Planet scene inside the new Game scene" class="image--center mx-auto" /></p>
<p>I've attached a script to the Game node. In the <code>_ready()</code> function, I aim to simulate the addition of a planet on top of another. I prefer doing this programmatically, as it mirrors the functionality within my game. The script now includes the following code to ensure the planets overlap each other:</p>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate()
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)
    add_child(second_planet)
</code></pre>
<p>And the result will be 2 overlapping planets:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701615085320/4d0194a1-00c3-41b6-8813-58d3b92e32de.png" alt="Example of two Planet scenes colliding" class="image--center mx-auto" /></p>
<p>The next objective is to implement a check before adding the Planet to the Game scene. I aim to determine whether it is permissible to spawn a planet within a certain radius from another planet.</p>
<h2 id="heading-the-not-so-good-solution-1-area2d-collision-detection">The not so good solution 1: Area2D collision detection</h2>
<p>The initial concept that crossed my mind involves incorporating an Area2D with a CollisionShape2D to the planet. By adding a signal when a different Area2D is in collision, we can trigger a function that sets a boolean. Prior to introducing the Planet to the Game scene, we can evaluate this boolean to determine whether the action is permissible:</p>
<ol>
<li><p>Open the Planet scene and include an Area2D and CollisionShape2D. Rename the Area2D node to "SpawnBoundary." Configure the CollisionShape2D as a CircleShape and set the radius to 150px:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701635279370/d2534069-923d-4b1a-9c85-7ec15031eef0.png" alt="Adding the spawn boundary around the planet" class="image--center mx-auto" /></p>
</li>
<li><p>Access the Project Settings and incorporate two additional 2D Physics layers: "Planets" and "SpawnBoundaries."</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701635412230/264b461e-ff82-45fa-967b-202516295752.png" alt="Setting the 2D Physics layers" class="image--center mx-auto" /></p>
</li>
<li><p>Assign the Planet node to Collision Layer 1 and Collision Mask 1.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701635715483/a0b22d04-837e-4761-bc75-197c833f56c8.png" alt="Setting the collision layers and masks for the Planet scene" class="image--center mx-auto" /></p>
</li>
<li><p>Assign the SpawnBoundary node to Collision Layer 2 and Collision Mask 2.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701635762362/f164a648-242f-4a9e-ab71-508729a70c91.png" alt="Setting the collision layer and mask for the SpawnBoundary node" class="image--center mx-auto" /></p>
</li>
<li><p>Attach a new script to the Planet node and name it <code>Planet.gd</code> .</p>
</li>
<li><p>Select the SpawnBoundary node, and incorporate the <code>area_entered(area: Area2D)</code> signal into the Planet script.</p>
</li>
<li><p>Introduce a boolean at the top of the script, such as <code>var planet_nearby: bool = false</code>, and set it to true within the signal function. Additionally, include a print statement for debugging. Here's an example:</p>
</li>
</ol>
<pre><code class="lang-python">extends CharacterBody2D

var planet_nearby: bool = false

func _on_spawn_boundary_area_entered(area):
    print(<span class="hljs-string">'Planet nearby detected'</span>)
    planet_nearby = true
</code></pre>
<p>The initial phase is complete. Now, let's return to the Game scene and put our solution to the test:</p>
<ol>
<li><p>In the Game scene, adjust the position of the Planet node (the one that is already linked in the scene) slightly to the left to prevent too much overlap with the other planet. I recommend moving it to x: 350px and y: 300px. After making this adjustment, save the scene.</p>
</li>
<li><p>Navigate to the Debug menu and activate "Visible Collision Shapes." Afterward, run the scene:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701636933789/f9ddb484-aba4-4102-95e2-2fc77531b30f.png" alt="Colliding spawnboundaries example" class="image--center mx-auto" /></p>
<ol>
<li>It's evident that the SpawnBoundary nodes are overlapping, thereby prohibiting the spawning of the Planet. To address this, let's enhance the <a target="_blank" href="http://Game.gd"><code>Game.gd</code></a> script with the following updates:</li>
</ol>
</li>
</ol>
<pre><code class="lang-python">    extends Node2D

    var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

    func _ready() -&gt; void:
        <span class="hljs-comment"># Programmatically add planet</span>
        var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
        second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

        <span class="hljs-comment"># Checking if planet is nearby before adding to scene</span>
        <span class="hljs-keyword">if</span> !second_planet.planet_nearby:
            print(<span class="hljs-string">'No planet nearby'</span>)
            add_child(second_planet)
</code></pre>
<ol>
<li>So this makes sense right? Before adding it to the scene, I will check the variable <code>planet_nearby</code>. In my case, this should be <code>true</code>, because the <code>area_entered</code> signal is triggered. So it should skip the if statement and don't add the planet to the scene. Let's run it:</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701637530544/efd77218-d200-4dd2-baea-729c818cbdd2.png" alt="Output messages are in wrong order" class="image--center mx-auto" /></p>
<p>Okay, that's not what I expected. As you can see in the output below, the 'No planet nearby' message appeared before the print statement in the <code>_on_spawn_boundary_area_entered</code> function in the Planet script. But why?</p>
<p>The reason is simple: Since the Planet is instantiated and not in the scene yet, we cannot check if the collision has happened yet. So the only option now is to add the planet to the scene, but before that, make it invisible. If a planet is not detected nearby, just make it visible:</p>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    <span class="hljs-comment"># Make the planet invisible and add to the tree</span>
    second_planet.visible = <span class="hljs-number">0</span>
    add_child(second_planet)

    <span class="hljs-comment"># Checking if planet is nearby before adding to scene</span>
    <span class="hljs-keyword">if</span> !second_planet.planet_nearby:
        print(<span class="hljs-string">'No planet nearby'</span>)
        second_planet.visible = <span class="hljs-number">1</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-comment"># Delete the planet if not needs to be rendered</span>
        second_planet.queue_free()
</code></pre>
<p>Now let's run it again:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701639005172/821c77c2-6957-42b5-9eae-308ad8f6c955.png" alt="Still no success" class="image--center mx-auto" /></p>
<p>Still not working. The debug messages are still in the wrong order. After some research, there is also a different problem: The physics engine must render at least one frame before any collision can be detected. So, how can we wait for one frame before checking the <code>planet_nearby</code> variable? We can do that with a <a target="_blank" href="https://docs.godotengine.org/en/4.1/tutorials/scripting/gdscript/gdscript_basics.html#awaiting-for-signals-or-coroutines">signal and coroutines</a>.</p>
<ol>
<li>Let's open the Planet.gd script again, and make sure that we add a signal <code>wait_first_frame</code> and a boolean variable at the top of the script. Also, we want to check when the <code>_process()</code> function has rendered the first frame. If that is done, we emit the new signal and set the boolean to true:</li>
</ol>
<pre><code class="lang-python">extends CharacterBody2D

var planet_nearby: bool = false

signal wait_first_frame

var first_frame_rendered: bool = false

func _on_spawn_boundary_area_entered(area):
    print(<span class="hljs-string">'Planet nearby detected'</span>)
    planet_nearby = true

func _process(delta):
    <span class="hljs-keyword">if</span> !first_frame_rendered:
        print(<span class="hljs-string">'First frame rendered...'</span>)
        wait_first_frame.emit()
        first_frame_rendered = true
</code></pre>
<ol>
<li>In the Game.gd script, we gonna wait for the signal until is has emitted:</li>
</ol>
<pre><code class="lang-python">extends Node2D

var planet: PackedScene = preload(<span class="hljs-string">"res://scenes/Planet.tscn"</span>)

func _ready() -&gt; void:
    <span class="hljs-comment"># Programmatically add planet</span>
    var second_planet = planet.instantiate() <span class="hljs-keyword">as</span> CharacterBody2D
    second_planet.position = Vector2(<span class="hljs-number">600</span>, <span class="hljs-number">300</span>)

    <span class="hljs-comment"># Make the planet invisible and add to the tree</span>
    second_planet.visible = <span class="hljs-number">0</span>
    add_child(second_planet)

    <span class="hljs-comment"># Now wait for the first frame to be rendered</span>
    <span class="hljs-keyword">await</span> second_planet.wait_first_frame

    <span class="hljs-comment"># Checking if planet is nearby before adding to scene</span>
    <span class="hljs-keyword">if</span> !second_planet.planet_nearby:
        print(<span class="hljs-string">'No planet nearby'</span>)
        second_planet.visible = <span class="hljs-number">1</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-comment"># Delete the planet if not needs to be rendered</span>
        second_planet.queue_free()
</code></pre>
<p>Now let's run the game again:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701696894882/3f3d2369-c93b-4f18-93da-8ca477c81226.png" alt="Planet not generated because of the await and emit functionality" class="image--right mx-auto mr-0" /></p>
<p>That's looking good! Let's move the instantiated planet a 100 pixels to the right, by changing the line <code>second_planet.position = Vector2(600, 300)</code> to <code>second_planet.position = Vector2(700, 300)</code> :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701696993777/ccffe361-81d2-4bd6-887a-7ebc37dad89f.png" alt="Second planet generated because no collision" class="image--center mx-auto" /></p>
<p>This seems to work! But do I like it? Not really. I'm adding a node, make it invisible, adding it to the scene, we need to hook into the <code>process()</code> function of the planet, adding a signal...it seems hard to maintain. Is there a different solution? <a target="_blank" href="https://joeridamme.hashnode.dev/godot-4-detect-body-collision-before-adding-node-to-scene-part-2">Let's find out in part 2</a>!</p>
]]></content:encoded></item></channel></rss>