Lua for Game Development — Chapter 13: NPCs, Town Simulation, Factions & AI Routines
Leeting Yan
NPCs breathe life into any game world:
- villagers with schedules
- merchants with stock
- guards who patrol
- hostile factions
- relationships
- dialogue that reacts to story flags
- simulation (hunger, work, needs)
- towns that evolve over time
Lua is perfect for simulation layers:
- lightweight logic
- hot reload
- easy-to-edit behavior packages
- clean data-driven content
- integrates with quests, story, and world triggers
This chapter builds:
- NPC definitions
- Behavior packages (wander, talk, patrol, work)
- Daily schedules (day/night cycles)
- Merchant system
- Factions & reputation
- Town simulation
- Social interactions
1. NPC Definitions (Data-Driven)
npcs/
villagers.lua
merchants.lua
guards.lua
1.1 Example NPC Definition
return {
elder = {
name = "Village Elder",
sprite = "npc_elder",
faction = "village",
behaviors = {"talk", "idle"},
dialogue = "elder_intro",
schedule = {
{time=0, state="sleep"},
{time=6, state="idle"},
{time=12, state="walk_square"},
{time=18, state="home"},
{time=22, state="sleep"},
}
},
}
NPC = data + behaviors + dialogue + schedule.
2. NPC Object & Behavior System
NPC behaviors are modular, like animation states.
2.1 NPC Object Class
local NPC = {}
NPC.__index = NPC
function NPC.new(data)
return setmetatable({
data = data,
x = 0, y = 0,
state = "idle",
schedule_index = 1,
}, NPC)
end
2.2 Behavior Packages
Stored in behaviors/.
wander.lua
return {
update = function(npc, dt)
npc.x = npc.x + math.random(-20,20) * dt
npc.y = npc.y + math.random(-20,20) * dt
end
}
talk.lua
return {
interact = function(npc, player)
DialogueSystem:start(npc.data.dialogue)
end
}
patrol.lua
return {
update = function(npc, dt)
-- follow waypoints
local wp = npc.data.waypoints[npc.waypoint_index]
npc:move_to(wp, dt)
end
}
All behaviors share a standard interface.
2.3 NPC Update
function NPC:update(dt)
local behaviors = self.data.behaviors
for _, b in ipairs(behaviors) do
local pkg = require("behaviors."..b)
if pkg.update then
pkg.update(self, dt)
end
end
end
3. Daily Schedules (Day/Night Simulation)
NPCs follow time-based routines.
3.1 Update Schedule Based on Time
function NPC:apply_schedule(hour)
for i = #self.data.schedule,1,-1 do
if hour >= self.data.schedule[i].time then
self.state = self.data.schedule[i].state
return
end
end
end
3.2 Day/Night Integration
Called every in-game minute:
for _, npc in pairs(NPC_LIST) do
npc:apply_schedule(WorldHour)
end
Common routines:
- villagers sleep at night
- guards patrol at night
- merchants open shops during day
- farmers work during morning
4. Merchant System (Economy Basics)
Defined with:
shops/
potions.lua
weapons.lua
4.1 Example Merchant Data
return {
items = {
{id="potion_small", price=20},
{id="potion_large", price=50},
},
restock_rate = 24, -- in-game hours
}
4.2 Merchant NPC Behavior
return {
interact = function(npc, player)
ShopUI:open(npc.data.shop_id)
end
}
4.3 Restocking
function Shop:restock(shop_data)
for _, item in ipairs(shop_data.items) do
item.stock = math.random(1,5)
end
end
Triggered by:
if WorldHour % shop.restock_rate == 0 then
Shop:restock(shop_data)
end
5. Factions & Reputation
Faction examples:
- village
- bandits
- merchants
- kingdom
- demons
- wilderness creatures
5.1 Faction Data
factions = {
village = {name="Village", hostility=0},
bandits = {name="Bandits", hostility=1},
}
5.2 Reputation System
local Reputation = {}
Reputation.__index = Reputation
function Reputation.new()
return setmetatable({rep={}}, Reputation)
end
function Reputation:add(faction, amount)
self.rep[faction] = (self.rep[faction] or 0) + amount
end
return Reputation
5.3 NPC Reaction Based on Reputation
function NPC:reaction(player)
local rep = player.reputation.rep[self.data.faction] or 0
if rep < -10 then
return "hostile"
elseif rep < 0 then
return "unfriendly"
else
return "neutral"
end
end
6. Guard & Aggro Behavior (AI Combat)
If faction is hostile, NPC attacks.
if npc:reaction(player) == "hostile" then
npc.state = "attack"
end
Attack behavior:
return {
update=function(npc, dt)
local dx = player.x - npc.x
local dy = player.y - npc.y
local d = math.sqrt(dx*dx + dy*dy)
if d < 50 then
player:take_damage(5)
else
npc.x = npc.x + dx/d * dt * 50
npc.y = npc.y + dy/d * dt * 50
end
end
}
7. Town Simulation (Lightweight Economy)
Simple simulation:
- villagers produce goods
- merchants consume goods
- food supply changes with population
- shops restock based on supply
7.1 Town Resource Data
Town = {
food = 100,
goods = 20,
population = 12,
}
7.2 Daily Update
function Town:update_daily()
self.food = self.food - self.population * 2
self.goods = self.goods + math.random(0,2)
if self.food < 0 then
event:emit("town_starving")
end
end
Called every 24 in-game hours.
8. NPC Social Interactions
NPCs interact with each other:
- greetings
- gossip
- sharing rumors
- giving quest hints
- reacting to player reputation
This is lightweight but adds life.
8.1 NPC Gossip System
function NPC:gossip(other)
if math.random() < 0.1 then
-- trade rumor
local rumor = "weather_rain" -- example
npc:learn_rumor(rumor)
end
end
8.2 NPC-to-NPC Interaction Update
for _, a in ipairs(NPC_LIST) do
for _, b in ipairs(NPC_LIST) do
if a ~= b and distance(a, b) < 40 then
a:gossip(b)
end
end
end
9. Story Integration
NPCs react to story flags.
9.1 Dialogue Changes
if quest_manager.flags["main_01"] then
npc.dialogue = "elder_after_quest"
end
9.2 World State Changes
if Town.food < 10 then
spawn("starving_villager", 200, 200)
end
10. Putting It All Together — Example Village Simulation
-- daily town tick
Town:update_daily()
-- update NPC behaviors
for _, npc in pairs(NPC_LIST) do
npc:apply_schedule(WorldHour)
npc:update(dt)
end
-- social interactions
NPCSocial:update(dt)
-- reactions to player
for _, npc in pairs(NPC_LIST) do
if npc:reaction(player) == "hostile" then
npc.state = "attack"
end
end
This simulates a living village.
11. Summary of Chapter 13
You now understand:
- NPC definition & modular behavior system
- Daily routines and schedule-driven AI
- Merchant system & restocking
- Town economy simulation
- Faction and reputation logic
- Hostile/neutral/friendly reactions
- NPC-to-NPC interactions
- Story integration
This system supports:
- RPG villages
- Survival colony sims
- Open-world games
- Town building games
- Strategy games with civilian life
Lua handles these simulation layers elegantly.