Can you write this from memory?
Declare an array called upgrade_names containing the strings 'speed', 'damage', and 'health'.
In GDScript, auto-targeting weapons need to find the closest enemy. That means getting all enemies from a group, looping through them, and comparing distances.
Practice array operations and group queries, and your weapon targeting logic writes itself.
Quick Answers:
1. Loop Over Values
var items: Array[String] = ["sword", "shield", "potion"]
for item in items:
print(item)
2. Loop With Index
for i in range(items.size()):
print(i, ": ", items[i])
3. Early Break When Found
func find_item(target: String) -> int:
for i in range(items.size()):
if items[i] == target:
return i # Found it, stop searching
return -1 # Not found
Sorting is O(n log n). If you just need the closest, do it in O(n):
func get_closest_enemy() -> Node2D:
var enemies := get_tree().get_nodes_in_group(&"enemy")
var closest: Node2D = null
var min_dist_sq := INF
for node in enemies:
var enemy := node as Node2D
if enemy == null:
continue
# distance_squared_to avoids sqrt—faster for comparisons
var dist_sq := global_position.distance_squared_to(enemy.global_position)
if dist_sq < min_dist_sq:
min_dist_sq = dist_sq
closest = enemy
return closest
In real games: Prefer one-pass closest target if you do this every frame. Save sorting for when you need the full ordered list.
When you need enemies sorted (e.g., chain lightning, splash damage falloff):
func get_enemies_sorted_by_distance() -> Array[Node2D]:
var enemies := get_tree().get_nodes_in_group(&"enemy")
# Sort using distance_squared_to (faster, same ordering)
enemies.sort_custom(func(a: Node, b: Node) -> bool:
var dist_a := global_position.distance_squared_to((a as Node2D).global_position)
var dist_b := global_position.distance_squared_to((b as Node2D).global_position)
return dist_a < dist_b
)
# Cast to typed array
var result: Array[Node2D] = []
for e in enemies:
if e is Node2D:
result.append(e)
return result
Note: get_nodes_in_group() returns Array[Node], not Array[Node2D]. Filter/cast before using global_position.
Godot warns: Erasing while iterating is unsupported and can behave unpredictably. Use one of these safe patterns:
# WRONG: Skips elements, unpredictable behavior
for enemy in enemies:
if enemy.is_dead:
enemies.erase(enemy) # Don't do this!
Option 1: Iterate Over a Copy
for enemy in enemies.duplicate():
if enemy.is_dead:
enemies.erase(enemy)
Option 2: Backward Index Loop (fastest, no allocation)
for i in range(enemies.size() - 1, -1, -1):
if enemies[i].is_dead:
enemies.remove_at(i)
Option 3: filter() for Clean Code
# Rebuild the list with only living enemies
enemies = enemies.filter(func(e): return not e.is_dead)
Trade-offs: Backward loop is fastest (no allocations). filter() is most readable. duplicate() is a safe middle ground.
| Type | Best For | Trade-offs |
|---|---|---|
Array | General collections, mixed types | Flexible, slower than typed |
Array[T] | Gameplay objects (enemies, items) | Type safety, autocomplete, good performance |
PackedVector2Array | Large numeric data (paths, geometry) | Contiguous memory layout like C arrays—better cache locality and lower per-element overhead than generic Arrays |
Dictionary | Key-value lookups (stats, config) | O(1) access by key, preserves insertion order |
In real games: Use Array[T] for gameplay collections (enemies, items, projectiles). Use Packed arrays for raw numeric buffers like navigation paths or vertex data where you iterate thousands of elements. Each element in a generic Array is a Variant (20+ bytes overhead per element), while PackedFloat32Array stores raw 4-byte floats—a significant memory difference at scale.
For a printable reference of collection methods, see the GDScript arrays & dictionaries cheat sheet. For real-world dictionary patterns in Godot 4, see our dictionary map blog post.
| Array | Dictionary |
|---|---|
| Ordered, integer-indexed | Key-value pairs |
items[0] | stats["hp"] |
| Use for lists, sequences | Use for named lookups |
# Array: ordered collection
var inventory: Array[String] = ["sword", "potion", "key"]
# Dictionary: key-value mapping
var stats: Dictionary = { "hp": 100, "attack": 10, "defense": 5 }
Note: Dictionaries preserve insertion order when adding entries, but they're not automatically sorted. If you need sorted keys, extract and sort explicitly.
When to Use GDScript Arrays & Loops
- Iterating over enemies, collectibles, or scene groups with get_tree().get_nodes_in_group()
- Storing and looking up game data with Dictionaries for items, stats, or configuration
- Finding the nearest node by looping through an array and comparing with distance_squared_to()
- Safely removing items during iteration using filter(), backward loops, or duplicate()
Check Your Understanding: GDScript Arrays & Loops
Check Your Understanding: Why use distance_squared_to() instead of distance_to() when finding the closest enemy?
distance_to() computes sqrt(dx² + dy²), while distance_squared_to() returns just dx² + dy². The sqrt operation is expensive. When comparing distances (closest, in-range checks), relative order is preserved without sqrt: if A² < B², then A < B. Only use distance_to() when you need the actual distance value (e.g., displaying "50 meters away"). In hot loops like per-frame targeting, distance_squared_to() is measurably faster.
What You'll Practice: GDScript Arrays & Loops
Common GDScript Arrays & Loops Pitfalls
- Modifying an Array while iterating with for-in—Godot warns this is unsupported and causes unpredictable behavior; use duplicate(), backward index loop, or filter() instead
- Using get_nodes_in_group() every frame—allocates a new Array each call; cache the result or use signals for dynamic membership
- Assuming get_nodes_in_group() returns typed nodes—it returns Array[Node], not Array[Node2D]; cast/filter before accessing global_position
- Using distance_to() in hot loops when you only need relative comparisons—distance_squared_to() avoids the expensive sqrt operation
- Dictionaries preserve insertion order but are not sorted—if you need sorted keys, extract keys() and sort explicitly
GDScript Arrays & Loops FAQ
How do I loop with index in GDScript?
Use range(): for i in range(items.size()): print(items[i]). This gives you the index i to access elements by position or modify the array.
How do I get the closest enemy in GDScript?
Loop through get_nodes_in_group(&"enemy"), track minimum distance using INF as the starting value. Use distance_squared_to() instead of distance_to()—it skips the sqrt and is faster for comparisons where you only need relative distance.
How do I remove items from an array safely while iterating?
Godot warns that erasing during iteration is unsupported. Three safe options: (1) iterate over enemies.duplicate(), (2) loop backward with for i in range(size - 1, -1, -1), or (3) use enemies.filter(func(e): return not e.is_dead).
What is distance_squared_to and when should I use it?
distance_squared_to() returns the squared distance without computing the square root. Use it when comparing distances (closest enemy, range checks) since sqrt is expensive. Only use distance_to() when you need the actual distance value.
How do I sort an array of enemies by distance in GDScript?
Use sort_custom with distance_squared_to: enemies.sort_custom(func(a, b): return global_position.distance_squared_to(a.global_position) < global_position.distance_squared_to(b.global_position)).
What is the difference between Array and Dictionary in GDScript?
Array is ordered and integer-indexed (items[0]). Dictionary uses key-value pairs (stats["hp"]). Dictionaries preserve insertion order but aren't sorted. Use Array for sequences; Dictionary for named lookups.
Does GDScript have filter and map like JavaScript?
Yes! Godot 4 has filter(), map(), and reduce(). Example: enemies.filter(func(e): return e.is_alive) returns a new array with only living enemies. Note: these allocate new arrays, so avoid in hot paths.
Array[T] vs PackedArray—when should I use which?
Use Array[T] for gameplay objects (enemies, items)—you get type safety and convenience methods. Use PackedVector2Array/PackedFloat32Array for large numeric datasets (navigation paths, geometry) where memory and iteration speed matter.
GDScript Arrays & Loops Syntax Quick Reference
func get_closest_enemy() -> Node2D:
var enemies := get_tree().get_nodes_in_group(&"enemy")
var closest: Node2D = null
var min_dist_sq := INF
for node in enemies:
var enemy := node as Node2D
if enemy == null:
continue
var dist_sq := global_position.distance_squared_to(enemy.global_position)
if dist_sq < min_dist_sq:
min_dist_sq = dist_sq
closest = enemy
return closestfunc remove_dead_enemies(enemies: Array[Node2D]) -> void:
for i in range(enemies.size() - 1, -1, -1):
if enemies[i].is_dead:
enemies.remove_at(i)# Rebuild array with only living enemies
enemies = enemies.filter(func(e): return not e.is_dead)func get_enemies_in_range(radius: float) -> Array[Node2D]:
var radius_sq := radius * radius
var result: Array[Node2D] = []
for node in get_tree().get_nodes_in_group(&"enemy"):
var enemy := node as Node2D
if enemy and global_position.distance_squared_to(enemy.global_position) <= radius_sq:
result.append(enemy)
return resultGDScript Arrays & Loops Sample Exercises
Write a for loop using range from 1 to 10 (inclusive of 1, exclusive of 11) to spawn waves, with loop variable wave.
for wave in range(1, 11):Write a for loop using range from 0 to 10, stepping by 2 (0, 2, 4, 6, 8), with loop variable x.
for x in range(0, 10, 2):Get all nodes in the 'enemies' group from the scene tree, storing the result in a variable called enemies.
var enemies = get_tree().get_nodes_in_group("enemies")+ 32 more exercises