acdream/src/AcDream.App/Rendering/CameraFrameController.cs
Erik 947c61d2d7 refactor(camera): extract the update frame
Move fly/chase publication, combat target tracking, and local player projection behind typed runtime owners. Preserve the inbound-created projection/reconcile barrier while removing GameWindow callbacks and duplicate shadow helpers.
2026-07-22 03:08:58 +02:00

114 lines
4.4 KiB
C#

using AcDream.App.Combat;
using AcDream.App.Input;
using AcDream.App.Update;
using AcDream.Core.Rendering;
namespace AcDream.App.Rendering;
/// <summary>
/// Owns the per-update fly/chase camera publication tail.
/// </summary>
/// <remarks>
/// The local object phase normally publishes the player before inbound
/// traffic. When the player is first created by that inbound pass, the local
/// frame publishes its initial root here and the spatial reconciler refreshes
/// child/effect anchors before either chase camera samples it.
/// </remarks>
internal sealed class CameraFrameController : ICameraFramePhase
{
private readonly CameraController _camera;
private readonly IInputCaptureSource _capture;
private readonly ICameraFrameInputSource _input;
private readonly ILocalPlayerPresentationRuntime _player;
private readonly IChaseCameraSource _chase;
private readonly RetailLocalPlayerFrameController _localFrame;
private readonly ILiveSpatialReconcilePhase _spatialReconciler;
private readonly ICombatCameraTargetSource _combatTarget;
public CameraFrameController(
CameraController camera,
IInputCaptureSource capture,
ICameraFrameInputSource input,
ILocalPlayerPresentationRuntime player,
IChaseCameraSource chase,
RetailLocalPlayerFrameController localFrame,
ILiveSpatialReconcilePhase spatialReconciler,
ICombatCameraTargetSource combatTarget)
{
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
_capture = capture ?? throw new ArgumentNullException(nameof(capture));
_input = input ?? throw new ArgumentNullException(nameof(input));
_player = player ?? throw new ArgumentNullException(nameof(player));
_chase = chase ?? throw new ArgumentNullException(nameof(chase));
_localFrame = localFrame ?? throw new ArgumentNullException(nameof(localFrame));
_spatialReconciler = spatialReconciler
?? throw new ArgumentNullException(nameof(spatialReconciler));
_combatTarget = combatTarget ?? throw new ArgumentNullException(nameof(combatTarget));
}
public void Tick(UpdateFrameTiming timing)
{
if (_capture.DevToolsWantCaptureKeyboard || !_input.IsAvailable)
return;
if (_camera.IsFlyMode)
{
FlyCameraInput input = _input.CaptureFly();
_camera.Fly.Update(
timing.SimulationDeltaSeconds,
input.Forward,
input.Left,
input.Backward,
input.Right,
input.Up,
input.Down,
input.Boost);
return;
}
PlayerMovementController? controller = _player.Controller;
ChaseCamera? legacy = _chase.Legacy;
RetailChaseCamera? retail = _chase.Retail;
if (!_player.CanPresentPlayer || controller is null || legacy is null)
return;
if (CameraDiagnostics.UseRetailChaseCamera && retail is not null)
{
ChaseCameraAdjustmentInput input = _input.CaptureChaseAdjustment();
float adjustment = CameraDiagnostics.CameraAdjustmentSpeed
* timing.SimulationDeltaSecondsSingle;
if (input.ZoomIn)
retail.AdjustDistance(-adjustment);
if (input.ZoomOut)
retail.AdjustDistance(+adjustment);
if (input.Raise)
retail.AdjustPitch(+adjustment * 0.02f);
if (input.Lower)
retail.AdjustPitch(-adjustment * 0.02f);
}
if (!_localFrame.TryGetPresentationAfterNetwork(out var playerFrame))
return;
if (!playerFrame.AdvancedBeforeNetwork)
_spatialReconciler.Reconcile();
MovementResult result = playerFrame.Movement;
legacy.Update(
result.RenderPosition,
controller.Yaw,
isOnGround: result.IsOnGround,
dt: timing.SimulationDeltaSecondsSingle);
retail?.Update(
result.RenderPosition,
controller.Yaw,
playerVelocity: controller.BodyVelocity,
isOnGround: result.IsOnGround,
contactPlaneNormal: controller.ContactPlane.Normal,
dt: timing.SimulationDeltaSecondsSingle,
cellId: controller.CellId,
selfEntityId: controller.LocalEntityId,
trackedTargetPoint: _combatTarget.GetTrackedTargetPoint());
}
}