From 9401913779c7ab34e11b9e4d50654bb41e8be1fa Mon Sep 17 00:00:00 2001 From: Cold-Mint Date: Thu, 18 Jul 2024 22:45:39 +0800 Subject: [PATCH] =?UTF-8?q?Join=20the=20status=20processor=20of=20the=20AI?= =?UTF-8?q?=20escape.=20=E5=8A=A0=E5=85=A5AI=E9=80=83=E8=B7=91=E7=9A=84?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=A4=84=E7=90=86=E5=99=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locals/Log.csv | 9 +- prefab/entitys/Character.tscn | 1 - prefab/entitys/DelivererOfDarkMagic.tscn | 11 +- scripts/Config.cs | 38 ------- scripts/character/AiCharacter.cs | 5 +- scripts/character/CharacterTemplate.cs | 13 +-- scripts/character/Player.cs | 2 +- scripts/projectile/ProjectileTemplate.cs | 62 +++++----- scripts/stateMachine/PatrolStateMachine.cs | 2 + .../StateProcessor/FleeProcessor.cs | 63 +++++++++++ .../StateProcessor/LookForWeaponProcessor.cs | 107 +++++++++++++++++- 11 files changed, 221 insertions(+), 92 deletions(-) create mode 100644 scripts/stateMachine/StateProcessor/FleeProcessor.cs diff --git a/locals/Log.csv b/locals/Log.csv index ec89ed7..d6d8e73 100644 --- a/locals/Log.csv +++ b/locals/Log.csv @@ -28,9 +28,7 @@ log_loot_list_has_no_entries,ID为{0}的战利品表,没有指定条目。,"Lo log_not_within_the_loot_spawn_range,给定的数值{0}没有在战利品{1}的生成范围{2}内。,The given value {0} is not within the spawn range {2} of loot {1}.,与えられた数値{0}は戦利品{1}の生成範囲{2}内にありません。 log_loot_data_quantity,有{0}个战利品数据被返回。,{0} loot data was returned.,{0}個の戦利品データが返されます。 log_loot_data_add,生成战利品{0},Add loot {0},戦利品{0}を生成する - log_warning_node_cannot_cast_to,创建的物品{0}无法被转型为类型{1},Created items {0} cannot be cast into type {1},作成されたアイテム {0} をタイプ {1} にキャストすることはできません。 - log_start_item_register_from_file,开始从文件注册物品信息,Start registering item information from files,アイテム情報をファイルから登録開始 log_found_files,找到{0}个文件,Found {0} files,{0}ファイルが見つかりました log_found_item_types,从文件中找到{0}个物品类型,Found {0} item types in files,ファイルから{0}個のアイテム・タイプが見つかった @@ -83,4 +81,9 @@ log_weapon_detected,检测到武器。,Weapon detected.,武器が検出されま log_no_weapon_detected,没有检测到武器。,No weapon detected.,武器が検出されません。 log_weapon_lost,武器丢失。,Weapon lost.,武器が失われました。 log_nearest_node_is_null,最近的节点为空。,The nearest node is null.,最も近いノードが空です。 -log_node_is_not_WeaponTemplate,节点不是WeaponTemplate。,The node is not a WeaponTemplate.,ノードはWeaponTemplateではありません。 \ No newline at end of file +log_node_is_not_WeaponTemplate,节点不是WeaponTemplate。,The node is not a WeaponTemplate.,ノードはWeaponTemplateではありません。 +log_weapon_not_in_pickup_range,武器不在拾取范围内。,The weapon is not within the pickup range.,武器は拾い取り範囲内にありません。 +log_weapon_picked_up,武器被拾取。,Weapon picked up.,武器が拾い取られました。 +log_weapon_pickup_failed,武器拾取失败。,Weapon pickup failed.,武器の拾い取りに失敗しました。 +log_enter_the_picking_range_body,进入拾取范围。,Enter the picking range.,拾い取り範囲に入ります。 +log_search_for_weapon_timeout,搜索武器超时。,Search for weapon timeout.,武器の検索がタイムアウトしました。 \ No newline at end of file diff --git a/prefab/entitys/Character.tscn b/prefab/entitys/Character.tscn index 463593b..0837ada 100644 --- a/prefab/entitys/Character.tscn +++ b/prefab/entitys/Character.tscn @@ -26,7 +26,6 @@ animations = [{ collision_layer = 4 collision_mask = 34 script = ExtResource("1_1dlls") -LootListId = null metadata/CampId = "Default" metadata/MaxHp = 32 diff --git a/prefab/entitys/DelivererOfDarkMagic.tscn b/prefab/entitys/DelivererOfDarkMagic.tscn index feedaf1..2df9169 100644 --- a/prefab/entitys/DelivererOfDarkMagic.tscn +++ b/prefab/entitys/DelivererOfDarkMagic.tscn @@ -10,8 +10,8 @@ radius = 20.0 height = 52.0 -[sub_resource type="CircleShape2D" id="CircleShape2D_vmqbt"] -radius = 34.5398 +[sub_resource type="RectangleShape2D" id="RectangleShape2D_a3myh"] +size = Vector2(46, 65) [sub_resource type="SpriteFrames" id="SpriteFrames_qumby"] animations = [{ @@ -25,10 +25,10 @@ animations = [{ }] [sub_resource type="CircleShape2D" id="CircleShape2D_c61vr"] -radius = 185.132 +radius = 153.0 [sub_resource type="CircleShape2D" id="CircleShape2D_fowd5"] -radius = 233.808 +radius = 172.29 [node name="DelivererOfDarkMagic" type="CharacterBody2D"] collision_layer = 64 @@ -48,7 +48,8 @@ collision_layer = 0 collision_mask = 8 [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2DPickingArea"] -shape = SubResource("CircleShape2D_vmqbt") +position = Vector2(0, 5.5) +shape = SubResource("RectangleShape2D_a3myh") [node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] sprite_frames = SubResource("SpriteFrames_qumby") diff --git a/scripts/Config.cs b/scripts/Config.cs index d626cbb..1278cff 100644 --- a/scripts/Config.cs +++ b/scripts/Config.cs @@ -8,19 +8,6 @@ namespace ColdMint.scripts; public static class Config { - /// - /// ID of the behavior tree - /// 行为树的ID - /// - public static class BehaviorTreeId - { - /// - /// 巡逻 - /// Patrol - /// - public const string Patrol = "Patrol"; - } - /// /// Loot table ID /// 战利品表ID @@ -34,31 +21,6 @@ public static class Config public const string Test = "test"; } - /// - /// BehaviorTreeResult - /// 行为树的结果 - /// - public static class BehaviorTreeResult - { - /// - /// Running - /// 运行中 - /// - public const int Running = 0; - - /// - /// Success - /// 成功 - /// - public const int Success = 1; - - /// - /// Failure - /// 失败 - /// - public const int Failure = 2; - } - /// /// Camp ID diff --git a/scripts/character/AiCharacter.cs b/scripts/character/AiCharacter.cs index c63eab4..505e06b 100644 --- a/scripts/character/AiCharacter.cs +++ b/scripts/character/AiCharacter.cs @@ -348,7 +348,10 @@ public sealed partial class AiCharacter : CharacterTemplate { if (node is WeaponTemplate weaponTemplate) { - _weaponInTheScoutRange?.Add(weaponTemplate); + if (CanPickItem(weaponTemplate)) + { + _weaponInTheScoutRange?.Add(weaponTemplate); + } } CanCauseHarmNode(node, (canCause, characterTemplate) => diff --git a/scripts/character/CharacterTemplate.cs b/scripts/character/CharacterTemplate.cs index baf6e6f..b0f7f89 100644 --- a/scripts/character/CharacterTemplate.cs +++ b/scripts/character/CharacterTemplate.cs @@ -240,14 +240,12 @@ public partial class CharacterTemplate : CharacterBody2D foreach (var pickingRangeBody in PickingRangeBodies) { if (pickingRangeBody is not WeaponTemplate weaponTemplate) continue; - if (weaponTemplate.Owner != null) + if (weaponTemplate.Picked) { continue; } - weaponTemplates.Add(weaponTemplate); } - return weaponTemplates.ToArray(); } @@ -308,7 +306,7 @@ public partial class CharacterTemplate : CharacterBody2D /// /// /// - private bool CanPickItem(Node node) + protected bool CanPickItem(Node node) { if (_currentItem != null && node == _currentItem) { @@ -336,7 +334,7 @@ public partial class CharacterTemplate : CharacterBody2D ///Whether successfully picked up ///是否成功拾起 /// - protected bool PickItem(Node2D? pickAbleItemNode2D) + public bool PickItem(Node2D? pickAbleItemNode2D) { //Empty reference checking is implicitly performed here. //此处隐式的执行了空引用检查。 @@ -585,12 +583,12 @@ public partial class CharacterTemplate : CharacterBody2D if (damageTemplate.Attacker is CharacterTemplate characterTemplate && !string.IsNullOrEmpty(characterTemplate.CharacterName)) { - LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, LogCat.UploadFormat,CharacterName, + LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, LogCat.UploadFormat, CharacterName, characterTemplate.CharacterName); } else { - LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, LogCat.UploadFormat,CharacterName, + LogCat.LogWithFormat("death_info", LogCat.LogLabel.Default, LogCat.UploadFormat, CharacterName, damageTemplate.Attacker.Name); } } @@ -612,6 +610,7 @@ public partial class CharacterTemplate : CharacterBody2D return; } + LogCat.Log("enter_the_picking_range_body"); PickingRangeBodiesList?.Add(node); } diff --git a/scripts/character/Player.cs b/scripts/character/Player.cs index 384161b..3977a58 100644 --- a/scripts/character/Player.cs +++ b/scripts/character/Player.cs @@ -48,7 +48,7 @@ public partial class Player : CharacterTemplate { base._Ready(); CharacterName = TranslationServerUtils.Translate("default_player_name"); - LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,ReadOnlyCharacterName, + LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, LogCat.UploadFormat, ReadOnlyCharacterName, GlobalPosition); var floatLabelPackedScene = GD.Load("res://prefab/ui/FloatLabel.tscn"); //Initializes the float label. diff --git a/scripts/projectile/ProjectileTemplate.cs b/scripts/projectile/ProjectileTemplate.cs index 80a3ad1..92b444e 100644 --- a/scripts/projectile/ProjectileTemplate.cs +++ b/scripts/projectile/ProjectileTemplate.cs @@ -14,17 +14,17 @@ namespace ColdMint.scripts.projectile; /// public partial class ProjectileTemplate : CharacterBody2D { - protected long Life; + private long _life; //The durability of the projectile //抛射体的耐久度 //When the projectile hits the object, the durability will be reduced, and when the durability is less than or equal to 0, the projectile will be destroyed //当抛射体撞击到物体时,会减少耐久度,当耐久度小于等于0时,销毁抛射体 - protected double Durability; + private double _durability; - protected int MaxDamage; - protected int MinDamage; - protected int DamageType; + private int _maxDamage; + private int _minDamage; + private int _damageType; /// /// After this time destroy the projectile @@ -37,7 +37,7 @@ public partial class ProjectileTemplate : CharacterBody2D /// The impact area of the bullet /// 子弹的碰撞区域 /// - protected Area2D? Area2D; + private Area2D? _area2D; /// /// knockback @@ -47,7 +47,7 @@ public partial class ProjectileTemplate : CharacterBody2D ///How much force does it have when hitting the character? Unit: Number of cells,The X direction of the force is inferred automatically. ///当击中角色时带有多大的力?单位:格数,力的X方向是自动推断的。 /// - protected Vector2 KnockbackForce; + private Vector2 _knockbackForce; public float Speed => GetMeta("Speed", "15").AsSingle(); @@ -61,26 +61,26 @@ public partial class ProjectileTemplate : CharacterBody2D { //The bullet's impact detection area //子弹的碰撞检测区域 - Area2D = GetNode("CollisionDetectionArea"); - Area2D.Monitoring = true; - Area2D.BodyEntered += OnBodyEnter; - Area2D.BodyExited += OnBodyExited; - Durability = GetMeta("Durability", "1").AsDouble(); - MaxDamage = GetMeta("MaxDamage", "7").AsInt32(); - MinDamage = GetMeta("MinDamage", "5").AsInt32(); - DamageType = GetMeta("DamageType", Config.DamageType.Physical).AsInt32(); - KnockbackForce = GetMeta("Knockback", Vector2.Zero).AsVector2(); + _area2D = GetNode("CollisionDetectionArea"); + _area2D.Monitoring = true; + _area2D.BodyEntered += OnBodyEnter; + _area2D.BodyExited += OnBodyExited; + _durability = GetMeta("Durability", "1").AsDouble(); + _maxDamage = GetMeta("MaxDamage", "7").AsInt32(); + _minDamage = GetMeta("MinDamage", "5").AsInt32(); + _damageType = GetMeta("DamageType", Config.DamageType.Physical).AsInt32(); + _knockbackForce = GetMeta("Knockback", Vector2.Zero).AsVector2(); //life(ms) //子弹的存在时间(毫秒) - Life = GetMeta("Life", "10000").AsInt64(); + _life = GetMeta("Life", "10000").AsInt64(); //If the existence time is less than or equal to 0, then it is set to exist for 10 seconds, and projectiles that exist indefinitely are prohibited //如果存在时间小于等于0,那么设置为存在10秒,禁止无限期存在的抛射体 - if (Life <= 0) + if (_life <= 0) { - Life = 10000; + _life = 10000; } - _destructionTime = DateTime.Now.AddMilliseconds(Life); + _destructionTime = DateTime.Now.AddMilliseconds(_life); } @@ -148,19 +148,19 @@ public partial class ProjectileTemplate : CharacterBody2D var damage = new Damage { Attacker = owner, - MaxDamage = MaxDamage, - MinDamage = MinDamage + MaxDamage = _maxDamage, + MinDamage = _minDamage }; damage.CreateDamage(); damage.MoveLeft = Velocity.X < 0; - damage.Type = DamageType; + damage.Type = _damageType; characterTemplate.Damage(damage); - if (KnockbackForce != Vector2.Zero) + if (_knockbackForce != Vector2.Zero) { //If we set the attack force, then apply the force to the object //如果我们设置了攻退力,那么将力应用到对象上 var force = new Vector2(); - var forceX = Math.Abs(KnockbackForce.X); + var forceX = Math.Abs(_knockbackForce.X); if (Velocity.X < 0) { //Beat back to port @@ -169,18 +169,18 @@ public partial class ProjectileTemplate : CharacterBody2D } force.X = forceX * Config.CellSize; - force.Y = KnockbackForce.Y * Config.CellSize; + force.Y = _knockbackForce.Y * Config.CellSize; characterTemplate.AddForce(force); } } else if (target is PickAbleTemplate pickAbleTemplate) { - if (KnockbackForce != Vector2.Zero) + if (_knockbackForce != Vector2.Zero) { //If we set the attack force, then apply the force to the object //如果我们设置了攻退力,那么将力应用到对象上 var force = new Vector2(); - var forceX = Math.Abs(KnockbackForce.X); + var forceX = Math.Abs(_knockbackForce.X); if (Velocity.X < 0) { //Beat back to port @@ -189,7 +189,7 @@ public partial class ProjectileTemplate : CharacterBody2D } force.X = forceX * Config.CellSize; - force.Y = KnockbackForce.Y * Config.CellSize; + force.Y = _knockbackForce.Y * Config.CellSize; pickAbleTemplate.ApplyImpulse(force); } } @@ -215,8 +215,8 @@ public partial class ProjectileTemplate : CharacterBody2D //请在Mask内配置子弹会和谁碰撞 //When a bullet hits an object, its durability decreases //子弹撞击到物体时,耐久度减少 - Durability--; - if (Durability <= 0) + _durability--; + if (_durability <= 0) { //When the durability is less than or equal to 0, destroy the bullet //当耐久度小于等于0时,销毁子弹 diff --git a/scripts/stateMachine/PatrolStateMachine.cs b/scripts/stateMachine/PatrolStateMachine.cs index 6286b1f..7929438 100644 --- a/scripts/stateMachine/PatrolStateMachine.cs +++ b/scripts/stateMachine/PatrolStateMachine.cs @@ -26,5 +26,7 @@ public class PatrolStateMachine : StateMachineTemplate RegisterProcessor(chaseStateProcessor); var lookForWeaponProcessor = new LookForWeaponProcessor(); RegisterProcessor(lookForWeaponProcessor); + var fleeProcessor = new FleeProcessor(); + RegisterProcessor(fleeProcessor); } } \ No newline at end of file diff --git a/scripts/stateMachine/StateProcessor/FleeProcessor.cs b/scripts/stateMachine/StateProcessor/FleeProcessor.cs new file mode 100644 index 0000000..f3f0b73 --- /dev/null +++ b/scripts/stateMachine/StateProcessor/FleeProcessor.cs @@ -0,0 +1,63 @@ +using System; +using ColdMint.scripts.character; +using Godot; + +namespace ColdMint.scripts.stateMachine.StateProcessor; + +/// +/// Escape state processor +/// 逃跑状态处理器 +/// +public class FleeProcessor : StateProcessorTemplate +{ + /// + /// When to return to enemy free status + /// 何时恢复到没有敌人的状态 + /// + private DateTime? _endTime; + + /// + /// When away from the enemy, how long to return to normal state + /// 当远离敌人后,多长时间恢复到正常状态 + /// + public TimeSpan RecoveryTimeSpan { get; set; } = TimeSpan.FromMilliseconds(300); + + protected override void OnExecute(StateContext context, Node owner) + { + if (owner is not AiCharacter aiCharacter) + { + return; + } + + var enemy = aiCharacter.GetFirstEnemyInScoutArea(); + if (enemy == null) + { + //There are no enemies left. + //没有敌人了 + if (_endTime == null) + { + _endTime = DateTime.Now + RecoveryTimeSpan; + return; + } + + if (DateTime.Now > _endTime) + { + //Recovery time, end status. + //恢复时间,结束状态。 + context.CurrentState = State.Patrol; + } + } + else + { + //Enemies + //有敌人 + //To calculate the escape direction, the vector of the enemy pointing to the character is the escape direction. + //计算逃跑方向,敌人指向角色的向量为逃跑方向。 + _endTime = null; + var direction = aiCharacter.GlobalPosition - enemy.GlobalPosition; + aiCharacter.SetTargetPosition(aiCharacter.GlobalPosition + direction); + } + } + + public override State State => State.Flee; +} \ No newline at end of file diff --git a/scripts/stateMachine/StateProcessor/LookForWeaponProcessor.cs b/scripts/stateMachine/StateProcessor/LookForWeaponProcessor.cs index 1eee7b3..da7acb0 100644 --- a/scripts/stateMachine/StateProcessor/LookForWeaponProcessor.cs +++ b/scripts/stateMachine/StateProcessor/LookForWeaponProcessor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using ColdMint.scripts.character; using ColdMint.scripts.debug; using ColdMint.scripts.utils; @@ -13,7 +14,37 @@ namespace ColdMint.scripts.stateMachine.StateProcessor; /// public class LookForWeaponProcessor : StateProcessorTemplate { - protected WeaponTemplate? TargetWeapon; + private WeaponTemplate? _targetWeapon; + + /// + /// + ///停止寻找武器的时间 + /// + /// + /// If you don't find a weapon in a while, give up looking + /// 如果在一段时间内没找到武器,那么放弃寻找 + /// + private DateTime _endTime; + + /// + /// When no weapon was found, how long did they give up looking + /// 当没有找到武器,多长时间放弃寻找 + /// + public TimeSpan RecoveryTimeSpan { get; set; } = TimeSpan.FromMilliseconds(150); + + public override void Enter(StateContext context) + { + UpdateEndTime(); + } + + /// + /// Update end time + /// 更新结束时间 + /// + private void UpdateEndTime() + { + _endTime = DateTime.Now + RecoveryTimeSpan; + } protected override void OnExecute(StateContext context, Node owner) { @@ -23,11 +54,77 @@ public class LookForWeaponProcessor : StateProcessorTemplate return; } - if (TargetWeapon != null) + if (_targetWeapon == null) { + if (DateTime.Now > _endTime) + { + //The search for the weapon ran out of time + //寻找武器时超时 + LogCat.Log("search_for_weapon_timeout", LogCat.LogLabel.LookForWeaponProcessor); + context.CurrentState = State.Flee; + return; + } + } + else + { + if (_targetWeapon.Picked) + { + //If the weapon we're looking for gets picked up, we find a new one. + //如果我们要拿的武器被别人捡了,那么重新找新的武器。 + _targetWeapon = null; + UpdateEndTime(); + return; + } + //If the nearest weapon is found, move the character to the weapon. //如果有最近的武器被找到了,那么将角色移动到武器旁边。 - aiCharacter.SetTargetPosition(TargetWeapon.GlobalPosition); + var weaponTemplates = aiCharacter.GetCanPickedWeapon(); + //Weapons are not in the range of the pickup. + //武器没在拾捡范围内。 + if (weaponTemplates.Length == 0) + { + LogCat.Log("weapon_not_in_pickup_range", LogCat.LogLabel.LookForWeaponProcessor); + aiCharacter.SetTargetPosition(_targetWeapon.GlobalPosition); + } + else + { + var haveWeapon = false; + foreach (var weaponTemplate in weaponTemplates) + { + if (weaponTemplate == _targetWeapon) + { + haveWeapon = true; + } + } + + if (haveWeapon) + { + var pickResult = aiCharacter.PickItem(_targetWeapon); + if (pickResult) + { + context.CurrentState = context.PreviousState; + //Successfully picked up the weapon. + //成功拾起武器。 + LogCat.Log("weapon_picked_up", LogCat.LogLabel.LookForWeaponProcessor); + } + else + { + _targetWeapon = null; + UpdateEndTime(); + //Weapon failed to pick up. + //武器捡起时失败。 + LogCat.Log("weapon_pickup_failed", LogCat.LogLabel.LookForWeaponProcessor); + } + } + else + { + //No weapons are included in the pickup area. + //拾捡范围内不包含武器。 + LogCat.Log("weapon_not_in_pickup_range", LogCat.LogLabel.LookForWeaponProcessor); + aiCharacter.SetTargetPosition(_targetWeapon.GlobalPosition); + } + } + return; } @@ -67,7 +164,7 @@ public class LookForWeaponProcessor : StateProcessorTemplate if (node is WeaponTemplate weaponTemplate) { - TargetWeapon = weaponTemplate; + _targetWeapon = weaponTemplate; } else {