# Macro BlueStacks con reconocimiento de imagen y bucles

> Crea una macro BlueStacks con reconocimiento de imagen: captura una plantilla en Asset Lab, búscala en BlueStacks o LDPlayer y repite hasta ver un estado.

Source: https://automationmacro.com/es/docs/guides/image-recognition-macros (Guías, updated 2026-09-05)

Una macro BlueStacks con reconocimiento de imagen, o una de LDPlayer, mira la pantalla antes de actuar. Toca un botón porque el botón está ahí, cierra un aviso porque el aviso apareció y se detiene cuando aparece el mensaje de "sin energía". Esta guía construye una macro BlueStacks con reconocimiento de imagen en Macro Automation Studio (MAS), también válida para LDPlayer, desde el primer recorte de plantilla hasta un bucle que espera a un estado. Está pensada para quien se ha quedado corto con el grabador integrado del emulador.

## Por qué un grabador no puede ramificarse

El grabador de macros de BlueStacks o LDPlayer guarda toques y tiempos y los reproduce. Nunca mira la pantalla. No puede saber que un aviso tapó el botón, que la pantalla de carga tardó más hoy o que el contador llegó a cero. Toda macro grabada es una línea recta.

Una macro de MAS es un script de Python. Cada llamada a `mas.find_object` hace una captura y busca en ella una imagen de plantilla con la coincidencia de plantillas (template matching) de OpenCV. Cuando la mejor coincidencia alcanza el umbral (`threshold=0.8` por defecto), la llamada devuelve su centro. Si no, devuelve `None`. Ese único `if` es lo que permite a una macro ramificarse, esperar, reintentar y parar.

## Antes de empezar

- MAS está instalado y con sesión iniciada con la prueba o un plan. La prueba empieza con la [descarga gratuita](/download); consulta [Instalar MAS](/docs/install).
- BlueStacks o LDPlayer está en marcha con ADB activado y añadido como dispositivo. Consulta la guía de [BlueStacks](/docs/bluestacks-setup-guide) o de [LDPlayer](/docs/ldplayer-setup-guide).
- El emulador usa una resolución fija que vas a mantener. Las guías usan 540x960 en vertical a 240 DPI como línea base.
- Un proyecto basado en código está abierto en el Code Editor. Consulta [Primeros pasos](/docs/getting-started).

## Capturar una plantilla en Asset Lab

1. Lleva la app a la pantalla con el botón que quieres encontrar.
2. En el Code Editor, abre el panel **Recursos** y haz clic en **Abrir Asset Helper**. Asset Lab se abre con la pantalla en directo de tu dispositivo.
3. Recorta un rectángulo ajustado alrededor del botón y guárdalo. El recorte va a tu biblioteca de imágenes y recibe un ID numérico.
4. Repite con cada estado que la macro deba reconocer: el botón de cerrar del aviso, el botón de confirmar, el mensaje de "hecho".
5. En el panel **Recursos**, haz clic en **Copiar ID** en cada imagen y decláralas al principio de tu script.

```python
import mas

images = mas.images({
    "play_button": 201,
    "close_popup": 202,
    "out_of_energy": 203,
})
```

`mas.images` da a cada ID un nombre legible e indica al empaquetador qué imágenes incluir cuando publiques. Las plantillas pueden ser jpg, png, gif o webp, de hasta 10 MB cada una. Consulta [Asset Lab](/docs/asset-lab).

> [!TIP]
> Recorta la parte que nunca cambia. Un botón con un número encima solo coincide con ese número; recorta el icono que hay junto al número.

## Elegir la llamada adecuada

| Llamada | Úsala cuando | Devuelve |
|---|---|---|
| `find_object(image)` | Quieres una sola mirada a la pantalla ahora mismo | `ObjectMatch` o `None` |
| `find_object_retry(image, total_tries=3, time_sleep=2.0)` | El elemento puede tardar un momento en aparecer | `ObjectMatch` o `None` tras el último intento |
| `find_any_object([a, b, c])` | Valen varias plantillas: variantes de un aviso, dos temas | La primera coincidencia, con `matched_template_id` |
| `find_objects(image, max_matches=10)` | El mismo icono aparece varias veces y las quieres todas | Una lista ordenada por confianza |

`find_object_retry` es la primitiva de espera de la casa: llama a `find_object` hasta `total_tries` veces con una pausa fija `time_sleep` entre intentos fallidos y nunca duerme después del último. Todos los demás argumentos con nombre (`threshold`, `search_region`, `screenshot`) se reenvían a `find_object`. `find_any_object_retry` hace lo mismo sobre una lista. Prefiere estas a `wait_for_object`, que se mantiene por las macros antiguas.

