state machine refactoring

pull/3/head
John Montagu, the 4th Earl of Sandvich 2023-05-25 15:28:33 -07:00
parent 64a9b98644
commit 1cf47d18a9
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
19 changed files with 120 additions and 275 deletions

View File

@ -2,6 +2,7 @@ using Godot;
using SupaLidlGame.Extensions; using SupaLidlGame.Extensions;
using SupaLidlGame.Items; using SupaLidlGame.Items;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
using SupaLidlGame.State.Character;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters
{ {
@ -26,12 +27,6 @@ namespace SupaLidlGame.Characters
protected float _mass = 1.0f; protected float _mass = 1.0f;
public float JumpVelocity { get; protected set; } = -400.0f;
public float AccelerationMagnitude { get; protected set; } = 256.0f;
public Vector2 Acceleration => Direction * AccelerationMagnitude;
public Vector2 NetImpulse { get; set; } = Vector2.Zero; public Vector2 NetImpulse { get; set; } = Vector2.Zero;
public Vector2 Direction { get; set; } = Vector2.Zero; public Vector2 Direction { get; set; } = Vector2.Zero;
@ -50,7 +45,6 @@ namespace SupaLidlGame.Characters
} }
_health = value; _health = value;
GD.Print(_health);
if (_health <= 0) if (_health <= 0)
{ {
Die(); Die();
@ -71,7 +65,7 @@ namespace SupaLidlGame.Characters
public Inventory Inventory { get; set; } public Inventory Inventory { get; set; }
[Export] [Export]
public State.Machine StateMachine { get; set; } public CharacterStateMachine StateMachine { get; set; }
[Export] [Export]
public ushort Faction { get; set; } public ushort Faction { get; set; }

View File

@ -3,9 +3,9 @@
[ext_resource type="Script" path="res://Characters/Enemy.cs" id="1_2yopk"] [ext_resource type="Script" path="res://Characters/Enemy.cs" id="1_2yopk"]
[ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="1_fx1w5"] [ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="1_fx1w5"]
[ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="3_ocaae"] [ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="3_ocaae"]
[ext_resource type="Script" path="res://State/Machine.cs" id="4_4nwgr"] [ext_resource type="Script" path="res://State/Character/CharacterStateMachine.cs" id="4_424ux"]
[ext_resource type="Script" path="res://Characters/States/NPCIdleState.cs" id="4_8r2qn"] [ext_resource type="Script" path="res://State/Character/NPCIdleState.cs" id="5_tn4cf"]
[ext_resource type="Script" path="res://Characters/States/NPCMoveState.cs" id="5_utogm"] [ext_resource type="Script" path="res://State/Character/NPCMoveState.cs" id="6_73mr6"]
[ext_resource type="PackedScene" uid="uid://cjgxyhgcyvsv7" path="res://BoundingBoxes/Hurtbox.tscn" id="6_jo0cg"] [ext_resource type="PackedScene" uid="uid://cjgxyhgcyvsv7" path="res://BoundingBoxes/Hurtbox.tscn" id="6_jo0cg"]
[ext_resource type="Script" path="res://Items/Inventory.cs" id="7_43gq8"] [ext_resource type="Script" path="res://Items/Inventory.cs" id="7_43gq8"]
[ext_resource type="PackedScene" uid="uid://d72ehtv1ks0e" path="res://Items/Weapons/Sword.tscn" id="8_s3c8r"] [ext_resource type="PackedScene" uid="uid://d72ehtv1ks0e" path="res://Items/Weapons/Sword.tscn" id="8_s3c8r"]
@ -141,6 +141,21 @@ Inventory = NodePath("Inventory")
StateMachine = NodePath("StateMachine") StateMachine = NodePath("StateMachine")
Faction = 2 Faction = 2
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
script = ExtResource("4_424ux")
InitialState = NodePath("Idle")
Character = NodePath("..")
[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState", "Character")]
script = ExtResource("5_tn4cf")
MoveState = NodePath("../Move")
Character = NodePath("../..")
[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState", "Character")]
script = ExtResource("6_73mr6")
IdleState = NodePath("../Idle")
Character = NodePath("../..")
[node name="Sprite" type="AnimatedSprite2D" parent="."] [node name="Sprite" type="AnimatedSprite2D" parent="."]
use_parent_material = true use_parent_material = true
position = Vector2(0, -4) position = Vector2(0, -4)
@ -151,19 +166,6 @@ animation = &"move"
position = Vector2(0, 4) position = Vector2(0, 4)
shape = SubResource("RectangleShape2D_uict5") shape = SubResource("RectangleShape2D_uict5")
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
script = ExtResource("4_4nwgr")
InitialState = NodePath("Idle")
Character = NodePath("..")
[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState")]
script = ExtResource("4_8r2qn")
MoveState = NodePath("../Move")
[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState")]
script = ExtResource("5_utogm")
IdleState = NodePath("../Idle")
[node name="Hurtbox" parent="." instance=ExtResource("6_jo0cg")] [node name="Hurtbox" parent="." instance=ExtResource("6_jo0cg")]
position = Vector2(0, -4) position = Vector2(0, -4)
Faction = 2 Faction = 2

View File

@ -1,5 +1,6 @@
using Godot; using Godot;
using SupaLidlGame.Utils; using SupaLidlGame.Utils;
using SupaLidlGame.State;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters
{ {
@ -41,7 +42,7 @@ namespace SupaLidlGame.Characters
public override void ModifyVelocity() public override void ModifyVelocity()
{ {
if (StateMachine.State is State.PlayerRollState) if (StateMachine.CurrentState is SupaLidlGame.State.Character.PlayerRollState)
{ {
Velocity *= 2; Velocity *= 2;
} }

View File

@ -2,14 +2,14 @@
[ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"] [ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"]
[ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="2_ngsgt"] [ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="2_ngsgt"]
[ext_resource type="Script" path="res://Characters/States/PlayerIdleState.cs" id="4_4k4mb"]
[ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="4_5vird"] [ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="4_5vird"]
[ext_resource type="PackedScene" uid="uid://cl56eadpklnbo" path="res://Utils/PlayerCamera.tscn" id="4_ym125"] [ext_resource type="PackedScene" uid="uid://cl56eadpklnbo" path="res://Utils/PlayerCamera.tscn" id="4_ym125"]
[ext_resource type="Script" path="res://State/Machine.cs" id="5_glslt"] [ext_resource type="Script" path="res://State/Character/CharacterStateMachine.cs" id="5_rgckv"]
[ext_resource type="Script" path="res://Characters/States/PlayerMoveState.cs" id="5_tx5rw"] [ext_resource type="Script" path="res://State/Character/PlayerIdleState.cs" id="6_wkfdm"]
[ext_resource type="Script" path="res://Characters/States/PlayerRollState.cs" id="6_6bgrj"]
[ext_resource type="PackedScene" uid="uid://d72ehtv1ks0e" path="res://Items/Weapons/Sword.tscn" id="7_4rxuv"] [ext_resource type="PackedScene" uid="uid://d72ehtv1ks0e" path="res://Items/Weapons/Sword.tscn" id="7_4rxuv"]
[ext_resource type="Script" path="res://State/Character/PlayerMoveState.cs" id="7_dfqd8"]
[ext_resource type="Script" path="res://Items/Inventory.cs" id="7_xyenu"] [ext_resource type="Script" path="res://Items/Inventory.cs" id="7_xyenu"]
[ext_resource type="Script" path="res://State/Character/PlayerRollState.cs" id="8_fy0v5"]
[ext_resource type="PackedScene" uid="uid://cjgxyhgcyvsv7" path="res://BoundingBoxes/Hurtbox.tscn" id="9_avyu4"] [ext_resource type="PackedScene" uid="uid://cjgxyhgcyvsv7" path="res://BoundingBoxes/Hurtbox.tscn" id="9_avyu4"]
[ext_resource type="AudioStream" uid="uid://njun3e6v4854" path="res://Assets/Sounds/hurt.wav" id="12_h0x0g"] [ext_resource type="AudioStream" uid="uid://njun3e6v4854" path="res://Assets/Sounds/hurt.wav" id="12_h0x0g"]
@ -148,6 +148,28 @@ Inventory = NodePath("Inventory")
StateMachine = NodePath("StateMachine") StateMachine = NodePath("StateMachine")
Faction = 1 Faction = 1
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
script = ExtResource("5_rgckv")
InitialState = NodePath("Idle")
Character = NodePath("..")
[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState", "IdleState", "Character")]
script = ExtResource("6_wkfdm")
MoveState = NodePath("../Move")
IdleState = NodePath(".")
Character = NodePath("../..")
[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("RollState", "IdleState", "Character")]
script = ExtResource("7_dfqd8")
RollState = NodePath("../Roll")
IdleState = NodePath("../Idle")
Character = NodePath("../..")
[node name="Roll" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState", "Character")]
script = ExtResource("8_fy0v5")
IdleState = NodePath("../Idle")
Character = NodePath("../..")
[node name="Camera2D" parent="." instance=ExtResource("4_ym125")] [node name="Camera2D" parent="." instance=ExtResource("4_ym125")]
position_smoothing_speed = 8.0 position_smoothing_speed = 8.0
@ -160,26 +182,6 @@ animation = &"idle"
position = Vector2(0, 8) position = Vector2(0, 8)
shape = SubResource("RectangleShape2D_bfqew") shape = SubResource("RectangleShape2D_bfqew")
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
script = ExtResource("5_glslt")
InitialState = NodePath("Idle")
Character = NodePath("..")
DebugLevel = 2
[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState", "IdleState")]
script = ExtResource("4_4k4mb")
MoveState = NodePath("../Move")
IdleState = NodePath(".")
[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("RollState", "IdleState")]
script = ExtResource("5_tx5rw")
RollState = NodePath("../Roll")
IdleState = NodePath("../Idle")
[node name="Roll" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState")]
script = ExtResource("6_6bgrj")
IdleState = NodePath("../Idle")
[node name="Debug" type="Control" parent="."] [node name="Debug" type="Control" parent="."]
visible = false visible = false
layout_mode = 3 layout_mode = 3

View File

@ -1,10 +0,0 @@
namespace SupaLidlGame.Characters.State
{
public partial class MoveState : CharacterState
{
public override CharacterState PhysicsProcess(double delta)
{
return base.PhysicsProcess(delta);
}
}
}

View File

@ -1,59 +0,0 @@
using Godot;
using SupaLidlGame.Items;
namespace SupaLidlGame.Characters.State
{
public partial class PlayerAttackState : PlayerState
{
private double _attackTime = 0;
public override CharacterState Enter(CharacterState previousState)
{
if (Character.Inventory.SelectedItem is Weapon weapon)
{
_attackTime = weapon.UseTime;
weapon.Visible = true;
Character.Inventory.SelectedItem.Use();
}
else
{
return IdleState;
}
return base.Enter(previousState);
}
public override void Exit(CharacterState nextState)
{
if (Character.Inventory.SelectedItem is null)
{
}
Character.Inventory.SelectedItem.Deuse();
if (Character.Inventory.SelectedItem is Weapon weapon)
{
//weapon.Visible = false;
}
base.Exit(nextState);
}
public override CharacterState Input(InputEvent @event)
{
return base.Input(@event);
}
public override CharacterState PhysicsProcess(double delta)
{
Character.Velocity *= 0.5f;
return base.PhysicsProcess(delta);
}
public override CharacterState Process(double delta)
{
if ((_attackTime -= delta) <= 0)
{
return IdleState;
}
return base.Process(delta);
}
}
}

View File

@ -1,26 +1,18 @@
using Godot; using Godot;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class CharacterState : Node public abstract partial class CharacterState : Node, IState<CharacterState>
{ {
public Character Character { get; set; } [Export]
public Characters.Character Character { get; set; }
/// <summary> public virtual IState<CharacterState> Enter(IState<CharacterState> prev) => null;
/// Called when the <c>Character</c> enters this <c>CharacterState</c>.
/// </summary>
/// <remarks>
/// This returns a <c>CharacterState</c> in case a state is being
/// transitioned to but wants to transition to another state. For
/// example, an attack state can return to an idle state, but that idle
/// state can override it to the move state immediately when necessary.
/// </remarks>
public virtual CharacterState Enter(CharacterState previousState) => null;
/// <summary> public virtual void Exit(IState<CharacterState> next)
/// Called when the <c>Character</c> exits this <c>CharacterState</c>. {
/// </summary>
public virtual void Exit(CharacterState nextState) { } }
public virtual CharacterState Process(double delta) public virtual CharacterState Process(double delta)
{ {

View File

@ -0,0 +1,40 @@
using Godot;
namespace SupaLidlGame.State.Character
{
public partial class CharacterStateMachine : StateMachine<CharacterState>
{
[Export]
public override CharacterState InitialState { get; set; }
[Export]
public Characters.Character Character { get; set; }
public void Process(double delta)
{
var state = CurrentState.Process(delta);
if (state is CharacterState)
{
ChangeState(state);
}
}
public void PhysicsProcess(double delta)
{
var state = CurrentState.PhysicsProcess(delta);
if (state is CharacterState)
{
ChangeState(state);
}
}
public void Input(InputEvent @event)
{
var state = CurrentState.Input(@event);
if (state is not null)
{
ChangeState(state);
}
}
}
}

View File

@ -1,6 +1,6 @@
using Godot; using Godot;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class NPCIdleState : NPCState public partial class NPCIdleState : NPCState
{ {
@ -17,7 +17,7 @@ namespace SupaLidlGame.Characters.State
return null; return null;
} }
public override CharacterState Enter(CharacterState previousState) public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
Character.Sprite.Play("idle"); Character.Sprite.Play("idle");
return base.Enter(previousState); return base.Enter(previousState);

View File

@ -1,6 +1,6 @@
using Godot; using Godot;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class NPCMoveState : NPCState public partial class NPCMoveState : NPCState
{ {
@ -17,10 +17,10 @@ namespace SupaLidlGame.Characters.State
return null; return null;
} }
public override CharacterState Enter(CharacterState previousState) public override IState<CharacterState> Enter(IState<CharacterState> prev)
{ {
Character.Sprite.Play("move"); Character.Sprite.Play("move");
return base.Enter(previousState); return base.Enter(prev);
} }
} }
} }

View File

@ -1,8 +1,8 @@
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class NPCState : CharacterState public abstract partial class NPCState : CharacterState
{ {
protected NPC _npc => Character as NPC; protected Characters.NPC _npc => Character as Characters.NPC;
public override CharacterState Process(double delta) public override CharacterState Process(double delta)
{ {

View File

@ -1,13 +1,13 @@
using Godot; using Godot;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class PlayerIdleState : PlayerState public partial class PlayerIdleState : PlayerState
{ {
[Export] [Export]
public CharacterState MoveState { get; set; } public CharacterState MoveState { get; set; }
public override CharacterState Enter(CharacterState previousState) public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
GD.Print("Entered idle state"); GD.Print("Entered idle state");
if (previousState is not PlayerMoveState) if (previousState is not PlayerMoveState)

View File

@ -1,14 +1,14 @@
using Godot; using Godot;
using SupaLidlGame.Items; using SupaLidlGame.Items;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class PlayerMoveState : PlayerState public partial class PlayerMoveState : PlayerState
{ {
[Export] [Export]
public PlayerRollState RollState { get; set; } public PlayerRollState RollState { get; set; }
public override CharacterState Enter(CharacterState previousState) public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
Godot.GD.Print("Started moving"); Godot.GD.Print("Started moving");
_player.Animation = "move"; _player.Animation = "move";

View File

@ -1,6 +1,6 @@
using Godot; using Godot;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class PlayerRollState : PlayerState public partial class PlayerRollState : PlayerState
{ {
@ -8,7 +8,7 @@ namespace SupaLidlGame.Characters.State
private Vector2 _rollDirection = Vector2.Zero; private Vector2 _rollDirection = Vector2.Zero;
public override CharacterState Enter(CharacterState previousState) public override IState<CharacterState> Enter(IState<CharacterState> previousState)
{ {
_timeLeftToRoll = 0.5; _timeLeftToRoll = 0.5;
// roll the direction we were previously moving in // roll the direction we were previously moving in
@ -17,7 +17,7 @@ namespace SupaLidlGame.Characters.State
return base.Enter(previousState); return base.Enter(previousState);
} }
public override void Exit(CharacterState nextState) public override void Exit(IState<CharacterState> nextState)
{ {
// we want to reset our state variables in case we are forced out of // we want to reset our state variables in case we are forced out of
// this state (e.g. from death) // this state (e.g. from death)
@ -35,14 +35,5 @@ namespace SupaLidlGame.Characters.State
} }
return null; return null;
} }
/*
public override CharacterState PhysicsProcess(double delta)
{
Character.Velocity = Character.Direction * Character.Speed * 1.5f;
Character.MoveAndSlide();
return null;
}
*/
} }
} }

View File

@ -1,11 +1,11 @@
using Godot; using Godot;
using SupaLidlGame.Items; using SupaLidlGame.Items;
using SupaLidlGame.Characters;
namespace SupaLidlGame.Characters.State namespace SupaLidlGame.State.Character
{ {
public partial class PlayerState : CharacterState public abstract partial class PlayerState : CharacterState
{ {
//public PlayerMachine PlayerMachine => Machine as PlayerMachine;
protected Player _player => Character as Player; protected Player _player => Character as Player;
[Export] [Export]
@ -15,28 +15,13 @@ namespace SupaLidlGame.Characters.State
{ {
var inventory = Character.Inventory; var inventory = Character.Inventory;
#if DEBUG if (this is PlayerIdleState or PlayerMoveState &&
//if (@event.IsActionPressed("equip")) !_player.Inventory.IsUsingItem)
//{
// inventory.SelectedItem = inventory.GetNode<Items.Item>("Sword");
//}
#endif
if (this is PlayerIdleState or PlayerMoveState
&& !_player.Inventory.IsUsingItem)
{ {
if (@event.IsActionPressed("equip_1")) if (@event.IsActionPressed("equip_1"))
{ {
inventory.SelectedItem = inventory.GetItemByMap("equip_1"); inventory.SelectedItem = inventory.GetItemByMap("equip_1");
} }
else if (@event.IsActionPressed("equip_2"))
{
inventory.SelectedItem = inventory.GetItemByMap("equip_2");
}
else if (@event.IsActionPressed("equip_3"))
{
inventory.SelectedItem = inventory.GetItemByMap("equip_3");
}
} }
return base.Input(@event); return base.Input(@event);

View File

@ -11,15 +11,12 @@ namespace SupaLidlGame.State
/// example, an attack state can return to an idle state, but that idle /// example, an attack state can return to an idle state, but that idle
/// state can override it to the move state immediately when necessary. /// state can override it to the move state immediately when necessary.
/// </remarks> /// </remarks>
public IState<T> Enter(IState<T> previousState) => null; public IState<T> Enter(IState<T> previousState);
/// <summary> /// <summary>
/// Called when the <c>Character</c> exits this <c>CharacterState</c>. /// Called when the <c>Character</c> exits this <c>CharacterState</c>.
/// </summary> /// </summary>
public void Exit(IState<T> nextState) public void Exit(IState<T> nextState);
{
}
public IState<T> Process(double delta) => null; public IState<T> Process(double delta) => null;
} }

View File

@ -1,90 +0,0 @@
using Godot;
namespace SupaLidlGame.Characters.State
{
public partial class Machine : Node
{
protected Character _character;
public CharacterState State { get; protected set; }
[Export]
public CharacterState InitialState { get; set; }
[Export]
public Character Character { get; set; }
[Export]
public int DebugLevel { get; set; }
public override void _Ready()
{
ChangeState(InitialState);
}
public void ChangeState(CharacterState nextState, bool isProxied = false)
{
if (nextState is null)
return;
if (DebugLevel >= 2)
{
if (State is not null)
{
string proxyNote = isProxied ? " (proxied)" : "";
GD.Print($"Transition{proxyNote} {State.Name} -> {nextState.Name}");
}
}
if (DebugLevel >= 1)
{
if (GetNode<Label>("../Debug/State") is Label label)
{
label.Text = nextState.Name;
}
}
nextState.Character = Character;
if (State != null)
{
State.Exit(nextState);
}
var nextNextState = nextState.Enter(State);
State = nextState;
if (nextNextState is not null)
{
ChangeState(nextNextState, true);
}
}
public void Process(double delta)
{
CharacterState nextState = State.Process(delta);
if (nextState is not null)
{
ChangeState(nextState);
}
}
public void PhysicsProcess(double delta)
{
CharacterState nextState = State.PhysicsProcess(delta);
if (nextState is not null)
{
ChangeState(nextState);
}
}
public void Input(InputEvent @event)
{
CharacterState nextState = State.Input(@event);
if (nextState is not null)
{
ChangeState(nextState);
}
}
}
}

View File

@ -8,7 +8,7 @@ namespace SupaLidlGame.State.Sword
public abstract IState<SwordState> Enter(IState<SwordState> previousState); public abstract IState<SwordState> Enter(IState<SwordState> previousState);
public virtual void Exit(IState<SwordState> previousState) public virtual void Exit(IState<SwordState> nextState)
{ {
} }