Remove the behavior tree and add it to the state machine.

移除行为树,加入状态机。
This commit is contained in:
Cold-Mint 2024-07-01 07:55:58 +08:00
parent 1bb63cbb66
commit bce36eeee9
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
30 changed files with 557 additions and 605 deletions

View File

@ -42,3 +42,9 @@ log_item_has_no_owner,物品没有所有者,Item has no owner,アイテムに所
log_no_damage_between_camps,没有阵营之间的伤害,No damage between camps,陣営間のダメージはありません log_no_damage_between_camps,没有阵营之间的伤害,No damage between camps,陣営間のダメージはありません
log_contact_damage_disabled_during_collision,在碰撞期间禁用接触伤害,Contact damage disabled during collision,衝突中に接触ダメージが無効になります log_contact_damage_disabled_during_collision,在碰撞期间禁用接触伤害,Contact damage disabled during collision,衝突中に接触ダメージが無効になります
log_owner_of_the_item_is_not_character,物品的所有者不是角色,The owner of the item is not a character,アイテムの所有者はキャラクターではありません log_owner_of_the_item_is_not_character,物品的所有者不是角色,The owner of the item is not a character,アイテムの所有者はキャラクターではありません
log_try_to_open_state_machine_that_is_on,尝试打开处于运行状态的状态机。,Try to open a state machine that is on.,実行中のステートマシンを開こうとしています。
log_state_machine_does_not_specify_context,状态机没有指定上下文。,The state machine does not specify a context.,ステートマシンはコンテキストを指定していません。
log_state_processor_already_registered,状态处理器已经注册。,State processor already registered.,ステートプロセッサは既に登録されています。
log_state_machine_does_not_specify_processor,状态机没有指定处理器。,The state machine does not specify a processor.,ステートマシンはプロセッサを指定していません。
log_try_to_set_the_same_state,尝试设置相同的状态。,Try to set the same state.,同じ状態を設定しようとしています。
log_state_machine_does_not_specify_active_processor,状态机没有指定活动处理器。,The state machine does not specify an active processor.,ステートマシンはアクティブプロセッサを指定していません。
1 id zh en ja
42 log_try_to_open_state_machine_that_is_on 尝试打开处于运行状态的状态机。 Try to open a state machine that is on. 実行中のステートマシンを開こうとしています。
43 log_state_machine_does_not_specify_context 状态机没有指定上下文。 The state machine does not specify a context. ステートマシンはコンテキストを指定していません。
44 log_state_processor_already_registered 状态处理器已经注册。 State processor already registered. ステートプロセッサは既に登録されています。
45 log_state_machine_does_not_specify_processor 状态机没有指定处理器。 The state machine does not specify a processor. ステートマシンはプロセッサを指定していません。
46 log_try_to_set_the_same_state 尝试设置相同的状态。 Try to set the same state. 同じ状態を設定しようとしています。
47 log_state_machine_does_not_specify_active_processor 状态机没有指定活动处理器。 The state machine does not specify an active processor. ステートマシンはアクティブプロセッサを指定していません。
48
49
50

View File

