
Mobile Game Dev
- 151 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Implement touch controls, game loops, and mobile performance patterns when building and shipping iOS or Android games.
About
Covers end-to-end mobile game implementation: engine selection, touch controls, frame budgets, asset pipelines, build flavors, and iteration patterns for shipping iOS and Android titles.
- touch input
- mobile performance
- game loops
- store packaging
- platform SDKs
Mobile Game Dev by the numbers
- 151 all-time installs (skills.sh)
- +8 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #111 of 247 Game Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill mobile-game-devAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 151 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Implement touch controls, game loops, and mobile performance patterns when building and shipping iOS or Android games.
Files
Mobile Game Dev
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: 1. Target your minimum spec device, not your development device 2. Battery drain and thermal throttling are bugs, not "optimization tasks" 3. Touch input has unique needs - no hover states, fat fingers, palm rejection 4. Memory pressure kills games silently - respect the OS memory limits 5. App lifecycle is your friend - save state, pause audio, release resources 6. Profile on real devices, every sprint, on the worst device you support 7. First-time user experience (FTUE) must load in under 5 seconds 8. Design for interruption - phone calls, notifications, backgrounding
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Mobile Game Development
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.
Mobile Game Dev - Sharp Edges
Thermal Throttling Kills Performance Over Time
Id
thermal-throttling
Severity
critical
Category
performance
Description
Mobile devices actively reduce CPU/GPU performance when they get hot. A game that runs at 60 FPS for the first 5 minutes may drop to 20 FPS after 15 minutes. This is not a bug in your game - it's the device protecting itself. You cannot disable this; you must design around it.
Symptom
- Game runs smoothly initially, then slows down after 10-15 minutes
- Device becomes noticeably warm/hot during gameplay
- Performance degrades even when nothing in-game has changed
- Frame rate drops correlate with device temperature, not game complexity
- iOS shows "iPhone needs to cool down" warning
- Android shows thermal warning or force-closes app
Solution
1. Leave thermal headroom - Don't target 100% utilization:
// Target 80% of peak performance to leave thermal headroom
// This means if you can hit 60 FPS, design for 48 FPS sustained
// Adaptive quality that responds to thermal state
void Update()
{
float currentFPS = 1f / Time.smoothDeltaTime;
// If performance drops significantly, device is likely throttling
if (currentFPS < targetFPS * 0.7f && !isThrottled)
{
isThrottled = true;
ReduceQuality();
Debug.LogWarning("Thermal throttling detected - reducing quality");
}
}2. Test for 30+ minutes - Short test sessions miss thermal issues.
3. Implement battery saver mode - Give players control:
- Lower frame rate cap
- Reduce visual effects
- Lower physics update rate
4. Reduce sustained load:
- Use idle throttling (15 FPS when no input)
- Pause background systems during menus
- Don't render what's not visible
Tags
- performance
- thermal
- sustained-load
OS Kills Apps Under Memory Pressure
Id
memory-pressure-os-kill
Severity
critical
Category
memory
Description
Mobile operating systems aggressively kill background apps and even foreground apps that use too much memory. iOS provides zero warning before killing your app. Android gives a brief moment via onTrimMemory. If your game uses too much RAM, it will be killed silently - players think it crashed.
Symptom
- Game closes without error or crash report
- App disappears when switching to another app briefly
- Players report "random crashes" on lower-RAM devices
- Memory usage grows over time (memory leak)
- App killed after loading large levels
- No crash log in Crashlytics/Firebase
Solution
// Unity - Memory pressure handling
public class MemoryPressureHandler : MonoBehaviour
{
[SerializeField] private float criticalMemoryMB = 50f;
void OnEnable()
{
// iOS low memory callback
Application.lowMemory += OnLowMemory;
}
void OnDisable()
{
Application.lowMemory -= OnLowMemory;
}
private void OnLowMemory()
{
Debug.LogError("[Memory] OS low memory warning - emergency cleanup!");
// 1. Unload all non-essential assets
Resources.UnloadUnusedAssets();
// 2. Force GC (normally avoid, but this is emergency)
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
// 3. Reduce quality to lower memory footprint
QualitySettings.masterTextureLimit = 2; // Quarter resolution
// 4. Clear caches
ClearAudioCache();
ClearTextureCache();
TrimObjectPools();
}
}Best practices:
- Track memory usage continuously
- Set memory budgets per system (textures, audio, objects)
- Unload assets when leaving levels
- Use asset bundles for on-demand loading
- Test on 2GB RAM devices
Tags
- memory
- os-behavior
- crashes
Mobile Draw Call Limits Are Strict
Id
draw-call-limits
Severity
critical
Category
rendering
Description
Mobile GPUs are optimized for throughput, not draw call overhead. Each draw call has significant CPU cost. While a PC can handle 5000+ draw calls, mobile devices struggle with 100+. This is the #1 performance issue in mobile games.
Symptom
- Low frame rate despite simple visuals
- CPU-bound in profiler, not GPU-bound
- "Batches" count in stats is very high
- Adding objects tanks performance more than expected
- UI particularly impacts performance
Solution
Target: < 50 draw calls for low-end, < 100 for high-end
1. Sprite atlasing:
// Combine sprites into atlases - same atlas = same draw call
// In Unity: Sprite Atlas asset, pack all UI sprites together2. GPU Instancing:
// Enable GPU Instancing on materials for identical meshes
// In Unity: Material settings > Enable GPU Instancing3. Static/Dynamic Batching:
- Static: Combine static objects at build time
- Dynamic: Combine similar objects at runtime
4. UI batching:
// Canvas batching rules:
// - Same material = same batch
// - Z-order interleaving breaks batching
// - Mask/RectMask2D breaks batching
// - Layout rebuilds break batching
// Separate Canvases for static vs dynamic UI5. Reduce material variants:
- Use Material Property Blocks instead of material copies
- Share materials where possible
- Use texture arrays for terrain/tiles
Tags
- rendering
- draw-calls
- batching
Touch Input Must Feel Instant
Id
touch-input-latency
Severity
high
Category
input
Description
Touch input has inherent latency from screen digitizer, OS processing, and display refresh. Any additional latency from your game (processing input next frame, animation delays) makes the game feel unresponsive. Players notice > 50ms latency.
Symptom
- Game feels "sluggish" or "laggy" despite good frame rate
- Button presses feel delayed
- Drag gestures feel imprecise or "behind" the finger
- Compared to other games, yours feels slower
Solution
1. Process input immediately:
// WRONG: Processing input after physics/update
void LateUpdate()
{
HandleTouch(); // Too late!
}
// RIGHT: Process input first thing
void Update()
{
HandleTouch(); // First action in update
// Then game logic...
}2. Respond visually immediately:
// Button feedback on touch START, not touch END
void OnPointerDown(PointerEventData eventData)
{
PlayButtonPressAnimation(); // Immediate feedback
PlayHapticFeedback();
}
void OnPointerUp(PointerEventData eventData)
{
TriggerButtonAction(); // Actual action on release
}3. Use high refresh rate if available:
// On 120Hz devices, targeting 120 FPS halves input latency
if (Screen.currentResolution.refreshRateRatio.value > 60)
{
Application.targetFrameRate = 120;
}4. Predictive touch for drag gestures:
- Anticipate finger movement direction
- Render predicted position slightly ahead
Tags
- input
- touch
- latency
App Lifecycle Requires Careful Handling
Id
background-foreground-lifecycle
Severity
critical
Category
lifecycle
Description
Mobile apps constantly transition between foreground, background, and terminated states. Phone calls, notifications, app switching, screen lock - all interrupt your game. If you don't handle these transitions, players lose progress or experience bugs.
Symptom
- Audio continues playing when app is backgrounded
- Game state lost when returning from background
- Timers continue running during pause, causing time skips
- Multiplayer desyncs after returning from background
- Music restarts from beginning on resume
Solution
public class LifecycleHandler : MonoBehaviour
{
private bool _wasPaused;
private float _pauseStartTime;
// Called when app loses focus (notification center, control center)
void OnApplicationFocus(bool hasFocus)
{
if (!hasFocus)
{
// Mute audio immediately (brief focus loss)
AudioListener.pause = true;
}
else if (!_wasPaused)
{
AudioListener.pause = false;
}
}
// Called when app goes to background
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// GOING TO BACKGROUND
_wasPaused = true;
_pauseStartTime = Time.realtimeSinceStartup;
// 1. Save state IMMEDIATELY - OS may kill app
SaveManager.Instance.QuickSave();
// 2. Pause everything
Time.timeScale = 0f;
AudioListener.pause = true;
// 3. Disconnect from servers (optional - reconnect on resume)
NetworkManager.Instance.Pause();
}
else
{
// RETURNING TO FOREGROUND
_wasPaused = false;
float pauseDuration = Time.realtimeSinceStartup - _pauseStartTime;
// 1. Resume systems
AudioListener.pause = false;
// 2. Handle time passage
if (pauseDuration > 300f) // 5 minutes
{
HandleLongAbsence(pauseDuration);
}
// 3. Reconnect to servers
NetworkManager.Instance.Reconnect();
// 4. Resume game (or show menu)
ShowResumeMenu();
}
}
}Tags
- lifecycle
- pause
- background
Device Fragmentation Is Worse Than You Think
Id
device-fragmentation
Severity
high
Category
compatibility
Description
There are thousands of Android devices with different screen sizes, aspect ratios, GPUs, RAM amounts, and OS versions. iOS is better but still has variation. A game that works on your test devices may break on others in unexpected ways.
Symptom
- 1-star reviews saying "doesn't work on my [device]"
- UI elements cut off on certain aspect ratios (tall phones, tablets)
- Shader errors on specific GPU families (Mali, Adreno, PowerVR)
- Game crashes on low-RAM devices
- Touch areas misaligned on notched devices
- Black bars or stretched visuals on unusual resolutions
Solution
1. Aspect ratio handling:
// Support range: 16:9 (old phones) to 21:9 (modern phones) to 4:3 (tablets)
float aspectRatio = (float)Screen.width / Screen.height;
if (aspectRatio > 2f) // Very tall phone
{
// Letterbox or expand view
}
else if (aspectRatio < 1.5f) // Tablet
{
// Pillarbox or expand view
}2. Safe area for notches:
Rect safeArea = Screen.safeArea;
// Adjust UI to fit within safe area
// Keep critical UI away from edges3. GPU-specific issues:
// Mali GPUs: Avoid dependent texture reads, complex shaders
// Adreno: Generally more capable, but watch for driver bugs
// PowerVR: Great performance but older devices only
string gpu = SystemInfo.graphicsDeviceName.ToLower();
if (gpu.Contains("mali"))
{
UseSimplifiedShaders();
}4. Test on device farms:
- AWS Device Farm
- Firebase Test Lab
- Samsung Remote Test Lab
- BrowserStack
Tags
- fragmentation
- compatibility
- android
App Store Rejections Are Common and Painful
Id
store-guidelines-rejection
Severity
high
Category
deployment
Description
Apple's App Store and Google's Play Store have extensive guidelines. Violations result in rejection, delays, and sometimes account penalties. Many developers are surprised by requirements they didn't know about.
Symptom
- App rejected during review
- "Metadata Rejected" or "Binary Rejected" emails
- Long delays between updates (especially iOS)
- App removed from store after policy change
- Account warning or suspension
Solution
Common rejection reasons and fixes:
1. Privacy violations:
- Include privacy policy URL
- Add App Tracking Transparency (iOS 14.5+)
- Justify camera/microphone permissions
- Implement "Delete My Data" feature
2. In-app purchase issues:
- Use platform IAP (no third-party payment for digital goods)
- Restore purchases button is required
- Clear pricing in local currency
- No "free trial" without disclosure
3. Content issues:
- Age rating must match content
- No real-money gambling without license
- User-generated content needs moderation
- No copyrighted material
4. Technical issues:
- iOS: Must work on latest two iOS versions
- Android: Must target latest API level (within 1 year)
- No placeholder content or "coming soon"
- App must be functional during review
5. Pre-submission checklist:
[ ] Privacy policy linked
[ ] GDPR consent implemented (EU users)
[ ] ATT prompt implemented (iOS)
[ ] Age rating set correctly
[ ] All IAP products created in store console
[ ] Restore purchases implemented
[ ] Test account provided for review
[ ] Screenshot/video matches app contentTags
- app-store
- play-store
- submission
ANR (App Not Responding) Freezes Get You Killed
Id
anr-freezes
Severity
critical
Category
performance
Description
Android monitors how long the main thread is blocked. If it exceeds 5 seconds (foreground) or 10 seconds (background), Android shows an "App Not Responding" dialog. Users can force-close. High ANR rate gets your app demoted in Play Store.
Symptom
- "App Not Responding" dialog appears
- Google Play Console shows ANR rate warnings
- "Freeze" during loading or heavy operations
- App becomes unresponsive during network calls
- UI stops updating while processing
Solution
1. Never block main thread:
// WRONG: Synchronous network call
void Start()
{
var response = HttpClient.Get(url); // BLOCKS!
}
// RIGHT: Async operation
async void Start()
{
var response = await HttpClient.GetAsync(url);
}2. Heavy operations off main thread:
// Unity Job System for heavy calculations
var job = new HeavyCalculationJob { input = data };
var handle = job.Schedule();
// Continue other work...
handle.Complete();
var result = job.output;3. Show loading indicator:
- Any operation > 200ms should show loading UI
- Keeps users informed that app is working
4. Chunk heavy work:
// Spread work across frames
IEnumerator ProcessLargeData(List<Data> items)
{
int perFrame = 10;
for (int i = 0; i < items.Count; i++)
{
ProcessItem(items[i]);
if (i % perFrame == 0)
{
yield return null; // Next frame
}
}
}Tags
- anr
- freeze
- threading
Battery Drain Destroys Your Reputation
Id
battery-reputation
Severity
high
Category
battery
Description
Mobile users are hyper-aware of battery drain. A game that drains 30% battery in an hour will get uninstalled and negative reviews. Battery drain is often mentioned in reviews before gameplay concerns.
Symptom
- Reviews mention "battery hog" or "drains battery"
- Players report device getting hot
- Short play sessions despite engaging gameplay
- Uninstalls correlate with battery usage reports
Solution
1. Frame rate management:
// Don't run at 60 FPS when 30 FPS is fine
void OnSceneLoaded(Scene scene)
{
if (scene.name.Contains("Menu"))
{
Application.targetFrameRate = 30;
}
else
{
Application.targetFrameRate = 60;
}
}
// Idle throttling - drop to 15 FPS when no input
if (Time.time - lastInputTime > 5f)
{
Application.targetFrameRate = 15;
}2. Reduce GPU work:
- Disable post-processing when possible
- Use simpler shaders for battery saver mode
- Reduce particle effects
3. Reduce CPU work:
- Reduce physics update rate
- Use aggressive culling
- Spread calculations across frames
4. Sensor management:
- Disable GPS/location when not needed
- Reduce accelerometer polling
- Turn off gyroscope when not active
5. Offer battery saver mode:
- Visible option in settings
- 30 FPS cap, reduced effects
- Communicate trade-offs clearly
Tags
- battery
- optimization
- user-experience
Slow Startup Kills Day-1 Retention
Id
startup-time-kills-retention
Severity
critical
Category
performance
Description
Mobile players expect instant gratification. If your game takes more than 5 seconds to show interactive content, you lose a significant percentage of new users. Cold start time is critical for retention.
Symptom
- Low day-1 retention despite good gameplay
- High uninstall rate before first play session
- Analytics shows drop-off before first level
- Competitors with similar games have better retention
Solution
1. Splash screen strategy:
// Show interactive loading, not static splash
// - Logo with animation
// - Tips or lore
// - Mini-game while loading
// Measure time to interactive
float startTime = Time.realtimeSinceStartup;
// ... load essential assets only ...
float loadTime = Time.realtimeSinceStartup - startTime;
Analytics.LogEvent("time_to_interactive", loadTime);2. Lazy loading:
- Load only what's needed for first screen
- Load rest in background while playing tutorial
- Use asset bundles for on-demand loading
3. Reduce initial download:
- Keep APK/IPA small (< 100MB)
- Use App Bundles (Android) for split APKs
- On Demand Resources (iOS) for optional content
4. Warm start optimization:
- Save state to avoid full reload
- Keep critical assets in memory
- Resume where player left off
Tags
- startup
- retention
- loading
Audio Interruptions Must Be Handled Gracefully
Id
audio-interruption
Severity
medium
Category
audio
Description
Mobile audio is constantly interrupted: phone calls, notifications, other apps, system sounds. Your game must respond appropriately. iOS particularly has strict audio session requirements.
Symptom
- Game audio continues during phone call
- Audio doesn't resume after interruption
- Music playback stops other apps' audio permanently
- Volume doesn't respond to system controls
- Silent mode doesn't work
Solution
// Unity audio session setup (iOS)
public class AudioSessionHandler : MonoBehaviour
{
void Start()
{
// Allow mixing with other apps unless gameplay critical
#if UNITY_IOS
UnityEngine.iOS.Device.hideHomeButton = false;
// Audio session configured in Xcode
#endif
}
void OnApplicationFocus(bool hasFocus)
{
if (!hasFocus)
{
// Mute immediately on focus loss
AudioListener.pause = true;
}
else
{
// Resume if game isn't paused
if (!GameManager.Instance.IsPaused)
{
AudioListener.pause = false;
ResumeMusicFromPosition();
}
}
}
}Best practices:
- Respect silent/vibrate mode (check ringer status)
- Pause your audio when other apps take audio focus
- Resume music from last position, not beginning
- Duck music volume for sound effects
- Test with Bluetooth headphones (connect/disconnect)
Tags
- audio
- interruption
- ios
Mobile Game Dev - Validations
Uncapped Frame Rate on Mobile
Id
mobile-uncapped-framerate
Severity
error
Type
regex
Pattern
Application\.targetFrameRate\s=\s-1
Message
Uncapped frame rate (-1) on mobile causes battery drain and thermal throttling. Set explicit cap (30/60).
Fix Action
Set appropriate frame rate: Application.targetFrameRate = 60; // or 30 for battery saver
Applies To
- *.cs
- *.gd
Synchronous Loading in Update
Id
mobile-sync-loading-update
Severity
error
Type
regex
Pattern
void\s+(Update|FixedUpdate|LateUpdate)\s\([^)]\)[^}]*(Resources\.Load|Addressables\.LoadAsset(?!Async))
Message
Synchronous loading in Update blocks main thread - causes ANR on Android.
Fix Action
Use async loading: var handle = Addressables.LoadAssetAsync<GameObject>(key); await handle;
Applies To
- *.cs
Thread.Sleep on Main Thread
Id
mobile-thread-sleep-main
Severity
error
Type
regex
Pattern
Thread\.Sleep\s\(\s\d+
Message
Thread.Sleep blocks main thread - causes ANR on Android (5s limit).
Fix Action
Use async/await or coroutines: await Task.Delay(milliseconds); // or yield return new WaitForSeconds(seconds);
Applies To
- *.cs
GC.Collect in Hot Path
Id
mobile-gc-in-update
Severity
error
Type
regex
Pattern
void\s+(Update|FixedUpdate|LateUpdate)\s\([^)]\)[^}]*GC\.Collect
Message
GC.Collect in Update causes frame spikes. Only call during loading screens.
Fix Action
Move GC.Collect to loading screens or scene transitions only
Applies To
- *.cs
Deprecated WWW Class
Id
mobile-www-deprecated
Severity
error
Type
regex
Pattern
new\s+WWW\s*\(
Message
WWW class is deprecated and blocks main thread. Use UnityWebRequest.
Fix Action
Replace with async UnityWebRequest: using (var request = UnityWebRequest.Get(url)) { await request.SendWebRequest(); }
Applies To
- *.cs
Texture Creation in Update
Id
mobile-texture-create-update
Severity
error
Type
regex
Pattern
void\s+Update\s\([^)]\)[^}]new\s+Texture2D\s\(
Message
Creating Texture2D in Update causes memory growth and GC spikes.
Fix Action
Create textures once and reuse. Pool textures if needed.
Applies To
- *.cs
Allocating Raycast in Hot Path
Id
mobile-raycast-allocating
Severity
warning
Type
regex
Pattern
void\s+(Update|FixedUpdate)\s\([^)]\)[^}]Physics\.RaycastAll\s\(
Message
RaycastAll allocates array every call. Use RaycastNonAlloc with buffer.
Fix Action
Use non-allocating version: private RaycastHit[] _hits = new RaycastHit[10]; int count = Physics.RaycastNonAlloc(ray, _hits);
Applies To
- *.cs
Allocating Physics Overlap
Id
mobile-overlap-allocating
Severity
warning
Type
regex
Pattern
void\s+(Update|FixedUpdate)\s\([^)]\)[^}]Physics\.(OverlapSphere|OverlapBox|OverlapCapsule)\s\([^)]+\)
Message
Overlap methods allocate arrays. Use NonAlloc variants.
Fix Action
private Collider[] _results = new Collider[20]; int count = Physics.OverlapSphereNonAlloc(pos, radius, _results);
Applies To
- *.cs
LINQ Allocation in Update
Id
mobile-linq-in-update
Severity
warning
Type
regex
Pattern
void\s+(Update|FixedUpdate)\s\([^)]\)[^}]*\.(Where|Select|OrderBy|FirstOrDefault|ToList|ToArray|Any\(\)|Count\(\))
Message
LINQ methods allocate on every call. Use loops for hot paths.
Fix Action
Replace LINQ with for/foreach loops in Update methods
Applies To
- *.cs
String Formatting in Update
Id
mobile-string-format-update
Severity
warning
Type
regex
Pattern
void\s+Update\s\([^)]\)[^}](string\.Format|String\.Format|\$"|\+\s\w+\.ToString\(\))
Message
String operations in Update cause GC allocations. Cache or use StringBuilder.
Fix Action
Cache strings or use StringBuilder: private StringBuilder _sb = new StringBuilder(); _sb.Clear().Append("Score: ").Append(score);
Applies To
- *.cs
Debug.Log Without Conditional
Id
mobile-debug-log-release
Severity
warning
Type
regex
Pattern
Debug\.(Log|LogWarning|LogError)\s*\([^)]+\)
Message
Debug.Log in release builds impacts performance. Use conditional compilation.
Fix Action
Wrap with conditional: #if UNITY_EDITOR || DEVELOPMENT_BUILD Debug.Log(message); #endif
Applies To
- *.cs
Camera.main in Update
Id
mobile-camera-main-update
Severity
warning
Type
regex
Pattern
void\s+(Update|FixedUpdate|LateUpdate)\s\([^)]\)[^}]*Camera\.main
Message
Camera.main searches by tag every call. Cache the reference.
Fix Action
Cache in Start: private Camera _mainCamera; void Start() => _mainCamera = Camera.main;
Applies To
- *.cs
Mouse Input Instead of Touch
Id
mobile-mouse-input
Severity
warning
Type
regex
Pattern
Input\.(GetMouseButton|GetMouseButtonDown|GetMouseButtonUp|mousePosition)
Message
Mouse input on mobile. Use Input.GetTouch or Input.touches for proper touch handling.
Fix Action
Use touch input for mobile: if (Input.touchCount > 0) { Touch touch = Input.GetTouch(0); // handle touch.phase, touch.position }
Applies To
- *.cs
OnApplicationPause Without Save
Id
mobile-no-pause-save
Severity
warning
Type
regex
Pattern
void\s+OnApplicationPause\s\([^)]\)[^}]{0,200}$
Message
OnApplicationPause without apparent save logic. State may be lost if OS kills app.
Fix Action
Save state when pausing: void OnApplicationPause(bool pauseStatus) { if (pauseStatus) { SaveManager.Instance.QuickSave(); } }
Applies To
- *.cs
PlayerPrefs Without Explicit Save
Id
mobile-playerprefs-no-save
Severity
warning
Type
regex
Pattern
PlayerPrefs\.(SetString|SetInt|SetFloat)\s\([^)]+\)(?!.PlayerPrefs\.Save)
Message
PlayerPrefs changes may not persist on mobile without explicit Save().
Fix Action
Call Save after writing: PlayerPrefs.SetInt("HighScore", score); PlayerPrefs.Save(); // Required for mobile
Applies To
- *.cs
High Quality Preset on Mobile
Id
mobile-high-quality-preset
Severity
info
Type
regex
Pattern
QualitySettings\.SetQualityLevel\s\(\s[3-6]\s*[,)]
Message
High quality presets (3+) may cause thermal throttling on mobile.
Fix Action
Consider using quality level 0-2 for mobile, with adaptive quality system
Applies To
- *.cs
GPS Location Without Management
Id
mobile-location-start
Severity
warning
Type
regex
Pattern
Input\.location\.Start\s\([^)]\)(?![^}]*Input\.location\.Stop)
Message
GPS location started without apparent stop. Location drains battery.
Fix Action
Stop location when not needed: Input.location.Start(); // ... use location ... Input.location.Stop();
Applies To
- *.cs
Gyroscope Without Disable
Id
mobile-gyroscope-enabled
Severity
info
Type
regex
Pattern
Input\.gyro\.enabled\s=\strue(?![^}]Input\.gyro\.enabled\s=\s*false)
Message
Gyroscope enabled without apparent disable. Sensors drain battery.
Fix Action
Disable gyroscope when not actively using it
Applies To
- *.cs
Accelerometer in Every Frame
Id
mobile-accelerometer-update
Severity
info
Type
regex
Pattern
void\s+Update\s\([^)]\)[^}]*Input\.acceleration
Message
Reading accelerometer every frame. Consider reducing poll rate.
Fix Action
Throttle accelerometer reads: if (Time.frameCount % 3 == 0) // Every 3rd frame { var acceleration = Input.acceleration; }
Applies To
- *.cs
VSync Without Mobile Consideration
Id
mobile-no-vsync-control
Severity
info
Type
regex
Pattern
QualitySettings\.vSyncCount\s=\s1
Message
VSync adds latency. Consider vSyncCount=0 with targetFrameRate on mobile.
Fix Action
For mobile, disable VSync and use frame rate cap: QualitySettings.vSyncCount = 0; Application.targetFrameRate = 60;
Applies To
- *.cs
Potential Small Touch Target
Id
mobile-touch-small-targets
Severity
info
Type
regex
Pattern
RectTransform.sizeDelta\s=\snew\s+Vector2\s\(\s\d{1,2}\s,\s\d{1,2}\s\)
Message
Small UI element size. Minimum touch target should be 44x44 points.
Fix Action
Ensure touch targets are at least 44x44 points for comfortable tapping
Applies To
- *.cs
Hover State in Mobile UI
Id
mobile-hover-state
Severity
info
Type
regex
Pattern
(OnPointerEnter|OnMouseEnter|IPointerEnterHandler)
Message
Hover state detected. Mobile has no hover - use press/release states instead.
Fix Action
Replace hover with pointer down/up states for mobile
Applies To
- *.cs
Realtime Reflection Probes
Id
mobile-realtime-reflection
Severity
warning
Type
regex
Pattern
ReflectionProbeMode\s\.\sRealtime
Message
Realtime reflection probes are expensive on mobile. Use baked probes.
Fix Action
Use ReflectionProbeMode.Baked for mobile
Applies To
- *.cs
Realtime Shadows Configuration
Id
mobile-realtime-shadows
Severity
info
Type
regex
Pattern
shadowType\s=\sLightShadows\.Hard|shadowType\s=\sLightShadows\.Soft
Message
Realtime shadows are expensive on mobile. Consider baked shadows or no shadows.
Fix Action
Use baked shadows or disable for low-tier devices
Applies To
- *.cs
High Texture Without Compression
Id
mobile-high-texture-resolution
Severity
info
Type
regex
Pattern
new\s+Texture2D\s\(\s\d{4,}\s,\s\d{4,}
Message
Large texture creation (4K+). Ensure ASTC/ETC2 compression for mobile.
Fix Action
Use compressed formats: // In import settings: Format = ASTC (iOS) or ETC2 (Android) // Or use smaller textures with mipmaps
Applies To
- *.cs
Audio Without Focus Handling
Id
mobile-no-focus-handler
Severity
warning
Type
regex
Pattern
AudioSource\.Play\s\([^)]\)
Message
AudioSource.Play without OnApplicationFocus handling may cause audio issues during interruptions.
Fix Action
Handle audio focus: void OnApplicationFocus(bool hasFocus) { AudioListener.pause = !hasFocus; }
Applies To
- *.cs
Infinite Coroutine Without Yield
Id
mobile-infinite-coroutine
Severity
warning
Type
regex
Pattern
while\s\(\strue\s\)\s\{[^}]*yield\s+return\s+null
Message
Infinite coroutine with yield return null runs every frame. Consider throttling.
Fix Action
Throttle infinite coroutines: while (true) { DoWork(); yield return new WaitForSeconds(0.1f); // Not every frame }
Applies To
- *.cs
Synchronous Web Request
Id
mobile-sync-web-request
Severity
error
Type
regex
Pattern
(WebClient|HttpWebRequest).Get(String|Stream)\s\(
Message
Synchronous web requests block main thread - causes ANR. Use async.
Fix Action
Use UnityWebRequest with async: using (var request = UnityWebRequest.Get(url)) { var operation = request.SendWebRequest(); while (!operation.isDone) { yield return null; } }
Applies To
- *.cs
Large JSON Parse on Main Thread
Id
mobile-large-json-parse
Severity
warning
Type
regex
Pattern
JsonUtility\.FromJson|JsonConvert\.Deserialize
Message
JSON parsing on main thread may cause frame drops for large payloads.
Fix Action
Move large JSON parsing off main thread: await Task.Run(() => JsonUtility.FromJson<T>(json));
Applies To
- *.cs
Godot Physics in _process
Id
mobile-godot-process-physics
Severity
error
Type
regex
Pattern
func\s+_process\s\([^)]\)[^}]*(move_and_slide|velocity|apply_force)
Message
Physics in _process causes inconsistent behavior. Use _physics_process.
Fix Action
Move physics code to _physics_process for fixed timestep
Applies To
- *.gd
Godot get_node in Process
Id
mobile-godot-get-node-process
Severity
warning
Type
regex
Pattern
func\s+_process\s\([^)]\)[^}]*(get_node\(|\$)
Message
get_node in _process is expensive. Cache with @onready.
Fix Action
Cache references: @onready var player = $"../Player"
Applies To
- *.gd
Godot load in Process
Id
mobile-godot-load-process
Severity
error
Type
regex
Pattern
func\s+_process\s\([^)]\)[^}]load\s\(
Message
load() in _process blocks. Use preload() or ResourceLoader.load_threaded.
Fix Action
Use preload for constants or async loading
Applies To
- *.gd