Lua for Game Development — Sample Game
Leeting Yan
A calm, practical handbook for building real games with Lua.
Lua is small, fast, embeddable, and battle-tested in countless shipped titles.
This book walks you from the very basics to shipping a production-ready game:
- core language & tooling
- game loops, scenes, and entities
- UI, inventory, quests, AI, and world systems
- multiplayer netcode & lag compensation
- ECS, engine architecture & optimization
- packaging, localization, and release pipelines
All examples are written in engine-agnostic Lua, so you can adapt them to Defold, LÖVE, Cocos2d-x, a custom C++ engine, or even a server-side runtime.
What You’ll Learn
Language & Foundations
- Clean, modern Lua syntax and patterns
- Tables, metatables, and light-weight OOP
- Modules, project structure, and code organization
- Coroutines and timeline scripting
Core Game Systems
- Game loops, scenes, and state management
- Entities, movement, collision helpers
- UI systems, HUD, menus & data binding
- Inventory, items, equipment, and stats
- Dialogue trees, quests, and story flags
- NPC routines, towns, factions and reputation
AI, Combat & Simulation
- Combat AI with FSM, Behavior Trees, Utility AI
- Squad tactics and boss phase scripting
- Town and economy simulation
- Timeline-driven cutscenes and cinematic effects
Multiplayer & Netcode
- Deterministic lockstep for RTS/SLG
- Snapshot interpolation for action games
- Client-side prediction and reconciliation
- Lag compensation and server rewind
- Entity replication and anti-cheat basics
Engine & Tooling
- ECS (Entity-Component-System) architecture
- Plugin/module system and hot reload
- Profiling, debugging, and memory optimisation
- Asset pipelines, data-driven content, and mod support
- Save systems, replays, and world persistence
Packaging & Release
- Project layout and build pipelines
- Asset bundling and Lua bytecode packaging
- Localization and i18n structure
- Patch/updates, DLC and mod distribution
- CI/CD integration for game builds
Who This Book Is For
- Engine devs embedding Lua into a custom runtime or C++ engine
- Indie devs using Defold, LÖVE, Cocos2d-x, Roblox/Luau or similar
- Backend/server devs building Lua-driven game logic (SLG/MMO/rooms)
- Tool builders and scripters who want clean, robust patterns
You don’t need to be a Lua expert — the book starts from zero and builds up progressively to advanced systems.
Structure & Navigation
The book is split into multiple chapters, each self-contained but designed to stack together:
- Chapters 1–6: Lua language & game programming fundamentals
- Chapters 7–12: Core gameplay systems (UI, items, quests, story)
- Chapters 13–16: NPCs, combat AI, multiplayer netcode & sync models
- Chapters 17–19: Save systems, ECS, tools, packaging & release pipeline
Use the sidebar navigation (or the chapter list) to jump into any section.
- View the full table of contents
- Or start from the beginning: Chapter 1 — Introduction
How to Use This Book
You can:
- read it linearly from Chapter 1 to 19, or
- dip into systems you care about right now (e.g., just AI or netcode), or
- use it as a design reference for your own engine/architecture.
Each chapter includes:
- engine-agnostic patterns
- clear Lua code snippets
- suggestions for integrating with popular engines
- notes on performance, determinism, and production pitfalls
Source Code & Project Skeleton
A matching GitHub project skeleton is available:
engine/— Lua ECS, systems, scheduler, net stubsgame/— sample game (scenes, entities, UI, quests)content/— items, NPCs, quests, maps, localizationtools/— build scripts, asset packers, localization checkers
You can adapt it directly or just copy patterns into your own repo.
Ready to Build?
Start with the introduction, wire up the sample engine, and ship a small prototype — then expand into AI, multiplayer, and polishing.
Happy hacking, and have fun making games with Lua.
Below is a GitHub-ready project structure with:
- Directory tree
- Short description of each folder
- Ready-to-commit README, .gitignore, minimal engine and game entry points
- A simple GitHub Actions CI workflow
- Basic tools/build script placeholder
You can literally copy this into a new repo called lua-game-dev (or whatever name you like) and start filling in code.
1. Directory Tree
lua-game-dev/
├── README.md
├── LICENSE
├── .gitignore
├── .editorconfig
├── .github/
│ └── workflows/
│ └── ci.yml
├── engine/
│ ├── core/
│ │ ├── engine.lua
│ │ ├── ecs.lua
│ │ ├── event_bus.lua
│ │ └── scheduler.lua
│ ├── systems/
│ │ ├── movement_system.lua
│ │ ├── render_system.lua
│ │ ├── physics_system.lua
│ │ └── combat_system.lua
│ ├── util/
│ │ ├── vector.lua
│ │ ├── tween.lua
│ │ └── serialize.lua
│ └── net/
│ ├── net_common.lua
│ ├── server.lua
│ └── client.lua
├── game/
│ ├── main.lua
│ ├── config/
│ │ ├── settings.lua
│ │ └── keybindings.lua
│ ├── scenes/
│ │ ├── boot_scene.lua
│ │ ├── menu_scene.lua
│ │ └── game_scene.lua
│ ├── entities/
│ │ ├── player.lua
│ │ ├── enemy_goblin.lua
│ │ └── npc_elder.lua
│ ├── ui/
│ │ ├── hud.lua
│ │ ├── inventory_ui.lua
│ │ └── dialogue_ui.lua
│ └── scripts/
│ ├── quests/
│ │ └── main_01.lua
│ └── cutscenes/
│ └── intro.lua
├── content/
│ ├── items/
│ │ ├── weapons.lua
│ │ ├── armor.lua
│ │ └── consumables.lua
│ ├── npcs/
│ │ └── villagers.lua
│ ├── quests/
│ │ └── main_quests.lua
│ ├── maps/
│ │ └── village_map.lua
│ └── localization/
│ ├── en.lua
│ └── zh.lua
├── assets/
│ ├── sprites/
│ ├── tilesets/
│ ├── audio/
│ └── fonts/
├── tools/
│ ├── build.lua
│ ├── dev_console.lua
│ ├── pack_assets.lua
│ └── check_localization.lua
├── build/
│ ├── debug/
│ └── release/
└── docs/
├── BOOK.md
├── lua-game-dev-structure.md
└── design/
├── ecs-design.md
├── netcode-design.md
└── save-system-design.md
You can trim or expand this to match your engine of choice (Defold, LÖVE, custom, etc.).
2. Root README.md
# Lua Game Dev — Engine + Sample Game
This repository is a **GitHub-ready skeleton** for the "Lua for Game Development" book:
- A small but realistic **Lua engine layer** (ECS, systems, scheduler)
- A **sample game** that uses the engine (scenes, entities, UI, quests)
- A **content pipeline** (items, NPCs, quests, maps, localization)
- Basic **tools** for building, packing, and validating content
- Hooks for **multiplayer**, **save systems**, and **modding**
You can use this as:
- A reference implementation for the book
- A starting point for your own Lua game
- A sandbox to experiment with ECS, AI, netcode, and tools
---
## Repository Layout
```text
engine/ # Core engine: ECS, systems, utilities, netcode
game/ # Game-specific scripts: scenes, entities, UI, quest logic
content/ # Data-driven content: items, NPCs, quests, maps, localization
assets/ # Art/audio/fonts (raw or packed)
tools/ # Build scripts, asset packers, dev console, checkers
build/ # Build artifacts (debug/release bundles)
docs/ # Book markdown, design docs, and technical notes
.github/ # CI workflows
Quick Start
Assumption: You have Lua (or LuaJIT) installed and some engine/runtime that can run
game/main.lua.
- Clone the repo:
git clone https://github.com/yourname/lua-game-dev.git
cd lua-game-dev
- Run the game (generic Lua runtime):
lua game/main.lua
Or integrate engine/ + game/ into your engine of choice (Defold, LÖVE, custom C++ host, etc.).
Engine Overview
The engine/ folder contains:
core/engine.lua— main game loop & scene handlingcore/ecs.lua— minimal ECS (entities, components, systems)core/event_bus.lua— global event dispatchercore/scheduler.lua— coroutine-based task/timeline systemsystems/*— example systems (movement, rendering, physics, combat)util/*— math helpers, tweening, serializationnet/*— stubs for server/client & netcode experiments
You can either use this directly or adapt it to your own engine.
Game Overview
The game/ folder includes:
main.lua— entry point that wires engine + gameconfig/— settings & keybindingsscenes/— boot, menu, gameplay scenesentities/— player, enemies, NPCsui/— HUD, inventory, dialogue UIscripts/— quests, cutscenes
It is intentionally small, but follows patterns from the book so you can extend it.
Tools
The tools/ folder is where all support scripts live:
build.lua— orchestrates build steps (pack scripts/assets intobuild/)pack_assets.lua— packs sprites/audio/etc. into bundles (PAK/ZIP/etc.)dev_console.lua— simple REPL-style dev console helpercheck_localization.lua— validates localization keys across languages
These are meant as starting points; expand as you build your own pipeline.
Docs
See docs/ for:
BOOK.md— links to all book chapters (Hugo content or raw markdown)design/*— ECS, netcode, save/load, and other design notes
CI / GitHub Actions
Under .github/workflows/ci.yml, a minimal CI job runs:
- basic Lua syntax checking (stub)
- placeholder build step (you can extend to run tests, linting, etc.)
License
Choose a license and put it in LICENSE (MIT, Apache-2.0, etc.).
By default, this skeleton assumes you’ll pick MIT or similar.
Next Steps
- Plug in a real engine (Defold, LÖVE, C++ host, etc.)
- Flesh out
engine/core/ecs.lua,engine/core/engine.lua - Implement a simple scene in
game/scenes/game_scene.lua - Hook input → player movement → rendering
- Add a tiny level in
content/maps/village_map.lua - Ship a small, complete prototype
3. .gitignore
# LuaGameDev generic ignore
# OS
.DS_Store
Thumbs.db
# Editors
.vscode/
.idea/
*.swp
*.swo
# Build output
/build/
dist/
*.log
# LuaRocks
*.rock
*.rockspec
# Temporary assets
assets/tmp/
assets/cache/
# Hugo site / generated docs (if you build a site)
public/
resources/
4. Minimal Engine Core
engine/core/engine.lua
-- engine/core/engine.lua
local Engine = {}
Engine.__index = Engine
function Engine.new()
return setmetatable({
scenes = {},
current = nil,
running = true,
}, Engine)
end
function Engine:register_scene(name, scene)
self.scenes[name] = scene
end
function Engine:switch_scene(name, ...)
local s = self.scenes[name]
assert(s, "Unknown scene: " .. tostring(name))
if self.current and self.current.leave then
self.current:leave()
end
self.current = s
if self.current.enter then
self.current:enter(...)
end
end
function Engine:update(dt)
if self.current and self.current.update then
self.current:update(dt)
end
end
function Engine:draw()
if self.current and self.current.draw then
self.current:draw()
end
end
return Engine
engine/core/ecs.lua (tiny starting point)
-- engine/core/ecs.lua
local ECS = {
next_id = 1,
entities = {},
components = {},
systems = {}
}
function ECS:create_entity()
local id = self.next_id
self.next_id = id + 1
self.entities[id] = true
return id
end
function ECS:add_component(id, name, data)
self.components[name] = self.components[name] or {}
self.components[name][id] = data
end
function ECS:get_component(id, name)
local c = self.components[name]
return c and c[id]
end
function ECS:remove_entity(id)
self.entities[id] = nil
for _, comps in pairs(self.components) do
comps[id] = nil
end
end
function ECS:add_system(fn)
table.insert(self.systems, fn)
end
function ECS:update(dt)
for _, sys in ipairs(self.systems) do
sys(self, dt)
end
end
return ECS
5. Minimal Game Entry Point
game/main.lua
-- game/main.lua
-- Entry point: wires engine + game scenes
local Engine = require("engine.core.engine")
local engine = Engine.new()
-- Simple scenes
local boot_scene = require("game.scenes.boot_scene")
local menu_scene = require("game.scenes.menu_scene")
local game_scene = require("game.scenes.game_scene")
engine:register_scene("boot", boot_scene)
engine:register_scene("menu", menu_scene)
engine:register_scene("game", game_scene)
engine:switch_scene("boot")
-- Fake main loop for plain Lua (replace with real engine loop)
local last = os.clock()
while engine.running do
local now = os.clock()
local dt = now - last
last = now
engine:update(dt)
engine:draw()
-- crude framerate cap
if dt < 1/60 then
local sleep = 1/60 - dt
if sleep > 0 then
-- naive busy-wait sleep
local t0 = os.clock()
while os.clock() - t0 < sleep do end
end
end
end
Example Boot Scene
game/scenes/boot_scene.lua:
local boot = {}
function boot:enter()
print("[Boot] Initializing game...")
-- Load content, config, etc. here (or show a loading screen).
end
function boot:update(dt)
-- For now, just jump to menu immediately.
local Engine = require("engine.core.engine") -- or pass engine via global/context
-- In a real engine, you'd have a global or injected context so you don't require here.
-- This is just a placeholder.
end
function boot:draw()
-- no-op; replace with your engine's rendering calls
end
return boot
You’ll likely adjust how engine is passed into scenes (via DI, global context, or a shared module).
6. Simple CI Workflow (.github/workflows/ci.yml)
name: CI
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Lua
uses: leafo/gh-actions-lua@v10
with:
luaVersion: "5.4"
- name: Lua syntax check (basic)
run: |
find engine game content tools -name "*.lua" -print0 | xargs -0 -n1 lua -p
- name: Placeholder build
run: |
echo "Run your build pipeline here (tools/build.lua, etc.)"
7. Minimal Build Tool (tools/build.lua)
-- tools/build.lua
-- Placeholder build orchestrator
local function run(cmd)
print("[build] " .. cmd)
os.execute(cmd)
end
local function main()
print("== Lua Game Dev Build ==")
-- Example: pack content, copy engine, etc.
-- You will adapt this to your actual engine/runtime.
run("mkdir -p build/debug")
-- TODO: add pack_assets.lua invocation, bytecode compilation, etc.
end
main()