Indholdsfortegnelse

AI ved Finite State Machine (FSM)

StateMachine er baseret på denne video, se video og forsøg at implementere statemachine.

Opgaver

  1. 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.
  2. Lav en konkret tilstandsmaskine EnemyFSM ud fra „Make a basic finite state machine“.
  3. Implementer tilstandene Patrol, Chase og Attack.

State

State.cs
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

StateMachine.cs
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<string, State> _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<string, State>();
 
		foreach (Node node in this.GetChildren()) {
			if (node is State s) {
				_states[node.Name] = s;
				s.fsm = this;
				s.Ready();
				s.Exit();
			}
		}
 
		_currentState = GetNode<State>(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

Patrol.cs
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

ChaseState.cs
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();
	}
}