Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. GDScript
  3. GDScript Groups & Pickups Practice
GDScript9 exercises

GDScript Groups & Pickups Practice

Master Godot 4 groups: add_to_group in code and editor, is_in_group filtering, call_group broadcasting, get_nodes_in_group queries. Build clean pickup and trigger systems.

Common ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Add the current node to the 'enemies' group so it can be found later.

On this page
  1. 1Why Groups Matter
  2. 2Add Groups in the Editor (Node Dock + Global Groups)
  3. 3Pickup Pattern (Area2D)
  4. 4call_group: Broadcast to Everyone
  5. 5get_nodes_in_group: Query and Filter
  6. Find Closest Enemy (Safe Pattern)
  7. 6StringName Constants (&"name")
  8. 7Group Naming Conventions
Why Groups MatterAdd Groups in the Editor (Node Dock + Global Groups)Pickup Pattern (Area2D)call_group: Broadcast to Everyoneget_nodes_in_group: Query and FilterStringName Constants (&"name")Group Naming Conventions

Groups are Godot's "tag" system. They let you label nodes (player, enemy, pickup, projectile) and then query or broadcast behavior without hard-coding node paths. Use groups to build pickups, triggers, and "affect all enemies" events without brittle node paths.

Combined with collision detection, groups power most pickup systems. The pattern: detect the overlap → check is_in_group("player") → apply effect → queue_free().

Related GDScript Topics
GDScript CollisionsGDScript Arrays & LoopsGDScript UI & HUD

Groups decouple systems—a weapon damages anything in the "enemy" group without knowing specific types. Use is_in_group() instead of brittle name checks.

Groups decouple your systems. A weapon doesn't need to know every enemy type—it just damages anything in the "enemy" group. A pickup doesn't need a direct player reference—it heals anything in the "player" group.

# Without groups: brittle, breaks if you rename/add enemies
if body.name == "Goblin" or body.name == "Skeleton":
    body.take_damage(10)

# With groups: flexible, scales to any number of enemy types
if body.is_in_group(&"enemy"):
    body.take_damage(10)

Ready to practice?

Start practicing GDScript Groups & Pickups with spaced repetition

You don't always need code to assign groups:

  1. Node dock method: Select a node → Node tab (next to Inspector) → Groups section → type a name and click Add
  2. Global Groups (Project Settings): Project → Project Settings → Global Groups → define groups here, then assign them to any node via the Node dock dropdown

Global Groups are useful for project-wide consistency—define player, enemy, pickup once, and they appear in every node's group dropdown.

This is the core pattern for coins, health packs, power-ups, and collectibles:

extends Area2D
@export var heal_amount := 25

const GROUP_PLAYER := &"player"

func _ready() -> void:
    body_entered.connect(_on_body_entered)

func _on_body_entered(body: Node) -> void:
    if not body.is_in_group(GROUP_PLAYER):
        return

    # If the player implements heal(), this works.
    # call() ignores nodes that don't have the method.
    body.call(&"heal", heal_amount)
    queue_free()

Key insight: Groups are not a replacement for collision layers/masks—they're a second filter. Use layers/masks to control which physics objects can overlap, then use groups to identify what role that object plays ("is this a player?").

call_group() runs immediately in Godot 4 (not deferred like Godot 3). Nodes without the method are silently skipped. Use GROUP_CALL_DEFERRED flag if you need end-of-frame timing.

# Freeze all enemies (runs IMMEDIATELY in Godot 4)
get_tree().call_group(&"enemy", &"freeze", 2.0)

# Heal all allies
get_tree().call_group(&"ally", &"heal", 50)

# Pause all AI
get_tree().call_group(&"ai", &"set_active", false)

Godot 4 behavior: call_group() acts immediately—not end-of-frame. Nodes that don't have the method are silently ignored (no error). If you need end-of-frame timing:

