Mobile Optimization

Optimize your RPG for mobile platforms with touch controls and performance improvements.

Mobile Adaptation Overview

Transform your desktop RPG into a mobile-friendly experience while maintaining gameplay quality.

📱 Touch Interface

Replace mouse/keyboard with intuitive touch controls

🔋 Battery Life

Optimize performance to extend battery life

📺 Screen Sizes

Support various screen resolutions and aspect ratios

💾 Storage

Minimize app size and optimize asset loading

Touch Controls Implementation

Virtual Joystick

Create a virtual joystick for player movement:

1"keyword">using UnityEngine;
2"keyword">using UnityEngine.UI;
3"keyword">using UnityEngine.EventSystems;
4
5"keyword">public "keyword">class VirtualJoystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
6{
7 [Header("Joystick Settings")]
8 "keyword">public "keyword">float joystickRange = 100f;
9 "keyword">public "keyword">bool dynamicJoystick = true;
10 "keyword">public "keyword">bool hideOnRelease = true;
11
12 [Header("Visual Components")]
13 "keyword">public RectTransform joystickBackground;
14 "keyword">public RectTransform joystickHandle;
15 "keyword">public CanvasGroup joystickCanvasGroup;
16
17 [Header("Input")]
18 "keyword">public "keyword">string horizontalAxis = "Horizontal";
19 "keyword">public "keyword">string verticalAxis = "Vertical";
20
21 "keyword">private Vector2 inputVector = Vector2.zero;
22 "keyword">private Vector2 joystickCenter;
23 "keyword">private "keyword">bool isPressed = false;
24
25 "comment">// Events
26 "keyword">public System.Action<Vector2> OnJoystickMoved;
27 "keyword">public System.Action OnJoystickPressed;
28 "keyword">public System.Action OnJoystickReleased;
29
30 "keyword">public Vector2 InputVector => inputVector;
31 "keyword">public "keyword">bool IsPressed => isPressed;
32
33 "keyword">void Start()
34 {
35 joystickCenter = joystickBackground.position;
36
37 "keyword">if (hideOnRelease)
38 {
39 SetJoystickVisibility(false);
40 }
41 }
42
43 "keyword">public "keyword">void OnPointerDown(PointerEventData eventData)
44 {
45 isPressed = true;
46
47 "keyword">if (dynamicJoystick)
48 {
49 "comment">// Move joystick to touch position
50 joystickBackground.position = eventData.position;
51 joystickCenter = eventData.position;
52 }
53
54 SetJoystickVisibility(true);
55 OnJoystickPressed?.Invoke();
56
57 "comment">// Handle the drag immediately
58 OnDrag(eventData);
59 }
60
61 "keyword">public "keyword">void OnPointerUp(PointerEventData eventData)
62 {
63 isPressed = false;
64 inputVector = Vector2.zero;
65
66 "comment">// Return handle to center
67 joystickHandle.position = joystickCenter;
68
69 "keyword">if (hideOnRelease)
70 {
71 SetJoystickVisibility(false);
72 }
73
74 OnJoystickReleased?.Invoke();
75 OnJoystickMoved?.Invoke(inputVector);
76 }
77
78 "keyword">public "keyword">void OnDrag(PointerEventData eventData)
79 {
80 "keyword">if (!isPressed) "keyword">return;
81
82 Vector2 direction = eventData.position - joystickCenter;
83 "keyword">float distance = Vector2.Distance(eventData.position, joystickCenter);
84
85 "comment">// Clamp to joystick range
86 "keyword">if (distance > joystickRange)
87 {
88 direction = direction.normalized * joystickRange;
89 distance = joystackRange;
90 }
91
92 "comment">// Update handle position
93 joystickHandle.position = joystickCenter + direction;
94
95 "comment">// Calculate input vector (-1 to 1)
96 inputVector = direction / joystickRange;
97
98 OnJoystickMoved?.Invoke(inputVector);
99 }
100
101 "keyword">void SetJoystickVisibility("keyword">bool visible)
102 {
103 "keyword">if (joystickCanvasGroup != null)
104 {
105 joystickCanvasGroup.alpha = visible ? 1f : 0.3f;
106 }
107 }
108
109 "comment">// Static access "keyword">for input
110 "keyword">public "keyword">static Vector2 GetAxis()
111 {
112 var joystick = FindObjectOfType<VirtualJoystick>();
113 "keyword">return joystick != null ? joystick.InputVector : Vector2.zero;
114 }
115}
116
117"comment">// Mobile-friendly player controller
118"keyword">public "keyword">class MobilePlayerController : PlayerController
119{
120 [Header("Mobile Controls")]
121 "keyword">public VirtualJoystick movementJoystick;
122 "keyword">public Button interactButton;
123 "keyword">public Button menuButton;
124 "keyword">public Button inventoryButton;
125
126 [Header("Touch Settings")]
127 "keyword">public "keyword">float doubleTapTime = 0.3f;
128 "keyword">public "keyword">float touchHoldTime = 0.5f;
129
130 "keyword">private "keyword">float lastTapTime = 0f;
131 "keyword">private "keyword">bool isTouchHolding = false;
132 "keyword">private Vector2 lastTouchPosition;
133
134 "keyword">protected "keyword">override "keyword">void Start()
135 {
136 "keyword">base.Start();
137 SetupMobileControls();
138 }
139
140 "keyword">void SetupMobileControls()
141 {
142 "comment">// Setup joystick
143 "keyword">if (movementJoystick != null)
144 {
145 movementJoystick.OnJoystickMoved += OnJoystickMoved;
146 }
147
148 "comment">// Setup UI buttons
149 "keyword">if (interactButton != null)
150 {
151 interactButton.onClick.AddListener(OnInteractPressed);
152 }
153
154 "keyword">if (menuButton != null)
155 {
156 menuButton.onClick.AddListener(OnMenuPressed);
157 }
158
159 "keyword">if (inventoryButton != null)
160 {
161 inventoryButton.onClick.AddListener(OnInventoryPressed);
162 }
163
164 "comment">// Disable desktop controls on mobile
165 #"keyword">if UNITY_ANDROID || UNITY_IOS
166 DisableDesktopControls();
167 #endif
168 }
169
170 "keyword">void DisableDesktopControls()
171 {
172 "comment">// Disable mouse-based interactions
173 var mouseInteraction = GetComponent<MouseInteraction>();
174 "keyword">if (mouseInteraction != null)
175 {
176 mouseInteraction.enabled = false;
177 }
178 }
179
180 "keyword">protected "keyword">override Vector2 GetMovementInput()
181 {
182 "comment">// Use "keyword">virtual joystick input instead of keyboard
183 "keyword">if (movementJoystick != null)
184 {
185 "keyword">return movementJoystick.InputVector;
186 }
187
188 "keyword">return "keyword">base.GetMovementInput(); "comment">// Fallback to keyboard "keyword">for testing
189 }
190
191 "keyword">void OnJoystickMoved(Vector2 direction)
192 {
193 "comment">// Update movement direction
194 moveDirection = direction;
195
196 "comment">// Update animation
197 "keyword">if (animator != null)
198 {
199 animator.SetFloat("moveX", direction.x);
200 animator.SetFloat("moveY", direction.y);
201 animator.SetBool("isMoving", direction.magnitude > 0.1f);
202 }
203 }
204
205 "keyword">void Update()
206 {
207 "keyword">base.Update();
208 HandleTouchInput();
209 }
210
211 "keyword">void HandleTouchInput()
212 {
213 "comment">// Handle direct screen touches "keyword">for interaction
214 "keyword">if (Input.touchCount > 0)
215 {
216 Touch touch = Input.GetTouch(0);
217
218 switch (touch.phase)
219 {
220 case TouchPhase.Began:
221 HandleTouchBegan(touch);
222 break;
223
224 case TouchPhase.Ended:
225 HandleTouchEnded(touch);
226 break;
227
228 case TouchPhase.Stationary:
229 HandleTouchHold(touch);
230 break;
231 }
232 }
233 }
234
235 "keyword">void HandleTouchBegan(Touch touch)
236 {
237 lastTouchPosition = touch.position;
238
239 "comment">// Check "keyword">for double tap
240 "keyword">if (Time.time - lastTapTime < doubleTapTime)
241 {
242 HandleDoubleTap(touch.position);
243 }
244
245 lastTapTime = Time.time;
246 }
247
248 "keyword">void HandleTouchEnded(Touch touch)
249 {
250 isTouchHolding = false;
251
252 "comment">// Check "keyword">if "keyword">this was a tap (not a drag)
253 "keyword">if (Vector2.Distance(touch.position, lastTouchPosition) < 50f)
254 {
255 HandleTap(touch.position);
256 }
257 }
258
259 "keyword">void HandleTouchHold(Touch touch)
260 {
261 "keyword">if (!isTouchHolding && Time.time - lastTapTime > touchHoldTime)
262 {
263 isTouchHolding = true;
264 HandleTouchHold(touch.position);
265 }
266 }
267
268 "keyword">void HandleTap(Vector2 screenPosition)
269 {
270 "comment">// Convert screen position to world position
271 Vector3 worldPosition = Camera.main.ScreenToWorldPoint(screenPosition);
272 worldPosition.z = 0;
273
274 "comment">// Check "keyword">for interactable objects
275 Collider2D hit = Physics2D.OverlapPoint(worldPosition);
276 "keyword">if (hit != null)
277 {
278 var interactable = hit.GetComponent<IInteractable>();
279 "keyword">if (interactable != null && Vector2.Distance(transform.position, worldPosition) <= interactionRange)
280 {
281 interactable.Interact("keyword">this);
282 }
283 }
284 }
285
286 "keyword">void HandleDoubleTap(Vector2 screenPosition)
287 {
288 "comment">// Double tap could be used "keyword">for quick actions like dash or special ability
289 Debug.Log("Double tap detected");
290 }
291
292 "keyword">void HandleTouchHold(Vector2 screenPosition)
293 {
294 "comment">// Touch hold could open context menus or show information
295 Debug.Log("Touch hold detected");
296 }
297
298 "comment">// UI Button handlers
299 "keyword">void OnInteractPressed()
300 {
301 TryInteract();
302 }
303
304 "keyword">void OnMenuPressed()
305 {
306 UIManager.Instance.ToggleGameMenu();
307 }
308
309 "keyword">void OnInventoryPressed()
310 {
311 UIManager.Instance.ToggleInventory();
312 }
313}

