13 Essential Godot Features Every 2D Game Developer Should Know (With Recommended Course)

Jun 20, 2025Mar 29, 2026 Updated📖 26 min read | 15,132 charsGame DevelopmentGodotLearning

In my previous article, I covered the basics of learning Godot. This time, I completed the Udemy course "Godot4: Build a 2D Action-Adventure Game" and tackled building a practical 2D action-adventure game.

The course systematically covers everything from basic player controls to NPC dialogue, puzzles, and combat systems. This article distills the key Godot features and concepts I learned along the way.

Course Overview

"Godot4: Build a 2D Action-Adventure Game" builds a 2D action-adventure game from scratch. As you work through it, questions like these get answered naturally:

  • How do you implement area transitions?
  • How do you handle pushable objects?
  • How do you persist opened treasure chest states?
  • How do you manage NPC dialogue?
  • How do you implement a door that requires multiple switches?
  • How do you create a damage flash effect?

The curriculum covers 8-directional player movement, Terrains-based auto-tiling, Y-Sort for draw order, RigidBody2D physics puzzles, dialogue systems, Autoload for data persistence, and enemy AI with knockback combat.

It's a step up in difficulty from the beginner course I covered previously, but packed with knowledge you actually need for real game development. Udemy runs sales frequently — add it to your wishlist and grab it on sale.

Godot4: Build a 2D Action-Adventure Game (Udemy)

Key Godot Features & Concepts

Here's a deep dive into the features I found most important during the course.

Process Mode: Pause Control

"Freeze enemies and the player during NPC dialogue, but keep the dialogue window interactive" — a common game development requirement. Godot handles this with per-node Process Mode settings.

  • Pausable (default): Stops when get_tree().paused = true. For game world objects like players and enemies.
  • Always: Ignores pause state. For active NPCs, UI, and background music.
  • When Paused: Only runs during pause. For pause menu UI.

In Unity, you'd set Time.timeScale = 0 and manually use Time.unscaledDeltaTime in individual scripts. Godot's approach of setting Process Mode per node is far more elegant.

Implementation: pausing the game during dialogue

The scene pauses during conversation and resumes when dialogue ends

# NPC.gd
# Set this NPC's Process Mode to "Always" in the inspector

func _process(delta):
    if Input.is_action_just_pressed("interact") and can_talk:
        if is_dialog_active():
            close_dialog()
            get_tree().paused = false
        else:
            open_dialog()
            get_tree().paused = true

The NPC keeps running while the rest of the game pauses, allowing safe dialogue open/close handling.

CharacterBody2D Motion Mode

CharacterBody2D has a Motion Mode setting that switches physics behavior based on your game's genre. Always check this at project start.

  • Grounded (default): Gravity applies, is_on_floor() works. For platformers and side-scrollers.
  • Floating: No gravity, no floor concept. For top-down action games and shooters.
# Player.gd (Motion Mode set to "Floating")
extends CharacterBody2D

@export var speed: float = 200.0

func _physics_process(delta):
    var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = direction * speed
    move_and_slide()

Wrong settings cause unexpected gravity or broken floor detection — an easy mistake to make.

InputMap: Key Binding Management

Godot InputMap

In Unity, it's tempting to write Input.GetKey(KeyCode.A) directly. Godot's Input Map (Project Settings → Input Map) takes a better approach: define action names and bind keys to them. Your code uses abstract names like "move_left", making it more readable and easy to add key remapping.

func _process(delta):
    # One-shot input (moment of press)
    if Input.is_action_just_pressed("interact"):
        open_chest()

    # Continuous input (while held)
    if Input.is_action_pressed("dash"):
        speed = DASH_SPEED
    else:
        speed = NORMAL_SPEED

    # Get 4-directional input as a normalized Vector2 (very handy)
    var direction = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = direction * speed
    move_and_slide()

Input.get_vector() returns a normalized Vector2 from four actions, letting you write top-down movement in a single line.

Input Processing Methods

Once you've defined actions in InputMap, choose the right method based on input state.

Input Class Reference (Godot Docs)

