Skip to content

Commit

Permalink
#16 Udemy 2.5D RPG - Enemies chasing player and attacking
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnLudlow committed Sep 7, 2024
1 parent 6c26b21 commit ae4d2c8
Show file tree
Hide file tree
Showing 16 changed files with 294 additions and 65 deletions.
5 changes: 2 additions & 3 deletions Udemy25dRpg/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${env:GODOT43}",
"args": [],
"args": ["--debug-collisions", "--debug-paths", "--debug-navigation"],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
}

]
}
19 changes: 19 additions & 0 deletions Udemy25dRpg/.vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@
"reveal": "silent"
},
"problemMatcher": "$msCompile"
},

{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"build",
// Ask dotnet build to generate full paths for file names.
"/property:GenerateFullPaths=true",
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary"
],
"group": { "kind": "build", "isDefault": true },
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}
28 changes: 14 additions & 14 deletions Udemy25dRpg/Resources/Tiles.tres

Large diffs are not rendered by default.

24 changes: 14 additions & 10 deletions Udemy25dRpg/Scenes/Characters/CharacterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,31 @@ namespace Udemy25dRpg.Scenes.Characters;

public abstract partial class CharacterBase : CharacterBody3D
{
[Export, ExportGroup("Required Nodes")]
public StateMachine StateMachineNode { get; protected set; }

[Export, ExportGroup("Required Nodes")]
public AnimatedSprite3D AnimatedSprite3DNode { get; protected set; }
#region Exports (Required Nodes)
[Export, ExportGroup("Required Nodes")] public StateMachine StateMachineNode { get; protected set; }

[Export, ExportGroup("AI Nodes")]
public Path3D PathNode { get; private set; }
[Export, ExportGroup("Required Nodes")] public AnimatedSprite3D AnimatedSprite3DNode { get; protected set; }
#endregion

[Export, ExportGroup("AI Nodes")]
public NavigationAgent3D NavigationAgentNode { get; private set; }
#region Exports (AI Nodes)
[Export, ExportGroup("AI Nodes")] public Path3D PathNode { get; private set; }

[Export, ExportGroup("AI Nodes")] public NavigationAgent3D NavigationAgentNode { get; private set; }

[Export, ExportGroup("AI Nodes")] public Area3D ChaseAreaNode { get; private set; }
[Export, ExportGroup("AI Nodes")] public Area3D AttackAreaNode { get; private set; }
#endregion

public Vector2 Direction { get; protected set; } = Vector2.Zero;

public void FlipSprite()
{
if (Direction.X < 0)
if (Velocity.X < 0)
{
AnimatedSprite3DNode.FlipH = true;
}
else if (Direction.X > 0)
else if (Velocity.X > 0)
{
AnimatedSprite3DNode.FlipH = false;
}
Expand Down
4 changes: 1 addition & 3 deletions Udemy25dRpg/Scenes/Characters/Enemy/Enemy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using Godot;
using System;

namespace Udemy25dRpg.Scenes.Characters.Enemy;

public partial class Enemy : CharacterBase
Expand All @@ -18,4 +15,5 @@ public override void _Ready()
{
StateMachineNode.SwitchState<EnemyIdleState>();
}

}
58 changes: 50 additions & 8 deletions Udemy25dRpg/Scenes/Characters/Enemy/Enemy.tscn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[gd_scene load_steps=107 format=3 uid="uid://26cy6bbsdt37"]
[gd_scene load_steps=111 format=3 uid="uid://26cy6bbsdt37"]

[ext_resource type="Texture2D" uid="uid://ckonrd67f8enh" path="res://Assets/Sprites/Characters/Enemy Knight/Attack/0_Demons_of_Darkness_Slashing_000.png" id="1_gty22"]
[ext_resource type="Texture2D" uid="uid://cjdjjbuattvtu" path="res://Assets/Sprites/Characters/Enemy Knight/Idle/0_Demons_of_Darkness_Idle_000.png" id="1_le6bp"]
Expand Down Expand Up @@ -104,6 +104,8 @@
[ext_resource type="Script" path="res://Scenes/Characters/Enemy/EnemyIdleState.cs" id="102_d6q6i"]
[ext_resource type="Script" path="res://Scenes/Characters/Enemy/EnemyReturnState.cs" id="103_k2nhi"]
[ext_resource type="Script" path="res://Scenes/Characters/Enemy/EnemyPatrolState.cs" id="104_0iq21"]
[ext_resource type="Script" path="res://Scenes/Characters/Enemy/EnemyChaseState.cs" id="105_pnbh4"]
[ext_resource type="Script" path="res://Scenes/Characters/Enemy/EnemyAttackState.cs" id="106_epu7y"]

[sub_resource type="SpriteFrames" id="SpriteFrames_nl2d3"]
animations = [{
Expand Down Expand Up @@ -434,7 +436,13 @@ animations = [{
radius = 0.549619
height = 1.29953

[node name="Enemy" type="CharacterBody3D" node_paths=PackedStringArray("StateMachineNode", "AnimatedSprite3DNode", "NavigationAgentNode")]
[sub_resource type="SphereShape3D" id="SphereShape3D_jdduw"]
radius = 6.0

[sub_resource type="SphereShape3D" id="SphereShape3D_e33me"]
radius = 1.4

[node name="Enemy" type="CharacterBody3D" node_paths=PackedStringArray("StateMachineNode", "AnimatedSprite3DNode", "NavigationAgentNode", "ChaseAreaNode", "AttackAreaNode")]
collision_layer = 4
collision_mask = 7
floor_max_angle = 1.55334
Expand All @@ -443,28 +451,62 @@ script = ExtResource("1_tph2b")
StateMachineNode = NodePath("StateMachine")
AnimatedSprite3DNode = NodePath("AnimatedSprite3D")
NavigationAgentNode = NodePath("NavigationAgent3D")
ChaseAreaNode = NodePath("ChaseArea")
AttackAreaNode = NodePath("AttackArea")

[node name="AnimatedSprite3D" type="AnimatedSprite3D" parent="."]
pixel_size = 0.0025
sprite_frames = SubResource("SpriteFrames_nl2d3")
animation = &"Move"
animation = &"Attack"
frame = 11
frame_progress = 1.0

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0016498, -0.0394009, 0.0142541)
shape = SubResource("CapsuleShape3D_vahjm")

[node name="StateMachine" parent="." node_paths=PackedStringArray("CurrentState", "PossibleStates") instance=ExtResource("101_svyoj")]
CurrentState = NodePath("IdleState")
PossibleStates = [NodePath("IdleState"), NodePath("ReturnState"), NodePath("PatrolState")]
PossibleStates = [NodePath("IdleState"), NodePath("ReturnState"), NodePath("PatrolState"), NodePath("ChaseState"), NodePath("AttackState")]

[node name="IdleState" type="Node" parent="StateMachine"]
script = ExtResource("102_d6q6i")

[node name="ReturnState" type="Node" parent="StateMachine"]
script = ExtResource("103_k2nhi")

[node name="PatrolState" type="Node" parent="StateMachine"]
[node name="PatrolState" type="Node" parent="StateMachine" node_paths=PackedStringArray("IdleTimerNode")]
script = ExtResource("104_0iq21")
IdleTimerNode = NodePath("Timer")

[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0016498, -0.0394009, 0.0142541)
shape = SubResource("CapsuleShape3D_vahjm")
[node name="Timer" type="Timer" parent="StateMachine/PatrolState"]
one_shot = true

[node name="ChaseState" type="Node" parent="StateMachine" node_paths=PackedStringArray("TimerNode")]
script = ExtResource("105_pnbh4")
TimerNode = NodePath("Timer")

[node name="Timer" type="Timer" parent="StateMachine/ChaseState"]
wait_time = 0.5

[node name="AttackState" type="Node" parent="StateMachine"]
script = ExtResource("106_epu7y")

[node name="NavigationAgent3D" type="NavigationAgent3D" parent="."]
debug_enabled = true

[node name="ChaseArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 2

[node name="CollisionShape3D" type="CollisionShape3D" parent="ChaseArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00191832, 0, 0)
shape = SubResource("SphereShape3D_jdduw")

[node name="AttackArea" type="Area3D" parent="."]
collision_layer = 0
collision_mask = 2

[node name="CollisionShape3D" type="CollisionShape3D" parent="AttackArea"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00685871, 0, 0)
shape = SubResource("SphereShape3D_e33me")
27 changes: 27 additions & 0 deletions Udemy25dRpg/Scenes/Characters/Enemy/EnemyAttackState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace Udemy25dRpg.Scenes.Characters.Enemy;

public partial class EnemyAttackState : EnemyState
{
protected override void ExitState()
{
base.ExitState();

_characterNode.AnimatedSprite3DNode.AnimationFinished -= HandleAnimationFinished;
}

protected override void EnterState()
{
base.EnterState();

_characterNode.AnimatedSprite3DNode.AnimationFinished += HandleAnimationFinished;
_characterNode.AnimatedSprite3DNode.Play(nameof(Enemy.EnemyAnimations.Attack));
}

private void HandleAnimationFinished()
{
_characterNode.StateMachineNode.SwitchState<EnemyChaseState>();
}

}
57 changes: 57 additions & 0 deletions Udemy25dRpg/Scenes/Characters/Enemy/EnemyChaseState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Godot;
using System;
using System.Linq;

namespace Udemy25dRpg.Scenes.Characters.Enemy;

public partial class EnemyChaseState : EnemyState
{
private CharacterBody3D _target;

[Export] public Timer TimerNode { get; set; }

protected override void ExitState()
{
base.ExitState();

_characterNode.AttackAreaNode.BodyEntered -= HandleAttackAreaBodyEntered;
_characterNode.ChaseAreaNode.BodyExited -= HandleChaseAreaBodyExited;

TimerNode.Timeout -= HandleTimerTimeout;
TimerNode.Stop();
}

protected override void EnterState()
{
base.EnterState();

_characterNode.AnimatedSprite3DNode.Play(nameof(Enemy.EnemyAnimations.Move));

_characterNode.AttackAreaNode.BodyEntered += HandleAttackAreaBodyEntered;
_characterNode.ChaseAreaNode.BodyExited += HandleChaseAreaBodyExited;

_target = _characterNode.ChaseAreaNode.GetOverlappingBodies().OfType<CharacterBody3D>().First();

TimerNode.Timeout += HandleTimerTimeout;
TimerNode.Start();
}

private void HandleChaseAreaBodyExited(Node3D body) => _characterNode.StateMachineNode.SwitchState<EnemyReturnState>();

private void HandleAttackAreaBodyEntered(Node3D body) => _characterNode.StateMachineNode.SwitchState<EnemyAttackState>();


private void HandleTimerTimeout()
{
_destination = _target.GlobalPosition;
_characterNode.NavigationAgentNode.TargetPosition = _destination;
}


public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);

Move();
}
}
11 changes: 10 additions & 1 deletion Udemy25dRpg/Scenes/Characters/Enemy/EnemyIdleState.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
namespace Udemy25dRpg.Scenes.Characters.Enemy;

public partial class EnemyIdleState : StateMachineStateBase
public partial class EnemyIdleState : EnemyState
{
protected override void ExitState()
{
base.ExitState();

_characterNode.ChaseAreaNode.BodyEntered -= HandleChaseAreaBodyEntered;
}

protected override void EnterState()
{
_characterNode.AnimatedSprite3DNode.Play(nameof(Enemy.EnemyAnimations.Idle));
_characterNode.ChaseAreaNode.BodyEntered += HandleChaseAreaBodyEntered;
}


public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
Expand Down
64 changes: 48 additions & 16 deletions Udemy25dRpg/Scenes/Characters/Enemy/EnemyPatrolState.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
using Godot;

namespace Udemy25dRpg.Scenes.Characters.Enemy;

public partial class EnemyPatrolState : StateMachineStateBase
public partial class EnemyPatrolState : EnemyState
{
[Export]
public Timer IdleTimerNode { get; set; }

[Export(PropertyHint.Range, "0, 20, .1")]
public float MaxIdleTime { get; set; } = 4;

private int _pathNodeIndex = 0;

protected override void ExitState()
{
_characterNode.NavigationAgentNode.NavigationFinished -= HandleNavigationFinished;
_characterNode.ChaseAreaNode.BodyEntered -= HandleChaseAreaBodyEntered;

IdleTimerNode.Timeout -= HandleTimerTimeout;
IdleTimerNode.Stop();
}

protected override void EnterState()
{
_characterNode.AnimatedSprite3DNode.Play(nameof(Enemy.EnemyAnimations.Move));

_pathNodeIndex = 1;
_destination = GetPointGlobalPosition(_pathNodeIndex);
_characterNode.NavigationAgentNode.TargetPosition = _destination;

_characterNode.NavigationAgentNode.NavigationFinished += HandleNavigationFinished;
_characterNode.ChaseAreaNode.BodyEntered += HandleChaseAreaBodyEntered;

IdleTimerNode.Timeout += HandleTimerTimeout;
IdleTimerNode.Start();
}

private void HandleTimerTimeout()
{
_characterNode.AnimatedSprite3DNode.Play(nameof(Enemy.EnemyAnimations.Move));

_pathNodeIndex = Mathf.Wrap(_pathNodeIndex + 1, 0, _characterNode.PathNode.Curve.PointCount);
_destination = GetPointGlobalPosition(_pathNodeIndex);
_characterNode.NavigationAgentNode.TargetPosition = _destination;
}

private void HandleNavigationFinished()
{
_characterNode.AnimatedSprite3DNode.Play(nameof(Enemy.EnemyAnimations.Idle));

IdleTimerNode.WaitTime = new RandomNumberGenerator().RandfRange(0, MaxIdleTime);
IdleTimerNode.Start();
}

public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);

if (_characterNode.NavigationAgentNode.IsNodeReady() && _characterNode.NavigationAgentNode.IsNavigationFinished())
{
if (_pathNodeIndex >= _characterNode.PathNode.Curve.PointCount)
{
_pathNodeIndex = 0;
}

_characterNode.NavigationAgentNode.TargetPosition =
_characterNode.PathNode.Curve.GetPointPosition(_pathNodeIndex++) +
_characterNode.PathNode.GlobalPosition;
if (IdleTimerNode.IsStopped())
{
Move();
}

_characterNode.Velocity = _characterNode.GlobalPosition.DirectionTo(_characterNode.NavigationAgentNode.TargetPosition);
_characterNode.MoveAndSlide();
}
}
Loading

0 comments on commit ae4d2c8

Please sign in to comment.