怪物可以捡起武器打玩家了。

This commit is contained in:
Cold-Mint 2024-07-18 19:46:23 +08:00
parent 7812d9c570
commit e4583aed3c
9 changed files with 1114 additions and 1103 deletions

View File

@ -83,4 +83,8 @@ 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ではありません。
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.,拾い取り範囲に入ります。
1 id zh en ja
83 log_nearest_node_is_null 最近的节点为空。 The nearest node is null. 最も近いノードが空です。
84 log_node_is_not_WeaponTemplate 节点不是WeaponTemplate。 The node is not a WeaponTemplate. ノードはWeaponTemplateではありません。
85 log_weapon_not_in_pickup_range 武器不在拾取范围内。 The weapon is not within the pickup range. 武器は拾い取り範囲内にありません。
86 log_weapon_picked_up 武器被拾取。 Weapon picked up. 武器が拾い取られました。
87 log_weapon_pickup_failed 武器拾取失败。 Weapon pickup failed. 武器の拾い取りに失敗しました。
88 log_enter_the_picking_range_body 进入拾取范围。 Enter the picking range. 拾い取り範囲に入ります。
89
90

View File

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

View File

@ -11,7 +11,7 @@ radius = 20.0
height = 52.0
[sub_resource type="CircleShape2D" id="CircleShape2D_vmqbt"]
radius = 34.5398
radius = 65.3758
[sub_resource type="SpriteFrames" id="SpriteFrames_qumby"]
animations = [{

View File

@ -8,19 +8,6 @@ namespace ColdMint.scripts;
public static class Config
{
/// <summary>
/// <para>ID of the behavior tree</para>
/// <para>行为树的ID</para>
/// </summary>
public static class BehaviorTreeId
{
/// <summary>
/// <para>巡逻</para>
/// <para>Patrol</para>
/// </summary>
public const string Patrol = "Patrol";
}
/// <summary>
/// <para>Loot table ID</para>
/// <para>战利品表ID</para>
@ -34,31 +21,6 @@ public static class Config
public const string Test = "test";
}
/// <summary>
/// <para>BehaviorTreeResult</para>
/// <para>行为树的结果</para>
/// </summary>
public static class BehaviorTreeResult
{
/// <summary>
/// <para>Running</para>
/// <para>运行中</para>
/// </summary>
public const int Running = 0;
/// <summary>
/// <para>Success</para>
/// <para>成功</para>
/// </summary>
public const int Success = 1;
/// <summary>
/// <para>Failure</para>
/// <para>失败</para>
/// </summary>
public const int Failure = 2;
}
/// <summary>
/// <para>Camp ID</para>

View File

@ -16,472 +16,475 @@ namespace ColdMint.scripts.character;
/// </summary>
public sealed partial class AiCharacter : CharacterTemplate
{
//Used to detect rays on walls
//用于检测墙壁的射线
private RayCast2D? _wallDetection;
public RayCast2D? WallDetection => _wallDetection;
private Vector2 _wallDetectionOrigin;
private Area2D? _attackArea;
/// <summary>
/// <para>Reconnaissance area</para>
/// <para>侦察区域</para>
/// </summary>
/// <remarks>
///<para>Most of the time, when the enemy enters the reconnaissance area, the character will issue a "question mark" and try to move slowly towards the event point.</para>
///<para>大多数情况下,当敌人进入侦察区域后,角色会发出“疑问(问号)”,并尝试向事件点缓慢移动。</para>
/// </remarks>
private Area2D? _scoutArea;
/// <summary>
/// <para>All enemies within striking distance</para>
/// <para>在攻击范围内的所有敌人</para>
/// </summary>
private List<CharacterTemplate>? _enemyInTheAttackRange;
/// <summary>
/// <para>Scout all enemies within range</para>
/// <para>在侦察范围内所有的敌人</para>
/// </summary>
private List<CharacterTemplate>? _enemyInTheScoutRange;
/// <summary>
/// <para>Every weapon in the recon area</para>
/// <para>在侦察范围内所有的武器</para>
/// </summary>
private List<WeaponTemplate>? _weaponInTheScoutRange;
/// <summary>
/// <para>Obstacle detection ray during attack</para>
/// <para>攻击时的障碍物检测射线</para>
/// </summary>
/// <remarks>
///<para></para>
///<para>检测与目标点直接是否间隔墙壁</para>
/// </remarks>
private RayCast2D? _attackObstacleDetection;
private VisibleOnScreenEnabler2D? _screenEnabler2D;
/// <summary>
/// <para>Navigation agent</para>
/// <para>导航代理</para>
/// </summary>
public NavigationAgent2D? NavigationAgent2D { get; set; }
public IStateMachine? StateMachine { get; set; }
public RayCast2D? AttackObstacleDetection => _attackObstacleDetection;
/// <summary>
/// <para>Exclamation bubble Id</para>
/// <para>感叹气泡Id</para>
/// </summary>
private const int plaintBubbleId = 0;
/// <summary>
/// <para>Query bubble Id</para>
/// <para>疑问气泡Id</para>
/// </summary>
private const int queryBubbleId = 1;
/// <summary>
/// <para>BubbleMarker</para>
/// <para>气泡标记</para>
/// </summary>
/// <remarks>
///<para>Subsequent production of dialogue bubbles can be put into the parent class for players to use.</para>
///<para>后续制作对话泡时可进其放到父类,供玩家使用。</para>
/// </remarks>
private BubbleMarker? _bubbleMarker;
/// <summary>
/// <para>The initial weapons scene</para>
/// <para>初始的武器场景</para>
/// </summary>
[Export] public string? InitWeaponRes;
public override void _Ready()
{
base._Ready();
_enemyInTheAttackRange = new List<CharacterTemplate>();
_enemyInTheScoutRange = new List<CharacterTemplate>();
_weaponInTheScoutRange = new List<WeaponTemplate>();
_screenEnabler2D = GetNode<VisibleOnScreenEnabler2D>("VisibleOnScreenEnabler2D");
_screenEnabler2D.ScreenEntered += () =>
{
//When the character enters the screen.
//当角色进入屏幕。
ProcessMode = ProcessModeEnum.Disabled;
};
_screenEnabler2D.ScreenExited += () =>
{
//When the character leaves the screen.
//当角色离开屏幕。
ProcessMode = ProcessModeEnum.Inherit;
};
_bubbleMarker = GetNode<BubbleMarker>("BubbleMarker");
if (_bubbleMarker != null)
{
using var plaintScene = GD.Load<PackedScene>("res://prefab/ui/plaint.tscn");
var plaint = NodeUtils.InstantiatePackedScene<Control>(plaintScene);
if (plaint != null)
{
_bubbleMarker.AddBubble(plaintBubbleId, plaint);
}
using var queryScene = GD.Load<PackedScene>("res://prefab/ui/query.tscn");
var query = NodeUtils.InstantiatePackedScene<Control>(queryScene);
if (query != null)
{
_bubbleMarker.AddBubble(queryBubbleId, query);
}
}
_wallDetection = GetNode<RayCast2D>("WallDetection");
_attackArea = GetNode<Area2D>("AttackArea2D");
_scoutArea = GetNode<Area2D>("ScoutArea2D");
NavigationAgent2D = GetNode<NavigationAgent2D>("NavigationAgent2D");
if (ItemMarker2D != null)
{
_attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection");
}
if (_attackArea != null)
{
//If true, the zone will detect objects or areas entering and leaving the zone.
//如果为true该区域将检测进出该区域的物体或区域。
_attackArea.Monitoring = true;
//Other areas can't detect our attack zone
//其他区域不能检测到我们的攻击区域
_attackArea.Monitorable = false;
_attackArea.BodyEntered += EnterTheAttackArea;
_attackArea.BodyExited += ExitTheAttackArea;
}
if (_scoutArea != null)
{
_scoutArea.Monitoring = true;
_scoutArea.Monitorable = false;
_scoutArea.BodyEntered += EnterTheScoutArea;
_scoutArea.BodyExited += ExitTheScoutArea;
}
_wallDetectionOrigin = _wallDetection.TargetPosition;
StateMachine = new PatrolStateMachine();
StateMachine.Context = new StateContext
{
CurrentState = State.Patrol,
Owner = this
};
if (StateMachine != null)
{
StateMachine.Start();
}
//You must create an item container for the character before you can pick up the item.
//必须为角色创建物品容器后才能拾起物品。
var universalItemContainer = new UniversalItemContainer();
var itemSlotNode = universalItemContainer.AddItemSlot(this);
itemSlotNode?.Hide();
ProtectedItemContainer = universalItemContainer;
//Add initial weapon
//添加初始武器
AddInitialWeapon(InitWeaponRes);
}
/// <summary>
/// <para>Adds an initial weapon to the character</para>
/// <para>为角色添加初始的武器</para>
/// </summary>
private void AddInitialWeapon(string? initWeaponRes)
{
if (initWeaponRes == null)
{
return;
}
//Set the resource path of the initial weapon and try to create the object of the initial weapon.
//设置了初始武器的资源路径,尝试创建初始武器的对象。
var packedScene = GD.Load<PackedScene>(initWeaponRes);
var weaponTemplate = NodeUtils.InstantiatePackedScene<WeaponTemplate>(packedScene);
if (weaponTemplate == null)
{
return;
}
NodeUtils.CallDeferredAddChild(this, weaponTemplate);
PickItem(weaponTemplate);
}
/// <summary>
/// <para>Display exclamation marks</para>
/// <para>显示感叹号</para>
/// </summary>
public void DispladyPlaint()
{
_bubbleMarker?.ShowBubble(plaintBubbleId);
}
public void HidePlaint()
{
_bubbleMarker?.HideBubble(plaintBubbleId);
}
/// <summary>
/// <para>Displady Query</para>
/// <para>显示疑问</para>
/// </summary>
public void DispladyQuery()
{
_bubbleMarker?.ShowBubble(queryBubbleId);
}
public void HideQuery()
{
_bubbleMarker?.HideBubble(queryBubbleId);
}
/// <summary>
/// <para>Whether the enemy has been detected in the reconnaissance area</para>
/// <para>侦察范围是否发现敌人</para>
/// </summary>
/// <returns>
///<para>Have you spotted the enemy?</para>
///<para>是否发现敌人</para>
/// </returns>
public bool ScoutEnemyDetected()
{
if (_enemyInTheScoutRange == null)
{
return false;
}
return _enemyInTheScoutRange.Count > 0;
}
/// <summary>
/// <para>Any weapons found in the recon area</para>
/// <para>侦察范围内是否发现武器</para>
/// </summary>
/// <returns></returns>
public bool ScoutWeaponDetected()
{
if (_weaponInTheScoutRange == null)
{
return false;
}
return _weaponInTheScoutRange.Count > 0;
}
/// <summary>
/// <para>Get weapons in the recon area</para>
/// <para>获取侦察范围内的武器</para>
/// </summary>
/// <returns></returns>
public WeaponTemplate[]? GetWeaponInScoutArea()
{
if (_weaponInTheScoutRange == null)
{
return null;
}
return _weaponInTheScoutRange.ToArray();
}
/// <summary>
/// <para>Get the first enemy in range</para>
/// <para>获取第一个进入侦察范围的敌人</para>
/// </summary>
/// <returns></returns>
public CharacterTemplate? GetFirstEnemyInScoutArea()
{
if (_enemyInTheScoutRange == null || _enemyInTheScoutRange.Count == 0)
{
return null;
}
return _enemyInTheScoutRange[0];
}
/// <summary>
/// <para>Get the first enemy within striking range</para>
/// <para>获取第一个进入攻击范围的敌人</para>
/// </summary>
/// <returns></returns>
public CharacterTemplate? GetFirstEnemyInAttackArea()
{
if (_enemyInTheAttackRange == null || _enemyInTheAttackRange.Count == 0)
{
return null;
}
return _enemyInTheAttackRange[0];
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
StateMachine?.Execute();
if (NavigationAgent2D != null && IsOnFloor())
{
var nextPathPosition = NavigationAgent2D.GetNextPathPosition();
var direction = (nextPathPosition - GlobalPosition).Normalized();
velocity = direction * Config.CellSize * Speed * ProtectedSpeedScale;
}
}
/// <summary>
/// <para>When the node enters the reconnaissance area</para>
/// <para>当节点进入侦察区域后</para>
/// </summary>
/// <param name="node"></param>
private void EnterTheScoutArea(Node node)
{
if (node is WeaponTemplate weaponTemplate)
{
_weaponInTheScoutRange?.Add(weaponTemplate);
}
CanCauseHarmNode(node, (canCause, characterTemplate) =>
{
if (canCause && characterTemplate != null)
{
_enemyInTheScoutRange?.Add(characterTemplate);
}
});
}
/// <summary>
/// <para>When the node exits the reconnaissance area</para>
/// <para>当节点退出侦察区域后</para>
/// </summary>
/// <param name="node"></param>
private void ExitTheScoutArea(Node node)
{
if (node == this)
{
return;
}
if (node is WeaponTemplate weaponTemplate)
{
_weaponInTheScoutRange?.Remove(weaponTemplate);
}
if (node is CharacterTemplate characterTemplate)
{
_enemyInTheScoutRange?.Remove(characterTemplate);
}
}
/// <summary>
/// <para>When a node enters the attack zone</para>
/// <para>当节点进入攻击区域后</para>
/// </summary>
/// <param name="node"></param>
private void EnterTheAttackArea(Node node)
{
CanCauseHarmNode(node, (canCause, characterTemplate) =>
{
if (canCause && characterTemplate != null)
{
_enemyInTheAttackRange?.Add(characterTemplate);
}
});
}
/// <summary>
/// <para>CanCauseHarmNode</para>
/// <para>是否可伤害某个节点</para>
/// </summary>
/// <param name="node"></param>
/// <param name="action"></param>
private void CanCauseHarmNode(Node node, Action<bool, CharacterTemplate?> action)
{
if (node == this)
{
//The target can't be yourself.
//攻击目标不能是自己。
action.Invoke(false, null);
return;
}
if (node is not CharacterTemplate characterTemplate)
{
action.Invoke(false, null);
return;
}
//Determine if damage can be done between factions
//判断阵营间是否可造成伤害
var camp = CampManager.GetCamp(CampId);
var enemyCamp = CampManager.GetCamp(characterTemplate.CampId);
if (enemyCamp != null && camp != null)
{
action.Invoke(CampManager.CanCauseHarm(camp, enemyCamp), characterTemplate);
return;
}
action.Invoke(false, characterTemplate);
}
private void ExitTheAttackArea(Node node)
{
if (node == this)
{
return;
}
if (node is CharacterTemplate characterTemplate)
{
_enemyInTheAttackRange?.Remove(characterTemplate);
}
}
/// <summary>
/// <para>Set target location</para>
/// <para>设置目标位置</para>
/// </summary>
/// <param name="targetPosition"></param>
public void SetTargetPosition(Vector2 targetPosition)
{
if (NavigationAgent2D == null)
{
return;
}
NavigationAgent2D.TargetPosition = targetPosition;
}
public override void _ExitTree()
{
base._ExitTree();
if (_attackArea != null)
{
_attackArea.BodyEntered -= EnterTheAttackArea;
_attackArea.BodyExited -= ExitTheAttackArea;
}
if (_scoutArea != null)
{
_scoutArea.BodyEntered -= EnterTheScoutArea;
_scoutArea.BodyExited -= ExitTheScoutArea;
}
if (StateMachine != null)
{
StateMachine.Stop();
}
}
}
//Used to detect rays on walls
//用于检测墙壁的射线
private RayCast2D? _wallDetection;
public RayCast2D? WallDetection => _wallDetection;
private Vector2 _wallDetectionOrigin;
private Area2D? _attackArea;
/// <summary>
/// <para>Reconnaissance area</para>
/// <para>侦察区域</para>
/// </summary>
/// <remarks>
///<para>Most of the time, when the enemy enters the reconnaissance area, the character will issue a "question mark" and try to move slowly towards the event point.</para>
///<para>大多数情况下,当敌人进入侦察区域后,角色会发出“疑问(问号)”,并尝试向事件点缓慢移动。</para>
/// </remarks>
private Area2D? _scoutArea;
/// <summary>
/// <para>All enemies within striking distance</para>
/// <para>在攻击范围内的所有敌人</para>
/// </summary>
private List<CharacterTemplate>? _enemyInTheAttackRange;
/// <summary>
/// <para>Scout all enemies within range</para>
/// <para>在侦察范围内所有的敌人</para>
/// </summary>
private List<CharacterTemplate>? _enemyInTheScoutRange;
/// <summary>
/// <para>Every weapon in the recon area</para>
/// <para>在侦察范围内所有的武器</para>
/// </summary>
private List<WeaponTemplate>? _weaponInTheScoutRange;
/// <summary>
/// <para>Obstacle detection ray during attack</para>
/// <para>攻击时的障碍物检测射线</para>
/// </summary>
/// <remarks>
///<para></para>
///<para>检测与目标点直接是否间隔墙壁</para>
/// </remarks>
private RayCast2D? _attackObstacleDetection;
private VisibleOnScreenEnabler2D? _screenEnabler2D;
/// <summary>
/// <para>Navigation agent</para>
/// <para>导航代理</para>
/// </summary>
public NavigationAgent2D? NavigationAgent2D { get; set; }
public IStateMachine? StateMachine { get; set; }
public RayCast2D? AttackObstacleDetection => _attackObstacleDetection;
/// <summary>
/// <para>Exclamation bubble Id</para>
/// <para>感叹气泡Id</para>
/// </summary>
private const int plaintBubbleId = 0;
/// <summary>
/// <para>Query bubble Id</para>
/// <para>疑问气泡Id</para>
/// </summary>
private const int queryBubbleId = 1;
/// <summary>
/// <para>BubbleMarker</para>
/// <para>气泡标记</para>
/// </summary>
/// <remarks>
///<para>Subsequent production of dialogue bubbles can be put into the parent class for players to use.</para>
///<para>后续制作对话泡时可进其放到父类,供玩家使用。</para>
/// </remarks>
private BubbleMarker? _bubbleMarker;
/// <summary>
/// <para>The initial weapons scene</para>
/// <para>初始的武器场景</para>
/// </summary>
[Export] public string? InitWeaponRes;
public override void _Ready()
{
base._Ready();
_enemyInTheAttackRange = new List<CharacterTemplate>();
_enemyInTheScoutRange = new List<CharacterTemplate>();
_weaponInTheScoutRange = new List<WeaponTemplate>();
_screenEnabler2D = GetNode<VisibleOnScreenEnabler2D>("VisibleOnScreenEnabler2D");
_screenEnabler2D.ScreenEntered += () =>
{
//When the character enters the screen.
//当角色进入屏幕。
ProcessMode = ProcessModeEnum.Disabled;
};
_screenEnabler2D.ScreenExited += () =>
{
//When the character leaves the screen.
//当角色离开屏幕。
ProcessMode = ProcessModeEnum.Inherit;
};
_bubbleMarker = GetNode<BubbleMarker>("BubbleMarker");
if (_bubbleMarker != null)
{
using var plaintScene = GD.Load<PackedScene>("res://prefab/ui/plaint.tscn");
var plaint = NodeUtils.InstantiatePackedScene<Control>(plaintScene);
if (plaint != null)
{
_bubbleMarker.AddBubble(plaintBubbleId, plaint);
}
using var queryScene = GD.Load<PackedScene>("res://prefab/ui/query.tscn");
var query = NodeUtils.InstantiatePackedScene<Control>(queryScene);
if (query != null)
{
_bubbleMarker.AddBubble(queryBubbleId, query);
}
}
_wallDetection = GetNode<RayCast2D>("WallDetection");
_attackArea = GetNode<Area2D>("AttackArea2D");
_scoutArea = GetNode<Area2D>("ScoutArea2D");
NavigationAgent2D = GetNode<NavigationAgent2D>("NavigationAgent2D");
if (ItemMarker2D != null)
{
_attackObstacleDetection = ItemMarker2D.GetNode<RayCast2D>("AttackObstacleDetection");
}
if (_attackArea != null)
{
//If true, the zone will detect objects or areas entering and leaving the zone.
//如果为true该区域将检测进出该区域的物体或区域。
_attackArea.Monitoring = true;
//Other areas can't detect our attack zone
//其他区域不能检测到我们的攻击区域
_attackArea.Monitorable = false;
_attackArea.BodyEntered += EnterTheAttackArea;
_attackArea.BodyExited += ExitTheAttackArea;
}
if (_scoutArea != null)
{
_scoutArea.Monitoring = true;
_scoutArea.Monitorable = false;
_scoutArea.BodyEntered += EnterTheScoutArea;
_scoutArea.BodyExited += ExitTheScoutArea;
}
_wallDetectionOrigin = _wallDetection.TargetPosition;
StateMachine = new PatrolStateMachine();
StateMachine.Context = new StateContext
{
CurrentState = State.Patrol,
Owner = this
};
if (StateMachine != null)
{
StateMachine.Start();
}
//You must create an item container for the character before you can pick up the item.
//必须为角色创建物品容器后才能拾起物品。
var universalItemContainer = new UniversalItemContainer();
var itemSlotNode = universalItemContainer.AddItemSlot(this);
itemSlotNode?.Hide();
ProtectedItemContainer = universalItemContainer;
//Add initial weapon
//添加初始武器
AddInitialWeapon(InitWeaponRes);
}
/// <summary>
/// <para>Adds an initial weapon to the character</para>
/// <para>为角色添加初始的武器</para>
/// </summary>
private void AddInitialWeapon(string? initWeaponRes)
{
if (initWeaponRes == null)
{
return;
}
//Set the resource path of the initial weapon and try to create the object of the initial weapon.
//设置了初始武器的资源路径,尝试创建初始武器的对象。
var packedScene = GD.Load<PackedScene>(initWeaponRes);
var weaponTemplate = NodeUtils.InstantiatePackedScene<WeaponTemplate>(packedScene);
if (weaponTemplate == null)
{
return;
}
NodeUtils.CallDeferredAddChild(this, weaponTemplate);
PickItem(weaponTemplate);
}
/// <summary>
/// <para>Display exclamation marks</para>
/// <para>显示感叹号</para>
/// </summary>
public void DispladyPlaint()
{
_bubbleMarker?.ShowBubble(plaintBubbleId);
}
public void HidePlaint()
{
_bubbleMarker?.HideBubble(plaintBubbleId);
}
/// <summary>
/// <para>Displady Query</para>
/// <para>显示疑问</para>
/// </summary>
public void DispladyQuery()
{
_bubbleMarker?.ShowBubble(queryBubbleId);
}
public void HideQuery()
{
_bubbleMarker?.HideBubble(queryBubbleId);
}
/// <summary>
/// <para>Whether the enemy has been detected in the reconnaissance area</para>
/// <para>侦察范围是否发现敌人</para>
/// </summary>
/// <returns>
///<para>Have you spotted the enemy?</para>
///<para>是否发现敌人</para>
/// </returns>
public bool ScoutEnemyDetected()
{
if (_enemyInTheScoutRange == null)
{
return false;
}
return _enemyInTheScoutRange.Count > 0;
}
/// <summary>
/// <para>Any weapons found in the recon area</para>
/// <para>侦察范围内是否发现武器</para>
/// </summary>
/// <returns></returns>
public bool ScoutWeaponDetected()
{
if (_weaponInTheScoutRange == null)
{
return false;
}
return _weaponInTheScoutRange.Count > 0;
}
/// <summary>
/// <para>Get weapons in the recon area</para>
/// <para>获取侦察范围内的武器</para>
/// </summary>
/// <returns></returns>
public WeaponTemplate[]? GetWeaponInScoutArea()
{
if (_weaponInTheScoutRange == null)
{
return null;
}
return _weaponInTheScoutRange.ToArray();
}
/// <summary>
/// <para>Get the first enemy in range</para>
/// <para>获取第一个进入侦察范围的敌人</para>
/// </summary>
/// <returns></returns>
public CharacterTemplate? GetFirstEnemyInScoutArea()
{
if (_enemyInTheScoutRange == null || _enemyInTheScoutRange.Count == 0)
{
return null;
}
return _enemyInTheScoutRange[0];
}
/// <summary>
/// <para>Get the first enemy within striking range</para>
/// <para>获取第一个进入攻击范围的敌人</para>
/// </summary>
/// <returns></returns>
public CharacterTemplate? GetFirstEnemyInAttackArea()
{
if (_enemyInTheAttackRange == null || _enemyInTheAttackRange.Count == 0)
{
return null;
}
return _enemyInTheAttackRange[0];
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
StateMachine?.Execute();
if (NavigationAgent2D != null && IsOnFloor())
{
var nextPathPosition = NavigationAgent2D.GetNextPathPosition();
var direction = (nextPathPosition - GlobalPosition).Normalized();
velocity = direction * Config.CellSize * Speed * ProtectedSpeedScale;
}
}
/// <summary>
/// <para>When the node enters the reconnaissance area</para>
/// <para>当节点进入侦察区域后</para>
/// </summary>
/// <param name="node"></param>
private void EnterTheScoutArea(Node node)
{
if (node is WeaponTemplate weaponTemplate)
{
if (CanPickItem(weaponTemplate))
{
_weaponInTheScoutRange?.Add(weaponTemplate);
}
}
CanCauseHarmNode(node, (canCause, characterTemplate) =>
{
if (canCause && characterTemplate != null)
{
_enemyInTheScoutRange?.Add(characterTemplate);
}
});
}
/// <summary>
/// <para>When the node exits the reconnaissance area</para>
/// <para>当节点退出侦察区域后</para>
/// </summary>
/// <param name="node"></param>
private void ExitTheScoutArea(Node node)
{
if (node == this)
{
return;
}
if (node is WeaponTemplate weaponTemplate)
{
_weaponInTheScoutRange?.Remove(weaponTemplate);
}
if (node is CharacterTemplate characterTemplate)
{
_enemyInTheScoutRange?.Remove(characterTemplate);
}
}
/// <summary>
/// <para>When a node enters the attack zone</para>
/// <para>当节点进入攻击区域后</para>
/// </summary>
/// <param name="node"></param>
private void EnterTheAttackArea(Node node)
{
CanCauseHarmNode(node, (canCause, characterTemplate) =>
{
if (canCause && characterTemplate != null)
{
_enemyInTheAttackRange?.Add(characterTemplate);
}
});
}
/// <summary>
/// <para>CanCauseHarmNode</para>
/// <para>是否可伤害某个节点</para>
/// </summary>
/// <param name="node"></param>
/// <param name="action"></param>
private void CanCauseHarmNode(Node node, Action<bool, CharacterTemplate?> action)
{
if (node == this)
{
//The target can't be yourself.
//攻击目标不能是自己。
action.Invoke(false, null);
return;
}
if (node is not CharacterTemplate characterTemplate)
{
action.Invoke(false, null);
return;
}
//Determine if damage can be done between factions
//判断阵营间是否可造成伤害
var camp = CampManager.GetCamp(CampId);
var enemyCamp = CampManager.GetCamp(characterTemplate.CampId);
if (enemyCamp != null && camp != null)
{
action.Invoke(CampManager.CanCauseHarm(camp, enemyCamp), characterTemplate);
return;
}
action.Invoke(false, characterTemplate);
}
private void ExitTheAttackArea(Node node)
{
if (node == this)
{
return;
}
if (node is CharacterTemplate characterTemplate)
{
_enemyInTheAttackRange?.Remove(characterTemplate);
}
}
/// <summary>
/// <para>Set target location</para>
/// <para>设置目标位置</para>
/// </summary>
/// <param name="targetPosition"></param>
public void SetTargetPosition(Vector2 targetPosition)
{
if (NavigationAgent2D == null)
{
return;
}
NavigationAgent2D.TargetPosition = targetPosition;
}
public override void _ExitTree()
{
base._ExitTree();
if (_attackArea != null)
{
_attackArea.BodyEntered -= EnterTheAttackArea;
_attackArea.BodyExited -= ExitTheAttackArea;
}
if (_scoutArea != null)
{
_scoutArea.BodyEntered -= EnterTheScoutArea;
_scoutArea.BodyExited -= ExitTheScoutArea;
}
if (StateMachine != null)
{
StateMachine.Stop();
}
}
}

View File

@ -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
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private bool CanPickItem(Node node)
protected bool CanPickItem(Node node)
{
if (_currentItem != null && node == _currentItem)
{
@ -336,7 +334,7 @@ public partial class CharacterTemplate : CharacterBody2D
///<para>Whether successfully picked up</para>
///<para>是否成功拾起</para>
/// </returns>
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);
}

View File

@ -17,498 +17,498 @@ namespace ColdMint.scripts.character;
/// </summary>
public partial class Player : CharacterTemplate
{
private Control? _floatLabel;
//Empty object projectile
//空的物品抛射线
private readonly Vector2[] _emptyVector2Array = [Vector2.Zero];
//抛物线
private Line2D? _parabola;
//用于检测玩家是否站在平台上的射线
private RayCast2D? _platformDetectionRayCast2D;
private const float PromptTextDistance = 50;
//抛出物品的飞行速度
private float _throwingVelocity = Config.CellSize * 13;
//射线是否与平台碰撞
private bool _collidingWithPlatform;
//How long does it take for the character to recover from a collision with the platform after jumping off the platform (in seconds)
//角色从平台上跳下后,多少时间后恢复与平台的碰撞(单位:秒)
private double _platformCollisionRecoveryTime = 0.2f;
public override void _Ready()
{
base._Ready();
CharacterName = TranslationServerUtils.Translate("default_player_name");
LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,ReadOnlyCharacterName,
GlobalPosition);
var floatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn");
//Initializes the float label.
//初始化悬浮标签。
_floatLabel = NodeUtils.InstantiatePackedScene<Control>(floatLabelPackedScene);
if (_floatLabel == null)
{
throw new NullReferenceException(TranslationServer.Translate("float_label_instantiate_failed"));
}
_floatLabel.Hide();
NodeUtils.CallDeferredAddChild(this, _floatLabel);
_parabola = GetNode<Line2D>("Parabola");
_platformDetectionRayCast2D = GetNode<RayCast2D>("PlatformDetectionRayCast");
UpdateOperationTip();
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
if (healthBarUi != null)
{
healthBarUi.MaxHp = MaxHp;
healthBarUi.CurrentHp = CurrentHp;
}
}
protected override void WhenBindItemContainer(IItemContainer? itemContainer)
{
if (itemContainer == null)
{
return;
}
//Subscribe to events when the item container is bound to the player.
//在物品容器与玩家绑定时订阅事件。
itemContainer.SelectedItemSlotChangeEvent += SelectedItemSlotChangeEvent;
}
public override void _ExitTree()
{
base._ExitTree();
if (ProtectedItemContainer != null)
{
//Unsubscribe to events when this object is destroyed.
//此节点被销毁时,取消订阅事件。
ProtectedItemContainer.SelectedItemSlotChangeEvent -= SelectedItemSlotChangeEvent;
}
}
private void SelectedItemSlotChangeEvent(SelectedItemSlotChangeEvent selectedItemSlotChangeEvent)
{
var item = selectedItemSlotChangeEvent.NewItemSlotNode?.GetItem();
GameSceneNodeHolder.HideBackpackUiContainerIfVisible();
if (item is Node2D node2D)
{
CurrentItem = node2D;
}
else
{
CurrentItem = null;
}
}
/// <summary>
/// <para>Update operation prompt</para>
/// <para>更新操作提示</para>
/// </summary>
private void UpdateOperationTip()
{
var operationTipLabel = GameSceneNodeHolder.OperationTipLabel;
if (operationTipLabel == null)
{
return;
}
var operationTipBuilder = new StringBuilder();
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_left")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_move_left"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_right")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_move_right"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_up")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_jump"));
if (_collidingWithPlatform)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_jump_down"));
}
var nearestItem = FindTheNearestItem();
if (nearestItem != null)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_pick_up"));
if (nearestItem is IItem item)
{
operationTipBuilder.Append(item.Name);
}
operationTipLabel.Text = operationTipBuilder.ToString();
}
if (CurrentItem != null)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("throw")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_throw"));
if (CurrentItem is IItem item)
{
operationTipBuilder.Append(TranslationServerUtils.Translate(item.Name));
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("use_item")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_use_item"));
operationTipBuilder.Append(TranslationServerUtils.Translate(item.Name));
}
}
operationTipLabel.Text = operationTipBuilder.ToString();
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
//When the collision state between the platform detection ray and the platform changes
//在平台检测射线与平台碰撞状态改变时
if (_platformDetectionRayCast2D != null && _platformDetectionRayCast2D.IsColliding() != _collidingWithPlatform)
{
//When the state changes, update the action hint
//当状态改变时,更新操作提示
_collidingWithPlatform = _platformDetectionRayCast2D.IsColliding();
UpdateOperationTip();
}
//If the character is on the ground, give an upward velocity when the jump button is pressed
//如果角色正在地面上,按下跳跃键时,给予一个向上的速度
if (Input.IsActionJustPressed("ui_up") && IsOnFloor())
velocity.Y = JumpVelocity;
//Moving left and right
//左右移动
var axis = Input.GetAxis("ui_left", "ui_right");
velocity.X = axis * Speed * Config.CellSize * ProtectedSpeedScale;
//Use items
//使用物品
if (Input.IsActionPressed("use_item"))
{
UseItem(GetGlobalMousePosition());
}
//Pick up an item
//捡起物品
if (Input.IsActionJustPressed("pick_up"))
{
var pickAbleItem = FindTheNearestItem();
var success = PickItem(pickAbleItem);
if (success)
{
if (pickAbleItem != null)
{
PickingRangeBodiesList?.Remove(pickAbleItem);
}
RecycleFloatLabel();
}
}
if (Input.IsActionJustPressed("ui_down"))
{
if (_collidingWithPlatform)
{
//When the character stands on the platform and presses the ui_down key, we cancel the collision between the character and the platform
//当角色站在平台上按下 ui_down 键时,我们取消角色与平台的碰撞
var timer = new Timer();
AddChild(timer);
timer.WaitTime = _platformCollisionRecoveryTime;
timer.OneShot = true;
timer.Start();
timer.Timeout += () =>
{
SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
SetCollisionMaskValue(Config.LayerNumber.Platform, false);
}
}
//Display a parabola when an item is thrown
//抛出物品时,显示抛物线
if (Input.IsActionPressed("throw"))
{
if (_parabola == null)
{
return;
}
if (ItemMarker2D == null)
{
//Cannot get the marked location of the item, then do not draw a line
//无法获取物品的标记位置,那么不绘制线
return;
}
_parabola.Points = CurrentItem == null
? _emptyVector2Array
: ParabolicUtils.ComputeParabolic(ItemMarker2D.Position, GetThrowVelocity(), Gravity, 0.1f);
}
//When you raise your hand, throw the object
//抬起手时,抛出物品
if (Input.IsActionJustReleased("throw"))
{
if (ItemContainer == null)
{
return;
}
if (_parabola != null)
{
_parabola.Points = [Vector2.Zero];
}
ThrowItem(ItemContainer.GetSelectIndex(), 1, GetThrowVelocity());
GameSceneNodeHolder.HideBackpackUiContainerIfVisible();
CurrentItem = null;
}
}
protected override void WhenUpdateCurrentItem(Node2D? currentItem)
{
UpdateOperationTip();
}
/// <summary>
/// <para>当玩家手动抛出物品时,施加到物品上的速度值</para>
/// </summary>
/// <returns></returns>
private Vector2 GetThrowVelocity()
{
//We take the mouse position, normalize it, and then multiply it by the distance the player can throw
//我们拿到鼠标的位置,将其归一化处理,然后乘以玩家可扔出的距离
return GetLocalMousePosition().Normalized() * _throwingVelocity;
}
public override void _Process(double delta)
{
if (!Visible)
{
return;
}
AimTheCurrentItemAtAPoint(GetGlobalMousePosition());
var itemMarker2DPosition = Vector2.Zero;
if (ItemMarker2D != null)
{
itemMarker2DPosition = ItemMarker2D.Position;
}
var axis = Input.GetAxis("ui_left", "ui_right");
switch (axis)
{
case -1:
//Minus 1, we move to the left
//-1向左移动
FacingLeft = true;
if (ItemMarker2D != null)
{
itemMarker2DPosition.X = -ReadOnlyItemMarkerOriginalX;
}
Flip();
break;
case 1:
//1, move to the right
//1向右移动
FacingLeft = false;
if (ItemMarker2D != null)
{
itemMarker2DPosition.X = ReadOnlyItemMarkerOriginalX;
}
Flip();
break;
}
if (ItemMarker2D != null)
{
ItemMarker2D.Position = itemMarker2DPosition;
}
}
protected override void Flip()
{
base.Flip();
//If there is a weapon, flip it too
//如果有武器的话,也要翻转
if (CurrentItem is PickAbleTemplate pickAbleTemplate)
{
pickAbleTemplate.Flip(FacingLeft);
}
}
public override void Revive(int newHp)
{
base.Revive(newHp);
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
if (healthBarUi != null)
{
//The purpose of setting Hp to the current Hp is to cause the life bar to refresh.
//将Hp设置为当前Hp的目的是使生命条刷新。
healthBarUi.CurrentHp = CurrentHp;
}
}
/// <summary>
/// <para>When the player dies</para>
/// <para>当玩家死亡的时候</para>
/// </summary>
/// <param name="damageTemplate"></param>
protected override async Task OnDie(DamageTemplate damageTemplate)
{
Hide();
ProcessMode = ProcessModeEnum.Disabled;
if (EventManager.GameOverEvent == null)
{
return;
}
var gameOverEvent = new GameOverEvent();
if (damageTemplate.Attacker != null)
{
gameOverEvent.DeathInfo = await DeathInfoGenerator.GenerateDeathInfo(this, damageTemplate.Attacker);
}
EventManager.GameOverEvent.Invoke(gameOverEvent);
}
protected override void EnterThePickingRangeBody(Node node)
{
base.EnterThePickingRangeBody(node);
if (CurrentItem == node)
{
//If the node entering the pick range is the node held by the player, then return.
//如果说进入拾捡范围的节点是玩家所持有的节点,那么返回。
return;
}
if (node is not Node2D node2D)
{
return;
}
if (_floatLabel != null)
{
if (node is not PickAbleTemplate pickAbleTemplate)
{
return;
}
if (pickAbleTemplate.Picked)
{
//If the pickables are picked up, the label is not displayed.
//如果可拾捡物被捡起了,那么不显示标签。
LogCat.LogWarning("pickable_picked_up");
return;
}
NodeUtils.CallDeferredReparent(node, _floatLabel);
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.GlobalPosition = node2D.GlobalPosition;
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
var stringBuilder = new StringBuilder();
if (pickAbleTemplate.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
stringBuilder.Append(TranslationServerUtils.Translate("de"));
}
stringBuilder.Append(TranslationServerUtils.Translate(pickAbleTemplate.Name));
label.Text = stringBuilder.ToString();
_floatLabel.Show();
}
UpdateOperationTip();
}
protected override void ExitThePickingRangeBody(Node node)
{
base.ExitThePickingRangeBody(node);
if (node is not Node2D)
{
return;
}
RecycleFloatLabel();
UpdateOperationTip();
}
/// <summary>
/// <para>Recycle Float Label</para>
/// <para>回收悬浮标签</para>
/// </summary>
private void RecycleFloatLabel()
{
if (_floatLabel == null)
{
return;
}
_floatLabel.Hide();
NodeUtils.CallDeferredReparent(this, _floatLabel);
}
protected override void OnHit(DamageTemplate damageTemplate)
{
base.OnHit(damageTemplate);
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
if (healthBarUi != null)
{
healthBarUi.CurrentHp = CurrentHp;
}
}
}
private Control? _floatLabel;
//Empty object projectile
//空的物品抛射线
private readonly Vector2[] _emptyVector2Array = [Vector2.Zero];
//抛物线
private Line2D? _parabola;
//用于检测玩家是否站在平台上的射线
private RayCast2D? _platformDetectionRayCast2D;
private const float PromptTextDistance = 50;
//抛出物品的飞行速度
private float _throwingVelocity = Config.CellSize * 13;
//射线是否与平台碰撞
private bool _collidingWithPlatform;
//How long does it take for the character to recover from a collision with the platform after jumping off the platform (in seconds)
//角色从平台上跳下后,多少时间后恢复与平台的碰撞(单位:秒)
private double _platformCollisionRecoveryTime = 0.2f;
public override void _Ready()
{
base._Ready();
CharacterName = TranslationServerUtils.Translate("default_player_name");
LogCat.LogWithFormat("player_spawn_debug", LogCat.LogLabel.Default, LogCat.UploadFormat,ReadOnlyCharacterName,
GlobalPosition);
var floatLabelPackedScene = GD.Load<PackedScene>("res://prefab/ui/FloatLabel.tscn");
//Initializes the float label.
//初始化悬浮标签。
_floatLabel = NodeUtils.InstantiatePackedScene<Control>(floatLabelPackedScene);
if (_floatLabel == null)
{
throw new NullReferenceException(TranslationServer.Translate("float_label_instantiate_failed"));
}
_floatLabel.Hide();
NodeUtils.CallDeferredAddChild(this, _floatLabel);
_parabola = GetNode<Line2D>("Parabola");
_platformDetectionRayCast2D = GetNode<RayCast2D>("PlatformDetectionRayCast");
UpdateOperationTip();
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
if (healthBarUi != null)
{
healthBarUi.MaxHp = MaxHp;
healthBarUi.CurrentHp = CurrentHp;
}
}
protected override void WhenBindItemContainer(IItemContainer? itemContainer)
{
if (itemContainer == null)
{
return;
}
//Subscribe to events when the item container is bound to the player.
//在物品容器与玩家绑定时订阅事件。
itemContainer.SelectedItemSlotChangeEvent += SelectedItemSlotChangeEvent;
}
public override void _ExitTree()
{
base._ExitTree();
if (ProtectedItemContainer != null)
{
//Unsubscribe to events when this object is destroyed.
//此节点被销毁时,取消订阅事件。
ProtectedItemContainer.SelectedItemSlotChangeEvent -= SelectedItemSlotChangeEvent;
}
}
private void SelectedItemSlotChangeEvent(SelectedItemSlotChangeEvent selectedItemSlotChangeEvent)
{
var item = selectedItemSlotChangeEvent.NewItemSlotNode?.GetItem();
GameSceneNodeHolder.HideBackpackUiContainerIfVisible();
if (item is Node2D node2D)
{
CurrentItem = node2D;
}
else
{
CurrentItem = null;
}
}
/// <summary>
/// <para>Update operation prompt</para>
/// <para>更新操作提示</para>
/// </summary>
private void UpdateOperationTip()
{
var operationTipLabel = GameSceneNodeHolder.OperationTipLabel;
if (operationTipLabel == null)
{
return;
}
var operationTipBuilder = new StringBuilder();
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_left")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_move_left"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_right")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_move_right"));
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_up")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_jump"));
if (_collidingWithPlatform)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_jump_down"));
}
var nearestItem = FindTheNearestItem();
if (nearestItem != null)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_pick_up"));
if (nearestItem is IItem item)
{
operationTipBuilder.Append(item.Name);
}
operationTipLabel.Text = operationTipBuilder.ToString();
}
if (CurrentItem != null)
{
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(TranslationServerUtils.Translate(InputMap.ActionGetEvents("throw")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_throw"));
if (CurrentItem is IItem item)
{
operationTipBuilder.Append(TranslationServerUtils.Translate(item.Name));
operationTipBuilder.Append(' ');
operationTipBuilder.Append("[color=");
operationTipBuilder.Append(Config.OperationTipActionColor);
operationTipBuilder.Append(']');
operationTipBuilder.Append(
TranslationServerUtils.Translate(InputMap.ActionGetEvents("use_item")[0].AsText()));
operationTipBuilder.Append("[/color]");
operationTipBuilder.Append(TranslationServerUtils.Translate("action_use_item"));
operationTipBuilder.Append(TranslationServerUtils.Translate(item.Name));
}
}
operationTipLabel.Text = operationTipBuilder.ToString();
}
protected override void HookPhysicsProcess(ref Vector2 velocity, double delta)
{
//When the collision state between the platform detection ray and the platform changes
//在平台检测射线与平台碰撞状态改变时
if (_platformDetectionRayCast2D != null && _platformDetectionRayCast2D.IsColliding() != _collidingWithPlatform)
{
//When the state changes, update the action hint
//当状态改变时,更新操作提示
_collidingWithPlatform = _platformDetectionRayCast2D.IsColliding();
UpdateOperationTip();
}
//If the character is on the ground, give an upward velocity when the jump button is pressed
//如果角色正在地面上,按下跳跃键时,给予一个向上的速度
if (Input.IsActionJustPressed("ui_up") && IsOnFloor())
velocity.Y = JumpVelocity;
//Moving left and right
//左右移动
var axis = Input.GetAxis("ui_left", "ui_right");
velocity.X = axis * Speed * Config.CellSize * ProtectedSpeedScale;
//Use items
//使用物品
if (Input.IsActionPressed("use_item"))
{
UseItem(GetGlobalMousePosition());
}
//Pick up an item
//捡起物品
if (Input.IsActionJustPressed("pick_up"))
{
var pickAbleItem = FindTheNearestItem();
var success = PickItem(pickAbleItem);
if (success)
{
if (pickAbleItem != null)
{
PickingRangeBodiesList?.Remove(pickAbleItem);
}
RecycleFloatLabel();
}
}
if (Input.IsActionJustPressed("ui_down"))
{
if (_collidingWithPlatform)
{
//When the character stands on the platform and presses the ui_down key, we cancel the collision between the character and the platform
//当角色站在平台上按下 ui_down 键时,我们取消角色与平台的碰撞
var timer = new Timer();
AddChild(timer);
timer.WaitTime = _platformCollisionRecoveryTime;
timer.OneShot = true;
timer.Start();
timer.Timeout += () =>
{
SetCollisionMaskValue(Config.LayerNumber.Platform, true);
timer.QueueFree();
};
SetCollisionMaskValue(Config.LayerNumber.Platform, false);
}
}
//Display a parabola when an item is thrown
//抛出物品时,显示抛物线
if (Input.IsActionPressed("throw"))
{
if (_parabola == null)
{
return;
}
if (ItemMarker2D == null)
{
//Cannot get the marked location of the item, then do not draw a line
//无法获取物品的标记位置,那么不绘制线
return;
}
_parabola.Points = CurrentItem == null
? _emptyVector2Array
: ParabolicUtils.ComputeParabolic(ItemMarker2D.Position, GetThrowVelocity(), Gravity, 0.1f);
}
//When you raise your hand, throw the object
//抬起手时,抛出物品
if (Input.IsActionJustReleased("throw"))
{
if (ItemContainer == null)
{
return;
}
if (_parabola != null)
{
_parabola.Points = [Vector2.Zero];
}
ThrowItem(ItemContainer.GetSelectIndex(), 1, GetThrowVelocity());
GameSceneNodeHolder.HideBackpackUiContainerIfVisible();
CurrentItem = null;
}
}
protected override void WhenUpdateCurrentItem(Node2D? currentItem)
{
UpdateOperationTip();
}
/// <summary>
/// <para>当玩家手动抛出物品时,施加到物品上的速度值</para>
/// </summary>
/// <returns></returns>
private Vector2 GetThrowVelocity()
{
//We take the mouse position, normalize it, and then multiply it by the distance the player can throw
//我们拿到鼠标的位置,将其归一化处理,然后乘以玩家可扔出的距离
return GetLocalMousePosition().Normalized() * _throwingVelocity;
}
public override void _Process(double delta)
{
if (!Visible)
{
return;
}
AimTheCurrentItemAtAPoint(GetGlobalMousePosition());
var itemMarker2DPosition = Vector2.Zero;
if (ItemMarker2D != null)
{
itemMarker2DPosition = ItemMarker2D.Position;
}
var axis = Input.GetAxis("ui_left", "ui_right");
switch (axis)
{
case -1:
//Minus 1, we move to the left
//-1向左移动
FacingLeft = true;
if (ItemMarker2D != null)
{
itemMarker2DPosition.X = -ReadOnlyItemMarkerOriginalX;
}
Flip();
break;
case 1:
//1, move to the right
//1向右移动
FacingLeft = false;
if (ItemMarker2D != null)
{
itemMarker2DPosition.X = ReadOnlyItemMarkerOriginalX;
}
Flip();
break;
}
if (ItemMarker2D != null)
{
ItemMarker2D.Position = itemMarker2DPosition;
}
}
protected override void Flip()
{
base.Flip();
//If there is a weapon, flip it too
//如果有武器的话,也要翻转
if (CurrentItem is PickAbleTemplate pickAbleTemplate)
{
pickAbleTemplate.Flip(FacingLeft);
}
}
public override void Revive(int newHp)
{
base.Revive(newHp);
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
if (healthBarUi != null)
{
//The purpose of setting Hp to the current Hp is to cause the life bar to refresh.
//将Hp设置为当前Hp的目的是使生命条刷新。
healthBarUi.CurrentHp = CurrentHp;
}
}
/// <summary>
/// <para>When the player dies</para>
/// <para>当玩家死亡的时候</para>
/// </summary>
/// <param name="damageTemplate"></param>
protected override async Task OnDie(DamageTemplate damageTemplate)
{
Hide();
ProcessMode = ProcessModeEnum.Disabled;
if (EventManager.GameOverEvent == null)
{
return;
}
var gameOverEvent = new GameOverEvent();
if (damageTemplate.Attacker != null)
{
gameOverEvent.DeathInfo = await DeathInfoGenerator.GenerateDeathInfo(this, damageTemplate.Attacker);
}
EventManager.GameOverEvent.Invoke(gameOverEvent);
}
protected override void EnterThePickingRangeBody(Node node)
{
base.EnterThePickingRangeBody(node);
if (CurrentItem == node)
{
//If the node entering the pick range is the node held by the player, then return.
//如果说进入拾捡范围的节点是玩家所持有的节点,那么返回。
return;
}
if (node is not Node2D node2D)
{
return;
}
if (_floatLabel != null)
{
if (node is not PickAbleTemplate pickAbleTemplate)
{
return;
}
if (pickAbleTemplate.Picked)
{
//If the pickables are picked up, the label is not displayed.
//如果可拾捡物被捡起了,那么不显示标签。
LogCat.LogWarning("pickable_picked_up");
return;
}
NodeUtils.CallDeferredReparent(node, _floatLabel);
var rotationDegreesNode2D = node2D.RotationDegrees;
var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D);
_floatLabel.GlobalPosition = node2D.GlobalPosition;
_floatLabel.Position = rotationDegreesNode2DAbs > 90
? new Vector2(0, PromptTextDistance)
: new Vector2(0, -PromptTextDistance);
_floatLabel.RotationDegrees = 0 - rotationDegreesNode2D;
var label = _floatLabel.GetNode<Label>("Label");
var stringBuilder = new StringBuilder();
if (pickAbleTemplate.Owner is CharacterTemplate characterTemplate)
{
stringBuilder.Append(characterTemplate.ReadOnlyCharacterName);
stringBuilder.Append(TranslationServerUtils.Translate("de"));
}
stringBuilder.Append(TranslationServerUtils.Translate(pickAbleTemplate.Name));
label.Text = stringBuilder.ToString();
_floatLabel.Show();
}
UpdateOperationTip();
}
protected override void ExitThePickingRangeBody(Node node)
{
base.ExitThePickingRangeBody(node);
if (node is not Node2D)
{
return;
}
RecycleFloatLabel();
UpdateOperationTip();
}
/// <summary>
/// <para>Recycle Float Label</para>
/// <para>回收悬浮标签</para>
/// </summary>
private void RecycleFloatLabel()
{
if (_floatLabel == null)
{
return;
}
_floatLabel.Hide();
NodeUtils.CallDeferredReparent(this, _floatLabel);
}
protected override void OnHit(DamageTemplate damageTemplate)
{
base.OnHit(damageTemplate);
var healthBarUi = GameSceneNodeHolder.HealthBarUi;
if (healthBarUi != null)
{
healthBarUi.CurrentHp = CurrentHp;
}
}
}

