478 lines
14 KiB
C#
478 lines
14 KiB
C#
using System;
|
|
using Godot;
|
|
|
|
namespace Movementtests.player_controller.Scripts;
|
|
|
|
public partial class HealthSystem : Node3D
|
|
{
|
|
new enum Rotation
|
|
{
|
|
NoRotation = 0,
|
|
CameraRotationTriggered = 1,
|
|
RotatingOnZAxis = 2,
|
|
ReturningBack = 3,
|
|
}
|
|
|
|
[ExportGroup("Health Metrics")]
|
|
[ExportSubgroup("Amounts")]
|
|
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
|
|
public float MaxHealth { get; set; } = 100.0f;
|
|
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
|
|
public float CurrentHealth { get; set; } = 100.0f;
|
|
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
|
|
public float MinimalDamageUnit { get; set; } = 25.0f;
|
|
[Export(PropertyHint.Range, "-100,0,0.1,or_smaller")]
|
|
public float ThresholdVelYForDamage { get; set; } = -15.0f;
|
|
[ExportSubgroup("Regeneration")]
|
|
[Export(PropertyHint.Range, "0,10,0.01,suffix:s,or_greater")]
|
|
public float SecondsBeforeRegeneration { get; set; } = 5.5f;
|
|
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
|
public float RegenerationSpeed { get; set; } = 10.0f;
|
|
|
|
[ExportGroup("Damage Camera Effects")]
|
|
[ExportSubgroup("Camera Shake")]
|
|
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
|
|
public float RotationSpeed { get; set; } = 9.0f;
|
|
[Export(PropertyHint.Range, "0,180,0.1,degrees")]
|
|
public float RotationDegree { get; set; } = 14.0f;
|
|
[ExportSubgroup("Visual Distortion")]
|
|
// Screen darkness controls how dark the screen will be, where 0.0 - natural
|
|
// color of the screen(unaltered) and 1.0 - black screen
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float ScreenDarknessMin { get; set; } = 0.0f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float ScreenDarknessMax { get; set; } = 0.3f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float DistortionSpeedMin { get; set; } = 0.0f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float DistortionSpeedMax { get; set; } = 0.6f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float DistortionSizeMin { get; set; } = 0.0f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float DistortionSizeMax { get; set; } = 1.0f;
|
|
[ExportSubgroup("Vignetting")]
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float ActiveZoneMultiplierMin { get; set; } = 0.45f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float ActiveZoneMultiplierMax { get; set; } = 0.475f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01,or_greater")]
|
|
public float MultiplierDeltaForAnimation { get; set; } = 0.066f;
|
|
[Export(PropertyHint.Range, "0.0,1.0,0.01")]
|
|
public float Softness { get; set; } = 1.0f;
|
|
[Export(PropertyHint.Range, "0.0,10,0.01")]
|
|
public float SpeedMin { get; set; } = 2.95f;
|
|
[Export(PropertyHint.Range, "0.0,10,0.01")]
|
|
public float SpeedMax { get; set; } = 4.0f;
|
|
|
|
// Death / GameOver
|
|
[ExportGroup("Death")]
|
|
[ExportSubgroup("Before Fade Out")]
|
|
[Export(PropertyHint.Range, "0,1,0.01,or_less,or_greater")]
|
|
public float BlurLimitValueToStartFadeOut { get; set; } = 0.3f;
|
|
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
|
|
public float BlurValueToStartFadeOut { get; set; } = 3.376f;
|
|
[ExportSubgroup("Speeds")]
|
|
[Export(PropertyHint.Range, "0.1,20.0,0.1,or_greater")]
|
|
public float CameraDropSpeedOnDeath { get; set; } = 18.0f;
|
|
[Export(PropertyHint.Range, "0.01,5.0,0.01,or_greater")]
|
|
public float FadeOutSpeed { get; set; } = 0.11f;
|
|
|
|
[Export(PropertyHint.Range, "0.1,10,0.01,or_greater")]
|
|
public float BlurLimitSpeedOnDeath { get; set; } = 0.9f;
|
|
[Export(PropertyHint.Range, "0.1,10,0.01,or_greater")]
|
|
public float BlurSpeedOnDeath { get; set; } = 1.5f;
|
|
|
|
[ExportSubgroup("Target values")]
|
|
[Export(PropertyHint.Range, "0,5.0,0.01,suffix:m,or_greater")]
|
|
public float CameraHeightOnDeath { get; set; } = 0.68f;
|
|
[Export(PropertyHint.Range, "0,5.0,0.01,suffix:m,or_greater")]
|
|
public float FadeOutTargetValue { get; set; } = 4.0f;
|
|
|
|
// TODO: add setter: BlurLimitValueToStartFadeOut should always be less than BlurLimitTargetValue
|
|
// (control it in editor)
|
|
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
|
public float BlurLimitTargetValue { get; set; } = 0.5f;
|
|
|
|
// TODO: add setter: BlurValueToStartFadeOut should always be less than BlurTargetValue
|
|
// (control it in editor)
|
|
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
|
public float BlurTargetValue { get; set; } = 7.0f;
|
|
|
|
[ExportSubgroup("Other")]
|
|
[Export(PropertyHint.Range, "0.0,4.0,0.01,suffix:s,or_greater")]
|
|
public float ScreenDarknessToReloadScene { get; set; } = 1.74f;
|
|
|
|
|
|
// Required to hide Vignette effect
|
|
private float _currentHealthInPrevFrame;
|
|
|
|
private float _currentVelocityYInAir;
|
|
|
|
private CharacterBody3D _characterBody3D;
|
|
|
|
private Camera3D _camera;
|
|
private float _cameraInitialRotationZ;
|
|
private float _targetRotationZAxis;
|
|
private Rotation _cameraRotation = Rotation.NoRotation;
|
|
private float _progressOnCamRotation;
|
|
|
|
private Vector2 _uvOffset = Vector2.Zero;
|
|
private float _offsetResetThreshold = 5.0f;
|
|
|
|
private ShaderMaterial _distortionMaterial;
|
|
|
|
private float _timeAccumulator;
|
|
|
|
private float _currentSpeed;
|
|
|
|
private const float InitialMultiplierMidVal = 0.6f;
|
|
private const float MultiplierMidValToHideVignette = 0.8f;
|
|
private float _currentMultiplierMidValue;
|
|
|
|
private ShaderMaterial _vignetteMaterial;
|
|
|
|
private bool _deathAnimationPlayed;
|
|
private float _screenDarknessOnDeath;
|
|
private float _currentBlurLimit;
|
|
private float _currentBlur;
|
|
private float _currentScreenDarkness;
|
|
|
|
private bool _dead;
|
|
private Node3D _head;
|
|
private ShaderMaterial _blurMaterial;
|
|
|
|
public struct HealthSystemInitParams
|
|
{
|
|
public CharacterBody3D Parent;
|
|
public Camera3D Camera;
|
|
public Node3D Head;
|
|
public ColorRect VignetteRect;
|
|
public ColorRect DistortionRect;
|
|
public ColorRect BlurRect;
|
|
}
|
|
|
|
public void Init(HealthSystemInitParams initParams)
|
|
{
|
|
_currentHealthInPrevFrame = CurrentHealth;
|
|
_currentMultiplierMidValue = InitialMultiplierMidVal;
|
|
|
|
_currentSpeed = SpeedMin;
|
|
|
|
_characterBody3D = initParams.Parent;
|
|
_camera = initParams.Camera;
|
|
|
|
_head = initParams.Head;
|
|
|
|
_vignetteMaterial = initParams.VignetteRect.Material as ShaderMaterial;
|
|
_distortionMaterial = initParams.DistortionRect.Material as ShaderMaterial;
|
|
_blurMaterial = initParams.BlurRect.Material as ShaderMaterial;
|
|
|
|
// Resetting shaders' parameters
|
|
|
|
_vignetteMaterial.SetShaderParameter(Constants.VIGNETTE_SHADER_MULTIPLIER, 1.0f);
|
|
_vignetteMaterial.SetShaderParameter(Constants.VIGNETTE_SHADER_SOFTNESS, 1.0f);
|
|
|
|
_distortionMaterial.SetShaderParameter(Constants.DISTORTION_SHADER_SCREEN_DARKNESS, 0.0f);
|
|
_distortionMaterial.SetShaderParameter(Constants.DISTORTION_SHADER_DARKNESS_PROGRESSION, 0.0f);
|
|
|
|
_distortionMaterial.SetShaderParameter(
|
|
Constants.DISTORTION_SHADER_UV_OFFSET, new Vector2(0.0f, 0.0f));
|
|
|
|
_distortionMaterial.SetShaderParameter(Constants.DISTORTION_SHADER_SIZE, 0.0);
|
|
|
|
_blurMaterial.SetShaderParameter(Constants.BLUR_SHADER_LIMIT, 0.0f);
|
|
_blurMaterial.SetShaderParameter(Constants.BLUR_SHADER_BLUR, 0.0f);
|
|
}
|
|
|
|
public override void _Process(double delta)
|
|
{
|
|
float deltaConverted = (float)delta;
|
|
|
|
HandleDeath(deltaConverted);
|
|
|
|
HandleVignetteShader(deltaConverted);
|
|
HandleDistortionShader(deltaConverted);
|
|
|
|
HandleCameraRotationOnHit(deltaConverted);
|
|
HandleDamageOnFall();
|
|
|
|
HandleHealthRegeneration(deltaConverted);
|
|
}
|
|
|
|
public void TakeDamage(float amount)
|
|
{
|
|
if (_dead)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_cameraRotation == Rotation.NoRotation)
|
|
{
|
|
_cameraRotation = Rotation.CameraRotationTriggered;
|
|
}
|
|
|
|
CurrentHealth -= amount;
|
|
CurrentHealth = Mathf.Clamp(CurrentHealth, 0, MaxHealth);
|
|
|
|
if (CurrentHealth == 0)
|
|
{
|
|
_dead = true;
|
|
return;
|
|
}
|
|
|
|
_lastHitTime = DateTime.UtcNow;
|
|
}
|
|
|
|
public float GetCurrentHealth() { return CurrentHealth; }
|
|
|
|
#if DEBUG
|
|
public override void _UnhandledInput(InputEvent @event)
|
|
{
|
|
if (@event is InputEventKey eventKey)
|
|
|
|
if (eventKey.Pressed && eventKey.Keycode == Key.H)
|
|
TakeDamage(MinimalDamageUnit);
|
|
}
|
|
#endif
|
|
|
|
public bool IsDead() { return _dead; }
|
|
|
|
private void HandleDeath(float delta)
|
|
{
|
|
if (!_dead) { return; }
|
|
|
|
if (!_deathAnimationPlayed)
|
|
{
|
|
_deathAnimationPlayed = true;
|
|
}
|
|
|
|
Vector3 newPosition = _head.Position;
|
|
newPosition.Y = Mathf.Lerp(newPosition.Y, CameraHeightOnDeath, CameraDropSpeedOnDeath * delta);
|
|
|
|
if (newPosition.Y < CameraHeightOnDeath) { newPosition.Y = CameraHeightOnDeath; }
|
|
|
|
_head.Position = newPosition;
|
|
|
|
_currentBlurLimit = Mathf.Lerp(
|
|
_currentBlurLimit, BlurLimitTargetValue, BlurLimitSpeedOnDeath * delta);
|
|
|
|
_blurMaterial.SetShaderParameter(Constants.BLUR_SHADER_LIMIT, _currentBlurLimit);
|
|
|
|
_currentBlur = Mathf.Lerp(_currentBlur, BlurTargetValue, BlurSpeedOnDeath * delta);
|
|
_blurMaterial.SetShaderParameter(Constants.BLUR_SHADER_BLUR, _currentBlur);
|
|
|
|
if (_currentBlurLimit >= BlurLimitValueToStartFadeOut && _currentBlur >= BlurValueToStartFadeOut)
|
|
{
|
|
float currentScreenDarknessVariant = (float)_distortionMaterial.GetShaderParameter(
|
|
Constants.DISTORTION_SHADER_SCREEN_DARKNESS);
|
|
|
|
_screenDarknessOnDeath = Mathf.Lerp(
|
|
currentScreenDarknessVariant, FadeOutTargetValue, FadeOutSpeed * delta);
|
|
|
|
_distortionMaterial.SetShaderParameter(
|
|
Constants.DISTORTION_SHADER_SCREEN_DARKNESS, _screenDarknessOnDeath);
|
|
|
|
if (_screenDarknessOnDeath >= ScreenDarknessToReloadScene)
|
|
{
|
|
GD.Print("reload");
|
|
// Reload the current scene
|
|
GetTree().ReloadCurrentScene();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleVignetteShader(float delta)
|
|
{
|
|
if (Mathf.IsEqualApprox(CurrentHealth, MaxHealth))
|
|
{
|
|
_currentHealthInPrevFrame = CurrentHealth;
|
|
_currentMultiplierMidValue = InitialMultiplierMidVal;
|
|
_timeAccumulator = 0;
|
|
return;
|
|
}
|
|
|
|
float healthNormalized = CurrentHealth / MaxHealth;
|
|
float healthReverted = 1.0f - healthNormalized;
|
|
|
|
float newAnimationSpeed = Mathf.Lerp(SpeedMin, SpeedMax, healthReverted);
|
|
_currentSpeed = Mathf.Lerp(_currentSpeed, newAnimationSpeed, delta);
|
|
|
|
float completeSinCycle = Mathf.Tau / _currentSpeed;
|
|
|
|
_timeAccumulator = Mathf.Wrap(_timeAccumulator + delta, 0.0f, completeSinCycle);
|
|
|
|
float rawAnimationWeight = Mathf.Sin(_timeAccumulator * _currentSpeed);
|
|
|
|
float animationWeight = Mathf.Abs(rawAnimationWeight);
|
|
|
|
float difference = _currentHealthInPrevFrame - CurrentHealth;
|
|
|
|
float newMultiplierMidValue;
|
|
|
|
if (difference < 0)
|
|
{
|
|
newMultiplierMidValue = Mathf.Lerp(
|
|
MultiplierMidValToHideVignette, ActiveZoneMultiplierMin, healthReverted);
|
|
} else
|
|
{
|
|
newMultiplierMidValue = Mathf.Lerp(
|
|
ActiveZoneMultiplierMax, ActiveZoneMultiplierMin, healthReverted);
|
|
}
|
|
|
|
_currentMultiplierMidValue = Mathf.Lerp(
|
|
_currentMultiplierMidValue, newMultiplierMidValue, delta);
|
|
|
|
float multiplier = Mathf.Lerp(
|
|
_currentMultiplierMidValue - MultiplierDeltaForAnimation,
|
|
_currentMultiplierMidValue + MultiplierDeltaForAnimation,
|
|
animationWeight * animationWeight
|
|
);
|
|
|
|
_vignetteMaterial.SetShaderParameter(Constants.VIGNETTE_SHADER_MULTIPLIER, multiplier);
|
|
_vignetteMaterial.SetShaderParameter(Constants.VIGNETTE_SHADER_SOFTNESS, Softness);
|
|
|
|
_currentHealthInPrevFrame = CurrentHealth;
|
|
}
|
|
|
|
private void HandleDistortionShader(float delta)
|
|
{
|
|
if (Mathf.IsEqualApprox(CurrentHealth, MaxHealth))
|
|
{
|
|
return;
|
|
}
|
|
|
|
float healthNormalized = CurrentHealth / MaxHealth;
|
|
float healthReverted = 1 - healthNormalized;
|
|
|
|
_distortionMaterial.SetShaderParameter(
|
|
Constants.DISTORTION_SHADER_DARKNESS_PROGRESSION, healthReverted);
|
|
|
|
if (!_dead)
|
|
{
|
|
float screenDarkness = Mathf.Remap(
|
|
healthReverted, 0, 1, ScreenDarknessMin, ScreenDarknessMax);
|
|
|
|
_distortionMaterial.SetShaderParameter(
|
|
Constants.DISTORTION_SHADER_SCREEN_DARKNESS, screenDarkness);
|
|
}
|
|
|
|
float distortionSpeed = Mathf.Remap(
|
|
healthReverted, 0.0f, 1.0f, DistortionSpeedMin, DistortionSpeedMax);
|
|
|
|
float offsetVal = delta * distortionSpeed;
|
|
|
|
_uvOffset += new Vector2(offsetVal, offsetVal);
|
|
|
|
_distortionMaterial.SetShaderParameter(
|
|
Constants.DISTORTION_SHADER_UV_OFFSET, _uvOffset);
|
|
|
|
if (_uvOffset.X > _offsetResetThreshold) { _uvOffset.X = 0.0f; _uvOffset.Y = 0.0f; }
|
|
|
|
float distortionSize = Mathf.Remap(
|
|
healthReverted, 0.0f, 1.0f, DistortionSizeMin, DistortionSizeMax);
|
|
|
|
_distortionMaterial.SetShaderParameter(
|
|
Constants.DISTORTION_SHADER_SIZE, distortionSize);
|
|
}
|
|
|
|
private void RotateCameraOnZAxis(float delta, float targetAngleInRadians, Rotation rotationStateToSetOnFinish)
|
|
{
|
|
_progressOnCamRotation += delta * RotationSpeed;
|
|
_progressOnCamRotation = Mathf.Clamp(_progressOnCamRotation, 0f, 1f);
|
|
|
|
float lerpedAngleZ = Mathf.LerpAngle(
|
|
_camera.Rotation.Z,targetAngleInRadians, _progressOnCamRotation);
|
|
|
|
_camera.Rotation = new Vector3(_camera.Rotation.X, _camera.Rotation.Y, lerpedAngleZ);
|
|
|
|
float difference = Mathf.Abs(targetAngleInRadians - _camera.Rotation.Z);
|
|
|
|
if (difference < Constants.ACCEPTABLE_TOLERANCE)
|
|
{
|
|
_cameraRotation = rotationStateToSetOnFinish;
|
|
_progressOnCamRotation = 0;
|
|
}
|
|
}
|
|
|
|
private void HandleCameraRotationOnHit(float delta)
|
|
{
|
|
if (_cameraRotation == Rotation.NoRotation || _dead) return;
|
|
|
|
if (_cameraRotation == Rotation.CameraRotationTriggered)
|
|
{
|
|
if (GD.Randi() % 2 == 0)
|
|
{
|
|
_targetRotationZAxis = Mathf.DegToRad(RotationDegree * -1);
|
|
}
|
|
else
|
|
{
|
|
_targetRotationZAxis = Mathf.DegToRad(RotationDegree);
|
|
}
|
|
|
|
_cameraRotation = Rotation.RotatingOnZAxis;
|
|
}
|
|
|
|
if (_cameraRotation == Rotation.RotatingOnZAxis)
|
|
{
|
|
RotateCameraOnZAxis(delta, _targetRotationZAxis, Rotation.ReturningBack);
|
|
}
|
|
|
|
if (_cameraRotation == Rotation.ReturningBack)
|
|
{
|
|
RotateCameraOnZAxis(delta, 0, Rotation.NoRotation);
|
|
}
|
|
}
|
|
|
|
private void HandleDamageOnFall()
|
|
{
|
|
if (_dead) { return;}
|
|
|
|
if (!_characterBody3D.IsOnFloor())
|
|
{
|
|
_currentVelocityYInAir = _characterBody3D.Velocity.Y;
|
|
}
|
|
else
|
|
{
|
|
if (_currentVelocityYInAir < ThresholdVelYForDamage && ThresholdVelYForDamage > 0)
|
|
{
|
|
float hit = Mathf.Remap(_currentVelocityYInAir,
|
|
ThresholdVelYForDamage, ThresholdVelYForDamage - 9.0f,
|
|
MinimalDamageUnit, MaxHealth);
|
|
|
|
GD.Print("Hit damage: ", hit);
|
|
|
|
TakeDamage(hit);
|
|
}
|
|
|
|
_currentVelocityYInAir = 0.0f;
|
|
}
|
|
}
|
|
|
|
private DateTime? _lastHitTime;
|
|
|
|
private void HandleHealthRegeneration(float delta)
|
|
{
|
|
if (_lastHitTime == null || _dead) return;
|
|
|
|
DateTime lastHitTimeConverted = (DateTime)_lastHitTime;
|
|
|
|
double differenceInSeconds = (DateTime.UtcNow - lastHitTimeConverted).TotalSeconds;
|
|
float differenceInSecondsConverted = (float)differenceInSeconds;
|
|
|
|
if (differenceInSecondsConverted < SecondsBeforeRegeneration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Mathf.IsEqualApprox(CurrentHealth, MaxHealth))
|
|
{
|
|
CurrentHealth = MaxHealth;
|
|
_lastHitTime = null;
|
|
return;
|
|
}
|
|
|
|
CurrentHealth += delta * RegenerationSpeed;
|
|
CurrentHealth = Mathf.Clamp(CurrentHealth, 0, MaxHealth);
|
|
}
|
|
}
|