Learning From The GameDev Tavern Beat 'em Up Tutorial Series
A new YouTube channel called The GameDev Tavern just released their first tutorial series a few days ago. His approach to teaching Godot 4 through a beat 'em up is amazingly thorough and thoughtful. This post is both a way to say thank you and to document my learnings throughout his 20-part tutorial.
Instead of just following along, I'm taking a more deliberate approach by writing down and categorizing my lessons learned with each video. This helps me with absorbing the material and building a reference I can return to later. Each part of the tutorial has provided insights into game design principles, technical architecture decisions, and Godot-specific features.
A Different Kind of Tutorial Experience
As someone who's comfortable coding and has built prototypes in various engines (some with and some without tutorials), The GameDev Tavern's beat 'em up game tutorial series has been the first instance where I've felt like I've gotten a highly relatable and focused glimpse into someone else's approach to making games.
Much like traditional education, the value you get from tutorials largely depends on how you engage with them. Simply following along and copying code might help you complete a project, but taking time to understand concepts, experiment with variations, and document your insights can lead to much deeper learning and retention. That's why I'm taking a more deliberate approach with this series.
Tracking the Learning Journey
While I follow along with the tutorial, I'm taking notes and categorizing what I'm learning, or just getting more practice with, in each lesson. I decided to bin these takeaways into three categories:
- Godot-Specific: Engine features, best practices, and workflow optimizations
- Technical Architecture: How to structure game systems and manage complexity
- Game Design: Everything else like core principles and other decisions that shape the player experience
While many of these are not new concepts, I've still found noting where and when I encounter them to be valuable in order to better cement the theory and the practice. You can hover or search in my notes below to see what I've taken away from this tutorial series.
Don't let engine version differences or technical hiccups when working through a tutorial discourage you. These bumps in the road are actually valuable learning opportunities that prepare you for real game development challenges you'll face in your own projects.
My Takeaways from the Tutorial
All Tutorial Takeaways
Godot-Specific
- Part 1: customizable UI through managing editor features
- Part 1: scenes, nodes, scene tree, and signals
- Part 1: organizing scenes and characters
- Part 1: smoothing pixel art via nearest texture filter
- Part 1: physics body nodes (rigid, static, character, animatable)
- Part 1: normalizing movement trajectory to 1 pixel per second via Input.get_vector
- Part 1: move_and_slide() to get collision detection going
- Part 2: animations - Bezier curves to smoothly swap between frames
- Part 2: setting keyframes per movement
- Part 2: @onready annotation for initialization
- Part 2: $ shortcut for get_node
- Part 2: flip_h as an easy way to manipulate sprites
- Part 2: function organization and renaming layers for cleaner collision settings
- Part 3: y-sorting in the scene tree to ensure sprites are rendered in the correct order
- Part 3: Area2D nodes for overlap detection (hits and collision)
- Part 3: editable children nodes in the scene tree
- Part 3: queue free for deleting nodes
- Part 5: array manipulation (filter/sort/etc)
- Part 5: silencing specific warnings through project settings
- Part 5: searching for all usages of a function
- Part 6: more animation cycles with damage emitting and callbacks assigned at key frames
- Part 6: filtering methods within classes
- Part 6: do not sync to physics when not using process physics functions
- Part 7: collisions for layers (where it lives) and masks (where it checks)
- Part 7: reps lining up character/weapon animation frames with existing animations
- Part 7: RayCast2D to know if there is a character in the current path of the projectile
- Part 7: accessing the camera to calculate enemy AI combat behavior
- Part 8: setting up a collectibles/weapons scene
- Part 8: organizing scenes into collision layers upon setup
- Part 8: animation via basic rotation (discrete to show frame-by-frame)
- Part 8: debugging tips and exercises (enable collision shapes, breakpoints, inspecting vars)
- Part 8: interaction issues: check collision layers and masks, confirm monitoring/monitorable for Area2D
- Part 9: project settings > global > autoload: creates a local + remote state for inspection in the editor. can debug the game as it is running through this.
- Part 9: using Node2D as a means to track position of objects in the scene
- Part 10: more reps with quickly going through animation keyframe assignments
- Part 10: more reps with quickly going through animation keyframe assignments and setting callbacks for those that do not loop
- Part 10: Line2D for projectiles, manipulating points, calculating shot trajectory
- Part 10: gradient manipulation for projectiles
- Part 10: linear interpolation (lerp)
- Part 11: relocating the monitorable/monitoring state for weapons within the inherited class
- Part 11: call_deferred for invoking during idle time (not immediately)
- Part 11: modulate.a -= delta for fading out upon loss
- Part 11: housekeeping and organizing via labels in the editor (export_category, export_group, export_subgroup)
- Part 12: more reps with assigning layers/masks to interact with damage systems and boundaries
- Part 13: Resource: simple data containers. all props must be exposed via exports
- Part 13: second instance of assigning a top-level node along with configuring autoloading
- Part 14: position smoothing for more gradual camera movement
- Part 14: re-instantiating child nodes after adding scripts for debugging
- Part 14: the _init() function is called before _ready() and useful for binding signals
- Part 14: Resource cannot use @export Node2D
- Part 15: using the canvas layer to manage UI elements
- Part 15: supporting multiple resolutions with the canvas layer (layout > Full Rect)
- Part 16: ability to create a theme in the UI (Godot itself is built using their UI)
- Part 16: how to override various components within the theme
- Part 17: audio tab to specify channels for audio nodes (SFX, Music, UI, etc.)
- Part 17: adding a scene to the global autoload context
- Part 17: dealing with race conditions in the init cycle for audio music
- Part 18: building custom UI components outside of the pre-built theme control (vbox, margin, etc.)
- Part 18: saving branches as scenes
- Part 18: running current scene instead of whole game (could be great for progression testing)
- Part 18: more reps with input map, default actions, custom actions, and input events
- Part 19: one way to pause the game (access the root node via get_tree() and pause)
- Part 19: changing the mode of the _process behavior for any node
- Part 19: AudioServer for modifying a bus volume via set_bus_volume_db() and linear_to_db() for converting ints properly to decibels
- Part 20: timer nodes to easily keep track of timing events and react via the timeout() signal
- Part 20: camera.reset_smoothing() to snap the camera position when the stage is loaded
- Part 20: having music loop via it's loop toggle on import
Technical Architecture
- Part 1: clean inheritance for similar character/enemy classes
- Part 1: mapping inputs to consistent actions
- Part 2: state machines (character can only be in one state at a time) (transitions are rules that allows you to change states)
- Part 3: key frame function calls
- Part 3: signals for broadcasting messages (signal up, call down)
- Part 4: calling back through animation keyframes
- Part 4: mapping all animations to states in a single source of truth
- Part 4: more reps on how to add new states to state machines
- Part 5: superclass function calls
- Part 5: disabling collision based on state
- Part 6: speeding up frames for the sake of better gameplay
- Part 7: organizing weapon sprites to align with the character spritesheets to layer their visibility
- Part 7: more animation callbacks for influencing character state
- Part 8: more reps with inheritance (and the limitations of inheritance)
- Part 8: spritesheet layout being exactly even for enemies lets you reuse all the animation logic around frames
- Part 9: event/signal bus: a global node object at the top of the scene tree which is accessible through all objects/scripts in the game. a common downside is that global objects can be hard to manage and debug.
- Part 10: more reps with the global event bus and entity manager systems
- Part 11: tying ammo to the unique character+weapon combinations
- Part 12: more inheritance overrides and super calls for more reusable code
- Part 13: making a base stage scene for all levels to inherit from
- Part 13: how to hide a node before it's needed (make it invisible or don't let them exist until they're needed)
- Part 13: managing camera through global event bus (stage manager
- Part 14: a way to orphan/parent within the scene tree via reparent and signaling
- Part 14: dealing with race conditions within an event bus can be tricky. confirm the emits/receives as a first point.
- Part 15: more signal/event bus usage for distant node communication
- Part 20: always being mindful of freeing up and cleaning resources when they're not actively needed like during stage transitions
- Part 20: wiping and loading entire stage state in a controlled way to accommodate flush race conditions
Game Design
- Part 1: leveraging delta (time since last frame rendered) for normalizing movement across computers with varying specs so that it's pixels per second
- Part 3: damage systems
- Part 3: how to make damaging objects feel good to hit (gravity, fade-out)
- Part 4: jumping (take-off > airtime > land)
- Part 4: knock back intensity for damaging characters (knock back intensity, knock back speed, knock back direction)
- Part 4: having enemies swarm a player
- Part 5: how to have enemies queue up to attack a player
- Part 5: splitting out hit types for different damage types
- Part 5: time calculations for animations (when to get up after falling)
- Part 6: death states across players and enemies
- Part 6: rotating animations and damage values within combos
- Part 6: rewarding players for landing multiple hits in a row
- Part 7: enemy AI for balanced combat
- Part 7: sharing weapon principles across types
- Part 8: when faking a 3D view, useful to put a shadow just to give the impression of depth/height
- Part 9: using global positions and relative positions for placing objects across a scene
- Part 10: multiple weapons in a single scene
- Part 10: giving players some breathing room for dealing with ranged attacks
- Part 11: preventing unlimited use of weapons
- Part 11: placing items in containers
- Part 11: balancing combat combos with resets for non-aggressive actions
- Part 12: embedding story within a boss fight and giving them distinct mechanics like the ability to block and dashing
- Part 12: adding friction to the ground for a more natural knockback feel
- Part 13: stage and checkpoint design for fluid brawler flow
- Part 13: balancing enemy spawn rate to prevent swarming
- Part 14: a way for enemies to drop in from the sky but give a player heads up (via shadows)
- Part 14: door for having enemies spawn from a door via some simple positioning manipulation
- Part 15: the value in using tons of animations/states to give more variety to the player engagement
- Part 15: how to incorporate UI elements into the game
- Part 16: how to translate combat into rewarding scoring (encourage aggression for higher score)
- Part 16: giving hits more oopmh through animations
- Part 17: simply making pitch varied for sfx for more interesting audio
- Part 17: camera shake for giving more impact to certain hits
- Part 18: options considerations (animations, audio, etc.)
- Part 19: pausing movement when attacks are happening to give combat more depth and less floaty feel
- Part 20: game over state that can only be engaged with at the cost of score performance
- Part 20: stage transitions and reusable components throughout. how to orchestrate it from the world view.
- Part 20: how to think about player spawn locations and who is responsible for them
- Part 20: making stage transitions feel clean through a simple curtain hide and reveal