Mobile Optimalisatie
Specifieke optimalisaties voor iOS en Android.
Mobile Constraints
┌─────────────────────────────────────────────────────────────────┐
│ Mobile Beperkingen │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Batterij: Conserveer energie │
│ ├── Beperk CPU/GPU gebruik │
│ ├── Vermijd continue animaties │
│ └── Dim screen bij inactiviteit │
│ │
│ Memory: Beperkt RAM │
│ ├── iOS: ~200MB voor games │
│ ├── Android: variabel, target 150-200MB │
│ └── Unload ongebruikte resources │
│ │
│ Thermal: Voorkom oververhitting │
│ ├── Throttle bij hoge temperatuur │
│ └── Beperk sustained GPU load │
│ │
│ Input: Touch-specifiek │
│ ├── Grotere touch targets (min 44x44 pt) │
│ ├── Geen hover states │
│ └── Gesture ondersteuning │
│ │
└─────────────────────────────────────────────────────────────────┘
Touch Input
Touch Targets
# Minimum touch target size: 44x44 pixels (iOS HIG)
# Recommended: 48x48 pixels
const MIN_TOUCH_SIZE = Vector2(48, 48)
func _ready() -> void:
# Ensure buttons are large enough
for button in get_tree().get_nodes_in_group("buttons"):
if button.size.x < MIN_TOUCH_SIZE.x or button.size.y < MIN_TOUCH_SIZE.y:
push_warning("Button too small for touch: " + button.name)
button.custom_minimum_size = MIN_TOUCH_SIZE
Gesture Recognition
# touch_handler.gd
extends Node
signal swipe_detected(direction: Vector2)
signal pinch_detected(scale: float)
signal tap_detected(position: Vector2)
const SWIPE_MIN_DISTANCE = 50.0
const TAP_MAX_DURATION = 0.3
var _touch_start_pos: Dictionary = {}
var _touch_start_time: Dictionary = {}
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
if event.pressed:
_touch_start_pos[event.index] = event.position
_touch_start_time[event.index] = Time.get_ticks_msec()
else:
_handle_touch_end(event)
func _handle_touch_end(event: InputEventScreenTouch) -> void:
if event.index not in _touch_start_pos:
return
var start_pos = _touch_start_pos[event.index]
var end_pos = event.position
var duration = (Time.get_ticks_msec() - _touch_start_time[event.index]) / 1000.0
var distance = start_pos.distance_to(end_pos)
if distance < 10 and duration < TAP_MAX_DURATION:
tap_detected.emit(end_pos)
elif distance >= SWIPE_MIN_DISTANCE:
var direction = (end_pos - start_pos).normalized()
swipe_detected.emit(direction)
_touch_start_pos.erase(event.index)
_touch_start_time.erase(event.index)
Multi-touch
# Pinch to zoom
var _initial_pinch_distance: float = 0.0
func _input(event: InputEvent) -> void:
if event is InputEventScreenDrag:
_handle_multitouch()
func _handle_multitouch() -> void:
var touches = get_viewport().get_touch_points()
if touches.size() == 2:
var distance = touches[0].distance_to(touches[1])
if _initial_pinch_distance == 0:
_initial_pinch_distance = distance
else:
var scale = distance / _initial_pinch_distance
pinch_detected.emit(scale)
else:
_initial_pinch_distance = 0.0
Graphics Quality Tiers
Quality Settings
# quality_settings.gd
extends Node
enum QualityTier { LOW, MEDIUM, HIGH }
var current_tier: QualityTier = QualityTier.MEDIUM
func _ready() -> void:
_detect_device_tier()
_apply_quality_settings()
func _detect_device_tier() -> void:
# Check device capabilities
var ram = OS.get_static_memory_usage()
if OS.get_name() == "iOS":
# Check for older devices
var model = OS.get_model_name()
if "iPhone 8" in model or "iPhone SE" in model:
current_tier = QualityTier.LOW
else:
current_tier = QualityTier.HIGH
elif OS.get_name() == "Android":
# Check GPU and RAM
if ram < 2 * 1024 * 1024 * 1024: # < 2GB
current_tier = QualityTier.LOW
elif ram < 4 * 1024 * 1024 * 1024: # < 4GB
current_tier = QualityTier.MEDIUM
else:
current_tier = QualityTier.HIGH
func _apply_quality_settings() -> void:
match current_tier:
QualityTier.LOW:
_apply_low_quality()
QualityTier.MEDIUM:
_apply_medium_quality()
QualityTier.HIGH:
_apply_high_quality()
func _apply_low_quality() -> void:
# Disable particles
get_tree().call_group("particles", "set_emitting", false)
# Lower resolution
get_viewport().scaling_3d_scale = 0.75
# Disable shadows
RenderingServer.directional_shadow_atlas_set_size(0, false)
# Reduce draw distance
ProjectSettings.set_setting("rendering/camera/near", 0.1)
func _apply_high_quality() -> void:
# Full particles
get_tree().call_group("particles", "set_emitting", true)
# Full resolution
get_viewport().scaling_3d_scale = 1.0
# Enable all effects
pass
Conditional Features
# Schakel features in/uit per tier
func spawn_particle_effect(position: Vector2) -> void:
if QualitySettings.current_tier == QualitySettings.QualityTier.LOW:
# Geen particles op low-end
return
var particles = PARTICLE_SCENE.instantiate()
particles.position = position
add_child(particles)
func update_shadows() -> void:
if QualitySettings.current_tier == QualitySettings.QualityTier.HIGH:
enable_dynamic_shadows()
else:
use_baked_shadows()
Battery Optimization
Frame Rate Management
# Verlaag framerate wanneer niet nodig
func _ready() -> void:
# Set initial target
Engine.max_fps = 60
func _on_app_paused() -> void:
# Minimize werk in background
Engine.max_fps = 5
func _on_app_resumed() -> void:
Engine.max_fps = 60
func _on_idle_timeout() -> void:
# Verlaag framerate bij inactiviteit
Engine.max_fps = 30
Background Handling
func _notification(what: int) -> void:
match what:
NOTIFICATION_APPLICATION_PAUSED:
_pause_game()
NOTIFICATION_APPLICATION_RESUMED:
_resume_game()
NOTIFICATION_APPLICATION_FOCUS_OUT:
# App gaat naar background
_reduce_activity()
NOTIFICATION_APPLICATION_FOCUS_IN:
# App komt terug
_restore_activity()
func _pause_game() -> void:
get_tree().paused = true
AudioServer.set_bus_mute(0, true)
# Stop network polling
SpacetimeClient.pause_subscriptions()
func _resume_game() -> void:
get_tree().paused = false
AudioServer.set_bus_mute(0, false)
# Resume network
SpacetimeClient.resume_subscriptions()
Memory Management
Texture Optimization
# Mobile texture settings
# In .import files:
# - compress/mode: ETC2 (Android), PVRTC (iOS)
# - mipmaps/generate: false (voor UI)
# - process/size_limit: 1024 (max texture size)
func get_texture_for_mobile(base_path: String) -> Texture2D:
var mobile_path = base_path.replace(".png", "_mobile.png")
if ResourceLoader.exists(mobile_path):
return load(mobile_path)
return load(base_path)
Scene Streaming
# Load scenes incrementeel
func load_scene_streamed(scene_path: String) -> void:
# Toon loading indicator
loading_indicator.visible = true
# Background load
ResourceLoader.load_threaded_request(scene_path)
while true:
var progress = []
var status = ResourceLoader.load_threaded_get_status(scene_path, progress)
loading_bar.value = progress[0] * 100
if status == ResourceLoader.THREAD_LOAD_LOADED:
var scene = ResourceLoader.load_threaded_get(scene_path)
_switch_to_scene(scene)
break
elif status == ResourceLoader.THREAD_LOAD_FAILED:
push_error("Failed to load: " + scene_path)
break
await get_tree().process_frame
loading_indicator.visible = false
Memory Warnings
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_MEMORY_WARNING:
_handle_memory_warning()
func _handle_memory_warning() -> void:
print("Memory warning received!")
# Clear caches
AssetLoader.clear_cache()
# Unload non-essential resources
_unload_background_scenes()
# Force garbage collection (where applicable)
# Note: Godot manages memory automatically
Mobile UI
Safe Areas
# Respecteer notch en gesture areas
func _ready() -> void:
var safe_area = DisplayServer.get_display_safe_area()
# Pas UI aan
ui_container.offset_left = safe_area.position.x
ui_container.offset_top = safe_area.position.y
ui_container.offset_right = -safe_area.position.x
ui_container.offset_bottom = -safe_area.position.y
Responsive Layout
func _ready() -> void:
get_tree().root.size_changed.connect(_on_viewport_size_changed)
_update_layout()
func _update_layout() -> void:
var viewport_size = get_viewport().get_visible_rect().size
var is_portrait = viewport_size.y > viewport_size.x
if is_portrait:
_apply_portrait_layout()
else:
_apply_landscape_layout()
func _apply_portrait_layout() -> void:
# Stack UI verticaal
ui_container.columns = 1
inventory_panel.position = Vector2(0, viewport_size.y * 0.5)
func _apply_landscape_layout() -> void:
# Side-by-side layout
ui_container.columns = 2
inventory_panel.position = Vector2(viewport_size.x * 0.7, 0)
Export Settings
Android
# export_presets.cfg
[preset.android]
platform = "Android"
custom_features = "mobile"
architectures/armeabi-v7a = true
architectures/arm64-v8a = true
# Graphics
graphics/opengl/debug = false
# APK optimization
package/retain_data_on_uninstall = true
gradle_build/use_gradle_build = true
gradle_build/export_format = 1 # AAB for Play Store
iOS
[preset.ios]
platform = "iOS"
custom_features = "mobile"
# Required capabilities
capabilities/access_wifi_state = false
capabilities/push_notifications = false
# App Store
application/app_store_team_id = "YOUR_TEAM_ID"
application/provisioning_profile_uuid_debug = "..."