using System; using System.Text; using ColdMint.scripts.damage; using ColdMint.scripts.utils; using ColdMint.scripts.weapon; using Godot; namespace ColdMint.scripts.character; /// /// 玩家角色 /// public partial class Player : CharacterTemplate { private PackedScene? _floatLabelPackedScene; private Control? _floatLabel; //Empty object projectile //空的物品抛射线 private readonly Vector2[] _emptyVector2Array = new[] { Vector2.Zero }; //抛物线 private Line2D? _parabola; //用于检测玩家是否站在平台上的射线 private RayCast2D? _platformDetectionRayCast2D; //在拾捡范围内,可拾起的物品数量 private int _totalNumberOfPickups; private const float PromptTextDistance = 50; //玩家可拾捡的物品 private Node2D? _pickAbleItem; //抛出物品的飞行速度 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; //物品被扔出后多长时间恢复与地面和平台的碰撞(单位:秒) private readonly double _itemCollisionRecoveryTime = 0.045f; public override void _Ready() { base._Ready(); CharacterName = TranslationServer.Translate("default_player_name"); _floatLabelPackedScene = GD.Load("res://prefab/ui/FloatLabel.tscn"); _parabola = GetNode("Parabola"); _platformDetectionRayCast2D = GetNode("PlatformDetectionRayCast"); UpdateOperationTip(); var healthBarUi = GameSceneNodeHolder.HealthBarUi; if (healthBarUi != null) { healthBarUi.MaxHp = MaxHp; healthBarUi.CurrentHp = CurrentHp; } } /// /// Update operation prompt /// 更新操作提示 /// private void UpdateOperationTip() { var operationTipLabel = GameSceneNodeHolder.OperationTipLabel; if (operationTipLabel == null) { return; } var operationTipBuilder = new StringBuilder(); if (_totalNumberOfPickups > 0) { //If there's anything around to pick up //如果周围有能捡的东西 if (CurrentItem == null) { if (_pickAbleItem != null) { string? name = null; if (_pickAbleItem is WeaponTemplate weaponTemplate) { //When the weapon has no owner, a pick up prompt is displayed. //当武器没有主人时,显示捡起提示。 if (weaponTemplate.Owner == null || weaponTemplate.Owner == this) { name = TranslationServer.Translate(weaponTemplate.Name); } } if (name != null) { operationTipBuilder.Append( TranslationServer.Translate(InputMap.ActionGetEvents("pick_up")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("pick_up")); operationTipBuilder.Append(name); } } } else { string? pickAbleItemName = null; string? currentItemName = null; string mustBeThrown = TranslationServer.Translate("must_be_thrown"); if (_pickAbleItem != null) { //可捡的物品是武器 if (_pickAbleItem is WeaponTemplate weaponTemplate) { pickAbleItemName = TranslationServer.Translate(weaponTemplate.Name); } } if (CurrentItem != null) { //当前持有的物品是武器 if (CurrentItem is WeaponTemplate weaponTemplate) { currentItemName = TranslationServer.Translate(weaponTemplate.Name); } } if (pickAbleItemName != null && currentItemName != null && mustBeThrown != "must_be_thrown") { operationTipBuilder.Append(string.Format(mustBeThrown, currentItemName, pickAbleItemName)); operationTipBuilder.Append(' '); operationTipBuilder.Append( TranslationServer.Translate(InputMap.ActionGetEvents("throw")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("throw")); operationTipBuilder.Append(currentItemName); } } operationTipLabel.Text = operationTipBuilder.ToString(); return; } operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_left")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("move_left")); operationTipBuilder.Append(' '); operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_right")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("move_right")); operationTipBuilder.Append(' '); operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_up")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("jump")); if (_collidingWithPlatform) { operationTipBuilder.Append(' '); operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("ui_down")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("jump_down")); } if (CurrentItem != null) { operationTipBuilder.Append(' '); operationTipBuilder.Append(TranslationServer.Translate(InputMap.ActionGetEvents("throw")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("throw")); if (CurrentItem is WeaponTemplate weaponTemplate) { operationTipBuilder.Append(TranslationServer.Translate(weaponTemplate.Name)); //提示武器攻击 operationTipBuilder.Append(' '); operationTipBuilder.Append( TranslationServer.Translate(InputMap.ActionGetEvents("use_item")[0].AsText())); operationTipBuilder.Append(TranslationServer.Translate("use_item")); operationTipBuilder.Append(TranslationServer.Translate(weaponTemplate.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; //Use items //使用物品 if (Input.IsActionPressed("use_item")) { UseItem(GetGlobalMousePosition()); } //Pick up an item //捡起物品 if (Input.IsActionJustPressed("pick_up")) { var success = PickItem(_pickAbleItem); if (success) { _pickAbleItem = null; _totalNumberOfPickups--; if (_floatLabel != null) { _floatLabel.QueueFree(); _floatLabel = null; } UpdateOperationTip(); } } 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 (CurrentItem == null) { return; } if (_parabola != null) { _parabola.Points = new[] { Vector2.Zero }; } CurrentItem.Reparent(GameSceneNodeHolder.WeaponContainer); switch (CurrentItem) { case WeaponTemplate weaponTemplate: { var timer = new Timer(); weaponTemplate.AddChild(timer); timer.WaitTime = _itemCollisionRecoveryTime; timer.OneShot = true; timer.Timeout += () => { //We cannot immediately resume the physical collision when the weapon is discharged, which will cause the weapon to collide with the ground and platform earlier, preventing the weapon from flying. //仍出武器时,我们不能立即恢复物理碰撞,立即恢复会导致武器更早的与地面和平台碰撞,阻止武器的飞行。 weaponTemplate.EnableContactInjury = true; weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Ground, true); weaponTemplate.SetCollisionMaskValue(Config.LayerNumber.Platform, true); timer.QueueFree(); }; timer.Start(); weaponTemplate.Sleeping = false; weaponTemplate.LinearVelocity = Vector2.Zero; break; } } //We apply force to objects. //我们给物品施加力。 switch (CurrentItem) { case CharacterBody2D characterBody2D: characterBody2D.Velocity = GetThrowVelocity(); break; case RigidBody2D rigidBody2D: rigidBody2D.LinearVelocity = GetThrowVelocity(); break; } CurrentItem = null; _totalNumberOfPickups++; var hotBar = GameSceneNodeHolder.HotBar; hotBar?.RemoveItemFromItemSlotBySelectIndex(1); UpdateOperationTip(); } } 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) { 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 WeaponTemplate weapon) { weapon.Flip(FacingLeft); } } protected override void EnterThePickingRangeBody(Node 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; } _totalNumberOfPickups++; _pickAbleItem = node2D; if (_floatLabelPackedScene != null) { //If there is a scene of floating text, then we generate floating text. //如果有悬浮文本的场景,那么我们生成悬浮文本。 _floatLabel?.QueueFree(); _floatLabel = (Control)_floatLabelPackedScene.Instantiate(); var rotationDegreesNode2D = node2D.RotationDegrees; var rotationDegreesNode2DAbs = Math.Abs(rotationDegreesNode2D); _floatLabel.Position = rotationDegreesNode2DAbs > 90 ? new Vector2(0, PromptTextDistance) : new Vector2(0, -PromptTextDistance); _floatLabel.RotationDegrees = 0 - rotationDegreesNode2D; var label = _floatLabel.GetNode