Голый, чтобы потереть горячие точки. Мини-игра Wechat jump hop, простой стиль, не может не прыгать автоматически, когда ум движется. Код был слишком трудоемким для чтения, поэтому я решил написать статью с описанием своего кода.
Только для отработки навыков nodejs, не обсуждайте методы читерства.
окончательный эффект
- Автоматически заряжаемый отскок
Вспомогательный прыжок по линии:player.Youku.com/embed/XM на MW…
Перейти автоматически:player.Youku.com/embed/XM на MW…
содержание
-
Используются нестандартные инструменты
-
анализ игровых целей
-
Данные устройства (с помощью чужого репозитория github, а не ADB)
- Получение изображения экрана мобильного телефона, отображение на том же экране
- Отправка событий касания мобильного телефона
-
Обработка изображения
-
Очки навыка:
- Electron-vue
- Vue directives
- Обещания, асинхронный/ожидание
- Nodejs Socket
- koa + websocket
- Opencv4nodejs
Используются нестандартные инструменты
- Opencv4Nodejsnodejs вызывает библиотеку opencv
- openstf/minicapПоток изображений скриншота Android-устройства в режиме сокета. Android 5.0 и выше, частота кадров выходного потока соответствует устройству.
- openstf/minitouchАльтернатива sendevent для устройств Android с высокой производительностью в реальном времени.
- electron-vueИспользуйте электрон для прямого взаимодействия с сокетом и используйте vue для отображения экрана.
анализ игровых целей
В игре продолжительность рывка злодея определяет расстояние прыжка, и если он удачно перепрыгнет на следующий пирс, ему будут добавляться очки.
Цель состоит в том, чтобы получить положение злодея, получить положение целевой точки, а затем рассчитать расстояние.
В процессе этого я обнаружил, что направление отскока персонажа составляет 30 градусов по диагонали.Если персонаж не прыгает в центральную точку, смещение положения, похоже, не приводит к сбою игры.
Таким образом, цель игры упрощается для поиска положения злодея и абсциссы центральной точки поискового пирса.
Абсцисса центральной точки пирса в основном совпадает с абсциссой вершины пирса, и только один прямоугольный пирс несовместим.
Изображение круглой головы злодея остается неизменным.Используя распознавание шаблона opencv, положение головы можно точно найти напрямую.
Таким образом, цель игры упрощается до:
- Найдите кривую времени-расстояния отскока.
- Найдите координаты злодея.
- Найдите координаты вершины.
данные устройства
Получение изображения экрана мобильного телефона, отображение на том же экране
Будуopenstf/minicap
,openstf/minitouch
Разверните на устройстве Android, затем запустите сокет через adb, а затем подключите сокет через adb.Последующие запросы и отправка данных не требуют повторного создания соединения adb, а производительность в реальном времени лучше.
стартовый сокет : /src/renderer/util/adbkit.js#L77
async function startMinicap
:
...
let command = util.format(
'LD_LIBRARY_PATH=%s exec %s %s',
path.dirname('/data/local/tmp/minicap.so'),
'/data/local/tmp/minicap',
`-P 1080x1920@360x640/${orientation} -S -Q ${quality}`
)
// `-P 540x960@360x640/${orientation} -S -Q ${quality}`
status.tryingStart = true
let stdout = await client.shell(device.id, command)
...
stdout — это объект сокета стандартного вывода, а затем добавьте обещание, которое разрешается без ошибок в течение 200 мс, чтобы startMinicap мог ожидать корректно.
Подключите Socket, получите поток:/src/renderer/util/getStream.js#L6 async function liveStream
:
...
var { err, stream } = await client
.openLocal(device.id, 'localabstract:minicap')
.timeout(10000)
.then(out => ({ stream: out }))
.catch(err => ({ err }))
...
Получите поток, а затем используйте событие on readable, чтобы получить изображение каждого кадра экрана, формат сжатия jpeg.
...
stream.on('readable', tryRead)
...
function tryRead #L50, логика заключается в том, чтобы каждый раз анализировать буфер, считываемый потоком, и записывать его в необработанный буфер jpeg в соответствии с условиями.
Здесь вы можете просто выполнить ограниченную обработку частоты обновления изображения.#L154
Использование холста для отображения буферных изображений в Vue
Отобразите изображение, которое может легко дать обратную связь по результату суждения.
Сокет на предыдущем шаге можно легко импортировать в электрон, а каждому фреймбуферу можно легко назначить vm.screendata.
Используя vue для мониторинга данных экрана, вы можете отображать данные экрана на холсте в режиме реального времени.
Здесь используются директивы vue.
<canvas v-screen='screendata' id='screen' :width="canvasWidth" :height="canvasHeight" :style="canvasStyle"></canvas>
...
directives: {
screen(el, binding, vNode) {
// console.info('[canvas Screen]')
if (!binding.value) return
// console.info('render an image ---- ', +new Date())
let BLANK_IMG = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
var g = el.getContext('2d')
var blob = new Blob([binding.value], { type: 'image/jpeg' })
var URL = window.URL || window.webkitURL
var img = new Image()
img.onload = () => {
vNode.context.canvasWidth = img.width
vNode.context.canvasHeight = img.height
g.drawImage(img, 0, 0)
// firstImgLoad = true
img.onload = null
img.src = BLANK_IMG
img = null
u = null
blob = null
}
var u = URL.createObjectURL(blob)
img.src = u
},
...
}
...
использоватьURL.createObjectURL
Создайте адрес src для img, а затем нарисуйте img на холсте.
определениеdirectives
час,vNode
Его необходимо передать вручную, и его нельзя использовать напрямую.this
.
【
此处,假装一个动态GIF:
stream.on('readable',function tryRead(){
...
framedata = chunk.read()
callback(framedata)
...
})
function callback (framedata){
vm.screendata = framedata
}
每一个framedata 赋给 vm.screendata, Canvas上显示的图像刷新一下。
】
То же самое используется в кодеdirectives
Сделал вспомогательный линейный слой для отображения вспомогательных линий и найденных точек.
Отправка события касания устройства
В соответствии с потоком экрана получите сокет minitouch и запишите сокет в соответствии с форматом в README minitouch, чтобы завершить симуляцию событий касания.
Контроль продолжительности касания можно регулировать, контролируя продолжительность приземления и касания. События касания совместимого устройства, настроенные на touchmove каждые 200 мс. кодMirrorScreen.vue#L221
Корректировка времени, реализованная через async/await. Стандартное приложение API, кажется, нечего сказать.
сбить землю
На данный момент подготовленные инструменты могут предоставить мне скриншоты, точки отрисовки и начислить точную продолжительность мс, поэтому я собрал некоторые данные:
X = [0,50,100,150,200,250,300,700,1000]
Y = [0,33, 69, 90,144,177,207,516, 753]
Уравнение получается, точность не предельная, но пользоваться можно.
f(x) = -6.232e-08 x^3 + 0.0001559 x^2 + 0.6601 x - 0.7638
Обработка изображения
Во-первых, использование open4nodejs.opencv4nodejsREADME довольно всеобъемлющий.
Когда я впервые искал версию opencv для узла, я обнаружил, что существуют версии 2.4 и 3.0. В этом репозитории используется версия 3.0, и установка также прошла гладко.
В README очень четко описаны изображения с разными номерами каналов, получение информации о цвете изображения по координатам, создание формы и т.д.
Чтобы найти вершины, я подумал об использовании метода заливки, чтобы заполнить цвет фона, а затем бинаризировать + инвертировать цвет, чтобы получить верхнюю вершину.
В реальном процессе вы столкнетесь:
- Злодей выше новой насыпи, или рябь и бонусный шрифт, появляющиеся, когда злодей прыгает в центр, выше новой насыпи.
Итак, сделайте еще один шаг и покройте злодея и части над ним фоновым цветом. - Простенки белые, или светло-зеленые, близко к фону При использовании бинаризации ОСТЮ эффект неидеален.
Итак, еще один шаг,
Установите цветовой диапазон от 80 до 255,
Если есть значение серого больше 235 (близкое к белому), оно сразу станет равным 80 (нижнее граничное значение). Создайте алгоритм оттенков серого, который больше подходит для каналов, чем цвет фона, вдали от оттенков серого фона. Возьмите средний фоновый цвет трех каналов RGB из 10 пикселей через буфер, а затем возведите в квадрат и просуммируйте разницу каждого элемента. Уменьшите эффект градиента, разница в пределах 13, установите на 0.
Затем используйте это изображение в градациях серого, чтобы заполнить фон водой, пороговое значение равно 40, и используйте метод BINARY_INV для обработки бинарного изображения. Затем ищите строку за строкой, чтобы найти строку, в которой находится вершина. Затем используйте метод массива, чтобы легко классифицировать элементы строки в соответствии с дисперсией, чтобы получить самый длинный непрерывный диапазон пикселей, и взять среднее значение, которое является абсциссой вершины.
Обработать:
- Перехват обрабатываемой области из кадра
- Покройте злодея фоновым цветом
Нарисуйте прямоугольник фоновым цветом, закрывающий злодея.
Черные квадраты — это окончательно найденные позиции вершин. - Используйте пользовательский метод оттенков серого, чтобы преобразовать изображение в оттенки серого.
grayExt2.js#L8 - Размытие по Гауссу + диффузная вода для заливки фона.
Размытие по Гауссу может легко убрать влияние шума
- Бинаризация
Верхняя линия на рисунке неровная. Когда вы сталкиваетесь с вершиной, она может быть устранена. Таким образом, когда берется абсцисса, максимальное количество строк n = 3 вычисляется из последней строки. Результат показан перед изображением. Отклонение есть, но в пределах допустимого.
Флаконы могут быть идентифицированы таким же образом:
Определите местонахождение злодея
Используйте метод templateMatch opencv для быстрого получения результатовfindTarget2.js#L11:
...
let ballMat = cv.imread(path.resolve(__dirname, '..', 'ball.jpg'), 0) # 小人头部为固定图片
...
let { maxLoc: ballPoint } = colorMat
.bgrToGray()
.matchTemplate(ballMat, 3)
.minMaxLoc()
...
Возьмите maxLoc из результата, чтобы получить положение базы злодея и сохраните его в переменнойballPoint
. Позиция взятия мяча каждый раз слишком точна, так что не пишется захват-исключение.
Другие технические моменты
- Используйте electronic-vue для создания приложений, взаимодействующих напрямую с сокетами, и предоставляйте сокеты для получения текущего образа.
- Используя koa + vue, создайте веб-интерфейс, который вручную анализирует текущее изображение. opencv находится на этом сервере.
- Анализ изображения, возьмем алгоритм максимально непрерывной классификации:findTopXY.js#L24~L56Используя метод массива, элементы текущей строки просто сортируются.
- Electron-vue будет обновляться каждый раз при отладке, что легко может привести к зависанию adb из-за многократного запуска бинарных файлов Android, поэтому часть логики размещается на внешнем сервере. Сокеты используются для взаимодействия между серверами. используется здесь
new Promise(r=>{cachedArray.push(r)}).then(...)
Вариант использует обещания и продолжает выполнять логику кода после того, как сокет возвращает данные. Реализация заключается в том, чтобы сначала зарядить, а затем вернуть результат обработки через n миллисекунд, а затем определить время отказов.
TODO
- [ ] Приведите в порядок сервер, используя этот хелпер полностью из коробки.
Передача данных, содержащих содержимое буфера, переведена в режим плоского буфера.- [x] Простая демонстрация завершена:testFlatBuffer
Недостатки и выводы
Это вспомогательное приложение дополняется постоянным накоплением навыков, которые я знаю, что больше, чем в демоверсии.
Этот инструмент работает полностью из коробки:электронная часть,часть opencv.
недостаточный
- Центральное положение сбивается, коррекция не производится.
- Webpack не хватает мастерства, горячее развертывание koa не настроено
- Использование обработки изображений для получения позиций вершин, кажется, занимает больше времени, чем создание снимка экрана вершины каждой насыпи с использованием метода templateMatch.
- Отсутствие рутинной организации кода, читаемость кода требует улучшения.
Суммировать
Знаком с использованием операций с сокетами и буферами, знаком с базовым использованием opencv и использованием директив vue. Пробовал использовать питон.
наконец.
Эффект в реальном времени, предыдущее видео автоматического хамелеона скорости без opencv:
youtu.be/7YSpqiYZJ0w