Make creatures try to chase enemies.
使生物尝试追逐敌人。
This commit is contained in:
parent
04858534ba
commit
4622d06e5c
|
@ -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.,敵がいない追跡です。
|
|
|
@ -26,6 +26,7 @@ animations = [{
|
|||
collision_layer = 4
|
||||
collision_mask = 34
|
||||
script = ExtResource("1_1dlls")
|
||||
LootListId = null
|
||||
metadata/CampId = "Default"
|
||||
metadata/MaxHp = 12
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,5 +22,7 @@ public class PatrolStateMachine : StateMachineTemplate
|
|||
]
|
||||
};
|
||||
RegisterProcessor(patrolStateProcessor);
|
||||
var chaseStateProcessor = new ChaseStateProcessor();
|
||||
RegisterProcessor(chaseStateProcessor);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
39
scripts/stateMachine/StateProcessor/ChaseStateProcessor.cs
Normal file
39
scripts/stateMachine/StateProcessor/ChaseStateProcessor.cs
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in New Issue
Block a user