Loading...
Loading...
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.
npx skill4agent add thedivergentai/gd-agentic-skills godot-adapt-mobile-to-desktopMANDATORY: Read the appropriate script before implementing the corresponding pattern.
# 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# 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# 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# 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# 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)# 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_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)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)# 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()# 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# 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)# 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