Wiki IA
Applications

IA et Jeux Vidéo

L'intelligence artificielle dans l'industrie du jeu vidéo

L'IA dans les jeux vidéo est l'un des domaines les plus anciens et les plus innovants de l'intelligence artificielle appliquée. Des PNJ aux mondes procéduraux, l'IA transforme la façon dont les jeux sont créés et joués.

Domaines d'application

IA DANS LES JEUX VIDÉO :

┌─────────────────────────────────────────────────────────────┐
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │ COMPORTEMENT │  │  GÉNÉRATION  │  │   ANALYSE    │      │
│  │   (Gameplay) │  │  (Contenu)   │  │  (Testing)   │      │
│  ├──────────────┤  ├──────────────┤  ├──────────────┤      │
│  │ • PNJ        │  │ • Niveaux    │  │ • QA auto    │      │
│  │ • Ennemis    │  │ • Textures   │  │ • Équilibrage│      │
│  │ • Pathfinding│  │ • Dialogues  │  │ • Analytics  │      │
│  │ • Difficulté │  │ • Musique    │  │ • Anti-cheat │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  GRAPHISMES  │  │   OUTILS     │  │    JOUEUR    │      │
│  │ (Rendu)      │  │ (Dev)        │  │ (Assistance) │      │
│  ├──────────────┤  ├──────────────┤  ├──────────────┤      │
│  │ • Upscaling  │  │ • Animation  │  │ • Matchmaking│      │
│  │ • Denoising  │  │ • Motion cap │  │ • Recommand. │      │
│  │ • Super Res  │  │ • Localisation│ │ • Accessibil.│      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

IA pour les PNJ (Personnages Non-Joueurs)

Machines à états finis (FSM)

L'approche classique encore très utilisée :

from enum import Enum, auto

class State(Enum):
    IDLE = auto()
    PATROL = auto()
    CHASE = auto()
    ATTACK = auto()
    FLEE = auto()

class EnemyFSM:
    def __init__(self):
        self.state = State.IDLE
        self.health = 100
        self.detection_range = 10
        self.attack_range = 2

    def update(self, player_position, self_position):
        distance = self.calculate_distance(player_position, self_position)

        # Transitions basées sur les conditions
        if self.health < 20:
            self.state = State.FLEE
        elif distance < self.attack_range:
            self.state = State.ATTACK
        elif distance < self.detection_range:
            self.state = State.CHASE
        elif self.state == State.CHASE:
            self.state = State.PATROL
        else:
            self.state = State.IDLE

        # Exécuter l'action correspondante
        return self.execute_state()

    def execute_state(self):
        actions = {
            State.IDLE: self.idle,
            State.PATROL: self.patrol,
            State.CHASE: self.chase,
            State.ATTACK: self.attack,
            State.FLEE: self.flee
        }
        return actions[self.state]()

    def idle(self):
        return {"action": "wait", "animation": "idle"}

    def patrol(self):
        return {"action": "move", "target": self.get_next_waypoint()}

    def chase(self):
        return {"action": "move", "target": "player", "speed": 1.5}

    def attack(self):
        return {"action": "attack", "damage": 10}

    def flee(self):
        return {"action": "move", "direction": "away_from_player", "speed": 2.0}

Behavior Trees

Plus flexibles et modulaires que les FSM :

from enum import Enum
from abc import ABC, abstractmethod

class NodeStatus(Enum):
    SUCCESS = 1
    FAILURE = 2
    RUNNING = 3

class Node(ABC):
    @abstractmethod
    def tick(self, context: dict) -> NodeStatus:
        pass

class Sequence(Node):
    """Exécute les enfants en séquence jusqu'à échec."""
    def __init__(self, children: list[Node]):
        self.children = children

    def tick(self, context: dict) -> NodeStatus:
        for child in self.children:
            status = child.tick(context)
            if status != NodeStatus.SUCCESS:
                return status
        return NodeStatus.SUCCESS

class Selector(Node):
    """Exécute les enfants jusqu'au premier succès."""
    def __init__(self, children: list[Node]):
        self.children = children

    def tick(self, context: dict) -> NodeStatus:
        for child in self.children:
            status = child.tick(context)
            if status != NodeStatus.FAILURE:
                return status
        return NodeStatus.FAILURE

