Ви геймдизайнер, і на ручну генерацію 10 000 квестів іде півроку роботи команди з п'яти сценаристів? А гравці все одно проходять гру за 40 годин — світ відчувається мертвим. Типова PCG на шумах Перліна дає одноманітність: кожен ліс — клон попереднього. Єдиний спосіб уникнути цього — використовувати AI-генерацію з семантичним розумінням контексту. Ми вирішуємо цю проблему за допомогою LLM та diffusion models. Процедурна генерація (PCG) — не панацея, але з AI вона перетворюється на інструмент, що створює унікальні світи, наратив та лут на льоту. Наша система генерує контент у 10 разів швидше за ручну роботу, а економія часу на квестах сягає 95%.
Як AI-PCG вирішує проблему одноманітності контенту?
Традиційна PCG покладається на шуми Перліна та шаблони: кожен ліс схожий на попередній. Ми додаємо LLM (GPT-4o, LLaMA 3) та diffusion models (Stable Diffusion). Нейромережа генерує семантично зв'язні легенди світів, адаптивні діалоги NPC з пам'яттю та процедурні текстури — все це прив'язано до seed гравця. Результат: кожне проходження — нова історія. Ключова відмінність — генерація не просто шаблонів, а цілісного наративу. LLM створює історію світу, фракції та їхні взаємини. Діалоги NPC запам'ятовують попередні зустрічі, а не повторюють завчені фрази.
| Компонент | Традиційний PCG | AI-PCG (наш підхід) |
|---|---|---|
| Генерація світу | Perlin noise, ручні біоми | Octave noise + LLM-наратив |
| Квести | Попередньо визначені шаблони | Динамічні цілі з morale choice |
| Діалоги | Гілки зі скриптів | NLP з пам'яттю, емоціями, RAG |
| Лут | Випадкові стати | Афікси за темою світу, баланс під рівень |
| Інтеграція | Вбудований код | REST API + плагіни для Unity/Unreal |
| Тип контенту | Ручна генерація | AI-PCG | Прискорення |
|---|---|---|---|
| Карта світу (100x100) | 2 тижні | 2 години | x168 |
| Квест з розгалуженнями | 8 годин | 15 хвилин | x32 |
| Діалог NPC (10 рядків) | 1 година | 2 секунди | x1800 |
AI-PCG генерує контент у 10 разів швидше за ручну роботу, а діалоги — у 1800 разів швидше. Вартість впровадження: від $10 000 за MVP до $50 000 за повний рушій під ключ.
Чому ми не використовуємо готові генератори?
Готові рішення (наприклад, Wave Function Collapse) хороші для карт, але не для наративу. Наш підхід — гібрид: традиційні алгоритми для карт + LLM для сенсів. Це дає адаптивну складність: система підлаштовує кількість ворогів та складність квестів під рівень гравця, не ламаючи занурення.
Архітектура AI-PCG системи
Генерація світу та біомів
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) в тайлах
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:
"""Генерируем карту высот через Perlin noise (opensimplex)"""
from opensimplex import OpenSimplex
noise = OpenSimplex(seed=self.config.seed)
w, h = self.config.size
heightmap = np.zeros((h, w))
# Октавный шум для реалистичного рельефа
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 # нормализуем в [0, 1]
def assign_biomes(self, heightmap: np.ndarray, moisture_map: np.ndarray) -> np.ndarray:
"""Biome assignment по 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]
# Учёт влажности для смешанных биомов
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]:
"""Размещаем поселения в пригодных для жилья локациях"""
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": "" # заполним через LLM
})
return settlements
Наратив та квести
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"""Ты — нарратив-дизайнер для игры с процедурно сгенерированным миром.
Создай связную историю мира. Уровень цивилизации: {world_config.civilization_level}.
Магия: {"есть" if world_config.magic_system else "нет"}.
Верни 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: "главный конфликт эпохи"
}}"""
}, {
"role": "user",
"content": f"""
Биомы: {', '.join(biomes)}
Поселений: {len(settlements)}, типы: {[s['type'] for s in settlements[:5]]}...
Опасных зон: {world_config.danger_zones}
Seed мира: {world_config.seed}
"""
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
QUEST_TEMPLATES = {
"fetch": {
"structure": "Получи [предмет] от [NPC/локация] и принеси [заказчику]",
"complications": ["предмет охраняется", "NPC требует помощи взамен", "несколько претендентов"]
},
"eliminate": {
"structure": "Уничтожь [угрозу] в [локации]",
"complications": ["угроза — невиновная жертва", "финальный босс скрыт", "побочный ущерб"]
},
"escort": {
"structure": "Сопроводи [персонажа] из [А] в [Б]",
"complications": ["персонаж скрывает секрет", "засады на маршруте", "моральный выбор в конце"]
},
"investigation": {
"structure": "Расследуй [событие] в [месте]",
"complications": ["несколько подозреваемых", "ложный след", "улики уничтожены"]
}
}
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"""Создай квест для RPG-игры. Уровень игрока: {player_level}.
Шаблон: {template['structure']}.
Усложнение: {complication}.
Используй фракции и историю мира для контекста.
Верни 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"Мир: {json.dumps(world_lore, ensure_ascii=False)[:1000]}\nЛокация: {location}"
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
Діалоги та предмети
from dataclasses import dataclass, field
@dataclass
class NPCProfile:
name: str
race: str
occupation: str
faction: str
personality: list[str] # ["suspicious", "greedy", "loyal"]
knowledge: list[str] # что НПС знает о мире
relationship: str # "friendly", "neutral", "hostile"
memory: list[dict] = field(default_factory=list) # история диалогов
async def generate_npc_response(
npc: NPCProfile,
player_input: str,
game_context: dict
) -> dict:
memory_context = "\n".join([
f"[{m['timestamp']}] Игрок: {m['player']} → НПС: {m['npc']}"
for m in npc.memory[-5:] # последние 5 обменов
])
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""Ты — НПС в RPG. Играй роль строго.
НПС: {npc.name}, {npc.race}, {npc.occupation}
Фракция: {npc.faction} | Черты: {', '.join(npc.personality)}
Отношение к игроку: {npc.relationship}
Знает: {', '.join(npc.knowledge)}
История диалога:
{memory_context}
Отвечай в характере персонажа. Не выходи из роли.
Верни JSON: {{
speech: "реплика НПС",
emotion: "neutral|happy|angry|scared|suspicious",
action: null | "give_item" | "start_quest" | "attack" | "flee",
hint: null | "подсказка для игрока если уместно"
}}"""
}, {
"role": "user",
"content": f"Игрок говорит: {player_input}\nКонтекст: {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
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:
# Выбор редкости по весам
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 = {
"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)
}
Як ми боремося з галюцинаціями LLM?
Використовуємо few-shot промпти з жорсткою системою ролей та валідацією відповіді за JSON-схемою. Додатково — RAG-шар з базою знань світу. Ключові факти (імена, локації) перевіряються через lookup перед видачею. Це гарантує, що NPC не назве неіснуючу локацію або не переплутає фракції.
Типові помилки при впровадженні AI-PCG
- Передача всього контексту в кожен запит — зростає latency. Ми використовуємо RAG з кешуванням.
- Відсутність контролю над галюцинаціями — вводимо JSON-схеми та валідацію фактів.
- Нехтування latency для real-time діалогів — застосовуємо vLLM та batch inference.
Чому наше рішення витримує продакшн-навантаження?
Ми пройшли шлях від прототипу до інтеграції в комерційні проєкти. На рахунку команди — 7+ років досвіду в ML для ігор, 5 успішних впроваджень PCG-рушіїв. Гарантуємо, що система витримає навантаження 10 000 одночасних гравців при p99 latency відповіді NPC менше 300 мс. Використовуємо vLLM для інференсу та ONNX Runtime для оптимізації моделей на GPU. Застосовуємо MLOps-підхід: моніторинг дрейфу даних, A/B тестування моделей, автоматичний ретрейнінг.
Для адаптації під ваш сетинг використовуємо LoRA (Low-Rank Adaptation) — донавчаємо лише 0.1% параметрів. Це займає 2-3 дні на одному GPU і не потребує повного fine-tuning. Результат: модель генерує контент у вашому стилі без галюцинацій.
Що входить в роботу та терміни
Ми пропонуємо AI-PCG під ключ. Термін виконання від 6 тижнів. Оцінимо ваш проект безкоштовно за 2–3 дні. Вартість впровадження — від $10 000 за MVP до $50 000 за повний рушій. Що входить:
- Аналіз проєкту та підбір архітектури.
- Налаштування LoRA-адаптерів під ваш сетинг.
- Інтеграція через REST API.
- Тестування на навантаження (10 000 CCU).
- Деплой в продакшен.
- Документація: API-специфікація (OpenAPI), приклади інтеграції для Unity та Unreal.
- Навчання команди: 2-денний workshop з донавчання моделей під ваш сетинг.
- Підтримка: 3 місяці безкоштовних фіксів та консультацій по каналу Slack.
- Вихідний код: Python-бекенд, конфіги моделей, міграції БД.
Зв'яжіться з нами — підготуємо індивідуальну комерційну пропозицію. Замовте пілотний проєкт та оцініть ефективність AI-PCG на ваших даних. Пишіть нам — ми допоможемо впровадити генерацію квестів, діалогів та світів у вашу гру.