Use CaseMethodExamples
One-shot on pressis_action_just_pressed()Jump, attack, menu toggle
While heldis_action_pressed()Movement, dash, charge
On releaseis_action_just_released()Fire charged attack
Analog (0.0–1.0)get_action_strength()Gamepad trigger
func _process(delta):
    # One-shot actions
    if Input.is_action_just_pressed("jump"):
        if is_on_floor():
            velocity.y = JUMP_VELOCITY

    if Input.is_action_just_pressed("attack"):
        perform_attack()

    # Continuous actions
    if Input.is_action_pressed("dash"):
        current_speed = dash_speed
    else:
        current_speed = normal_speed

    # Charge: hold to build, release to fire
    if Input.is_action_pressed("charge"):
        charge_power += charge_rate * delta
        charge_power = min(charge_power, max_charge)

    if Input.is_action_just_released("charge"):
        fire_charged_shot(charge_power)
        charge_power = 0.0

A common mistake: using is_action_pressed() for shooting fires a bullet every frame. Always use is_action_just_pressed() for one-shot actions.

move_and_slide vs move_toward

Knockback requires move_toward

Two essential movement functions in Godot:

  • move_and_slide(): The workhorse of CharacterBody2D. Moves based on current velocity and automatically handles wall/floor collisions.
  • move_toward(target, delta): Gradually shifts a value toward a target. Used for smooth acceleration and deceleration.

The distinction becomes critical when implementing knockback. Here's a real problem I hit during the course:

Directly assigning velocity means the moment a player inputs movement during knockback, the knockback effect vanishes instantly. Using move_toward for gradual velocity changes lets knockback decay naturally into normal movement.

# Direct assignment — knockback disappears instantly
func move_player():
    var move_vector = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = move_vector * move_speed  # Overwrites knockback velocity

# move_toward — knockback decays smoothly
@export var acceleration: float = 500.0

func move_player():
    var move_vector = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    var target_velocity = move_vector * move_speed
    velocity = velocity.move_toward(target_velocity, acceleration * delta)

# Knockback
func apply_knockback(direction: Vector2, strength: float):
    velocity += direction * strength

The second argument of move_toward specifies how much to approach the target per frame. Using acceleration * delta ensures frame-rate-independent smooth transitions.

Groups: Flexible Object Identification

"Did the attack hit an enemy or a pushable object?" — Godot uses groups to answer this.

Similar to Unity's Tag system, but with a crucial difference: Unity allows only one Tag per GameObject. Godot groups support multiple assignments, so an object can be both "enemies" and "damageable" simultaneously.

Setup is simple: select a node → Inspector → Node tab → Groups → add a group name. In code, use is_in_group() to check.

# Connected to player's sword Area2D
func _on_sword_area_body_entered(body: Node2D):
    if body.is_in_group("enemies"):
        body.take_damage(attack_power)
    elif body.is_in_group("pushable"):
        pass  # Handle pushable object

Collision Layers/Masks

Without proper collision organization, you'll get "the player's sword hits allies" or "enemies get stuck on each other." Godot's Collision Layers/Masks prevent this.

  • Layer: Which collision layer this object exists on
  • Mask: Which layers this object checks for collisions

For example, with Player (Layer 1), Enemies (Layer 2), and Weapons (Layer 3):

ObjectLayerMaskReason
Player12Takes damage from enemies
Enemy21Only contacts player (enemies pass through each other)
Player's weapon32Only hits enemies (doesn't collide with player)
# Weapon's Mask only detects enemies, so only enemies trigger this
func _on_weapon_area_body_entered(body):
    if body.is_in_group("enemies"):
        body.take_damage(attack_power)

Name your layers in Project Settings → Layer Names to make inspector setup much clearer.

Terrains: Auto-Tiling

Godot's Terrains feature makes auto-tiling surprisingly easy. If you've used Rule Tiles in Unity, you'll appreciate how intuitive this is.

In the TileSet resource's Terrains tab, you visually paint which edges of each tile connect to which terrain type. Then the TileMap editor's brush tool automatically selects the right tile based on boundaries.

Handy features include: random brush (dice icon) for selecting from multiple tiles, probability control for tile frequency, and "F" key for batch-applying collision to all tiles.

Marker2D: The Go-To Manager Node

In Unity, the standard practice was attaching scripts to empty GameObjects to create "managers." In Godot, Marker2D fills this role.

Marker2D holds only position data (Transform) — the lightest 2D node available. No rendering, no physics overhead, making it perfect for scene management scripts like GameManager or PuzzleManager. Unlike Unity's empty GameObjects, it shows a crosshair marker in the editor for easy visibility.

