Build a Python Recoil‑Compensation Bot for PUBG Using Image Recognition
This guide explains how to create a non‑intrusive Python bot that automatically compensates weapon recoil in PUBG by capturing the screen, recognizing equipped gear with OpenCV and SSIM, and moving the mouse via pydirectinput based on weapon‑specific recoil tables.
Overview
The method achieves recoil compensation via image recognition without injecting code or reading game memory, making it undetectable by anti‑cheat systems.
Prerequisite Knowledge
Different guns in PUBG have distinct recoil patterns and fire rates; attaching accessories changes recoil.
The vertical (Y‑axis) recoil is deterministic, while horizontal (X‑axis) spread is random, so the script only moves the mouse vertically.
Implementation Principle
Use pynput to listen to keyboard and mouse events.
When the left mouse button is pressed, start moving the mouse; releasing the button stops movement.
Pressing Tab opens the inventory, triggering a screenshot for equipment detection.
Capture screen regions with pyautogui, process images with opencv, and compare them using the SSIM algorithm.
Control mouse movement with pydirectinput based on the identified weapon and its recoil data.
Detailed Steps
1. pynput Keyboard Listener
import pynput.keyboard as keyboard
def listen_keyboard():
listener = keyboard.Listener(on_press=on_pressed, on_release=on_released)
listener.start() pynputgenerates asynchronous events; long‑running handlers must be off‑loaded to avoid blocking.
2. Event Handling
def on_released(key):
try:
if key.char == '1':
c_equipment.switch = 1 # primary weapon slot 1
elif key.char == '2':
c_equipment.switch = 2 # primary weapon slot 2
elif key.char == '3':
c_equipment.switch = 3 # pistol
elif key.char == '4':
c_equipment.switch = 3 # melee (treated as no recoil)
elif key.char == '5':
c_equipment.switch = 3 # grenade (treated as no recoil)
except AttributeError:
if key.name == 'tab':
async_handle() # start equipment detection
elif key.name == 'num_lock':
toggle_program() # enable/disable script
elif key.name == 'shift':
c_constants.hold = False3. Screenshot Capture
pyautogui.screenshot(region=[x, y, w, h])The captured image is converted to grayscale and binarized for easier comparison.
4. Reference Images
Reference pictures for weapon names, stocks, grips, and muzzle attachments are stored under ./resource/guns/. They are used for template matching.
5. Cropping Equipment Area
# Load previously saved screenshot
screen = cv2.imread("./resource/shotcut/screen.bmp", 0)
# Crop the weapon‑1 name region (example coordinates)
screenWeapon1 = screen[0:40, 45:125]
# Compare with reference images to obtain the name
w1_name = compare_and_get_name(screenWeapon1, "./resource/guns/")6. Image Comparison with SSIM
def compare_and_get_name(screen_img, directory):
files = os.listdir(directory)
best_name = 'none'
best_score = 0
for file_name in files:
ref_img = cv2.imread(os.path.join(directory, file_name), 0)
score = calculate_ssim(np.asarray(screen_img), np.asarray(ref_img))
if score > best_score and score > 0.5:
best_score = score
best_name = file_name[:-4] # strip extension
return best_name
def calculate_ssim(img1, img2):
if img1.shape != img2.shape:
raise ValueError('Input images must have the same dimensions.')
if img1.ndim == 2:
return ssim(img1, img2)
elif img1.ndim == 3:
if img1.shape[2] == 3:
ssims = [ssim(img1[:, :, i], img2[:, :, i]) for i in range(3)]
return np.mean(ssims)
elif img1.shape[2] == 1:
return ssim(np.squeeze(img1), np.squeeze(img2))
else:
raise ValueError('Unsupported image dimensions.')When the similarity exceeds 0.5, the corresponding file name is returned as the detected equipment.
7. Mouse Movement Based on Recoil Data
def move_mouse():
cur_weapon = get_current_weapon()
if cur_weapon.name == 'none':
return
basic = cur_weapon.basic # list of vertical offsets per bullet
speed = cur_weapon.speed # fire interval in ms
start_time = round(time.perf_counter(), 3) * 1000
for i in range(cur_weapon.max_bullets):
if not can_fire():
break
hold_factor = cur_weapon.hold if c_constants.hold else 1.0
move_sum = int(round(basic[i] * cur_weapon.k * hold_factor, 2))
while move_sum > 0:
step = min(10, move_sum)
pydirectinput.move(xOffset=0, yOffset=step, relative=True)
move_sum -= step
elapsed = (round(time.perf_counter(), 3) * 1000) - start_time
if not can_fire() or elapsed > (i + 1) * speed + 10:
break
time.sleep(0.01)The inner loop splits a large vertical move into several ≤10‑pixel steps to avoid noticeable jitter.
8. Data Collection
Each weapon has a unique recoil pattern. Accurate data must be collected manually in the game’s training mode, recording the vertical offset required for every bullet. Example for the M762 weapon:
# fire interval (ms)
speed = 86
# vertical recoil offsets per bullet
recoil = [42, 36, 36, 36, 42, 43, 42, 43, 54, 55, 54, 55, 54, 55, 54, 55,
62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
62, 62, 62, 77, 78, 77, 78]Most Challenging Part
Collecting precise recoil tables for every weapon and attachment combination requires manual measurement in the training arena.
Repository
Full source code is hosted on Gitee:
https://gitee.com/lookoutthebush/PUBGPython Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
