Skip to content

MapView PhotoCamera GarageCamera

Bartholomew Ho edited this page Nov 9, 2026 · 1 revision

MapView, PhotoCamera and GarageCamera

Three cameras outside of driving. MapView is the map while it's open: its cursor, its camera and its zoom. PhotoCamera lets you add to what photo mode's camera reads from the sticks. GarageCamera moves the garage's orbiting camera closer or further. A mouse mod uses them to drag the map, fly the photo camera with keys, and zoom the garage camera with the wheel.

Thread: MapView: the UI thread, inside Menus.Frame. PhotoCamera.InputRead: its handlers run on the game's thread. GarageCamera: any thread.

Example

The left mouse button drags the map, W and S fly the photo camera forward and back, and the mouse wheel zooms the garage camera.

using System.Numerics;
using FH2;
using FH2ModPlatform;

public sealed class MouseCameras : Mod
{
    private Vector2 _last;

    public override ModInfo Info => new("MouseCameras", "1.0.0");

    protected override void OnInit()
    {
        Menus.Frame += dt =>
        {
            var at = Menus.ToUi(GameWindow.Cursor);
            if (MapView.Current is { } map && GameWindow.IsMouseDown(MouseButton.Left))
                map.Pan(at - _last);
            _last = at;
        };

        PhotoCamera.InputRead += input =>
            input.Forward += (GameWindow.IsKeyDown(0x57) ? 1 : 0) - (GameWindow.IsKeyDown(0x53) ? 1 : 0);   // W, S

        GameWindow.MouseWheel += delta =>
        {
            if (GarageCamera.IsActive) GarageCamera.Zoom(delta / 120f * 0.5f);   // half a metre a notch
        };
    }
}
Imports System.Numerics
Imports FH2
Imports FH2ModPlatform

Public Class MouseCameras
    Inherits [Mod]

    Private _last As Vector2

    Public Overrides ReadOnly Property Info As ModInfo
        Get
            Return New ModInfo("MouseCameras", "1.0.0")
        End Get
    End Property

    Protected Overrides Sub OnInit()
        AddHandler Menus.Frame,
            Sub(dt)
                Dim at = Menus.ToUi(GameWindow.Cursor)
                Dim map = MapView.Current
                If map IsNot Nothing AndAlso GameWindow.IsMouseDown(MouseButton.Left) Then map.Pan(at - _last)
                _last = at
            End Sub

        AddHandler PhotoCamera.InputRead,
            Sub(input)
                input.Forward += If(GameWindow.IsKeyDown(&H57), 1.0F, 0.0F) - If(GameWindow.IsKeyDown(&H53), 1.0F, 0.0F)   ' W, S
            End Sub

        AddHandler GameWindow.MouseWheel,
            Sub(delta)
                If GarageCamera.IsActive Then GarageCamera.Zoom(delta / 120.0F * 0.5F)   ' half a metre a notch
            End Sub
    End Sub
End Class

MapView

⚠️ Not tested much in the game yet.

The map, while it's open. It's seen from straight above: X and Z are the world's, and in a Vector2 the world's Z is .Y. The map's cursor is what the stick moves; the camera above follows the cursor once it strays from the middle.

Member What it does
Current (static property, MapView?) The map on screen (it has the focus), or null.
Cursor (property, Vector2, get/set) The map's cursor in the world (X, Z): what A selects or puts a waypoint on.
Camera (property, Vector2, get/set) Where the camera is (X, Z). It eases towards Target.
Target (property, Vector2, get/set) Where the camera is going (X, Z).
Zoom (property, float, get/set) The zoom, 0..1 as the game keeps it. Setting it keeps it between ZoomMin and ZoomMax.
ZoomMin (property, float) One end of the zoom's range.
ZoomMax (property, float) The other end of the zoom's range.
Leash (property, float, get/set) How far the cursor goes from the middle before the camera follows it, in world units.
UnitsPerPixel (property, float) World units per UI pixel at the current zoom.
WorldAt(Vector2 ui) (method, returns Vector2) The spot under a point in the UI's pixels, in the world (X, Z). Use it to put the map's cursor under the mouse.
Pan(Vector2 ui) (method) Moves what's on the map by ui UI pixels, like a drag: moves both the camera and where it's going.
Handle (property, nint) The map's address. Two MapViews of the same map have the same one.

PhotoCamera

⚠️ Not tested much in the game yet.

Photo mode's camera, as it reads its controller each frame.

Member What it does
InputRead (static event, Action<PhotoCameraInput>) The photo camera has just read its controller (every frame in photo mode, on the game's thread). Add your own values to what it read.

PhotoCameraInput (class)

What the photo camera read from its controller this frame: stick values, -1..1, that you can add to.

Member What it does
Forward (property, float, get/set) Forward (+) and back: the left stick up and down.
Right (property, float, get/set) Right (+) and left, as the camera has it: the left stick sideways.
Rise (property, float, get/set) Up (+) and down.
TurnX (property, float, get/set) Turning sideways (the right stick).
TurnY (property, float, get/set) Turning up and down (the right stick).
Zoom (property, float, get/set) Zooming in (+) and out.

GarageCamera

⚠️ Not tested much in the game yet.

The garage's camera, and the other menus' car cameras that orbit the car.

Member What it does
IsActive (static property, bool) True while the garage camera is updating (within the last 200 ms).
Zoom(float metres) (static method) Moves the orbiting camera closer (+) or further (-) by metres at its next update. The distance is kept between 1.5 and 8 m. The game's own steps are 2, 3.5 and 5 m.

Notes

  • They start with Menus. PhotoCamera and GarageCamera switch on when Menus does: the first time a mod reads Menus.IsReady or subscribes to Menus.Frame. (Subscribing to Menus.Frame for MapView already does it.)
  • MapView.Current uses Menus.Focused, so it throws InvalidOperationException off the UI thread. Get it again each frame; don't keep it.
  • WorldAt and Pan take the UI's pixels. Turn the mouse position into them with Menus.ToUi(GameWindow.Cursor).
  • PhotoCameraInput values are what the controller gave. Add to them (+=) to keep the controller working alongside your keys. Use the object only inside your handler.
  • In the Autoshow, two cameras read the controller, so InputRead can come twice a frame there.
  • An exception in an InputRead handler is written to platform.log. If your mod is a hosted mod and it's reloaded, its InputRead handlers are removed.
  • GarageCamera.Zoom adds up: calls before the camera's next update are added together. If the camera isn't updating, the amount waits until it next does, so check IsActive first. The step only applies while the camera is orbiting the car.
  • Unsupported game build: if the game's code isn't as expected, PhotoCamera or GarageCamera switches itself off and platform.log says "PhotoCamera off: ..." or "GarageCamera off: ...". InputRead then never comes, and GarageCamera.IsActive stays false. MapView.Current returns null if it doesn't recognise the map.

See also

Clone this wiki locally