```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")
```

Un `ObjectMatch` tiene `x`, `y`, `center` y `matched_template_id`. No lleva puntuación, así que ajusta el umbral en la llamada en lugar de leer una confianza después.

## Acotar la búsqueda con search_region

La coincidencia de plantillas desliza la plantilla por toda la captura. Una `Region` limita el barrido a un rectángulo, lo que es más rápido y evita parecidos en otras partes de la pantalla. Las coordenadas son píxeles desde la esquina superior izquierda.

```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)
```

Usa una región siempre que sepas dónde vive el elemento: la barra superior para los contadores, la inferior para los botones de acción, el centro para los diálogos. Lee la región en Asset Lab; el selector de puntos muestra las coordenadas en píxeles sobre la pantalla en directo.

## Ajustar el umbral

`threshold` va de 0.0 a 1.0 y por defecto es 0.8. La coincidencia debe alcanzarlo para contar.

- Bájalo a 0.7 cuando una plantilla correcta sigue fallando por el suavizado, un ligero cambio de escala o un brillo en el botón.
- Súbelo a 0.9 cuando una plantilla coincide en el sitio equivocado, por ejemplo dos iconos parecidos seguidos o un botón que también aparece en gris.
- Vuelve a recortar antes de bajar de 0.7. Un umbral tan bajo acepta casi cualquier cosa del mismo color.

```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
```

## Gestionar avisos emergentes con find_any_object

Los avisos emergentes son el motivo por el que se rompen las macros grabadas. Dale a la macro una función que conozca todos los avisos y llámala al principio de cada pasada del bucle.

```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` recibe una lista y devuelve la primera coincidencia. `search_strategy="best_match"` elige en cambio la puntuación más alta de la lista, y `"priority_order"` prueba las plantillas en el orden en que las listaste. Devolver `True` permite al bucle hacer `continue`, así que la siguiente pasada mira una pantalla limpia.

## Un LDPlayer macro loop que espera a un estado

Esperar es un bucle con un plazo. `find_object_retry` cubre las esperas cortas; para una pantalla de carga que puede tardar un minuto, escribe el bucle tú mismo para registrar el progreso y rendirte limpiamente.

```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()` cuenta segundos y nunca salta cuando cambia el reloj, lo que lo convierte en el temporizador adecuado para un plazo. Toda espera debe terminar: devuelve `None` y deja que quien llama decida si detener la ejecución.

## Una macro BlueStacks completa con reconocimiento de imagen

El script abre una pantalla, cierra los avisos, toca un botón mientras está activo y se detiene cuando aparece la plantilla de "sin energía" o se agota el presupuesto de tiempo. Sustituye los ID de imagen por los de tu biblioteca de imágenes.

```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()
```

Ejecútalo con **Ejecutar** (<kbd>F5</kbd>) en el Code Editor y observa la consola. Cuando una plantilla falle, arregla primero el recorte y después el umbral.

## Solución de problemas

### La plantilla nunca coincide

El recorte se hizo con una resolución o un DPI distintos de los que usa ahora el dispositivo, el recorte incluye fondo que cambió, o el elemento está animado. Compara los ajustes de pantalla del emulador con los de la captura, recorta más ajustado y prueba `threshold=0.7`. Si el elemento tiene varios aspectos, captura cada uno y usa `find_any_object`. Un `mas.ImageNotFoundError` significa que el ID no está en tu biblioteca de imágenes; cópialo otra vez desde el panel **Recursos**.

### Coincide con lo que no debe

La plantilla es genérica: una flecha simple, un cuadrado de un solo color, una palabra que aparece dos veces. Recorta algo único junto a ella, sube el umbral a 0.9 o pasa una `search_region` para que la búsqueda se quede donde vive el elemento. `find_objects` te muestra todos los sitios donde la plantilla supera el umbral, lo que hace fácil detectar el parecido.

### Funciona en una instancia y falla en otra

La segunda instancia del emulador va a otra resolución u otro DPI. La coincidencia de plantillas se basa en píxeles, así que una plantilla capturada a 540x960 no coincide a 720x1280 ni con otro DPI. Pon todas las instancias con los mismos ajustes de pantalla y reinícialas. En un dispositivo en la nube, crea el dispositivo con el mismo preajuste de pantalla con el que capturaste. Si las instancias deben ser distintas, captura un juego de plantillas por resolución y elígelo con `mas.get_screen_size()`.

Ninguna herramienta de automatización está libre de riesgos al 100 %, así que automatiza con responsabilidad y bajo tu propio criterio.
