mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 11:15:33 +00:00
620 lines
No EOL
14 KiB
C#
620 lines
No EOL
14 KiB
C#
using Godot;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Cirno.Scripts;
|
|
using Cirno.Scripts.Components;
|
|
using Cirno.Scripts.Components.Actors;
|
|
using Cirno.Scripts.Resources;
|
|
using Godot.Collections;
|
|
using System.Threading.Tasks;
|
|
|
|
public partial class PlayerMovement : CharacterBody2D, IDestructible
|
|
{
|
|
[Export]
|
|
public int Speed { get; set; } = 400;
|
|
|
|
[Export]
|
|
public int StrafeSpeed { get; set; } = 200;
|
|
|
|
public int MovementSpeed => _isStrafing ? StrafeSpeed : Speed;
|
|
|
|
[Export]
|
|
public float CrosshairDistance { get; set; } = 10f;
|
|
|
|
[Export]
|
|
public PackedScene SelectorScene { get; set; }
|
|
|
|
[Export]
|
|
public string GameOverScene { get; set; }
|
|
|
|
[Export]
|
|
public Texture2D WingsSprite { get; set; }
|
|
|
|
private Selector _selector;
|
|
|
|
//private Interactable _lastInteractable;
|
|
|
|
[Export]
|
|
public Marker2D Muzzle { get; set; }
|
|
|
|
private AnimatedSprite2D _animatedSprite;
|
|
|
|
private Vector2 _movementDirection { get; set; }
|
|
private Vector2 _facingDirection { get; set; }
|
|
|
|
private Vector2 _rightStickInput { get; set; }
|
|
|
|
private Sprite2D _crosshair;
|
|
|
|
[Export] public float MaxHealth = 32f;
|
|
[Export] public float MaxShield = 32f;
|
|
[Export] public Shader BlinkShader { get; set; }
|
|
[Export] public Sprite2D HitboxSprite { get; set; }
|
|
[Export] public BulletOwner BulletGroup { get; set; } = BulletOwner.Player;
|
|
|
|
[ExportGroup("Action Names")]
|
|
[Export] private string _shootActionName = "shoot";
|
|
[Export] private string _useActionName = "Use";
|
|
[Export] private string _strafeActionName = "strafe";
|
|
[Export] private string _nextWeaponActionName = "next_weapon";
|
|
[Export] private string _previousWeaponActionName = "previous_weapon";
|
|
[ExportCategory("Particles")]
|
|
[Export] private PackedScene _deathParticles;
|
|
[Export] private GpuParticles2D _shieldParticles;
|
|
public Weapon EquippedWeapon { get; set; }
|
|
|
|
private PlayerState _state;
|
|
|
|
public Array<Weapon> EquippedWeapons { get; set; } = new Array<Weapon>();
|
|
|
|
public int CurrentWeaponIndex { get; set; } = 0;
|
|
|
|
//private float _currentHealth = 0f;
|
|
//private float _currentShield = 0f;
|
|
|
|
private bool _isDestroyed = false;
|
|
|
|
private GameManager _gameManager;
|
|
|
|
private InventoryManager _inventoryManager;
|
|
|
|
private ActorResourceProvider _healthProvider;
|
|
private ActorResourceProvider _shieldProvider;
|
|
|
|
private bool _isStrafing { get; set; }
|
|
|
|
private bool _canMove = true;
|
|
|
|
[Signal]
|
|
public delegate void HealthChangedEventHandler(float newHealth, float maxHealth);
|
|
|
|
[Signal]
|
|
public delegate void ShieldChangedEventHandler(float newShield, float maxShield);
|
|
|
|
[Signal]
|
|
public delegate void InteractableAreaEnteredEventHandler(Interactable interactable);
|
|
|
|
[Signal]
|
|
public delegate void InteractableAreaExitedEventHandler(Interactable interactable);
|
|
|
|
[Signal]
|
|
public delegate void DeathEventHandler();
|
|
|
|
public float CurrentHealth
|
|
{
|
|
get => _healthProvider.CurrentResource;
|
|
set => _healthProvider.CurrentResource = value;
|
|
}
|
|
|
|
public float CurrentShield
|
|
{
|
|
get => _shieldProvider.CurrentResource;
|
|
set => _shieldProvider.CurrentResource = value;
|
|
}
|
|
|
|
private Vector2 _lastCheckPointPosition;
|
|
|
|
public Vector2 LastCheckPointPosition
|
|
{
|
|
get => _lastCheckPointPosition;
|
|
set => _lastCheckPointPosition = value;
|
|
}
|
|
|
|
public override void _Ready()
|
|
{
|
|
// CurrentHealth = MaxHealth;
|
|
// CurrentShield = MaxShield;
|
|
|
|
_state = PlayerState.Active;
|
|
|
|
_animatedSprite = GetNode<AnimatedSprite2D>("./Smoothing2D/AnimatedSprite2D");
|
|
_crosshair = GetNode<Sprite2D>("./Smoothing2D/Crosshair");
|
|
|
|
_healthProvider = GetNode<ActorResourceProvider>("HealthProvider");
|
|
_shieldProvider = GetNode<ActorResourceProvider>("ShieldProvider");
|
|
|
|
_healthProvider.MaxResource = MaxHealth;
|
|
_shieldProvider.MaxResource = MaxShield;
|
|
_healthProvider.ResourceChanged += (value, maxValue) =>
|
|
{
|
|
EmitSignal(nameof(HealthChanged), value, maxValue);
|
|
};
|
|
_shieldProvider.ResourceChanged += (value, maxValue) =>
|
|
{
|
|
EmitSignal(nameof(ShieldChanged), value, maxValue);
|
|
};
|
|
_healthProvider.FillResource();
|
|
_shieldProvider.FillResource();
|
|
|
|
_movementDirection = Vector2.Zero;
|
|
_facingDirection = Vector2.Zero;
|
|
_rightStickInput = Vector2.Zero;
|
|
_isStrafing = false;
|
|
|
|
_gameManager = GameManager.Instance;
|
|
|
|
//_gameManager = this.GetGameManager();
|
|
_inventoryManager = this.GetInventoryManager();
|
|
|
|
_gameManager.GameStateChange += GameManagerOnGameStateChange;
|
|
|
|
if (SelectorScene != null)
|
|
{
|
|
_selector = this.CreateSibling<Selector>(SelectorScene, this.GlobalPosition);
|
|
_selector.Visible = false;
|
|
}
|
|
|
|
_lastCheckPointPosition = GlobalPosition;
|
|
|
|
_ = UnTeleport();
|
|
}
|
|
|
|
private void GameManagerOnGameStateChange(GameState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case GameState.Menu:
|
|
case GameState.Paused:
|
|
_canMove = false;
|
|
_state = PlayerState.Paused;
|
|
break;
|
|
case GameState.Playing:
|
|
_canMove = true;
|
|
_state = PlayerState.Active;
|
|
break;
|
|
case GameState.Dialogue:
|
|
_canMove = false;
|
|
_state = PlayerState.Paused;
|
|
break;
|
|
case GameState.Controlling:
|
|
_canMove = false;
|
|
_state = PlayerState.Controlling;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests disable movement
|
|
/// </summary>
|
|
/// <param name="disable">true disables false enables</param>
|
|
public void RequestMovementDisable(bool disable)
|
|
{
|
|
if (disable)
|
|
{
|
|
_canMove = false;
|
|
}
|
|
else
|
|
{
|
|
_canMove = true;
|
|
}
|
|
}
|
|
|
|
public void AddWeapon(Weapon weapon)
|
|
{
|
|
EquippedWeapons.Add(weapon);
|
|
}
|
|
|
|
public void EquipWeapon(string itemKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(itemKey)) return;
|
|
var weapon = EquippedWeapons.FirstOrDefault(x => x.WeaponData.ItemKey == itemKey);
|
|
|
|
if (weapon is null) return;
|
|
|
|
EquipWeapon(weapon);
|
|
}
|
|
|
|
public void UseItem(LootItem item, int currentAmount)
|
|
{
|
|
GD.Print($"Used item on player {item.ItemKey}");
|
|
|
|
switch (item.Item)
|
|
{
|
|
case ItemTypes.FrogBomb:
|
|
|
|
_inventoryManager.RemoveItem(item.ItemKey, 1);
|
|
// emit projectile
|
|
var bullet = this.CreateChildOf<Bullet>(_gameManager.BulletsContainer, item.WeaponData.BulletData.BulletScene, this.GlobalPosition);
|
|
|
|
var bulletData = item.WeaponData.MakeBullet(this.GlobalPosition);
|
|
|
|
bullet.Initialize(bulletData, _gameManager);
|
|
|
|
//bullet.SetDirection(ShootDirection);
|
|
bullet.SetDirection(_facingDirection);
|
|
bullet.Speed = item.WeaponData.BulletData.BulletSpeed;
|
|
RequestMovementDisable(true);
|
|
// set camera
|
|
_gameManager.CameraTargetObject(bullet);
|
|
// set event destroy
|
|
bullet.OnDestroy += () =>
|
|
{
|
|
_gameManager.CameraTargetPlayer();
|
|
RequestMovementDisable(false);
|
|
};
|
|
// go back
|
|
break;
|
|
|
|
}
|
|
//var item = _inventoryManager.getitem
|
|
|
|
}
|
|
|
|
// Triggered by event in inventorymanager
|
|
public void EquipWeapon(Weapon weapon)
|
|
{
|
|
EquippedWeapon = weapon;
|
|
CurrentWeaponIndex = EquippedWeapons.IndexOf(weapon);
|
|
}
|
|
|
|
public void NextWeapon()
|
|
{
|
|
CurrentWeaponIndex += 1;
|
|
if (CurrentWeaponIndex > EquippedWeapons.Count - 1)
|
|
{
|
|
CurrentWeaponIndex = EquippedWeapons.Count - 1;
|
|
}
|
|
|
|
EquipWeapon(EquippedWeapons[CurrentWeaponIndex]);
|
|
}
|
|
|
|
public void PreviousWeapon()
|
|
{
|
|
CurrentWeaponIndex -= 1;
|
|
if (CurrentWeaponIndex < 0)
|
|
{
|
|
CurrentWeaponIndex = 0;
|
|
}
|
|
|
|
EquipWeapon(EquippedWeapons[CurrentWeaponIndex]);
|
|
}
|
|
|
|
private void FindInteractable()
|
|
{
|
|
var selected = _selector.SelectedInteractable;
|
|
if (!Input.IsActionJustPressed(_useActionName) || selected == null) return;
|
|
if (!selected.CanActivate()) return;
|
|
bool success = selected.Activate();
|
|
|
|
if (success)
|
|
{
|
|
// Deselect and scan for next
|
|
_selector.RemoveInteractable(selected);
|
|
//_selector.SelectedInteractable = null;
|
|
//_selector.SelectNext();
|
|
}
|
|
|
|
//var spaceState = GetWorld2D().DirectSpaceState;
|
|
|
|
//var query = PhysicsRayQueryParameters2D.Create(Vector2.Zero, )
|
|
}
|
|
|
|
private void HandleShoot()
|
|
{
|
|
if (EquippedWeapon == null) return;
|
|
if (!Input.IsActionPressed(_shootActionName)) return;
|
|
|
|
EquippedWeapon.ShootDirection = this._facingDirection;
|
|
EquippedWeapon.Shoot();
|
|
}
|
|
|
|
private void SetAnimation()
|
|
{
|
|
|
|
if (Velocity.X == 0 && Velocity.Y == 0)
|
|
{
|
|
_animatedSprite.SpeedScale = 0;
|
|
}
|
|
else
|
|
{
|
|
_animatedSprite.SpeedScale = 1;
|
|
}
|
|
|
|
if (Velocity.X > 0)
|
|
{
|
|
_animatedSprite.Play("walk_right");
|
|
}
|
|
else if (Velocity.X < 0)
|
|
{
|
|
_animatedSprite.Play("walk_left");
|
|
}
|
|
else if (Velocity.Y > 0)
|
|
{
|
|
_animatedSprite.Play("walk_down");
|
|
}
|
|
else if (Velocity.Y < 0)
|
|
{
|
|
_animatedSprite.Play("walk_up");
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public Vector2 GetInput()
|
|
{
|
|
return Input.GetVector("left", "right", "up", "down");
|
|
}
|
|
|
|
private Vector2 GetRightStickInput()
|
|
{
|
|
return new Vector2(
|
|
Input.GetAxis("aim_left", "aim_right"),
|
|
Input.GetAxis("aim_up", "aim_down")
|
|
);
|
|
}
|
|
|
|
private Vector2 CalculateCrosshairPosition()
|
|
{
|
|
|
|
return _facingDirection * CrosshairDistance;// + this.Position;
|
|
|
|
//var angle = Mathf.Atan2(this.Position.X, this.Position.Y);
|
|
|
|
|
|
//var cPos = new Vector2(this.Position.X + CrosshairDistance * Godot.Mathf.Cos(angle), this.Position.Y + CrosshairDistance * Godot.Mathf.Sin(angle));
|
|
}
|
|
|
|
public override void _Process(double delta)
|
|
{
|
|
if (_isDestroyed)
|
|
{
|
|
if (Input.IsActionJustPressed(_shootActionName))
|
|
Respawn();
|
|
return;
|
|
}
|
|
;
|
|
|
|
SetAnimation();
|
|
if (!_canMove) return;
|
|
|
|
_movementDirection = GetInput();
|
|
|
|
_isStrafing = Input.IsActionPressed(_strafeActionName);
|
|
// Toggle visibility of the hitbox sprite based on strafing
|
|
if (HitboxSprite != null)
|
|
{
|
|
HitboxSprite.Visible = _isStrafing;
|
|
}
|
|
|
|
_rightStickInput = GetRightStickInput();
|
|
|
|
// Update Facing Direction
|
|
if (!_isStrafing)
|
|
{
|
|
if (_rightStickInput.Length() > 0.1f) // If the right stick is moved
|
|
{
|
|
_facingDirection = _rightStickInput.Normalized();
|
|
}
|
|
else if (_movementDirection != Vector2.Zero) // Fall back to movement direction
|
|
{
|
|
_facingDirection = _movementDirection;
|
|
}
|
|
}
|
|
|
|
HandleShoot();
|
|
FindInteractable();
|
|
|
|
if (Input.IsActionJustPressed(_nextWeaponActionName))
|
|
{
|
|
NextWeapon();
|
|
}
|
|
|
|
if (Input.IsActionJustPressed(_previousWeaponActionName))
|
|
{
|
|
PreviousWeapon();
|
|
}
|
|
|
|
_crosshair.Position = CalculateCrosshairPosition();
|
|
}
|
|
|
|
public void Respawn()
|
|
{
|
|
if (!_isDestroyed) return;
|
|
_isDestroyed = false;
|
|
this.GlobalPosition = LastCheckPointPosition;
|
|
_healthProvider.FillResource();
|
|
this.Visible = true;
|
|
}
|
|
|
|
public override void _PhysicsProcess(double delta)
|
|
{
|
|
if (_isDestroyed) return;
|
|
if (!_canMove) return;
|
|
|
|
Velocity = _movementDirection * MovementSpeed;
|
|
|
|
MoveAndSlide();
|
|
}
|
|
|
|
private void _on_interaction_controller_area_entered(Area2D area)
|
|
{
|
|
if (!_canMove) return;
|
|
// Replace with function body.
|
|
if (area.IsInGroup("Interactable") && area is Interactable interactable && interactable.CanActivate())
|
|
{
|
|
Debug.WriteLine($"Interactable {area.Name} Entered");
|
|
|
|
EmitSignal(nameof(InteractableAreaEntered), interactable);
|
|
|
|
if (_selector == null) return;
|
|
|
|
_selector.AddInteractable(interactable);
|
|
|
|
//_selector.SelectedInteractable = interactable;
|
|
|
|
}
|
|
}
|
|
|
|
private void _on_interaction_controller_area_exited(Area2D area)
|
|
{
|
|
if (!_canMove) return;
|
|
if (area.IsInGroup("Interactable") && area is Interactable interactable)
|
|
{
|
|
Debug.WriteLine($"Interactable {area.Name} Exited");
|
|
|
|
EmitSignal(nameof(InteractableAreaExited), interactable);
|
|
|
|
if (_selector == null) return;
|
|
_selector.RemoveInteractable(interactable);
|
|
}
|
|
}
|
|
|
|
private void Explode()
|
|
{
|
|
Debug.WriteLine("Ded");
|
|
//CreateParticles();
|
|
//CreateDebris();
|
|
// if (GameOverScene != null)
|
|
// {
|
|
// GetTree().ChangeSceneToFile(GameOverScene);
|
|
// }
|
|
// else
|
|
// {
|
|
// QueueFree();
|
|
// }
|
|
var particles = this.CreateSibling<AutodeleteParticle>(_deathParticles, this.GlobalPosition);
|
|
//particles.Init();
|
|
this.Visible = false;
|
|
EmitSignal(SignalName.Death);
|
|
}
|
|
|
|
public void Hit(float damage, DamageType type = DamageType.Neutral)
|
|
{
|
|
if (!_canMove) return;
|
|
GD.Print($"Player damaged for {damage}");
|
|
if (_isDestroyed) return;
|
|
|
|
if (CurrentShield > 0 && type is not DamageType.Explosive or DamageType.Acid)
|
|
{
|
|
// Reduce shield
|
|
PlayShieldAnimation();
|
|
CurrentShield -= damage;
|
|
if (CurrentShield < 0)
|
|
{
|
|
CurrentHealth -= Math.Abs(CurrentShield);
|
|
CurrentShield = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (type is DamageType.Fire)
|
|
{
|
|
CurrentHealth -= damage * 2;
|
|
}
|
|
else
|
|
{
|
|
CurrentHealth -= damage;
|
|
}
|
|
|
|
Blink();
|
|
}
|
|
|
|
if (!(CurrentHealth <= 0)) return;
|
|
_isDestroyed = true;
|
|
Explode();
|
|
}
|
|
|
|
private void PlayShieldAnimation()
|
|
{
|
|
if (_shieldParticles is null) return;
|
|
_shieldParticles.Emitting = true;
|
|
}
|
|
|
|
public void Blink()
|
|
{
|
|
if (BlinkShader != null)
|
|
{
|
|
_ = BlinkAsync();
|
|
}
|
|
}
|
|
|
|
private async Task BlinkAsync()
|
|
{
|
|
((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader;
|
|
|
|
Tween tween = GetTree().CreateTween();
|
|
tween.TweenMethod(Callable.From((float value) => SetShaderBlinkIntensity(value)), 1f, 0, 0.5);
|
|
await ToSignal(tween, "finished");
|
|
}
|
|
|
|
public async Task Teleport()
|
|
{
|
|
((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader;
|
|
|
|
Tween tween = GetTree().CreateTween();
|
|
tween.TweenMethod(Callable.From((float value) => SetShaderScanlineDensity(value)), 0f, 50f, 0.5);
|
|
tween.Parallel().TweenMethod(Callable.From((float value) => SetShaderTeleportProgress(value)), 0f, 1f, 0.5);
|
|
await ToSignal(tween, "finished");
|
|
}
|
|
|
|
public async Task UnTeleport()
|
|
{
|
|
((ShaderMaterial)_animatedSprite.Material).Shader = BlinkShader;
|
|
|
|
Tween tween = GetTree().CreateTween();
|
|
tween.TweenMethod(Callable.From((float value) => SetShaderTeleportProgress(value)), 1f, 0f, 0.5);
|
|
tween.Parallel().TweenMethod(Callable.From((float value) => SetShaderScanlineDensity(value)), 50f, 0f, 0.5);
|
|
await ToSignal(tween, "finished");
|
|
}
|
|
|
|
private void SetShaderTeleportProgress(float value)
|
|
{
|
|
((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("teleport_progress", value);
|
|
}
|
|
|
|
private void SetShaderScanlineDensity(float value)
|
|
{
|
|
((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("scanline_density", value);
|
|
}
|
|
|
|
private void SetShaderBlinkIntensity(float newValue)
|
|
{
|
|
((ShaderMaterial)_animatedSprite.Material).SetShaderParameter("blink_intensity", newValue);
|
|
}
|
|
|
|
|
|
public bool IsDestroyed()
|
|
{
|
|
return _isDestroyed;
|
|
}
|
|
|
|
private void _on_damage_hit_box_area_entered(Area2D area)
|
|
{
|
|
if (!_canMove) return;
|
|
if (area is Bullet bullet && bullet.BulletOwner != BulletGroup)
|
|
{
|
|
this.Hit(bullet.Damage, bullet.DamageType);
|
|
bullet.RequestCollisionDestruction();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public enum PlayerState
|
|
{
|
|
Active,
|
|
Paused,
|
|
Controlling,
|
|
Dead,
|
|
} |