$npx -y skills add thedivergentai/GD-Agentic-Skills --skill godot-adapt-desktop-to-mobileExpert patterns for porting desktop games to mobile including touch control schemes (virtual joystick, gesture detection), UI scaling for small screens, performance optimization for mobile GPUs, battery life management, and platform-specific features. Use when creating mobile por
| 1 | # Adapt: Desktop to Mobile |
| 2 | |
| 3 | Expert guidance for porting desktop games to mobile platforms. |
| 4 | |
| 5 | ## NEVER Do |
| 6 | |
| 7 | - **NEVER use mouse position directly** — Touch has no "hover" state. Replace mouse_motion with screen_drag and check InputEventScreenTouch.pressed. |
| 8 | - **NEVER keep small UI elements** — Apple HIG requires 44pt minimum touch targets. Android Material: 48dp. Scale up buttons 2-3x. |
| 9 | - **NEVER forget finger occlusion** — User's finger blocks 50-100px radius. Position critical info ABOVE touch controls, not below. |
| 10 | - **NEVER run at full performance when backgrounded** — Mobile OSs kill apps that drain battery in background. Pause physics, reduce FPS to 1-5 when app loses focus. |
| 11 | - **NEVER use desktop-only features** — Mouse hover, right-click, keyboard shortcuts, scroll wheel don't exist on mobile. Provide touch alternatives. |
| 12 | |
| 13 | --- |
| 14 | |
| 15 | ## Godot 4.7: Desktop→Mobile |
| 16 | |
| 17 | - Use built-in **virtual joystick** instead of third-party touch plugins where possible. |
| 18 | |
| 19 | ## Available Scripts |
| 20 | |
| 21 | > **MANDATORY**: Read the appropriate script before implementing the corresponding pattern. |
| 22 | |
| 23 | ### [dynamic_joystick_spawner.gd](scripts/dynamic_joystick_spawner.gd) |
| 24 | Expert Dynamic Virtual Joystick that appears exactly where the user touches the left half of the screen instead of relying on fixed UI positions. |
| 25 | |
| 26 | ### [resolution_scaler.gd](scripts/resolution_scaler.gd) |
| 27 | Adaptive Viewport scaler that dynamically drops the `scaling_3d_scale` to maintain 60FPS on weak GPUs while keeping the 2D UI perfectly sharp. |
| 28 | |
| 29 | ### [gesture_combo_system.gd](scripts/gesture_combo_system.gd) |
| 30 | Advanced touch gesture recognizer tracking duration, distance, and multi-touch ratios to output precise swipe and pinch-to-zoom signals. |
| 31 | |
| 32 | ### [battery_saver_mode.gd](scripts/battery_saver_mode.gd) |
| 33 | Crucial lifecycle manager that hooks `NOTIFICATION_APPLICATION_PAUSED` to instantly lock `Engine.max_fps = 1` and pause physics to prevent the OS from killing the app due to background battery drain. |
| 34 | |
| 35 | ### [ui_safe_area_margins.gd](scripts/ui_safe_area_margins.gd) |
| 36 | Dynamic MarginContainer script querying `DisplayServer.get_display_safe_area()` to automatically pad UI elements around iPhone notches and Android hole-punch cameras. |
| 37 | |
| 38 | ### [touch_camera_pan_zoom.gd](scripts/touch_camera_pan_zoom.gd) |
| 39 | Smooth Camera2D controller combining 1-finger relative panning and 2-finger distance-ratio pinch zooming simultaneously. |
| 40 | |
| 41 | ### [haptic_feedback_manager.gd](scripts/haptic_feedback_manager.gd) |
| 42 | Centralized singleton triggering `Input.vibrate_handheld` for Android, and demonstrating the hook pattern for iOS native haptic plugins. |
| 43 | |
| 44 | ### [mobile_shader_fallback.gd](scripts/mobile_shader_fallback.gd) |
| 45 | SceneTree crawler that strips expensive sub-surface scattering, clearcoats, and dynamic shading from `StandardMaterial3D` on weak mobile renderers. |
| 46 | |
| 47 | ### [on_screen_keyboard_handler.gd](scripts/on_screen_keyboard_handler.gd) |
| 48 | Listens to `DisplayServer.virtual_keyboard_update` to tween the entire UI upward, preventing the OS keyboard from occluding LineEdits. |
| 49 | |
| 50 | ### [offline_save_sync.gd](scripts/offline_save_sync.gd) |
| 51 | Memory-based save dictionary that bypasses `NOTIFICATION_WM_CLOSE_REQUEST` (which fails on mobile kill) and guarantees encrypted disk writes during the App Pause lifecycle. |
| 52 | |
| 53 | |
| 54 | --- |
| 55 | |
| 56 | ## Touch Control Schemes |
| 57 | |
| 58 | ### Decision Matrix |
| 59 | |
| 60 | | Genre | Recommended Control | Example | |
| 61 | |-------|-------------------|---------| |
| 62 | | Platformer | Virtual joystick (left) + jump button (right) | Super Mario Run | |
| 63 | | Top-down shooter | Dual-stick (move left, aim right) | Brawl Stars | |
| 64 | | Turn-based | Direct tap on units/tiles | Into the Breach | |
| 65 | | Puzzle | Tap, swipe, pinch gestures | Candy Crush | |
| 66 | | Card game | Drag-and-drop | Hearthstone | |
| 67 | | Racing | Tilt steering or tap left/right | Asphalt 9 | |
| 68 | |
| 69 | ### Virtual Joystick |
| 70 | |
| 71 | ```gdscript |
| 72 | # virtual_joystick.gd |
| 73 | extends Control |
| 74 | |
| 75 | signal direction_changed(direction: Vector2) |
| 76 | |
| 77 | @export var dead_zone: float = 0.2 |
| 78 | @export var max_distance: float = 100.0 |
| 79 | |
| 80 | var stick_center: Vector2 |
| 81 | var is_pressed: bool = false |
| 82 | var touch_index: int = -1 |
| 83 | |
| 84 | @onready var base: Sprite2D = $Base |
| 85 | @onready var knob: Sprite2D = $Knob |
| 86 | |
| 87 | func _ready() -> void: |
| 88 | stick_center = base.position |
| 89 | |
| 90 | func _input(event: InputEvent) -> void: |
| 91 | if event is InputEventScreenTouch: |
| 92 | if event.pressed and is_point_inside(event.position): |
| 93 | is_pressed = true |
| 94 | touch_index = event.index |
| 95 | elif not event.pressed and event.index == touch_index: |