@ -1,10 +1,9 @@
[gd_scene load_steps=10 format=3 uid="uid://cj65pso40syj5"] [gd_scene load_steps=9 format=3 uid="uid://cj65pso40syj5"]
[ext_resource type="Script" path="res://scripts/character/AiCharacter.cs" id="1_ubaid"] [ext_resource type="Script" path="res://scripts/character/AiCharacter.cs" id="1_ubaid"]
[ext_resource type="Texture2D" uid="uid://b1twcink38sh0" path="res://sprites/Player.png" id="2_eha68"] [ext_resource type="Texture2D" uid="uid://b1twcink38sh0" path="res://sprites/Player.png" id="2_eha68"]
[ext_resource type="Script" path="res://scripts/damage/DamageNumberNodeSpawn.cs" id="3_kiam3"] [ext_resource type="Script" path="res://scripts/damage/DamageNumberNodeSpawn.cs" id="3_kiam3"]
[ext_resource type="PackedScene" uid="uid://sqqfrmikmk5v" path="res://prefab/ui/HealthBar.tscn" id="4_gt388"] [ext_resource type="PackedScene" uid="uid://sqqfrmikmk5v" path="res://prefab/ui/HealthBar.tscn" id="4_gt388"]
[ext_resource type="Script" path="res://scripts/behaviorTree/BehaviorNode.cs" id="5_h6w2s"]
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bb8wt"] [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_bb8wt"]
radius = 20.0 radius = 20.0
@ -67,9 +66,6 @@ offset_top = 41.0
offset_right = 50.0 offset_right = 50.0
offset_bottom = 53.0 offset_bottom = 53.0
[node name="Behavior" type="Node2D" parent="."]
script = ExtResource("5_h6w2s")
[node name="WallDetection" type="RayCast2D" parent="."] [node name="WallDetection" type="RayCast2D" parent="."]
position = Vector2(3, -1) position = Vector2(3, -1)
target_position = Vector2(50, 0) target_position = Vector2(50, 0)
@ -81,3 +77,5 @@ collision_mask = 68
[node name="CollisionShape2D" type="CollisionShape2D" parent="AttackArea2D"] [node name="CollisionShape2D" type="CollisionShape2D" parent="AttackArea2D"]
shape = SubResource("CircleShape2D_c61vr") shape = SubResource("CircleShape2D_c61vr")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=7 format=3 uid="uid://b0uurp551pku"] [gd_scene load_steps=8 format=3 uid="uid://b0uurp551pku"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_a15hy"] [ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_a15hy"]
[ext_resource type="Script" path="res://scripts/map/AiCharacterSpawn.cs" id="2_wamhd"] [ext_resource type="Script" path="res://scripts/map/AiCharacterSpawn.cs" id="2_wamhd"]
@ -15,6 +15,12 @@ size = Vector2(20, 46)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_7tsse"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_7tsse"]
size = Vector2(53, 24) size = Vector2(53, 24)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_rh1gx"]
vertices = PackedVector2Array(501, 150, 9, 151, 11, 107, 43, 107, 170, 41, 169, 11, 217, 11, 219, 42, 42, 44, 470, 107, 499, 106, 469, 43)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 5, 6, 7), PackedInt32Array(0, 3, 8, 4, 7, 9), PackedInt32Array(9, 10, 0), PackedInt32Array(9, 7, 11)])
outlines = Array[PackedVector2Array]([PackedVector2Array(479, 34, 228, 32, 226, 1, 159, 1, 160, 31, 32, 35, 33, 97, 1, 97, -1, 162, 512, 160, 509, 96, 480, 97)])
source_geometry_group_name = &"navigation_polygon_source_group"
[node name="InitialRoom" type="Node2D"] [node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."] [node name="TileMap" type="TileMap" parent="."]
@ -59,3 +65,6 @@ shape = SubResource("RectangleShape2D_7tsse")
position = Vector2(220, 103) position = Vector2(220, 103)
script = ExtResource("2_wamhd") script = ExtResource("2_wamhd")
metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn" metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn"
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
navigation_polygon = SubResource("NavigationPolygon_rh1gx")

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=7 format=3 uid="uid://dslr5tdbp4noq"] [gd_scene load_steps=8 format=3 uid="uid://dslr5tdbp4noq"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"] [ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[ext_resource type="Script" path="res://scripts/map/AiCharacterSpawn.cs" id="2_7q101"] [ext_resource type="Script" path="res://scripts/map/AiCharacterSpawn.cs" id="2_7q101"]
@ -15,6 +15,12 @@ size = Vector2(46, 20)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_131jn"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_131jn"]
size = Vector2(20, 54) size = Vector2(20, 54)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_db40i"]
vertices = PackedVector2Array(499, 108, 501, 150, 246, 149, 471, 108, 12, 149, 11, 107, 41, 107, 203, 149, 245, 180, 203, 178, 43, 43, 470, 44)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 5, 6, 7), PackedInt32Array(2, 8, 9, 7), PackedInt32Array(10, 11, 3, 2, 7, 6)])
outlines = Array[PackedVector2Array]([PackedVector2Array(34, 33, 31, 97, 1, 98, 2, 159, 193, 159, 193, 188, 255, 191, 256, 159, 512, 161, 509, 98, 481, 98, 480, 35)])
source_geometry_group_name = &"navigation_polygon_source_group"
[node name="InitialRoom" type="Node2D"] [node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."] [node name="TileMap" type="TileMap" parent="."]
@ -60,3 +66,6 @@ shape = SubResource("RectangleShape2D_131jn")
position = Vector2(183, 72) position = Vector2(183, 72)
script = ExtResource("2_7q101") script = ExtResource("2_7q101")
metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn" metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn"
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
navigation_polygon = SubResource("NavigationPolygon_db40i")

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=7 format=3 uid="uid://du5ldsp613fei"] [gd_scene load_steps=8 format=3 uid="uid://du5ldsp613fei"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"] [ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
[ext_resource type="Script" path="res://scripts/map/PlayerSpawn.cs" id="2_6p8mv"] [ext_resource type="Script" path="res://scripts/map/PlayerSpawn.cs" id="2_6p8mv"]
@ -11,6 +11,12 @@ size = Vector2(450, 191)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jxmys"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_jxmys"]
size = Vector2(18, 57.75) size = Vector2(18, 57.75)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_064c7"]
vertices = PackedVector2Array(468, 174, 500, 174, 499, 214, 41, 214, 45, 45, 468, 45)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(0, 3, 4, 5)])
outlines = Array[PackedVector2Array]([PackedVector2Array(35, 35, 31, 224, 509, 225, 510, 164, 478, 164, 478, 35)])
source_geometry_group_name = &"navigation_polygon_source_group"
[node name="InitialRoom" type="Node2D"] [node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."] [node name="TileMap" type="TileMap" parent="."]
@ -52,3 +58,6 @@ ItemId = "staff_of_the_undead"
[node name="Icon" type="Sprite2D" parent="ItemMarker2D"] [node name="Icon" type="Sprite2D" parent="ItemMarker2D"]
scale = Vector2(0.3, 0.3) scale = Vector2(0.3, 0.3)
texture = ExtResource("4_psvpu") texture = ExtResource("4_psvpu")
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
navigation_polygon = SubResource("NavigationPolygon_064c7")

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=6 format=3 uid="uid://c57cc1tyreybb"] [gd_scene load_steps=7 format=3 uid="uid://c57cc1tyreybb"]
[ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"] [ext_resource type="TileSet" uid="uid://c4wpp12rr44hi" path="res://tileSets/dungeon.tres" id="1_rn2om"]
@ -14,6 +14,12 @@ size = Vector2(22, 46)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_6qg1t"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_6qg1t"]
size = Vector2(11.875, 51) size = Vector2(11.875, 51)
[sub_resource type="NavigationPolygon" id="NavigationPolygon_1qloc"]
vertices = PackedVector2Array(696, 425, 720, 425, 722, 467, 12, 470, 10, 426, 46, 424, 691, 84, 45, 48, 721, 44, 723, 80)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3, 4), PackedInt32Array(0, 4, 5), PackedInt32Array(6, 0, 5, 7), PackedInt32Array(6, 7, 8, 9)])
outlines = Array[PackedVector2Array]([PackedVector2Array(35, 39, 36, 415, 0, 417, 3, 481, 733, 477, 729, 415, 706, 415, 701, 93, 734, 89, 731, 34)])
source_geometry_group_name = &"navigation_polygon_source_group"
[node name="InitialRoom" type="Node2D"] [node name="InitialRoom" type="Node2D"]
[node name="TileMap" type="TileMap" parent="."] [node name="TileMap" type="TileMap" parent="."]
@ -54,3 +60,6 @@ position = Vector2(138, 11)
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D3"] [node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D3"]
position = Vector2(584, 437) position = Vector2(584, 437)
shape = SubResource("RectangleShape2D_6qg1t") shape = SubResource("RectangleShape2D_6qg1t")
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
navigation_polygon = SubResource("NavigationPolygon_1qloc")