class Condition(Node):
    """Vérifie une condition."""
    def __init__(self, check_func):
        self.check_func = check_func

    def tick(self, context: dict) -> NodeStatus:
        if self.check_func(context):
            return NodeStatus.SUCCESS
        return NodeStatus.FAILURE

class Action(Node):
    """Exécute une action."""
    def __init__(self, action_func):
        self.action_func = action_func

    def tick(self, context: dict) -> NodeStatus:
        return self.action_func(context)

# Exemple : Comportement d'un garde
def build_guard_behavior():
    return Selector([
        # Priorité 1: Combattre si ennemi visible
        Sequence([
            Condition(lambda ctx: ctx.get("enemy_visible")),
            Selector([
                Sequence([
                    Condition(lambda ctx: ctx.get("in_attack_range")),
                    Action(lambda ctx: attack(ctx))
                ]),
                Action(lambda ctx: chase_enemy(ctx))
            ])
        ]),
        # Priorité 2: Investiguer les bruits
        Sequence([
            Condition(lambda ctx: ctx.get("heard_noise")),
            Action(lambda ctx: investigate(ctx))
        ]),
        # Priorité 3: Patrouiller
        Action(lambda ctx: patrol(ctx))
    ])

GOAP (Goal-Oriented Action Planning)

Pour des PNJ plus autonomes et adaptatifs :

from dataclasses import dataclass
from typing import Callable
import heapq

@dataclass
class ActionDef:
    name: str
    preconditions: dict  # État requis
    effects: dict        # Changements d'état
    cost: float          # Coût de l'action

class GOAPPlanner:
    def __init__(self, actions: list[ActionDef]):
        self.actions = actions

    def plan(self, current_state: dict, goal: dict) -> list[str]:
        """Trouve une séquence d'actions pour atteindre le goal."""

        # A* sur l'espace des états
        open_list = [(0, current_state, [])]  # (cost, state, actions)
        closed = set()

        while open_list:
            cost, state, actions = heapq.heappop(open_list)

            # Vérifier si goal atteint
            if self._goal_reached(state, goal):
                return actions

            state_tuple = tuple(sorted(state.items()))
            if state_tuple in closed:
                continue
            closed.add(state_tuple)

            # Explorer les actions possibles
            for action in self.actions:
                if self._preconditions_met(state, action.preconditions):
                    new_state = self._apply_effects(state.copy(), action.effects)
                    new_cost = cost + action.cost
                    heuristic = self._estimate_cost(new_state, goal)

                    heapq.heappush(open_list, (
                        new_cost + heuristic,
                        new_state,
                        actions + [action.name]
                    ))

        return []  # Pas de plan trouvé

    def _preconditions_met(self, state: dict, preconditions: dict) -> bool:
        return all(state.get(k) == v for k, v in preconditions.items())

    def _apply_effects(self, state: dict, effects: dict) -> dict:
        state.update(effects)
        return state

    def _goal_reached(self, state: dict, goal: dict) -> bool:
        return all(state.get(k) == v for k, v in goal.items())

    def _estimate_cost(self, state: dict, goal: dict) -> float:
        return sum(1 for k, v in goal.items() if state.get(k) != v)

# Exemple : PNJ qui doit manger
actions = [
    ActionDef("find_food", {"has_food": False}, {"found_food": True}, 2),
    ActionDef("pick_food", {"found_food": True}, {"has_food": True}, 1),
    ActionDef("eat", {"has_food": True, "hungry": True}, {"hungry": False, "has_food": False}, 1),
]

planner = GOAPPlanner(actions)
state = {"hungry": True, "has_food": False, "found_food": False}
goal = {"hungry": False}

plan = planner.plan(state, goal)
# Résultat: ["find_food", "pick_food", "eat"]

PNJ avec LLM

La nouvelle frontière : des PNJ conversationnels intelligents :

from anthropic import Anthropic

