using System; using Godot; using RustyOptions; namespace Movementtests.systems; public partial class HeadSystem : Node3D { [Signal] public delegate void HitboxActivatedEventHandler(); [Signal] public delegate void HitboxDeactivatedEventHandler(); [Signal] public delegate void HitTargetEventHandler(); [Signal] public delegate void GotHitEventHandler(); [Signal] public delegate void StepFootEventHandler(); public record CameraParameters( double Delta, Vector2 LookDir, Vector3 PlayerInput, Vector3 PlayerVelocity, Vector3 WallContactPoint, float SensitivitMultiplier, bool WithCameraJitter, bool WithCameraBobbing, float BobbingMultiplier, float FovMultiplier); private Camera3D _camera; private Marker3D _cameraAnchor; private AnimationPlayer _animationPlayer; private AnimationTree _animationTree; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float LookSensitivity { get; set; } = 1f; [ExportGroup("Camera incline")] [Export(PropertyHint.Range, "0.1,50,0.1,or_greater")] public double CameraInclineAcceleration { get; set; } = 10f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float WallRunCameraIncline { get; set; } = 5f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float GroundedCameraIncline { get; set; } = 5f; [ExportGroup("Sliding")] [Export(PropertyHint.Range, "0,2,0.1,or_greater")] public float SlidingCameraHeightOffset { get; set; } = 1.0f; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float SlidingJitterFrequency { get; set; } = 0.01f; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float SlidingJitterAmplitude { get; set; } = 0.1f; private FastNoiseLite _slidingNoise = new FastNoiseLite(); [ExportGroup("Bobbing")] private float _bobbingAccumulator; // Constantly increases when player moves in X or/and Z axis [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; [ExportGroup("FOV")] [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; [Export(PropertyHint.Range, "0,100,1,or_greater")] public float FovMaxedOutSpeed { get; set; } = 20f; [ExportGroup("First Person rig")] private Node3D _fpRig; private Node3D _fpDisplacedRig; private Vector3 _fpDisplacedRigInitialRotation; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float WeaponSway { get; set; } = 5f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float WeaponLookRotation { get; set; } = 1f; [Export(PropertyHint.Range, "0,200,1,or_greater")] public float WeaponMoveRotation { get; set; } = 80f; [Export(PropertyHint.Range, "0,20,0.1,or_greater")] public float WeaponAdjustmentSpeed { get; set; } = 10f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float DisplacedWeaponSway { get; set; } = 5f; [Export(PropertyHint.Range, "0,10,0.1,or_greater")] public float DisplacedWeaponLookRotation { get; set; } = 1f; [Export(PropertyHint.Range, "0,1,0.01,or_greater")] public float DisplacedWeaponMoveRotation { get; set; } = 0.1f; [Export(PropertyHint.Range, "0,20,0.1,or_greater")] public float DisplacedWeaponAdjustmentSpeed { get; set; } = 10f; public void Init() { Input.SetMouseMode(Input.MouseModeEnum.Captured); _camera = GetNode("CameraSmooth/Camera3D"); _cameraAnchor = GetNode("CameraAnchor"); _animationPlayer = GetNode("AnimationPlayer"); _animationTree = GetNode("AnimationTree"); _fpRig = GetNode("FPRig"); _fpDisplacedRig = GetNode("FPRig/Sword"); _fpDisplacedRigInitialRotation = _fpDisplacedRig.Rotation; _slidingNoise.NoiseType = FastNoiseLite.NoiseTypeEnum.Perlin; _slidingNoise.SetFrequency(SlidingJitterFrequency); } public void OnMantle() { _animationTree.Set("parameters/OnMantle/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnJumpStarted() { _animationTree.Set("parameters/OnJumpStart/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnJumpEnded() { _animationTree.Set("parameters/OnJumpEnd/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnHit() { _animationTree.Set("parameters/OnHit/request", (int) AnimationNodeOneShot.OneShotRequest.Fire); } public void OnHitTarget() { EmitSignalHitTarget(); } public void OnGetHit() { EmitSignalGotHit(); } public void OnHitboxActivated() { EmitSignalHitboxActivated(); } public void OnHitboxDeactivated() { EmitSignalHitboxDeactivated(); } private bool _footstepEmitted; public void LookAround(CameraParameters inputs) { var (delta, lookDir, playerInput, playerVelocity, wallContactPoint, sensitivitMultiplier, withCameraJitter, withCameraBobbing, bobbingMultiplier, fovMultiplier) = inputs; // Horizontal movement of head float angleForHorizontalRotation = lookDir.X * LookSensitivity * sensitivitMultiplier; RotateY(angleForHorizontalRotation); // Vertical movement of head Vector3 currentCameraRotation = _cameraAnchor.Rotation; currentCameraRotation.X += Convert.ToSingle(lookDir.Y * LookSensitivity * sensitivitMultiplier); currentCameraRotation.X = Mathf.Clamp(currentCameraRotation.X, Mathf.DegToRad(-90f), Mathf.DegToRad(90f)); // Camera incline on Wall and more var isWallRunning = wallContactPoint.Length() > Mathf.Epsilon; float cameraIncline; if (isWallRunning) { var directionToWall = (wallContactPoint - GlobalPosition).Normalized(); var cameraInclineFactor = ComputeCameraInclineFactor(directionToWall); cameraIncline = Mathf.DegToRad(WallRunCameraIncline * cameraInclineFactor); } else { var cameraInclineFactor = ComputeCameraInclineFactor(playerInput); cameraIncline = Mathf.DegToRad(GroundedCameraIncline * cameraInclineFactor * -1.0f); } currentCameraRotation.Z = (float) Mathf.Lerp(currentCameraRotation.Z, cameraIncline, delta * CameraInclineAcceleration); _cameraAnchor.Rotation = currentCameraRotation; if (withCameraJitter) { _cameraAnchor.Position = Vector3.Down*SlidingCameraHeightOffset; float noise1D = _slidingNoise.GetNoise1D(Time.GetTicksMsec()); float noiseAmplitude = SlidingJitterAmplitude*Mathf.Clamp(playerVelocity.Length(), 0f, 1f); _cameraAnchor.Position += Vector3.Up*noise1D*noiseAmplitude; } else { _cameraAnchor.Position = Vector3.Zero; } Vector3 newPositionForCamera = Vector3.Zero; Vector3 newPositionForRig = Vector3.Zero; if (withCameraBobbing) { _bobbingAccumulator += (float) delta * playerVelocity.Length(); // 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 * bobbingMultiplier; newPositionForCamera.X = Mathf.Cos(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * bobbingMultiplier; if (newPositionForCamera.Y < -0.07 && !_footstepEmitted) Footstep(); if (newPositionForCamera.Y > 0) _footstepEmitted = false; // Offset bobbing for weapon rig newPositionForRig.Y = Mathf.Cos(_bobbingAccumulator * BobbingFrequency) * BobbingAmplitude * bobbingMultiplier * 0.2f; newPositionForRig.X = Mathf.Sin(_bobbingAccumulator * BobbingFrequency / 2.0f) * BobbingAmplitude * bobbingMultiplier * 0.2f; } _cameraAnchor.Position += newPositionForCamera; _camera.GlobalTransform = _cameraAnchor.GetGlobalTransformInterpolated(); // First person rig adjustments _fpRig.GlobalTransform = _cameraAnchor.GetGlobalTransformInterpolated(); // Apply bobbing _fpRig.Position += newPositionForRig; // Rotate the whole rig based on movement input var newRigRotation = _fpRig.Rotation; var camTilt = Mathf.Lerp(_fpRig.Rotation.Z, cameraIncline*WeaponMoveRotation, delta*WeaponAdjustmentSpeed); newRigRotation.Z = (float) camTilt; // Rotate the whole rig based on camera rotation input newRigRotation.X = Mathf.Lerp(newRigRotation.X, -lookDir.Y*WeaponSway, (float) delta*WeaponAdjustmentSpeed); newRigRotation.Y = Mathf.Lerp(newRigRotation.Y, -lookDir.X*WeaponSway, (float) delta*WeaponAdjustmentSpeed); // Apply _fpRig.Rotation = newRigRotation; // Compute displaced rig adjustments, starting with movement input var newDisplacedRigRotation = _fpDisplacedRig.Rotation; var howMuchForward = ComputeHowMuchInputForward(playerInput); var howMuchSideways = ComputeHowMuchInputSideways(playerInput); var displacedCamTiltForward = Mathf.Lerp(newDisplacedRigRotation.Z, _fpDisplacedRigInitialRotation.Z + howMuchForward*DisplacedWeaponMoveRotation, delta*DisplacedWeaponAdjustmentSpeed); var displacedCamTiltSide = Mathf.Lerp(newDisplacedRigRotation.X, _fpDisplacedRigInitialRotation.X - howMuchSideways*DisplacedWeaponMoveRotation, delta*DisplacedWeaponAdjustmentSpeed); newDisplacedRigRotation.X = (float) displacedCamTiltSide; newDisplacedRigRotation.Z = (float) displacedCamTiltForward; var displacedSwayY = Mathf.Lerp(newDisplacedRigRotation.Y, _fpDisplacedRigInitialRotation.Y - lookDir.X*DisplacedWeaponSway, delta*DisplacedWeaponAdjustmentSpeed); newDisplacedRigRotation.Y = (float) displacedSwayY; // Apply _fpDisplacedRig.Rotation = newDisplacedRigRotation; // Camera adjustments float velocityClamped = Mathf.Clamp(playerVelocity.Length(), 0.5f, FovMaxedOutSpeed); float targetFov = BaseFov + FovChangeFactor * velocityClamped * fovMultiplier; _camera.Fov = Mathf.Lerp(_camera.Fov, targetFov, (float) delta * FovChangeSpeed); } public void Footstep() { _footstepEmitted = true; EmitSignalStepFoot(); } public void HideWeapon() { _fpRig.Visible = false; } public void ShowWeapon() { _fpRig.Visible = true; } public float ComputeCameraInclineFactor(Vector3 direction) { var forward = GetForwardHorizontalVector().Normalized(); var crossProduct = forward.Cross(direction); return crossProduct.Length()*Mathf.Sign(crossProduct.Y); } public float ComputeHowMuchInputForward(Vector3 playerInput) { var forwardAngle = GetForwardHorizontalVector().AngleTo(playerInput); var forwardRemapped = Mathf.Remap(forwardAngle, 0, Mathf.Pi, -1, 1); return playerInput.Length() > 0 ? forwardRemapped : 0; } public float ComputeHowMuchInputSideways(Vector3 playerInput) { var rightAngle = GetForwardHorizontalVector().Cross(Vector3.Up).Normalized().AngleTo(playerInput); var forwardRemapped = Mathf.Remap(rightAngle, 0, Mathf.Pi, -1, 1); return playerInput.Length() > 0 ? forwardRemapped : 0; } public Vector3 GetForwardHorizontalVector() { return GetGlobalTransform().Basis.Z; } public Vector3 GetGlobalLookRotation() { return new Vector3( _camera.Rotation.X, Rotation.Y, _camera.Rotation.Z); } public void SetHeight(float height) { Position = new Vector3(Position.X, height, Position.Z); } }