View File

@ -1,31 +0,0 @@
using Godot;
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>BehaviorNode</para>
/// <para>行为节点</para>
/// </summary>
public partial class BehaviorNode : Node2D
{
public IBehaviorTreeNode? Root { get; set; }
public override void _PhysicsProcess(double delta)
{
InvokeBehaviorTreeNode(true, delta);
}
/// <summary>
/// <para>InvokeBehaviorTreeNode</para>
/// <para>调用行为树节点</para>
/// </summary>
private void InvokeBehaviorTreeNode(bool isPhysicsProcess, double delta)
{
if (Root == null)
{
return;
}
Root.Execute(isPhysicsProcess, delta);
}
}

View File

@ -1,54 +0,0 @@
using System.Collections.Generic;
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>Behavior tree node template</para>
/// <para>行为树节点模板</para>
/// </summary>
public abstract class BehaviorTreeNodeTemplate : IBehaviorTreeNode
{
private readonly List<IBehaviorTreeNode> _children = new List<IBehaviorTreeNode>();
public void AddChild(IBehaviorTreeNode child)
{
_children.Add(child);
child.Parent = this;
}
public void RemoveChild(IBehaviorTreeNode child)
{
_children.Remove(child);
child.Parent = null;
}
/// <summary>
/// <para>Gets the child node of the specified type</para>
/// <para>获取指定类型的子节点</para>
/// </summary>
/// <param name="defaultT"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected T? GetChild<T>(T? defaultT)
{
if (_children.Count == 0)
{
return defaultT;
}
foreach (var behaviorTreeNode in _children)
{
if (behaviorTreeNode is T t)
{
return t;
}
}
return defaultT;
}
public abstract int Execute(bool isPhysicsProcess, double delta);
public IBehaviorTreeNode? Parent { get; set; }
public IBehaviorTreeNode[] Children => _children.ToArray();
}

View File

@ -1,23 +0,0 @@
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>BehaviorTreeTemplate</para>
/// <para>行为树模板</para>
/// </summary>
public abstract class BehaviorTreeTemplate : IBehaviorTree
{
private IBehaviorTreeNode? _root;
private string? _id;
public IBehaviorTreeNode? Root => _root;
public string? Id => _id;
public void Init()
{
_root = CreateRoot();
_id = CreateId();
}
protected abstract IBehaviorTreeNode? CreateRoot();
protected abstract string? CreateId();
}

View File

@ -1,12 +0,0 @@
namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>IBehavior Tree</para>
/// <para>行为树</para>
/// </summary>
public interface IBehaviorTree
{
string? Id { get; }
IBehaviorTreeNode? Root { get; }
}

View File

@ -1,31 +0,0 @@

namespace ColdMint.scripts.behaviorTree;
/// <summary>
/// <para>Behavior tree node</para>
/// <para>行为树节点</para>
/// </summary>
public interface IBehaviorTreeNode
{
/// <summary>
/// <para>execution node</para>
/// <para>执行节点</para>
/// </summary>
/// <paramref name="isPhysicsProcess">
///<para>Whether to call within a physical process</para>
///<para>是否在物理流程内调用</para>
/// </paramref>
int Execute(bool isPhysicsProcess, double delta);
/// <summary>
/// <para>The parent of this node</para>
/// <para>此节点的父节点</para>
/// </summary>
IBehaviorTreeNode? Parent { get; set; }
/// <summary>
/// <para>child node</para>
/// <para>子节点</para>
/// </summary>
IBehaviorTreeNode[] Children { get; }
}

View File