class LLMCharacter:
    def __init__(self, character_data: dict):
        self.client = Anthropic()
        self.name = character_data["name"]
        self.personality = character_data["personality"]
        self.backstory = character_data["backstory"]
        self.knowledge = character_data["knowledge"]
        self.conversation_history = []

    def get_system_prompt(self) -> str:
        return f"""Tu es {self.name}, un personnage dans un jeu vidéo de fantasy.

PERSONNALITÉ : {self.personality}

HISTOIRE : {self.backstory}

CE QUE TU SAIS :
{self.knowledge}

RÈGLES :
- Reste dans ton personnage à tout moment
- Réponds de manière concise (2-3 phrases max)
- Utilise un langage approprié à l'époque médiévale
- Tu peux donner des quêtes ou des indices si pertinent
- Ne révèle pas d'informations que ton personnage ne connaîtrait pas"""

    def chat(self, player_input: str, context: dict = None) -> str:
        """Génère une réponse du personnage."""

        # Ajouter le contexte de jeu si disponible
        game_context = ""
        if context:
            game_context = f"\n[Contexte: Le joueur est niveau {context.get('level', 1)}, "
            game_context += f"a complété {context.get('quests_done', 0)} quêtes, "
            game_context += f"heure du jeu: {context.get('time', 'jour')}]"

        self.conversation_history.append({
            "role": "user",
            "content": player_input + game_context
        })

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=150,
            system=self.get_system_prompt(),
            messages=self.conversation_history
        )

        npc_response = response.content[0].text

        self.conversation_history.append({
            "role": "assistant",
            "content": npc_response
        })

        # Limiter l'historique pour économiser les tokens
        if len(self.conversation_history) > 20:
            self.conversation_history = self.conversation_history[-10:]

        return npc_response

# Exemple d'utilisation
blacksmith = LLMCharacter({
    "name": "Thorin le Forgeron",
    "personality": "Bourru mais bienveillant, fier de son travail, amateur de bière",
    "backstory": "Ancien soldat devenu forgeron après une blessure. A forgé l'épée du roi.",
    "knowledge": "Expert en armes et armures. Sait qu'un dragon a été vu près des montagnes."
})

response = blacksmith.chat(
    "Salut l'ami ! Tu aurais une épée pour un aventurier débutant ?",
    context={"level": 1, "quests_done": 0, "time": "matin"}
)

Génération procédurale

Génération de niveaux (Wave Function Collapse)

import random
import numpy as np
from typing import Optional

class WFCGenerator:
    """Génération de niveaux par Wave Function Collapse simplifié."""

    def __init__(self, tiles: dict, rules: dict):
        self.tiles = tiles  # {"grass": "🌿", "water": "💧", ...}
        self.rules = rules  # {"grass": {"right": ["grass", "path"], ...}}

    def generate(self, width: int, height: int) -> list[list[str]]:
        # Initialiser avec toutes les possibilités
        grid = [[set(self.tiles.keys()) for _ in range(width)] for _ in range(height)]

        while not self._is_collapsed(grid):
            # Trouver la cellule avec le moins d'entropie (choix)
            x, y = self._find_min_entropy(grid)

            if grid[y][x]:
                # Choisir un tile aléatoirement
                chosen = random.choice(list(grid[y][x]))
                grid[y][x] = {chosen}

                # Propager les contraintes
                self._propagate(grid, x, y)

        # Convertir en résultat final
        return [[list(cell)[0] if cell else "?" for cell in row] for row in grid]

    def _is_collapsed(self, grid) -> bool:
        return all(len(cell) == 1 for row in grid for cell in row)

    def _find_min_entropy(self, grid) -> tuple[int, int]:
        min_entropy = float('inf')
        min_pos = (0, 0)

        for y, row in enumerate(grid):
            for x, cell in enumerate(row):
                if len(cell) > 1 and len(cell) < min_entropy:
                    min_entropy = len(cell)
                    min_pos = (x, y)

        return min_pos

    def _propagate(self, grid, start_x: int, start_y: int):
        """Propage les contraintes aux voisins."""
        stack = [(start_x, start_y)]
        height, width = len(grid), len(grid[0])

        while stack:
            x, y = stack.pop()
            current_tiles = grid[y][x]

            # Vérifier chaque voisin
            neighbors = [
                (x+1, y, "right"), (x-1, y, "left"),
                (x, y+1, "down"), (x, y-1, "up")
            ]

            for nx, ny, direction in neighbors:
                if 0 <= nx < width and 0 <= ny < height:
                    # Calculer les tiles valides pour le voisin
                    valid = set()
                    for tile in current_tiles:
                        if tile in self.rules:
                            valid.update(self.rules[tile].get(direction, []))

                    # Réduire les possibilités du voisin
                    old_len = len(grid[ny][nx])
                    grid[ny][nx] &= valid

                    if len(grid[ny][nx]) < old_len:
                        stack.append((nx, ny))

