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
