Реалізація керування акселерометром для мобільної гри
Нахил телефону як геймпад — інтуїтивний спосіб керування для гоночних ігор, аркад та лабіринтів. Багато розробників інтегрують його за день, а потім тижні витрачають на полірування: згладжування затримки, боротьбу з дрейфом, налаштування мертвої зони та калібрування. Особливо гостро стоять проблеми з дрифтом гіроскопа та нестабільністю показань на різних пристроях. Ми пропонуємо готову реалізацію під ключ з гарантією чуйності та передбачуваної поведінки на всіх апаратах. Оцінимо ваш проект за один день. Зв'яжіться з нами, щоб отримати демо.
Правильний сенсор-ф'южн дає в 10 разів якісніше керування, ніж сирий акселерометр. Готовий модуль скорочує витрати на розробку в 2–3 рази порівняно з самостійною реалізацією — ви економите бюджет на R&D і фокусуєтеся на геймплеї. Згідно з документацією Apple Core Motion, комбінація акселерометра, гіроскопа та магнітометра забезпечує найкращий результат.
Чому сирий акселерометр не підходить
Сирий акселерометр містить гравітацію. На рівному столі: (x: 0, y: 0, z: -9.81). При нахилі пристрою вектор гравітації розмазується по осях, що ламає керування. Правильне джерело — Device Motion / Linear Acceleration — дані вже без гравітації. Але вони мають шум і повільний дрейф гіроскопа. Сенсор-ф'южн комбінує акселерометр, гіроскоп та магнітометр для отримання чистого кута нахилу. На iOS за це відповідає CoreMotion, на Android — SensorManager з алгоритмом Rotation Vector.
Як уникнути дрейфу гіроскопа?
Дрейф гіроскопа виникає через інтеграцію кутової швидкості. Рішення — комбінація з акселерометром (комплементарний фільтр або фільтр Калмана). На iOS використовуємо CMAttitude з xArbitraryZVertical, на Android — getRotationMatrixFromVector. Для критичних сцен підключаємо магнітометр.
Комплементарний фільтр vs фільтр Калмана
Для згладжування даних сенсорів використовуються два основні підходи: комплементарний фільтр і фільтр Калмана. Комплементарний фільтр (alpha = 0.98 для гіроскопа, 0.02 для акселерометра) простий у реалізації і дає затримку менше 5 мс на iOS. Фільтр Калмана на 30% точніший, але вимагає більше обчислювальних ресурсів. Ми вибираємо фільтр залежно від жанру: для шутерів — Калман, для казуальних ігор — комплементарний.
| Параметр | Комплементарний | Калмана |
|---|---|---|
| Точність | Висока | Дуже висока |
| Затримка | <5 мс | ~10 мс |
| Складність | Низька | Середня |
| Продуктивність | Швидко | Вимогливо |
Реалізація на iOS (Swift)
let motionManager = CMMotionManager() motionManager.deviceMotionUpdateInterval = 1.0 / 60.0 motionManager.startDeviceMotionUpdates( using: .xArbitraryZVertical, to: OperationQueue.main ) { [weak self] motion, _ in guard let motion = motion else { return } self?.applyTilt( pitch: Float(motion.attitude.pitch), roll: Float(motion.attitude.roll) ) } Реалізація на Android (Kotlin)
private var baselineAttitude: FloatArray? = null private val currentRotationMatrix = FloatArray(16) // В SensorEventListener.onSensorChanged для TYPE_ROTATION_VECTOR: val rotationMatrix = FloatArray(9) SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values) val orientationAngles = FloatArray(3) SensorManager.getOrientation(rotationMatrix, orientationAngles) val pitch = orientationAngles[1] val roll = orientationAngles[2] val calibratedPitch = pitch - (baselineAttitude?.get(0) ?: 0f) val calibratedRoll = roll - (baselineAttitude?.get(1) ?: 0f) gameEngine.setTilt(calibratedPitch, calibratedRoll) Як виконати калібрування?
Калібрування фіксує нейтральну позицію в момент старту або по натисканню кнопки. Без неї керування буде зміщеним.
fun calibrate() { baselineAttitude = floatArrayOf(currentPitch, currentRoll) } Зберігаємо baseline у SharedPreferences — щоб не перекалібровувати при наступному запуску.
Детальний алгоритм калібрування
- Зафіксувати нейтральну позицію (pitch=0, roll=0).
- Зберегти baseline (середнє за 100 мс).
- Відняти baseline з поточних кутів.
- Застосувати low-pass фільтр до різниці.
- Оновлювати baseline при кожному новому запуску.
Згладжування: low-pass фільтр і мертва зона
Простий експоненційний фільтр прибирає тремтіння рук. Мертва зона ±5° прибирає ненавмисний рух.
struct LowPassFilter { var value: Float = 0 let alpha: Float mutating func update(_ newValue: Float) -> Float { value = alpha * newValue + (1 - alpha) * value return value } } func applyDeadZone(_ value: Float, threshold: Float = 0.087) -> Float { guard abs(value) > threshold else { return 0 } let sign: Float = value > 0 ? 1 : -1 return sign * (abs(value) - threshold) } Непінійна чутливість (степенева функція) дає точне керування на малих кутах і швидке на великих.
Порівняння підходів: iOS vs Android
| Параметр | iOS (CoreMotion) | Android (Rotation Vector) |
|---|---|---|
| Затримка | ~5–10 мс | ~10–15 мс |
| Калібрування | Вбудована | Ручна через baseline |
| Дрейф | Мінімальний | Компенсується магнітометром |
| Простота інтеграції | Висока | Середня |
Параметри для різних жанрів
| Жанр | Alpha (low-pass) | Мертва зона | Нелінійність |
|---|---|---|---|
| Гонки | 0.3-0.4 | ±5° | 1.5 |
| Аркада | 0.2 | ±3° | 1.2 |
| Шутер | 0.6-0.7 | ±2° | 1.0 |
Процес інтеграції
- Ініціалізація сенсора — 60 Гц.
- Отримання даних — pitch/roll, корекція орієнтації.
- Калібрування — нейтральна позиція.
- Фільтрація — low-pass.
- Мертва зона — усунення тремтіння.
- Нелінійна чутливість — перетворення кута.
- Передача в ігровий движок.
Що входить у роботу
- Вихідний код модуля (Swift, Kotlin, C#).
- Документація з інтеграції.
- Рекомендації з тестування на пристроях.
- Підтримка при публікації в сторах.
- Доробка під жанр.
Строки
Базове керування — 3–5 робочих днів. З поліруванням під жанр — 1–2 тижні. Зв'яжіться з нами для точної оцінки вашого проекту — досвід понад 10 років гарантує якість. Замовте демо-версію та оцініть керування.







