En aquest projecte, implementarem un personatge no jugador (NPC) que utilitza tècniques de presa de decisions per comportar-se de manera intel·ligent en un entorn de joc. Utilitzarem màquines d'estats finits (FSM) i arbres de decisió per gestionar el comportament de l’NPC.
Objectius del Projecte
- Comprendre les tècniques de presa de decisions: Aprendre com les màquines d'estats finits i els arbres de decisió poden ser utilitzats per gestionar el comportament dels NPCs.
- Implementar una FSM per a un NPC: Crear una màquina d'estats finits que defineixi diferents estats i transicions per a un NPC.
- Implementar un arbre de decisió per a un NPC: Desenvolupar un arbre de decisió que permeti a l’NPC prendre decisions basades en diferents condicions.
- Integrar la FSM i l'arbre de decisió en un motor de joc: Implementar aquestes tècniques en un motor de joc com Unity o Unreal Engine.
Requisits Previs
- Coneixements bàsics de programació (preferiblement en C# per Unity o C++ per Unreal Engine).
- Familiaritat amb el motor de joc que s’utilitzarà.
- Comprensió dels conceptes de màquines d'estats finits i arbres de decisió.
Passos del Projecte
- Definició del Comportament de l’NPC
Abans de començar a programar, definirem el comportament que volem que tingui l’NPC. Per exemple, un guardià que patrulla una àrea, persegueix intrusos i torna a la patrulla quan no hi ha intrusos.
Comportaments de l’NPC:
- Patrullar: L’NPC es mou entre diferents punts de patrulla.
- Perseguir: L’NPC persegueix un intrús quan el detecta.
- Tornar a la Patrulla: L’NPC torna al punt de patrulla més proper quan perd de vista l’intrús.
- Implementació de la FSM
2.1. Definició dels Estats i Transicions
Definirem els estats i les transicions per a la FSM del nostre NPC.
Estats:
- Patrullar
- Perseguir
- Tornar a la Patrulla
Transicions:
- De Patrullar a Perseguir: Quan es detecta un intrús.
- De Perseguir a Tornar a la Patrulla: Quan es perd de vista l’intrús.
- De Tornar a la Patrulla a Patrullar: Quan l’NPC arriba al punt de patrulla.
2.2. Implementació en Codi
A continuació, implementarem la FSM en C# per Unity.
using UnityEngine; using System.Collections; public class NPCController : MonoBehaviour { private enum State { Patrol, Chase, ReturnToPatrol } private State currentState; public Transform[] patrolPoints; private int currentPatrolIndex; public Transform target; public float chaseRange = 5f; public float loseSightRange = 10f; void Start() { currentState = State.Patrol; currentPatrolIndex = 0; } void Update() { switch (currentState) { case State.Patrol: Patrol(); break; case State.Chase: Chase(); break; case State.ReturnToPatrol: ReturnToPatrol(); break; } } void Patrol() { Transform patrolPoint = patrolPoints[currentPatrolIndex]; MoveTowards(patrolPoint.position); if (Vector3.Distance(transform.position, patrolPoint.position) < 0.5f) { currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length; } if (Vector3.Distance(transform.position, target.position) < chaseRange) { currentState = State.Chase; } } void Chase() { MoveTowards(target.position); if (Vector3.Distance(transform.position, target.position) > loseSightRange) { currentState = State.ReturnToPatrol; } } void ReturnToPatrol() { Transform patrolPoint = patrolPoints[currentPatrolIndex]; MoveTowards(patrolPoint.position); if (Vector3.Distance(transform.position, patrolPoint.position) < 0.5f) { currentState = State.Patrol; } } void MoveTowards(Vector3 destination) { Vector3 direction = (destination - transform.position).normalized; transform.position += direction * Time.deltaTime * 2f; // Adjust speed as needed } }
- Implementació de l'Arbre de Decisió
3.1. Definició de les Condicions i Accions
Definirem les condicions i accions per a l’arbre de decisió del nostre NPC.
Condicions:
- Detecta Intrús
- Perd de Vista l’Intrús
Accions:
- Patrullar
- Perseguir
- Tornar a la Patrulla
3.2. Implementació en Codi
A continuació, implementarem l’arbre de decisió en C# per Unity.
using UnityEngine; public class NPCDecisionTree : MonoBehaviour { private abstract class DecisionNode { public abstract void Execute(NPCDecisionTree npc); } private class ActionNode : DecisionNode { private System.Action<NPCDecisionTree> action; public ActionNode(System.Action<NPCDecisionTree> action) { this.action = action; } public override void Execute(NPCDecisionTree npc) { action(npc); } } private class ConditionNode : DecisionNode { private System.Func<NPCDecisionTree, bool> condition; private DecisionNode trueNode; private DecisionNode falseNode; public ConditionNode(System.Func<NPCDecisionTree, bool> condition, DecisionNode trueNode, DecisionNode falseNode) { this.condition = condition; this.trueNode = trueNode; this.falseNode = falseNode; } public override void Execute(NPCDecisionTree npc) { if (condition(npc)) { trueNode.Execute(npc); } else { falseNode.Execute(npc); } } } public Transform[] patrolPoints; private int currentPatrolIndex; public Transform target; public float chaseRange = 5f; public float loseSightRange = 10f; private DecisionNode rootNode; void Start() { currentPatrolIndex = 0; // Build the decision tree rootNode = new ConditionNode( DetectsIntruder, new ActionNode(Chase), new ConditionNode( LostSightOfIntruder, new ActionNode(ReturnToPatrol), new ActionNode(Patrol) ) ); } void Update() { rootNode.Execute(this); } bool DetectsIntruder(NPCDecisionTree npc) { return Vector3.Distance(npc.transform.position, npc.target.position) < npc.chaseRange; } bool LostSightOfIntruder(NPCDecisionTree npc) { return Vector3.Distance(npc.transform.position, npc.target.position) > npc.loseSightRange; } void Patrol(NPCDecisionTree npc) { Transform patrolPoint = npc.patrolPoints[npc.currentPatrolIndex]; MoveTowards(patrolPoint.position); if (Vector3.Distance(npc.transform.position, patrolPoint.position) < 0.5f) { npc.currentPatrolIndex = (npc.currentPatrolIndex + 1) % npc.patrolPoints.Length; } } void Chase(NPCDecisionTree npc) { MoveTowards(npc.target.position); } void ReturnToPatrol(NPCDecisionTree npc) { Transform patrolPoint = npc.patrolPoints[npc.currentPatrolIndex]; MoveTowards(patrolPoint.position); if (Vector3.Distance(npc.transform.position, patrolPoint.position) < 0.5f) { npc.currentPatrolIndex = (npc.currentPatrolIndex + 1) % npc.patrolPoints.Length; } } void MoveTowards(Vector3 destination) { Vector3 direction = (destination - transform.position).normalized; transform.position += direction * Time.deltaTime * 2f; // Adjust speed as needed } }
- Integració i Proves
- Integració: Assegura’t que el codi de la FSM i l’arbre de decisió estiguin correctament integrats en el motor de joc.
- Proves: Prova el comportament de l’NPC en diferents escenaris per assegurar-te que les transicions i les accions es realitzen correctament.
- Optimització i Millores
- Optimització: Revisa el codi per optimitzar el rendiment, especialment en escenaris amb múltiples NPCs.
- Millores: Considera afegir més estats o condicions per fer el comportament de l’NPC més complex i realista.
Conclusió
En aquest projecte, hem creat un NPC que utilitza tècniques de presa de decisions per comportar-se de manera intel·ligent en un entorn de joc. Hem implementat una màquina d'estats finits i un arbre de decisió, i hem integrat aquestes tècniques en un motor de joc. Aquest projecte proporciona una base sòlida per desenvolupar comportaments més complexos i realistes per als NPCs en futurs jocs.
IA per a Videojocs
Mòdul 1: Introducció a la IA en Videojocs
Mòdul 2: Navegació en Videojocs
Mòdul 3: Presa de Decisions
Mòdul 4: Aprenentatge Automàtic
- Introducció a l'Aprenentatge Automàtic
- Xarxes Neuronals en Videojocs
- Aprenentatge per Reforç
- Implementació d'un Agent d'Aprenentatge
Mòdul 5: Integració i Optimització
Mòdul 6: Projectes Pràctics
- Projecte 1: Implementació de Navegació Bàsica
- Projecte 2: Creació d'un NPC amb Presa de Decisions
- Projecte 3: Desenvolupament d'un Agent amb Aprenentatge Automàtic