Touch UI Components

Create touch-friendly UI elements:

1"keyword">using UnityEngine;
2"keyword">using UnityEngine.UI;
3"keyword">using UnityEngine.EventSystems;
4
5"comment">// Touch-friendly button with visual feedback
6"keyword">public "keyword">class TouchButton : Button
7{
8 [Header("Touch Feedback")]
9 "keyword">public "keyword">float touchScale = 0.95f;
10 "keyword">public "keyword">float feedbackDuration = 0.1f;
11 "keyword">public AudioClip touchSound;
12
13 "keyword">private Vector3 originalScale;
14 "keyword">private AudioSource audioSource;
15
16 "keyword">protected "keyword">override "keyword">void Start()
17 {
18 "keyword">base.Start();
19 originalScale = transform.localScale;
20 audioSource = GetComponent<AudioSource>();
21 }
22
23 "keyword">public "keyword">override "keyword">void OnPointerDown(PointerEventData eventData)
24 {
25 "keyword">base.OnPointerDown(eventData);
26
27 "comment">// Scale down "keyword">for touch feedback
28 transform.localScale = originalScale * touchScale;
29
30 "comment">// Play sound
31 "keyword">if (touchSound != null && audioSource != null)
32 {
33 audioSource.PlayOneShot(touchSound);
34 }
35
36 "comment">// Haptic feedback on mobile
37 #"keyword">if UNITY_ANDROID || UNITY_IOS
38 Handheld.Vibrate();
39 #endif
40 }
41
42 "keyword">public "keyword">override "keyword">void OnPointerUp(PointerEventData eventData)
43 {
44 "keyword">base.OnPointerUp(eventData);
45
46 "comment">// Return to original scale
47 StartCoroutine(ScaleBackToOriginal());
48 }
49
50 System.Collections.IEnumerator ScaleBackToOriginal()
51 {
52 "keyword">float elapsedTime = 0f;
53 Vector3 currentScale = transform.localScale;
54
55 "keyword">while (elapsedTime < feedbackDuration)
56 {
57 elapsedTime += Time.unscaledDeltaTime;
58 "keyword">float t = elapsedTime / feedbackDuration;
59
60 transform.localScale = Vector3.Lerp(currentScale, originalScale, t);
61 yield "keyword">return null;
62 }
63
64 transform.localScale = originalScale;
65 }
66}
67
68"comment">// Swipe gesture detector
69"keyword">public "keyword">class SwipeDetector : MonoBehaviour
70{
71 [Header("Swipe Settings")]
72 "keyword">public "keyword">float minSwipeDistance = 100f;
73 "keyword">public "keyword">float maxSwipeTime = 1f;
74
75 "keyword">private Vector2 startPosition;
76 "keyword">private "keyword">float startTime;
77 "keyword">private "keyword">bool isSwaping = false;
78
79 "keyword">public System.Action<SwipeDirection> OnSwipe;
80
81 "keyword">void Update()
82 {
83 DetectSwipe();
84 }
85
86 "keyword">void DetectSwipe()
87 {
88 "keyword">if (Input.touchCount > 0)
89 {
90 Touch touch = Input.GetTouch(0);
91
92 switch (touch.phase)
93 {
94 case TouchPhase.Began:
95 startPosition = touch.position;
96 startTime = Time.time;
97 isSwaping = true;
98 break;
99
100 case TouchPhase.Ended:
101 "keyword">if (isSwaping)
102 {
103 Vector2 endPosition = touch.position;
104 "keyword">float swipeTime = Time.time - startTime;
105 "keyword">float swipeDistance = Vector2.Distance(startPosition, endPosition);
106
107 "keyword">if (swipeTime <= maxSwipeTime && swipeDistance >= minSwipeDistance)
108 {
109 Vector2 swipeDirection = (endPosition - startPosition).normalized;
110 SwipeDirection direction = GetSwipeDirection(swipeDirection);
111 OnSwipe?.Invoke(direction);
112 }
113 }
114 isSwaping = false;
115 break;
116
117 case TouchPhase.Canceled:
118 isSwaping = false;
119 break;
120 }
121 }
122 }
123
124 SwipeDirection GetSwipeDirection(Vector2 direction)
125 {
126 "keyword">if (Mathf.Abs(direction.x) > Mathf.Abs(direction.y))
127 {
128 "keyword">return direction.x > 0 ? SwipeDirection.Right : SwipeDirection.Left;
129 }
130 "keyword">else
131 {
132 "keyword">return direction.y > 0 ? SwipeDirection.Up : SwipeDirection.Down;
133 }
134 }
135}
136
137"keyword">public "keyword">enum SwipeDirection
138{
139 Up, Down, Left, Right
140}
141
142"comment">// Mobile inventory with touch gestures
143"keyword">public "keyword">class MobileInventoryUI : InventoryUI
144{
145 [Header("Mobile Inventory")]
146 "keyword">public SwipeDetector swipeDetector;
147 "keyword">public "keyword">float itemDragThreshold = 30f;
148
149 "keyword">private InventorySlot draggedSlot;
150 "keyword">private Vector2 dragStartPosition;
151
152 "keyword">protected "keyword">override "keyword">void Start()
153 {
154 "keyword">base.Start();
155
156 "keyword">if (swipeDetector != null)
157 {
158 swipeDetector.OnSwipe += HandleInventorySwipe;
159 }
160 }
161
162 "keyword">void HandleInventorySwipe(SwipeDirection direction)
163 {
164 switch (direction)
165 {
166 case SwipeDirection.Left:
167 "comment">// Switch to next inventory tab
168 SwitchToNextTab();
169 break;
170
171 case SwipeDirection.Right:
172 "comment">// Switch to previous inventory tab
173 SwitchToPreviousTab();
174 break;
175
176 case SwipeDirection.Up:
177 "comment">// Quick sort inventory
178 SortInventory();
179 break;
180
181 case SwipeDirection.Down:
182 "comment">// Close inventory
183 CloseInventory();
184 break;
185 }
186 }
187
188 "comment">// Touch-based item dragging
189 "keyword">public "keyword">void OnItemTouchBegan(InventorySlot slot, Vector2 position)
190 {
191 draggedSlot = slot;
192 dragStartPosition = position;
193 }
194
195 "keyword">public "keyword">void OnItemTouchDrag(Vector2 position)
196 {
197 "keyword">if (draggedSlot == null) "keyword">return;
198
199 "keyword">float dragDistance = Vector2.Distance(position, dragStartPosition);
200
201 "keyword">if (dragDistance > itemDragThreshold)
202 {
203 "comment">// Start visual drag feedback
204 ShowDragFeedback(draggedSlot, position);
205 }
206 }
207
208 "keyword">public "keyword">void OnItemTouchEnd(Vector2 position)
209 {
210 "keyword">if (draggedSlot == null) "keyword">return;
211
212 "comment">// Check "keyword">if dropped on valid target
213 InventorySlot targetSlot = GetSlotAtPosition(position);
214
215 "keyword">if (targetSlot != null && targetSlot != draggedSlot)
216 {
217 "comment">// Attempt to swap or combine items
218 TryMoveItem(draggedSlot, targetSlot);
219 }
220
221 "comment">// Clean up
222 HideDragFeedback();
223 draggedSlot = null;
224 }
225
226 "keyword">void ShowDragFeedback(InventorySlot slot, Vector2 position)
227 {
228 "comment">// Visual feedback "keyword">for dragging items
229 "comment">// This could create a ghost image that follows the touch
230 }
231
232 "keyword">void HideDragFeedback()
233 {
234 "comment">// Hide drag feedback visuals
235 }
236
237 InventorySlot GetSlotAtPosition(Vector2 screenPosition)
238 {
239 "comment">// Convert screen position to check which slot is underneath
240 "keyword">foreach (var slot in inventorySlots)
241 {
242 RectTransform rectTransform = slot.GetComponent<RectTransform>();
243 "keyword">if (RectTransformUtility.RectangleContainsScreenPoint(rectTransform, screenPosition, UICamera))
244 {
245 "keyword">return slot;
246 }
247 }
248
249 "keyword">return null;
250 }
251}

