DesktopCharacterController.cs
using Sandbox;
using System;
public sealed class DesktopCharacterController : Component
{
[Property] private CameraComponent Camera { get; set; }
/// <summary>World-units per second the desktop death camera rises while looking at the corpse.</summary>
[Property] public float DeathCamRiseSpeed { get; set; } = 30f;
/// <summary>Height above the player root the death camera looks at (approx. torso).</summary>
[Property] public float DeathCamLookHeight { get; set; } = 40f;
/// <summary>Eye height in units when standing upright (5'9" avatar ≈ 64).</summary>
[Property] public float StandingEyeHeight { get; set; } = 64f;
/// <summary>Eye height in units when fully crouched.</summary>
[Property] public float CrouchedEyeHeight { get; set; } = 34f;
/// <summary>How quickly duck level transitions between standing and crouched.</summary>
[Property] public float DuckSpeed { get; set; } = 10f;
[Property] private ScreenPanel Scoreboard { get; set; }
private PlayerCharacterController PlayerCharacterController { get; set; }
private HealthComponent Health { get; set; }
private Angles desktopEyeAngles;
private float _deathCamHeight;
private bool _deathCamInitialized;
private float _duckLevel;
protected override void OnStart()
{
PlayerCharacterController = GetComponentInParent<PlayerCharacterController>();
PlayerCharacterController.Camera = Camera;
Health = GetComponentInParent<HealthComponent>();
}
protected override void OnFixedUpdate()
{
if ( IsProxy )
return;
if ( !NetworkManager.Instance.IsMapReady )
{
PlayerCharacterController.Velocity = Vector3.Zero;
PlayerCharacterController.SyncedVelocity = Vector3.Zero;
return;
}
if ( DeathmatchGame.Instance.State == MatchState.ENDED )
return;
if ( Health.IsDead )
return;
UpdateDuckCapsule();
HandleDesktopMovement();
}
protected override void OnUpdate()
{
if ( IsProxy )
return;
UpdateScoreboard();
UpdateCursor();
UpdateDesktopDeathCam();
if ( Health.IsDead || DeathmatchGame.Instance.State == MatchState.ENDED )
return;
UpdateMouseLook();
UpdateCameraEyeHeight();
}
private void UpdateCursor()
{
Mouse.Visibility = DeathmatchGame.Instance.State == MatchState.ENDED
? MouseVisibility.Visible
: MouseVisibility.Hidden;
}
private void UpdateMouseLook()
{
desktopEyeAngles.pitch += Input.AnalogLook.pitch;
desktopEyeAngles.yaw += Input.AnalogLook.yaw;
desktopEyeAngles.pitch = desktopEyeAngles.pitch.Clamp( -89f, 89f );
Camera.WorldRotation = Rotation.From( desktopEyeAngles );
}
private void UpdateDuckCapsule()
{
float targetDuck = Input.Down( "duck" ) ? 1f : 0f;
_duckLevel = MathX.Lerp( _duckLevel, targetDuck, Time.Delta * DuckSpeed );
_duckLevel = PlayerCharacterController.SetDuckLevel( _duckLevel );
}
private void UpdateCameraEyeHeight()
{
float eyeHeight = MathX.Lerp( StandingEyeHeight, CrouchedEyeHeight, PlayerCharacterController.DuckLevel );
Camera.LocalPosition = Camera.LocalPosition.WithZ( eyeHeight );
}
private void HandleDesktopMovement()
{
var moveInput = Input.AnalogMove;
var lookYaw = Rotation.FromYaw( desktopEyeAngles.yaw );
var moveDir = (lookYaw.Forward * moveInput.x) + (lookYaw.Left * moveInput.y);
if ( moveDir.Length > 1f )
moveDir = moveDir.Normal;
if ( PlayerCharacterController.IsGrappling )
{
var wishVel = moveDir * PlayerCharacterController.MoveSpeed;
if ( !(Input.Pressed( "jump" ) && PlayerCharacterController.TryJump( wishVel.WithZ( 0 ) )) )
PlayerCharacterController.ApplyGrappleVelocity();
}
else
{
var vel = PlayerCharacterController.Velocity;
vel.x = moveDir.x * PlayerCharacterController.MoveSpeed;
vel.y = moveDir.y * PlayerCharacterController.MoveSpeed;
if ( Input.Pressed( "jump" ) && PlayerCharacterController.TryJump( vel.WithZ( 0 ) ) )
{
vel = PlayerCharacterController.Velocity;
}
else if ( PlayerCharacterController.IsOnGround )
{
vel.z = 0;
PlayerCharacterController.Velocity = vel;
}
else
{
vel.z -= PlayerCharacterController.Gravity * Time.Delta;
vel.z = MathF.Max( vel.z, -PlayerCharacterController.TerminalVelocity );
PlayerCharacterController.Velocity = vel;
}
}
PlayerCharacterController.SyncedVelocity = PlayerCharacterController.Velocity;
PlayerCharacterController.Move();
PlayerCharacterController.UpdateFallAndBoundsDamage();
}
private void UpdateDesktopDeathCam()
{
if ( !Health.IsDead )
return;
var lookTarget = WorldPosition + Vector3.Up * DeathCamLookHeight;
if ( !_deathCamInitialized )
{
_deathCamHeight = (Camera.WorldPosition - lookTarget).Dot( Vector3.Up );
_deathCamHeight = MathF.Max( _deathCamHeight, 8f );
_deathCamInitialized = true;
}
_deathCamHeight += DeathCamRiseSpeed * Time.Delta;
var camPos = lookTarget + Vector3.Up * _deathCamHeight;
Camera.WorldPosition = camPos;
Camera.WorldRotation = Rotation.LookAt( lookTarget - camPos, Vector3.Up );
}
private void UpdateScoreboard()
{
Scoreboard.Enabled = Input.Down( "score" ) || DeathmatchGame.Instance.State == MatchState.ENDED;
}
}