# Deferred call_group (runs at end of frame, like call_deferred)
get_tree().call_group_flags(
    SceneTree.GROUP_CALL_DEFERRED,
    &"enemy",
    &"freeze",
    2.0
)

# Find all enemies
var enemies := get_tree().get_nodes_in_group(&"enemy")

Find Closest Enemy (Safe Pattern)

The array returns Node references, so cast before accessing global_position. For more iteration patterns and safe removal techniques, see arrays & loops.

const GROUP_ENEMY := &"enemy"

var closest: Node2D = null
var best_d2 := INF

for node in get_tree().get_nodes_in_group(GROUP_ENEMY):
    var enemy := node as Node2D
    if enemy == null:
        continue  # Skip non-Node2D entries

    # Squared distance is cheaper (no sqrt)
    var d2 := global_position.distance_squared_to(enemy.global_position)
    if d2 < best_d2:
        best_d2 = d2
        closest = enemy

Performance note: get_nodes_in_group() allocates an Array every call. Don't call it every frame—cache the result or use signals for dynamic membership.

In Godot 4, group names and method names are StringName parameters. Using the &"literal" syntax avoids String→StringName conversion and enables fast comparisons:

# Best practice: define as StringName constants
const GROUP_PLAYER := &"player"
const GROUP_ENEMY := &"enemy"
const GROUP_PICKUP := &"pickup"

# Usage
add_to_group(GROUP_ENEMY)
if body.is_in_group(GROUP_PLAYER):
    ...
get_tree().call_group(GROUP_ENEMY, &"freeze", 1.0)

This is a small optimization, but it signals "I know the Godot 4 API."

Use singular nouns (player, enemy) or role groups (damageable, interactable). Never use state groups (stunned, frozen)—state belongs in variables, not group membership.

PatternExamplesNotes
Singular nounsplayer, enemy, pickupMost common, reads naturally
Role groupsdamageable, collectible, interactableGood for shared interfaces
Avoid state groupsstunned, frozenState belongs in variables, not group membership

State groups seem convenient but create sync bugs—you'll forget to remove the group when state changes. Use a var is_stunned: bool instead. For advanced group-based data patterns like mapping group members to stats, see our dictionary map blog post.

When to Use GDScript Groups & Pickups

  • Pickups, coins, and collectibles that should only react to players
  • Global actions: pause all AI, despawn all bullets, heal all allies
  • Loose coupling between systems (UI finds player via group, weapons damage anything in "enemy" group)
  • Filtering collision callbacks without hard-coding node names or paths

Check Your Understanding: GDScript Groups & Pickups

Prompt

Check Your Understanding: Why use groups instead of node names or paths?

What a strong answer looks like

Names/paths are brittle: they break when you refactor scenes. Groups express intent ("this is a player") and scale to multiple instances. They also reduce coupling: collision code doesn't need to know which scene the player is; it just checks the group. Plus, call_group() lets you broadcast actions to all tagged nodes without maintaining an explicit list.

What You'll Practice: GDScript Groups & Pickups

Add/remove nodes from groups at runtime (add_to_group / remove_from_group)Assign groups in the editor via Node dock and Global Groups in Project SettingsFilter collision events with is_in_group() instead of brittle name checksBroadcast behavior with call_group() and understand immediate vs deferred timingQuery nodes with get_nodes_in_group() safely, cast results, and cache when neededBuild complete pickup systems combining groups with Area2D collision detectionUse StringName constants (&"name") for group names in Godot 4

Common GDScript Groups & Pickups Pitfalls

  • Calling get_nodes_in_group() in _process/_physics_process every frame—allocates arrays repeatedly; cache instead
  • Assuming call_group() is deferred (Godot 3 behavior)—in Godot 4 it runs immediately; use GROUP_CALL_DEFERRED flag if needed
  • Using groups for game state (stunned, frozen)—state belongs in variables; groups create sync bugs when you forget to remove them
  • Accessing global_position on uncast Node from get_nodes_in_group()—cast to Node2D first or you'll get errors
  • Hard-coding string literals everywhere—use StringName constants (&"enemy") for type safety and performance