@ -1,89 +0,0 @@
using ColdMint.scripts.camp;
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>AI attack node</para>
/// <para>AI的攻击节点</para>
/// </summary>
public class AiAttackNode : BehaviorTreeNodeTemplate
{
public AiCharacter? Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
var nodesInTheAttackRange = Character.NodesInTheAttackRange;
if (nodesInTheAttackRange.Length == 0)
{
//No nodes are in range of the attack
//没有节点在攻击范围内
return Config.BehaviorTreeResult.Failure;
}
//Save the nearest enemy
//保存最近的敌人
CharacterTemplate? closestEnemy = null;
var closestDistance = float.MaxValue;
var selfCamp = CampManager.GetCamp(Character.CampId);
foreach (var node in nodesInTheAttackRange)
{
if (node is not CharacterTemplate characterTemplate)
{
continue;
}
if (node == Character)
{
continue;
}
var characterCamp = CampManager.GetCamp(characterTemplate.CampId);
var canCause = CampManager.CanCauseHarm(selfCamp, characterCamp);
if (!canCause)
{
continue;
}
if (selfCamp == null || characterCamp == null)
{
continue;
}
if (selfCamp.Id == characterCamp.Id)
{
//If it is the same side, do not attack, if allowed friend damage, this code will prevent the AI from actively attacking the player.
//如果是同一阵营不攻击如果允许友伤这段代码会阻止AI主动攻击玩家。
continue;
}
var distance = characterTemplate.GlobalPosition.DistanceTo(Character.GlobalPosition);
if (distance < closestDistance)
{
closestDistance = distance;
closestEnemy = characterTemplate;
}
}
if (closestEnemy != null && Character.AttackObstacleDetection != null)
{
//With the nearest enemy and no obstacles
//有距离最近的敌人,且没有障碍物
var distanceVector2 = closestEnemy.GlobalPosition - Character.GlobalPosition;
Character.AttackObstacleDetection.TargetPosition = distanceVector2;
if (Character.AttackObstacleDetection.GetCollider() == null)
{
Character.StopMoving();
Character.AimTheCurrentItemAtAPoint(closestEnemy.GlobalPosition);
Character.UseItem(closestEnemy.GlobalPosition);
}
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -1,73 +0,0 @@
using ColdMint.scripts.behaviorTree.framework;
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>AI patrol node</para>
/// <para>AI巡逻节点</para>
/// </summary>
public class AiPatrolNode : SelectorNode
{
public AiCharacter? Character { get; set; }
protected override IBehaviorTreeNode? SelectNode(bool isPhysicsProcess, double delta, IBehaviorTreeNode[] children)
{
if (Character == null)
{
return null;
}
if (Character.NodesInTheAttackRange.Length > 1)
{
if (Character.CurrentItem == null)
{
//No weapon
//没有武器
var weaponTemplates = Character.GetCanPickedWeapon();
if (weaponTemplates.Length > 0)
{
var aiPickNode = GetChild<AiPickNode>(null);
if (aiPickNode != null)
{
return aiPickNode;
}
}
//No weapon, and no weapon to pick up, then try to escape
//没有武器,且没有武器可捡,那么尝试逃跑
var aiRotorNode = GetChild<AiRotorNode>(null);
if (aiRotorNode != null)
{
return aiRotorNode;
}
return children[0];
}
//There are enemies around
//周围有敌人
if (Character.AttackObstacleDetection != null && Character.AttackObstacleDetection.GetCollider() == null)
{
var aiAttackNode = GetChild<AiAttackNode>(null);
if (aiAttackNode != null)
{
return aiAttackNode;
}
}
}
if (Character.WallDetection?.GetCollider() != null)
{
//Encounter a wall
//遇到墙壁
var aiRotorNode = GetChild<AiRotorNode>(null);
if (aiRotorNode != null)
{
return aiRotorNode;
}
}
return children[0];
}
}

View File

@ -1,66 +0,0 @@
using ColdMint.scripts.character;
using WeaponTemplate = ColdMint.scripts.weapon.WeaponTemplate;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>Deal with AI picking up items</para>
/// <para>处理AI拾起物品的行为</para>
/// </summary>
public class AiPickNode : BehaviorTreeNodeTemplate
{
public AiCharacter? Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
if (Character.CurrentItem != null)
{
//If the character already has the item, we don't pick it up
//如果角色已经持有物品了,我们就不再拾取
return Config.BehaviorTreeResult.Success;
}
//Find the nearest item
//查找距离最近的物品
var childCount = Character.PickingRangeBodies.Length;
if (childCount == 0)
{
//We can't pick things up without them
//没有物品,我们不能捡起
return Config.BehaviorTreeResult.Failure;
}
//The closest weapon
//距离最近的武器
WeaponTemplate? closestWeapon = null;
var closestDistance = float.MaxValue;
foreach (var weaponTemplate in Character.GetCanPickedWeapon())
{
//If it's a weapon
//如果是武器
var distanceLength = weaponTemplate.GlobalPosition.DistanceTo(Character.GlobalPosition);
if (distanceLength < closestDistance)
{
closestDistance = distanceLength;
closestWeapon = weaponTemplate;
}
}
//绘制一条线从AI到武器
// Draw a line from AI to weapon
if (closestWeapon != null)
{
//If we find the nearest weapon
//如果找到了最近的武器
Character.PickItem(closestWeapon);
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -1,31 +0,0 @@
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>The node that controls the rotor when the AI is facing the wall</para>
/// <para>当AI面向墙壁时控制转头的节点</para>
/// </summary>
public class AiRotorNode : BehaviorTreeNodeTemplate
{
public AiCharacter? Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
var notFacingTheWall = Character.WallDetection?.GetCollider() == null;
if (notFacingTheWall)
{
return Config.BehaviorTreeResult.Failure;
}
else
{
Character.Rotor();
return Config.BehaviorTreeResult.Success;
}
}
}

View File

@ -1,33 +0,0 @@
using ColdMint.scripts.character;
namespace ColdMint.scripts.behaviorTree.ai;
/// <summary>
/// <para>一个节点用于实现角色的移动</para>
/// <para>A node is used to implement the movement of the character</para>
/// </summary>
public class AiWalkNode : BehaviorTreeNodeTemplate
{
public AiCharacter? Character { get; set; }
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Character == null)
{
return Config.BehaviorTreeResult.Failure;
}
if (Character.FacingLeft)
{
//If the character is facing left, move left
//如果角色面向左边,那么向左移动
Character.MoveLeft();
}
else
{
Character.MoveRight();
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -1,22 +0,0 @@
namespace ColdMint.scripts.behaviorTree.framework;
/// <summary>
/// <para>并行节点</para>
/// <para>ParallelNode</para>
/// </summary>
/// <remarks>
///<para>Run all of its child nodes</para>
///<para>将其所有子节点都运行一遍</para>
/// </remarks>
public class ParallelNode : BehaviorTreeNodeTemplate
{
public override int Execute(bool isPhysicsProcess, double delta)
{
foreach (var child in Children)
{
child.Execute(isPhysicsProcess, delta);
}
return Config.BehaviorTreeResult.Success;
}
}

View File

@ -1,27 +0,0 @@
namespace ColdMint.scripts.behaviorTree.framework;
/// <summary>
/// <para>Selector node</para>
/// <para>选择器节点</para>
/// </summary>
/// <remarks>
///<para>Select an execution of the child node and pass the execution result to the parent node</para>
///<para>选择其子节点的某一个执行,并将执行结果传递给父节点</para>
/// </remarks>
public abstract class SelectorNode : BehaviorTreeNodeTemplate
{
public override int Execute(bool isPhysicsProcess, double delta)
{
var behaviorTreeNode = SelectNode(isPhysicsProcess, delta, Children);
return behaviorTreeNode?.Execute(isPhysicsProcess, delta) ?? Config.BehaviorTreeResult.Failure;
}
/// <summary>
/// <para>Select an abstract method for the node</para>
/// <para>选择节点的抽象方法</para>
/// </summary>
/// <returns></returns>
protected abstract IBehaviorTreeNode? SelectNode(bool isPhysicsProcess, double delta, IBehaviorTreeNode[] children);
}

View File

@ -1,71 +0,0 @@
namespace ColdMint.scripts.behaviorTree.framework;
/// <summary>
/// <para>SequenceNode</para>
/// <para>序列器节点</para>
/// </summary>
/// <remarks>
///<para>Execute all its child nodes in turn, that is, after the current one returns to the "finished" state, run the second child node, until all nodes return "finished", then this node returns "finished".</para>
///<para>将其所有子节点依次执行,也就是说当前一个返回“完成”状态后,再运行第二个子节点,直到所有节点都返回“完成”后,那么此节点返回"完成"</para>
/// </remarks>
public class SequenceNode : BehaviorTreeNodeTemplate
{
/// <summary>
/// <para>Check whether all child nodes are executed in sequence</para>
/// <para>所有子节点是否按顺序执行完毕</para>
/// </summary>
private bool _complete = true;
public override int Execute(bool isPhysicsProcess, double delta)
{
if (Children.Length == 0)
{
return Config.BehaviorTreeResult.Failure;
}
if (_complete)
{
//If the last execution is over, we start executing a new sequence
//如果上次执行完毕了,我们开始执行新的序列
_complete = false;
}
else
{
//If it hasn't finished, we return to Running
//如果还没有执行完毕我们返回Running
return Config.BehaviorTreeResult.Running;
}
var result = true;
foreach (var behaviorTreeNode in Children)
{
var singleResult = behaviorTreeNode.Execute(isPhysicsProcess, delta);
while (singleResult == Config.BehaviorTreeResult.Running)
{
//Wait for the child node to complete execution
//等得子节点执行完毕
}
//Single child node is executed
//单个子节点执行完毕
if (singleResult == Config.BehaviorTreeResult.Failure)
{
//If a child node fails, the entire sequence fails
//如果有一个子节点失败,整个序列失败
result = false;
}
}
//All child nodes are executed
//全部子节点执行完毕
_complete = true;
if (result)
{
return Config.BehaviorTreeResult.Success;
}
else
{
return Config.BehaviorTreeResult.Failure;
}
}
}

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ColdMint.scripts.behaviorTree; using ColdMint.scripts.stateMachine;
using Godot; using Godot;
namespace ColdMint.scripts.character; namespace ColdMint.scripts.character;
@ -11,14 +11,6 @@ namespace ColdMint.scripts.character;
/// </summary> /// </summary>
public sealed partial class AiCharacter : CharacterTemplate public sealed partial class AiCharacter : CharacterTemplate
{ {
/// <summary>
/// <para>How fast the character moves</para>
/// <para>角色的移动速度</para>
/// </summary>
private float _movementSpeed = 300.0f;
private BehaviorNode? _behaviorNode;
//Used to detect rays on walls //Used to detect rays on walls
//用于检测墙壁的射线 //用于检测墙壁的射线
private RayCast2D? _wallDetection; private RayCast2D? _wallDetection;
@ -50,6 +42,17 @@ public sealed partial class AiCharacter : CharacterTemplate
/// </remarks> /// </remarks>
private RayCast2D? _attackObstacleDetection; private RayCast2D? _attackObstacleDetection;
private float _horizontalMoveVelocity;
/// <summary>
/// <para>Navigation agent</para>
/// <para>导航代理</para>
/// </summary>
public NavigationAgent2D? NavigationAgent2D { get; set; }
public IStateMachine? StateMachine { get; set; }
public RayCast2D? AttackObstacleDetection => _attackObstacleDetection; public RayCast2D? AttackObstacleDetection => _attackObstacleDetection;
@ -57,9 +60,9 @@ public sealed partial class AiCharacter : CharacterTemplate
{ {
base._Ready(); base._Ready();
_nodesInTheAttackRange = new List<Node>(); _nodesInTheAttackRange = new List<Node>();
_behaviorNode = GetNode<BehaviorNode>("Behavior");
_wallDetection = GetNode<RayCast2D>("WallDetection"); _wallDetection = GetNode<RayCast2D>("WallDetection");
_attackArea = GetNode<Area2D>("AttackArea2D"); _attackArea = GetNode<Area2D>("AttackArea2D");
NavigationAgent2D = GetNode<NavigationAgent2D>("NavigationAgent2D");
if (ItemMarker2D != null) if (ItemMarker2D != null)
{ {
_attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection"); _attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection");
@ -78,10 +81,22 @@ public sealed partial class AiCharacter : CharacterTemplate
} }
_wallDetectionOrigin = _wallDetection.TargetPosition; _wallDetectionOrigin = _wallDetection.TargetPosition;
// var patrolBehaviorTree = new PatrolBehaviorTree(); StateMachine = new PatrolStateMachine();
// patrolBehaviorTree.Character = this; StateMachine.Context = new StateContext
// patrolBehaviorTree.Init(); {
// _behaviorNode.Root = patrolBehaviorTree.Root; CurrentState = State.Patrol,
Owner = this
};
if (StateMachine != null)
{
StateMachine.Start();
}
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
StateMachine?.Execute();
velocity.X = _horizontalMoveVelocity;
} }
private void EnterTheAttackArea(Node node) private void EnterTheAttackArea(Node node)
@ -100,9 +115,12 @@ public sealed partial class AiCharacter : CharacterTemplate
/// </summary> /// </summary>
public void MoveLeft() public void MoveLeft()
{ {
var oldVelocity = Velocity; if (!IsOnFloor())
oldVelocity.X = -_movementSpeed; {
Velocity = oldVelocity; return;
}
_horizontalMoveVelocity = -Speed * Config.CellSize;
} }
/// <summary> /// <summary>
@ -111,9 +129,12 @@ public sealed partial class AiCharacter : CharacterTemplate
/// </summary> /// </summary>
public void MoveRight() public void MoveRight()
{ {
var oldVelocity = Velocity; if (!IsOnFloor())
oldVelocity.X = _movementSpeed; {
Velocity = oldVelocity; return;
}
_horizontalMoveVelocity = Speed;
} }
/// <summary> /// <summary>
@ -138,6 +159,21 @@ public sealed partial class AiCharacter : CharacterTemplate
/// </summary> /// </summary>
public void StopMoving() public void StopMoving()
{ {
Velocity = Vector2.Zero; _horizontalMoveVelocity = 0;
}
public override void _ExitTree()
{
base._ExitTree();
if (_attackArea != null)
{
_attackArea.BodyEntered -= EnterTheAttackArea;
_attackArea.BodyExited -= ExitTheAttackArea;
}
if (StateMachine != null)
{
StateMachine.Stop();
}
} }
} }

View File

@ -27,7 +27,15 @@ public partial class CharacterTemplate : CharacterBody2D
// Get the gravity from the project settings to be synced with RigidBody nodes. // Get the gravity from the project settings to be synced with RigidBody nodes.
// 从项目设置中获取与RigidBody节点同步的重力。 // 从项目设置中获取与RigidBody节点同步的重力。
protected float Gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle(); protected float Gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();
protected const float Speed = 300.0f; /// <summary>
/// <para>How fast the character moves</para>
/// <para>角色的移动速度</para>
/// </summary>
/// <remarks>
///<para>How many squares per second?</para>
///<para>每秒移动几格?</para>
/// </remarks>
protected const float Speed = 5f;
protected const float JumpVelocity = -240; protected const float JumpVelocity = -240;
@ -722,6 +730,10 @@ public partial class CharacterTemplate : CharacterBody2D
public sealed override void _PhysicsProcess(double delta) public sealed override void _PhysicsProcess(double delta)
{ {
if (!Visible)
{
return;
}
//We continuously set the position of the items to prevent them from changing as we zoom in and out of the window. //We continuously set the position of the items to prevent them from changing as we zoom in and out of the window.
//我们持续设置物品的位置,为了防止放大缩小窗口时物品位置的变化。 //我们持续设置物品的位置,为了防止放大缩小窗口时物品位置的变化。
if (_currentItem != null) if (_currentItem != null)
@ -730,13 +742,13 @@ public partial class CharacterTemplate : CharacterBody2D
} }
var velocity = Velocity; var velocity = Velocity;
// The ref keyword passes its pointer to the following method so that it can be modified in the method.
// ref关键字将其指针传递给下面的方法以便在方法中修改它。
HookPhysicsProcess(ref velocity, delta);
// Add the gravity. // Add the gravity.
//增加重力。 //增加重力。
if (!IsOnFloor()) if (!IsOnFloor())
velocity.Y += Gravity * (float)delta; velocity.Y += Gravity * (float)delta;
// The ref keyword passes its pointer to the following method so that it can be modified in the method.
// ref关键字将其指针传递给下面的方法以便在方法中修改它。
HookPhysicsProcess(ref velocity, delta);
Velocity = velocity + _additionalForce; Velocity = velocity + _additionalForce;
_additionalForce = Vector2.Zero; _additionalForce = Vector2.Zero;
MoveAndSlide(); MoveAndSlide();

View File

@ -191,11 +191,6 @@ public partial class Player : CharacterTemplate
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta) protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{ {
if (!Visible)
{
return;
}
//When the collision state between the platform detection ray and the platform changes //When the collision state between the platform detection ray and the platform changes
//在平台检测射线与平台碰撞状态改变时 //在平台检测射线与平台碰撞状态改变时
if (_platformDetectionRayCast2D != null && _platformDetectionRayCast2D.IsColliding() != _collidingWithPlatform) if (_platformDetectionRayCast2D != null && _platformDetectionRayCast2D.IsColliding() != _collidingWithPlatform)
@ -214,7 +209,7 @@ public partial class Player : CharacterTemplate
//Moving left and right //Moving left and right
//左右移动 //左右移动
var axis = Input.GetAxis("ui_left", "ui_right"); var axis = Input.GetAxis("ui_left", "ui_right");
velocity.X = axis * Speed; velocity.X = axis * Speed * Config.CellSize;
//Use items //Use items
//使用物品 //使用物品

View File

@ -0,0 +1,46 @@
using System;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>Context of the state machine</para>
/// <para>状态机的上下文环境</para>
/// </summary>
public class StateContext
{
private State _currentState;
/// <summary>
/// <para>The state context holds the current state</para>
/// <para>状态上下文持有当前状态</para>
/// </summary>
public State CurrentState
{
get => _currentState;
set
{
if (_currentState == value)
{
LogCat.LogWarning("try_to_set_the_same_state");
return;
}
OnStateChange?.Invoke(_currentState, value);
_currentState = value;
}
}
/// <summary>
/// <para>When the state changes</para>
/// <para>当状态改变时</para>
/// </summary>
public Action<State, State>? OnStateChange;
/// <summary>
/// <para>owner</para>
/// <para>主人</para>
/// </summary>
public Node? Owner { get; set; }
}

View File

@ -0,0 +1,44 @@
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>IStateMachine</para>
/// <para>状态机</para>
/// </summary>
public interface IStateMachine
{
/// <summary>
/// <para>StateContext</para>
/// <para>状态机上下文</para>
/// </summary>
/// <remarks>
///<para>The state machine holds the context</para>
///<para>状态机持有上下文</para>
/// </remarks>
StateContext? Context { get; set; }
/// <summary>
/// <para>In operation or not</para>
/// <para>是否运行中</para>
/// </summary>
bool IsRunning { get;}
/// <summary>
/// <para>Open state machine</para>
/// <para>开启状态机</para>
/// </summary>
void Start();
/// <summary>
/// <para>Stop state machine</para>
/// <para>停止状态机</para>
/// </summary>
void Stop();
/// <summary>
/// <para>execute</para>
/// <para>执行</para>
/// </summary>
void Execute();
}

View File

@ -0,0 +1,34 @@
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>IStateProcessor</para>
/// <para>状态处理器</para>
/// </summary>
public interface IStateProcessor
{
/// <summary>
/// <para>Enter the current state</para>
/// <para>进入当前状态时</para>
/// </summary>
/// <param name="context"></param>
void Enter(StateContext context);
/// <summary>
/// <para>Execution processor</para>
/// <para>执行处理器</para>
/// </summary>
/// <param name="context"></param>
void Execute(StateContext context);
/// <summary>
/// <para>When exiting a state</para>
/// <para>退出某个状态时</para>
/// </summary>
/// <param name="context"></param>
void Exit(StateContext context);
/// <summary>
/// <para>Gets the state to be processed by this processor</para>
/// <para>获取此处理器要处理的状态</para>
/// </summary>
State State { get; }
}

View File

@ -0,0 +1,15 @@
using ColdMint.scripts.stateMachine.StateProcessor;
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>State machine for patrollers</para>
/// <para>适用于巡逻者的状态机</para>
/// </summary>
public class PatrolStateMachine : StateMachineTemplate
{
protected override void OnStart(StateContext context)
{
RegisterProcessor(new PatrolStateProcessor());
}
}

View File

@ -0,0 +1,77 @@
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>State</para>
/// <para>状态</para>
/// </summary>
public enum State
{
/// <summary>
/// <para>idle</para>
/// <para>空闲</para>
/// </summary>
Idle,
/// <summary>
/// <para>Look for weapons</para>
/// <para>寻找武器</para>
/// </summary>
LookForWeapon,
/// <summary>
/// <para>Chase</para>
/// <para>追击</para>
/// </summary>
/// <remarks>
///<para>When the AI character detects the player or target, it enters this state and attempts to chase the target. It can be subdivided into searching target, shortening distance, keeping distance and other sub-states.</para>
///<para>当 AI 角色检测到玩家或目标时,进入此状态并尝试追赶目标。可以细分为搜索目标、缩短距离、保持距离等子状态。</para>
/// </remarks>
Chase,
/// <summary>
/// <para>Attack</para>
/// <para>攻击</para>
/// </summary>
/// <remarks>
///<para>This state is entered when the AI character is close to the target and ready to attack. It can be subdivided into the sub-states of selecting attack mode, accumulating power, executing attack, etc.</para>
///<para>当 AI 角色接近目标并准备进行攻击时,进入此状态。可以细分为选择攻击方式、蓄力、执行攻击等子状态。</para>
/// </remarks>
Attack,
/// <summary>
/// <para>Flee</para>
/// <para>逃跑</para>
/// </summary>
Flee,
/// <summary>
/// <para>Patrol</para>
/// <para>巡逻</para>
/// </summary>
/// <remarks>
///<para>When the AI character is in a goal-less state, it enters this state and patrols the designated area. It can be subdivided into sub-states such as selecting patrol path, moving patrol execution, and detecting abnormal conditions.</para>
///<para>当 AI 角色处于无目标状态时,进入此状态并在指定区域内进行巡逻。可以细分为选择巡逻路径、移动执行巡逻、检测异常情况等子状态。</para>
/// </remarks>
Patrol,
/// <summary>
/// <para>Alert</para>
/// <para>警戒</para>
/// </summary>
/// <remarks>
///<para>When the AI character detects something suspicious, it enters this state and alerts. It can be subdivided into sub-states such as scanning the environment, moving to the appropriate position, and remaining alert</para>
///<para>当 AI 角色检测到可疑情况时,进入此状态并进行警戒。可以细分为扫描环境、移动至合适位置、保持警戒状态等子状态</para>
/// </remarks>
Alert,
/// <summary>
/// <para>Interact</para>
/// <para>互动</para>
/// </summary>
/// <remarks>
///<para>This state is entered when the AI character needs to interact with the environment or other characters. It can be subdivided into sub-states such as judging the interaction object, choosing the interaction mode, and executing the interaction.</para>
///<para>当 AI 角色需要与环境或其他角色进行互动时,进入此状态。可以细分为判断互动对象、选择互动方式、执行互动等子状态。</para>
/// </remarks>
Interact
}

View File

@ -0,0 +1,153 @@
using System.Collections.Generic;
using ColdMint.scripts.debug;
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>State machine template</para>
/// <para>状态机模板</para>
/// </summary>
public abstract class StateMachineTemplate : IStateMachine
{
private StateContext? _context;
private IStateProcessor? _activeStatusrocessor;
public StateContext? Context
{
get => _context;
set
{
if (value == null)
{
return;
}
if (_context != null)
{
_context.OnStateChange -= OnStateChange;
}
_context = value;
value.OnStateChange += OnStateChange;
}
}
private bool _isRunning;
private Dictionary<State, IStateProcessor>? _processors;
public bool IsRunning => _isRunning;
/// <summary>
/// <para>When the state in the context changes</para>
/// <para>当上下文内的状态改变时</para>
/// </summary>
/// <param name="oldState"></param>
/// <param name="newState"></param>
private void OnStateChange(State oldState, State newState)
{
if (_context == null)
{
LogCat.LogError("state_machine_does_not_specify_context");
return;
}
if (_processors == null)
{
LogCat.LogError("state_machine_does_not_specify_processor");
return;
}
if (_processors.TryGetValue(oldState, out var processor))
{
processor.Exit(_context);
}
if (_processors.TryGetValue(newState, out processor))
{
processor.Enter(_context);
_activeStatusrocessor = processor;
}
}
/// <summary>
/// <para>Registration status handler</para>
/// <para>注册状态处理器</para>
/// </summary>
/// <param name="processor"></param>
protected void RegisterProcessor(IStateProcessor processor)
{
_processors ??= new Dictionary<State, IStateProcessor>();
if (!_processors.TryAdd(processor.State, processor))
{
LogCat.LogError("state_processor_already_registered");
}
}
public void Start()
{
if (_isRunning)
{
LogCat.LogError("try_to_open_state_machine_that_is_on");
return;
}
if (Context == null)
{
LogCat.LogError("state_machine_does_not_specify_context");
return;
}
OnStart(Context);
_activeStatusrocessor = _processors?[Context.CurrentState];
_isRunning = true;
}
/// <summary>
/// <para>When the state machine is turned on</para>
/// <para>在状态机开启时</para>
/// </summary>
/// <remarks>
///<para>Register the status handler in this method<see cref="RegisterProcessor"/>.</para>
///<para>请在此方法内注册状态处理器<see cref="RegisterProcessor"/>。</para>
/// </remarks>
protected abstract void OnStart(StateContext context);
/// <summary>
/// <para>When the state switch is off</para>
/// <para>在状态机关闭时</para>
/// </summary>
protected virtual void OnStop()
{
}
public void Stop()
{
if (!_isRunning)
{
return;
}
_isRunning = false;
OnStop();
}
public void Execute()
{
if (!_isRunning)
{
return;
}
if (Context == null)
{
LogCat.LogError("state_machine_does_not_specify_context");
return;
}
if (_activeStatusrocessor == null)
{
LogCat.LogError("state_machine_does_not_specify_active_processor");
return;
}
_activeStatusrocessor.Execute(Context);
}
}

View File

@ -0,0 +1,24 @@
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.stateMachine.StateProcessor;
/// <summary>
/// <para>PatrolStateProcessor</para>
/// <para>巡逻状态处理器</para>
/// </summary>
public class PatrolStateProcessor : StateProcessorTemplate
{
protected override void OnExecute(StateContext context, Node owner)
{
if (owner is not AiCharacter aiCharacter)
{
return;
}
aiCharacter.MoveLeft();
}
public override State State => State.Patrol;
}

View File

@ -0,0 +1,39 @@
using Godot;
namespace ColdMint.scripts.stateMachine;
/// <summary>
/// <para>StateProcessorTemplate</para>
/// <para>状态处理器模板</para>
/// </summary>
public abstract class StateProcessorTemplate : IStateProcessor
{
public void Enter(StateContext context)
{
throw new System.NotImplementedException();
}
public void Execute(StateContext context)
{
if (context.Owner == null)
{
return;
}
OnExecute(context, context.Owner);
}
/// <summary>
/// <para>When executed</para>
/// <para>当执行时</para>
/// </summary>
/// <param name="context"></param>
/// <param name="owner"></param>
protected abstract void OnExecute(StateContext context, Node owner);
public void Exit(StateContext context)
{
}
public abstract State State { get; }
}