Created
April 26, 2026 14:29
-
-
Save JimmyCushnie/669b0179d966fcc35a81247bd8722f23 to your computer and use it in GitHub Desktop.
Godot 3 C#: browser-like touchscreen camera controller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Browser-like touchscreen camera controls for Godot 3.
Demo:
screen-20260426-101100-censored.mp4
Notes:
Hope this is useful to someone :)