GDScript Groups & Pickups FAQ

How do I add a node to a group in the Godot editor?

Select the node → Node tab (next to Inspector) → Groups section → type a name and click Add. For project-wide groups, use Project Settings → Global Groups to define them once, then assign via the Node dock dropdown.

Does call_group() run immediately or at end of frame?

In Godot 4, call_group() acts immediately—not deferred. Nodes without the method are silently ignored. If you need end-of-frame behavior, use call_group_flags(SceneTree.GROUP_CALL_DEFERRED, "group", "method").

What order does get_nodes_in_group() return in Godot 4?

Returns nodes in scene tree (hierarchy) order. However, don't rely on this being stable if nodes are spawned, freed, or reparented during gameplay—sort the array if you need deterministic ordering rules.

How do I get the player node via groups?

get_tree().get_first_node_in_group(&"player") returns the first match (or null). For a single player, this is cleaner than get_nodes_in_group()[0]. Cast the result: var player := get_tree().get_first_node_in_group(&"player") as CharacterBody2D.

Is get_nodes_in_group() expensive?

It allocates a new Array every call. Fine for occasional queries (ability activation, level start), but don't call it every frame in _process. Cache references or use signals to track membership changes.

When should I use groups vs signals?

Groups are for querying and broadcasting to a category of nodes ("affect all enemies"). Signals are for specific events between connected nodes ("this enemy died"). Use both together: groups to find nodes, signals to react to individual events.

How do I safely cast get_nodes_in_group results?

The array contains Node references. Cast each element: for node in get_nodes_in_group(&"enemy"): var enemy := node as Enemy; if enemy == null: continue. This handles cases where non-matching types accidentally joined the group.

What are Global Groups in Godot 4?

Project Settings → Global Groups lets you define groups project-wide. They appear in the Node dock dropdown for easy assignment. Use them to ensure consistent group names across your project.

GDScript Groups & Pickups Syntax Quick Reference

Tag node in _ready
func _ready() -> void:
	add_to_group(&"enemy")
Pickup (Area2D)
extends Area2D
@export var heal_amount := 25

func _on_body_entered(body: Node) -> void:
	if not body.is_in_group(&"player"):
		return
	body.call(&"heal", heal_amount)
	queue_free()
Broadcast (immediate)
# Runs NOW in Godot 4
get_tree().call_group(&"enemy", &"take_damage", 10)
Broadcast (deferred)
# Runs at end of frame
get_tree().call_group_flags(
	SceneTree.GROUP_CALL_DEFERRED,
	&"enemy",
	&"freeze",
	2.0
)
Find closest (safe)
var closest: Node2D = null
var best_d2 := INF

for node in get_tree().get_nodes_in_group(&"enemy"):
	var e := node as Node2D
	if e == null:
		continue
	var d2 := global_position.distance_squared_to(e.global_position)
	if d2 < best_d2:
		best_d2 = d2
		closest = e

GDScript Groups & Pickups Sample Exercises

Example 1Difficulty: 2/5

Fetch the first node in group 'player', storing it in player.

var player = get_tree().get_first_node_in_group("player")
Example 2Difficulty: 2/5

Write just the expression (no if keyword) that checks target is still a valid instance.

is_instance_valid(target)
Example 3Difficulty: 2/5

Call take_damage(5) on every node in group 'enemies'.

get_tree().call_group("enemies", "take_damage", 5)

+ 6 more exercises

Practice in Build a Game

Arena Survivor: Part 3: Survive the SwarmBuild a GameRoguelike: Part 4: Mutations in the DustBuild a Game

Further Reading

  • GDScript Dictionary map() and map_in_place12 min read
  • Facade Pattern in Godot 4 GDScript: Taming "End Turn" Spaghetti12 min read

Start practicing GDScript Groups & Pickups

Free daily exercises with spaced repetition. No credit card required.

← Back to GDScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.