From 4622d06e5c0020b60da743c3c8ba8df0ca68ec82 Mon Sep 17 00:00:00 2001 From: Cold-Mint Date: Sat, 6 Jul 2024 22:55:07 +0800 Subject: [PATCH] =?UTF-8?q?Make=20creatures=20try=20to=20chase=20enemies.?= =?UTF-8?q?=20=E4=BD=BF=E7=94=9F=E7=89=A9=E5=B0=9D=E8=AF=95=E8=BF=BD?= =?UTF-8?q?=E9=80=90=E6=95=8C=E4=BA=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locals/Log.csv | 12 ++- prefab/entitys/Character.tscn | 1 + scripts/camp/CampManager.cs | 7 ++ scripts/character/AiCharacter.cs | 82 +++++++++++++++---- scripts/debug/LogCat.cs | 28 ++++++- scripts/loader/uiLoader/MainMenuLoader.cs | 1 - scripts/stateMachine/IStateContext.cs | 20 ++++- scripts/stateMachine/PatrolStateMachine.cs | 2 + scripts/stateMachine/StateMachineTemplate.cs | 21 +++-- .../StateProcessor/ChaseStateProcessor.cs | 39 +++++++++ .../StateProcessor/PatrolStateProcessor.cs | 26 ++++++ .../stateMachine/StateProcessorTemplate.cs | 7 +- 12 files changed, 216 insertions(+), 30 deletions(-) create mode 100644 scripts/stateMachine/StateProcessor/ChaseStateProcessor.cs diff --git a/locals/Log.csv b/locals/Log.csv index 74ae339..606a13a 100644 --- a/locals/Log.csv +++ b/locals/Log.csv @@ -59,4 +59,14 @@ log_backpack_not_allowed,不允许添加到背包。,Not allowed to add to backp log_item_is_null,物品为空。,Item is null.,アイテムが空です。 log_item_id_not_same,物品ID不同。,Item ID is different.,アイテムIDが異なります。 log_max_quantity_exceeded,超过最大数量。,Exceeded maximum quantity.,最大数量を超えました。 -log_item_slot_is_selected_and_not_allowed,已选择物品槽,不允许添加。,"Item slot is selected, not allowed to add.",アイテムスロットが選択されており、追加は許可されていません。 \ No newline at end of file +log_item_slot_is_selected_and_not_allowed,已选择物品槽,不允许添加。,"Item slot is selected, not allowed to add.",アイテムスロットが選択されており、追加は許可されていません。 +log_patrol_enemy_detected,检测到敌人。,Enemy detected.,敵を検出しました。 +log_attacker_or_target_is_null,攻击者或目标为空。,Attacker or target is null.,攻撃者またはターゲットが空です。 +log_in_the_same_camp,在同一阵营。,In the same camp.,同じ陣営です。 +log_friendly_target,友好目标。,Friendly target.,友好的なターゲットです。 +log_can_cause_harm,可以造成伤害。,Can cause harm.,ダメージを与えることができます。 +log_camp_is_null,阵营为空。,Camp is null.,陣営が空です。 +log_set_default_camp,设置默认阵营{0}。,Set default camp {0}.,デフォルトの陣営{0}を設定します。 +log_state_change,状态变更从{0}到{1}。,State change from {0} to {1}.,状態が{0}から{1}に変更されます。 +log_state_processor_not_found,找不到状态处理器{0}。,State processor {0} not found.,ステートプロセッサ{0}が見つかりません。 +log_chase_no_enemy,追逐没有敌人。,Chase no enemy.,敵がいない追跡です。 \ No newline at end of file diff --git a/prefab/entitys/Character.tscn b/prefab/entitys/Character.tscn index 84a2e99..7053706 100644 --- a/prefab/entitys/Character.tscn +++ b/prefab/entitys/Character.tscn @@ -26,6 +26,7 @@ animations = [{ collision_layer = 4 collision_mask = 34 script = ExtResource("1_1dlls") +LootListId = null metadata/CampId = "Default" metadata/MaxHp = 12 diff --git a/scripts/camp/CampManager.cs b/scripts/camp/CampManager.cs index 6014320..c944ec8 100644 --- a/scripts/camp/CampManager.cs +++ b/scripts/camp/CampManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using ColdMint.scripts.debug; namespace ColdMint.scripts.camp; @@ -32,12 +33,14 @@ public static class CampManager { if (camp == null) { + LogCat.Log("camp_is_null", label: LogCat.LogLabel.CampManager); return false; } if (camp.Id != Config.CampId.Default) return false; _defaultCamp = camp; AddCamp(camp); + LogCat.LogWithFormat("set_default_camp", label: LogCat.LogLabel.CampManager, camp.Id); return true; } @@ -52,6 +55,7 @@ public static class CampManager { if (attacker == null || target == null) { + LogCat.Log("attacker_or_target_is_null", label: LogCat.LogLabel.CampManager); return false; } @@ -59,6 +63,7 @@ public static class CampManager { //In the same camp, return whether friendly fire is allowed //在同一阵营内,返回是否允许友伤 + LogCat.Log("in_the_same_camp", label: LogCat.LogLabel.CampManager); return attacker.FriendInjury; } @@ -74,11 +79,13 @@ public static class CampManager { //The attacker thinks the target is friendly, and we can't hurt a friendly target //攻击者认为目标友好,我们不能伤害友好的目标 + LogCat.Log("friendly_target", label: LogCat.LogLabel.CampManager); return false; } } } + LogCat.Log("can_cause_harm", label: LogCat.LogLabel.CampManager); return true; } diff --git a/scripts/character/AiCharacter.cs b/scripts/character/AiCharacter.cs index f3de832..638489b 100644 --- a/scripts/character/AiCharacter.cs +++ b/scripts/character/AiCharacter.cs @@ -1,6 +1,5 @@ -using System; using System.Collections.Generic; -using ColdMint.scripts.debug; +using ColdMint.scripts.camp; using ColdMint.scripts.stateMachine; using Godot; @@ -21,16 +20,10 @@ public sealed partial class AiCharacter : CharacterTemplate private Area2D? _attackArea; /// - /// Nodes in the attack range - /// 在攻击范围内的节点 + /// All enemies within striking distance + /// 在攻击范围内的所有敌人 /// - private List? _nodesInTheAttackRange; - - /// - /// All nodes in the attack range - /// 在攻击范围内的全部节点 - /// - public Node[] NodesInTheAttackRange => _nodesInTheAttackRange?.ToArray() ?? Array.Empty(); + private List? _enemyInTheAttackRange; /// @@ -59,7 +52,7 @@ public sealed partial class AiCharacter : CharacterTemplate public override void _Ready() { base._Ready(); - _nodesInTheAttackRange = new List(); + _enemyInTheAttackRange = new List(); _wallDetection = GetNode("WallDetection"); _attackArea = GetNode("AttackArea2D"); NavigationAgent2D = GetNode("NavigationAgent2D"); @@ -93,6 +86,38 @@ public sealed partial class AiCharacter : CharacterTemplate } } + /// + /// EnemyDetected + /// 是否发现敌人 + /// + /// + ///Have you spotted the enemy? + ///是否发现敌人 + /// + public bool EnemyDetected() + { + if (_enemyInTheAttackRange == null) + { + return false; + } + + return _enemyInTheAttackRange.Count > 0; + } + + /// + /// Get the first enemy to enter range + /// 获取第一个进入范围的敌人 + /// + /// + public CharacterTemplate? GetFirstEnemy() + { + if (_enemyInTheAttackRange == null || _enemyInTheAttackRange.Count == 0) + { + return null; + } + return _enemyInTheAttackRange[0]; + } + protected override void HookPhysicsProcess(ref Vector2 velocity, double delta) { StateMachine?.Execute(); @@ -106,12 +131,41 @@ public sealed partial class AiCharacter : CharacterTemplate private void EnterTheAttackArea(Node node) { - _nodesInTheAttackRange?.Add(node); + if (node == this) + { + //The target can't be yourself. + //攻击目标不能是自己。 + return; + } + + if (node is CharacterTemplate characterTemplate) + { + //Determine if damage can be done between factions + //判断阵营间是否可造成伤害 + var camp = CampManager.GetCamp(CampId); + var enemyCamp = CampManager.GetCamp(characterTemplate.CampId); + if (enemyCamp != null && camp != null) + { + var canCause = CampManager.CanCauseHarm(camp, enemyCamp); + if (canCause) + { + _enemyInTheAttackRange?.Add(characterTemplate); + } + } + } } private void ExitTheAttackArea(Node node) { - _nodesInTheAttackRange?.Remove(node); + if (node == this) + { + return; + } + + if (node is CharacterTemplate characterTemplate) + { + _enemyInTheAttackRange?.Remove(characterTemplate); + } } diff --git a/scripts/debug/LogCat.cs b/scripts/debug/LogCat.cs index a2508f7..e3829d5 100644 --- a/scripts/debug/LogCat.cs +++ b/scripts/debug/LogCat.cs @@ -21,8 +21,32 @@ public static class LogCat /// 巡逻状态处理器 /// public const string PatrolStateProcessor = "PatrolStateProcessor"; + + /// + /// CampManager + /// 阵营管理器 + /// + public const string CampManager = "CampManager"; + + /// + /// State context + /// 状态上下文 + /// + public const string StateContext = "StateContext"; + + /// + /// StateMachineTemplate + /// 状态机模板 + /// + public const string StateMachineTemplate = "StateMachineTemplate"; + + /// + /// Pursuit enemy processor + /// 追击敌人处理器 + /// + public const string ChaseStateProcessor = "ChaseStateProcessor"; } - + /// /// Information log level @@ -71,7 +95,7 @@ public static class LogCat /// 禁用的日志标签 /// private static HashSet DisabledLogLabels { get; } = []; - + /// /// Disable log Label diff --git a/scripts/loader/uiLoader/MainMenuLoader.cs b/scripts/loader/uiLoader/MainMenuLoader.cs index a547240..5c19867 100644 --- a/scripts/loader/uiLoader/MainMenuLoader.cs +++ b/scripts/loader/uiLoader/MainMenuLoader.cs @@ -46,7 +46,6 @@ public partial class MainMenuLoader : UiLoaderTemplate LogCat.MinLogLevel = LogCat.DisableAllLogLevel; } - LogCat.DisableLogLabel(LogCat.LogLabel.PatrolStateProcessor); ContributorDataManager.RegisterAllContributorData(); DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler()); MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor()); diff --git a/scripts/stateMachine/IStateContext.cs b/scripts/stateMachine/IStateContext.cs index 46aa6d2..2e5effc 100644 --- a/scripts/stateMachine/IStateContext.cs +++ b/scripts/stateMachine/IStateContext.cs @@ -12,6 +12,12 @@ public class StateContext { private State _currentState; + /// + /// Previous state + /// 前一个状态 + /// + private State _previousState; + /// /// The state context holds the current state /// 状态上下文持有当前状态 @@ -23,15 +29,27 @@ public class StateContext { if (_currentState == value) { - LogCat.LogWarning("try_to_set_the_same_state"); + LogCat.LogWarning("try_to_set_the_same_state", label: LogCat.LogLabel.StateContext); return; } + LogCat.LogWithFormat("state_change", label: LogCat.LogLabel.StateContext, _currentState, value); OnStateChange?.Invoke(_currentState, value); + _previousState = _currentState; _currentState = value; } } + /// + /// Previous state + /// 前一个状态 + /// + public State PreviousState + { + get => _previousState; + set => _previousState = value; + } + /// /// When the state changes /// 当状态改变时 diff --git a/scripts/stateMachine/PatrolStateMachine.cs b/scripts/stateMachine/PatrolStateMachine.cs index 3032ad2..cafa471 100644 --- a/scripts/stateMachine/PatrolStateMachine.cs +++ b/scripts/stateMachine/PatrolStateMachine.cs @@ -22,5 +22,7 @@ public class PatrolStateMachine : StateMachineTemplate ] }; RegisterProcessor(patrolStateProcessor); + var chaseStateProcessor = new ChaseStateProcessor(); + RegisterProcessor(chaseStateProcessor); } } \ No newline at end of file diff --git a/scripts/stateMachine/StateMachineTemplate.cs b/scripts/stateMachine/StateMachineTemplate.cs index 26d8e1b..e0fb25c 100644 --- a/scripts/stateMachine/StateMachineTemplate.cs +++ b/scripts/stateMachine/StateMachineTemplate.cs @@ -46,13 +46,13 @@ public abstract class StateMachineTemplate : IStateMachine { if (_context == null) { - LogCat.LogError("state_machine_does_not_specify_context"); + LogCat.LogError("state_machine_does_not_specify_context", label: LogCat.LogLabel.StateMachineTemplate); return; } if (_processors == null) { - LogCat.LogError("state_machine_does_not_specify_processor"); + LogCat.LogError("state_machine_does_not_specify_processor", label: LogCat.LogLabel.StateMachineTemplate); return; } @@ -66,6 +66,11 @@ public abstract class StateMachineTemplate : IStateMachine processor.Enter(_context); _activeStatusrocessor = processor; } + else + { + LogCat.LogErrorWithFormat("state_processor_not_found", label: LogCat.LogLabel.StateMachineTemplate, + newState); + } } /// @@ -78,7 +83,7 @@ public abstract class StateMachineTemplate : IStateMachine _processors ??= new Dictionary(); if (!_processors.TryAdd(processor.State, processor)) { - LogCat.LogError("state_processor_already_registered"); + LogCat.LogError("state_processor_already_registered", label: LogCat.LogLabel.StateMachineTemplate); } } @@ -86,13 +91,13 @@ public abstract class StateMachineTemplate : IStateMachine { if (_isRunning) { - LogCat.LogError("try_to_open_state_machine_that_is_on"); + LogCat.LogError("try_to_open_state_machine_that_is_on", label: LogCat.LogLabel.StateMachineTemplate); return; } if (Context == null) { - LogCat.LogError("state_machine_does_not_specify_context"); + LogCat.LogError("state_machine_does_not_specify_context", label: LogCat.LogLabel.StateMachineTemplate); return; } @@ -139,15 +144,17 @@ public abstract class StateMachineTemplate : IStateMachine if (Context == null) { - LogCat.LogError("state_machine_does_not_specify_context"); + LogCat.LogError("state_machine_does_not_specify_context", label: LogCat.LogLabel.StateMachineTemplate); return; } if (_activeStatusrocessor == null) { - LogCat.LogError("state_machine_does_not_specify_active_processor"); + LogCat.LogError("state_machine_does_not_specify_active_processor", + label: LogCat.LogLabel.StateMachineTemplate); return; } + _activeStatusrocessor.Execute(Context); } } \ No newline at end of file diff --git a/scripts/stateMachine/StateProcessor/ChaseStateProcessor.cs b/scripts/stateMachine/StateProcessor/ChaseStateProcessor.cs new file mode 100644 index 0000000..0874edd --- /dev/null +++ b/scripts/stateMachine/StateProcessor/ChaseStateProcessor.cs @@ -0,0 +1,39 @@ +using ColdMint.scripts.character; +using ColdMint.scripts.debug; +using Godot; + +namespace ColdMint.scripts.stateMachine.StateProcessor; + +/// +/// Chasing state processor +/// 追击状态处理器 +/// +public class ChaseStateProcessor : StateProcessorTemplate +{ + protected override void OnExecute(StateContext context, Node owner) + { + if (owner is not AiCharacter aiCharacter) + { + return; + } + + //Get the first enemy to enter the attack range. + //获取第一次进入攻击范围的敌人。 + var enemy = aiCharacter.GetFirstEnemy(); + if (enemy == null) + { + //No more enemies. Return to previous status. + //没有敌人了,返回上一个状态。 + LogCat.Log("chase_no_enemy", label: LogCat.LogLabel.ChaseStateProcessor); + context.CurrentState = context.PreviousState; + } + else + { + //Chase the enemy. + //追击敌人。 + aiCharacter.SetTargetPosition(enemy.GlobalPosition); + } + } + + public override State State => State.Chase; +} \ No newline at end of file diff --git a/scripts/stateMachine/StateProcessor/PatrolStateProcessor.cs b/scripts/stateMachine/StateProcessor/PatrolStateProcessor.cs index f21c0fe..3b40f4b 100644 --- a/scripts/stateMachine/StateProcessor/PatrolStateProcessor.cs +++ b/scripts/stateMachine/StateProcessor/PatrolStateProcessor.cs @@ -11,10 +11,29 @@ namespace ColdMint.scripts.stateMachine.StateProcessor; public class PatrolStateProcessor : StateProcessorTemplate { public Vector2[]? Points { get; set; } + /// + /// Whether to guard the origin + /// 是否需要守护原点 + /// + /// + ///When empty by default, PatrolStateProcessor will take the first point where the character touches the ground as the origin. This property handles whether or not the origin is "guarded" when the character is attracted to another character, such as chasing an enemy, and switches back to patrol mode. If set to true, the role tries to return to the origin, otherwise, a new origin is assigned. + ///默认清空下,PatrolStateProcessor会将角色与地面接触的第一个位置当作原点。这个属性用来处理当角色被其他角色所吸引,(例如追击敌人)转换回巡逻模式,是否“守护”原点。如果设置为true,则角色会尝试返回原点,否则,将分配新的原点。 + /// + public bool Guard { get; set; } private int _index; private Vector2? _originPosition; + public override void Enter(StateContext context) + { + if (!Guard) + { + //Reset the origin when transitioning to patrol. + //转换到巡逻状态时重置原点。 + _originPosition = null; + } + } + protected override void OnExecute(StateContext context, Node owner) { if (owner is not AiCharacter aiCharacter) @@ -22,6 +41,13 @@ public class PatrolStateProcessor : StateProcessorTemplate return; } + if (aiCharacter.EnemyDetected()) + { + context.CurrentState = State.Chase; + LogCat.Log("patrol_enemy_detected", label: LogCat.LogLabel.PatrolStateProcessor); + return; + } + if (Points == null || Points.Length == 0) { LogCat.LogError("no_points", label: LogCat.LogLabel.PatrolStateProcessor); diff --git a/scripts/stateMachine/StateProcessorTemplate.cs b/scripts/stateMachine/StateProcessorTemplate.cs index 798ab41..df3c2d0 100644 --- a/scripts/stateMachine/StateProcessorTemplate.cs +++ b/scripts/stateMachine/StateProcessorTemplate.cs @@ -8,9 +8,8 @@ namespace ColdMint.scripts.stateMachine; /// public abstract class StateProcessorTemplate : IStateProcessor { - public void Enter(StateContext context) + public virtual void Enter(StateContext context) { - throw new System.NotImplementedException(); } public void Execute(StateContext context) @@ -19,6 +18,7 @@ public abstract class StateProcessorTemplate : IStateProcessor { return; } + OnExecute(context, context.Owner); } @@ -30,9 +30,8 @@ public abstract class StateProcessorTemplate : IStateProcessor /// protected abstract void OnExecute(StateContext context, Node owner); - public void Exit(StateContext context) + public virtual void Exit(StateContext context) { - } public abstract State State { get; }