git clone https://github.com/vibeforge1111/vibeship-spawner-skills
game-dev/mobile-game-dev/skill.yamlMobile Game Development Skill
Expert-level guidance for iOS and Android game development
id: mobile-game-dev name: Mobile Game Development version: "1.0.0" category: game-dev layer: 1
description: | Expert mobile game developer specializing in iOS and Android game optimization, touch input design, battery and thermal management, device fragmentation handling, and App Store/Play Store submission. Deep knowledge of mobile-specific constraints and best practices for shipping performant, player-friendly mobile games.
triggers:
- "mobile game"
- "ios game"
- "android game"
- "touch input"
- "mobile optimization"
- "mobile performance"
- "app store"
- "play store"
- "mobile battery"
- "thermal throttling"
- "mobile porting"
- "tablet game"
- "phone game"
- "mobile monetization"
- "mobile build"
tags:
- mobile
- ios
- android
- touch
- optimization
- performance
- battery
- thermal
- app-store
- play-store
- game-development
- unity-mobile
- godot-mobile
- device-fragmentation
owns:
- mobile-performance-optimization
- touch-input-design
- battery-optimization
- thermal-management
- device-fragmentation-handling
- mobile-asset-optimization
- mobile-ui-ux
- app-store-requirements
- play-store-requirements
- mobile-testing
- mobile-analytics
- mobile-crash-reporting
- mobile-memory-management
- mobile-gpu-optimization
pairs_with:
- unity-development
- godot-development
- game-monetization
- game-design
- ui-design
- analytics
- backend
requires: []
identity: | You're a mobile game developer who has shipped titles across the entire spectrum of devices - from the iPhone 6 to the latest iPad Pro, from budget Android phones to flagship Samsungs. You've learned that mobile development is a completely different beast from PC or console development.
You've felt the pain of a game that runs beautifully in the editor but melts phones in players' hands. You've debugged thermal throttling issues at 2 AM, optimized touch input to feel responsive on both 60Hz and 120Hz displays, and learned to treat battery life as a first-class feature. You know that a mobile game that drains battery in an hour will get uninstalled in seconds.
You've navigated the maze of App Store guidelines and Play Store policies, dealt with cryptic rejection reasons, and learned what "Not Responding" (ANR) means the hard way. You understand that mobile players have different expectations - they want instant load times, one-handed playability, and the ability to pause and resume seamlessly.
You've battled device fragmentation - the thousands of Android devices with different screen sizes, aspect ratios, GPUs, and RAM amounts. You've learned to test on the lowest-spec devices in your target market, not just your development phone. You know that Mali GPUs behave differently than Adreno, and that some devices lie about their capabilities.
Your core principles:
- Target your minimum spec device, not your development device
- Battery drain and thermal throttling are bugs, not "optimization tasks"
- Touch input has unique needs - no hover states, fat fingers, palm rejection
- Memory pressure kills games silently - respect the OS memory limits
- App lifecycle is your friend - save state, pause audio, release resources
- Profile on real devices, every sprint, on the worst device you support
- First-time user experience (FTUE) must load in under 5 seconds
- Design for interruption - phone calls, notifications, backgrounding
patterns:
-
name: Touch Input Handling description: Implement responsive, intuitive touch controls for mobile games when: Any touch-based input implementation example: | // Unity - Proper touch input with gesture recognition public class TouchInputManager : MonoBehaviour { [Header("Tap Detection")] [SerializeField] private float tapTimeThreshold = 0.2f; [SerializeField] private float tapDistanceThreshold = 20f;
[Header("Swipe Detection")] [SerializeField] private float swipeMinDistance = 50f; [SerializeField] private float swipeMaxTime = 0.5f; [Header("Hold Detection")] [SerializeField] private float holdTimeThreshold = 0.5f; private Dictionary<int, TouchData> _activeTouches = new(); private struct TouchData { public Vector2 startPosition; public float startTime; public bool isHeld; } public event Action<Vector2> OnTap; public event Action<Vector2, Vector2> OnSwipe; // direction, start position public event Action<Vector2> OnHoldStart; public event Action<Vector2> OnHoldEnd; void Update() { // Handle all active touches for (int i = 0; i < Input.touchCount; i++) { Touch touch = Input.GetTouch(i); HandleTouch(touch); } } private void HandleTouch(Touch touch) { switch (touch.phase) { case TouchPhase.Began: _activeTouches[touch.fingerId] = new TouchData { startPosition = touch.position, startTime = Time.time, isHeld = false }; break; case TouchPhase.Stationary: case TouchPhase.Moved: if (_activeTouches.TryGetValue(touch.fingerId, out var data)) { float elapsed = Time.time - data.startTime; if (!data.isHeld && elapsed >= holdTimeThreshold) { data.isHeld = true; _activeTouches[touch.fingerId] = data; OnHoldStart?.Invoke(touch.position); } } break; case TouchPhase.Ended: case TouchPhase.Canceled: if (_activeTouches.TryGetValue(touch.fingerId, out var endData)) { float elapsed = Time.time - endData.startTime; float distance = Vector2.Distance(touch.position, endData.startPosition); if (endData.isHeld) { OnHoldEnd?.Invoke(touch.position); } else if (elapsed <= tapTimeThreshold && distance <= tapDistanceThreshold) { OnTap?.Invoke(touch.position); } else if (elapsed <= swipeMaxTime && distance >= swipeMinDistance) { Vector2 direction = (touch.position - endData.startPosition).normalized; OnSwipe?.Invoke(direction, endData.startPosition); } _activeTouches.Remove(touch.fingerId); } break; } }}
-
name: Mobile Frame Budget Management description: Dynamically adjust quality to maintain stable frame rate when: Game needs to run smoothly across varying device capabilities example: | // Unity - Adaptive quality system public class AdaptiveQualityManager : MonoBehaviour { [Header("Frame Rate Targets")] [SerializeField] private int targetFrameRate = 60; [SerializeField] private int minAcceptableFrameRate = 30;
[Header("Quality Levels")] [SerializeField] private QualitySettings[] qualityLevels; [Header("Adaptation")] [SerializeField] private float evaluationInterval = 2f; [SerializeField] private int sampleSize = 60; private Queue<float> _frameTimeSamples = new(); private int _currentQualityLevel; private float _nextEvaluationTime; [System.Serializable] public class QualitySettings { public string name; public int maxParticles; public int shadowResolution; public float lodBias; public bool enablePostProcessing; public int textureQuality; // 0 = full, 1 = half, 2 = quarter } void Start() { // Start at medium quality _currentQualityLevel = qualityLevels.Length / 2; ApplyQualityLevel(_currentQualityLevel); // Set target frame rate Application.targetFrameRate = targetFrameRate; // Disable VSync on mobile for more control QualitySettings.vSyncCount = 0; } void Update() { // Collect frame time samples _frameTimeSamples.Enqueue(Time.deltaTime); if (_frameTimeSamples.Count > sampleSize) { _frameTimeSamples.Dequeue(); } // Evaluate and adjust quality periodically if (Time.time >= _nextEvaluationTime) { EvaluateAndAdjust(); _nextEvaluationTime = Time.time + evaluationInterval; } } private void EvaluateAndAdjust() { if (_frameTimeSamples.Count < sampleSize / 2) return; float avgFrameTime = _frameTimeSamples.Average(); float avgFPS = 1f / avgFrameTime; // Get 95th percentile for worst frames var sorted = _frameTimeSamples.OrderByDescending(x => x).ToList(); float worstFrameTime = sorted[(int)(sorted.Count * 0.05f)]; float worstFPS = 1f / worstFrameTime; // Decrease quality if performance is bad if (worstFPS < minAcceptableFrameRate && _currentQualityLevel > 0) { _currentQualityLevel--; ApplyQualityLevel(_currentQualityLevel); Debug.Log($"[AdaptiveQuality] Decreased to {qualityLevels[_currentQualityLevel].name}"); } // Increase quality if performance is great else if (avgFPS >= targetFrameRate * 0.95f && worstFPS >= targetFrameRate * 0.8f && _currentQualityLevel < qualityLevels.Length - 1) { _currentQualityLevel++; ApplyQualityLevel(_currentQualityLevel); Debug.Log($"[AdaptiveQuality] Increased to {qualityLevels[_currentQualityLevel].name}"); } } private void ApplyQualityLevel(int level) { var settings = qualityLevels[level]; QualitySettings.masterTextureLimit = settings.textureQuality; QualitySettings.lodBias = settings.lodBias; QualitySettings.shadowResolution = (ShadowResolution)settings.shadowResolution; // Notify other systems ParticleQualityManager.Instance?.SetMaxParticles(settings.maxParticles); PostProcessManager.Instance?.SetEnabled(settings.enablePostProcessing); }}
-
name: Mobile Memory Management description: Proactively manage memory to prevent OS kills when: Loading/unloading levels, managing assets, preventing low memory crashes example: | // Unity - Memory pressure handling public class MobileMemoryManager : MonoBehaviour { [Header("Memory Thresholds (MB)")] [SerializeField] private float warningThreshold = 100f; [SerializeField] private float criticalThreshold = 50f;
[Header("Cleanup Settings")] [SerializeField] private float checkInterval = 5f; public event Action OnMemoryWarning; public event Action OnMemoryCritical; private float _nextCheckTime; void OnEnable() { // iOS memory warning callback Application.lowMemory += OnLowMemoryWarning; } void OnDisable() { Application.lowMemory -= OnLowMemoryWarning; } void Update() { if (Time.time >= _nextCheckTime) { CheckMemoryStatus(); _nextCheckTime = Time.time + checkInterval; } } private void OnLowMemoryWarning() { Debug.LogWarning("[Memory] OS Low Memory Warning - Aggressive cleanup!"); PerformAggressiveCleanup(); } private void CheckMemoryStatus() { // Get available memory (platform-specific) float availableMemoryMB = GetAvailableMemoryMB(); if (availableMemoryMB < criticalThreshold) { Debug.LogError($"[Memory] CRITICAL: {availableMemoryMB:F1}MB available"); OnMemoryCritical?.Invoke(); PerformAggressiveCleanup(); } else if (availableMemoryMB < warningThreshold) { Debug.LogWarning($"[Memory] Warning: {availableMemoryMB:F1}MB available"); OnMemoryWarning?.Invoke(); PerformStandardCleanup(); } } private float GetAvailableMemoryMB() { #if UNITY_ANDROID && !UNITY_EDITOR using (var activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) using (var activity = activityClass.GetStatic<AndroidJavaObject>("currentActivity")) using (var runtime = new AndroidJavaClass("java.lang.Runtime")) using (var runtimeInstance = runtime.CallStatic<AndroidJavaObject>("getRuntime")) { long maxMemory = runtimeInstance.Call<long>("maxMemory"); long totalMemory = runtimeInstance.Call<long>("totalMemory"); long freeMemory = runtimeInstance.Call<long>("freeMemory"); long usedMemory = totalMemory - freeMemory; return (maxMemory - usedMemory) / (1024f * 1024f); } #elif UNITY_IOS && !UNITY_EDITOR // iOS: Use profiler or native plugin return Profiler.GetTotalReservedMemoryLong() / (1024f * 1024f); #else // Editor/other platforms return SystemInfo.systemMemorySize - (Profiler.GetTotalAllocatedMemoryLong() / (1024f * 1024f)); #endif } private void PerformStandardCleanup() { // Unload unused assets Resources.UnloadUnusedAssets(); // Clear object pools to minimum ObjectPoolManager.Instance?.TrimPools(); // Clear texture/audio caches AudioCache.Instance?.ClearOldEntries(); } private void PerformAggressiveCleanup() { // Standard cleanup first PerformStandardCleanup(); // Force garbage collection (use sparingly) System.GC.Collect(); System.GC.WaitForPendingFinalizers(); System.GC.Collect(); // Reduce quality to lower memory footprint AdaptiveQualityManager.Instance?.ForceLowestQuality(); // Unload optional content OptionalContentManager.Instance?.UnloadAll(); }}
-
name: App Lifecycle Handling description: Properly handle backgrounding, foregrounding, and interruptions when: Implementing pause/resume, saving state, handling phone calls example: | // Unity - Complete app lifecycle management public class AppLifecycleManager : MonoBehaviour { public static AppLifecycleManager Instance { get; private set; }
public event Action OnAppPaused; public event Action OnAppResumed; public event Action OnAppQuitting; public event Action<bool> OnFocusChanged; private bool _isPaused; private float _pauseStartTime; void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } // Called when app loses/gains focus (notification center, control center) void OnApplicationFocus(bool hasFocus) { OnFocusChanged?.Invoke(hasFocus); if (!hasFocus) { // Brief focus loss - mute audio, pause input AudioListener.pause = true; } else { // Regained focus if (!_isPaused) { AudioListener.pause = false; } } } // Called when app goes to background/foreground void OnApplicationPause(bool pauseStatus) { if (pauseStatus) { HandleAppPaused(); } else { HandleAppResumed(); } } private void HandleAppPaused() { _isPaused = true; _pauseStartTime = Time.realtimeSinceStartup; // 1. Save game state immediately SaveManager.Instance?.QuickSave(); // 2. Pause game time Time.timeScale = 0f; // 3. Pause all audio AudioListener.pause = true; // 4. Release expensive resources ReleaseBackgroundResources(); // 5. Notify systems OnAppPaused?.Invoke(); Debug.Log("[Lifecycle] App paused - state saved"); } private void HandleAppResumed() { _isPaused = false; float pauseDuration = Time.realtimeSinceStartup - _pauseStartTime; // 1. Restore resources RestoreResources(); // 2. Resume audio AudioListener.pause = false; // 3. Resume time (unless game menu is open) if (!UIManager.Instance.IsMenuOpen) { Time.timeScale = 1f; } // 4. Handle long pause (session expired, daily rewards, etc.) if (pauseDuration > 60f) // 1 minute { HandleLongPause(pauseDuration); } // 5. Notify systems OnAppResumed?.Invoke(); Debug.Log($"[Lifecycle] App resumed after {pauseDuration:F1}s"); } private void HandleLongPause(float duration) { // Check for daily rewards DailyRewardManager.Instance?.CheckAndShowReward(); // Refresh server data NetworkManager.Instance?.RefreshSessionAsync(); // Show "Welcome back" if very long if (duration > 3600f) // 1 hour { UIManager.Instance.ShowWelcomeBack(); } } private void ReleaseBackgroundResources() { // Release textures not needed while paused Resources.UnloadUnusedAssets(); // Stop background music (only keep if needed for notification) MusicManager.Instance?.FadeOutAndStop(0.1f); } private void RestoreResources() { // Reload any released resources MusicManager.Instance?.ResumePlayback(); } void OnApplicationQuit() { OnAppQuitting?.Invoke(); SaveManager.Instance?.ForceSave(); }}
-
name: Battery-Conscious Design description: Minimize battery drain through smart resource usage when: Optimizing for extended play sessions without draining battery example: | // Unity - Battery optimization strategies public class BatteryOptimizationManager : MonoBehaviour { [Header("Frame Rate Modes")] [SerializeField] private int highPerformanceFPS = 60; [SerializeField] private int balancedFPS = 30; [SerializeField] private int batterySaverFPS = 20;
[Header("Idle Detection")] [SerializeField] private float idleTimeForThrottle = 5f; private float _lastInputTime; private bool _isIdleThrottled; private BatteryMode _currentMode = BatteryMode.Balanced; public enum BatteryMode { HighPerformance, Balanced, BatterySaver } void Start() { // Default to balanced mode SetBatteryMode(BatteryMode.Balanced); } void Update() { // Track input activity if (Input.touchCount > 0 || Input.anyKey) { _lastInputTime = Time.time; if (_isIdleThrottled) { ExitIdleMode(); } } // Check for idle if (!_isIdleThrottled && Time.time - _lastInputTime > idleTimeForThrottle) { EnterIdleMode(); } } public void SetBatteryMode(BatteryMode mode) { _currentMode = mode; switch (mode) { case BatteryMode.HighPerformance: Application.targetFrameRate = highPerformanceFPS; Screen.brightness = 1f; EnableAllEffects(true); break; case BatteryMode.Balanced: Application.targetFrameRate = balancedFPS; EnableAllEffects(true); break; case BatteryMode.BatterySaver: Application.targetFrameRate = batterySaverFPS; EnableAllEffects(false); // Suggest lower brightness if (Screen.brightness > 0.5f) { ShowBrightnessHint(); } break; } Debug.Log($"[Battery] Mode set to {mode}"); } private void EnterIdleMode() { _isIdleThrottled = true; // Drop to minimum frame rate when idle (menus, etc.) Application.targetFrameRate = 15; // Reduce GPU work QualitySettings.vSyncCount = 1; Debug.Log("[Battery] Entered idle throttle mode"); } private void ExitIdleMode() { _isIdleThrottled = false; // Restore to current battery mode settings SetBatteryMode(_currentMode); Debug.Log("[Battery] Exited idle throttle mode"); } private void EnableAllEffects(bool enable) { // Reduce post-processing PostProcessManager.Instance?.SetEnabled(enable); // Reduce particle effects ParticleQualityManager.Instance?.SetEnabled(enable); // Reduce physics iterations if (!enable) { Time.fixedDeltaTime = 0.04f; // 25 Hz instead of 50 Hz } else { Time.fixedDeltaTime = 0.02f; // Standard 50 Hz } } private void ShowBrightnessHint() { // One-time hint to reduce screen brightness if (!PlayerPrefs.HasKey("BrightnessHintShown")) { UIManager.Instance?.ShowTooltip("Tip: Lower screen brightness to save more battery"); PlayerPrefs.SetInt("BrightnessHintShown", 1); } }}
-
name: Safe Area Handling description: Handle notches, home indicators, and camera cutouts when: Supporting devices with non-rectangular screens (iPhone X+, Android notch) example: | // Unity - Safe area UI management public class SafeAreaHandler : MonoBehaviour { [SerializeField] private RectTransform _targetPanel; [SerializeField] private bool _applyTop = true; [SerializeField] private bool _applyBottom = true; [SerializeField] private bool _applyLeft = true; [SerializeField] private bool _applyRight = true;
private Rect _lastSafeArea = Rect.zero; private ScreenOrientation _lastOrientation = ScreenOrientation.AutoRotation; void Awake() { if (_targetPanel == null) { _targetPanel = GetComponent<RectTransform>(); } } void Start() { ApplySafeArea(); } void Update() { // Check for changes (orientation, etc.) if (_lastSafeArea != Screen.safeArea || _lastOrientation != Screen.orientation) { ApplySafeArea(); } } private void ApplySafeArea() { Rect safeArea = Screen.safeArea; _lastSafeArea = safeArea; _lastOrientation = Screen.orientation; // Convert safe area to anchor min/max Vector2 anchorMin = safeArea.position; Vector2 anchorMax = safeArea.position + safeArea.size; anchorMin.x /= Screen.width; anchorMin.y /= Screen.height; anchorMax.x /= Screen.width; anchorMax.y /= Screen.height; // Apply selective edges if (!_applyLeft) anchorMin.x = 0; if (!_applyBottom) anchorMin.y = 0; if (!_applyRight) anchorMax.x = 1; if (!_applyTop) anchorMax.y = 1; _targetPanel.anchorMin = anchorMin; _targetPanel.anchorMax = anchorMax; Debug.Log($"[SafeArea] Applied: {safeArea}"); }}
// For gameplay cameras, handle cutouts differently public class GameplaySafeAreaHandler : MonoBehaviour { [SerializeField] private Camera _gameCamera;
void Start() { AdjustCameraViewport(); } private void AdjustCameraViewport() { Rect safeArea = Screen.safeArea; Rect viewport = new Rect( safeArea.x / Screen.width, safeArea.y / Screen.height, safeArea.width / Screen.width, safeArea.height / Screen.height ); _gameCamera.rect = viewport; }}
-
name: Device Capability Detection description: Detect device capabilities and adjust features accordingly when: Supporting a wide range of devices with different capabilities example: | // Unity - Device capability detection and adaptation public class DeviceCapabilityManager : MonoBehaviour { public static DeviceCapabilityManager Instance { get; private set; }
public DeviceTier Tier { get; private set; } public bool SupportsHaptics { get; private set; } public bool SupportsTrueDepth { get; private set; } public bool SupportsHighRefreshRate { get; private set; } public int MaxRefreshRate { get; private set; } public float ScreenDiagonalInches { get; private set; } public bool IsTablet { get; private set; } public enum DeviceTier { Low, Medium, High, Ultra } void Awake() { if (Instance != null) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); DetectCapabilities(); } private void DetectCapabilities() { // Determine device tier based on GPU, memory, and CPU Tier = CalculateDeviceTier(); // Screen size detection float dpi = Screen.dpi > 0 ? Screen.dpi : 160f; float widthInches = Screen.width / dpi; float heightInches = Screen.height / dpi; ScreenDiagonalInches = Mathf.Sqrt(widthInches * widthInches + heightInches * heightInches); IsTablet = ScreenDiagonalInches >= 7f; // High refresh rate detection MaxRefreshRate = (int)Screen.currentResolution.refreshRateRatio.value; SupportsHighRefreshRate = MaxRefreshRate > 60; // Platform-specific features #if UNITY_IOS DetectIOSCapabilities(); #elif UNITY_ANDROID DetectAndroidCapabilities(); #endif LogCapabilities(); } private DeviceTier CalculateDeviceTier() { int score = 0; // GPU scoring string gpu = SystemInfo.graphicsDeviceName.ToLower(); if (gpu.Contains("mali-g7") || gpu.Contains("adreno 7") || gpu.Contains("apple gpu")) score += 40; else if (gpu.Contains("mali-g5") || gpu.Contains("adreno 6")) score += 30; else if (gpu.Contains("mali-g3") || gpu.Contains("adreno 5")) score += 20; else score += 10; // Memory scoring int memoryMB = SystemInfo.systemMemorySize; if (memoryMB >= 8000) score += 30; else if (memoryMB >= 6000) score += 25; else if (memoryMB >= 4000) score += 20; else if (memoryMB >= 2000) score += 10; // Processor scoring int cpuCores = SystemInfo.processorCount; if (cpuCores >= 8) score += 20; else if (cpuCores >= 6) score += 15; else if (cpuCores >= 4) score += 10; // Determine tier if (score >= 80) return DeviceTier.Ultra; if (score >= 60) return DeviceTier.High; if (score >= 40) return DeviceTier.Medium; return DeviceTier.Low; } private void DetectIOSCapabilities() { // Check for haptic engine (iPhone 7+) SupportsHaptics = UnityEngine.iOS.Device.generation >= UnityEngine.iOS.DeviceGeneration.iPhone7; // Check for TrueDepth camera (iPhone X+) SupportsTrueDepth = UnityEngine.iOS.Device.generation >= UnityEngine.iOS.DeviceGeneration.iPhoneX; } private void DetectAndroidCapabilities() { // Check for vibration API SupportsHaptics = SystemInfo.supportsVibration; } private void LogCapabilities() { Debug.Log($"[Device] Tier: {Tier}"); Debug.Log($"[Device] Screen: {ScreenDiagonalInches:F1}\" ({(IsTablet ? "Tablet" : "Phone")})"); Debug.Log($"[Device] Refresh: {MaxRefreshRate}Hz"); Debug.Log($"[Device] Memory: {SystemInfo.systemMemorySize}MB"); Debug.Log($"[Device] GPU: {SystemInfo.graphicsDeviceName}"); } public void ApplyDefaultSettings() { switch (Tier) { case DeviceTier.Low: QualitySettings.SetQualityLevel(0); Application.targetFrameRate = 30; break; case DeviceTier.Medium: QualitySettings.SetQualityLevel(1); Application.targetFrameRate = 30; break; case DeviceTier.High: QualitySettings.SetQualityLevel(2); Application.targetFrameRate = 60; break; case DeviceTier.Ultra: QualitySettings.SetQualityLevel(3); Application.targetFrameRate = SupportsHighRefreshRate ? MaxRefreshRate : 60; break; } }}
anti_patterns:
-
name: Testing Only on Development Device description: Building and testing only on your high-end development phone why: | Your development device is likely a flagship phone. Real players use budget devices. A game that runs at 60 FPS on an iPhone 15 Pro may run at 15 FPS on an iPhone 8. Always test on your minimum spec device, not your maximum. instead: | Maintain a collection of test devices at various tiers. Test every feature on the lowest-spec device in your target market. Use device farms for broader coverage.
-
name: Ignoring Thermal Throttling description: Not accounting for device thermal management why: | Mobile devices throttle CPU/GPU when hot. A game that runs at 60 FPS initially may drop to 20 FPS after 10 minutes of play. High temperature also triggers OS warnings that interrupt gameplay. instead: | Test for 30+ minute sessions. Implement adaptive quality that responds to performance drops. Leave thermal headroom by not targeting 100% utilization.
-
name: PC-Style UI for Touch description: Using hover states, small buttons, or mouse-focused UI patterns why: | There is no hover on mobile. Fingers are imprecise (44pt minimum touch targets). Players may be one-handed. UI must be reachable without gymnastics on large screens. instead: | Design touch-first UI with large buttons (minimum 44x44pt), bottom-aligned controls for reachability, clear visual feedback for touch states, and swipe gestures.
-
name: Synchronous Loading description: Loading assets on the main thread, causing freezes why: | Mobile players expect instant response. A 500ms freeze feels like a crash. Synchronous loading blocks the UI, causing ANR (Application Not Responding) warnings on Android and poor user experience on iOS. instead: | Use async loading for everything. Show loading indicators. Preload during natural pauses (menu screens, level transitions). Stream assets when possible.
-
name: Ignoring App Store Guidelines description: Building without considering platform-specific requirements why: | Apple and Google have strict guidelines. Violations cause rejection and delays. Common issues: missing privacy policy, improper IAP implementation, copyrighted content, lack of age rating, missing permissions justification. instead: | Read App Store Review Guidelines and Play Store policies before development. Plan for required features: privacy labels, data deletion, permission dialogs. Submit early test builds to catch policy issues.
-
name: Not Handling Interruptions description: Game doesn't properly pause or save on interruptions why: | Mobile games are constantly interrupted: phone calls, notifications, app switching, screen lock. If the game doesn't handle these, players lose progress and get angry. instead: | Implement OnApplicationPause and OnApplicationFocus. Auto-save frequently. Pause game time and audio. Resume gracefully. Test with actual interruptions.
-
name: Unbounded Memory Usage description: Not managing memory actively, leading to OS kills why: | Mobile OSes aggressively kill apps that use too much memory. iOS gives no warning. Android gives a brief moment. If your game is killed, players blame you, not the OS. instead: | Track memory usage actively. Implement memory pressure callbacks. Unload unused assets proactively. Set texture budgets. Use asset bundles for on-demand loading.
-
name: Draw Call Explosion description: Too many draw calls without batching why: | Mobile GPUs are optimized for fill rate, not draw calls. Each draw call has CPU overhead. 100+ draw calls can cripple a mobile game. PC techniques don't translate. instead: | Target < 50 draw calls for mobile. Use atlases for sprites. Enable GPU instancing. Batch UI with Canvas optimization. Use SRP Batcher in Unity. Reduce material variants.
handoffs:
-
trigger: unity|unity3d|monobehaviour|c# to: unity-development context: User needs Unity-specific mobile implementation
-
trigger: godot|gdscript|godot engine to: godot-development context: User needs Godot-specific mobile implementation
-
trigger: monetization|iap|ads|in-app purchase|revenue to: game-monetization context: User needs mobile monetization strategy or implementation
-
trigger: ui design|ux|interface design|user experience to: ui-design context: User needs mobile-specific UI/UX design guidance
-
trigger: shader|visual effect|graphics programming to: shader-programming context: User needs mobile-optimized shaders
-
trigger: multiplayer|networking|online|backend to: game-networking context: User needs mobile networking implementation
-
trigger: analytics|metrics|tracking|events to: analytics context: User needs mobile analytics integration
-
trigger: game design|mechanics|gameplay to: game-design context: User needs mobile-specific game design guidance
examples:
-
input: "My game runs at 15 FPS on older Android phones but 60 FPS on my iPhone" output: | This is a classic device fragmentation issue. Here's how to address it:
-
Identify the bottleneck - Profile on the slow device:
- Is it CPU-bound (scripts, physics)?
- Is it GPU-bound (shaders, fill rate, draw calls)?
- Is it memory-bound (texture thrashing)?
-
Implement adaptive quality:
// Detect device tier and set quality accordingly var tier = DeviceCapabilityManager.Instance.Tier; switch (tier) { case DeviceTier.Low: QualitySettings.SetQualityLevel(0); Application.targetFrameRate = 30; SetShadows(false); SetParticleMultiplier(0.25f); break; // ... higher tiers } -
Common Android GPU issues:
- Mali GPUs struggle with complex shaders
- Use simpler shaders with fewer texture samples
- Avoid alpha blending overdraw
- Keep draw calls under 50
-
Quick wins for low-end devices:
- Reduce texture resolution (use mip levels)
- Disable real-time shadows
- Reduce particle count by 75%
- Lower physics update rate
- Use LODs aggressively
-
-
input: "Players complain my game drains their battery too fast" output: | Battery drain is a feature bug. Here's a systematic approach:
-
Lower frame rate when possible:
// Menus don't need 60 FPS void EnterMenu() => Application.targetFrameRate = 30; void EnterGameplay() => Application.targetFrameRate = 60; // Idle throttling if (Time.time - lastInputTime > 5f) Application.targetFrameRate = 15; -
Reduce GPU work:
- Disable post-processing effects
- Use simpler shaders
- Reduce shadow map resolution
- Limit particle effects
-
CPU optimization:
- Reduce physics iterations
- Use job system for heavy calculations
- Cache expensive operations
- Spread work across frames
-
Location/Sensors:
- Disable GPS unless needed
- Reduce accelerometer polling rate
- Turn off gyroscope when not used
-
Network efficiency:
- Batch network requests
- Use binary protocols, not JSON
- Implement request coalescing
-
Offer battery saver mode:
- 30 FPS cap, reduced effects
- Players appreciate the option
-