Week 67: Fancy touch controls

An animated GIF of a recording from Meteor Power showing my recent improvements to camera panning and zooming controls.
Inertial camera panning and pinch-to-zoom!
(on a mobile phone)

tl;dr: Last week I implemented traditional swipe-based level-camera controls in Godot. This includes drag-to-pan, multi-touch pinch-to-zoom, inertial panning, swipe gesture-smoothing, and automatic camera limits based on the level-boundaries.

I'll cover my touch-control implementation in more detail in a separate post!

An animated GIF of a recording from Meteor Power showing my original camera controls.
  • This is how my old camera-pan-controller behaves.
  • It just pans the camera based on the cursor's proximity to the edge of the window.
  • This was designed around the premise that you want to keep your player character in view, while choosing a navigation destination somewhere nearby.

What happened last week?


  • Updated my camera-controller system to support swapping the active pan-and-zoom-controller.
  • Created a traditional mobile touch-based pan-and-zoom camera controller.
    • This includes a lot of cool features:
      • Drag-to-pan.
        • The basics.
      • Multi-touch pinch-to-zoom.
        • Tracking multiple touches is a lot more complicated than just tracking one!
      • Zoom to target position.
        • This is important, because zooming without changing the pan offset position would probably cause the position you're trying to zoom into to exit the screen.
      • Inertial panning.
        • This lets the camera coast to a stop after you're done dragging, which makes it feel much smoother and more natural.
      • Swipe gesture-smoothing.
        • This fixes gesture-velocity values, because gesture positions can be very noisy and imprecise.
      • Automatic camera limits based on the viewable region.
        • This calculates the boundary of the level, and forces the camera to not go too far outside of it.
    • How my swipe inertia works:
      • Completely disabled when a touch is present.
      • In LevelPointerListener, track the (app-physics) time and distance between the latest touch position and a touch position of at most x events ago.
        • Will need to use a more recent x, if there are not at least x events in the current gesture.
      • Use this time and distance diff to calculate a current gesture velocity.
      • When the gesture is released, start applying physics to the camera position.

Laundry list

  • Start refactoring my camera-pan/zoom-control system to support configuration as a traditional mobile touch-based controller.
  • Add support for configuring the default camera-pan-controller.
  • Add support for swapping the current camera-pan-controller based on bot selection.
    • When a bot is selected, use my old navigation preselection camera controller.
    • When a bot isn't selected, use the new more-traditional pan-and-zoom controller.
  • Move the camera-pan-controller system into Scaffolder.
  • Add better support for configuring the camera-pan-controller system.
  • Add support for syncing old pan state when swapping the active camera-pan-controller.
  • Add support for clearing navigation-pre-selection state when swapping camera-pan-controllers.
  • Split-apart camera zoom and offset components for manual contributions, pan-controller contributions, camera-swap contributions, and misc contributions.
    • This allows me to update camera offset/zoom simultaneously from multiple systems, without having them break each other.
  • Move pointer-event tracking out of players and into levels.
  • Add multi-touch support to LevelPointerListener.
  • Update NavigationPreselectionDragPanController to rely on LevelpointerListener for touch updates.
  • Move camera_controller to camera manifest class.
  • Rename zoom_multiplier to zoom.
  • Update LevelPointerListener to track level-space positions separately from screen-space positions.
  • Move some NavigationPreselectionDragPanController-specific logic out of CameraPanController.
  • Implement SwipePanController.
  • Create a new level_manifest dictionary in ScaffolderSchema.
  • Move the level-session reference into level-config.
  • Add support for configuring default level-boundary margins for camera and character boundaries.
  • Add support for limiting the boundary of the camera-pan-controller.
  • Fix logic for clamping camera-pan-controller zoom and pan to the level's camera-bounds.
  • Move scroll-to-zoom functionality out of camera-controller (as a manual override) and into camera-pan-controller (as a standard, limited control).
  • Refactor scroll-to-zoom functionality to zoom into the cursor position rather than the current camera center position.
  • Adjust max-zoom limit.
  • Add support for tracking the center of a pinch.
  • Update pinch-to-zoom to also focus the zoom around the pinch position.
  • Add support for swipe inertia to my camera-pan-and-zoom system.
  • Add support for tracking gesture velocity, and for smoothing this velocity by tracking touch events over a given time window.
  • Fix a bug from checking a touch-registration before the touch was registered.
  • Move CameraPanController to Scaffolder, and re-arrange camera-related files.
  • Move camera parameters into CameraManifest and ScaffolderSchema.
  • Add support for configuring whether the camera max-zoom limit should be defined by the level bounds or by a manifest constant.
  • Fix a bug where the pan would still occur when the zoom is clipped to the min or max, while trying to zoom around a specific target position.
  • Add support for omitting the Sc.logger.print argument or for passing in non-String values.
  • Create a utility function for clamping a vector's length.
  • Update CircularBuffer:
    • Add size(), empty(), and peek() methods.
    • Add support for configuring whether it should automatically free items that are overwritten.

What's next?

  • Continue creating a more-polished version of Meteor Power (my game-jam game) for release as a polished mobile app.
    • The camera refactor was definitely the most complicated task on my list for this.
    • So I should cover a lot more ground next week!

🎉 Cheers!

This is a simple icon representing my sabbatical.