Се­год­ня мы с тобой раз­работа­ем бота, спо­соб­ного при помощи машин­ного зре­ния ори­енти­ровать­ся в игро­вом прос­транс­тве. Для при­мера возь­мем Luanti и Minecraft, одна­ко общие прин­ципы и осно­ва, которую мы напишем, подой­дут для любой схо­жей игры.

Мы будем управлять ботом так же, как это дела­ет обыч­ный игрок: отправ­лять ему коман­ды нажатия кла­виш (WASD и дру­гих). Получать информа­цию от бота будем через встро­енную в игру фун­кцию сох­ранения скрин­шотов. Нап­ример, отпра­вили боту коман­ду нажать F12 — получи­ли сни­мок того, что он видит в дан­ный момент. Про­ана­лизи­рова­ли изоб­ражение, отпра­вили сле­дующую коман­ду — и так по кру­гу.

Важ­ный момент: у нас не будет цели делать в игре что‑то кон­крет­ное. Мы прос­то под­готовим осно­ву и заод­но изу­чим прин­ципы работы с машин­ным зре­нием.

Для рас­позна­вания объ­ектов мы будем исполь­зовать модели YOLO (You Only Look Once). Наша задача — обу­чить модель находить булыж­ник и дви­гать­ся по дорож­ке, вымощен­ной им.

Обу­чение будет про­ходить в два эта­па.

  • Пер­вый этап. Обу­чим модель YOLOv8n находить наш объ­ект на сним­ках. Эта модель смо­жет опре­делить, содер­жится ли на изоб­ражении иско­мый объ­ект, и выделить его с помощью огра­ничи­вающе­го пря­моуголь­ника (bounding box).
  • Вто­рой этап. Под­готовим обу­чающий матери­ал для вто­рой модели — YOLOv8-seg, которая смо­жет обво­дить объ­екты по кон­туру. Это даст нам воз­можность ана­лизи­ровать их раз­мер, фор­му и положе­ние. Это имен­но то, что нуж­но для точ­ной ори­ента­ции бота в прос­транс­тве.
 

Что нам понадобится

  1. Обыч­ный компь­ютер. Мы исполь­зуем лег­ковес­ные модели, с которы­ми спра­вит­ся даже сред­ний по мощ­ности ПК. Весь код был написан и про­тес­тирован на машине со сле­дующи­ми харак­терис­тиками:
    • про­цес­сор — вось­миядер­ный AMD Ryzen 7 5700U с кар­той Radeon;
    • опе­ратив­ная память — 8 Гбайт.
  2. Сво­бод­ное мес­то на дис­ке. Пот­ребу­ется око­ло 10 Гбайт:
    • 400–500 Мбайт для хра­нения сним­ков (зависит от их количес­тва);
    • ~400 Мбайт для ска­чива­ния пре­добу­чен­ных моделей;
    • ~8,6 Гбайт для уста­нов­ки биб­лиотек Python;
    • 38,8 Кбайт для нашего кода!
  3. Хо­роший фильм или даже сери­ал. Обу­чение модели занима­ет нес­коль­ко часов, так что сто­ит запас­тись тер­пени­ем и раз­вле­чени­ями.
  4. Опе­раци­онная сис­тема. Я исполь­зовал Ubuntu, но подой­дет любой дис­три­бутив Linux. Нам понадо­бят­ся нес­коль­ко допол­нитель­ных прог­рамм, которые, ско­рее все­го, уже есть в офи­циаль­ных репози­тори­ях тво­его дис­тра.

Ста­вим при­ложе­ния для работы с буфером обме­на:

sudo apt install xclip xsel

И для работы с окна­ми:

sudo apt install wmctrl xdotool

Тес­тировать я буду на игре Luanti (так­же извес­тна под ста­рым наз­вани­ем Minetest) — она поп­роще, чем Minecraft, и есть в стан­дар­тных репози­тори­ях. Одна­ко попут­но дам рекомен­дации для работы с Minecraft — отли­чия будут минималь­ными.

Мы вос­поль­зуем­ся сво­им набором тек­стур для игры, что­бы наш бот зарабо­тал на любом сер­вере. Ска­чать тек­стур­ки мож­но из репози­тория про­екта.

В качес­тве язы­ка возь­мем Python — как один из наибо­лее популяр­ных и име­ющий мно­жес­тво удоб­ных биб­лиотек.

Весь про­цесс мож­но раз­бить на три основных эта­па: под­готов­ка, обу­чение, исполь­зование.

 

Подготовка

У нас будет мно­го раз­ных фай­лов, поэто­му, что­бы ничего не потерять, соз­дадим каталог для про­екта.

Пос­коль­ку нам нуж­но будет ста­вить модули Python, сра­зу сде­лаем вир­туаль­ное окру­жение.

