AI Procedural Content Generation for Games PCG System

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI Procedural Content Generation for Games PCG System
Complex
~2-4 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

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.