Search

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
Beginner Updated 7 min read
On this page
  1. Why a recorder cannot branch
  2. Before you start
  3. Capture a template in Asset Lab
  4. Pick the right call
  5. Narrow the search with search_region
  6. Tune the threshold
  7. Handle popups with find_any_object
  8. An LDPlayer macro loop that waits for a state
  9. A complete BlueStacks macro with image recognition
  10. Troubleshooting
  11. Template never matches
  12. Matches the wrong thing
  13. 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

  1. Bring the app to the screen with the button you want to find.
  2. In the Code Editor, open the Assets panel and click Open Asset Helper. Asset Lab opens with the live screen of your device.
  3. Crop a tight rectangle around the button and save it. The crop goes to your Image Library and gets a numeric ID.
  4. Repeat for every state the macro must recognise: the popup’s close button, the confirm button, the “done” message.
  5. In the Assets panel, click Copy ID on each image and declare them at the top of your script.
python
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

CallUse it whenReturns
find_object(image)You want one look at the screen right nowObjectMatch or None
find_object_retry(image, total_tries=3, time_sleep=2.0)The thing may take a moment to appearObjectMatch or None after the last try
find_any_object([a, b, c])Several templates are acceptable: popup variants, two themesThe first match, with matched_template_id
find_objects(image, max_matches=10)The same icon appears several times and you want them allA 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.

python
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.

python
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.
python
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 glow

Handle 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.

python
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 True

find_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.

python
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 None

time.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.

python
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

Was this page helpful?

Questions? Ask in Discord