# Exemple d'utilisation
tiles = {"grass": "🌿", "water": "💧", "sand": "🏖️", "tree": "🌲"}
rules = {
    "grass": {"right": ["grass", "sand", "tree"], "left": ["grass", "sand", "tree"],
              "up": ["grass", "tree"], "down": ["grass", "sand"]},
    "water": {"right": ["water", "sand"], "left": ["water", "sand"],
              "up": ["water", "sand"], "down": ["water", "sand"]},
    "sand": {"right": ["sand", "water", "grass"], "left": ["sand", "water", "grass"],
             "up": ["sand", "grass"], "down": ["sand", "water"]},
    "tree": {"right": ["grass", "tree"], "left": ["grass", "tree"],
             "up": ["grass"], "down": ["grass"]}
}

generator = WFCGenerator(tiles, rules)
level = generator.generate(10, 10)

Génération de quêtes

from anthropic import Anthropic
import json

class QuestGenerator:
    def __init__(self):
        self.client = Anthropic()

    def generate_quest(
        self,
        player_level: int,
        location: str,
        available_npcs: list[str],
        world_state: dict
    ) -> dict:
        """Génère une quête adaptée au contexte."""

        prompt = f"""Génère une quête de jeu vidéo RPG au format JSON.

CONTEXTE :
- Niveau du joueur : {player_level}
- Lieu actuel : {location}
- PNJ disponibles : {', '.join(available_npcs)}
- État du monde : {json.dumps(world_state, ensure_ascii=False)}

FORMAT DE SORTIE (JSON strict) :
{{
    "title": "Titre de la quête",
    "description": "Description narrative",
    "giver": "Nom du PNJ qui donne la quête",
    "type": "main/side/daily",
    "objectives": [
        {{"type": "kill/collect/talk/explore", "target": "...", "count": 1}}
    ],
    "rewards": {{
        "xp": 100,
        "gold": 50,
        "items": ["item_id"]
    }},
    "dialogue_intro": "Ce que dit le PNJ",
    "dialogue_complete": "Ce que dit le PNJ à la fin"
}}

Génère une quête appropriée au niveau et au contexte."""

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=500,
            messages=[{"role": "user", "content": prompt}]
        )

        return json.loads(response.content[0].text)

# Exemple
generator = QuestGenerator()
quest = generator.generate_quest(
    player_level=5,
    location="Village de Millefeuilles",
    available_npcs=["Thorin le Forgeron", "Elara la Guérisseuse", "Gareth le Garde"],
    world_state={"menace_gobelin": True, "saison": "automne", "guerre_proche": False}
)

Pathfinding

A* (A-Star)

L'algorithme de référence pour le pathfinding :

import heapq
from typing import Optional

class AStar:
    def __init__(self, grid: list[list[int]]):
        """grid: 0 = libre, 1 = obstacle"""
        self.grid = grid
        self.height = len(grid)
        self.width = len(grid[0])

    def find_path(
        self,
        start: tuple[int, int],
        goal: tuple[int, int]
    ) -> Optional[list[tuple[int, int]]]:
        """Trouve le chemin le plus court."""

        def heuristic(a, b):
            # Distance de Manhattan
            return abs(a[0] - b[0]) + abs(a[1] - b[1])

        open_set = [(0, start)]
        came_from = {}
        g_score = {start: 0}

        while open_set:
            _, current = heapq.heappop(open_set)

            if current == goal:
                # Reconstruire le chemin
                path = [current]
                while current in came_from:
                    current = came_from[current]
                    path.append(current)
                return path[::-1]

            for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                neighbor = (current[0] + dx, current[1] + dy)

                # Vérifier les limites et obstacles
                if not (0 <= neighbor[0] < self.width and
                        0 <= neighbor[1] < self.height):
                    continue
                if self.grid[neighbor[1]][neighbor[0]] == 1:
                    continue

                tentative_g = g_score[current] + 1

                if tentative_g < g_score.get(neighbor, float('inf')):
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g
                    f_score = tentative_g + heuristic(neighbor, goal)
                    heapq.heappush(open_set, (f_score, neighbor))

        return None  # Pas de chemin

