diff --git a/Characters/Character.cs b/Characters/Character.cs index 585082f..247c921 100644 --- a/Characters/Character.cs +++ b/Characters/Character.cs @@ -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); + } } } } diff --git a/Characters/ExampleEnemy.tscn b/Characters/ExampleEnemy.tscn index f44728c..2563266 100644 --- a/Characters/ExampleEnemy.tscn +++ b/Characters/ExampleEnemy.tscn @@ -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") diff --git a/Characters/NPC.cs b/Characters/NPC.cs index aa64ed0..282fee4 100644 --- a/Characters/NPC.cs +++ b/Characters/NPC.cs @@ -8,9 +8,8 @@ namespace SupaLidlGame.Characters /// /// Time in seconds it takes for the NPC to think FeelsDankCube /// - 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; } } } diff --git a/Characters/Player.cs b/Characters/Player.cs index c6111e7..457ee19 100644 --- a/Characters/Player.cs +++ b/Characters/Player.cs @@ -3,16 +3,11 @@ using System; namespace SupaLidlGame.Characters { - public partial class Player : Character - { - public override void _Ready() - { + public partial class Player : Character + { + public override void _Ready() + { - } - - public override void _Process(double delta) - { - Direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); - } - } + } + } } diff --git a/Characters/Player.tscn b/Characters/Player.tscn index 18ee00d..0754ceb 100644 --- a/Characters/Player.tscn +++ b/Characters/Player.tscn @@ -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="."] diff --git a/Characters/States/CharacterState.cs b/Characters/States/CharacterState.cs new file mode 100644 index 0000000..2d64e41 --- /dev/null +++ b/Characters/States/CharacterState.cs @@ -0,0 +1,36 @@ +using Godot; + +namespace SupaLidlGame.Characters.State +{ + public partial class CharacterState : Node + { + public Character Character { get; set; } + + /// + /// Called when the Character enters this CharacterState. + /// + /// + /// This returns a CharacterState 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. + /// + public virtual CharacterState Enter(CharacterState previousState) => null; + + /// + /// Called when the Character exits this CharacterState. + /// + 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; + } +} diff --git a/Characters/States/EnemyMachine.cs b/Characters/States/EnemyMachine.cs new file mode 100644 index 0000000..e69de29 diff --git a/Characters/States/Machine.cs b/Characters/States/Machine.cs new file mode 100644 index 0000000..bba2291 --- /dev/null +++ b/Characters/States/Machine.cs @@ -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