Editable Children & Scene Inheritance

Two approaches for creating variations from a base scene, each suited to different use cases:

  • Editable Children: Right-click an instance → "Editable Children" to directly modify its internals (sprites, colliders, etc.). Changes only apply to that specific placement. Great for mass-producing NPCs that differ only in appearance or dialogue.
  • Scene Inheritance: Create a new scene (Shopkeeper.tscn) that inherits from a base (BaseNPC.tscn). Add unique nodes and scripts while keeping parent functionality. Best for functionally different variants like a merchant NPC that can "talk" and "trade."
Editable Children nodes appear in yellow
AspectEditable ChildrenScene Inheritance
Best forVillager A, B, etc. (visual/dialogue differences)Merchant, Blacksmith (unique functionality)
ReusabilityLow (local to that scene)High (inherited scene usable everywhere)
ManagementSimple (base scene only)Structured (functionality split across files)

Autoload: Cross-Scene Data Management

"Open a treasure chest, leave the area, come back — and it's closed again." The classic scene-switching data loss problem is solved in Godot with Autoload — equivalent to Unity's DontDestroyOnLoad + singleton pattern.

Register a script as Autoload in Project Settings, and it loads automatically at game start, accessible globally from any scene.

# GameManager.gd (registered as AutoLoad)
extends Node

var opened_chests: Array[String] = []
var player_hp: int = 3
var player_spawn_position: Vector2
# TreasureChest.gd
extends StaticBody2D

@export var chest_id: String  # Set a unique ID like "forest_chest_01"

func _ready():
    if GameManager.opened_chests.has(chest_id):
        play_open_animation(false)

func open_chest():
    GameManager.opened_chests.append(chest_id)
    play_open_animation(true)

Player HP, scores, inventory, quest progress — any data that needs to survive scene changes goes through Autoload.

White Flash with modulate

Knockback requires move_toward

A brief white flash on damage is incredibly effective player feedback. In Godot, it's trivial to implement using the modulate property.

modulate is a color value multiplied against a node and all its descendants. Default is white (1, 1, 1). Higher values = brighter, lower = darker. Changing CharacterBody2D's modulate automatically affects its child AnimatedSprite2D — no individual sprite manipulation needed.

# Player.gd
func take_damage(amount):
    # ...damage calculation...
    flash_effect()

func flash_effect():
    modulate = Color(2, 2, 2)  # Flash white
    await get_tree().create_timer(0.1).timeout  # Wait 0.1 seconds
    modulate = Color(1, 1, 1)  # Restore original

await get_tree().create_timer(0.1).timeout is a one-liner for temporary delays without adding timer nodes.

AnimatedSprite2D vs AnimationPlayer

Godot has two main 2D animation systems, each suited to different needs.

AnimatedSprite2D

Dedicated to sprite frame animation. Best for walk cycles, attacks, idles — anything that's just swapping sprite sheet frames.

if velocity.x > 0:
    $AnimatedSprite2D.play("move_right")
elif velocity.x < 0:
    $AnimatedSprite2D.play("move_left")
else:
    $AnimatedSprite2D.stop()

AnimationPlayer

A general-purpose animation system controlling position, rotation, scale, and any property simultaneously. Similar to Unity's Animator — used for sword swings, UI animations, and camera work.

func attack():
    var player_animation: String = $AnimatedSprite2D.animation
    if player_animation == "move_right":
        $AnimatedSprite2D.play("attack_right")
        $AnimationPlayer.play("attack_right")  # Controls sword position & angle

Selection Guide

Animation ContentRecommended System
Sprite frame switching onlyAnimatedSprite2D
Position/rotation/scale changesAnimationPlayer
Multi-object synchronizationAnimationPlayer
Complex state transitionsAnimationTree

In practice, you'll typically combine AnimatedSprite2D for basic character animation with AnimationPlayer for weapon and effect animations.

Conclusion

This course reinforced how consistently Godot's design philosophy holds together. Process Mode, Motion Mode, Collision Layers — concepts are unified and solutions to common problems are straightforward.

What stands out most is how many "I wish Unity had this built-in" features come standard: Terrains, Y-Sort, move_and_slide(), and more. For Unity developers, Godot offers a compelling combination of low learning curve and development comfort.

Godot4: Build a 2D Action-Adventure Game (Udemy)

Share this article