- 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. desktop_input_adapter.gd Bridges virtual joystick logic to keyboard WASD. Maps keys to input vectors and handles high-DPI UI scaling. hover_bridge.gd Re-enables desktop hover features in mobile-first codebases. Adds tooltips, mouse enter/exit signals, and hover visual feedback. Control Scheme Expansion Touch → Mouse Conversion
Mobile: Virtual joystick for movement
var direction : Vector2 = virtual_joystick . get_direction ( )
⬇️ Desktop: WASD + mouse aim
extends CharacterBody2D func _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 aiming
var 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.gd
extends Node func _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 . visible func 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 wheel
func _input ( event : InputEvent ) -> void : if event is InputEventMouseButton : if event . button_index == MOUSE_BUTTON_WHEEL_UP : camera . zoom = 1.1 elif 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 resolution
var screen_size := DisplayServer . screen_get_size ( ) get_window ( ) . size = screen_size
Enable higher quality
enable_desktop_graphics ( ) func enable_desktop_graphics ( ) -> void :
Enable MSAA
get_viewport ( ) . msaa_2d = Viewport . MSAA_2X get_viewport ( ) . msaa_3d = Viewport . MSAA_4X
Enable screen space AA
get_viewport ( ) . screen_space_aa = Viewport . SCREEN_SPACE_AA_FXAA
Higher shadow resolution
RenderingServer . directional_shadow_atlas_set_size ( 4096 , true )
Enable post-processing
var env := get_viewport ( ) . world_3d . environment if env : env . glow_enabled = true env . ssao_enabled = true env . adjustment_enabled = true Settings Menu
graphics_settings.gd
extends 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 / Fullscreen func _ready ( ) -> void : populate_settings ( ) load_settings ( ) func populate_settings ( ) -> void :
Resolution options
resolution_option . add_item ( "1280x720" ) resolution_option . add_item ( "1920x1080" ) resolution_option . add_item ( "2560x1440" ) resolution_option . add_item ( "3840x2160" )
Quality presets
quality_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 :
Low
apply_low_quality ( ) 1 :
Medium
apply_medium_quality ( ) 2 :
High
apply_high_quality ( ) 3 :
Ultra
apply_ultra_quality ( ) save_settings ( ) func apply_ultra_quality ( ) -> void : get_viewport ( ) . msaa_3d = Viewport . MSAA_8X get_viewport ( ) . screen_space_aa = Viewport . SCREEN_SPACE_AA_FXAA RenderingServer . directional_shadow_atlas_set_size ( 8192 , true ) var env := get_viewport ( ) . world_3d . environment if env : env . glow_enabled = true env . ssao_enabled = true env . ssil_enabled = true env . sdfgi_enabled = true func _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 Control func _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 cluttered
func _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.gd
extends Node func _ready ( ) -> void :
Detect monitors
var screen_count := DisplayServer . get_screen_count ( ) print ( "Detected %d monitors" % screen_count )
Allow window dragging between monitors
DisplayServer . 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 monitor
var window_size := get_window ( ) . size var centered_pos := screen_pos + ( screen_size - window_size ) / 2 DisplayServer . window_set_position ( centered_pos ) Borderless Fullscreen func set_borderless_fullscreen ( enabled : bool ) -> void : if enabled :
Get screen size
var screen_size := DisplayServer . screen_get_size ( )
Set window to screen size
get_window ( ) . size = screen_size get_window ( ) . position = Vector2i . ZERO
Remove border
DisplayServer . 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 plugin
extends Node var steam_initialized := false func _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 = true print ( "Steam initialized" )
Enable achievements
Steam . requestStats ( ) func unlock_achievement ( achievement_id : String ) -> void : if steam_initialized : Steam . setAchievement ( achievement_id ) Steam . storeStats ( ) Discord Rich Presence
Requires Discord SDK integration
extends Node func 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 FPS
Engine . max_fps = 60
Desktop: Unlock or match monitor refresh rate
func _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 distance
var camera : Camera3D camera . far = 100.0
Desktop: Higher
camera . far = 500.0
Also increase shadow distance
var sun : DirectionalLight3D sun . 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) Reference Master Skill: godot-master