complete project reorganization
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 55s
Create tag and build when new code gets to main / Test (push) Successful in 7m7s
Create tag and build when new code gets to main / Export (push) Successful in 9m56s

This commit is contained in:
2026-02-04 11:20:00 +01:00
parent b6e8d0b590
commit cd519e528f
824 changed files with 397 additions and 16380 deletions

View File

@@ -0,0 +1,9 @@
namespace Movementtests.player_controller.Scripts;
public partial class AnimationPlayer : Godot.AnimationPlayer
{
public void PlayCameraRotationOnDeath()
{
Play(Constants.PLAYERS_HEAD_ANIMATION_ON_DYING);
}
}

View File

@@ -0,0 +1 @@
uid://bt8flen3mi28r

View File

@@ -0,0 +1,48 @@
using Godot;
namespace Movementtests.player_controller.Scripts;
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;
public float SettingsMultiplier;
}
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 * parameters.SettingsMultiplier;
newPositionForCamera.X = Mathf.Cos(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * parameters.SettingsMultiplier;
_camera.Position = newPositionForCamera;
}
}
}

View File

@@ -0,0 +1 @@
uid://g8idirw62qe0

View File

@@ -0,0 +1,52 @@
using Godot;
namespace Movementtests.player_controller.Scripts;
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()
{
_shape.Height = CapsuleCrouchHeight;
}
public void Uncrouch()
{
_shape.Height = CapsuleDefaultHeight;
}
}

View File

@@ -0,0 +1 @@
uid://dd1yrt7eiiyf4

View File

@@ -0,0 +1,45 @@
using Godot;
namespace Movementtests.player_controller.Scripts;
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 float FOVMultiplier;
}
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 * parameters.FOVMultiplier;
if (parameters.IsCrouchingHeight){
targetFov = BaseFov - FovChangeFactor * velocityClamped * parameters.FOVMultiplier;
}
_camera.Fov = Mathf.Lerp(_camera.Fov, targetFov, parameters.Delta * FovChangeSpeed);
}
}

View File

@@ -0,0 +1 @@
uid://b6k73aj5povgv

View File

@@ -0,0 +1,23 @@
namespace Movementtests.player_controller.Scripts;
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;
}

View File

@@ -0,0 +1 @@
uid://yrcg34scpt5k

View File

@@ -0,0 +1,32 @@
using Godot;
namespace Movementtests.player_controller.Scripts;
public partial class Gravity: Node3D
{
[Export(PropertyHint.Range, "0,10,0.01,or_greater")]
public float Weight { get; set; } = 3.0f;
[Export(PropertyHint.Range, "0,2,0.01,or_greater")]
public float StartVelocity { get; set; } = 1.0f;
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float DoubleJumpSpeedFactor { get; set; } = 2f;
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float JumpFromDashSpeedFactor { get; set; } = 2f;
[Export(PropertyHint.Range, "0.1,10,0.1,or_greater")]
public float JumpFromWallSpeedFactor { get; set; } = 2f;
[Export(PropertyHint.Range, "0,1,0.01,or_greater")]
public float AdditionalGravityPower { get; set; } = 1f;
private float _gravity;
public void Init(float gravitySetting)
{
_gravity = gravitySetting;
}
public float CalculateJumpForce() => _gravity * (StartVelocity / AdditionalGravityPower);
public float CalculateJumpFromDashForce() => CalculateJumpForce() * JumpFromDashSpeedFactor;
public float CalculateJumpFromWallForce() => CalculateJumpForce() * JumpFromWallSpeedFactor;
public float CalculateDoubleJumpForce() => CalculateJumpForce() * DoubleJumpSpeedFactor;
public float CalculateGravityForce() => _gravity * Weight;
}

View File

@@ -0,0 +1 @@
uid://bt0xv2q8iv1vn

View File

@@ -0,0 +1,470 @@
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 void Init(HealthSystemInitParams initParams)
{
_currentHealthInPrevFrame = CurrentHealth;
_currentMultiplierMidValue = InitialMultiplierMidVal;
_currentSpeed = SpeedMin;
_characterBody3D = initParams.Parent;
_camera = initParams.Camera;
_head = initParams.Head;
// 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);
}
}

View File

@@ -0,0 +1 @@
uid://dv7v1ywmbvvcd

View File

@@ -0,0 +1,41 @@
using System;
using Godot;
namespace Movementtests.player_controller.Scripts;
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 void LookAround(Vector2 lookDir)
{
// Horizontal movement of head
float angleForHorizontalRotation = lookDir.X * Sensitivity;
_head.RotateY(angleForHorizontalRotation);
// Vertical movement of head
Vector3 currentCameraRotation = _camera.Rotation;
currentCameraRotation.X += Convert.ToSingle(lookDir.Y * Sensitivity);
currentCameraRotation.X = Mathf.Clamp(currentCameraRotation.X, Mathf.DegToRad(-90f), Mathf.DegToRad(90f));
_camera.Rotation = currentCameraRotation;
}
}

View File

@@ -0,0 +1 @@
uid://c6bx47wr7fbdm

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
uid://bbbrf5ckydfna

View File

@@ -0,0 +1,353 @@
using Godot;
namespace Movementtests.player_controller.Scripts;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_boots.png")]
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;
}
}

View File

@@ -0,0 +1 @@
uid://cwbvxlfvmocc1

View File

@@ -0,0 +1,40 @@
using Godot;
namespace Movementtests.player_controller.Scripts;
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;
}
}

View File

@@ -0,0 +1 @@
uid://vuq8rjq3vegn

View File

@@ -0,0 +1,59 @@
using Godot;
using System;
using System.Collections.Generic;
using RustyOptions;
namespace Movementtests.systems;
[GlobalClass, Icon("res://assets/ui/IconGodotNode/node_3D/icon_wall.png")]
public partial class WallHugSystem : Node3D
{
[Signal]
public delegate void WallDetectedEventHandler();
private List<RayCast3D> _raycasts;
public Option<Vector3> WallHugLocation { get; private set; } = Option<Vector3>.None;
public Option<Vector3> WallHugNormal { get; private set; } = Option<Vector3>.None;
public void Init()
{
_raycasts = new List<RayCast3D>();
_raycasts.Add(GetNode<RayCast3D>("front"));
_raycasts.Add(GetNode<RayCast3D>("front2"));
_raycasts.Add(GetNode<RayCast3D>("back"));
_raycasts.Add(GetNode<RayCast3D>("back2"));
_raycasts.Add(GetNode<RayCast3D>("left"));
_raycasts.Add(GetNode<RayCast3D>("left2"));
_raycasts.Add(GetNode<RayCast3D>("right"));
_raycasts.Add(GetNode<RayCast3D>("right2"));
}
public override void _PhysicsProcess(double delta)
{
base._PhysicsProcess(delta);
CheckWallHugging();
if (IsWallHugging())
EmitSignal(SignalName.WallDetected);
}
public void CheckWallHugging()
{
foreach (RayCast3D raycast in _raycasts)
{
if (raycast.IsColliding() && Math.Abs(raycast.GetCollisionNormal().Y) < 0.3f)
{
WallHugLocation = raycast.GetCollisionPoint().Some();
WallHugNormal = raycast.GetCollisionNormal().Some();
return;
}
}
WallHugLocation = Option<Vector3>.None;
WallHugNormal = Option<Vector3>.None;
}
public bool IsWallHugging()
{
return !WallHugLocation.IsNone;
}
}

View File

@@ -0,0 +1 @@
uid://tjiji63wlom5