python -m venv minebot

Те­перь поп­робу­ем покоман­довать нашим ботом. Для это­го вос­поль­зуем­ся биб­лиоте­кой PyAutoGUI. С ее помощью мы можем отправ­лять окну в фокусе нажатие кла­виш, дви­жение мыши и нажатие ее кно­пок.

Ак­тивиру­ем наше вир­туаль­ное окру­жение:

source minebot/bin/activate

Ус­тановим биб­лиоте­ки.

pip install pyautogui pyperclip opencv-python numpy ultralytics

Ко­ман­ды управле­ния ботом я соб­рал в одном фай­ле.

luanti/controller.py
import pyautogui
import pyperclip
import time
import math
class LuantiController:
"""Класс для управления персонажем в Luanti с использованием библиотеки PyAutoGUI."""
def __init__(self, window_width, window_height):
"""Инициализирует контроллер, получает размеры экрана."""
self.screen_width = window_width
self.screen_height = window_height
self.fov = 72 # Угол обзора в градусах
def take_screenshot(self):
"""Делает скриншот (нажатие F12)."""
pyautogui.press('f12')
time.sleep(0.1)
def _move(self, key, duration):
"""Вспомогательная функция для перемещения."""
pyautogui.keyDown(key)
time.sleep(duration)
pyautogui.keyUp(key)
def move_forward(self, duration):
"""Перемещает персонажа вперед (нажатие W)."""
self._move('w', duration)
def move_backward(self, duration):
"""Перемещает персонажа назад (нажатие S)."""
self._move('s', duration)
def move_left(self, duration):
"""Перемещает персонажа влево (нажатие A)."""
self._move('a', duration)
def move_right(self, duration):
"""Перемещает персонажа вправо (нажатие D)."""
self._move('d', duration)
def jump(self):
"""Прыжок (нажатие пробела)."""
pyautogui.keyDown('space')
time.sleep(0.1)
pyautogui.keyUp('space')
def run(self, direction, duration):
"""Бег в указанном направлении."""
pyautogui.keyDown('e')
time.sleep(0.1)
if direction == 'forward':
self._move('w', duration)
elif direction == 'backward':
self._move('s', duration)
elif direction == 'left':
self._move('a', duration)
elif direction == 'right':
self._move('d', duration)
pyautogui.keyUp('e')
def crouch_and_move(self, direction, duration):
"""Перемещение при приседании."""
pyautogui.keyDown('shift')
time.sleep(0.1)
if direction == 'forward':
self._move('w', duration)
elif direction == 'backward':
self._move('s', duration)
elif direction == 'left':
self._move('a', duration)
elif direction == 'right':
self._move('d', duration)
pyautogui.keyUp('shift')
def open_inventory(self):
"""Открывает инвентарь (нажатие I)."""
pyautogui.press('i')
def close_inventory(self):
"""Закрывает инвентарь (нажатие Esc)."""
pyautogui.press('esc')
def change_camera(self, clicks):
"""Меняет вид камеры (нажатие C)."""
for _ in range(clicks):
pyautogui.press('c')
time.sleep(0.5)
def select_inventory_slot(self, slot_number):
"""Выбирает слот в инвентаре (нажатие клавиш 1–8)."""
if 1 <= slot_number <= 8:
pyautogui.press(str(slot_number))
else:
print("Недопустимый номер слота: " + str(slot_number))
def place_block(self):
"""Ставит блок (правый клик мыши)."""
pyautogui.click(button='right')
def mine_block(self):
"""Ломает блок (удержание левого клика мыши)."""
pyautogui.mouseDown(button='left')
time.sleep(2) # Удерживаем кнопку мыши 2 секунды
pyautogui.mouseUp(button='left')
def send_chat_message(self, message):
"""Отправляет сообщение в чат с поддержкой кириллицы."""
# Открываем чат
pyautogui.press('t')
time.sleep(0.1)
# Копируем текст в буфер обмена
pyperclip.copy(message)
# Вставляем текст из буфера обмена (Ctrl + V)
pyautogui.hotkey('ctrl', 'v')
# Отправляем сообщение
pyautogui.press('enter')
def _calculate_pixels_for_degrees(self, degrees):
"""Вычисляет количество пикселей для сдвига мыши, необходимое для имитации поворота на заданный угол."""
pixels_per_degree = self.screen_width / self.fov
return int(degrees * pixels_per_degree)
def turn(self, degrees_x, degrees_y):
"""Поворачивает персонажа на указанное количество градусов."""
pixel_offset_x = self._calculate_pixels_for_degrees(degrees_x)
pixel_offset_y = self._calculate_pixels_for_degrees(degrees_y)
pyautogui.moveRel(pixel_offset_x, pixel_offset_y, duration=0.0)
time.sleep(0.05)
def drop_item(self):
"""Выбрасывает предмет (нажатие Q)."""
pyautogui.press('q')
time.sleep(0.1)