# Exemple
grid = [
    [0, 0, 0, 0, 0],
    [0, 1, 1, 1, 0],
    [0, 0, 0, 1, 0],
    [0, 1, 0, 0, 0],
    [0, 0, 0, 0, 0]
]

pathfinder = AStar(grid)
path = pathfinder.find_path((0, 0), (4, 4))
# [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2), (2, 3), (3, 3), (4, 3), (4, 4)]

Pour des environnements 3D complexes :

import numpy as np
from scipy.spatial import Delaunay

class NavMesh:
    def __init__(self, walkable_points: list[tuple[float, float]]):
        """Crée un navmesh à partir de points marchables."""
        self.points = np.array(walkable_points)
        self.triangulation = Delaunay(self.points)

    def find_path(self, start: tuple, goal: tuple) -> list[tuple]:
        """Pathfinding sur le navmesh."""
        # Trouver les triangles contenant start et goal
        start_tri = self.triangulation.find_simplex(start)
        goal_tri = self.triangulation.find_simplex(goal)

        if start_tri == -1 or goal_tri == -1:
            return []  # Points hors du navmesh

        # A* sur les triangles adjacents
        # (Simplifié - en production, utiliser les centers des triangles)
        path_triangles = self._astar_triangles(start_tri, goal_tri)

        # Convertir en waypoints (centres des triangles)
        path = [start]
        for tri_idx in path_triangles:
            center = self.points[self.triangulation.simplices[tri_idx]].mean(axis=0)
            path.append(tuple(center))
        path.append(goal)

        return path

    def _astar_triangles(self, start: int, goal: int) -> list[int]:
        """A* sur le graphe de triangles adjacents."""
        # Utiliser self.triangulation.neighbors pour les adjacences
        # Implémentation similaire à A* classique
        pass

Apprentissage par renforcement

Agent simple avec Q-Learning

import numpy as np
import random

class QLearningAgent:
    def __init__(
        self,
        state_size: int,
        action_size: int,
        learning_rate: float = 0.1,
        discount_factor: float = 0.99,
        epsilon: float = 1.0,
        epsilon_decay: float = 0.995
    ):
        self.q_table = np.zeros((state_size, action_size))
        self.lr = learning_rate
        self.gamma = discount_factor
        self.epsilon = epsilon
        self.epsilon_decay = epsilon_decay
        self.epsilon_min = 0.01
        self.action_size = action_size

    def choose_action(self, state: int) -> int:
        """Politique epsilon-greedy."""
        if random.random() < self.epsilon:
            return random.randrange(self.action_size)
        return np.argmax(self.q_table[state])

    def learn(self, state: int, action: int, reward: float, next_state: int, done: bool):
        """Mise à jour Q-Learning."""
        target = reward
        if not done:
            target += self.gamma * np.max(self.q_table[next_state])

        self.q_table[state, action] += self.lr * (target - self.q_table[state, action])

        # Décroissance de l'exploration
        if self.epsilon > self.epsilon_min:
            self.epsilon *= self.epsilon_decay

# Exemple : Entraîner un agent de jeu
def train_game_agent(env, episodes: int = 1000):
    agent = QLearningAgent(
        state_size=env.observation_space.n,
        action_size=env.action_space.n
    )

    for episode in range(episodes):
        state = env.reset()
        total_reward = 0

        while True:
            action = agent.choose_action(state)
            next_state, reward, done, _ = env.step(action)
            agent.learn(state, action, reward, next_state, done)

            state = next_state
            total_reward += reward

            if done:
                break

        if episode % 100 == 0:
            print(f"Episode {episode}, Reward: {total_reward}, Epsilon: {agent.epsilon:.2f}")

    return agent

Technologies graphiques IA

DLSS / FSR (Super Resolution)

SUPER RÉSOLUTION IA :

SANS UPSCALING :
┌─────────────┐      ┌─────────────┐
│  Rendu      │      │  Affichage  │
│  1080p      │ ───→ │  1080p      │
│  (100% GPU) │      │             │
└─────────────┘      └─────────────┘

