D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Предисловие
Пока Arduino для переноса на него эмуляции движения курсора доставляется, хочется разобрать еще одну интересную функцию, а именно: визуальная отрисовка объектов на экране, то есть боксов, FOV, FPS, с которой работает нейросеть.Для отрисовки будет использована библиотека Pygame. Как следует из названия, обычно её применяют для создания игр на Python, но игры на Python — в принципе плохая идея, потому что, как минимум, в таких играх сложно реализовать сложные механики, или, по крайней мере, их будет сложнее реализовать, чем на игровом движке. Также игры на Python будут гораздо более ресурсоемкими, чем на тех же движках, как GameMaker или Unity. В связи с этим функция отрисовки на экране также будет ресурсоемкой, и поэтому будет возможность её отключения.
Дополнительно было решено добавить ещё один режим наводки и антиотдачи специально для пистолетов и неавтоматического оружия.
Отрисовка боксов
Подготовка проекта
Перед началом разработки функций отрисовки объектов потребуется подготовить проект. Первое, что будет сделано — это добавление RGB-значений в конфиг для определения цвета отрисованного бокса, а также возможность изменения этого цвета через веб-интерфейс.Вот как выглядит ключ с цветом в конфиге:
JavaScript: Скопировать в буфер обмена
Код:
"cs2_box_color": {
"r": 27,
"g": 177,
"b": 22
}
Теперь нужно в веб-интерфейсе (HTML-файле) добавить объект, через который будет меняться цвет. Этот объект будет input типа color.
HTML: Скопировать в буфер обмена
Код:
<label for="cs2_box_color">CS2 Box Color:</label>
<input type="color" id="cs2_box_color" name="cs2_box_color"><br>
Конвертация форматов rgb и hex
Функция для конвертации из RGB в HEX и для конвертации HEX в RGB. Нужно это, потому что объект принимает данные в формате HEX, а в конфиге они в формате RGB. Поэтому при получении данных в формате RGB из конфига, данные будут конвертироваться в HEX и записываться в объект, а при обновлении конфига данные из объекта будут конвертироваться в RGB и записываться в конфиг.Функция конвертации RGB в HEX:
JavaScript: Скопировать в буфер обмена
Код:
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
}
Пошаговое объяснение:
Шестнадцатеричное представление цвета:Цвет в формате RGB задается тремя компонентами: красный (r), зеленый (g) и синий (b). Диапазон значений каждого цвета начинается от 0 и заканчивается 255.
В формате HEX цвет представляется как строка из шести символов, где каждый компонент RGB кодируется двумя символами: #RRGGBB.
Сдвиг битов и сложение:
1 << 24 — это операция сдвига влево на 24 бита. Результат этого будет 0x1000000, что соответствует 24 битам для использования в шестнадцатеричном формате цвета. Это значение используется для того, чтобы результат всегда имел 6 цифр, даже если RGB имеет значения, которые могут привести к более короткому значению.
(r << 16) — это сдвиг значения красного компонента на 16 битов влево. Это перемещает значение r в старшие 8 битов из 24 битов для цвета.
(g << 8) — это сдвиг значения зеленого компонента на 8 битов влево. Это перемещает значение g в средние 8 битов из 24 битов для цвета.
b — значение синего компонента помещается в младшие 8 битов.
Все эти значения складываются: (1 << 24) + (r << 16) + (g << 8) + b.
Преобразование в строку:
.toString(16) преобразует результат в строку в шестнадцатеричном формате.
.slice(1) удаляет первый символ строки, который является 1 из-за операции 1 << 24. Это делает строку длиной 6 символов, представляющую код цвета без ненужного префикса.
.toUpperCase() переводит все буквы в строке в верхний регистр для соответствия стандартному формату HEX, в котором буквы, как правило, находятся в верхнем регистре.
Теперь можно рассмотреть функцию конвертации из HEX в RGB:
JavaScript: Скопировать в буфер обмена
Код:
function hexToRgb(hex) {
const bigint = parseInt(hex.slice(1), 16);
return {
r: (bigint >> 16) & 255,
g: (bigint >> 8) & 255,
b: bigint & 255
};
}
Дополнение функций принятия и отправки данных конфига
После написания функций конвертации цвета из одного формата в другой нужно дополнить функцию load_config для получения значений из конфига.JavaScript: Скопировать в буфер обмена
Код:
const cs2_boxColor = rgbToHex(config.cs2_box_color.r, config.cs2_box_color.g, config.cs2_box_color.b);
document.getElementById('cs2_box_color').value = cs2_boxColor;
Теперь нужно дополнить функцию отправки конфига под названием update_config.
JavaScript: Скопировать в буфер обмена
cs2_box_color: hexToRgb(document.getElementById('cs2_box_color').value),
В данном коде берется значение из объекта на странице, конвертируется в RGB формат, используя ранее написанную функцию, и полученные значения записываются в ключ cs2_box_color.
После этого нужно дополнить функцию update_config на Python, которая принимает данные с веб-страницы и записывает эти значения в конфигурационный файл.
Python: Скопировать в буфер обмена
Код:
config_data['cs2_box_color'] = {
'r': int(request.json['cs2_box_color']['r']),
'g': int(request.json['cs2_box_color']['g']),
'b': int(request.json['cs2_box_color']['b'])
}
Теперь можно приступить к написанию самой логики отрисовки боксов на экране.
Как будет работать отрисовка боксов
Будет создаваться прозрачное окно размерами на весь экран и всегда поверх других окон, и отрисовка будет происходить именно на нем, а не в игре, то есть отрисовка не будет вмешиваться в код игры, как это делают читы.Когда с примерной работой отрисовки все понятно, можно начинать писать сам код. Для начала нужно создать отдельный файл для всей логики отрисовки; в нем будет инициализация конфига, Pygame и настройка окна Pygame, а также сама функция отрисовки.
Варианты реализации
Есть несколько вариантов, как реализовать инициализацию Pygame и настройку окна. Их можно сделать вообще вне функций, но в таком случае, если для других копий нейронной сети сделать такой же код, то при запуске программы весь код будет инициализироваться повторно для каждой игры. Также можно реализовать через функции; таким образом, уже решается проблема с повторной инициализацией во всех копиях нейронной сети.Написание функции отрисовки боксов
Когда новый Python файл создан, в него сразу нужно вписать функцию инициализации конфига, как и в других файлах.Python: Скопировать в буфер обмена
Код:
def load_config(key=None, max_retries=3, delay=1):
attempt = 0
while attempt < max_retries:
try:
with open("config.json", 'r') as file:
config = json.load(file)
if key:
return config.get(key)
return config
except (FileNotFoundError, json.JSONDecodeError) as e:
attempt += 1
if attempt >= max_retries:
raise RuntimeError(f"Не удалось загрузить конфигурацию после {max_retries} попыток. Ошибка: {e}")
else:
print(f"Ошибка при загрузке конфигурации: {e}. Попытка {attempt} из {max_retries}.")
time.sleep(delay) # Задержка перед повторной попыткой
Создание окна для отрисовки
Затем нужно создать функцию, в которой будет инициализироваться Pygame и сразу же настраиваться окно. Функция будет называться pygame_initialization. Далее, в самой функции нужно создать объект инициализации Pygame.Python: Скопировать в буфер обмена
Код:
def pygame_initialization():
pygame.init()
Далее нужно создать окно, в котором как раз будут отрисовываться графические элементы по типу FPS, FOV и боксов противников.
Python: Скопировать в буфер обмена
screen = pygame.display.set_mode((load_config("cs2_screen_width"), load_config("cs2_screen_height")), pygame.HWSURFACE)
cs2_screen_width и cs2_screen_height отвечают за размер окна (равный размеру экрана). pygame.HWSURFACE — флаг для использования аппаратного ускорения (видеокарты).
Затем нужно получить дескриптор созданного окна.
Python: Скопировать в буфер обмена
hwnd = pygame.display.get_wm_info()["window"]
pygame.display.get_wm_info() получает информацию об окне; затем в полученной информации находит ключ window, в котором хранится дескриптор окна. Таким образом, hwnd = дескриптор окна.
Далее к свойствам окна нужно добавить стиль для того, чтобы в дальнейшем сделать окно прозрачным.
Python: Скопировать в буфер обмена
Код:
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)
Далее нужно поработать с добавленным стилем, чтобы сделать окно прозрачным.
Python: Скопировать в буфер обмена
win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(0, 0, 0), 0, win32con.LWA_COLORKEY)
win32gui.SetLayeredWindowAttributes — это функция, которая позволяет работать с окном, у которого есть стиль WS_EX_LAYERED (этот стиль был добавлен ранее), для изменения прозрачности окна и других визуальных эффектов. hwnd — это дескриптор окна, win32api.RGB(*(0, 0, 0)) — это цвет окна, в данном случае черный, win32con.LWA_COLORKEY делает прозрачным цвет, указанный в win32api.RGB. Таким образом, все черные пиксели, отрисованные в окне, станут прозрачными.
Теперь нужно сделать так, чтобы окно было поверх всех окон.
Python: Скопировать в буфер обмена
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
win32con.HWND_TOPMOST — флаг, указывающий, что окно должно быть поверх других окон. Параметры 0, 0, 0, 0 указывают, что положение и размер окна по умолчанию такие, как при создании окна. win32con.SWP_NOMOVE указывает, что положение окна не должно меняться, а win32con.SWP_NOSIZE — что размер окна не должен меняться.
Отрисовка
Далее нужно написать функцию отрисовки боксов, которая будет называться draw_box. Эта функция должна принимать один параметр, а если точнее, в дальнейшем она будет вызываться в функции детекции, и в нее будут передаваться координаты бокса объекта, то есть x1, y1 и x2, y2, о которых было рассказано в предыдущей теме. То есть, раз функция принимает данные бокса, нужно как-то их обработать, и для начала потребуется каждую из координат назначить в переменную.Python: Скопировать в буфер обмена
Код:
def draw_box(box_cord):
x1, y1, x2, y2 = map(int, box_cord)
Далее нужно вызвать функцию рисования pygame.draw.rect, и в ней первым делом указать окно, на котором нужно рисовать (оно находится в переменной screen). Затем нужно указать цвет, который нужно брать из конфига.
Python: Скопировать в буфер обмена
Код:
pygame.draw.rect(
screen,
(load_config("cs2_box_color")['r'], load_config("cs2_box_color")['g'], load_config("cs2_box_color")['b']),
Теперь нужно указать координаты, по которым нужно нарисовать прямоугольник.
Python: Скопировать в буфер обмена
Код:
[
x1 + load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2,
y1 + load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2,
x2 - x1,
y2 - y1
],
1
Вначале берется начальная координата x1, затем к ней добавляется половина ширины экрана (то есть центр экрана по x) и вычитается половина ширины FOV, чтобы сместить координату относительно центра экрана и FOV. Там, где y1 плюсуется, происходят аналогичные действия. x2 - x1 и y2 - y1 нужны для того, чтобы рассчитать ширину и высоту бокса. Единица в конце указывает на толщину линии, которой будет нарисован бокс.
Теперь с файлом детекции закончено на данный момент, и можно приступать к инициализации и запуску функции отрисовки. Для этого нужно перейти в Python файл с основной логикой нейронной сети и обратиться к функции screenshot; в ней есть бесконечный цикл, в начале этого цикла нужно указать обработку событий с окном Pygame.
Python: Скопировать в буфер обмена
pygame.event.pump()
Это нужно для того, чтобы обрабатывать все события, происходящие с окном, то есть клик по окну, закрытие, сворачивание и т.д. Если этого не сделать, при клике на окно оно просто зависнет.
Далее, в той же функции, но выше цикла, нужно вызвать функцию инициализации Pygame и настройки окна.
Python: Скопировать в буфер обмена
pygame_initialization()
Теперь нужно перейти к функции детекции и найти в ней эту строку:
Python: Скопировать в буфер обмена
if len(results.boxes) > 0:
Над этой строкой нужно вызвать переменную screen, которая хранит в себе данные об окне Pygame, и указать в ней закрашивание всего экрана черным. Если не забыли, то черный в данном случае является прозрачным. Это нужно для того, чтобы после детекции объекта рисовался бокс, и при следующем вызове функции детекции нарисованный ранее бокс стирался, а не оставался на экране.
Python: Скопировать в буфер обмена
screen.fill((0, 0, 0))
Но на этом моменте возникает проблема: эта переменная не глобальная, и для начала ее нужно сделать глобальной. Для этого нужно вернуться в файл с отрисовкой боксов и перейти в функцию pygame_initialization(). В ней нужно указать эту переменную как глобальную:
Python: Скопировать в буфер обмена
global screen
Теперь вне функции нужно назначить этой переменной изначальное значение при запуске программы. None не подойдет, поэтому будет указано то же самое, что и в функции pygame_initialization(), но с нулевыми значениями.
Python: Скопировать в буфер обмена
screen = pygame.display.set_mode((0, 0), pygame.HWSURFACE)
Теперь в главном файле нейросети в функции detection данная переменная успешно используется, и теперь нужно внутри этой функции вызывать функцию отрисовки боксов, передавая туда координаты объекта.
Для этого нужно найти строку:
Python: Скопировать в буфер обмена
if box.cls[0].item() in load_config("cs2_obj_detection"):
И в самом низу вызвать функцию отрисовки.
Python: Скопировать в буфер обмена
draw_box(box_cord)
После этого нужно вызвать функцию Pygame для обновления окна.
Python: Скопировать в буфер обмена
pygame.display.update()
После этого уже можно запустить нейросеть и увидеть результат:
Функция отрисовки будет более полезна для дебага и понимания, на каком расстоянии хорошо или плохо детектит, а также чтобы вычислить, на какие случайные объекты детектит, чтобы в новой обученной модели это исправить. Например, был найден такой забавный момент, где курицу детектит как террориста:
Возможность отключать отрисовку
Также, как и упоминалось ранее, данная функция очень ресурсоемкая, и из-за ее скорости обработки скорость работы всей нейросети достаточно сильно падает. Конечно, в таком случае софт все равно дает прирост к вашему аиму, но если хочется максимального профита, то лучше данную функцию отключать. Для этого нужно в конфиге создать новый ключ для включения и отключения отрисовки боксов.Python: Скопировать в буфер обмена
"cs2_draw_box_valid": true
Далее нужно добавить на сайте возможность смены этого параметра. Для этого нужно перейти в HTML файл и добавить селектор объекта с выбором true или false.
HTML: Скопировать в буфер обмена
Код:
<select id="cs2_draw_box_valid" name="cs2_draw_box_valid">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
Затем в функции загрузки конфига с Python сервера нужно добавить назначение значения из конфига в данный селектор.
JavaScript: Скопировать в буфер обмена
document.getElementById('cs2_draw_box_valid').value = config.cs2_draw_box_valid || '';
После этого нужно дополнить функцию передачи данных с сайта в конфиг, добавив строку сбора данных из объекта в ключ внутри JSON, который будет отправлен на Python-часть, и уже Python-часть добавит это значение в конфиг.
JavaScript: Скопировать в буфер обмена
cs2_draw_box_valid: document.getElementById('cs2_draw_box_valid').value === 'true',
=== 'true' проверяет значение из объекта на то, является ли оно true. Если является, то записывается true; если не является, то false. Это сделано потому, что значение в селекторе по умолчанию текстовое, а данная проверка в любом случае переведет значение в тип булевый. Проще говоря, этот механизм по сути превращает строку 'true' в булевое true, а всё остальное (включая 'false') — в булевое false. Это делается автоматически благодаря сравнению === 'true'.
С веб-частью закончено, и теперь нужно дополнить файл, где инициализируется Flask и обновляются значения конфига. Нужно перейти к функции update_config и вписать в нее принятие значения ключа из запроса с веб-части и запись этого значения в ключ в конфиг файле.
Python: Скопировать в буфер обмена
config_data['cs2_draw_box_valid'] = bool(request.json['cs2_draw_box_valid'])
На этом с отрисовкой боксов закончено. Так как функция отрисовки вызывается внутри цикла, при каждом цикле берется свежее значение из конфига, соответственно его можно менять без перезапуска нейросети.
Функция отрисовки FOV
Подготовка проекта
Теперь можно сделать отрисовку зоны FOV, но перед этим нужно сначала добавить ключи в конфиг, а если точнее, ключ с цветом FOV и ключ для отключения и включения данной функции отрисовки.JavaScript: Скопировать в буфер обмена
Код:
"cs2_draw_fov_valid": true,
"cs2_fov_color": {
"r": 255,
"g": 187,
"b": 0
}
Затем нужно, так же как и с настройкой отрисовки боксов, зайти в HTML файл и добавить 2 объекта: объект для включения и выключения функции и объект выбора цвета.
HTML: Скопировать в буфер обмена
Код:
<select id="cs2_draw_fov_valid" name="cs2_draw_fov_valid">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
<input type="color" id="cs2_fov_color" name="cs2_fov_color"><br>
В функции, где принимаются данные из конфига, нужно добавить назначение данных из конфига в объекты на странице:
JavaScript: Скопировать в буфер обмена
Код:
document.getElementById('cs2_draw_fov_valid').value = config.cs2_draw_fov_valid || '';
const cs2_fovColor = rgbToHex(config.cs2_fov_color.r, config.cs2_fov_color.g, config.cs2_fov_color.b);
document.getElementById('cs2_fov_color').value = cs2_fovColor;
Далее также идентичный код, но в функции для отправки данных из полей в конфиг.
JavaScript: Скопировать в буфер обмена
Код:
cs2_draw_fov_valid: document.getElementById('cs2_draw_fov_valid').value === 'true',
cs2_fov_color: hexToRgb(document.getElementById('cs2_fov_color').value),
После этого нужно перейти в Python файл, в котором происходит инициализация Flask, и перейти в функцию update_config. Затем внутри этой функции нужно принимать запрос от веб-части, брать оттуда ключи с параметрами и назначать эти параметры в ключи в конфиг файле.
Python: Скопировать в буфер обмена
Код:
config_data['cs2_draw_fov_valid'] = bool(request.json['cs2_draw_fov_valid'])
config_data['cs2_fov_color'] = {
'r': int(request.json['cs2_fov_color']['r']),
'g': int(request.json['cs2_fov_color']['g']),
'b': int(request.json['cs2_fov_color']['b'])
}
Отрисовка fov
С подготовкой интерфейса закончено, теперь нужно перейти в Python файл, где находится функция отрисовки боксов, и добавить новую функцию с названием draw_fov. Внутри функции нужно вызвать pygame.draw.rect для рисования и указать окно, в котором будет это происходить, цвет, которым будет отрисовываться FOV, и затем определить левый верхний угол FOV по x и y, а также указать ширину и высоту FOV.Python: Скопировать в буфер обмена
Код:
def draw_fov():
pygame.draw.rect(
screen,
(load_config("cs2_fov_color")['r'], load_config("cs2_fov_color")['g'], load_config("cs2_fov_color")['b']),
[load_config("cs2_screen_width") // 2 - load_config("cs2_fov_width") // 2,
load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2,
load_config("cs2_fov_width"), load_config("cs2_fov_height")],
1)
Точка FOV по y: load_config("cs2_screen_height") // 2 - load_config("cs2_fov_height") // 2
Вместе это получается верхний левый угол FOV. Ширина и высота FOV нужны для понимания, каких размеров рисовать объект, то есть рисовать начнет из левого верхнего угла размерами, которые указаны в cs2_fov_width и cs2_fov_height.
Функция отрисовки готова, и теперь нужно ее вызывать.
Варианты вызова функции
У меня было несколько вариантов того, как её вызывать.В отдельном потоке и внутри функции сделать бесконечный цикл, или вызывать её там же, где и отрисовку боксов, и без цикла в функции детекции. Так как в отдельном потоке должно было меньше нагружать систему и не увеличивать скорость работы функции детекции. Но в таком случае строка из функции детекции, которая очищает экран после каждой итерации цикла, ломала отрисовку FOV, и он начинал моргать на экране. Происходило это из-за того, что оба эти действия работали, так сказать, не в такт друг с другом, и после этого остался только второй вариант.
Сделать вызов функции внутри функции detection и не делать саму функцию отрисовки в потоке. В таком случае очистка экрана и вызов функции отрисовки вызываются по порядку, и всё отрисовывается корректно.
В итоге, так как выбран второй вариант, нужно соответственно перейти в функцию детекции в основном файле нейросети и в самом низу, там же где вызывается pygame.display.update(), добавить над ней if для проверки на то, указано ли в конфиге true в ключе cs2_draw_fov_valid. Внутри if нужно вызвать функцию отрисовки FOV.
Python: Скопировать в буфер обмена
Код:
if load_config("cs2_draw_fov_valid"):
draw_fov()
pygame.display.update()
Функция отрисовки FPS
Подготовка проекта
Теперь можно написать последнюю функцию, связанную с отрисовкой — это отрисовка FPS нейронной сети. Первое, что будет сделано, это, как и с предыдущими функциями, подготовка конфига и интерфейса для настройки. Останавливаться долго на этом не буду, так как там опять всё делается точно так же.Новые ключи в конфиге:
JavaScript: Скопировать в буфер обмена
Код:
"cs2_draw_fps_valid": true,
"cs2_fps_color": {
"r": 140,
"g": 251,
"b": 4
}
Далее нужно перейти в HTML и добавить объект slider для включения и выключения, а также объект input типа color.
HTML: Скопировать в буфер обмена
Код:
<select id="cs2_draw_fps_valid" name="cs2_draw_fps_valid">
<option value="true">TRUE</option>
<option value="false">FALSE</option>
</select><br>
<input type="color" id="cs2_fps_color" name="cs2_fps_color"><br>
Теперь в том же HTML дополняем функцию load_config для получения данных из конфига и записи в объект.
JavaScript: Скопировать в буфер обмена
Код:
document.getElementById('cs2_draw_fps_valid').value = config.cs2_draw_fps_valid || '';
// Загружаем значение цвета в HEX формате
const cs2_fpsColor = rgbToHex(config.cs2_fps_color.r, config.cs2_fps_color.g, config.cs2_fps_color.b);
document.getElementById('cs2_fps_color').value = cs2_fpsColor;
После этого нужно дополнить функцию обновления конфига, взяв значения из объектов.
JavaScript: Скопировать в буфер обмена
Код:
cs2_draw_fps_valid: document.getElementById('cs2_draw_fps_valid').value === 'true',
// Преобразуем цвет обратно в RGB формат
cs2_fps_color: hexToRgb(document.getElementById('cs2_fps_color').value),
Далее нужно перейти в файл инициализации Flask и дополнить функцию update_config.
Python: Скопировать в буфер обмена
Код:
config_data['cs2_draw_fps_valid'] = bool(request.json['cs2_draw_fps_valid'])
config_data['cs2_fps_color'] = {
'r': int(request.json['cs2_fps_color']['r']),
'g': int(request.json['cs2_fps_color']['g']),
'b': int(request.json['cs2_fps_color']['b'])
}
Функция отрисовки FPS
После этого можно приступать к написанию самой функции отрисовки. Называться функция будет draw_fps и принимать она будет один аргумент — это количество кадров в секунду.Python: Скопировать в буфер обмена
def draw_fps(fps):
Далее нужно записать в переменную шрифт для отрисовки и его размер.
Python: Скопировать в буфер обмена
font = pygame.font.Font('freesansbold.ttf', 32)
Затем нужно настроить рендеринг текста, который будет отрисовываться. В его настройках будет 4 параметра: текст для отрисовки, включить или отключить сглаживание текста, цвет текста (будет браться из конфига) и задний фон (по умолчанию черный, то есть прозрачный).
Python: Скопировать в буфер обмена
Код:
font_text = font.render(
'FPS: ' + fps,
True,
(load_config("cs2_fps_color")['r'], load_config("cs2_fps_color")['g'], load_config("cs2_fps_color")['b']),
(0, 0, 0))
Далее будет создан прямоугольник вокруг текста, чтобы в дальнейшем было легко изменять положение текста на экране.
Python: Скопировать в буфер обмена
text_rect = font_text.get_rect()
Затем нужна строка для переноса прямоугольника с текстом на конкретные координаты экрана. Не нужно забывать, что нулевые координаты начинаются с левого верхнего угла. Также в указанных координатах будет находиться именно центр прямоугольника, а не его верхний левый угол.
Python: Скопировать в буфер обмена
text_rect.center = (75, 25)
Теперь нужно отрисовывать сам текст в окне, данные которого занесены в переменную screen.
Python: Скопировать в буфер обмена
screen.blit(font_text, text_rect)
С функцией отрисовки закончено, теперь нужно написать логику расчета количества FPS в секунду и вызвать функцию отрисовки, передавая в неё данные. Для этого нужно перейти в Python файл с основной логикой нейронной сети в функцию детекции и в самом её начале создать переменную, в которую запишется текущее количество тиков
Python: Скопировать в буфер обмена
start_timer = cv2.getTickCount()
Далее нужно в конце функции получить количество тиков на данный момент, то есть после всей логики функции. То есть нужно найти этот участок кода:
Python: Скопировать в буфер обмена
Код:
if load_config("cs2_draw_fov_valid"):
draw_fov()
И под этим кодом вписать эту строку:
Python: Скопировать в буфер обмена
fps = cv2.getTickFrequency() / (cv2.getTickCount() - start_timer)
Сначала из cv2.getTickCount() (текущее значение тиков) вычитается значение тиков при старте таймера (start_timer), затем количество тиков в секунду (cv2.getTickFrequency()) делится на полученное значение. Функция cv2.getTickFrequency() — это количество тактов процессора, то есть количество тиков, происходящих за одну секунду, и оно зависит от частоты процессора.
Способы подсчета FPS
Можно было использовать обычный таймер, а не cv2. Мной было опробовано оба варианта реализации подсчета, но по моим наблюдениям обычный таймер нагружал систему чуть сильнее, поэтому был выбран метод с cv2. Скорее всего, метод с использованием обычного таймера будет проще в понимании для новичков, и разница в нагрузке не критична, так что вариант с обычным таймером также можно использовать без особой разницы, если вам так будет удобнее.Теперь, под последней написанной строкой, нужно вызывать функцию отрисовки, но делать это нужно лишь проверив в конфиге флаг включения и отключения.
Python: Скопировать в буфер обмена
Код:
if load_config("cs2_draw_fps_valid"):
draw_fps(str(int(fps)))
На этом со всеми функциями отрисовки закончено, и было решено также заодно добавить новый режим наводки с антиотдачей, а именно — для пистолетов.
Новая механика наводки и антиотдачи
Зачем нужен новый режим наводки и антиотдачи
Так как обычный режим наводки работает, когда зажата левая кнопка мыши, этот режим не подойдет для пистолетов, так как на пистолетах нужно кликать левой кнопкой мыши, а не зажимать. Кроме того, при зажатии ЛКМ срабатывают макросы для винтовок, а это крайне не подходит к пистолетам. Поэтому и было принято решение сделать дополнительный режим наводки с дополнительной антиотдачей.Подготовка проекта
И первое, что нужно сделать, — это снова добавить новые ключи в конфиг для большей настраиваемости софта.JavaScript: Скопировать в буфер обмена
Код:
"cs2_aim_pistol_step": 1,
"cs2_aim_pistol_time_sleep": 0.001,
"cs2_anti_recoil_pistol_px": 2,
"cs2_anti_recoil_pistol_time_sleep": 0.25,
После того как ключи были добавлены, нужно, как и раньше, добавить на веб-странице объекты.
HTML: Скопировать в буфер обмена
Код:
<label for="cs2_aim_target">CS2 Aim Pistol Step:</label>
<input type="number" step="1" id="cs2_aim_pistol_step" name="cs2_aim_pistol_step"><br>
<label for="cs2_aim_target">CS2 Aim Pistol Time Sleep:</label>
<input type="number" step="0.001" id="cs2_aim_pistol_time_sleep" name="cs2_aim_pistol_time_sleep"><br>
<label for="cs2_aim_target">CS2 Anti Recoil Pistol PX:</label>
<input type="number" step="1" id="cs2_anti_recoil_pistol_px" name="cs2_anti_recoil_pistol_px"><br>
<label for="cs2_aim_target">CS2 Anti Recoil Pistol Time Sleep:</label>
<input type="number" step="0.1" id="cs2_anti_recoil_pistol_time_sleep" name="cs2_anti_recoil_pistol_time_sleep"><br>
Затем нужно дополнить на веб-странице функцию загрузки данных из конфига:
JavaScript: Скопировать в буфер обмена
Код:
document.getElementById('cs2_aim_pistol_step').value = config.cs2_aim_pistol_step || '';
document.getElementById('cs2_aim_pistol_time_sleep').value = config.cs2_aim_pistol_time_sleep || '';
document.getElementById('cs2_anti_recoil_pistol_px').value = config.cs2_anti_recoil_pistol_px || '';
document.getElementById('cs2_anti_recoil_pistol_time_sleep').value = config.cs2_anti_recoil_pistol_time_sleep || '';
Также нужно дополнить функцию отправки данных со страницы в конфиг:
JavaScript: Скопировать в буфер обмена
Код:
cs2_aim_pistol_step: document.getElementById('cs2_aim_pistol_step').value,
cs2_aim_pistol_time_sleep: document.getElementById('cs2_aim_pistol_time_sleep').value,
cs2_anti_recoil_pistol_px: document.getElementById('cs2_anti_recoil_pistol_px').value,
cs2_anti_recoil_pistol_time_sleep: document.getElementById('cs2_anti_recoil_pistol_time_sleep').value,
После этого нужно в Python файле, где происходит инициализация Flask и, в принципе, логика взаимодействия с веб-страницей, дополнить функцию update_config:
Python: Скопировать в буфер обмена
Код:
config_data['cs2_aim_pistol_step'] = int(request.json['cs2_aim_pistol_step'])
config_data['cs2_aim_pistol_time_sleep'] = float(request.json['cs2_aim_pistol_time_sleep'])
config_data['cs2_anti_recoil_pistol_px'] = int(request.json['cs2_anti_recoil_pistol_px'])
config_data['cs2_anti_recoil_pistol_time_sleep'] = float(request.json['cs2_anti_recoil_pistol_time_sleep'])
Функция наводки
Первым делом нужно создать функцию aim_pistol, которая будет принимать координаты до объекта dx и dy. Затем нужно добавить проверку if на нажатие боковой кнопки мыши.На какую клавишу включать функции?
Дело в том, что если сделать проверку на нажатие левой кнопки мыши, то внутри if в любом случае потребуется делать эмуляцию нажатия ЛКМ и отпускания. Из-за того что вы будете нажимать левую кнопку мыши, а нейронка будет её отпускать, работать ничего не будет, так как нейронная сеть постоянно будет отменять ваше нажатие. Соответственно, придётся делать на боковую кнопку мыши, чтобы не было конфликтов.Python: Скопировать в буфер обмена
Код:
def aim_pistol(dx, dy):
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
Далее, внутри нужно назначить две переменные, в которых будут храниться данные, полученные от разделения координат до объекта, деленных на шаги из ключа cs2_aim_pistol_step в конфиге.
Python: Скопировать в буфер обмена
Код:
aim_step_x = dx / load_config("cs2_aim_pistol_step")
aim_step_y = dy / load_config("cs2_aim_pistol_step")
Далее нужно создать цикл for, который будет повторяться столько раз, сколько указано в ключе с шагами. Внутри цикла нужно выполнять эмуляцию мыши для перемещения на координаты, указанные в переменных, созданных выше.
Python: Скопировать в буфер обмена
Код:
for i in range(load_config("cs2_aim_pistol_step")):
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
time.sleep(load_config("cs2_aim_pistol_time_sleep"))
Функция анти отдачи
Пока что функция наводки пистолетом закончена, и можно начать писать функцию антиотдачи для пистолета. Называться функция будет anti_recoil_pistol. Внутри функции нужно сразу создать глобальную переменную, которая будет использоваться в дальнейшем.Python: Скопировать в буфер обмена
Код:
def anti_recoil_pistol():
global anti_recoil_pistol_y
Далее нужно создать бесконечный цикл while True, в котором нужно добавить проверку if на нажатие боковой кнопки мыши.
Python: Скопировать в буфер обмена
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
Внутри if нужно назначить ранее созданной глобальной переменной значение из ключа cs2_anti_recoil_pistol_px, а после этого установить паузу длиной в значение из ключа cs2_anti_recoil_pistol_time_sleep.
Python: Скопировать в буфер обмена
Код:
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
anti_recoil_pistol_y += load_config("cs2_anti_recoil_pistol_px")
"""Пауза"""
time.sleep(load_config("cs2_anti_recoil_pistol_time_sleep"))
Также нужно добавить else, если клавиша не нажата, и внутри него указать обнуление глобальной переменной.
Python: Скопировать в буфер обмена
Код:
else:
anti_recoil_pistol_y = 0
Теперь нужно создать эту переменную вне функции и назначить ей стандартное значение 0.
Python: Скопировать в буфер обмена
anti_recoil_pistol_y = 0
После этого нужно дополнить функцию наводки пистолетом, и внутри этой функции, но не внутри if, добавлять значение из глобальной переменной к переменной dy.
Python: Скопировать в буфер обмена
dy += anti_recoil_pistol_y
Для чего передавать значения из функции анти отдачи в функцию наводки?
Работа с глобальной переменной нужна точно для того же, что и глобальная переменная в обычной наводке для игры CS2, а если точнее — чтобы при стрельбе курсор наводился на объект относительно значения из переменной. Это нужно для того, чтобы наводилось не постоянно в голову, а каждый раз все ниже, так как при стрельбе в данной игре курсор всегда в центре, а пули летят выше. Благодаря таким действиям курсор будет стрелять ниже головы, но в итоге пули будут попадать именно в голову.Вызов функций
Теперь нужно вызывать функцию антиотдачи для пистолета и наводку. Функцию наводки нужно вызывать там же, где и обычную наводку, а именно — в функции отрисовкиPython: Скопировать в буфер обмена
aim_pistol(dx, dy)
Функция антиотдачи для пистолета будет вызываться в функции start_game в отдельном потоке, точно так же, как и обычная антиотдача. Именно для этого в функции антиотдачи бесконечный цикл, так как без него функция выполнилась бы один раз при запуске и всё.
Python: Скопировать в буфер обмена
Код:
anti_recoil_pistol_thread = threading.Thread(target=anti_recoil_pistol)
anti_recoil_pistol_thread.start()
Функция стрельбы на боковую кнопку мыши
Если вы внимательно читали код, то могли заметить, что в функции антиотдачи для пистолета и наводки нет выполнения нажатия левой кнопки мыши. То есть, на данный момент при нажатии на боковую кнопку мыши будет происходить только наводка и смещение курсора по антиотдаче. Дело в том, что стрельба будет вынесена в отдельную функцию.Почему стрельбу нужно вынести в отдельную функцию?
Из-за того что объекты могут пропадать прямо во время стрельбы, добавление нажатия левой кнопки мыши внутрь функции наводки пистолетом нежелательно, так как стрельба также прекратится, если объект потеряется. Кроме того, в функции наводки находится пауза, которая может меняться в зависимости от настроек. Соответственно, пауза между выстрелами также будет зависеть от этой функции и может не подходить под скорострельность пистолета.По той же причине с неподходящей паузой не получится нормально вставить нажатие левой кнопки в функцию антиотдачи, так как, допустим, в функции антиотдачи пауза составляет 0.25, и данная пауза слишком большая для паузы между выстрелами из пистолета.
Написание функции
Когда было выяснено, зачем нужна отдельная функция для стрельбы, можно приступать к её написанию. Эта функция крайне проста и коротка. В ней нужно создать бесконечный цикл while True и внутри этого цикла добавить проверку на нажатие боковой кнопки мыши. Внутри этой проверки нужно добавить нажатие левой кнопки, отжатие левой кнопки и нужную паузу.Python: Скопировать в буфер обмена
Код:
def shoot_pistol():
while True:
if 0 > win32api.GetKeyState(win32con.VK_XBUTTON2):
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
time.sleep(0.025)
Далее нужно вызывать эту функцию внутри функции start_game в отдельном цикле, чтобы она не блокировала основной поток.
Python: Скопировать в буфер обмена
Код:
shoot_pistol_thread = threading.Thread(target=shoot_pistol)
shoot_pistol_thread.start()
Вывод
На этом статья подходит к концу. В ней была реализована новая логика наводки с антиотдачей и отрисовка различных объектов на экране наподобие привычных читов. Статья получилась достаточно маленькой и простой. Следующая статья по данной нейронной сети выйдет, когда мне придёт Arduino для переноса логики эмуляции на него. Перенос на Arduino поможет обойти некоторые античиты, в которых блокируется эмуляция мыши, так что софт станет ещё менее заметным и более безопасным.Также прилагаю видеопрезентацию работы добавленных в этой статье функций: https://rutube.ru/video/062ddf063114057852e7a21d5db39ffc/
Статья в виде документа: https://docs.google.com/document/d/1DlaUk9l3wAcMsmcm3FwmeRLJVGM6ygj0Uu4DBAi9FGM/edit?usp=sharing
Сделано OverlordGameDev специально для форума XSS.IS