AI-процедурная генерация контента для игр
Процедурная генерация контента (PCG) позволяет создавать игровые миры, квесты, диалоги, предметы и события алгоритмически — без ручного создания каждого элемента. AI-подходы расширяют классическую PCG: нейросети генерируют семантически связный нарратив, LLM создают адаптивные диалоги НПС, диффузионные модели — уникальные текстуры и спрайты.
Генератор игровых миров и биомов
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
LLM-генерация нарратива мира
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)
Адаптивные диалоги НПС
@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)
}
PCG-система с генерацией мира, квестами и диалогами НПС — 6–8 недель. Полноценный PCG-движок с адаптивным балансом сложности, процедурными текстурами и интеграцией в Unity/Unreal — 4–6 месяцев.







