Make creatures try to chase enemies.

使生物尝试追逐敌人。
This commit is contained in:
Cold-Mint 2024-07-06 22:55:07 +08:00
parent 04858534ba
commit 4622d06e5c
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
12 changed files with 216 additions and 30 deletions

View File

@ -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.",アイテムスロットが選択されており、追加は許可されていません。
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.,敵がいない追跡です。
1 id zh en ja
59 log_item_slot_is_selected_and_not_allowed 已选择物品槽,不允许添加。 Item slot is selected, not allowed to add. アイテムスロットが選択されており、追加は許可されていません。
60 log_patrol_enemy_detected 检测到敌人。 Enemy detected. 敵を検出しました。
61 log_attacker_or_target_is_null 攻击者或目标为空。 Attacker or target is null. 攻撃者またはターゲットが空です。
62 log_in_the_same_camp 在同一阵营。 In the same camp. 同じ陣営です。
63 log_friendly_target 友好目标。 Friendly target. 友好的なターゲットです。
64 log_can_cause_harm 可以造成伤害。 Can cause harm. ダメージを与えることができます。
65 log_camp_is_null 阵营为空。 Camp is null. 陣営が空です。
66 log_set_default_camp 设置默认阵营{0}。 Set default camp {0}. デフォルトの陣営{0}を設定します。
67 log_state_change 状态变更从{0}到{1}。 State change from {0} to {1}. 状態が{0}から{1}に変更されます。
68 log_state_processor_not_found 找不到状态处理器{0}。 State processor {0} not found. ステートプロセッサ{0}が見つかりません。
69 log_chase_no_enemy 追逐没有敌人。 Chase no enemy. 敵がいない追跡です。
70
71
72

View File

@ -26,6 +26,7 @@ animations = [{
collision_layer = 4
collision_mask = 34
script = ExtResource("1_1dlls")
LootListId = null
metadata/CampId = "Default"
metadata/MaxHp = 12

View File

@ -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;
}

View File

@ -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;
/// <summary>
/// <para>Nodes in the attack range</para>
/// <para>在攻击范围内的节点</para>
/// <para>All enemies within striking distance</para>
/// <para>在攻击范围内的所有敌人</para>
/// </summary>
private List<Node>? _nodesInTheAttackRange;
/// <summary>
/// <para>All nodes in the attack range</para>
/// <para>在攻击范围内的全部节点</para>
/// </summary>
public Node[] NodesInTheAttackRange => _nodesInTheAttackRange?.ToArray() ?? Array.Empty<Node>();
private List<CharacterTemplate>? _enemyInTheAttackRange;
/// <summary>
@ -59,7 +52,7 @@ public sealed partial class AiCharacter : CharacterTemplate
public override void _Ready()
{
base._Ready();
_nodesInTheAttackRange = new List<Node>();
_enemyInTheAttackRange = new List<CharacterTemplate>();
_wallDetection = GetNode<RayCast2D>("WallDetection");
_attackArea = GetNode<Area2D>("AttackArea2D");
NavigationAgent2D = GetNode<NavigationAgent2D>("NavigationAgent2D");
@ -93,6 +86,38 @@ public sealed partial class AiCharacter : CharacterTemplate
}
}
/// <summary>
/// <para>EnemyDetected</para>
/// <para>是否发现敌人</para>
/// </summary>
/// <returns>
///<para>Have you spotted the enemy?</para>
///<para>是否发现敌人</para>
/// </returns>
public bool EnemyDetected()
{
if (_enemyInTheAttackRange == null)
{
return false;
}
return _enemyInTheAttackRange.Count > 0;
}
/// <summary>
/// <para>Get the first enemy to enter range</para>
/// <para>获取第一个进入范围的敌人</para>
/// </summary>
/// <returns></returns>
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);
}
}

View File

@ -21,8 +21,32 @@ public static class LogCat
/// <para>巡逻状态处理器</para>
/// </summary>
public const string PatrolStateProcessor = "PatrolStateProcessor";
/// <summary>
/// <para>CampManager</para>
/// <para>阵营管理器</para>
/// </summary>
public const string CampManager = "CampManager";
/// <summary>
/// <para>State context</para>
/// <para>状态上下文</para>
/// </summary>
public const string StateContext = "StateContext";
/// <summary>
/// <para>StateMachineTemplate</para>
/// <para>状态机模板</para>
/// </summary>
public const string StateMachineTemplate = "StateMachineTemplate";
/// <summary>
/// <para>Pursuit enemy processor</para>
/// <para>追击敌人处理器</para>
/// </summary>
public const string ChaseStateProcessor = "ChaseStateProcessor";
}
/// <summary>
/// <para>Information log level</para>
@ -71,7 +95,7 @@ public static class LogCat
/// <para>禁用的日志标签</para>
/// </summary>
private static HashSet<string> DisabledLogLabels { get; } = [];
/// <summary>
/// <para>Disable log Label</para>

View File

@ -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());

View File

@ -12,6 +12,12 @@ public class StateContext
{
private State _currentState;
/// <summary>
/// <para>Previous state</para>
/// <para>前一个状态</para>
/// </summary>
private State _previousState;
/// <summary>
/// <para>The state context holds the current state</para>
/// <para>状态上下文持有当前状态</para>
@ -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;
}
}
/// <summary>
/// <para>Previous state</para>
/// <para>前一个状态</para>
/// </summary>
public State PreviousState
{
get => _previousState;
set => _previousState = value;
}
/// <summary>
/// <para>When the state changes</para>
/// <para>当状态改变时</para>

View File

@ -22,5 +22,7 @@ public class PatrolStateMachine : StateMachineTemplate
]
};
RegisterProcessor(patrolStateProcessor);
var chaseStateProcessor = new ChaseStateProcessor();
RegisterProcessor(chaseStateProcessor);
}
}

View File

@ -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);
}
}
/// <summary>
@ -78,7 +83,7 @@ public abstract class StateMachineTemplate : IStateMachine
_processors ??= new Dictionary<State, IStateProcessor>();
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);
}
}

View File

@ -0,0 +1,39 @@
using ColdMint.scripts.character;
using ColdMint.scripts.debug;
using Godot;
namespace ColdMint.scripts.stateMachine.StateProcessor;
/// <summary>
/// <para>Chasing state processor</para>
/// <para>追击状态处理器</para>
/// </summary>
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;
}

View File

@ -11,10 +11,29 @@ namespace ColdMint.scripts.stateMachine.StateProcessor;
public class PatrolStateProcessor : StateProcessorTemplate
{
public Vector2[]? Points { get; set; }
/// <summary>
/// <para>Whether to guard the origin</para>
/// <para>是否需要守护原点</para>
/// </summary>
/// <remarks>
///<para>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.</para>
///<para>默认清空下PatrolStateProcessor会将角色与地面接触的第一个位置当作原点。这个属性用来处理当角色被其他角色所吸引例如追击敌人转换回巡逻模式是否“守护”原点。如果设置为true则角色会尝试返回原点否则将分配新的原点。</para>
/// </remarks>
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);

View File

@ -8,9 +8,8 @@ namespace ColdMint.scripts.stateMachine;
/// </summary>
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
/// <param name="owner"></param>
protected abstract void OnExecute(StateContext context, Node owner);
public void Exit(StateContext context)
public virtual void Exit(StateContext context)
{
}
public abstract State State { get; }