Уп­равле­ние в Minecraft будет отли­чать­ся нес­коль­кими кла­виша­ми, код фай­ла мож­но взять в репози­тории про­екта.

Даль­ше — демонс­тра­цион­ный код, который пооче­ред­но отправ­ляет все коман­ды боту, что­бы про­верить, как он их выпол­няет.

Вот как это работа­ет.

Мы под­клю­чаем к нашему фай­лу коман­ды управле­ния ботом из пре­дыду­щего лис­тинга.

Важ­но: при под­клю­чении нуж­но ука­зывать раз­меры окна игры! Если раз­мер невер­ный, то бот будет неп­равиль­но рас­счи­тывать углы для поворо­та. Для упро­щения и сок­ращения кода в этом фай­ле они ука­заны вруч­ную, ты можешь их испра­вить на свои зна­чения. Далее мы будем опре­делять эти парамет­ры авто­мати­чес­ки. Сей­час же нам надо про­верить, что мы можем управлять пер­сонажем.

Зай­ди в игру и най­ди ров­ную повер­хность, запус­ти experiment_1.py в тер­минале и кли­кай по окну с игрой, что­бы оно получи­ло фокус.

Для Minecraft нуж­но будет заком­менти­ровать одну стро­ку и рас­коммен­тировать дру­гую — они помече­ны в коде.

experiment_1.py
import pyautogui
import time
import cv2
import os
import datetime
import re
# Для Luanti
from luanti.controller import LuantiController as Controller
# Для Minecraft
# from minecraft.controller import MinecraftController as Controller
# Лучше сделать размер окна игры такого размера
window_width, window_height = 640, 640
def run_bot_actions(controller):
"""Выполняет все действия бота."""
normal_move_time = 2
run_move_time = 1
methods = [
("устанавливает направление взгляда",
lambda: (
controller.turn(0, 90),
controller.turn(0, -25)
)
),
("идет вперед", lambda: controller.move_forward(normal_move_time)),
("идет назад", lambda: controller.move_backward(normal_move_time)),
("идет влево", lambda: controller.move_left(normal_move_time)),
("идет вправо", lambda: controller.move_right(normal_move_time)),
("прыгает", controller.jump),
("бежит вперед", lambda: controller.run("forward", run_move_time)),
("бежит назад", lambda: controller.run("backward", run_move_time)),
("бежит влево", lambda: controller.run("left", run_move_time)),
("бежит вправо", lambda: controller.run("right", run_move_time)),
("приседает и идет вперед", lambda: controller.crouch_and_move("forward", normal_move_time)),
("приседает и идет назад", lambda: controller.crouch_and_move("backward", normal_move_time)),
("приседает и идет влево", lambda: controller.crouch_and_move("left", normal_move_time)),
("приседает и идет вправо", lambda: controller.crouch_and_move("right", normal_move_time)),
("открывает инвентарь", controller.open_inventory),
("закрывает инвентарь", controller.close_inventory),
("меняет камеру", lambda: controller.change_camera(3)),
("выбирает слот 8 и ставит блок",
lambda: (
controller.select_inventory_slot(8),
controller.place_block()
)
),
("выбирает слот 5 и копает блок",
lambda: (
controller.select_inventory_slot(5),
controller.mine_block()
)
),
("выбирает слот 1 и кушает :)",
lambda: (
controller.select_inventory_slot(1),
time.sleep(0.2),
pyautogui.mouseDown(button='left'),
time.sleep(0.1),
pyautogui.mouseUp(button='left')
)
),
("поворачивается на 10 градусов вправо", lambda: controller.turn(10, 0)),
("поворачивается на 10 градусов влево", lambda: controller.turn(-10, 0)),
("поворачивается на 10 градусов влево",lambda: controller.turn(-10, 0)),
("поворачивается на 10 градусов вправо",lambda: controller.turn(10, 0)),
("Делает скриншот", controller.take_screenshot),
("пишет в чат", lambda: controller.send_chat_message("Тест пройден, мои поздравления!")),
]
for description, method in methods:
print(f"[БОТ] {description}")
if isinstance(method, tuple):
for action in method:
action()
else:
method()
time.sleep(0.2)
if __name__ == "__main__":
print("Начинаем тестирование всех методов...")
print("Пожалуйста, переключите фокус на окно с игрой...")
time.sleep(5)
if window_width is not None and window_height is not None:
controller = Controller(window_width, window_height)
run_bot_actions(controller)
print(f"[БОТ] работу закончил.")

Ес­ли бот зашеве­лил­ся, зна­чит, есть кон­троль над ним и мож­но дви­гать­ся даль­ше.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии