Skill v1.0.1
currentAutomated scan100/1001 files
version: "1.0.1" name: godot-adapt-mobile-to-desktop description: "Expert patterns for scaling mobile games to desktop including mouse/keyboard controls, increased resolution and graphical fidelity, expanded UI layouts, settings menus, window management, and platform-specific features. Use when creating desktop ports or cross-platform releases. Trigger keywords: mouse_controls, keyboard_shortcuts, resolution_scaling, graphics_settings, fullscreen_toggle, window_modes, Steam_integration, desktop_optimization."
Adapt: Mobile to Desktop
Expert guidance for scaling mobile games to desktop platforms.
NEVER Do
- NEVER keep touch-only controls — Add mouse/keyboard alternatives. Touch controls on desktop feel awkward and limit precision.
- NEVER lock to mobile resolution — Desktop can handle 1920x1080+ and higher frame rates. Upscale UI, increase render distance.
- NEVER hide graphics settings — Desktop players expect quality options (resolution, VSync, shadows, anti-aliasing).
- NEVER use mobile-sized UI — Touch targets (44pt) are too large for mouse. Reduce button/text size by 30-50%.
- NEVER forget window management — Players expect fullscreen, borderless, maximize, and multi-monitor support.
Available Scripts
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
mouse_capture_look.gd
Expert Mouse Capture Controller that completely overrides mobile touch/swipe logic by accumulating InputEventMouseMotion.relative against pitch/yaw variables while clamped.
dynamic_window_manager.gd
Crucial lifecycle manager handling the expectation of PC gamers to toggle between Windowed, Fullscreen Exclusive, and modern Borderless Fullscreen via DisplayServer flags.
keybinding_remapper.gd
Complete runtime input remapper. Mobile relies on hardcoded touch zones, but PC requires the ability to swap WASD to custom keycodes via InputMap and saving to ConfigFile.
cursor_state_manager.gd
Hardware cursor state machine. Replaces the default OS arrow with custom Texture2D hardware cursors and handles hiding the cursor during combat while freeing it in menus.
uncapped_framerate.gd
Expert VSync and FPS unlocker. Mobile locks at 60FPS to save battery; PC gamers expect the ability to disable VSync and unlock Engine.max_fps for 144Hz+ monitors.
resolution_dropdown.gd
Query engine utilizing DisplayServer.screen_get_size to build an OptionButton of supported native 16:9, 21:9, and 4K resolutions without exceeding the user's physical monitor.
desktop_ui_scaler.gd
Recursive SceneTree crawler that scales down massive thumb-sized mobile buttons by a percentage shrink factor specifically on Desktop builds while retaining their anchor points.
scroll_wheel_zoom.gd
Replaces the mobile Pinch-to-Zoom gesture with discrete physical mouse wheel ticks, smoothly interpolating the Camera2D zoom continuously via delta.
quit_confirmation.gd
Hooks get_tree().set_auto_accept_quit(false) to intercept the OS-level 'X' window button, pausing the game and prompting the user to save instead of instantly terminating like mobile.
multi_monitor_handling.gd
Advanced PC window placement script that queries the mouse position to identify the active screen on multi-monitor setups, ensuring the game launches exactly where the user is looking.
Control Scheme Expansion
Touch → Mouse Conversion
# Mobile: Virtual joystick for movementvar direction: Vector2 = virtual_joystick.get_direction()# ⬇️ Desktop: WASD + mouse aimextends CharacterBody2Dfunc _physics_process(delta: float) -> void:# Keyboard movement (WASD)var input := Input.get_vector("move_left", "move_right", "move_up", "move_down")velocity = input.normalized() * SPEED# Mouse aimingvar mouse_pos := get_global_mouse_position()look_at(mouse_pos)move_and_slide()# Configure Project Settings → Input Map:# move_left: A, Left Arrow# move_right: D, Right Arrow# move_up: W, Up Arrow# move_down: S, Down Arrow
Add Keyboard Shortcuts
# desktop_shortcuts.gdextends Nodefunc _input(event: InputEvent) -> void:if event.is_action_pressed("toggle_fullscreen"):toggle_fullscreen()if event.is_action_pressed("quick_save"):save_game()if event.is_action_pressed("toggle_inventory"):$UI/Inventory.visible = not $UI/Inventory.visiblefunc toggle_fullscreen() -> void:if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN:DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)else:DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)# Add to Project Settings → Input Map:# toggle_fullscreen: F11# quick_save: F5# toggle_inventory: I, Tab
Scroll Wheel Support
# Mobile: Pinch to zoom# Desktop: Scroll wheelfunc _input(event: InputEvent) -> void:if event is InputEventMouseButton:if event.button_index == MOUSE_BUTTON_WHEEL_UP:camera.zoom *= 1.1elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:camera.zoom *= 0.9
Graphics Enhancement
Resolution Scaling
# mobile_settings.gd (mobile)func _ready() -> void:get_viewport().size = Vector2i(1280, 720) # Mobile resolution# ⬇️ desktop_settings.gd (desktop)extends Node@export var supported_resolutions: Array[Vector2i] = [Vector2i(1280, 720),Vector2i(1920, 1080),Vector2i(2560, 1440),Vector2i(3840, 2160)]func _ready() -> void:if OS.get_name() in ["Windows", "macOS", "Linux"]:# Start at native resolutionvar screen_size := DisplayServer.screen_get_size()get_window().size = screen_size# Enable higher qualityenable_desktop_graphics()func enable_desktop_graphics() -> void:# Enable MSAAget_viewport().msaa_2d = Viewport.MSAA_2Xget_viewport().msaa_3d = Viewport.MSAA_4X# Enable screen space AAget_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA# Higher shadow resolutionRenderingServer.directional_shadow_atlas_set_size(4096, true)# Enable post-processingvar env := get_viewport().world_3d.environmentif env:env.glow_enabled = trueenv.ssao_enabled = trueenv.adjustment_enabled = true
Settings Menu
# graphics_settings.gdextends Control@onready var resolution_option: OptionButton = $VBoxContainer/Resolution@onready var quality_option: OptionButton = $VBoxContainer/Quality@onready var vsync_check: CheckBox = $VBoxContainer/VSync@onready var fullscreen_check: CheckBox = $VBoxContainer/Fullscreenfunc _ready() -> void:populate_settings()load_settings()func populate_settings() -> void:# Resolution optionsresolution_option.add_item("1280x720")resolution_option.add_item("1920x1080")resolution_option.add_item("2560x1440")resolution_option.add_item("3840x2160")# Quality presetsquality_option.add_item("Low")quality_option.add_item("Medium")quality_option.add_item("High")quality_option.add_item("Ultra")func _on_resolution_selected(index: int) -> void:var resolutions := [Vector2i(1280, 720),Vector2i(1920, 1080),Vector2i(2560, 1440),Vector2i(3840, 2160)]get_window().size = resolutions[index]save_settings()func _on_quality_selected(index: int) -> void:match index:0: # Lowapply_low_quality()1: # Mediumapply_medium_quality()2: # Highapply_high_quality()3: # Ultraapply_ultra_quality()save_settings()func apply_ultra_quality() -> void:get_viewport().msaa_3d = Viewport.MSAA_8Xget_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAARenderingServer.directional_shadow_atlas_set_size(8192, true)var env := get_viewport().world_3d.environmentif env:env.glow_enabled = trueenv.ssao_enabled = trueenv.ssil_enabled = trueenv.sdfgi_enabled = truefunc _on_vsync_toggled(enabled: bool) -> void:if enabled:DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)else:DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)save_settings()func _on_fullscreen_toggled(enabled: bool) -> void:if enabled:DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)else:DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)save_settings()func save_settings() -> void:var config := ConfigFile.new()config.set_value("graphics", "resolution_index", resolution_option.selected)config.set_value("graphics", "quality", quality_option.selected)config.set_value("graphics", "vsync", vsync_check.button_pressed)config.set_value("graphics", "fullscreen", fullscreen_check.button_pressed)config.save("user://settings.cfg")func load_settings() -> void:var config := ConfigFile.new()if config.load("user://settings.cfg") == OK:resolution_option.selected = config.get_value("graphics", "resolution_index", 1)quality_option.selected = config.get_value("graphics", "quality", 2)vsync_check.button_pressed = config.get_value("graphics", "vsync", true)fullscreen_check.button_pressed = config.get_value("graphics", "fullscreen", false)# Apply settings_on_resolution_selected(resolution_option.selected)_on_quality_selected(quality_option.selected)_on_vsync_toggled(vsync_check.button_pressed)_on_fullscreen_toggled(fullscreen_check.button_pressed)
UI Layout Expansion
Mobile UI → Desktop UI
# Mobile: Compact HUD, large touch buttons# Scene: MobileHUD.tscn# - Virtual joystick (bottom-left)# - Action buttons (bottom-right, 80x80px)# ⬇️ Desktop: Spread UI, smaller elements# Scene: DesktopHUD.tscn# - Health/Mana bars (top-left, 40px tall)# - Minimap (top-right, 200x200px)# - Hotbar (bottom-center, 50x50px slots)# - Chat (bottom-left, resizable)extends Controlfunc _ready() -> void:if OS.has_feature("mobile"):_setup_mobile_ui()else:_setup_desktop_ui()func _setup_mobile_ui() -> void:# Large buttons, bottom corners$VirtualJoystick.visible = true$ActionButtons.scale = Vector2(1.5, 1.5)$Minimap.visible = false # Too clutteredfunc _setup_desktop_ui() -> void:# Compact, corners and edges$VirtualJoystick.visible = false$ActionButtons.scale = Vector2(0.8, 0.8)$Minimap.visible = true$ChatBox.visible = true
Window Management
Multi-Monitor Support
# window_manager.gdextends Nodefunc _ready() -> void:# Detect monitorsvar screen_count := DisplayServer.get_screen_count()print("Detected %d monitors" % screen_count)# Allow window dragging between monitorsDisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)func move_to_monitor(monitor_index: int) -> void:var screen_pos := DisplayServer.screen_get_position(monitor_index)var screen_size := DisplayServer.screen_get_size(monitor_index)# Center window on target monitorvar window_size := get_window().sizevar centered_pos := screen_pos + (screen_size - window_size) / 2DisplayServer.window_set_position(centered_pos)
Borderless Fullscreen
func set_borderless_fullscreen(enabled: bool) -> void:if enabled:# Get screen sizevar screen_size := DisplayServer.screen_get_size()# Set window to screen sizeget_window().size = screen_sizeget_window().position = Vector2i.ZERO# Remove borderDisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true)else:DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false)DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
Platform-Specific Features
Steam Integration (Example)
# Requires GodotSteam pluginextends Nodevar steam_initialized := falsefunc _ready() -> void:if OS.get_name() in ["Windows", "Linux", "macOS"]:initialize_steam()func initialize_steam() -> void:var init_result := Steam.steamInit()if init_result.status == Steam.STEAM_OK:steam_initialized = trueprint("Steam initialized")# Enable achievementsSteam.requestStats()func unlock_achievement(achievement_id: String) -> void:if steam_initialized:Steam.setAchievement(achievement_id)Steam.storeStats()
Discord Rich Presence
# Requires Discord SDK integrationextends Nodefunc update_presence(state: String, details: String) -> void:if OS.get_name() == "Windows":# Update Discord presence# (Requires plugin)pass
Performance Enhancements
Unlock Frame Rate
# Mobile: Locked to 60 FPSEngine.max_fps = 60# Desktop: Unlock or match monitor refresh ratefunc _ready() -> void:if not OS.has_feature("mobile"):Engine.max_fps = 0 # Unlimited (use VSync to cap)# Or match monitor:var refresh_rate := DisplayServer.screen_get_refresh_rate()Engine.max_fps = int(refresh_rate)
Increased Draw Distance
# Mobile: Low draw distancevar camera: Camera3Dcamera.far = 100.0# Desktop: Highercamera.far = 500.0# Also increase shadow distancevar sun: DirectionalLight3Dsun.directional_shadow_max_distance = 200.0 # Up from 50
Testing Checklist
- [ ] Mouse controls feel precise (no acceleration issues)
- [ ] All mobile touch controls have keyboard/mouse equivalents
- [ ] Graphics settings menu works correctly
- [ ] Fullscreen, windowed, borderless modes all function
- [ ] Multi-monitor setup works (dragging window, centering)
- [ ] Resolution changes don't crash or distort UI
- [ ] VSync toggle works
- [ ] Runs at 144+ FPS on high-end hardware
- [ ] Settings persist across sessions
- [ ] Game scales well to ultrawide monitors (21:9, 32:9)
Expert Techniques & Optimizations
1. Standalone Desktop Launcher
For heavy desktop builds, use a lightweight Godot "Launcher" executable. This allows users to configure settings via ConfigFile before spawning the main game process using OS.create_process().
func launch_game() -> void:# Save settings to user://settings.cfgconfig.save("user://settings.cfg")# Spawn main game with data pack argumentvar path := OS.get_executable_path()var args := PackedStringArray(["--main-pack", "game_data.pck"])var pid := OS.create_process(path, args)if pid > 0:get_tree().quit()
2. Graphics Benchmark Mode
Automatically suggest optimal settings by running a 5-second performance test. Use ProjectSettings.set_setting() to toggle high-end features like Screen Space AA or SDFGI half-resolution based on average FPS.
func _evaluate_benchmark(frame_count: int, duration: float) -> void:var avg_fps := frame_count / durationif avg_fps < 30.0:# Disable heavy featuresProjectSettings.set_setting("rendering/anti_aliasing/quality/screen_space_aa", 0)ProjectSettings.set_setting("rendering/global_illumination/gi/use_half_resolution", true)ProjectSettings.save_custom("user://override.cfg")
3. Steamworks Integration (GDExtension)
While Godot doesn't natively include Steamworks, the expert approach is to use a GDExtension (like GodotSteam) to bridge the Steam SDK for cloud saves and achievements. This keeps the core engine binary small while providing deep platform integration.
# Example using GodotSteam GDExtensionfunc _ready() -> void:if Steam.steamInit().status == Steam.STEAM_OK:print("Steam Cloud and Achievements Active")Steam.requestStats()
Reference
- Master Skill: godot-master