View File

@ -20,100 +20,100 @@ namespace ColdMint.scripts.loader.uiLoader;
/// </summary>
public partial class SplashScreenLoader : UiLoaderTemplate
{
private Label? _loadingLabel;
private PackedScene? _mainMenuScene;
private AnimationPlayer? _animationPlayer;
private string _startup = "startup";
private Label? _nameLabel;
private Label? _loadingLabel;
private PackedScene? _mainMenuScene;
private AnimationPlayer? _animationPlayer;
private string _startup = "startup";
private Label? _nameLabel;
public override void InitializeData()
{
_mainMenuScene = GD.Load<PackedScene>("res://scenes/mainMenu.tscn");
}
public override void InitializeData()
{
_mainMenuScene = GD.Load<PackedScene>("res://scenes/mainMenu.tscn");
}
public override void InitializeUi()
{
_nameLabel = GetNode<Label>("NameLabel");
_loadingLabel = GetNode<Label>("loadingStateLabel");
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
//Disable animation in Debug mode.
//在Debug模式禁用动画。
if (Config.IsDebug())
{
_loadingLabel.Modulate = Colors.White;
_nameLabel.Modulate = Colors.White;
AnimationFinished(_startup);
}
else
{
_animationPlayer.Play(_startup);
_animationPlayer.AnimationFinished += AnimationFinished;
}
}
public override void InitializeUi()
{
_nameLabel = GetNode<Label>("NameLabel");
_loadingLabel = GetNode<Label>("loadingStateLabel");
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
//Disable animation in Debug mode.
//在Debug模式禁用动画。
if (Config.IsDebug())
{
_loadingLabel.Modulate = Colors.White;
_nameLabel.Modulate = Colors.White;
AnimationFinished(_startup);
}
else
{
_animationPlayer.Play(_startup);
_animationPlayer.AnimationFinished += AnimationFinished;
}
}
private async void AnimationFinished(StringName name)
{
await LoadingGlobalData();
if (_mainMenuScene == null)
{
return;
}
private async void AnimationFinished(StringName name)
{
await LoadingGlobalData();
if (_mainMenuScene == null)
{
return;
}
GetTree().ChangeSceneToPacked(_mainMenuScene);
}
GetTree().ChangeSceneToPacked(_mainMenuScene);
}
/// <summary>
/// <para>Load the game's global data</para>
/// <para>加载游戏的全局数据</para>
/// </summary>
private async Task LoadingGlobalData()
{
//Loading App configuration
//加载App配置
var appConfigData = AppConfig.LoadFromFile();
if (appConfigData != null)
{
AppConfig.ApplyAppConfig(appConfigData);
}
/// <summary>
/// <para>Load the game's global data</para>
/// <para>加载游戏的全局数据</para>
/// </summary>
private async Task LoadingGlobalData()
{
//Loading App configuration
//加载App配置
var appConfigData = AppConfig.LoadFromFile();
if (appConfigData != null)
{
AppConfig.ApplyAppConfig(appConfigData);
}
//Set the minimum log level to Info in debug mode.(Print all logs)
//在调试模式下将最小日志等级设置为Info。打印全部日志
//Disable all logs in the release version.
//在发行版禁用所有日志。
LogCat.MinLogLevel = Config.IsDebug() ? LogCat.InfoLogLevel : LogCat.DisableAllLogLevel;
ContributorDataManager.RegisterAllContributorData();
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
MapGenerator.RegisterRoomInjectionProcessor(new TimeIntervalRoomInjectorProcessor());
//Register the corresponding encoding provider to solve the problem of garbled Chinese path of the compressed package
//注册对应的编码提供程序,解决压缩包中文路径乱码问题
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
//创建游戏数据文件夹
var dataPath = Config.GetGameDataDirectory();
if (!Directory.Exists(dataPath))
{
Directory.CreateDirectory(dataPath);
}
//Set the minimum log level to Info in debug mode.(Print all logs)
//在调试模式下将最小日志等级设置为Info。打印全部日志
//Disable all logs in the release version.
//在发行版禁用所有日志。
LogCat.MinLogLevel = Config.IsDebug() ? LogCat.InfoLogLevel : LogCat.DisableAllLogLevel;
ContributorDataManager.RegisterAllContributorData();
DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler());
MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor());
MapGenerator.RegisterRoomInjectionProcessor(new TimeIntervalRoomInjectorProcessor());
//Register the corresponding encoding provider to solve the problem of garbled Chinese path of the compressed package
//注册对应的编码提供程序,解决压缩包中文路径乱码问题
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
//创建游戏数据文件夹
var dataPath = Config.GetGameDataDirectory();
if (!Directory.Exists(dataPath))
{
Directory.CreateDirectory(dataPath);
}
//Registered camp
//注册阵营
var defaultCamp = new Camp(Config.CampId.Default)
{
FriendInjury = true
};
CampManager.SetDefaultCamp(defaultCamp);
var mazoku = new Camp(Config.CampId.Mazoku);
CampManager.AddCamp(mazoku);
var aborigines = new Camp(Config.CampId.Aborigines);
CampManager.AddCamp(aborigines);
//Register ItemTypes from file
//从文件注册物品类型
ItemTypeRegister.RegisterFromFile();
//Hardcoded ItemTypes Register
//硬编码注册物品类型
ItemTypeRegister.StaticRegister();
//静态注册掉落表
LootRegister.StaticRegister();
await Task.Delay(TimeSpan.FromMilliseconds(500));
}
}
//Registered camp
//注册阵营
var defaultCamp = new Camp(Config.CampId.Default)
{
FriendInjury = true
};
CampManager.SetDefaultCamp(defaultCamp);
var mazoku = new Camp(Config.CampId.Mazoku);
CampManager.AddCamp(mazoku);
var aborigines = new Camp(Config.CampId.Aborigines);
CampManager.AddCamp(aborigines);
//Register ItemTypes from file
//从文件注册物品类型
ItemTypeRegister.RegisterFromFile();
//Hardcoded ItemTypes Register
//硬编码注册物品类型
ItemTypeRegister.StaticRegister();
//静态注册掉落表
LootRegister.StaticRegister();
await Task.Delay(TimeSpan.FromMilliseconds(500));
}
}

View File

@ -27,7 +27,51 @@ public class LookForWeaponProcessor : StateProcessorTemplate
{
//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;
//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;
}