===== AI ved Finite State Machine (FSM)=====
StateMachine er baseret på denne video, se video og forsøg at implementere statemachine.
* [[https://www.youtube.com/watch?v=Kcg1SEgDqyk|StateMachine C# Godot]]
==== Opgaver ====
- Opret en Tilstandsmaskine(FSM) og Tilstande (States) ud fra ovenstående tutorials. (Det er ikke nødvendigt at se disse videoer i deres fulde længde) Hav fokus på FSM delen, og mindre fokus på det grafiske, animationer m.m.
- Lav en konkret tilstandsmaskine **EnemyFSM** ud fra "Make a basic finite state machine".
- Implementer tilstandene **Patrol**, **Chase** og **Attack**.
==== State ====
using Godot;
using System;
public partial class State : Node
{
public StateMachine fsm;
public virtual void Ready() { }
// ENTER state
public virtual void Enter() { }
// UPDATE state (Process)
public virtual void Update(float delta) { }
// EXIT state
public virtual void Exit() { }
public virtual void PhysicsUpdate(float delta) {}
public virtual void HandleInput(InputEvent @event) { }
}
----
==== StateMachine ====
using Godot;
using System;
using System.Collections.Generic;
public abstract partial class StateMachine : Node
{
[Export] public CharacterBody2D npc;
[Export] public NodePath initialState; // Start tilstand skal angives...
private Dictionary _states; // alle tilstande i denne datastruktur
private State _currentState; // nuværende tilstand
public override void _Ready()
{
if (npc == null) {
GD.Print("You forgot to set NPC..export");
}
if (initialState == null)
{
GD.Print("You forgot to set initialState..export");
}
_states = new Dictionary();
foreach (Node node in this.GetChildren()) {
if (node is State s) {
_states[node.Name] = s;
s.fsm = this;
s.Ready();
s.Exit();
}
}
_currentState = GetNode(initialState);
_currentState.Enter();
GD.Print("StateMachine is ready.");
}
// Der der skiftes tilstand
public void TransitionTo(string stateName){
// sikkerheds tjeck at tilstand eksisterer, eller at den allerede er valgt
if (!_states.ContainsKey(stateName) || _currentState == _states[stateName])
{
return;
}
// Der skiftes tilstand
_currentState.Exit(); // vi rydder op efter os !
_currentState = _states[stateName]; // Vi skifter til den nye tilstands
_currentState.Enter(); // initialisering..
}
public override void _Process(double delta)
{
_currentState.Update((float) delta);
}
public override void _PhysicsProcess(double delta)
{
_currentState.PhysicsUpdate((float)delta);
}
// Sender ikke håndterede events videre til nuværende tilstand (State)
public override void _UnhandledInput(InputEvent @event) {
_currentState.HandleInput(@event);
}
}
----
==== Patrolstate ====
using Godot;
using System;
public partial class PatrolState : State
{
[Export] public float Speed = 40f;
[Export] public float visibilityRange = 50f;
[Export] public Node2D[] waypoints; // Waypoints som der patruljeres imellem.
[Export] public CharacterBody2D player;
public int currentWaypointIndex = 0; // Det nuværende waypoint
public Vector2 targetPosition;
public override void Ready()
{
targetPosition = waypoints[currentWaypointIndex].GlobalPosition;
GD.Print("PatrolState ready.");
}
public override void Update(float delta)
{
fsm.npc.Velocity = fsm.npc.GlobalPosition.DirectionTo(targetPosition) * Speed;
// Er vi nået til vores nuværende Waypoint?
if( fsm.npc.GlobalPosition.DistanceTo( targetPosition ) < 1.5f ){
currentWaypointIndex++;
if (currentWaypointIndex > waypoints.Length - 1) {
currentWaypointIndex = 0;
}
targetPosition = waypoints[currentWaypointIndex].GlobalPosition;
}
// Er vi inde for visibility range af Player?
if (fsm.npc.GlobalPosition.DistanceTo(player.GlobalPosition) < this.visibilityRange) {
GD.Print("Skift til ChaseState");
fsm.TransitionTo("ChaseState");
}
fsm.npc.MoveAndSlide();
}
}
----
==== ChaseState ====
using Godot;
using System;
public partial class ChaseState : State
{
[Export] public float Speed = 40f;
[Export] public Node2D player;
public CharacterBody2D skeleton;
public override void Ready()
{
if (player == null) {
GD.Print("Du har glemt at lave en reference til Player.");
}
GD.Print("ChaseState ready.");
}
public override void PhysicsUpdate(float delta)
{
fsm.npc.Velocity = fsm.npc.GlobalPosition.DirectionTo(player.GlobalPosition) * this.Speed;
if ( fsm.npc.GlobalPosition.DistanceTo( player.GlobalPosition ) > 50f )
{
GD.Print("Skift til PatrolState");
fsm.TransitionTo("PatrolState");
}
if (fsm.npc.GlobalPosition.DistanceTo(player.GlobalPosition) < 10f)
{
GD.Print("Skift til AttackState");
fsm.TransitionTo("AttackState");
}
fsm.npc.MoveAndSlide();
}
}
----