AI Procedural Content Generation for Games
Procedural Content Generation (PCG) enables creating game worlds, quests, dialogues, items, and events algorithmically — without manually creating each element. AI approaches extend classical PCG: neural networks generate semantically coherent narratives, LLMs create adaptive NPC dialogues, diffusion models produce unique textures and sprites.
Game World and Biome Generator
from openai import AsyncOpenAI
from dataclasses import dataclass, field
import json
import random
import numpy as np
client = AsyncOpenAI()
@dataclass
class WorldConfig:
seed: int
size: tuple # (width, height) in tiles
biomes: list[str] # ["forest", "desert", "tundra", "swamp"]
civilization_level: str # primitive, medieval, industrial, futuristic
magic_system: bool = True
danger_zones: int = 5
settlements: int = 10
class ProceduralWorldGenerator:
def __init__(self, config: WorldConfig):
self.config = config
self.rng = random.Random(config.seed)
self.np_rng = np.random.default_rng(config.seed)
def generate_heightmap(self) -> np.ndarray:
"""Generate height map via Perlin noise (opensimplex)"""
from opensimplex import OpenSimplex
noise = OpenSimplex(seed=self.config.seed)
w, h = self.config.size
heightmap = np.zeros((h, w))
# Octave noise for realistic terrain
for y in range(h):
for x in range(w):
nx, ny = x / w, y / h
heightmap[y][x] = (
1.0 * noise.noise2(1 * nx, 1 * ny) +
0.5 * noise.noise2(2 * nx, 2 * ny) +
0.25 * noise.noise2(4 * nx, 4 * ny) +
0.125 * noise.noise2(8 * nx, 8 * ny)
)
return (heightmap + 1) / 2 # normalize to [0, 1]
def assign_biomes(self, heightmap: np.ndarray, moisture_map: np.ndarray) -> np.ndarray:
"""Biome assignment by Whittaker biome diagram"""
biome_map = np.zeros_like(heightmap, dtype=int)
BIOME_RULES = [
(0.0, 0.3, "ocean"),
(0.3, 0.4, "beach"),
(0.4, 0.6, "plains"),
(0.6, 0.8, "forest"),
(0.8, 0.9, "mountain"),
(0.9, 1.0, "snow_peak")
]
biome_ids = {b[2]: i for i, b in enumerate(BIOME_RULES)}
for y in range(heightmap.shape[0]):
for x in range(heightmap.shape[1]):
h = heightmap[y][x]
m = moisture_map[y][x]
# Account for moisture in mixed biomes
if 0.4 < h < 0.8 and m < 0.3:
biome_map[y][x] = biome_ids.get("desert_variant", 2)
else:
for min_h, max_h, biome_name in BIOME_RULES:
if min_h <= h < max_h:
biome_map[y][x] = biome_ids[biome_name]
break
return biome_map
def place_settlements(self, heightmap: np.ndarray, biome_map: np.ndarray) -> list[dict]:
"""Place settlements in suitable habitable locations"""
settlements = []
valid_positions = np.argwhere(
(heightmap > 0.4) & (heightmap < 0.7) & (biome_map != 0)
)
chosen = self.np_rng.choice(
len(valid_positions),
size=min(self.config.settlements, len(valid_positions)),
replace=False
)
for idx in chosen:
y, x = valid_positions[idx]
settlements.append({
"x": int(x), "y": int(y),
"type": self.rng.choice(["village", "town", "city", "fortress"]),
"population": self.rng.randint(50, 10000),
"name": "" # populate via LLM
})
return settlements
LLM World Narrative Generation
async def generate_world_lore(
world_config: WorldConfig,
settlements: list[dict],
biomes: list[str]
) -> dict:
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""You are a narrative designer for a game with procedurally generated world.
Create coherent world history. Civilization level: {world_config.civilization_level}.
Magic: {"yes" if world_config.magic_system else "no"}.
Return JSON: {{
world_name: "...",
history_eras: [{{name, years_ago, key_event}}],
factions: [{{name, ideology, home_biome, relation_to_others}}],
settlement_names: [{{id, name, local_legend}}],
notable_artifacts: [{{name, description, location_hint}}],
creation_myth: "...",
current_conflict: "main conflict of the age"
}}"""
}, {
"role": "user",
"content": f"""
Biomes: {', '.join(biomes)}
Settlements: {len(settlements)}, types: {[s['type'] for s in settlements[:5]]}...
Danger zones: {world_config.danger_zones}
World seed: {world_config.seed}
"""
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
Procedural Quest Generation
QUEST_TEMPLATES = {
"fetch": {
"structure": "Obtain [item] from [NPC/location] and bring to [employer]",
"complications": ["item is guarded", "NPC requires help in return", "multiple claimants"]
},
"eliminate": {
"structure": "Eliminate [threat] in [location]",
"complications": ["threat is innocent victim", "final boss is hidden", "collateral damage"]
},
"escort": {
"structure": "Escort [character] from [A] to [B]",
"complications": ["character hides secret", "ambushes on route", "moral choice at end"]
},
"investigation": {
"structure": "Investigate [event] at [place]",
"complications": ["multiple suspects", "false lead", "evidence destroyed"]
}
}
async def generate_quest(
template_type: str,
world_lore: dict,
player_level: int,
location: dict
) -> dict:
template = QUEST_TEMPLATES[template_type]
complication = random.choice(template["complications"])
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""Create a quest for an RPG game. Player level: {player_level}.
Template: {template['structure']}.
Complication: {complication}.
Use factions and world history for context.
Return JSON: {{
title, description, giver_npc, objectives: [{{id, text, optional: bool}}],
rewards: {{xp, gold, items: []}},
moral_choice: {{description, option_a, option_b, consequences}},
estimated_time_minutes: int
}}"""
}, {
"role": "user",
"content": f"World: {json.dumps(world_lore, ensure_ascii=False)[:1000]}\nLocation: {location}"
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
Adaptive NPC Dialogue
@dataclass
class NPCProfile:
name: str
race: str
occupation: str
faction: str
personality: list[str] # ["suspicious", "greedy", "loyal"]
knowledge: list[str] # what NPC knows about the world
relationship: str # "friendly", "neutral", "hostile"
memory: list[dict] = field(default_factory=list) # dialogue history
async def generate_npc_response(
npc: NPCProfile,
player_input: str,
game_context: dict
) -> dict:
memory_context = "\n".join([
f"[{m['timestamp']}] Player: {m['player']} → NPC: {m['npc']}"
for m in npc.memory[-5:] # last 5 exchanges
])
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""You are an NPC in an RPG. Play the role strictly.
NPC: {npc.name}, {npc.race}, {npc.occupation}
Faction: {npc.faction} | Traits: {', '.join(npc.personality)}
Attitude toward player: {npc.relationship}
Knows: {', '.join(npc.knowledge)}
Dialogue history:
{memory_context}
Respond in character. Stay in role.
Return JSON: {{
speech: "NPC line",
emotion: "neutral|happy|angry|scared|suspicious",
action: null | "give_item" | "start_quest" | "attack" | "flee",
hint: null | "player hint if appropriate"
}}"""
}, {
"role": "user",
"content": f"Player says: {player_input}\nContext: {game_context.get('location', 'unknown')}"
}],
response_format={"type": "json_object"}
)
result = json.loads(response.choices[0].message.content)
npc.memory.append({"timestamp": "now", "player": player_input, "npc": result["speech"]})
return result
Procedural Item and Loot Generation
ITEM_RARITIES = {
"common": {"prob": 0.60, "affix_count": (0, 1), "base_multiplier": 1.0},
"uncommon": {"prob": 0.25, "affix_count": (1, 2), "base_multiplier": 1.3},
"rare": {"prob": 0.10, "affix_count": (2, 3), "base_multiplier": 1.7},
"epic": {"prob": 0.04, "affix_count": (3, 4), "base_multiplier": 2.5},
"legendary": {"prob": 0.01, "affix_count": (4, 5), "base_multiplier": 4.0},
}
def generate_item(
item_type: str,
player_level: int,
world_theme: str,
rng: random.Random
) -> dict:
# Choose rarity by weight
rarity = rng.choices(
list(ITEM_RARITIES.keys()),
weights=[v["prob"] for v in ITEM_RARITIES.values()]
)[0]
spec = ITEM_RARITIES[rarity]
base_stats = {
"damage": player_level * 5 * spec["base_multiplier"] if item_type == "weapon" else 0,
"defense": player_level * 3 * spec["base_multiplier"] if item_type == "armor" else 0,
"durability": rng.randint(50, 100)
}
# Affixes from theme-based pool
AFFIXES = {
"fantasy": ["of Flames", "of the Ancient", "Cursed", "Holy", "Shadow"],
"scifi": ["Mk.II", "Prototype", "Military Grade", "Corrupted", "Quantum"]
}
prefix_pool = AFFIXES.get(world_theme, AFFIXES["fantasy"])
affixes = rng.sample(prefix_pool, k=rng.randint(*spec["affix_count"]))
return {
"name": f"{' '.join(affixes)} {item_type.title()}",
"rarity": rarity,
"type": item_type,
"stats": base_stats,
"level_requirement": max(1, player_level - 2)
}
PCG system with world generation, quests, and NPC dialogues — 6–8 weeks. Full-featured PCG engine with adaptive difficulty balancing, procedural textures, and Unity/Unreal integration — 4–6 months.







