state machines
parent
9cd76af028
commit
ae0b914717
|
@ -1,5 +1,4 @@
|
|||
using Godot;
|
||||
using System;
|
||||
|
||||
namespace SupaLidlGame.Characters
|
||||
{
|
||||
|
@ -16,17 +15,35 @@ namespace SupaLidlGame.Characters
|
|||
// Get the gravity from the project settings to be synced with RigidBody nodes.
|
||||
public float Gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
|
||||
|
||||
public Vector2 Direction { get; protected set; } = Vector2.Zero;
|
||||
public Vector2 Direction { get; set; } = Vector2.Zero;
|
||||
|
||||
public Vector2 Target { get; set; } = Vector2.Zero;
|
||||
|
||||
[Export]
|
||||
public State.Machine StateMachine { get; set; }
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (StateMachine != null)
|
||||
{
|
||||
StateMachine.Process(delta);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
if (StateMachine != null)
|
||||
{
|
||||
StateMachine.Input(@event);
|
||||
}
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
// movement would be more crisp with no acceleration
|
||||
Velocity = Direction * Speed;
|
||||
MoveAndSlide();
|
||||
if (StateMachine != null)
|
||||
{
|
||||
StateMachine.PhysicsProcess(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://dymwd5ihpwyqm"]
|
||||
[gd_scene load_steps=7 format=3 uid="uid://dymwd5ihpwyqm"]
|
||||
|
||||
[ext_resource type="Script" path="res://Characters/NPC.cs" id="1_4x3dm"]
|
||||
[ext_resource type="Texture2D" uid="uid://bw052v8ikfget" path="res://icon.svg" id="2_ujqd7"]
|
||||
[ext_resource type="Script" path="res://Characters/States/Machine.cs" id="3_k4ypw"]
|
||||
[ext_resource type="Script" path="res://Characters/States/NPCIdleState.cs" id="4_8r2qn"]
|
||||
[ext_resource type="Script" path="res://Characters/States/NPCMoveState.cs" id="5_utogm"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_uict5"]
|
||||
size = Vector2(32, 16)
|
||||
|
||||
[node name="ExampleEnemy" type="CharacterBody2D"]
|
||||
[node name="ExampleEnemy" type="CharacterBody2D" node_paths=PackedStringArray("StateMachine")]
|
||||
script = ExtResource("1_4x3dm")
|
||||
Speed = 32.0
|
||||
StateMachine = NodePath("StateMachine")
|
||||
|
||||
[node name="Icon" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.25, 0.25)
|
||||
|
@ -17,3 +21,16 @@ texture = ExtResource("2_ujqd7")
|
|||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0, 8)
|
||||
shape = SubResource("RectangleShape2D_uict5")
|
||||
|
||||
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
|
||||
script = ExtResource("3_k4ypw")
|
||||
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")
|
||||
|
|
|
@ -8,9 +8,8 @@ namespace SupaLidlGame.Characters
|
|||
/// <summary>
|
||||
/// Time in seconds it takes for the NPC to think FeelsDankCube
|
||||
/// </summary>
|
||||
public const float ThinkTime = 0.125f;
|
||||
public const float ThinkTime = 0.25f;
|
||||
|
||||
public Character Target { get; protected set; }
|
||||
public float[] Weights => _weights;
|
||||
|
||||
float[] _weights = new float[16];
|
||||
|
@ -30,6 +29,7 @@ namespace SupaLidlGame.Characters
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if ((_thinkTimeElapsed += delta) > ThinkTime)
|
||||
|
@ -42,6 +42,7 @@ namespace SupaLidlGame.Characters
|
|||
//Direction = (Target.GlobalPosition - GlobalPosition).Normalized();
|
||||
base._Process(delta);
|
||||
}
|
||||
*/
|
||||
|
||||
public override void _Draw()
|
||||
{
|
||||
|
@ -83,10 +84,21 @@ namespace SupaLidlGame.Characters
|
|||
return bestChar;
|
||||
}
|
||||
|
||||
public void ThinkProcess(double delta)
|
||||
{
|
||||
if ((_thinkTimeElapsed += delta) > ThinkTime)
|
||||
{
|
||||
_thinkTimeElapsed = 0;
|
||||
Think();
|
||||
}
|
||||
|
||||
Direction = _weightDirs[_bestWeightIdx];
|
||||
}
|
||||
|
||||
private void Think()
|
||||
{
|
||||
Vector2 pos = FindBestTarget().GlobalPosition;
|
||||
Vector2 dir = GlobalPosition.DirectionTo(pos);
|
||||
Vector2 dir = Target = GlobalPosition.DirectionTo(pos);
|
||||
float dist = GlobalPosition.DistanceSquaredTo(pos);
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
|
@ -99,9 +111,9 @@ namespace SupaLidlGame.Characters
|
|||
Vector2 rotatedDir = new Vector2(-dir.y, dir.x);
|
||||
float horizDot = Math.Abs(_weightDirs[i].Dot(rotatedDir));
|
||||
|
||||
// this is a smaller weight so they are more likely to
|
||||
// pick the direction they are currently heading when
|
||||
// choosing between two horizontal weights
|
||||
// this is a smaller weight so they are more likely to pick the
|
||||
// direction they are currently heading when choosing between two
|
||||
// horizontal weights
|
||||
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
|
||||
|
||||
// square so lower values are even lower
|
||||
|
@ -158,7 +170,7 @@ namespace SupaLidlGame.Characters
|
|||
else
|
||||
{
|
||||
float dot = _weightDirs[i].Dot(_weightDirs[j]);
|
||||
_weights[j] -= (dot + 1) / 4;
|
||||
_weights[j] -= (dot + 1) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,5 @@ namespace SupaLidlGame.Characters
|
|||
{
|
||||
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://bncaar8vp3b84"]
|
||||
[gd_scene load_steps=9 format=3 uid="uid://bncaar8vp3b84"]
|
||||
|
||||
[ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"]
|
||||
[ext_resource type="Texture2D" uid="uid://bw052v8ikfget" path="res://icon.svg" id="2_xmgd1"]
|
||||
[ext_resource type="Script" path="res://Characters/States/Machine.cs" id="3_npkjp"]
|
||||
[ext_resource type="Script" path="res://Characters/States/PlayerIdleState.cs" id="4_4k4mb"]
|
||||
[ext_resource type="Script" path="res://Characters/States/PlayerMoveState.cs" id="5_tx5rw"]
|
||||
[ext_resource type="Script" path="res://Characters/States/PlayerRollState.cs" id="6_6bgrj"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_bfqew"]
|
||||
size = Vector2(32, 16)
|
||||
|
||||
[node name="Node2D" type="CharacterBody2D"]
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_q5h1n"]
|
||||
font_size = 24
|
||||
|
||||
[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("StateMachine")]
|
||||
script = ExtResource("1_flygr")
|
||||
StateMachine = NodePath("StateMachine")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
scale = Vector2(0.25, 0.25)
|
||||
|
@ -21,4 +29,36 @@ shape = SubResource("RectangleShape2D_bfqew")
|
|||
current = true
|
||||
zoom = Vector2(4, 4)
|
||||
|
||||
[node name="StateManager" type="Node" parent="."]
|
||||
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
|
||||
script = ExtResource("3_npkjp")
|
||||
InitialState = NodePath("Idle")
|
||||
Character = NodePath("..")
|
||||
DebugLevel = 2
|
||||
|
||||
[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("MoveState")]
|
||||
script = ExtResource("4_4k4mb")
|
||||
MoveState = NodePath("../Move")
|
||||
|
||||
[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState", "RollState")]
|
||||
script = ExtResource("5_tx5rw")
|
||||
IdleState = NodePath("../Idle")
|
||||
RollState = NodePath("../Roll")
|
||||
|
||||
[node name="Roll" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleState")]
|
||||
script = ExtResource("6_6bgrj")
|
||||
IdleState = NodePath("../Idle")
|
||||
|
||||
[node name="Debug" type="Control" parent="."]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
|
||||
[node name="State" type="Label" parent="Debug"]
|
||||
offset_left = -20.0
|
||||
offset_top = -60.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = -34.0
|
||||
text = "lol"
|
||||
label_settings = SubResource("LabelSettings_q5h1n")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Node" type="Node" parent="."]
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class CharacterState : Node
|
||||
{
|
||||
public Character Character { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// Called when the <c>Character</c> exits this <c>CharacterState</c>.
|
||||
/// </summary>
|
||||
public virtual void Exit(CharacterState nextState) { }
|
||||
|
||||
public virtual CharacterState Process(double delta) => null;
|
||||
|
||||
public virtual CharacterState PhysicsProcess(double delta)
|
||||
{
|
||||
Character.Velocity = Character.Direction * Character.Speed;
|
||||
Character.MoveAndSlide();
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual CharacterState Input(InputEvent @event) => null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class MoveState : CharacterState
|
||||
{
|
||||
public override CharacterState PhysicsProcess(double delta)
|
||||
{
|
||||
return base.PhysicsProcess(delta);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class NPCIdleState : NPCState
|
||||
{
|
||||
[Export]
|
||||
public CharacterState MoveState { get; set; }
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() > 0)
|
||||
{
|
||||
return MoveState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class NPCMoveState : NPCState
|
||||
{
|
||||
[Export]
|
||||
public CharacterState IdleState { get; set; }
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() == 0)
|
||||
{
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class NPCState : CharacterState
|
||||
{
|
||||
protected NPC _npc => Character as NPC;
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
_npc.ThinkProcess(delta);
|
||||
return base.Process(delta);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Godot.NativeInterop;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class PlayerAttackState : PlayerState
|
||||
{
|
||||
[Export]
|
||||
public CharacterState IdleState { get; set; }
|
||||
|
||||
private float _attackTime = 0;
|
||||
|
||||
public override CharacterState Enter(CharacterState previousState)
|
||||
{
|
||||
_attackTime = 0;
|
||||
return base.Enter(previousState);
|
||||
}
|
||||
|
||||
public override void Exit(CharacterState nextState)
|
||||
{
|
||||
_attackTime = 0;
|
||||
base.Exit(nextState);
|
||||
}
|
||||
|
||||
public override CharacterState Input(InputEvent @event)
|
||||
{
|
||||
return base.Input(@event);
|
||||
}
|
||||
|
||||
public override CharacterState PhysicsProcess(double delta)
|
||||
{
|
||||
return base.PhysicsProcess(delta);
|
||||
}
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
return base.Process(delta);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class PlayerIdleState : PlayerState
|
||||
{
|
||||
[Export]
|
||||
public CharacterState MoveState { get; set; }
|
||||
|
||||
public override CharacterState Enter(CharacterState previousState)
|
||||
{
|
||||
GD.Print("Entered idle state");
|
||||
if (previousState is not PlayerMoveState)
|
||||
{
|
||||
if (Character.Direction.LengthSquared() > 0)
|
||||
{
|
||||
// other states like attacking or rolling can just delegate
|
||||
// the work of checking if the player is moving to this idle
|
||||
// state, so they do not have to manually check if the player
|
||||
// wants to move to switch to the move state.
|
||||
return MoveState;
|
||||
}
|
||||
}
|
||||
return base.Enter(previousState);
|
||||
}
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() > 0)
|
||||
{
|
||||
return MoveState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class PlayerMoveState : PlayerState
|
||||
{
|
||||
[Export]
|
||||
public CharacterState IdleState { get; set; }
|
||||
|
||||
[Export]
|
||||
public CharacterState RollState { get; set; }
|
||||
|
||||
public override CharacterState Enter(CharacterState previousState)
|
||||
{
|
||||
Godot.GD.Print("Started moving");
|
||||
return base.Enter(previousState);
|
||||
}
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
base.Process(delta);
|
||||
if (Character.Direction.LengthSquared() == 0)
|
||||
{
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override CharacterState Input(Godot.InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed("roll"))
|
||||
{
|
||||
return RollState;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class PlayerRollState : PlayerState
|
||||
{
|
||||
[Export]
|
||||
public CharacterState IdleState { get; set; }
|
||||
|
||||
private double _timeLeftToRoll = 0;
|
||||
|
||||
private Vector2 _rollDirection = Vector2.Zero;
|
||||
|
||||
public override CharacterState Enter(CharacterState previousState)
|
||||
{
|
||||
_timeLeftToRoll = 0.5;
|
||||
// roll the direction we were previously moving in
|
||||
_rollDirection = Character.Direction;
|
||||
return base.Enter(previousState);
|
||||
}
|
||||
|
||||
public override void Exit(CharacterState nextState)
|
||||
{
|
||||
// we want to reset our state variables in case we are forced out of
|
||||
// this state (e.g. from death)
|
||||
_timeLeftToRoll = 0;
|
||||
_rollDirection = Character.Direction;
|
||||
base.Exit(nextState);
|
||||
}
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
Character.Direction = _rollDirection;
|
||||
if ((_timeLeftToRoll -= delta) <= 0)
|
||||
{
|
||||
return IdleState;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override CharacterState PhysicsProcess(double delta)
|
||||
{
|
||||
Character.Velocity = Character.Direction * Character.Speed * 1.5f;
|
||||
Character.MoveAndSlide();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace SupaLidlGame.Characters.State
|
||||
{
|
||||
public partial class PlayerState : CharacterState
|
||||
{
|
||||
//public PlayerMachine PlayerMachine => Machine as PlayerMachine;
|
||||
protected Player _player => Character as Player;
|
||||
|
||||
public override CharacterState Process(double delta)
|
||||
{
|
||||
Character.Direction = Godot.Input.GetVector("ui_left", "ui_right",
|
||||
"ui_up", "ui_down");
|
||||
return base.Process(delta);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -45,6 +45,11 @@ ui_down={
|
|||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null)
|
||||
]
|
||||
}
|
||||
roll={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":3,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
|
|
Loading…
Reference in New Issue