Guides
BlueStacks Macro Image Recognition and LDPlayer Loops
Build a BlueStacks macro with image recognition: capture a template in Asset Lab, match it on BlueStacks or LDPlayer, and loop until a state appears.
- Windows
- Mac
- Emulator
- Cloud device
- Phone
- Studio
- Python SDK
On this page
- Why a recorder cannot branch
- Before you start
- Capture a template in Asset Lab
- Pick the right call
- Narrow the search with search_region
- Tune the threshold
- Handle popups with find_any_object
- An LDPlayer macro loop that waits for a state
- A complete BlueStacks macro with image recognition
- Troubleshooting
- Template never matches
- Matches the wrong thing
- Works on one instance, fails on another
A BlueStacks or LDPlayer macro with image recognition looks at the screen before it acts. It taps a button because the button is there, closes a popup because the popup appeared, and stops when the “out of energy” message shows up. This guide builds one in Macro Automation Studio (MAS) on BlueStacks or LDPlayer, from the first template crop to a loop that waits for a state. It is for anyone who has outgrown the emulator’s built-in recorder.
Why a recorder cannot branch
The macro recorder in BlueStacks or LDPlayer stores taps and timings and plays them back. It never looks at the screen. It cannot tell that a popup covered the button, that the loading screen took longer today, or that the counter reached zero. Every recorded macro is a straight line.
A MAS macro is a Python script. Each mas.find_object call takes a screenshot and searches it for a template image with OpenCV template matching. When the best match reaches the threshold (threshold=0.8 by default) the call returns its centre. Otherwise it returns None. That single if is what lets a macro branch, wait, retry and stop.
Before you start
- MAS is installed and signed in with the trial or a plan. The trial starts with the free download; see Install MAS.
- BlueStacks or LDPlayer is running with ADB enabled and is added as a device. See the BlueStacks or LDPlayer guide.
- The emulator uses a fixed resolution you will keep. The guides use 540x960 in portrait at 240 DPI as the baseline.
- A Code-Based project is open in the Code Editor. See Getting started.
Capture a template in Asset Lab
- Bring the app to the screen with the button you want to find.
- In the Code Editor, open the Assets panel and click Open Asset Helper. Asset Lab opens with the live screen of your device.
- Crop a tight rectangle around the button and save it. The crop goes to your Image Library and gets a numeric ID.
- Repeat for every state the macro must recognise: the popup’s close button, the confirm button, the “done” message.
- In the Assets panel, click Copy ID on each image and declare them at the top of your script.
import mas
images = mas.images({
"play_button": 201,
"close_popup": 202,
"out_of_energy": 203,
})mas.images gives each ID a readable name and tells the packer which images to bundle when you publish. Templates can be jpg, png, gif or webp, up to 10 MB each. See Asset Lab.
Pick the right call
| Call | Use it when | Returns |
|---|---|---|
find_object(image) | You want one look at the screen right now | ObjectMatch or None |
find_object_retry(image, total_tries=3, time_sleep=2.0) | The thing may take a moment to appear | ObjectMatch or None after the last try |
find_any_object([a, b, c]) | Several templates are acceptable: popup variants, two themes | The first match, with matched_template_id |
find_objects(image, max_matches=10) | The same icon appears several times and you want them all | A list sorted by confidence |
find_object_retry is the house wait primitive: it calls find_object up to total_tries times with a flat time_sleep pause between failed tries and never sleeps after the last one. Every other keyword (threshold, search_region, screenshot) is forwarded to find_object. find_any_object_retry does the same over a list. Prefer these over wait_for_object, which is kept for older macros.
match = mas.find_object_retry(images.play_button, total_tries=5, time_sleep=1.5)
if match:
mas.click(match.x, match.y, delay_ms=1000)
else:
mas.log("Play button did not appear", level="warning")An ObjectMatch has x, y, center and matched_template_id. It does not carry a score, so tune the threshold on the call rather than reading a confidence afterwards.
Narrow the search with search_region
Template matching slides the template over the whole screenshot. A Region limits the slide to a rectangle, which is faster and avoids look-alikes elsewhere on screen. Coordinates are pixels from the top-left corner.
from mas import Region
TOP_BAR = Region(x1=0, y1=0, x2=540, y2=120)
energy_icon = mas.find_object(images.energy_icon, search_region=TOP_BAR)Use a region whenever you know where the element lives: the top bar for counters, the bottom for action buttons, the centre for dialogs. Read the region off Asset Lab; the point picker shows pixel coordinates on the live screen.
Tune the threshold
threshold runs from 0.0 to 1.0 and defaults to 0.8. The match must reach it to count.
- Lower it to 0.7 when a correct template keeps missing because of anti-aliasing, a slight scale change, or a glow effect on the button.
- Raise it to 0.9 when a template matches the wrong place, for instance two similar icons in a row or a button that also appears greyed out.
- Recrop before you go below 0.7. A threshold that low accepts nearly anything of the same colour.
enabled = mas.find_object(images.claim_enabled, threshold=0.9) # strict: disabled and enabled look alike
glowing = mas.find_object(images.reward_icon, threshold=0.7) # lenient: the icon has a pulsing glowHandle popups with find_any_object
Popups are the reason recorded macros break. Give the macro one function that knows every popup and call it at the top of each loop pass.
POPUPS = [images.close_popup, images.confirm_button, images.later_button]
def clear_popups():
popup = mas.find_any_object(POPUPS)
if popup is None:
return False
mas.log(f"Closing popup {popup.matched_template_id}")
mas.click(popup.x, popup.y, delay_ms=800)
return Truefind_any_object takes a list and returns the first match. search_strategy="best_match" picks the highest score across the list instead, and "priority_order" tries the templates in the order you listed them. Returning True lets the loop continue, so the next pass looks at a clean screen.
An LDPlayer macro loop that waits for a state
Waiting is a loop with a deadline. find_object_retry covers short waits; for a loading screen that can take a minute, write the loop yourself so you can log progress and give up cleanly.
import time
def wait_for(image, timeout_s=60, every_s=2.0):
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
match = mas.find_object(image)
if match:
return match
time.sleep(every_s)
return Nonetime.monotonic() counts seconds and never jumps when the clock changes, which makes it the right timer for a deadline. Every wait must end: return None and let the caller decide whether to stop the run.
A complete BlueStacks macro with image recognition
The script opens a screen, clears popups, taps a button while it is enabled, and stops when the “out of energy” template appears or the time budget runs out. Replace the image IDs with the ones from your Image Library.
import random
import sys
import time
import mas
from mas import Region
images = mas.images({
"play_button": 201,
"close_popup": 202,
"confirm_button": 203,
"out_of_energy": 204,
"home_screen": 205,
})
BOTTOM = Region(x1=0, y1=700, x2=540, y2=960)
TIME_BUDGET_S = 15 * 60
def clear_popups():
popup = mas.find_any_object([images.close_popup, images.confirm_button])
if popup is None:
return False
mas.click(popup.x, popup.y, delay_ms=800)
return True
def main():
home = mas.find_object_retry(images.home_screen, total_tries=10, time_sleep=3.0)
if home is None:
mas.log("Home screen never appeared", level="error")
sys.exit(2)
started = time.monotonic()
taps = 0
while time.monotonic() - started < TIME_BUDGET_S:
if clear_popups():
continue
if mas.find_object(images.out_of_energy):
mas.log("Out of energy, done")
break
button = mas.find_object_retry(images.play_button, total_tries=3, time_sleep=2.0, search_region=BOTTOM)
if button is None:
mas.log("Play button not found, looking again", level="warning")
continue
mas.click(button.x, button.y, delay_ms=1000)
taps += 1
time.sleep(random.uniform(0.8, 2.0))
mas.log(f"Finished after {taps} taps")
if __name__ == "__main__":
main()Run it with Run (F5) in the Code Editor and watch the console. When a template misses, fix the crop first and the threshold second.
Troubleshooting
Template never matches
The crop was taken on a different resolution or DPI than the device is running now, the crop includes background that changed, or the element is animated. Check the emulator’s display settings against the ones you captured on, recrop tighter, and try threshold=0.7. If the element has several looks, capture each one and use find_any_object. A mas.ImageNotFoundError means the ID is not in your Image Library at all; copy it again from the Assets panel.
Matches the wrong thing
The template is generic: a plain arrow, a square of one colour, a word that appears twice. Crop something unique next to it, raise the threshold to 0.9, or pass a search_region so the search stays where the element lives. find_objects shows you every place the template scores above the threshold, which makes the look-alike easy to spot.
Works on one instance, fails on another
The second emulator instance runs at another resolution or DPI. Template matching is pixel based, so a template captured at 540x960 does not match at 720x1280 or at a different DPI. Set every instance to the same display settings and restart it. On a cloud device, create the device with the same display preset you captured on. If the instances must differ, capture a template set per resolution and pick it with mas.get_screen_size().
No automation tool is 100% risk-free, so automate responsibly and at your own discretion.
Next steps
Related pages
Thanks. If something is wrong, tell us in Discord.
Questions? Ask in Discord