AI-движок процедурной генерации (PCG) для игр: миры, квесты, диалоги

Вы геймдизайнер, и на ручную генерацию 10 000 квестов уходит полгода работы команды из пяти сценаристов? А игроки всё равно проходят игру за 40 часов — мир ощущается мёртвым. Типичная PCG на шумах Перлина даёт однообразие: каждый лес — клон предыдущего. Единственный способ избежать этого — использов

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1003

Вы геймдизайнер, и на ручную генерацию 10 000 квестов уходит полгода работы команды из пяти сценаристов? А игроки всё равно проходят игру за 40 часов — мир ощущается мёртвым. Типичная PCG на шумах Перлина даёт однообразие: каждый лес — клон предыдущего. Единственный способ избежать этого — использовать AI-генерацию с семантическим пониманием контекста. Мы решаем эту проблему с помощью LLM и diffusion models. Процедурная генерация (PCG) — не панацея, но с AI она превращается в инструмент, создающий уникальные миры, нарратив и лут на лету. Наша система генерирует контент в 10 раз быстрее ручной работы, а экономия времени на квестах достигает 95%.

Как AI-PCG решает проблему однообразия контента?

Традиционная PCG полагается на шумы Перлина и шаблоны: каждый лес похож на предыдущий. Мы добавляем LLM (GPT-4o, LLaMA 3) и diffusion models (Stable Diffusion). Нейросеть генерирует семантически связные легенды миров, адаптивные диалоги НПС с памятью и процедурные текстуры — всё это привязано к seed игрока. Результат: каждое прохождение — новая история. Ключевое отличие — генерация не просто шаблонов, а целостного нарратива. LLM создаёт историю мира, фракции и их взаимоотношения. Диалоги НПС запоминают предыдущие встречи, а не повторяют заученные фразы.

Компонент Традиционный 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
Диалог НПС (10 строк) 1 час 2 секунды x1800

Почему мы не используем готовые генераторы?

Готовые решения (например, 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 перед выдачей. Это гарантирует, что НПС не назовёт несуществующую локацию или не перепутает фракции.

Типичные ошибки при внедрении AI-PCG

  • Передача всего контекста в каждый запрос — растёт latency. Мы используем RAG с кэшированием.
  • Отсутствие контроля над галлюцинациями — вводим JSON-схемы и валидацию фактов.
  • Неучёт latency для real-time диалогов — применяем vLLM и batch inference.

Почему наше решение выдерживает продакшн-нагрузки?

Мы прошли путь от прототипа до интеграции в коммерческие проекты. На счету команды — 7+ лет опыта в ML для игр, 5 успешных внедрений PCG-движков. Гарантируем, что система выдержит нагрузку 10 000 одновременных игроков при p99 latency ответа НПС менее 300 мс. Используем vLLM для инференса и ONNX Runtime для оптимизации моделей на GPU. Применяем MLOps-подход: мониторинг дрейфа данных, A/B тестирование моделей, автоматический ретрайнинг.

Для адаптации под ваш сеттинг применяем LoRA (Low-Rank Adaptation) — дообучаем только 0.1% параметров. Это занимает 2-3 дня на одном GPU и не требует полного fine-tuning. Результат: модель генерирует контент в вашем стиле без галлюцинаций.

Что входит в работу и сроки

  • Анализ проекта и подбор архитектуры.
  • Настройка LoRA-адаптеров под ваш сеттинг.
  • Интеграция через REST API.
  • Тестирование на нагрузку (10 000 CCU).
  • Деплой в продакшен.
  • Документация: API-спецификация (OpenAPI), примеры интеграции для Unity и Unreal.
  • Обучение команды: 2-дневный workshop по дообучению моделей под ваш сеттинг.
  • Поддержка: 3 месяца бесплатных фиксов и консультаций по каналу Slack.
  • Исходный код: Python-бэкенд, конфиги моделей, миграции БД.

Оцениваем проект бесплатно в течение 2–3 рабочих дней. Сроки: MVP генерации мира + квестов — от 6 до 8 недель. Полный PCG-движок с адаптивным балансом и текстурами — 4–6 месяцев. Свяжитесь с нами — подготовим индивидуальное коммерческое предложение. Закажите пилотный проект и оцените эффективность AI-PCG на ваших данных.