
Godot Platform Mobile
- 224 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-platform-mobile for development tasks
About
godot-platform-mobile: A skill for development. This provides functionality for development workflows.
- godot-platform-mobile
Godot Platform Mobile by the numbers
- 224 all-time installs (skills.sh)
- +26 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,781 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-platform-mobileAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 224 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-platform-mobile for development tasks
Files
Platform: Mobile
Touch-first input, safe area handling, and battery optimization define mobile development.
NEVER Do (Expert Mobile Rules)
Input & Display
- NEVER use mouse events for touch interaction — Relying on
InputEventMouseButtonon mobile is unreliable. Always useInputEventScreenTouchandInputEventScreenDragfor high-fidelity multi-touch support. - NEVER ignore display safe areas (notches/cutouts) — UI placed behind a camera notch is unusable. Query
DisplayServer.get_display_safe_area()and offset critical UI accordingly. - NEVER assume fixed orientation — Locking a landscape game without handling the
size_changedsignal leads to broken layouts on foldable devices or tablet orientation shifts.
Battery & Performance
- NEVER maintain high framerate when backgrounded — Keeping an app at 60 FPS in the background drains battery. Use
NOTIFICATION_APPLICATION_PAUSEDto dropEngine.max_fpsto 1. - NEVER use the Forward+ renderer for mobile — Most mobile GPUs are not optimized for Forward+. Use the dedicated Mobile or Compatibility renderers for optimal fill-rate.
- NEVER leave 'ETC2/ASTC' texture compression disabled — Uncompressed desktop textures will crash mobile devices due to VRAM exhaustion.
Permissions & OS Integration
- NEVER assume Android permissions are automatically granted — You MUST explicitly call
OS.request_permission()and verify withOS.get_granted_permissions(). - NEVER call handheld vibration without permission — On Android, vibration calls are ignored unless the
VIBRATEpermission is enabled in the export preset. - NEVER block the main thread for I/O — Large file saves on mobile can trigger ANR (Application Not Responding) errors. Use background threads.
---
Available Scripts
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
mobile_gesture_recognizer.gd
Expert multi-touch logic for pinch-to-zoom and two-finger rotation.
adaptive_safe_area_inset.gd
Dynamic safe-area (notch) handling using DisplayServer insets.
thermal_throttle_monitor.gd
Battery and heat management via NOTIFICATION_APPLICATION_PAUSED.
mobile_iap_flow_boilerplate.gd
Unified boilerplate for Android/iOS In-App Purchases (IAP).
haptic_pattern_generator.gd
Advanced vibration patterns for mobile haptic feedback.
android_runtime_permissions.gd
Expert Android permission requesting and verification logic.
mobile_sensor_fusion.gd
Stable motion controls using Accelerometer and Gravity fusion.
orientation_layout_adaptor.gd
Adaptive UI swapping for Landscape/Portrait transitions.
mobile_vram_optimizer.gd
VRAM monitoring and texture compression enforcement rules.
native_share_invoker.gd
OS-level native share sheet integration for social features.
---
# Replace mouse/keyboard with touch
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
if event.pressed:
on_touch_start(event.position)
else:
on_touch_end(event.position)
elif event is InputEventScreenDrag:
on_touch_drag(event.position, event.relative)Virtual Joystick
# virtual_joystick.gd
extends Control
signal joystick_moved(direction: Vector2)
var is_pressed := false
var center: Vector2
var touch_index := -1
func _gui_input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
if event.pressed:
is_pressed = true
center = event.position
touch_index = event.index
elif event.index == touch_index:
is_pressed = false
joystick_moved.emit(Vector2.ZERO)
elif event is InputEventScreenDrag and event.index == touch_index:
var direction := (event.position - center).normalized()
joystick_moved.emit(direction)Responsive UI
# Adapt to screen size
func _ready() -> void:
get_viewport().size_changed.connect(_on_viewport_resized)
_on_viewport_resized()
func _on_viewport_resized() -> void:
var viewport_size := get_viewport().get_visible_rect().size
var aspect := viewport_size.x / viewport_size.y
if aspect < 1.5: # Tall screen
$UI.layout_mode = VBoxContainer.LAYOUT_MODE_VERTICAL
else: # Wide screen
$UI.layout_mode = HBoxContainer.LAYOUT_MODE_HORIZONTALBattery Optimization
# Lower frame rate when inactive
func _notification(what: int) -> void:
match what:
NOTIFICATION_APPLICATION_FOCUS_OUT:
Engine.max_fps = 30
NOTIFICATION_APPLICATION_FOCUS_IN:
Engine.max_fps = 60Safe Areas (Notches)
func apply_safe_area() -> void:
var safe_area := DisplayServer.get_display_safe_area()
# Adjust UI margins
$UI.offset_top = safe_area.position.y
$UI.offset_left = safe_area.position.xPerformance Settings
# project.godot mobile settings
[rendering]
renderer/rendering_method="mobile"
textures/vram_compression/import_etc2_astc=true
[display]
window/handheld/orientation="landscape"App Store Metadata
- Icons: 512x512 (Android), 1024x1024 (iOS)
- Screenshots: Multiple resolutions
- Privacy policy required
- Age rating
Best Practices
1. Touch-First - Design for fingers, not mouse 2. Performance - Target 60 FPS on mid-range 3. Battery - Reduce FPS when backgrounded 4. Permissions - Request only what you need
1. Android-Back-Button Handler (Navigation-Stack Popping)
Intercept the Android hardware Back button to pop a UI navigation stack. Disable SceneTree.quit_on_go_back and listen for NOTIFICATION_WM_GO_BACK_REQUEST in a global manager.
class_name MobileNavigationManager extends Node
## Autoload: Intercepts the Android Back button to pop UI screens.
var _ui_stack: Array[Control] = []
func _ready() -> void:
# Stop the app from quitting immediately
get_tree().set_quit_on_go_back(false)
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_GO_BACK_REQUEST:
if _ui_stack.size() > 0:
var screen := _ui_stack.pop_back()
screen.hide()
else:
get_tree().quit()2. Vibration-Intensity Haptic Profiles
Create nuanced haptic profiles by defining specific amplitudes and durations. For Android, ensure the VIBRATE permission is enabled in the export preset.
class_name MobileHaptics extends Node
## Executes predefined haptic feedback profiles.
func play_light_haptic() -> void:
# 30ms duration, 20% strength
Input.vibrate_handheld(30, 0.2)
func play_heavy_haptic() -> void:
# 400ms duration, 100% strength
Input.vibrate_handheld(400, 1.0)3. Mobile-Shader-Precompiler (Prevent Frame-Hitches)
To avoid shader compilation stutter, force the GPU to compile pipelines during a loading screen. For Forward+/Mobile renderers, instance hidden effects. For Compatibility, force-draw them in the camera frustum for one frame.
class_name MobileShaderPrecompiler extends Node3D
## Forces RenderingServer to compile pipelines during loading.
@export var effects_to_precompile: Array[PackedScene] = []
func _ready() -> void:
for scene in effects_to_precompile:
var instance := scene.instantiate() as Node3D
add_child(instance)
# Hidden nodes trigger compilation in Forward+/Mobile
instance.hide()
# Compatibility renderer needs one visible frame in frustum
if ProjectSettings.get_setting("rendering/renderer/rendering_method") == "gl_compatibility":
for child in get_children():
child.show()
child.position = Vector3(0, 0, -2) # In front of camera
await get_tree().process_frame
for child in get_children(): child.queue_free()Reference
- Related:
godot-export-builds,godot-ui-containers
Related
- Master Skill: godot-master
class_name AdaptiveSafeAreaInset
extends Node
## Expert Safe Area handler for modern mobile notches and punch-holes.
## Automatically applies insets to UI margins based on DisplayServer data.
@export var target_control: Control
func _ready() -> void:
get_viewport().size_changed.connect(_update_safe_area)
_update_safe_area()
func _update_safe_area() -> void:
if not target_control: return
var safe_area := DisplayServer.get_display_safe_area()
var screen_size := DisplayServer.screen_get_size()
# Convert absolute safe area to relative offsets
target_control.offset_top = safe_area.position.y
target_control.offset_left = safe_area.position.x
target_control.offset_bottom = -(screen_size.y - safe_area.end.y)
target_control.offset_right = -(screen_size.x - safe_area.end.x)
## Rule: Always use safe-area insets for critical gameplay UI (Health, Menu buttons).
class_name AndroidRuntimePermissions
extends Node
## Expert Android runtime permission handler.
## Correctly requests and checks for hardware permissions to avoid store rejections.
const STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE"
const CAMERA_PERMISSION = "android.permission.CAMERA"
func request_access_to_camera() -> void:
if OS.get_name() != "Android": return
if not OS.get_granted_permissions().has(CAMERA_PERMISSION):
OS.request_permission(CAMERA_PERMISSION)
_check_permission_status.call_deferred(CAMERA_PERMISSION)
func _check_permission_status(permission: String) -> void:
if OS.get_granted_permissions().has(permission):
print("Mobile: Permission granted: ", permission)
else:
print("Mobile: Permission denied: ", permission)
class_name HapticPatternGenerator
extends Node
## Expert haptic feedback system for Mobile triggers.
## Provides complex vibration lengths for feedback (Success, Failure, Impact).
func vibrate_success() -> void:
# Short double pulse
Input.vibrate_handheld(50)
await get_tree().create_timer(0.1).timeout
Input.vibrate_handheld(50)
func vibrate_failure() -> void:
# Long heavy pulse
Input.vibrate_handheld(400)
func vibrate_impact(intensity: float = 1.0) -> void:
# Variable intensity vibration
Input.vibrate_handheld(int(100 * intensity))
class_name MobileGestureRecognizer
extends Node
## Expert multi-touch gesture detection for Mobile.
## Handles Pinch-to-Zoom, Two-Finger Rotation, and Swipe detection.
signal pinch_performed(factor: float)
signal rotate_performed(angle: float)
signal swipe_performed(direction: Vector2)
var _touches := {}
var _last_distance := 0.0
var _last_angle := 0.0
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
if event.pressed:
_touches[event.index] = event.position
else:
_touches.erase(event.index)
if _touches.size() < 2:
_last_distance = 0.0
_last_angle = 0.0
elif event is InputEventScreenDrag:
_touches[event.index] = event.position
if _touches.size() == 2:
_handle_two_finger_gesture()
func _handle_two_finger_gesture() -> void:
var keys = _touches.keys()
var p1: Vector2 = _touches[keys[0]]
var p2: Vector2 = _touches[keys[1]]
var current_dist = p1.distance_to(p2)
var current_angle = p1.angle_to_point(p2)
if _last_distance > 0:
pinch_performed.emit(current_dist / _last_distance)
if _last_angle != 0:
rotate_performed.emit(current_angle - _last_angle)
_last_distance = current_dist
_last_angle = current_angle
class_name MobileIAPFlowBoilerplate
extends Node
## Expert boilerplate for In-App Purchases (IAP).
## Abstracts GooglePlayBilling and AppleInAppStore functionality.
var _payment: Object = null
func _ready() -> void:
if Engine.has_singleton("GodotGooglePlayBilling"):
_payment = Engine.get_singleton("GodotGooglePlayBilling")
_payment.startConnection()
elif Engine.has_singleton("InAppStore"):
_payment = Engine.get_singleton("InAppStore")
func purchase_product(product_id: String) -> void:
if not _payment:
push_error("MobileIAP: No payment singleton found.")
return
# Platform specific purchase logic...
if _payment.has_method("purchase"):
_payment.purchase({"product_id": product_id})
## Expert: Always perform server-side receipt validation for production games.
# skills/platform-mobile/scripts/mobile_safe_area_handler.gd
extends Control
## Mobile Safe Area Handler Expert Pattern
## Dynamic UI Adjuster for notches, punch-holes, and rounded corners.
class_name MobileSafeAreaHandler
enum Side { SIDE_LEFT, SIDE_TOP, SIDE_RIGHT, SIDE_BOTTOM }
@export_group("References")
@export var left_margin: Control
@export var right_margin: Control
@export var top_margin: Control
@export var bottom_margin: Control
@export_group("Layout")
@export var extra_padding: int = 0 # Extra safety padding in pixels
func _ready() -> void:
# Monitor for orientation changes
get_tree().root.size_changed.connect(_update_margins)
# Initial update
_update_margins()
func _update_margins() -> void:
var safe_area = DisplayServer.get_display_safe_area()
var screen_size = DisplayServer.screen_get_size()
# Calculate offsets based on safe area rect relative to screen
var top_offset = safe_area.position.y
var left_offset = safe_area.position.x
var right_offset = screen_size.x - (safe_area.position.x + safe_area.size.x)
var bottom_offset = screen_size.y - (safe_area.position.y + safe_area.size.y)
# Apply to UI containers or margins if assigned
if top_margin:
_apply_margin(top_margin, Side.SIDE_TOP, top_offset)
if bottom_margin:
_apply_margin(bottom_margin, Side.SIDE_BOTTOM, bottom_offset)
if left_margin:
_apply_margin(left_margin, Side.SIDE_LEFT, left_offset)
if right_margin:
_apply_margin(right_margin, Side.SIDE_RIGHT, right_offset)
func _apply_margin(node: Control, side: Side, offset: float) -> void:
# Only apply if offset is significant (e.g. > 0)
if offset > 0:
var total = offset + extra_padding
match side:
Side.SIDE_TOP, Side.SIDE_BOTTOM:
node.custom_minimum_size.y = total
Side.SIDE_LEFT, Side.SIDE_RIGHT:
node.custom_minimum_size.x = total
else:
# Reset if no notch
match side:
Side.SIDE_TOP, Side.SIDE_BOTTOM:
node.custom_minimum_size.y = 0
Side.SIDE_LEFT, Side.SIDE_RIGHT:
node.custom_minimum_size.x = 0
## EXPERT USAGE:
## Attach to a root Control. Assign margin containers (ColorRects or empty Controls)
## that push your main UI content inward.
class_name MobileSensorFusion
extends Node
## Expert usage of mobile hardware sensors for motion controls.
## Fuses Accelerometer and Gravity for stable gameplay input.
func get_tilt_input() -> Vector3:
var accel := Input.get_accelerometer()
var gravity := Input.get_gravity()
# Compute stable tilt by subtracting gravity from raw accelerometer
var tilt := accel - gravity
return tilt.normalized()
func get_device_rotation() -> Vector3:
return Input.get_gyroscope()
## Rule: Always normalize sensor data to account for varying hardware sensitivity.
class_name MobileVRAMOptimizer
extends Node
## Expert VRAM and memory monitor for mobile devices.
## Flushes texture caches or lowers resolution when memory is critical.
func _process(_delta: float) -> void:
# Check engine memory usage
var usage_mb := OS.get_static_memory_usage() / 1024 / 1024
if usage_mb > 1800: # Threshold for high-end mobile
_flush_expensive_resources()
func _flush_expensive_resources() -> void:
# Expert: Manually trigger garbage collection or resource flushing
# if your game uses massive temporary scenes.
pass
## Rule: Always enable 'ETC2/ASTC' compression in Export Presets.
class_name NativeShareInvoker
extends Node
## Expert OS-level sharing for Mobile.
## Proxies to native share sheets for text and images.
func share_text(text: String, title: String = "Share My Score") -> void:
if OS.has_feature("android") or OS.has_feature("ios"):
# Requires a native plugin (e.g., 'GodotShare')
# This boilerplate shows the typical API call
if Engine.has_singleton("GodotShare"):
Engine.get_singleton("GodotShare").shareText(title, "Checkout my score!", text)
else:
print("Mobile: Native share only available on mobile devices.")
class_name OrientationLayoutAdaptor
extends Node
## Expert adaptive orientation handler.
## Swaps UI layouts dynamically when the device is rotated.
@export var landscape_root: Control
@export var portrait_root: Control
func _ready() -> void:
get_viewport().size_changed.connect(_on_screen_resized)
_on_screen_resized()
func _on_screen_resized() -> void:
var size := DisplayServer.screen_get_size()
var is_portrait := size.y > size.x
if landscape_root: landscape_root.visible = not is_portrait
if portrait_root: portrait_root.visible = is_portrait
## Tip: Use separate CanvasLayers for Landscape/Portrait roots for easier design.
# platform_mobile_patterns.gd
extends Node
# 1. Processing Application Suspension
# EXPERT NOTE: Mobile OSs aggressively suspend apps. Save state immediately.
func _notification(what: int) -> void:
if what == NOTIFICATION_APPLICATION_PAUSED:
print("Mobile app suspended. Saving progress...")
# SaveManager.save_game_state()
# 2. Emulating Mouse Events from Touch
# EXPERT NOTE: Allows drag/click logic to work without rewriting code for touch.
func enable_touch_emulation() -> void:
Input.emulate_mouse_from_touch = true
# 3. Querying Safe Areas (Notch/Cutout Support)
# EXPERT NOTE: Respect camera notches so essential UI doesn't get obscured.
func adjust_ui_for_safe_area(container: Control) -> void:
var safe_rect := DisplayServer.get_display_safe_area()
# Apply global screen safe area to the UI container
container.position = safe_rect.position
container.size = safe_rect.size
# 4. Triggering the Software Keyboard
# EXPERT NOTE: Invoke the native virtual keyboard for text input fields.
func show_mobile_keyboard(current_text: String = "") -> void:
DisplayServer.virtual_keyboard_show(current_text)
# 5. Accessing Hardware Accelerometer
# EXPERT NOTE: Use for tilt controls. Requires 'sensors/enable_accelerometer' in export.
func _physics_process(_delta: float) -> void:
var tilt_vector := Input.get_accelerometer()
if tilt_vector.length() > 0.1:
# Move logic...
pass
# 6. Invoking Native Mobile Haptics
# EXPERT NOTE: Provides tactical feedback. On Android, requires VIBRATE permission.
func trigger_short_vibration() -> void:
Input.vibrate_handheld(250) # Duration in ms
# 7. Requesting Android Runtime Permissions
# EXPERT NOTE: Mandatory for accessing storage or camera on modern Android versions.
func request_file_permission() -> void:
var permission := "android.permission.READ_EXTERNAL_STORAGE"
if OS.has_feature(&"android"):
OS.request_permission(permission)
# 8. Locking Screen Orientation Programmatically
# EXPERT NOTE: Force landscape for levels and portrait for menus if needed.
func set_portrait_mode() -> void:
DisplayServer.screen_set_orientation(DisplayServer.SCREEN_PORTRAIT)
# 9. Utilizing JavaClassWrapper (Android JNI)
# EXPERT NOTE: Access native Android APIs (like SDK version) via JNI bridge.
func get_android_sdk_int() -> int:
if OS.has_feature(&"android"):
var build_version = JavaClassWrapper.wrap("android.os.Build$VERSION")
return build_version.SDK_INT
return -1
# 10. Modifying Canvas Scaling for High DPI
# EXPERT NOTE: Expands the canvas to fill tall/wide screens without stretching.
func setup_mobile_resolution_policy() -> void:
get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
get_tree().root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
class_name ThermalThrottleMonitor
extends Node
## Expert thermal and battery manager for Mobile.
## Throttles logic/rendering when app is backgrounded or device is hot.
func _notification(what: int) -> void:
match what:
NOTIFICATION_APPLICATION_PAUSED:
# Drop FPS to minimal to save battery and reduce heat
Engine.max_fps = 1
AudioServer.set_bus_mute(0, true)
NOTIFICATION_APPLICATION_RESUMED:
# Restore performance
Engine.max_fps = 60
AudioServer.set_bus_mute(0, false)
func apply_thermal_throttle(is_hot: bool) -> void:
# Expert: Dynamic FPS scaling based on device temperature signal
Engine.max_fps = 30 if is_hot else 60