Skip to content

Instantly share code, notes, and snippets.

@JimmyCushnie
Created April 26, 2026 14:29
Show Gist options
  • Select an option

  • Save JimmyCushnie/669b0179d966fcc35a81247bd8722f23 to your computer and use it in GitHub Desktop.

Select an option

Save JimmyCushnie/669b0179d966fcc35a81247bd8722f23 to your computer and use it in GitHub Desktop.
Godot 3 C#: browser-like touchscreen camera controller
using Godot;
// Touch controls for the camera.
// Use two fingers to pan or zoom.
public class TouchCamera2D : Camera2D
{
[Export] public float MinZoom = 0.25f;
[Export] public float MaxZoom = 4.0f;
// All of these are in screen space.
// They can be scaled by Zoom to get them in world space.
Vector2 LastTouchPoint_0, LastTouchPoint_1;
float LastPinchDistance;
Vector2 LastPinchMidpoint;
bool IsTouching_0, IsTouching_1;
bool TwoFingersTouching => IsTouching_0 && IsTouching_1;
bool LastPinchDataIsValid;
// Like Camera2D.Zoom, 0.25 means 4x zoom in, 4.0 means 4x zoom out.
new float Zoom
{
get => base.Zoom.x;
set
{
var zoom = Mathf.Clamp(value, MinZoom, MaxZoom);
base.Zoom = new Vector2(zoom, zoom);
}
}
float GetPinchDistance() => LastTouchPoint_0.DistanceTo(LastTouchPoint_1);
Vector2 GetPinchMidpoint() => (LastTouchPoint_0 + LastTouchPoint_1) / 2f;
public override void _UnhandledInput(InputEvent @event)
{
// The touch event is fired when a new finger comes into contact, or ends contact, with the screen.
// The drag event is fired when a finger which is already down moves.
if (@event is InputEventScreenTouch touch)
HandleTouch(touch);
if (@event is InputEventScreenDrag drag)
HandleDrag(drag);
}
void HandleTouch(InputEventScreenTouch touchEvent)
{
if (touchEvent.Index == 0)
{
LastTouchPoint_0 = touchEvent.Position;
IsTouching_0 = touchEvent.Pressed;
}
if (touchEvent.Index == 1)
{
LastTouchPoint_1 = touchEvent.Position;
IsTouching_1 = touchEvent.Pressed;
}
if (TwoFingersTouching)
{
LastPinchDistance = GetPinchDistance();
LastPinchMidpoint = GetPinchMidpoint();
LastPinchDataIsValid = true;
}
else
{
LastPinchDataIsValid = false;
}
}
void HandleDrag(InputEventScreenDrag touchEvent)
{
if (touchEvent.Index == 0)
LastTouchPoint_0 = touchEvent.Position;
if (touchEvent.Index == 1)
LastTouchPoint_1 = touchEvent.Position;
if (!TwoFingersTouching || !LastPinchDataIsValid)
return;
var currentPinchDistance = GetPinchDistance();
var currentMidpoint = GetPinchMidpoint();
// Handle zoom
{
float viewportWidthPixels = GetViewportRect().Size.x;
// These three variables are all measured in worldspace.
float distanceToZoom = (LastPinchDistance - currentPinchDistance) * Zoom;
float currentViewportSize = viewportWidthPixels * Zoom;
float targetViewportSize = currentViewportSize + distanceToZoom;
float previousZoom = Zoom;
Zoom = targetViewportSize / viewportWidthPixels;
// We must add a camera position offset to account for the fact that the user is not zooming in on the exact center of the screen.
Vector2 viewportCenter = GetViewportRect().Size / 2f;
Vector2 pinchOffset = currentMidpoint - viewportCenter;
Position += pinchOffset * (previousZoom - Zoom);
}
// Handle pan
{
Vector2 panDelta = (currentMidpoint - LastPinchMidpoint) * Zoom;
Position -= panDelta;
}
LastPinchDistance = currentPinchDistance;
LastPinchMidpoint = currentMidpoint;
}
}
@JimmyCushnie

JimmyCushnie commented Apr 26, 2026

Copy link
Copy Markdown
Author

Browser-like touchscreen camera controls for Godot 3.

Demo:

screen-20260426-101100-censored.mp4

Notes:

  • This script assumes the camera's "Anchor Mode" is "Drag Center".
  • It should be easy to port this to Godot 4 or GDScript if you need to.
    • In Godot 4 they changed it from "zoom in = Camera2D.Zoom is small" to "zoom in = Camera2D.Zoom is big" so you'll have to invert the thing on line 27 or something, I'm not sure exactly. Also swap the default values for MinZoom and MaxZoom

Hope this is useful to someone :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment