chore,gd: refactored project structure and started the mantle system
This commit is contained in:
9
player_controller/Scripts/AnimationPlayer.cs
Normal file
9
player_controller/Scripts/AnimationPlayer.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class AnimationPlayer : Godot.AnimationPlayer
|
||||
{
|
||||
public void PlayCameraRotationOnDeath()
|
||||
{
|
||||
Play(Constants.PLAYERS_HEAD_ANIMATION_ON_DYING);
|
||||
}
|
||||
}
|
1
player_controller/Scripts/AnimationPlayer.cs.uid
Normal file
1
player_controller/Scripts/AnimationPlayer.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bt8flen3mi28r
|
47
player_controller/Scripts/Bobbing.cs
Normal file
47
player_controller/Scripts/Bobbing.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class Bobbing: Node3D
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
||||
public float BobbingFrequency { set; get; } = 2.4f;
|
||||
[Export(PropertyHint.Range, "0,0.4,0.01,or_greater")]
|
||||
public float BobbingAmplitude { set; get; } = 0.08f;
|
||||
|
||||
private Camera3D _camera;
|
||||
|
||||
public void Init(Camera3D cam)
|
||||
{
|
||||
_camera = cam;
|
||||
}
|
||||
|
||||
public struct CameraBobbingParams
|
||||
{
|
||||
public float Delta;
|
||||
public bool IsOnFloorCustom;
|
||||
public Vector3 Velocity;
|
||||
}
|
||||
|
||||
private float _bobbingAccumulator; // Constantly increases when player moves in X or/and Z axis
|
||||
|
||||
public void PerformCameraBobbing(CameraBobbingParams parameters)
|
||||
{
|
||||
if (parameters.IsOnFloorCustom)
|
||||
{
|
||||
// Head bob
|
||||
_bobbingAccumulator += parameters.Delta * parameters.Velocity.Length();
|
||||
|
||||
Vector3 newPositionForCamera = Vector3.Zero;
|
||||
|
||||
// As the _bobbingAccumulator increases we're changing values for sin and cos functions.
|
||||
// Because both of them are just waves, we will be slide up with y and then slide down with y
|
||||
// creating bobbing effect. The same works for cos. As the _bobbingAccumulator increases the cos decreases and then increases
|
||||
|
||||
newPositionForCamera.Y = Mathf.Sin(_bobbingAccumulator * BobbingFrequency) * BobbingAmplitude;
|
||||
newPositionForCamera.X = Mathf.Cos(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude;
|
||||
|
||||
_camera.Position = newPositionForCamera;
|
||||
}
|
||||
}
|
||||
}
|
1
player_controller/Scripts/Bobbing.cs.uid
Normal file
1
player_controller/Scripts/Bobbing.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://g8idirw62qe0
|
56
player_controller/Scripts/CapsuleCollider.cs
Normal file
56
player_controller/Scripts/CapsuleCollider.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class CapsuleCollider : CollisionShape3D
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,5.0,0.01,suffix:m,or_greater")]
|
||||
public float CapsuleDefaultHeight { get; set; } = 2.0f;
|
||||
[Export(PropertyHint.Range, "0,5.0,0.01,suffix:m,or_greater")]
|
||||
public float CapsuleCrouchHeight { get; set; } = 1.0f;
|
||||
|
||||
public float GetCurrentHeight() { return _shape.Height; }
|
||||
public float GetDefaultHeight() { return CapsuleDefaultHeight; }
|
||||
|
||||
private CapsuleShape3D _shape;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_shape = Shape as CapsuleShape3D;
|
||||
_shape.Height = CapsuleDefaultHeight;
|
||||
}
|
||||
|
||||
public bool IsCapsuleHeightLessThanNormal()
|
||||
{
|
||||
return _shape.Height < CapsuleDefaultHeight;
|
||||
}
|
||||
|
||||
public bool IsBetweenCrouchingAndNormalHeight()
|
||||
{
|
||||
return _shape.Height > CapsuleCrouchHeight && _shape.Height < CapsuleDefaultHeight;
|
||||
}
|
||||
|
||||
public bool IsDefaultHeight()
|
||||
{
|
||||
return Mathf.IsEqualApprox(_shape.Height, CapsuleDefaultHeight);
|
||||
}
|
||||
|
||||
public bool IsCrouchingHeight()
|
||||
{
|
||||
return Mathf.IsEqualApprox(_shape.Height, CapsuleCrouchHeight);
|
||||
}
|
||||
|
||||
public void Crouch(float delta, float crouchTransitionSpeed)
|
||||
{
|
||||
_shape.Height -= delta * crouchTransitionSpeed;
|
||||
|
||||
_shape.Height = Mathf.Clamp(_shape.Height, CapsuleCrouchHeight, CapsuleDefaultHeight);
|
||||
}
|
||||
|
||||
public void UndoCrouching(float delta, float crouchTransitionSpeed)
|
||||
{
|
||||
_shape.Height += delta * crouchTransitionSpeed;
|
||||
|
||||
_shape.Height = Mathf.Clamp(_shape.Height, CapsuleCrouchHeight, CapsuleDefaultHeight);
|
||||
}
|
||||
}
|
1
player_controller/Scripts/CapsuleCollider.cs.uid
Normal file
1
player_controller/Scripts/CapsuleCollider.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dd1yrt7eiiyf4
|
44
player_controller/Scripts/FieldOfView.cs
Normal file
44
player_controller/Scripts/FieldOfView.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class FieldOfView: Node3D
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,180,0.1,degrees")]
|
||||
public float BaseFov { get; set; } = 75.0f;
|
||||
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
||||
public float FovChangeFactor { get; set; } = 1.2f;
|
||||
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
||||
public float FovChangeSpeed { get; set; } = 6.25f;
|
||||
|
||||
private Camera3D _camera;
|
||||
|
||||
public void Init(Camera3D cam)
|
||||
{
|
||||
_camera = cam;
|
||||
}
|
||||
|
||||
public struct FovParameters
|
||||
{
|
||||
public bool IsCrouchingHeight;
|
||||
public float Delta;
|
||||
public float SprintSpeed;
|
||||
public Vector3 Velocity;
|
||||
}
|
||||
|
||||
public void PerformFovAdjustment(FovParameters parameters)
|
||||
{
|
||||
float velocityClamped = Mathf.Clamp(
|
||||
Mathf.Abs(parameters.Velocity.X) + Mathf.Abs(parameters.Velocity.Z),
|
||||
0.5f,
|
||||
parameters.SprintSpeed * 2.0f);
|
||||
|
||||
float targetFov = BaseFov + FovChangeFactor * velocityClamped;
|
||||
|
||||
if (parameters.IsCrouchingHeight){
|
||||
targetFov = BaseFov - FovChangeFactor * velocityClamped;
|
||||
}
|
||||
|
||||
_camera.Fov = Mathf.Lerp(_camera.Fov, targetFov, parameters.Delta * FovChangeSpeed);
|
||||
}
|
||||
}
|
1
player_controller/Scripts/FieldOfView.cs.uid
Normal file
1
player_controller/Scripts/FieldOfView.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b6k73aj5povgv
|
25
player_controller/Scripts/Global.cs
Normal file
25
player_controller/Scripts/Global.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public class Constants
|
||||
{
|
||||
// Shaders' parameters
|
||||
public const string DISTORTION_SHADER_SCREEN_DARKNESS = "screen_darkness";
|
||||
public const string DISTORTION_SHADER_DARKNESS_PROGRESSION = "darkness_progression";
|
||||
public const string DISTORTION_SHADER_UV_OFFSET = "uv_offset";
|
||||
public const string DISTORTION_SHADER_SIZE = "size";
|
||||
|
||||
public const string VIGNETTE_SHADER_MULTIPLIER = "multiplier";
|
||||
public const string VIGNETTE_SHADER_SOFTNESS = "softness";
|
||||
|
||||
public const string BLUR_SHADER_LIMIT = "limit";
|
||||
public const string BLUR_SHADER_BLUR = "blur";
|
||||
|
||||
// Animation
|
||||
public const string PLAYERS_HEAD_ANIMATION_ON_DYING = "players_head_on_dying";
|
||||
|
||||
// Math
|
||||
public const float ACCEPTABLE_TOLERANCE = 0.01f;
|
||||
}
|
||||
|
1
player_controller/Scripts/Global.cs.uid
Normal file
1
player_controller/Scripts/Global.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://yrcg34scpt5k
|
23
player_controller/Scripts/Gravity.cs
Normal file
23
player_controller/Scripts/Gravity.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class Gravity: Node3D
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
|
||||
public float Weight { get; set; } = 70.0f;
|
||||
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
|
||||
public float StartVelocity { get; set; } = 3.0f;
|
||||
[Export(PropertyHint.Range, "0.01,10,0.01,or_greater")]
|
||||
public float AdditionalGravityPower { get; set; } = 2f;
|
||||
|
||||
private float _gravity;
|
||||
|
||||
public void Init(float gravitySetting)
|
||||
{
|
||||
_gravity = gravitySetting;
|
||||
}
|
||||
|
||||
public float CalculateJumpForce() => Weight * (_gravity * (StartVelocity / AdditionalGravityPower));
|
||||
public float CalculateGravityForce() => _gravity * Weight / 30.0f;
|
||||
}
|
1
player_controller/Scripts/Gravity.cs.uid
Normal file
1
player_controller/Scripts/Gravity.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bt0xv2q8iv1vn
|
485
player_controller/Scripts/HealthSystem.cs
Normal file
485
player_controller/Scripts/HealthSystem.cs
Normal file
@ -0,0 +1,485 @@
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
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 Gravity _gravity;
|
||||
|
||||
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 AnimationPlayer _animationPlayer;
|
||||
private ShaderMaterial _blurMaterial;
|
||||
|
||||
public struct HealthSystemInitParams
|
||||
{
|
||||
public Gravity Gravity;
|
||||
public CharacterBody3D Parent;
|
||||
public Camera3D Camera;
|
||||
public AnimationPlayer AnimationPlayer;
|
||||
public Node3D Head;
|
||||
public ColorRect VignetteRect;
|
||||
public ColorRect DistortionRect;
|
||||
public ColorRect BlurRect;
|
||||
}
|
||||
|
||||
public void Init(HealthSystemInitParams initParams)
|
||||
{
|
||||
_currentHealthInPrevFrame = CurrentHealth;
|
||||
_currentMultiplierMidValue = InitialMultiplierMidVal;
|
||||
|
||||
_currentSpeed = SpeedMin;
|
||||
|
||||
_gravity = initParams.Gravity;
|
||||
_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);
|
||||
|
||||
_animationPlayer = initParams.AnimationPlayer;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_animationPlayer.PlayCameraRotationOnDeath();
|
||||
_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);
|
||||
}
|
||||
}
|
1
player_controller/Scripts/HealthSystem.cs.uid
Normal file
1
player_controller/Scripts/HealthSystem.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dv7v1ywmbvvcd
|
23
player_controller/Scripts/MantleSystem.cs
Normal file
23
player_controller/Scripts/MantleSystem.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class MantleSystem: Node3D
|
||||
{
|
||||
private RayCast3D _wallInFrontRaycast3D;
|
||||
|
||||
public void Init(RayCast3D wallInFrontRaycast3D)
|
||||
{
|
||||
_wallInFrontRaycast3D = wallInFrontRaycast3D;
|
||||
}
|
||||
|
||||
public void CheckWallInFront()
|
||||
{
|
||||
var collider = _wallInFrontRaycast3D.GetCollider();
|
||||
if (collider != null)
|
||||
{
|
||||
GD.Print(_wallInFrontRaycast3D.GetCollisionPoint());
|
||||
GD.Print(_wallInFrontRaycast3D.GetCollisionNormal());
|
||||
}
|
||||
}
|
||||
}
|
1
player_controller/Scripts/MantleSystem.cs.uid
Normal file
1
player_controller/Scripts/MantleSystem.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bja6tis1vaysu
|
48
player_controller/Scripts/Mouse.cs
Normal file
48
player_controller/Scripts/Mouse.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class Mouse : Node3D
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,0.1,0.001,or_greater")]
|
||||
public float Sensitivity { get; set; } = 0.004f;
|
||||
|
||||
private Node3D _head;
|
||||
private Camera3D _camera;
|
||||
|
||||
public delegate bool IsDead();
|
||||
|
||||
private IsDead _isPlayerDead;
|
||||
|
||||
public void Init(Node3D head, Camera3D cam, IsDead isDeadFunc)
|
||||
{
|
||||
Input.SetMouseMode(Input.MouseModeEnum.Captured);
|
||||
|
||||
_head = head;
|
||||
_camera = cam;
|
||||
_isPlayerDead = isDeadFunc;
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (_isPlayerDead())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (@event is InputEventMouseMotion eventMouseMotion)
|
||||
{
|
||||
// Horizontal movement of head
|
||||
float angleForHorizontalRotation = -eventMouseMotion.Relative.X * Sensitivity;
|
||||
_head.RotateY(angleForHorizontalRotation);
|
||||
|
||||
// Vertical movement of head
|
||||
Vector3 currentCameraRotation = _camera.Rotation;
|
||||
currentCameraRotation.X += Convert.ToSingle(-eventMouseMotion.Relative.Y * Sensitivity);
|
||||
currentCameraRotation.X = Mathf.Clamp(currentCameraRotation.X, Mathf.DegToRad(-90f), Mathf.DegToRad(90f));
|
||||
|
||||
_camera.Rotation = currentCameraRotation;
|
||||
}
|
||||
}
|
||||
}
|
1
player_controller/Scripts/Mouse.cs.uid
Normal file
1
player_controller/Scripts/Mouse.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c6bx47wr7fbdm
|
355
player_controller/Scripts/PlayerController.cs
Normal file
355
player_controller/Scripts/PlayerController.cs
Normal file
@ -0,0 +1,355 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class PlayerController : CharacterBody3D
|
||||
{
|
||||
// User API to important child nodes.
|
||||
public Node3D Head;
|
||||
public Bobbing Bobbing;
|
||||
public FieldOfView FieldOfView;
|
||||
public Stamina Stamina;
|
||||
public StairsSystem StairsSystem;
|
||||
public MantleSystem MantleSystem;
|
||||
public CapsuleCollider CapsuleCollider;
|
||||
public Gravity Gravity;
|
||||
public HealthSystem HealthSystem;
|
||||
public Mouse Mouse;
|
||||
|
||||
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
|
||||
public float WalkSpeed { get; set; } = 5.0f;
|
||||
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
|
||||
public float SprintSpeed { get; set; } = 7.2f;
|
||||
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
|
||||
public float CrouchSpeed { get; set; } = 2.5f;
|
||||
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
|
||||
public float CrouchTransitionSpeed { get; set; } = 20.0f;
|
||||
|
||||
[Export(PropertyHint.Range, "0,5,0.1,or_greater")]
|
||||
public float DoubleJumpSpeedFactor { get; set; } = 2f;
|
||||
|
||||
private bool _canDoubleJump = true;
|
||||
|
||||
private float _currentSpeed;
|
||||
|
||||
private const float DecelerationSpeedFactorFloor = 15.0f;
|
||||
private const float DecelerationSpeedFactorAir = 7.0f;
|
||||
|
||||
private float _lastFrameWasOnFloor = -Mathf.Inf;
|
||||
|
||||
private const int NumOfHeadCollisionDetectors = 4;
|
||||
private RayCast3D[] _headCollisionDetectors;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_currentSpeed = WalkSpeed;
|
||||
|
||||
Head = GetNode<Node3D>("Head");
|
||||
|
||||
_headCollisionDetectors = new RayCast3D[NumOfHeadCollisionDetectors];
|
||||
|
||||
for (int i = 0; i < NumOfHeadCollisionDetectors; i++)
|
||||
{
|
||||
_headCollisionDetectors[i] = GetNode<RayCast3D>(
|
||||
"HeadCollisionDetectors/HeadCollisionDetector" + i);
|
||||
}
|
||||
|
||||
// Getting dependencies of the components(In godot we manage this from upwards to downwards not vice versa)
|
||||
Camera3D camera = GetNode<Camera3D>("Head/CameraSmooth/Camera3D");
|
||||
|
||||
RayCast3D stairsBelowRayCast3D = GetNode<RayCast3D>("StairsBelowRayCast3D");
|
||||
RayCast3D stairsAheadRayCast3D = GetNode<RayCast3D>("StairsAheadRayCast3D");
|
||||
RayCast3D wallInFrontRaycast3D = GetNode<RayCast3D>("WallInFrontRayCast3D");
|
||||
|
||||
Node3D cameraSmooth = GetNode<Node3D>("Head/CameraSmooth");
|
||||
|
||||
AnimationPlayer animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
|
||||
|
||||
// Getting universal setting from GODOT editor to be in sync
|
||||
float gravitySetting = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");
|
||||
|
||||
ColorRect vignetteRect = GetNode<ColorRect>(
|
||||
"Head/CameraSmooth/Camera3D/CLVignette(Layer_1)/HealthVignetteRect");
|
||||
|
||||
ColorRect distortionRect = GetNode<ColorRect>(
|
||||
"Head/CameraSmooth/Camera3D/CLDistortion(Layer_2)/HealthDistortionRect");
|
||||
|
||||
ColorRect blurRect = GetNode<ColorRect>("Head/CameraSmooth/Camera3D/CLBlur(Layer_2)/BlurRect");
|
||||
|
||||
Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D;
|
||||
|
||||
// Getting components
|
||||
|
||||
Bobbing = GetNode<Bobbing>("Bobbing");
|
||||
Bobbing.Init(camera);
|
||||
|
||||
FieldOfView = GetNode<FieldOfView>("FieldOfView");
|
||||
FieldOfView.Init(camera);
|
||||
|
||||
Stamina = GetNode<Stamina>("Stamina");
|
||||
Stamina.SetSpeeds(WalkSpeed, SprintSpeed);
|
||||
|
||||
StairsSystem = GetNode<StairsSystem>("StairsSystem");
|
||||
StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth);
|
||||
|
||||
MantleSystem = GetNode<MantleSystem>("MantleSystem");
|
||||
MantleSystem.Init(wallInFrontRaycast3D);
|
||||
|
||||
CapsuleCollider = GetNode<CapsuleCollider>("CapsuleCollider");
|
||||
|
||||
Gravity = GetNode<Gravity>("Gravity");
|
||||
Gravity.Init(gravitySetting);
|
||||
|
||||
HealthSystem = GetNode<HealthSystem>("HealthSystem");
|
||||
|
||||
HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams()
|
||||
{
|
||||
Gravity = Gravity,
|
||||
Parent = this,
|
||||
Camera = camera,
|
||||
AnimationPlayer = animationPlayer,
|
||||
Head = Head,
|
||||
VignetteRect = vignetteRect,
|
||||
DistortionRect = distortionRect,
|
||||
BlurRect = blurRect,
|
||||
};
|
||||
|
||||
HealthSystem.Init(healthSystemParams);
|
||||
|
||||
Mouse = GetNode<Mouse>("Mouse");
|
||||
Mouse.Init(Head, camera, HealthSystem.IsDead);
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
MantleSystem.CheckWallInFront();
|
||||
|
||||
if (isOnFloorCustom())
|
||||
{
|
||||
_lastFrameWasOnFloor = Engine.GetPhysicsFrames();
|
||||
_canDoubleJump = true;
|
||||
}
|
||||
|
||||
// Adding the gravity
|
||||
if (!isOnFloorCustom())
|
||||
{
|
||||
Velocity = new Vector3(
|
||||
x: Velocity.X,
|
||||
y: Velocity.Y - (Gravity.CalculateGravityForce() * (float)delta),
|
||||
z: Velocity.Z);
|
||||
}
|
||||
|
||||
bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight();
|
||||
|
||||
bool isPlayerDead = HealthSystem.IsDead();
|
||||
|
||||
// Handle Jumping
|
||||
if (Input.IsActionJustPressed("jump") && isOnFloorCustom()
|
||||
&& !doesCapsuleHaveCrouchingHeight && !isPlayerDead)
|
||||
{
|
||||
Velocity = new Vector3(
|
||||
x: Velocity.X,
|
||||
y: Gravity.CalculateJumpForce() * (float)delta,
|
||||
z: Velocity.Z);
|
||||
}
|
||||
else if (Input.IsActionJustPressed("jump") && !isOnFloorCustom()
|
||||
&& !doesCapsuleHaveCrouchingHeight && !isPlayerDead && _canDoubleJump)
|
||||
{
|
||||
_canDoubleJump = false;
|
||||
Velocity = new Vector3(
|
||||
x: Velocity.X,
|
||||
y: Gravity.CalculateJumpForce() * (float)delta * DoubleJumpSpeedFactor,
|
||||
z: Velocity.Z);
|
||||
}
|
||||
|
||||
bool isHeadTouchingCeiling = IsHeadTouchingCeiling();
|
||||
bool doesCapsuleHaveDefaultHeight = CapsuleCollider.IsDefaultHeight();
|
||||
|
||||
// The code below is required to quickly adjust player's position on Y-axis when there's a ceiling on the
|
||||
// trajectory of player's jump and player is standing
|
||||
if (isHeadTouchingCeiling && doesCapsuleHaveDefaultHeight)
|
||||
{
|
||||
Velocity = new Vector3(
|
||||
x: Velocity.X,
|
||||
y: Velocity.Y - 2.0f,
|
||||
z: Velocity.Z);
|
||||
}
|
||||
|
||||
if (!isPlayerDead)
|
||||
{
|
||||
|
||||
// Used both for detecting the moment when we enter into crouching mode and the moment when we're already
|
||||
// in the crouching mode
|
||||
if (Input.IsActionPressed("crouch") ||
|
||||
(doesCapsuleHaveCrouchingHeight && isHeadTouchingCeiling))
|
||||
{
|
||||
CapsuleCollider.Crouch((float)delta, CrouchTransitionSpeed);
|
||||
_currentSpeed = CrouchSpeed;
|
||||
}
|
||||
// Used both for the moment when we exit the crouching mode and for the moment when we just walk
|
||||
else
|
||||
{
|
||||
CapsuleCollider.UndoCrouching((float)delta, CrouchTransitionSpeed);
|
||||
_currentSpeed = WalkSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
// Each component of the boolean statement for sprinting is required
|
||||
if (Input.IsActionPressed("sprint") && !isHeadTouchingCeiling &&
|
||||
!doesCapsuleHaveCrouchingHeight && !isPlayerDead)
|
||||
{
|
||||
_currentSpeed = SprintSpeed;
|
||||
}
|
||||
|
||||
// Get the input direction
|
||||
Vector2 inputDir = Input.GetVector("left", "right", "up", "down");
|
||||
|
||||
// Basis is a 3x4 matrix. It contains information about scaling and rotation of head.
|
||||
// By multiplying our Vector3 by this matrix we're doing multiple things:
|
||||
// a) We start to operate in global space;
|
||||
// b) We're applying to Vector3 the current rotation of "head" object;
|
||||
// c) We're applying to Vector3 the current scaling of "head" object;
|
||||
Vector3 direction = (Head.Transform.Basis * new Vector3(inputDir.X, 0, inputDir.Y)).Normalized();
|
||||
|
||||
if (isPlayerDead)
|
||||
{
|
||||
direction = Vector3.Zero;
|
||||
}
|
||||
|
||||
if (isOnFloorCustom())
|
||||
{
|
||||
// Set velocity based on input direction when on the floor
|
||||
if (direction.Length() > 0)
|
||||
{
|
||||
float availableSpeed = Stamina.AccountStamina(delta, _currentSpeed);
|
||||
|
||||
float newX = direction.X * availableSpeed;
|
||||
float newZ = direction.Z * availableSpeed;
|
||||
|
||||
Velocity = new Vector3(newX, Velocity.Y, newZ);
|
||||
}
|
||||
// If there is no input, smoothly decelerate the character on the floor
|
||||
else
|
||||
{
|
||||
float xDeceleration = Mathf.Lerp(Velocity.X, direction.X * _currentSpeed,
|
||||
(float)delta * DecelerationSpeedFactorFloor);
|
||||
float zDeceleration = Mathf.Lerp(Velocity.Z, direction.Z * _currentSpeed,
|
||||
(float)delta * DecelerationSpeedFactorFloor);
|
||||
|
||||
Velocity = new Vector3(xDeceleration, Velocity.Y, zDeceleration);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float xDeceleration = Mathf.Lerp(Velocity.X, direction.X * _currentSpeed,
|
||||
(float)delta * DecelerationSpeedFactorAir);
|
||||
float zDeceleration = Mathf.Lerp(Velocity.Z, direction.Z * _currentSpeed,
|
||||
(float)delta * DecelerationSpeedFactorAir);
|
||||
|
||||
Velocity = new Vector3(xDeceleration, Velocity.Y, zDeceleration);
|
||||
}
|
||||
|
||||
if (isPlayerDead)
|
||||
{
|
||||
MoveAndSlide();
|
||||
return;
|
||||
}
|
||||
|
||||
Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams
|
||||
{
|
||||
Delta = (float)delta,
|
||||
IsOnFloorCustom = isOnFloorCustom(),
|
||||
Velocity = Velocity
|
||||
};
|
||||
|
||||
Bobbing.PerformCameraBobbing(cameraBobbingParams);
|
||||
|
||||
FieldOfView.FovParameters fovParams = new FieldOfView.FovParameters
|
||||
{
|
||||
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
|
||||
Delta = (float)delta,
|
||||
SprintSpeed = SprintSpeed,
|
||||
Velocity = Velocity
|
||||
};
|
||||
|
||||
FieldOfView.PerformFovAdjustment(fovParams);
|
||||
|
||||
StairsSystem.UpStairsCheckParams upStairsCheckParams = new StairsSystem.UpStairsCheckParams
|
||||
{
|
||||
IsOnFloorCustom = isOnFloorCustom(),
|
||||
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
|
||||
CurrentSpeedGreaterThanWalkSpeed = _currentSpeed > WalkSpeed,
|
||||
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
|
||||
Delta = (float)delta,
|
||||
FloorMaxAngle = FloorMaxAngle,
|
||||
GlobalPositionFromDriver = GlobalPosition,
|
||||
Velocity = Velocity,
|
||||
GlobalTransformFromDriver = GlobalTransform,
|
||||
Rid = GetRid()
|
||||
};
|
||||
|
||||
// TODO: SnapUpStairsCheck influences the ability of player to crouch because of `stepHeightY <= 0.01` part
|
||||
// Ideally, it should not. SnapUpStairsCheck and SnapDownStairsCheck should be called, when player is actually
|
||||
// on the stairs
|
||||
|
||||
StairsSystem.UpStairsCheckResult upStairsCheckResult = StairsSystem.SnapUpStairsCheck(upStairsCheckParams);
|
||||
|
||||
if (upStairsCheckResult.UpdateRequired)
|
||||
{
|
||||
upStairsCheckResult.Update(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
MoveAndSlide();
|
||||
|
||||
StairsSystem.DownStairsCheckParams downStairsCheckParams = new StairsSystem.DownStairsCheckParams
|
||||
{
|
||||
IsOnFloor = IsOnFloor(), // TODO: replace on IsOnFloor Custom
|
||||
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
|
||||
LastFrameWasOnFloor = _lastFrameWasOnFloor,
|
||||
CapsuleDefaultHeight = CapsuleCollider.GetDefaultHeight(),
|
||||
CurrentCapsuleHeight = CapsuleCollider.GetCurrentHeight(),
|
||||
FloorMaxAngle = FloorMaxAngle,
|
||||
VelocityY = Velocity.Y,
|
||||
GlobalTransformFromDriver = GlobalTransform,
|
||||
Rid = GetRid()
|
||||
};
|
||||
|
||||
StairsSystem.DownStairsCheckResult downStairsCheckResult = StairsSystem.SnapDownStairsCheck(
|
||||
downStairsCheckParams);
|
||||
|
||||
if (downStairsCheckResult.UpdateIsRequired)
|
||||
{
|
||||
downStairsCheckResult.Update(this);
|
||||
}
|
||||
}
|
||||
|
||||
StairsSystem.SlideCameraParams slideCameraParams = new StairsSystem.SlideCameraParams
|
||||
{
|
||||
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
|
||||
CurrentSpeedGreaterThanWalkSpeed = _currentSpeed > WalkSpeed,
|
||||
BetweenCrouchingAndNormalHeight = CapsuleCollider.IsBetweenCrouchingAndNormalHeight(),
|
||||
Delta = (float)delta
|
||||
};
|
||||
|
||||
StairsSystem.SlideCameraSmoothBackToOrigin(slideCameraParams);
|
||||
}
|
||||
|
||||
private bool IsHeadTouchingCeiling()
|
||||
{
|
||||
for (int i = 0; i < NumOfHeadCollisionDetectors; i++)
|
||||
{
|
||||
if (_headCollisionDetectors[i].IsColliding())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool isOnFloorCustom()
|
||||
{
|
||||
return IsOnFloor() || StairsSystem.WasSnappedToStairsLastFrame();
|
||||
}
|
||||
}
|
1
player_controller/Scripts/PlayerController.cs.uid
Normal file
1
player_controller/Scripts/PlayerController.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bbbrf5ckydfna
|
352
player_controller/Scripts/StairsSystem.cs
Normal file
352
player_controller/Scripts/StairsSystem.cs
Normal file
@ -0,0 +1,352 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class StairsSystem: Node3D
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,10,0.01,suffix:m,or_greater")]
|
||||
public float MaxStepHeight = 0.5f;
|
||||
|
||||
private RayCast3D _stairsBelowRayCast3D;
|
||||
private RayCast3D _stairsAheadRayCast3D;
|
||||
|
||||
private Node3D _cameraSmooth;
|
||||
|
||||
private bool _snappedToStairsLastFrame;
|
||||
|
||||
private Vector3? _savedCameraGlobalPos;
|
||||
|
||||
|
||||
|
||||
public void Init(RayCast3D stairsBelowRayCast3D, RayCast3D stairsAheadRayCast3D, Node3D cameraSmooth)
|
||||
{
|
||||
_stairsBelowRayCast3D = stairsBelowRayCast3D;
|
||||
_stairsAheadRayCast3D = stairsAheadRayCast3D;
|
||||
_cameraSmooth = cameraSmooth;
|
||||
|
||||
}
|
||||
|
||||
public bool WasSnappedToStairsLastFrame() { return _snappedToStairsLastFrame; }
|
||||
|
||||
private bool RunBodyTestMotion(
|
||||
Transform3D from, Vector3 motion, Rid rid, out PhysicsTestMotionResult3D resultOut)
|
||||
{
|
||||
PhysicsTestMotionResult3D result = new PhysicsTestMotionResult3D();
|
||||
|
||||
resultOut = result;
|
||||
|
||||
PhysicsTestMotionParameters3D parameters = new PhysicsTestMotionParameters3D
|
||||
{
|
||||
From = from,
|
||||
Motion = motion,
|
||||
Margin = 0.00001f
|
||||
};
|
||||
|
||||
return PhysicsServer3D.BodyTestMotion(rid, parameters, result);
|
||||
}
|
||||
|
||||
|
||||
public struct UpStairsCheckParams
|
||||
{
|
||||
public bool IsOnFloorCustom;
|
||||
public bool IsCapsuleHeightLessThanNormal;
|
||||
public bool CurrentSpeedGreaterThanWalkSpeed;
|
||||
public bool IsCrouchingHeight;
|
||||
public float Delta;
|
||||
public float FloorMaxAngle;
|
||||
public Vector3 GlobalPositionFromDriver;
|
||||
public Vector3 Velocity;
|
||||
public Transform3D GlobalTransformFromDriver;
|
||||
public Rid Rid;
|
||||
}
|
||||
|
||||
public delegate void UpdateAfterUpStairsCheck(CharacterBody3D cb3D);
|
||||
public struct UpStairsCheckResult
|
||||
{
|
||||
public bool UpdateRequired;
|
||||
public UpdateAfterUpStairsCheck Update;
|
||||
}
|
||||
|
||||
public UpStairsCheckResult SnapUpStairsCheck(UpStairsCheckParams parameters)
|
||||
{
|
||||
UpStairsCheckResult updateIsNotRequired = new UpStairsCheckResult
|
||||
{
|
||||
UpdateRequired = false,
|
||||
Update = (CharacterBody3D cb3D) => { }
|
||||
};
|
||||
|
||||
if (!parameters.IsOnFloorCustom) { return updateIsNotRequired; }
|
||||
|
||||
// Different velocity multipliers are set for different situations because we alter player's speed
|
||||
// depending on those situations. For example, while crouching, player is moving slower, so we should account
|
||||
// for this while running body test motion, as the velocity of the player becomes lower. When sprinting,
|
||||
// player will have higher velocity and we can compensate it by setting lower velocity multiplier.
|
||||
|
||||
float motionVelocityMultiplier;
|
||||
|
||||
if (parameters.IsCapsuleHeightLessThanNormal)
|
||||
{
|
||||
motionVelocityMultiplier = 1.55f; // Going to crouch mode
|
||||
}else if (parameters.CurrentSpeedGreaterThanWalkSpeed)
|
||||
{
|
||||
motionVelocityMultiplier = 1.1f; // Sprinting
|
||||
}
|
||||
else
|
||||
{
|
||||
motionVelocityMultiplier = 1.4f; // Walking
|
||||
}
|
||||
|
||||
Vector3 expectedMoveMotion = parameters.Velocity *
|
||||
new Vector3(motionVelocityMultiplier, 0.0f, motionVelocityMultiplier) *
|
||||
parameters.Delta;
|
||||
|
||||
Vector3 offset = expectedMoveMotion + new Vector3(0, MaxStepHeight * 2.0f, 0);
|
||||
|
||||
Transform3D stepPosWithClearance = parameters.GlobalTransformFromDriver.Translated(offset);
|
||||
|
||||
Vector3 motion = new Vector3(0, -MaxStepHeight * 2.0f, 0);
|
||||
bool doesProjectionCollide = RunBodyTestMotion(
|
||||
stepPosWithClearance, motion, parameters.Rid, out var downCheckResult);
|
||||
|
||||
if (doesProjectionCollide)
|
||||
{
|
||||
GodotObject collider = downCheckResult.GetCollider();
|
||||
|
||||
if (!collider.IsClass("StaticBody3D") && !collider.IsClass("CSGShape3D"))
|
||||
{
|
||||
return updateIsNotRequired;
|
||||
}
|
||||
|
||||
// We add 0.5 because when player is crouching, his height is less than normal by the factor of 2:
|
||||
// so, 2 meters / 2 = 1 meter (Crouching height). In Godot, this is achieved by subtracting 0.5 from the
|
||||
// top and the bottom of capsule collider shape. As a result, GlobalPosition goes under the ground by 0.5
|
||||
// because capsule shape collider was cut off by 0.5 from the bottom, and GlobalPosition does not give a
|
||||
// a damn about physics (it's not the GlobalPosition of capsule shape, it's just the global coordinate of a
|
||||
// point in space). So, MaxHeight is 0.5 - 0.5(GlobalPosition that went under the ground) = 0. It means that
|
||||
// MaxStepHeight for the player while he is crouching is 0, so he can't overcome obstacles while crouching.
|
||||
// In order to address this problem we should add some offset to 0. Basically, the average height of
|
||||
// stairs is 0.23. But, also, we want to climb automatically to obstacles that are 50cm height. So, we add
|
||||
// 0.5 offset. And it means, that we balanced the max step height while crouching: when capsule height is
|
||||
// normal then max step height is 0.5, when player is crouching, then max step height is also 0.5.
|
||||
|
||||
float maxStepHeightAdjusted = parameters.IsCrouchingHeight ? MaxStepHeight + 0.5f : MaxStepHeight;
|
||||
|
||||
Vector3 stepHeight = stepPosWithClearance.Origin + downCheckResult.GetTravel() - parameters.GlobalPositionFromDriver;
|
||||
float stepHeightYToTravelEnd = stepHeight.Y;
|
||||
|
||||
float realStepHeightY = (downCheckResult.GetCollisionPoint() - parameters.GlobalPositionFromDriver).Y;
|
||||
|
||||
if (stepHeightYToTravelEnd <= 0.01 || realStepHeightY > maxStepHeightAdjusted)
|
||||
{
|
||||
return updateIsNotRequired;
|
||||
}
|
||||
|
||||
_stairsAheadRayCast3D.GlobalPosition = downCheckResult.GetCollisionPoint() + new Vector3(
|
||||
0, MaxStepHeight, 0) + expectedMoveMotion.Normalized() * 0.1f;
|
||||
|
||||
_stairsAheadRayCast3D.ForceRaycastUpdate();
|
||||
|
||||
// It's needed in order to deny too steep angles of climbing. For hills. And hills-like bumps
|
||||
// For casual stairs it's, of course, will pass
|
||||
if (!IsSurfaceTooSteep(
|
||||
_stairsAheadRayCast3D.GetCollisionNormal(), parameters.FloorMaxAngle))
|
||||
{
|
||||
|
||||
return new UpStairsCheckResult
|
||||
{
|
||||
UpdateRequired = true,
|
||||
Update = (CharacterBody3D cb3D) =>
|
||||
{
|
||||
SaveCameraGlobalPosForSmoothing();
|
||||
|
||||
cb3D.GlobalPosition = stepPosWithClearance.Origin + downCheckResult.GetTravel();
|
||||
cb3D.ApplyFloorSnap();
|
||||
|
||||
_snappedToStairsLastFrame = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return updateIsNotRequired;
|
||||
}
|
||||
|
||||
public struct DownStairsCheckParams
|
||||
{
|
||||
public bool IsOnFloor;
|
||||
public bool IsCrouchingHeight;
|
||||
public float LastFrameWasOnFloor;
|
||||
public float CapsuleDefaultHeight;
|
||||
public float CurrentCapsuleHeight;
|
||||
public float FloorMaxAngle;
|
||||
public float VelocityY;
|
||||
public Transform3D GlobalTransformFromDriver;
|
||||
public Rid Rid;
|
||||
}
|
||||
|
||||
public delegate void UpdateAfterDownStairsCheck(CharacterBody3D cb3D);
|
||||
|
||||
public struct DownStairsCheckResult
|
||||
{
|
||||
public bool UpdateIsRequired;
|
||||
public UpdateAfterDownStairsCheck Update;
|
||||
|
||||
}
|
||||
|
||||
public DownStairsCheckResult SnapDownStairsCheck(DownStairsCheckParams parameters)
|
||||
{
|
||||
bool didSnap = false;
|
||||
|
||||
if (parameters.IsCrouchingHeight)
|
||||
{
|
||||
float yCoordAdjustment = (parameters.CapsuleDefaultHeight - parameters.CurrentCapsuleHeight) / 2.0f;
|
||||
_stairsBelowRayCast3D.Position = new Vector3(0f, yCoordAdjustment, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_stairsBelowRayCast3D.Position = new Vector3(0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
_stairsBelowRayCast3D.ForceRaycastUpdate();
|
||||
|
||||
bool floorBelow = _stairsBelowRayCast3D.IsColliding() && !IsSurfaceTooSteep(
|
||||
_stairsBelowRayCast3D.GetCollisionNormal(), parameters.FloorMaxAngle);
|
||||
|
||||
float differenceInPhysicalFrames = Engine.GetPhysicsFrames() - parameters.LastFrameWasOnFloor;
|
||||
|
||||
bool wasOnFloorLastFrame = Mathf.IsEqualApprox(differenceInPhysicalFrames, 1.0f);
|
||||
|
||||
PhysicsTestMotionResult3D bodyTestResult = new PhysicsTestMotionResult3D();
|
||||
|
||||
if (!parameters.IsOnFloor && parameters.VelocityY <= 0 &&
|
||||
(wasOnFloorLastFrame || _snappedToStairsLastFrame) && floorBelow)
|
||||
{
|
||||
Vector3 motion = new Vector3(0, -MaxStepHeight, 0);
|
||||
|
||||
bool doesProjectionCollide = RunBodyTestMotion(
|
||||
parameters.GlobalTransformFromDriver, motion, parameters.Rid, out bodyTestResult);
|
||||
|
||||
if (doesProjectionCollide)
|
||||
{
|
||||
didSnap = true;
|
||||
}
|
||||
}
|
||||
|
||||
_snappedToStairsLastFrame = didSnap;
|
||||
|
||||
if (_snappedToStairsLastFrame)
|
||||
{
|
||||
return new DownStairsCheckResult
|
||||
{
|
||||
UpdateIsRequired = true,
|
||||
Update = (CharacterBody3D cb3D) =>
|
||||
{
|
||||
SaveCameraGlobalPosForSmoothing();
|
||||
|
||||
float yDelta = bodyTestResult.GetTravel().Y;
|
||||
|
||||
Vector3 positionForModification = cb3D.Position;
|
||||
|
||||
positionForModification.Y += yDelta;
|
||||
|
||||
cb3D.Position = positionForModification;
|
||||
|
||||
cb3D.ApplyFloorSnap();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new DownStairsCheckResult
|
||||
{
|
||||
UpdateIsRequired = false,
|
||||
Update = (CharacterBody3D cb3D) => { }
|
||||
};
|
||||
}
|
||||
|
||||
private void SaveCameraGlobalPosForSmoothing()
|
||||
{
|
||||
if (_savedCameraGlobalPos == null)
|
||||
{
|
||||
_savedCameraGlobalPos = _cameraSmooth.GlobalPosition;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct SlideCameraParams
|
||||
{
|
||||
public bool IsCapsuleHeightLessThanNormal;
|
||||
public bool CurrentSpeedGreaterThanWalkSpeed;
|
||||
public bool BetweenCrouchingAndNormalHeight;
|
||||
public float Delta;
|
||||
}
|
||||
|
||||
private const float CrouchingLerpingWeight = 15;
|
||||
private const float WalkingLerpingWeight = 30;
|
||||
private const float SprintingLerpingWeight = 75;
|
||||
|
||||
private const float DefaultLerpingWeight = 100;
|
||||
|
||||
private float _lerpingWeight = DefaultLerpingWeight;
|
||||
|
||||
private const float MaxCameraDelayDistance = 0.25f;
|
||||
|
||||
public void SlideCameraSmoothBackToOrigin(SlideCameraParams parameters)
|
||||
{
|
||||
if (_savedCameraGlobalPos == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 savedCameraGlobalPosConverted = (Vector3)_savedCameraGlobalPos;
|
||||
|
||||
Vector3 globalPositionForModification = _cameraSmooth.GlobalPosition;
|
||||
globalPositionForModification.Y = savedCameraGlobalPosConverted.Y;
|
||||
_cameraSmooth.GlobalPosition = globalPositionForModification;
|
||||
|
||||
Vector3 positionForModification = _cameraSmooth.Position;
|
||||
positionForModification.Y = Mathf.Clamp(
|
||||
_cameraSmooth.Position.Y, -MaxCameraDelayDistance, MaxCameraDelayDistance);
|
||||
_cameraSmooth.Position = positionForModification;
|
||||
|
||||
|
||||
|
||||
if (parameters.IsCapsuleHeightLessThanNormal)
|
||||
{
|
||||
_lerpingWeight = CrouchingLerpingWeight;
|
||||
}else
|
||||
{
|
||||
if (parameters.CurrentSpeedGreaterThanWalkSpeed)
|
||||
{
|
||||
_lerpingWeight = SprintingLerpingWeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lerpingWeight = WalkingLerpingWeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth control, to smoothly go to crouching mode on stairs (if capsule height has default height initially)
|
||||
if (parameters.BetweenCrouchingAndNormalHeight)
|
||||
{
|
||||
_lerpingWeight = 150;
|
||||
positionForModification.Y = 0.05f;
|
||||
}
|
||||
|
||||
positionForModification.Y = Mathf.Lerp(
|
||||
_cameraSmooth.Position.Y, 0.0f, _lerpingWeight * parameters.Delta);
|
||||
|
||||
_cameraSmooth.Position = positionForModification;
|
||||
|
||||
_savedCameraGlobalPos = _cameraSmooth.GlobalPosition;
|
||||
|
||||
if (_cameraSmooth.Position.Y == 0)
|
||||
{
|
||||
_savedCameraGlobalPos = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSurfaceTooSteep(Vector3 normal, float floorMaxAngle)
|
||||
{
|
||||
return normal.AngleTo(Vector3.Up) > floorMaxAngle;
|
||||
}
|
||||
}
|
1
player_controller/Scripts/StairsSystem.cs.uid
Normal file
1
player_controller/Scripts/StairsSystem.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cwbvxlfvmocc1
|
40
player_controller/Scripts/Stamina.cs
Normal file
40
player_controller/Scripts/Stamina.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Godot;
|
||||
|
||||
namespace PolarBears.PlayerControllerAddon;
|
||||
|
||||
public partial class Stamina : Node
|
||||
{
|
||||
[Export(PropertyHint.Range, "0,60,0.1,suffix:s,or_greater")]
|
||||
public float MaxRunTime { get; set; } = 10.0f;
|
||||
// Regenerate run time multiplier (when run 10s and RunTimeMultiplier = 2.0f to full regenerate you need 5s)
|
||||
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
|
||||
public float RunTimeMultiplier { get; set; } = 2.0f;
|
||||
|
||||
private float _currentRunTime;
|
||||
|
||||
private float _walkSpeed;
|
||||
private float _sprintSpeed;
|
||||
|
||||
public void SetSpeeds(float walkSpeed, float sprintSpeed)
|
||||
{
|
||||
_walkSpeed = walkSpeed;
|
||||
_sprintSpeed = sprintSpeed;
|
||||
}
|
||||
|
||||
public float AccountStamina(double delta, float wantedSpeed)
|
||||
{
|
||||
if (Mathf.Abs(wantedSpeed - _sprintSpeed) > 0.1f)
|
||||
{
|
||||
float runtimeLeft = _currentRunTime - (RunTimeMultiplier * (float)delta);
|
||||
|
||||
if (_currentRunTime != 0.0f)
|
||||
_currentRunTime = Mathf.Clamp(runtimeLeft, 0, MaxRunTime);
|
||||
|
||||
return wantedSpeed;
|
||||
}
|
||||
|
||||
_currentRunTime = Mathf.Clamp(_currentRunTime + (float) delta, 0, MaxRunTime);
|
||||
|
||||
return _currentRunTime >= MaxRunTime ? _walkSpeed : wantedSpeed;
|
||||
}
|
||||
}
|
1
player_controller/Scripts/Stamina.cs.uid
Normal file
1
player_controller/Scripts/Stamina.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://vuq8rjq3vegn
|
Reference in New Issue
Block a user