Mobile UI Adaptation

Responsive UI System

Create UI that adapts to different screen sizes:

1"keyword">using UnityEngine;
2"keyword">using UnityEngine.UI;
3
4"keyword">public "keyword">class MobileUIAdapter : MonoBehaviour
5{
6 [Header("Screen Adaptation")]
7 "keyword">public "keyword">float referenceScreenWidth = 1920f;
8 "keyword">public "keyword">float referenceScreenHeight = 1080f;
9 "keyword">public "keyword">float minScaleFactor = 0.5f;
10 "keyword">public "keyword">float maxScaleFactor = 2f;
11
12 [Header("Safe Area")]
13 "keyword">public "keyword">bool adaptToSafeArea = true;
14 "keyword">public RectTransform safeAreaTransform;
15
16 [Header("UI Elements")]
17 "keyword">public CanvasScaler canvasScaler;
18 "keyword">public Button[] mobileOnlyButtons;
19 "keyword">public GameObject[] desktopOnlyElements;
20
21 "keyword">private Rect lastSafeArea = "keyword">new Rect(0, 0, 0, 0);
22
23 "keyword">void Start()
24 {
25 AdaptToCurrentPlatform();
26 AdaptToSafeArea();
27 }
28
29 "keyword">void Update()
30 {
31 "comment">// Check "keyword">for safe area changes (device rotation, etc.)
32 "keyword">if (adaptToSafeArea && Screen.safeArea != lastSafeArea)
33 {
34 AdaptToSafeArea();
35 }
36 }
37
38 "keyword">void AdaptToCurrentPlatform()
39 {
40 "keyword">bool isMobile = Application.isMobilePlatform;
41
42 "comment">// Show/hide platform-specific UI elements
43 "keyword">foreach (var button in mobileOnlyButtons)
44 {
45 button.gameObject.SetActive(isMobile);
46 }
47
48 "keyword">foreach (var element in desktopOnlyElements)
49 {
50 element.SetActive(!isMobile);
51 }
52
53 "comment">// Adjust canvas scaling "keyword">for mobile
54 "keyword">if (isMobile && canvasScaler != null)
55 {
56 canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
57 canvasScaler.referenceResolution = "keyword">new Vector2(referenceScreenWidth, referenceScreenHeight);
58 canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
59 canvasScaler.matchWidthOrHeight = GetOptimalMatchValue();
60 }
61
62 "comment">// Adjust UI element sizes "keyword">for touch
63 "keyword">if (isMobile)
64 {
65 AdaptUIElementsForTouch();
66 }
67 }
68
69 "keyword">float GetOptimalMatchValue()
70 {
71 "keyword">float screenAspect = ("keyword">float)Screen.width / Screen.height;
72 "keyword">float referenceAspect = referenceScreenWidth / referenceScreenHeight;
73
74 "comment">// Match width "keyword">for wider screens, height "keyword">for taller screens
75 "keyword">return screenAspect > referenceAspect ? 0f : 1f;
76 }
77
78 "keyword">void AdaptUIElementsForTouch()
79 {
80 "comment">// Make buttons larger "keyword">for touch input
81 Button[] allButtons = FindObjectsOfType<Button>();
82
83 "keyword">foreach (var button in allButtons)
84 {
85 RectTransform rect = button.GetComponent<RectTransform>();
86
87 "comment">// Ensure minimum touch target size (44x44 points on iOS, 48x48dp on Android)
88 "keyword">float minSize = 44f * (Screen.dpi / 160f); "comment">// Convert to pixels
89
90 "keyword">if (rect.sizeDelta.x < minSize || rect.sizeDelta.y < minSize)
91 {
92 Vector2 newSize = rect.sizeDelta;
93 newSize.x = Mathf.Max(newSize.x, minSize);
94 newSize.y = Mathf.Max(newSize.y, minSize);
95 rect.sizeDelta = newSize;
96 }
97 }
98
99 "comment">// Increase spacing between interactive elements
100 HorizontalLayoutGroup[] horizontalLayouts = FindObjectsOfType<HorizontalLayoutGroup>();
101 "keyword">foreach (var layout in horizontalLayouts)
102 {
103 layout.spacing = Mathf.Max(layout.spacing, 10f);
104 }
105
106 VerticalLayoutGroup[] verticalLayouts = FindObjectsOfType<VerticalLayoutGroup>();
107 "keyword">foreach (var layout in verticalLayouts)
108 {
109 layout.spacing = Mathf.Max(layout.spacing, 10f);
110 }
111 }
112
113 "keyword">void AdaptToSafeArea()
114 {
115 "keyword">if (safeAreaTransform == null) "keyword">return;
116
117 Rect safeArea = Screen.safeArea;
118
119 "comment">// Convert safe area rectangle from absolute pixels to normalized anchor coordinates
120 Vector2 anchorMin = safeArea.position;
121 Vector2 anchorMax = safeArea.position + safeArea.size;
122
123 anchorMin.x /= Screen.width;
124 anchorMin.y /= Screen.height;
125 anchorMax.x /= Screen.width;
126 anchorMax.y /= Screen.height;
127
128 "comment">// Apply to safe area transform
129 safeAreaTransform.anchorMin = anchorMin;
130 safeAreaTransform.anchorMax = anchorMax;
131
132 lastSafeArea = safeArea;
133
134 Debug.Log($"Safe Area adapted: {safeArea}");
135 }
136}
137
138"comment">// Orientation manager "keyword">for mobile
139"keyword">public "keyword">class OrientationManager : MonoBehaviour
140{
141 [Header("Orientation Settings")]
142 "keyword">public "keyword">bool allowPortrait = true;
143 "keyword">public "keyword">bool allowLandscape = true;
144 "keyword">public "keyword">bool allowPortraitUpsideDown = false;
145 "keyword">public "keyword">bool allowLandscapeLeft = true;
146 "keyword">public "keyword">bool allowLandscapeRight = true;
147
148 [Header("UI Layout")]
149 "keyword">public GameObject portraitUI;
150 "keyword">public GameObject landscapeUI;
151
152 "keyword">private ScreenOrientation lastOrientation;
153
154 "keyword">void Start()
155 {
156 SetAllowedOrientations();
157 lastOrientation = Screen.orientation;
158 AdaptUIToOrientation();
159 }
160
161 "keyword">void Update()
162 {
163 "keyword">if (Screen.orientation != lastOrientation)
164 {
165 lastOrientation = Screen.orientation;
166 AdaptUIToOrientation();
167 }
168 }
169
170 "keyword">void SetAllowedOrientations()
171 {
172 "keyword">if (allowPortrait && allowLandscape)
173 {
174 Screen.orientation = ScreenOrientation.AutoRotation;
175 }
176 "keyword">else "keyword">if (allowPortrait)
177 {
178 Screen.orientation = ScreenOrientation.Portrait;
179 }
180 "keyword">else "keyword">if (allowLandscape)
181 {
182 Screen.orientation = ScreenOrientation.LandscapeLeft;
183 }
184
185 "comment">// Set auto-rotation options
186 Screen.autorotateToPortrait = allowPortrait;
187 Screen.autorotateToPortraitUpsideDown = allowPortraitUpsideDown;
188 Screen.autorotateToLandscapeLeft = allowLandscapeLeft;
189 Screen.autorotateToLandscapeRight = allowLandscapeRight;
190 }
191
192 "keyword">void AdaptUIToOrientation()
193 {
194 "keyword">bool isPortrait = Screen.height > Screen.width;
195
196 "keyword">if (portraitUI != null)
197 portraitUI.SetActive(isPortrait);
198
199 "keyword">if (landscapeUI != null)
200 landscapeUI.SetActive(!isPortrait);
201
202 "comment">// Notify other systems about orientation change
203 OnOrientationChanged(isPortrait);
204 }
205
206 "keyword">void OnOrientationChanged("keyword">bool isPortrait)
207 {
208 "comment">// Adjust "keyword">virtual joystick position
209 var joystick = FindObjectOfType<VirtualJoystick>();
210 "keyword">if (joystick != null)
211 {
212 AdaptJoystickToOrientation(joystick, isPortrait);
213 }
214
215 "comment">// Adjust UI panel layouts
216 AdaptPanelLayouts(isPortrait);
217 }
218
219 "keyword">void AdaptJoystickToOrientation(VirtualJoystick joystick, "keyword">bool isPortrait)
220 {
221 RectTransform joystickRect = joystick.GetComponent<RectTransform>();
222
223 "keyword">if (isPortrait)
224 {
225 "comment">// Position joystick in bottom-left "keyword">for portrait
226 joystickRect.anchorMin = "keyword">new Vector2(0f, 0f);
227 joystickRect.anchorMax = "keyword">new Vector2(0f, 0f);
228 joystickRect.anchoredPosition = "keyword">new Vector2(100f, 100f);
229 }
230 "keyword">else
231 {
232 "comment">// Position joystick in bottom-left "keyword">for landscape
233 joystickRect.anchorMin = "keyword">new Vector2(0f, 0f);
234 joystickRect.anchorMax = "keyword">new Vector2(0f, 0f);
235 joystickRect.anchoredPosition = "keyword">new Vector2(120f, 80f);
236 }
237 }
238
239 "keyword">void AdaptPanelLayouts("keyword">bool isPortrait)
240 {
241 "comment">// Find inventory panel and adjust layout
242 var inventoryPanel = FindObjectOfType<InventoryUI>();
243 "keyword">if (inventoryPanel != null)
244 {
245 var gridLayout = inventoryPanel.GetComponentInChildren<GridLayoutGroup>();
246 "keyword">if (gridLayout != null)
247 {
248 "keyword">if (isPortrait)
249 {
250 "comment">// More rows, fewer columns "keyword">for portrait
251 gridLayout.constraintCount = 4;
252 }
253 "keyword">else
254 {
255 "comment">// More columns, fewer rows "keyword">for landscape
256 gridLayout.constraintCount = 6;
257 }
258 }
259 }
260 }
261}

