Remove the behavior tree and add it to the state machine.
移除行为树,加入状态机。
This commit is contained in:
parent
1bb63cbb66
commit
bce36eeee9
|
@ -42,3 +42,9 @@ log_item_has_no_owner,物品没有所有者,Item has no owner,アイテムに所
|
|||
log_no_damage_between_camps,没有阵营之间的伤害,No damage between camps,陣営間のダメージはありません
|
||||
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_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,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="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="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"]
|
||||
radius = 20.0
|
||||
|
@ -67,9 +66,6 @@ offset_top = 41.0
|
|||
offset_right = 50.0
|
||||
offset_bottom = 53.0
|
||||
|
||||
[node name="Behavior" type="Node2D" parent="."]
|
||||
script = ExtResource("5_h6w2s")
|
||||
|
||||
[node name="WallDetection" type="RayCast2D" parent="."]
|
||||
position = Vector2(3, -1)
|
||||
target_position = Vector2(50, 0)
|
||||
|
@ -81,3 +77,5 @@ collision_mask = 68
|
|||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="AttackArea2D"]
|
||||
shape = SubResource("CircleShape2D_c61vr")
|
||||
|
||||
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
|
||||
|
|
|
@ -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="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"]
|
||||
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="TileMap" type="TileMap" parent="."]
|
||||
|
@ -59,3 +65,6 @@ shape = SubResource("RectangleShape2D_7tsse")
|
|||
position = Vector2(220, 103)
|
||||
script = ExtResource("2_wamhd")
|
||||
metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn"
|
||||
|
||||
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
|
||||
navigation_polygon = SubResource("NavigationPolygon_rh1gx")
|
||||
|
|
|
@ -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="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"]
|
||||
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="TileMap" type="TileMap" parent="."]
|
||||
|
@ -60,3 +66,6 @@ shape = SubResource("RectangleShape2D_131jn")
|
|||
position = Vector2(183, 72)
|
||||
script = ExtResource("2_7q101")
|
||||
metadata/ResPath = "res://prefab/entitys/DelivererOfDarkMagic.tscn"
|
||||
|
||||
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
|
||||
navigation_polygon = SubResource("NavigationPolygon_db40i")
|
||||
|
|
|
@ -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="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"]
|
||||
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="TileMap" type="TileMap" parent="."]
|
||||
|
@ -52,3 +58,6 @@ ItemId = "staff_of_the_undead"
|
|||
[node name="Icon" type="Sprite2D" parent="ItemMarker2D"]
|
||||
scale = Vector2(0.3, 0.3)
|
||||
texture = ExtResource("4_psvpu")
|
||||
|
||||
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
|
||||
navigation_polygon = SubResource("NavigationPolygon_064c7")
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
@ -14,6 +14,12 @@ size = Vector2(22, 46)
|
|||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_6qg1t"]
|
||||
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="TileMap" type="TileMap" parent="."]
|
||||
|
@ -54,3 +60,6 @@ position = Vector2(138, 11)
|
|||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomSlotList/Area2D3"]
|
||||
position = Vector2(584, 437)
|
||||
shape = SubResource("RectangleShape2D_6qg1t")
|
||||
|
||||
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="."]
|
||||
navigation_polygon = SubResource("NavigationPolygon_1qloc")
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ColdMint.scripts.behaviorTree;
|
||||
using ColdMint.scripts.stateMachine;
|
||||
using Godot;
|
||||
|
||||
namespace ColdMint.scripts.character;
|
||||
|
@ -11,14 +11,6 @@ namespace ColdMint.scripts.character;
|
|||
/// </summary>
|
||||
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
|
||||
//用于检测墙壁的射线
|
||||
private RayCast2D? _wallDetection;
|
||||
|
@ -50,6 +42,17 @@ public sealed partial class AiCharacter : CharacterTemplate
|
|||
/// </remarks>
|
||||
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;
|
||||
|
||||
|
@ -57,9 +60,9 @@ public sealed partial class AiCharacter : CharacterTemplate
|
|||
{
|
||||
base._Ready();
|
||||
_nodesInTheAttackRange = new List<Node>();
|
||||
_behaviorNode = GetNode<BehaviorNode>("Behavior");
|
||||
_wallDetection = GetNode<RayCast2D>("WallDetection");
|
||||
_attackArea = GetNode<Area2D>("AttackArea2D");
|
||||
NavigationAgent2D = GetNode<NavigationAgent2D>("NavigationAgent2D");
|
||||
if (ItemMarker2D != null)
|
||||
{
|
||||
_attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection");
|
||||
|
@ -78,10 +81,22 @@ public sealed partial class AiCharacter : CharacterTemplate
|
|||
}
|
||||
|
||||
_wallDetectionOrigin = _wallDetection.TargetPosition;
|
||||
// var patrolBehaviorTree = new PatrolBehaviorTree();
|
||||
// patrolBehaviorTree.Character = this;
|
||||
// patrolBehaviorTree.Init();
|
||||
// _behaviorNode.Root = patrolBehaviorTree.Root;
|
||||
StateMachine = new PatrolStateMachine();
|
||||
StateMachine.Context = new StateContext
|
||||
{
|
||||
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)
|
||||
|
@ -100,9 +115,12 @@ public sealed partial class AiCharacter : CharacterTemplate
|
|||
/// </summary>
|
||||
public void MoveLeft()
|
||||
{
|
||||
var oldVelocity = Velocity;
|
||||
oldVelocity.X = -_movementSpeed;
|
||||
Velocity = oldVelocity;
|
||||
if (!IsOnFloor())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_horizontalMoveVelocity = -Speed * Config.CellSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -111,9 +129,12 @@ public sealed partial class AiCharacter : CharacterTemplate
|
|||
/// </summary>
|
||||
public void MoveRight()
|
||||
{
|
||||
var oldVelocity = Velocity;
|
||||
oldVelocity.X = _movementSpeed;
|
||||
Velocity = oldVelocity;
|
||||
if (!IsOnFloor())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_horizontalMoveVelocity = Speed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -138,6 +159,21 @@ public sealed partial class AiCharacter : CharacterTemplate
|
|||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,7 +27,15 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
// Get the gravity from the project settings to be synced with RigidBody nodes.
|
||||
// 从项目设置中获取与RigidBody节点同步的重力。
|
||||
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;
|
||||
|
||||
|
@ -722,6 +730,10 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
|
||||
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.
|
||||
//我们持续设置物品的位置,为了防止放大缩小窗口时物品位置的变化。
|
||||
if (_currentItem != null)
|
||||
|
@ -730,13 +742,13 @@ public partial class CharacterTemplate : CharacterBody2D
|
|||
}
|
||||
|
||||
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.
|
||||
//增加重力。
|
||||
if (!IsOnFloor())
|
||||
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;
|
||||
_additionalForce = Vector2.Zero;
|
||||
MoveAndSlide();
|
||||
|
|
|
@ -191,11 +191,6 @@ public partial class Player : CharacterTemplate
|
|||
|
||||
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
|
||||
//在平台检测射线与平台碰撞状态改变时
|
||||
if (_platformDetectionRayCast2D != null && _platformDetectionRayCast2D.IsColliding() != _collidingWithPlatform)
|
||||
|
@ -214,7 +209,7 @@ public partial class Player : CharacterTemplate
|
|||
//Moving left and right
|
||||
//左右移动
|
||||
var axis = Input.GetAxis("ui_left", "ui_right");
|
||||
velocity.X = axis * Speed;
|
||||
velocity.X = axis * Speed * Config.CellSize;
|
||||
|
||||
//Use items
|
||||
//使用物品
|
||||
|
|
46
scripts/stateMachine/IStateContext.cs
Normal file
46
scripts/stateMachine/IStateContext.cs
Normal 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; }
|
||||
}
|
44
scripts/stateMachine/IStateMachine.cs
Normal file
44
scripts/stateMachine/IStateMachine.cs
Normal 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();
|
||||
|
||||
}
|
34
scripts/stateMachine/IStateProcessor.cs
Normal file
34
scripts/stateMachine/IStateProcessor.cs
Normal 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; }
|
||||
}
|
15
scripts/stateMachine/PatrolStateMachine.cs
Normal file
15
scripts/stateMachine/PatrolStateMachine.cs
Normal 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());
|
||||
}
|
||||
}
|
77
scripts/stateMachine/State.cs
Normal file
77
scripts/stateMachine/State.cs
Normal 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
|
||||
}
|
153
scripts/stateMachine/StateMachineTemplate.cs
Normal file
153
scripts/stateMachine/StateMachineTemplate.cs
Normal 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);
|
||||
}
|
||||
}
|
24
scripts/stateMachine/StateProcessor/PatrolStateProcessor.cs
Normal file
24
scripts/stateMachine/StateProcessor/PatrolStateProcessor.cs
Normal 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;
|
||||
}
|
39
scripts/stateMachine/StateProcessorTemplate.cs
Normal file
39
scripts/stateMachine/StateProcessorTemplate.cs
Normal 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; }
|
||||
}
|
Loading…
Reference in New Issue
Block a user