AVEC DLSS/FSR :
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│  Rendu      │      │  IA Super   │      │  Affichage  │
│  540p       │ ───→ │  Resolution │ ───→ │  1080p      │
│  (25% GPU)  │      │  (Neural)   │      │  (Qualité+) │
└─────────────┘      └─────────────┘      └─────────────┘

Avantages :
• 2-4x plus de FPS
• Qualité souvent supérieure au rendu natif
• Utilise les Tensor Cores (NVIDIA) ou shaders (AMD)

Ray Tracing Denoising

DENOISING IA :

RAY TRACING BRUITÉ          APRÈS DENOISING IA
(1 rayon par pixel)         (Résultat propre)

  ░▒█░▒░░█▒░                 ███████████
  ▒░█▒░▒░░█▒                 █████████████
  ░▒░░█░▒░░▒     ───→        █████████████
  ▒░▒░░█░▒▒░                 ███████████
  ░░▒░░░▒░░█                 █████████████

Le modèle IA apprend à :
• Distinguer bruit de détail
• Préserver les bords nets
• Interpoler les zones sous-échantillonnées

Outils et frameworks

Unity ML-Agents

# Configuration d'un agent Unity avec ML-Agents (Python side)
from mlagents_envs.environment import UnityEnvironment
from mlagents_envs.base_env import ActionTuple
import numpy as np

# Connecter à l'environnement Unity
env = UnityEnvironment(file_name="Build/MyGame", seed=42)
env.reset()

# Obtenir les specs du comportement
behavior_name = list(env.behavior_specs)[0]
spec = env.behavior_specs[behavior_name]

for episode in range(100):
    env.reset()
    decision_steps, terminal_steps = env.get_steps(behavior_name)

    while len(decision_steps) > 0:
        # Récupérer les observations
        observations = decision_steps.obs[0]

        # Décider des actions (ici aléatoire, normalement réseau de neurones)
        actions = np.random.randn(len(decision_steps), spec.action_spec.continuous_size)
        action_tuple = ActionTuple(continuous=actions)

        # Envoyer les actions
        env.set_actions(behavior_name, action_tuple)
        env.step()

        decision_steps, terminal_steps = env.get_steps(behavior_name)

env.close()

Résumé des outils

FRAMEWORKS ET OUTILS :

COMPORTEMENT IA
├── Unity ML-Agents : RL pour Unity
├── Unreal AI : Behavior Trees natifs
├── RAIN AI : Plugin Unity complet
└── NodeCanvas : Visual scripting IA

GÉNÉRATION PROCÉDURALE
├── Houdini : Assets procéduraux
├── World Machine : Terrains
├── SpeedTree : Végétation
└── Substance : Textures

GRAPHISMES IA
├── NVIDIA DLSS : Super resolution
├── AMD FSR : Alternative ouverte
├── Intel XeSS : Cross-platform
└── NVIDIA RTX : Ray tracing + denoising

LLM POUR JEUX
├── Inworld AI : PNJ conversationnels
├── Convai : Voix + personnalité
├── Replica Studios : Voix IA
└── Charisma.ai : Narratif interactif

Résumé

IA DANS LES JEUX VIDÉO :

COMPORTEMENT PNJ
├── FSM : Simple, prévisible
├── Behavior Trees : Modulaire, flexible
├── GOAP : Autonome, adaptatif
└── LLM : Conversationnel, émergent

GÉNÉRATION PROCÉDURALE
├── WFC : Niveaux cohérents
├── L-Systems : Végétation, donjons
├── Perlin/Simplex : Terrains
└── LLM : Quêtes, dialogues

PATHFINDING
├── A* : Standard, efficace
├── NavMesh : 3D complexe
├── Flow Fields : Foules
└── Hierarchical : Grandes cartes

APPRENTISSAGE
├── Q-Learning : Simple, discret
├── DQN : États complexes
├── PPO : Continue, stable
└── Imitation : Apprendre des joueurs

GRAPHISMES
├── Super Resolution (DLSS/FSR)
├── Ray Tracing Denoising
├── Frame Generation
└── Asset Generation

L'IA dans les jeux vidéo évolue rapidement avec l'intégration des LLM pour des PNJ véritablement conversationnels et des mondes qui s'adaptent dynamiquement aux joueurs.

On this page