feat(app): add CameraController with F toggle and cursor capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7cf6ea267a
commit
22f684e8c6
2 changed files with 100 additions and 15 deletions
31
src/AcDream.App/Rendering/CameraController.cs
Normal file
31
src/AcDream.App/Rendering/CameraController.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// src/AcDream.App/Rendering/CameraController.cs
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
public sealed class CameraController
|
||||
{
|
||||
public OrbitCamera Orbit { get; }
|
||||
public FlyCamera Fly { get; }
|
||||
public ICamera Active { get; private set; }
|
||||
public bool IsFlyMode => Active == Fly;
|
||||
|
||||
public event Action<bool>? ModeChanged;
|
||||
|
||||
public CameraController(OrbitCamera orbit, FlyCamera fly)
|
||||
{
|
||||
Orbit = orbit;
|
||||
Fly = fly;
|
||||
Active = orbit;
|
||||
}
|
||||
|
||||
public void ToggleFly()
|
||||
{
|
||||
Active = IsFlyMode ? (ICamera)Orbit : Fly;
|
||||
ModeChanged?.Invoke(IsFlyMode);
|
||||
}
|
||||
|
||||
public void SetAspect(float aspect)
|
||||
{
|
||||
Orbit.Aspect = aspect;
|
||||
Fly.Aspect = aspect;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ public sealed class GameWindow : IDisposable
|
|||
private IInputContext? _input;
|
||||
private TerrainRenderer? _terrain;
|
||||
private Shader? _shader;
|
||||
private OrbitCamera? _camera;
|
||||
private CameraController? _cameraController;
|
||||
private IMouse? _capturedMouse;
|
||||
private DatCollection? _dats;
|
||||
private float _lastMouseX;
|
||||
private float _lastMouseY;
|
||||
|
|
@ -42,6 +43,7 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
_window = Window.Create(options);
|
||||
_window.Load += OnLoad;
|
||||
_window.Update += OnUpdate;
|
||||
_window.Render += OnRender;
|
||||
_window.Closing += OnClosing;
|
||||
|
||||
|
|
@ -55,26 +57,49 @@ public sealed class GameWindow : IDisposable
|
|||
foreach (var kb in _input.Keyboards)
|
||||
kb.KeyDown += (_, key, _) =>
|
||||
{
|
||||
if (key == Key.Escape)
|
||||
_window!.Close();
|
||||
if (key == Key.F)
|
||||
_cameraController?.ToggleFly();
|
||||
else if (key == Key.Escape)
|
||||
{
|
||||
if (_cameraController?.IsFlyMode == true)
|
||||
_cameraController.ToggleFly(); // exit fly, release cursor
|
||||
else
|
||||
_window!.Close();
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var mouse in _input.Mice)
|
||||
{
|
||||
mouse.MouseMove += (m, pos) =>
|
||||
{
|
||||
if (m.IsButtonPressed(MouseButton.Left))
|
||||
if (_cameraController is null) return;
|
||||
|
||||
if (_cameraController.IsFlyMode)
|
||||
{
|
||||
_camera!.Yaw -= (pos.X - _lastMouseX) * 0.005f;
|
||||
_camera!.Pitch = Math.Clamp(
|
||||
_camera.Pitch + (pos.Y - _lastMouseY) * 0.005f,
|
||||
0.1f, 1.5f);
|
||||
// Raw cursor mode: Silk.NET gives deltas via position. Compute delta from last.
|
||||
float dx = pos.X - _lastMouseX;
|
||||
float dy = pos.Y - _lastMouseY;
|
||||
_cameraController.Fly.Look(dx, dy);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m.IsButtonPressed(MouseButton.Left))
|
||||
{
|
||||
_cameraController.Orbit.Yaw -= (pos.X - _lastMouseX) * 0.005f;
|
||||
_cameraController.Orbit.Pitch = Math.Clamp(
|
||||
_cameraController.Orbit.Pitch + (pos.Y - _lastMouseY) * 0.005f,
|
||||
0.1f, 1.5f);
|
||||
}
|
||||
}
|
||||
_lastMouseX = pos.X;
|
||||
_lastMouseY = pos.Y;
|
||||
};
|
||||
mouse.Scroll += (_, scroll) =>
|
||||
_camera!.Distance = Math.Clamp(_camera.Distance - scroll.Y * 20f, 50f, 2000f);
|
||||
{
|
||||
if (_cameraController is null || _cameraController.IsFlyMode) return;
|
||||
_cameraController.Orbit.Distance = Math.Clamp(
|
||||
_cameraController.Orbit.Distance - scroll.Y * 20f, 50f, 2000f);
|
||||
};
|
||||
}
|
||||
|
||||
_gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
|
||||
|
|
@ -89,10 +114,10 @@ public sealed class GameWindow : IDisposable
|
|||
Path.Combine(shadersDir, "mesh.vert"),
|
||||
Path.Combine(shadersDir, "mesh.frag"));
|
||||
|
||||
_camera = new OrbitCamera
|
||||
{
|
||||
Aspect = _window!.Size.X / (float)_window.Size.Y,
|
||||
};
|
||||
var orbit = new OrbitCamera { Aspect = _window!.Size.X / (float)_window.Size.Y };
|
||||
var fly = new FlyCamera { Aspect = _window.Size.X / (float)_window.Size.Y };
|
||||
_cameraController = new CameraController(orbit, fly);
|
||||
_cameraController.ModeChanged += OnCameraModeChanged;
|
||||
|
||||
_dats = new DatCollection(_datDir, DatAccessType.Read);
|
||||
|
||||
|
|
@ -199,11 +224,40 @@ public sealed class GameWindow : IDisposable
|
|||
Console.WriteLine($"hydrated {_entities.Count} entities");
|
||||
}
|
||||
|
||||
private void OnUpdate(double dt)
|
||||
{
|
||||
if (_cameraController is null || _input is null) return;
|
||||
if (!_cameraController.IsFlyMode) return;
|
||||
|
||||
var kb = _input.Keyboards[0];
|
||||
_cameraController.Fly.Update(
|
||||
dt,
|
||||
w: kb.IsKeyPressed(Key.W),
|
||||
a: kb.IsKeyPressed(Key.A),
|
||||
s: kb.IsKeyPressed(Key.S),
|
||||
d: kb.IsKeyPressed(Key.D),
|
||||
up: kb.IsKeyPressed(Key.Space),
|
||||
down: kb.IsKeyPressed(Key.ControlLeft));
|
||||
}
|
||||
|
||||
private void OnCameraModeChanged(bool isFlyMode)
|
||||
{
|
||||
if (_input is null) return;
|
||||
var mouse = _input.Mice.FirstOrDefault();
|
||||
if (mouse is null) return;
|
||||
|
||||
mouse.Cursor.CursorMode = isFlyMode ? CursorMode.Raw : CursorMode.Normal;
|
||||
_capturedMouse = isFlyMode ? mouse : null;
|
||||
}
|
||||
|
||||
private void OnRender(double deltaSeconds)
|
||||
{
|
||||
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
_terrain?.Draw(_camera!);
|
||||
_staticMesh?.Draw(_camera!, _entities);
|
||||
if (_cameraController is not null)
|
||||
{
|
||||
_terrain?.Draw(_cameraController.Active);
|
||||
_staticMesh?.Draw(_cameraController.Active, _entities);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClosing()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue