怪物可以捡起武器打玩家了。
This commit is contained in:
parent
7812d9c570
commit
e4583aed3c
|
@ -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.,拾い取り範囲に入ります。
|
|
|
@ -26,7 +26,6 @@ animations = [{
|
|||
collision_layer = 4
|
||||
collision_mask = 34
|
||||
script = ExtResource("1_1dlls")
|
||||
LootListId = null
|
||||
metadata/CampId = "Default"
|
||||
metadata/MaxHp = 32
|
||||
|
||||
|
|
|
@ -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 = [{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user