Camera-related code for the Player partial class. It controls camera positioning, field of view, orthographic height, bounds clamping, camera shake management, and provides RPC/utility to trigger camera shake.
using System;
public struct CamShakeData
{
public float strength;
public float startTime;
public float time;
public EasingType easingType;
public bool useRealTime;
public CamShakeData( float _strength, float _startTime, float _time, EasingType _easingType, bool _useRealTime )
{
strength = _strength;
startTime = _startTime;
time = _time;
easingType = _easingType;
useRealTime = _useRealTime;
}
}
public partial class Player
{
private List<CamShakeData> _camShakeDatas = new();
[Sync] public bool ShouldOverrideCameraPos { get; set; }
[Sync] public Vector2 CameraTargetPosOverride { get; set; }
[Sync] public float CameraHurtZoomAmount { get; set; }
public void HandleCamera()
{
ApplyCameraView();
}
public void ApplyCameraView( bool allowHurtZoomDecay = true )
{
var camera = Manager.Instance.Camera;
var container = Manager.Instance.CameraContainer;
if ( GetSyncStat( PlayerStat.FpsMode ) > 0f )
{
if( ShouldOverrideCameraPos && !IsDead )
{
container.WorldPosition = (Vector3)CameraTargetPosOverride + Vector3.Up * 125f;
container.WorldRotation = Rotation.LookAt( WorldPosition - container.WorldPosition );
}
else
{
container.WorldPosition = WorldPosition + Vector3.Up * 125f + Model.LocalRotation.Forward * -170f;
container.WorldRotation = new Angles( 20f, Model.LocalRotation.Yaw(), 0f );
}
camera.FieldOfView = 60f * (GetSyncStat( PlayerStat.CameraDistance ) / 800f);
// todo: animate camera when choosing perk or gameover
return;
}
// todo: handle perspective cam changes when choosing/hurt/dying
camera.FieldOfView = 60f;
container.WorldRotation = new Angles( 50f, 90f, 0f );
//var targetPitchOffset = Manager.Instance.IsPausedForChoosing ? Utils.FastSin( RealTime.Now * 0.4f ) * 2f : 0f;
//var targetYawOffset = Manager.Instance.IsPausedForChoosing ? Utils.FastSin( RealTime.Now * 0.3f ) * 2f : 0f;
//container.WorldRotation = Rotation.Lerp( container.WorldRotation, Rotation.From( new Angles( 50f + targetPitchOffset, 90f + targetYawOffset, 0f ) ), RealTime.Delta * 2f );
var camDistScale = GetSyncStat( PlayerStat.CameraDistance ) / 800f;
var pos2D = (ShouldOverrideCameraPos && !IsDead)
? CameraTargetPosOverride
: Position2D;
var targetPos = GetCamTargetPos( pos2D, camDistScale );
if( Manager.Instance.IsOrthoCamera && !Manager.Instance.IsPaused )
{
var targetOrthoHeight = 500f
+ (Manager.Instance.IsPausedForChoosing && !Manager.Instance.IsGameOver ? -80f + Utils.FastSin( RealTime.Now * 1.5f ) * 13f : 0f)
+ (Manager.Instance.IsGameOver ? -40f : 0f);
var cameraHurtZoomAmount = CameraHurtZoomAmount;
camera.OrthographicHeight = MathX.Lerp( camera.OrthographicHeight, targetOrthoHeight * camDistScale, RealTime.Delta * 3f ) - cameraHurtZoomAmount;
if ( allowHurtZoomDecay && Math.Abs( CameraHurtZoomAmount ) > 0f )
{
CameraHurtZoomAmount = MathX.Lerp( CameraHurtZoomAmount, 0f, RealTime.Delta * 13f );
if ( Math.Abs( CameraHurtZoomAmount ) < 0.01f )
CameraHurtZoomAmount = 0f;
}
}
container.WorldPosition = Vector3.Lerp( container.WorldPosition, targetPos, 9f * RealTime.Delta, true );
if ( targetPos.y < -5000f )
camera.ZFar = 50000f;
}
public void RestartCamera()
{
var container = Manager.Instance.CameraContainer;
container.WorldPosition = new Vector3( -50f, -500f, 650f );
container.WorldRotation = new Angles( 50f, 90f, 0f );
container.Transform.ClearInterpolation();
Manager.Instance.Camera.ZFar = 10000f;
ShouldOverrideCameraPos = false;
CameraHurtZoomAmount = 0f;
}
Vector3 GetCamTargetPos( Vector2 pos, float camDistScale )
{
if ( !Manager.Instance.IsOrthoCamera )
camDistScale *= 0.75f;
ClampToBounds( ref pos, camDistScale );
var distance = Manager.Instance.IsOrthoCamera
? 800f
: GetSyncStat( PlayerStat.CameraDistance );
return (Vector3)pos + Manager.Instance.CameraContainer.WorldRotation.Backward * distance;
}
void ClampToBounds( ref Vector2 pos2D, float camDistScale )
{
// if camera is zoomed in, allow it to go more out of bounds
if ( camDistScale < 1f )
camDistScale = MathX.Lerp( camDistScale, 0f, 0.3f );
float X_BUFFER = 310f * camDistScale;
float Y_LOWER_BUFFER = 200f * camDistScale;
float Y_UPPER_BUFFER = 170f * camDistScale;
pos2D = new Vector2(
MathX.Clamp( pos2D.x, Manager.Instance.BOUNDS_MIN.x + X_BUFFER, Manager.Instance.BOUNDS_MAX.x - X_BUFFER ),
MathX.Clamp( pos2D.y, Manager.Instance.BOUNDS_MIN.y + Y_LOWER_BUFFER, Manager.Instance.BOUNDS_MAX.y - Y_UPPER_BUFFER )
);
}
void HandleCamShaking()
{
var shakeAmount = 0f;
for ( int i = _camShakeDatas.Count - 1; i >= 0; i-- )
{
var data = _camShakeDatas[i];
var time = data.useRealTime ? RealTime.Now : Time.Now;
if ( time > data.startTime + data.time )
{
_camShakeDatas.RemoveAt( i );
}
else
{
float amount = Utils.Map( time, data.startTime, data.startTime + data.time, data.strength, 0f, data.easingType );
shakeAmount = MathF.Max( amount, shakeAmount );
}
}
Manager.Instance.Camera.LocalPosition = shakeAmount > 0f
? Rotation.Random.Forward * shakeAmount
: Vector3.Zero;
}
[Rpc.Owner]
public void ShakeCamRpc( float strength, float time, EasingType easingType = EasingType.Linear, bool useRealTime = true )
{
ShakeCam( strength, time, easingType, useRealTime );
}
public void ShakeCam( float strength, float time, EasingType easingType = EasingType.Linear, bool useRealTime = true )
{
var timeNow = useRealTime ? RealTime.Now : Time.Now;
_camShakeDatas.Add( new CamShakeData( strength, timeNow, time, easingType, useRealTime ) );
}
}