Performance Optimization

Mobile Performance Manager

Optimize performance for mobile devices:

1"keyword">using UnityEngine;
2"keyword">using UnityEngine.Rendering;
3
4"keyword">public "keyword">class MobilePerformanceManager : MonoBehaviour
5{
6 [Header("Performance Settings")]
7 "keyword">public "keyword">int targetFrameRate = 60;
8 "keyword">public "keyword">bool enableVSync = false;
9 "keyword">public "keyword">int qualityLevel = 2; "comment">// 0=Low, 1=Medium, 2=High
10
11 [Header("Battery Optimization")]
12 "keyword">public "keyword">bool reduceFPSOnLowBattery = true;
13 "keyword">public "keyword">float lowBatteryThreshold = 0.2f;
14 "keyword">public "keyword">int lowBatteryFrameRate = 30;
15
16 [Header("Thermal Management")]
17 "keyword">public "keyword">bool enableThermalMonitoring = true;
18 "keyword">public "keyword">float thermalCheckInterval = 5f;
19
20 [Header("Memory Management")]
21 "keyword">public "keyword">bool enableMemoryManagement = true;
22 "keyword">public "keyword">float memoryCheckInterval = 10f;
23 "keyword">public long maxMemoryUsage = 1024 * 1024 * 1024; "comment">// 1GB
24
25 "keyword">private "keyword">float lastThermalCheck;
26 "keyword">private "keyword">float lastMemoryCheck;
27 "keyword">private "keyword">bool isPerformanceReduced = false;
28
29 "keyword">void Start()
30 {
31 InitializePerformanceSettings();
32 InvokeRepeating(nameof(MonitorPerformance), 1f, 1f);
33 }
34
35 "keyword">void InitializePerformanceSettings()
36 {
37 "comment">// Set target frame rate
38 Application.targetFrameRate = targetFrameRate;
39
40 "comment">// Configure VSync
41 QualitySettings.vSyncCount = enableVSync ? 1 : 0;
42
43 "comment">// Set quality level based on device
44 SetQualityBasedOnDevice();
45
46 "comment">// Mobile-specific optimizations
47 #"keyword">if UNITY_ANDROID || UNITY_IOS
48 EnableMobileOptimizations();
49 #endif
50 }
51
52 "keyword">void SetQualityBasedOnDevice()
53 {
54 "comment">// Auto-detect appropriate quality level
55 "keyword">int deviceTier = GetDeviceTier();
56
57 switch (deviceTier)
58 {
59 case 0: "comment">// Low-end device
60 QualitySettings.SetQualityLevel(0);
61 qualityLevel = 0;
62 break;
63 case 1: "comment">// Mid-range device
64 QualitySettings.SetQualityLevel(1);
65 qualityLevel = 1;
66 break;
67 case 2: "comment">// High-end device
68 QualitySettings.SetQualityLevel(2);
69 qualityLevel = 2;
70 break;
71 }
72
73 Debug.Log($"Device tier: {deviceTier}, Quality level: {qualityLevel}");
74 }
75
76 "keyword">int GetDeviceTier()
77 {
78 "comment">// Determine device performance tier based on specs
79 "keyword">int tier = 1; "comment">// Default to medium
80
81 #"keyword">if UNITY_ANDROID
82 "comment">// Android device classification
83 "keyword">if (SystemInfo.systemMemorySize < 2048) "comment">// Less than 2GB RAM
84 {
85 tier = 0; "comment">// Low-end
86 }
87 "keyword">else "keyword">if (SystemInfo.systemMemorySize > 4096) "comment">// More than 4GB RAM
88 {
89 tier = 2; "comment">// High-end
90 }
91 #elif UNITY_IOS
92 "comment">// iOS device classification based on generation
93 "keyword">string deviceModel = SystemInfo.deviceModel;
94 "keyword">if (deviceModel.Contains("iPhone"))
95 {
96 "comment">// Extract iPhone model number logic here
97 tier = ClassifyiPhoneModel(deviceModel);
98 }
99 "keyword">else "keyword">if (deviceModel.Contains("iPad"))
100 {
101 tier = ClassifyiPadModel(deviceModel);
102 }
103 #endif
104
105 "keyword">return tier;
106 }
107
108 "keyword">void EnableMobileOptimizations()
109 {
110 "comment">// Disable unnecessary features "keyword">for mobile
111 Screen.sleepTimeout = SleepTimeout.NeverSleep;
112
113 "comment">// Optimize graphics settings
114 QualitySettings.shadowResolution = ShadowResolution.Low;
115 QualitySettings.shadowDistance = 20f;
116 QualitySettings.anisotropicFiltering = AnisotropicFiltering.Disable;
117
118 "comment">// Reduce particle system max particles
119 var particleSystems = FindObjectsOfType<ParticleSystem>();
120 "keyword">foreach (var ps in particleSystems)
121 {
122 var main = ps.main;
123 main.maxParticles = Mathf.Min(main.maxParticles, 50);
124 }
125
126 "comment">// Optimize audio settings
127 AudioSettings.GetConfiguration(out var audioConfig);
128 audioConfig.numVirtualVoices = 32;
129 audioConfig.numRealVoices = 16;
130 AudioSettings.Reset(audioConfig);
131 }
132
133 "keyword">void MonitorPerformance()
134 {
135 "keyword">if (enableThermalMonitoring && Time.time - lastThermalCheck > thermalCheckInterval)
136 {
137 CheckThermalState();
138 lastThermalCheck = Time.time;
139 }
140
141 "keyword">if (enableMemoryManagement && Time.time - lastMemoryCheck > memoryCheckInterval)
142 {
143 CheckMemoryUsage();
144 lastMemoryCheck = Time.time;
145 }
146
147 "keyword">if (reduceFPSOnLowBattery)
148 {
149 CheckBatteryLevel();
150 }
151 }
152
153 "keyword">void CheckThermalState()
154 {
155 #"keyword">if UNITY_IOS
156 var thermalState = iOS.Device.thermalState;
157
158 switch (thermalState)
159 {
160 case iOS.ThermalState.Critical:
161 ReducePerformance(0.5f);
162 break;
163 case iOS.ThermalState.Serious:
164 ReducePerformance(0.7f);
165 break;
166 case iOS.ThermalState.Fair:
167 ReducePerformance(0.9f);
168 break;
169 case iOS.ThermalState.Nominal:
170 RestorePerformance();
171 break;
172 }
173 #endif
174 }
175
176 "keyword">void CheckMemoryUsage()
177 {
178 long memoryUsage = System.GC.GetTotalMemory(false);
179
180 "keyword">if (memoryUsage > maxMemoryUsage)
181 {
182 "comment">// Force garbage collection
183 System.GC.Collect();
184 Resources.UnloadUnusedAssets();
185
186 Debug.LogWarning($"High memory usage detected: {memoryUsage / (1024 * 1024)}MB");
187 }
188 }
189
190 "keyword">void CheckBatteryLevel()
191 {
192 "keyword">float batteryLevel = SystemInfo.batteryLevel;
193
194 "keyword">if (batteryLevel <= lowBatteryThreshold && batteryLevel > 0)
195 {
196 "keyword">if (!isPerformanceReduced)
197 {
198 Application.targetFrameRate = lowBatteryFrameRate;
199 isPerformanceReduced = true;
200 Debug.Log("Performance reduced due to low battery");
201 }
202 }
203 "keyword">else "keyword">if (isPerformanceReduced && batteryLevel > lowBatteryThreshold + 0.1f)
204 {
205 Application.targetFrameRate = targetFrameRate;
206 isPerformanceReduced = false;
207 Debug.Log("Performance restored");
208 }
209 }
210
211 "keyword">void ReducePerformance("keyword">float multiplier)
212 {
213 "keyword">int reducedFrameRate = Mathf.RoundToInt(targetFrameRate * multiplier);
214 Application.targetFrameRate = reducedFrameRate;
215
216 "comment">// Reduce visual effects
217 "keyword">if (multiplier < 0.8f)
218 {
219 SetLowQualityMode();
220 }
221
222 Debug.Log($"Performance reduced to {multiplier * 100}%");
223 }
224
225 "keyword">void RestorePerformance()
226 {
227 Application.targetFrameRate = targetFrameRate;
228 RestoreQualityMode();
229 Debug.Log("Performance restored");
230 }
231
232 "keyword">void SetLowQualityMode()
233 {
234 "comment">// Temporarily reduce quality settings
235 QualitySettings.shadowResolution = ShadowResolution.Low;
236 QualitySettings.shadowDistance = 10f;
237
238 "comment">// Reduce particle effects
239 var particleSystems = FindObjectsOfType<ParticleSystem>();
240 "keyword">foreach (var ps in particleSystems)
241 {
242 var emission = ps.emission;
243 emission.rateOverTime = emission.rateOverTime.constant * 0.5f;
244 }
245 }
246
247 "keyword">void RestoreQualityMode()
248 {
249 "comment">// Restore quality settings
250 SetQualityBasedOnDevice();
251 }
252}
253
254"comment">// Asset streaming system "keyword">for mobile
255"keyword">public "keyword">class MobileAssetStreamer : MonoBehaviour
256{
257 [Header("Streaming Settings")]
258 "keyword">public "keyword">float streamingRadius = 50f;
259 "keyword">public "keyword">float unloadDistance = 100f;
260 "keyword">public "keyword">int maxAssetsPerFrame = 3;
261
262 [Header("Asset Categories")]
263 "keyword">public AssetCategory[] assetCategories;
264
265 "keyword">private Transform playerTransform;
266 "keyword">private Dictionary<"keyword">string, GameObject> loadedAssets = "keyword">new Dictionary<"keyword">string, GameObject>();
267 "keyword">private Queue<AssetRequest> loadQueue = "keyword">new Queue<AssetRequest>();
268 "keyword">private Queue<"keyword">string> unloadQueue = "keyword">new Queue<"keyword">string>();
269
270 "keyword">void Start()
271 {
272 playerTransform = FindObjectOfType<PlayerController>().transform;
273 InvokeRepeating(nameof(UpdateStreaming), 0f, 0.5f);
274 }
275
276 "keyword">void Update()
277 {
278 ProcessAssetQueue();
279 }
280
281 "keyword">void UpdateStreaming()
282 {
283 Vector3 playerPosition = playerTransform.position;
284
285 "comment">// Check which assets should be loaded
286 "keyword">foreach (var category in assetCategories)
287 {
288 "keyword">foreach (var assetInfo in category.assets)
289 {
290 "keyword">float distance = Vector3.Distance(playerPosition, assetInfo.position);
291 "keyword">string assetKey = assetInfo.GetKey();
292
293 "keyword">if (distance <= streamingRadius && !loadedAssets.ContainsKey(assetKey))
294 {
295 "comment">// Queue "keyword">for loading
296 loadQueue.Enqueue("keyword">new AssetRequest
297 {
298 assetInfo = assetInfo,
299 priority = GetLoadPriority(distance, category.priority)
300 });
301 }
302 "keyword">else "keyword">if (distance > unloadDistance && loadedAssets.ContainsKey(assetKey))
303 {
304 "comment">// Queue "keyword">for unloading
305 unloadQueue.Enqueue(assetKey);
306 }
307 }
308 }
309 }
310
311 "keyword">void ProcessAssetQueue()
312 {
313 "keyword">int processed = 0;
314
315 "comment">// Process unloading first (faster)
316 "keyword">while (unloadQueue.Count > 0 && processed < maxAssetsPerFrame)
317 {
318 "keyword">string assetKey = unloadQueue.Dequeue();
319 UnloadAsset(assetKey);
320 processed++;
321 }
322
323 "comment">// Process loading
324 "keyword">while (loadQueue.Count > 0 && processed < maxAssetsPerFrame)
325 {
326 AssetRequest request = loadQueue.Dequeue();
327 LoadAsset(request.assetInfo);
328 processed++;
329 }
330 }
331
332 "keyword">void LoadAsset(AssetInfo assetInfo)
333 {
334 StartCoroutine(LoadAssetAsync(assetInfo));
335 }
336
337 System.Collections.IEnumerator LoadAssetAsync(AssetInfo assetInfo)
338 {
339 ResourceRequest request = Resources.LoadAsync<GameObject>(assetInfo.resourcePath);
340 yield "keyword">return request;
341
342 "keyword">if (request.asset != null)
343 {
344 GameObject instance = Instantiate(request.asset as GameObject, assetInfo.position, assetInfo.rotation);
345 loadedAssets[assetInfo.GetKey()] = instance;
346
347 "comment">// Apply mobile optimizations to loaded asset
348 OptimizeAssetForMobile(instance);
349 }
350 }
351
352 "keyword">void UnloadAsset("keyword">string assetKey)
353 {
354 "keyword">if (loadedAssets.ContainsKey(assetKey))
355 {
356 GameObject asset = loadedAssets[assetKey];
357 loadedAssets.Remove(assetKey);
358
359 "keyword">if (asset != null)
360 {
361 Destroy(asset);
362 }
363 }
364 }
365
366 "keyword">void OptimizeAssetForMobile(GameObject asset)
367 {
368 "comment">// Reduce LOD distances
369 LODGroup lodGroup = asset.GetComponent<LODGroup>();
370 "keyword">if (lodGroup != null)
371 {
372 LOD[] lods = lodGroup.GetLODs();
373 "keyword">for ("keyword">int i = 0; i < lods.Length; i++)
374 {
375 lods[i].screenRelativeTransitionHeight *= 0.7f; "comment">// Switch to lower LOD sooner
376 }
377 lodGroup.SetLODs(lods);
378 }
379
380 "comment">// Optimize colliders
381 Collider[] colliders = asset.GetComponentsInChildren<Collider>();
382 "keyword">foreach (var collider in colliders)
383 {
384 "keyword">if (collider is MeshCollider meshCollider && !meshCollider.convex)
385 {
386 "comment">// Replace complex mesh colliders with simpler ones
387 meshCollider.convex = true;
388 }
389 }
390 }
391
392 "keyword">int GetLoadPriority("keyword">float distance, "keyword">int categoryPriority)
393 {
394 "keyword">return categoryPriority * 1000 + ("keyword">int)(100f - distance);
395 }
396}
397
398[System.Serializable]
399"keyword">public "keyword">class AssetCategory
400{
401 "keyword">public "keyword">string categoryName;
402 "keyword">public "keyword">int priority = 1;
403 "keyword">public AssetInfo[] assets;
404}
405
406[System.Serializable]
407"keyword">public "keyword">class AssetInfo
408{
409 "keyword">public "keyword">string resourcePath;
410 "keyword">public Vector3 position;
411 "keyword">public Quaternion rotation = Quaternion.identity;
412
413 "keyword">public "keyword">string GetKey()
414 {
415 "keyword">return $"{resourcePath}_{position.x}_{position.y}_{position.z}";
416 }
417}
418
419"keyword">public "keyword">class AssetRequest
420{
421 "keyword">public AssetInfo assetInfo;
422 "keyword">public "keyword">int priority;
423}

