flutter3d_pointer_lock 0.5.0 copy "flutter3d_pointer_lock: ^0.5.0" to clipboard
flutter3d_pointer_lock: ^0.5.0 copied to clipboard

[pending analysis]

Locks the mouse pointer and reports its motion as relative deltas, which Flutter offers on no desktop platform and does not surface in a browser. Formerly pointer_lock.

flutter3d_pointer_lock #

Mouse capture for a game loop that reads mouse deltas once per step, from the flutter3d engine.

Until 0.4.2+2 this package was published as pointer_lock. From 0.4.3 on it is flutter3d_pointer_lock; the code is the same, and 0.5.0's changelog lists what changed after the rename.

For an ordinary Flutter app, helgoboss_pointer_lock is the more natural choice.

flutter pub add flutter3d_pointer_lock
import 'package:flutter3d_pointer_lock/flutter3d_pointer_lock.dart';

Locks the mouse pointer in place and reports its motion as relative deltas.

Flutter exposes no pointer lock on any desktop platform and does not surface the browser's either, so a first-person camera cannot work out of the box: the cursor reaches the edge of the window and the view stops turning. This plugin adds pointer lock on both.

final capture = PointerLock.instance;

if (capture.isSupported) {
  await capture.capture();
}

// Once per simulation step:
final delta = capture.drainDelta();
yaw   += delta.dx * sensitivity;
pitch += delta.dy * sensitivity;

Pull, not push #

The caller drains the delta with drainDelta instead of receiving it from a stream, because that is the shape a game wants. A simulation on a fixed timestep asks "how far did the mouse move since the last step" once per step. A stream of individual mouse events would move the accumulation into every caller, and each of them would have to get it right.

drainDelta is synchronous for the same reason. It is called from inside the step, and awaiting anything there would mean the step no longer sees a consistent snapshot of its inputs.

Releasing #

Losing window focus drops the capture and announces it on onStateChanged. Otherwise a hidden cursor would be left over another application, and the user could not get it back. Treat an unrequested CaptureState.released as a reason to pause.

The plugin does not watch for Escape. Key handling belongs to the application, which calls release() itself.

Platforms #

Platform Status How
macOS supported CGAssociateMouseAndMouseCursorPosition(0), NSCursor.hide(), a local NSEvent monitor
Web, desktop browser supported document.requestPointerLock through package:web. Pure Dart, so nothing is registered and flutter test --platform chrome reaches it
Web, phone or tablet not applicable reported by (pointer: coarse); isSupported is false so the game shows its touch controls
Windows supported Raw Input for the motion, ClipCursor to the window's centre, ShowCursor(FALSE)
Linux supported on X11 gdk_seat_grab with a blank cursor, the pointer warped back to the centre after each motion. Wayland forbids the warp, so there the deltas stop at the window's edge
Android supported with a mouse, API 26 on View.requestPointerCapture on the Flutter view, the motion from its captured-pointer listener; a click is handed to Flutter at the view's centre
iPadOS supported with a mouse or a trackpad, full screen prefersPointerLocked on the Flutter view controller, the motion from GCMouse; the scene's UIPointerLockState says when the lock is held
iPhone, a phone or tablet with no mouse not applicable no pointer to capture; isSupported is false

The backend answers isSupported; the caller does not have to guess. An application can then offer another control scheme instead of discovering the gap at the first call. The engine above this plugin reads exactly that flag: in a build where it is false, the camera turns by dragging.

On a phone or a tablet the answer follows the mouse: it is false until the native side has said one is connected, a moment after start, and changes when one comes or goes. Listen to onSupportChanged to swap touch controls for the mouse:

capture.onSupportChanged.listen((supported) {
  setState(() => showTouchControls = !supported);
});

What an iPad asks for #

iPadOS grants the lock only to a scene that fills the screen, so a game in Split View or Slide Over stays released, and onStateChanged never reports a capture it did not get. The Flutter view controller has to be the root of its window, or a container above it has to return it from childViewControllerForPointerLock. The plugin adds the prefersPointerLocked answer to the controller's class at run time, so the application does not subclass anything.

What a browser does that a desktop does not #

A capture must come from a user gesture. The browser refuses requestPointerLock when it is called from a timer, a future or a frame callback, so ask inside the handler of the press that prompted it.

A refusal arrives as an event, not an exception. It comes as pointerlockerror, and on browsers that return a promise, also as a rejected promise. The plugin handles both, and a refused capture leaves the state released instead of pretending it succeeded.

The player can leave without asking. Escape releases the lock, and so does switching tabs. Both arrive as an unrequested CaptureState.released, which is the signal to pause.

In an iframe the parent page decides. A page embedding the game needs allow="pointer-lock" on the iframe, or every capture is refused and nothing in the console says why.

Hot restart #

The native side outlives the Dart isolate. Without a reset, a hot restart while the pointer is captured would leave the cursor hidden, with nothing left that remembers to ask for it back. So construction always issues a reset first.

Prior art #

helgoboss_pointer_lock (source, MIT) solves the same problem across more platforms, and the macOS technique here came from it. This plugin differs from it in three places a game needed:

  • an observer for focus loss, so Cmd+Tab does not strand the cursor;
  • deltas read by the caller once per step;
  • isSupported, for a build that also targets mobile.

Cost #

On the desktops, one platform-channel message per mouse event, carrying a two-element Float64List. Nobody has yet measured whether that shows up in a frame profile at a 1000 Hz polling rate. Android and iPadOS already sum the motion natively and send it once a frame, and the desktops can do the same if the profile says so.


Part of flutter3d, an independent implementation of a 3D engine for Flutter. It is not a fork or a binding of another engine, and it is not affiliated with Google or the Flutter project. It has four switchable rendering backends: Impeller via Flutter GPU, WebGL2, WebGPU and a software rasteriser. It loads glTF, OBJ and .f3d, and has six lighting models, shadows, bloom, skinning, animation, BVH culling and picking, plus a deterministic fixed-step game layer with collision, navigation, positional audio, and gamepad and touch input. Four example games (shooter, platformer, racing, strategy) are each built on a genre package: flutter3d_game_shooter, flutter3d_game_platformer, flutter3d_game_racing, flutter3d_game_strategy. A new game starts from the editor's scaffold, which writes one from a template: https://flutter3d.pleion.dev/first-project/. Documentation: https://flutter3d.pleion.dev.

0
likes
0
points
0
downloads

Publisher

verified publisherpleion.dev

Weekly Downloads

Locks the mouse pointer and reports its motion as relative deltas, which Flutter offers on no desktop platform and does not surface in a browser. Formerly pointer_lock.

Homepage
Repository (GitHub)
View/report issues

Topics

#input #mouse #pointer-lock #game-development

License

(pending) (license)

Dependencies

flutter, plugin_platform_interface, web

More

Packages that depend on flutter3d_pointer_lock

Packages that implement flutter3d_pointer_lock