Platform-Specific Features

iOS Integration

Implement iOS-specific features:

1#"keyword">if UNITY_IOS
2"keyword">using UnityEngine;
3"keyword">using UnityEngine.iOS;
4
5"keyword">public "keyword">class iOSIntegration : MonoBehaviour
6{
7 [Header("iOS Features")]
8 "keyword">public "keyword">bool enableGameCenter = true;
9 "keyword">public "keyword">bool enableNotifications = true;
10 "keyword">public "keyword">bool enableCloudSave = true;
11
12 "keyword">void Start()
13 {
14 InitializeiOSFeatures();
15 }
16
17 "keyword">void InitializeiOSFeatures()
18 {
19 "comment">// Request notification permissions
20 "keyword">if (enableNotifications)
21 {
22 RequestNotificationPermissions();
23 }
24
25 "comment">// Initialize Game Center
26 "keyword">if (enableGameCenter)
27 {
28 InitializeGameCenter();
29 }
30
31 "comment">// Setup cloud save
32 "keyword">if (enableCloudSave)
33 {
34 SetupCloudSave();
35 }
36
37 "comment">// Configure device settings
38 ConfigureDeviceSettings();
39 }
40
41 "keyword">void RequestNotificationPermissions()
42 {
43 var request = "keyword">new AuthorizationRequest(AuthorizationOption.Alert | AuthorizationOption.Badge, true);
44 request.OnCompleted += (response) =>
45 {
46 "keyword">if (response.granted)
47 {
48 Debug.Log("Notification permissions granted");
49 }
50 };
51 }
52
53 "keyword">void InitializeGameCenter()
54 {
55 Social.localUser.Authenticate(("keyword">bool success) =>
56 {
57 "keyword">if (success)
58 {
59 Debug.Log("Game Center authentication successful");
60 }
61 "keyword">else
62 {
63 Debug.Log("Game Center authentication failed");
64 }
65 });
66 }
67
68 "keyword">void SetupCloudSave()
69 {
70 "comment">// Enable iCloud document sync
71 Application.persistentDataPath = System.IO.Path.Combine(Application.persistentDataPath, "Documents");
72 }
73
74 "keyword">void ConfigureDeviceSettings()
75 {
76 "comment">// Prevent device sleep
77 Screen.sleepTimeout = SleepTimeout.NeverSleep;
78
79 "comment">// Configure audio session
80 AVAudioSession.sharedInstance().SetCategory(AVAudioSessionCategory.Ambient);
81 }
82
83 "keyword">public "keyword">void ShowLeaderboard()
84 {
85 "keyword">if (Social.localUser.authenticated)
86 {
87 Social.ShowLeaderboardUI();
88 }
89 }
90
91 "keyword">public "keyword">void ReportScore(long score, "keyword">string leaderboardID)
92 {
93 "keyword">if (Social.localUser.authenticated)
94 {
95 Social.ReportScore(score, leaderboardID, ("keyword">bool success) =>
96 {
97 Debug.Log($"Score reported: {success}");
98 });
99 }
100 }
101}
102#endif
103
104#"keyword">if UNITY_ANDROID
105"keyword">using UnityEngine;
106
107"keyword">public "keyword">class AndroidIntegration : MonoBehaviour
108{
109 [Header("Android Features")]
110 "keyword">public "keyword">bool enableGooglePlay = true;
111 "keyword">public "keyword">bool enableNotifications = true;
112 "keyword">public "keyword">bool enableBackButton = true;
113
114 "keyword">private AndroidJavaObject currentActivity;
115
116 "keyword">void Start()
117 {
118 InitializeAndroidFeatures();
119 }
120
121 "keyword">void InitializeAndroidFeatures()
122 {
123 "comment">// Get current activity
124 AndroidJavaClass unityPlayer = "keyword">new AndroidJavaClass("com.unity3d.player.UnityPlayer");
125 currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
126
127 "comment">// Initialize Google Play Games
128 "keyword">if (enableGooglePlay)
129 {
130 InitializeGooglePlay();
131 }
132
133 "comment">// Setup notifications
134 "keyword">if (enableNotifications)
135 {
136 SetupAndroidNotifications();
137 }
138
139 "comment">// Configure back button behavior
140 "keyword">if (enableBackButton)
141 {
142 SetupBackButtonHandling();
143 }
144 }
145
146 "keyword">void InitializeGooglePlay()
147 {
148 Social.localUser.Authenticate(("keyword">bool success) =>
149 {
150 "keyword">if (success)
151 {
152 Debug.Log("Google Play Games authentication successful");
153 }
154 });
155 }
156
157 "keyword">void SetupAndroidNotifications()
158 {
159 "comment">// Create notification channel "keyword">for Android 8.0+
160 "keyword">if (Application.platform == RuntimePlatform.Android)
161 {
162 var channelId = "rpg_notifications";
163 var channelName = "RPG Notifications";
164 var channelDescription = "Notifications ">for RPG game events";
165
166 "comment">// This would require additional Android plugins
167 Debug.Log("Android notifications configured");
168 }
169 }
170
171 "keyword">void SetupBackButtonHandling()
172 {
173 "comment">// Handle Android back button
174 Input.backButtonLeavesApp = false;
175 }
176
177 "keyword">void Update()
178 {
179 "keyword">if (enableBackButton && Input.GetKeyDown(KeyCode.Escape))
180 {
181 HandleBackButton();
182 }
183 }
184
185 "keyword">void HandleBackButton()
186 {
187 "comment">// Custom back button behavior
188 "keyword">if (UIManager.Instance.IsAnyPanelOpen())
189 {
190 UIManager.Instance.CloseTopPanel();
191 }
192 "keyword">else
193 {
194 "comment">// Show quit confirmation
195 UIManager.Instance.ShowQuitConfirmation();
196 }
197 }
198
199 "keyword">public "keyword">void RequestReview()
200 {
201 "comment">// Open Play Store "keyword">for review
202 Application.OpenURL($"market:">//details?id={Application.identifier}");
203 }
204
205 "keyword">public "keyword">void ShowAchievements()
206 {
207 "keyword">if (Social.localUser.authenticated)
208 {
209 Social.ShowAchievementsUI();
210 }
211 }
212}
213#endif

Testing & Deployment

Mobile Testing Framework

Create automated testing for mobile features:

1"keyword">using UnityEngine;
2"keyword">using System.Collections;
3
4"keyword">public "keyword">class MobileTesting : MonoBehaviour
5{
6 [Header("Test Configuration")]
7 "keyword">public "keyword">bool runTestsOnStart = false;
8 "keyword">public "keyword">bool testTouchControls = true;
9 "keyword">public "keyword">bool testPerformance = true;
10 "keyword">public "keyword">bool testUI = true;
11
12 "keyword">private TestResults testResults = "keyword">new TestResults();
13
14 "keyword">void Start()
15 {
16 "keyword">if (runTestsOnStart)
17 {
18 StartCoroutine(RunAllTests());
19 }
20 }
21
22 IEnumerator RunAllTests()
23 {
24 Debug.Log("Starting mobile tests...");
25
26 "keyword">if (testTouchControls)
27 {
28 yield "keyword">return StartCoroutine(TestTouchControls());
29 }
30
31 "keyword">if (testPerformance)
32 {
33 yield "keyword">return StartCoroutine(TestPerformance());
34 }
35
36 "keyword">if (testUI)
37 {
38 yield "keyword">return StartCoroutine(TestUIScaling());
39 }
40
41 LogTestResults();
42 }
43
44 IEnumerator TestTouchControls()
45 {
46 Debug.Log("Testing touch controls...");
47
48 "comment">// Test "keyword">virtual joystick
49 var joystick = FindObjectOfType<VirtualJoystick>();
50 "keyword">if (joystick != null)
51 {
52 testResults.touchControlsWorking = true;
53 Debug.Log("✓ Virtual joystick found");
54 }
55 "keyword">else
56 {
57 testResults.touchControlsWorking = false;
58 Debug.LogError("✗ Virtual joystick not found");
59 }
60
61 "comment">// Test touch buttons
62 var touchButtons = FindObjectsOfType<TouchButton>();
63 testResults.touchButtonCount = touchButtons.Length;
64 Debug.Log($"✓ Found {touchButtons.Length} touch buttons");
65
66 yield "keyword">return null;
67 }
68
69 IEnumerator TestPerformance()
70 {
71 Debug.Log("Testing performance...");
72
73 "keyword">float startTime = Time.realtimeSinceStartup;
74 "keyword">int frameCount = 0;
75 "keyword">float totalFrameTime = 0f;
76
77 "comment">// Measure performance "keyword">for 5 seconds
78 "keyword">while (Time.realtimeSinceStartup - startTime < 5f)
79 {
80 totalFrameTime += Time.unscaledDeltaTime;
81 frameCount++;
82 yield "keyword">return null;
83 }
84
85 "keyword">float averageFPS = frameCount / totalFrameTime;
86 testResults.averageFPS = averageFPS;
87
88 "keyword">if (averageFPS >= 30f)
89 {
90 Debug.Log($"✓ Performance good: {averageFPS:F1} FPS");
91 }
92 "keyword">else
93 {
94 Debug.LogWarning($"⚠ Performance low: {averageFPS:F1} FPS");
95 }
96
97 "comment">// Test memory usage
98 long memoryUsage = System.GC.GetTotalMemory(false);
99 testResults.memoryUsageMB = memoryUsage / (1024 * 1024);
100 Debug.Log($"Memory usage: {testResults.memoryUsageMB} MB");
101 }
102
103 IEnumerator TestUIScaling()
104 {
105 Debug.Log("Testing UI scaling...");
106
107 "comment">// Test canvas scaler
108 var canvasScaler = FindObjectOfType<CanvasScaler>();
109 "keyword">if (canvasScaler != null)
110 {
111 testResults.uiScalingWorking = true;
112 Debug.Log($"✓ Canvas scaler found: {canvasScaler.uiScaleMode}");
113 }
114
115 "comment">// Test safe area adaptation
116 var safeAreaAdapter = FindObjectOfType<MobileUIAdapter>();
117 "keyword">if (safeAreaAdapter != null)
118 {
119 testResults.safeAreaWorking = true;
120 Debug.Log("✓ Safe area adapter found");
121 }
122
123 "comment">// Test button sizes
124 var buttons = FindObjectsOfType<Button>();
125 "keyword">int appropriateSizeButtons = 0;
126
127 "keyword">foreach (var button in buttons)
128 {
129 RectTransform rect = button.GetComponent<RectTransform>();
130 "keyword">if (rect.sizeDelta.x >= 44f && rect.sizeDelta.y >= 44f)
131 {
132 appropriateSizeButtons++;
133 }
134 }
135
136 testResults.appropriateSizeButtons = appropriateSizeButtons;
137 testResults.totalButtons = buttons.Length;
138
139 Debug.Log($"✓ {appropriateSizeButtons}/{buttons.Length} buttons have appropriate touch target size");
140
141 yield "keyword">return null;
142 }
143
144 "keyword">void LogTestResults()
145 {
146 Debug.Log("=== MOBILE TEST RESULTS ===");
147 Debug.Log($"Touch Controls: {(testResults.touchControlsWorking ? "PASS" : "FAIL")}");
148 Debug.Log($"Touch Buttons: {testResults.touchButtonCount} found");
149 Debug.Log($"Average FPS: {testResults.averageFPS:F1}");
150 Debug.Log($"Memory Usage: {testResults.memoryUsageMB} MB");
151 Debug.Log($"UI Scaling: {(testResults.uiScalingWorking ? "PASS" : "FAIL")}");
152 Debug.Log($"Safe Area: {(testResults.safeAreaWorking ? "PASS" : "FAIL")}");
153 Debug.Log($"Button Sizes: {testResults.appropriateSizeButtons}/{testResults.totalButtons} appropriate");
154 Debug.Log("========================");
155 }
156}
157
158[System.Serializable]
159"keyword">public "keyword">class TestResults
160{
161 "keyword">public "keyword">bool touchControlsWorking;
162 "keyword">public "keyword">int touchButtonCount;
163 "keyword">public "keyword">float averageFPS;
164 "keyword">public long memoryUsageMB;
165 "keyword">public "keyword">bool uiScalingWorking;
166 "keyword">public "keyword">bool safeAreaWorking;
167 "keyword">public "keyword">int appropriateSizeButtons;
168 "keyword">public "keyword">int totalButtons;
169}
170
171"comment">// Build settings "keyword">for mobile deployment
172#"keyword">if UNITY_EDITOR
173"keyword">using UnityEditor;
174
175"keyword">public "keyword">class MobileBuildSettings
176{
177 [MenuItem("RPG Tools/Build/Configure ">for Mobile")]
178 "keyword">public "keyword">static "keyword">void ConfigureForMobile()
179 {
180 "comment">// Graphics settings
181 PlayerSettings.colorSpace = ColorSpace.Gamma;
182 PlayerSettings.gpuSkinning = false;
183
184 "comment">// iOS settings
185 PlayerSettings.iOS.targetDevice = iOSTargetDevice.iPhoneAndiPad;
186 PlayerSettings.iOS.targetOSVersionString = "12.0";
187
188 "comment">// Android settings
189 PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
190 PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel23;
191 PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevelAuto;
192
193 Debug.Log("Mobile build settings configured");
194 }
195
196 [MenuItem("RPG Tools/Build/Build Mobile")]
197 "keyword">public "keyword">static "keyword">void BuildMobile()
198 {
199 ConfigureForMobile();
200
201 "comment">// Build "keyword">for current platform
202 BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
203 "keyword">string buildPath = $"Builds/Mobile/{Application.productName}";
204
205 BuildPlayerOptions buildOptions = "keyword">new BuildPlayerOptions
206 {
207 scenes = EditorBuildSettings.scenes.Select(scene => scene.path).ToArray(),
208 locationPathName = buildPath,
209 target = target,
210 options = BuildOptions.None
211 };
212
213 BuildPipeline.BuildPlayer(buildOptions);
214 Debug.Log($"Mobile build completed: {buildPath}");
215 }
216}
217#endif

Mobile Optimization Checklist

📱 Touch Controls

  • Virtual joystick implemented
  • Touch buttons for all actions
  • Gesture support added
  • Haptic feedback configured

🎨 UI Adaptation

  • Safe area handling
  • Multiple screen sizes supported
  • Touch target sizes appropriate
  • Orientation changes handled

⚡ Performance

  • Target framerate set
  • Battery optimization enabled
  • Memory management active
  • Asset streaming implemented

🚀 Deployment

  • Platform-specific features
  • App store guidelines followed
  • Testing on real devices
  • Performance profiling done

Continue Learning

Explore related tutorials to expand your knowledge