# 블루스택 이미지 인식 매크로 만들기와 LD플레이어 반복

> Asset Lab에서 템플릿을 잘라 BlueStacks나 LDPlayer 화면에서 찾고, 원하는 상태가 나타날 때까지 반복하는 블루스택 이미지 인식 매크로를 만듭니다.

Source: https://automationmacro.com/ko/docs/guides/image-recognition-macros (가이드, updated 2026-09-05)

블루스택 이미지 인식 매크로, 또는 LDPlayer용 이미지 인식 매크로는 행동하기 전에 화면을 봅니다. 버튼이 있기 때문에 버튼을 탭하고, 팝업이 나타났기 때문에 팝업을 닫고, "out of energy" 메시지가 뜨면 멈춥니다. 이 가이드는 Macro Automation Studio(MAS)에서 BlueStacks나 LDPlayer용 블루스택 이미지 인식 매크로를 첫 템플릿 자르기부터 상태를 기다리는 반복문까지 만듭니다. 에뮬레이터 내장 녹화기로는 부족해진 분을 위한 가이드입니다.

## 녹화기가 분기할 수 없는 이유

BlueStacks나 LDPlayer의 매크로 녹화기는 탭과 타이밍을 저장해 재생합니다. 화면을 보지 않습니다. 팝업이 버튼을 덮었는지, 오늘 로딩 화면이 더 오래 걸렸는지, 카운터가 0에 도달했는지 알 수 없습니다. 녹화된 매크로는 모두 일직선입니다.

MAS 매크로는 파이썬 스크립트입니다. 각 `mas.find_object` 호출이 스크린샷을 찍어 OpenCV 템플릿 매칭으로 템플릿 이미지를 찾습니다. 최고 일치가 임계값(기본 `threshold=0.8`)에 도달하면 중심을 반환하고, 아니면 `None`을 반환합니다. 그 `if` 하나가 매크로가 분기하고, 기다리고, 재시도하고, 멈출 수 있게 합니다.

## 시작하기 전에

- MAS가 설치되어 있고 체험 또는 요금제로 로그인한 상태. 체험은 [무료 다운로드](/download)로 시작합니다. [MAS 설치](/docs/install)를 참고하세요.
- BlueStacks나 LDPlayer가 ADB가 켜진 채 실행 중이고 기기로 추가된 상태. [BlueStacks](/docs/bluestacks-setup-guide) 또는 [LDPlayer](/docs/ldplayer-setup-guide) 가이드를 참고하세요.
- 에뮬레이터가 계속 유지할 고정 해상도를 사용. 가이드는 세로 540x960, 240 DPI를 기준선으로 씁니다.
- Code Editor에 Code-Based 프로젝트가 열려 있는 상태. [시작하기](/docs/getting-started)를 참고하세요.

## Asset Lab에서 템플릿 캡처

1. 찾고 싶은 버튼이 있는 화면으로 앱을 가져옵니다.
2. Code Editor에서 **Assets** 패널을 열고 **Open Asset Helper**를 클릭합니다. Asset Lab이 기기의 실제 화면과 함께 열립니다.
3. 버튼 주위를 꼭 맞게 사각형으로 잘라 저장합니다. 잘라낸 조각이 Image Library로 가고 숫자 ID를 받습니다.
4. 매크로가 인식해야 하는 모든 상태에 대해 반복합니다: 팝업 닫기 버튼, 확인 버튼, "done" 메시지.
5. **Assets** 패널에서 각 이미지의 **Copy ID**를 클릭하고 스크립트 맨 위에 선언합니다.

```python
import mas

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

`mas.images`는 각 ID에 읽기 쉬운 이름을 주고, 게시할 때 어떤 이미지를 묶을지 패커에 알려 줍니다. 템플릿은 jpg, png, gif, webp를 쓸 수 있고 각각 최대 10 MB입니다. [Asset Lab](/docs/asset-lab)을 참고하세요.

> [!TIP]
> 절대 바뀌지 않는 부분을 자르세요. 숫자가 적힌 버튼은 그 숫자에서만 일치합니다. 대신 숫자 옆의 아이콘을 자르세요.

## 알맞은 호출 고르기

| 호출 | 쓰는 상황 | 반환 |
|---|---|---|
| `find_object(image)` | 지금 화면을 한 번 보고 싶을 때 | `ObjectMatch` 또는 `None` |
| `find_object_retry(image, total_tries=3, time_sleep=2.0)` | 대상이 잠시 뒤에 나타날 수 있을 때 | `ObjectMatch` 또는 마지막 시도 후 `None` |
| `find_any_object([a, b, c])` | 여러 템플릿이 모두 허용될 때: 팝업 변형, 두 가지 테마 | `matched_template_id`가 있는 첫 일치 |
| `find_objects(image, max_matches=10)` | 같은 아이콘이 여러 번 나타나고 모두 원할 때 | 신뢰도순 목록 |

`find_object_retry`가 기본 대기 수단입니다. 실패한 시도 사이에 일정한 `time_sleep` 쉼을 두고 `find_object`를 최대 `total_tries`번 호출하며, 마지막 시도 후에는 쉬지 않습니다. 그 밖의 모든 키워드(`threshold`, `search_region`, `screenshot`)는 `find_object`로 전달됩니다. `find_any_object_retry`는 목록에 대해 같은 일을 합니다. 오래된 매크로를 위해 남아 있는 `wait_for_object`보다 이것들을 쓰세요.

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

`ObjectMatch`에는 `x`, `y`, `center`, `matched_template_id`가 있습니다. 점수는 담지 않으므로, 나중에 신뢰도를 읽는 대신 호출에서 임계값을 조정하세요.

## search_region으로 검색 범위 좁히기

템플릿 매칭은 스크린샷 전체 위로 템플릿을 밀며 비교합니다. `Region`은 비교를 사각형 안으로 제한하므로 더 빠르고, 화면 다른 곳의 비슷한 것과 헷갈리지 않습니다. 좌표는 왼쪽 위 모서리 기준 픽셀입니다.

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

요소가 어디 있는지 알 때마다 영역을 쓰세요: 카운터는 상단 바, 동작 버튼은 하단, 대화 상자는 중앙. 영역은 Asset Lab에서 읽으세요. 좌표 선택기가 실제 화면의 픽셀 좌표를 보여 줍니다.

## 임계값 조정

`threshold`는 0.0부터 1.0까지이며 기본값은 0.8입니다. 일치로 인정되려면 이 값에 도달해야 합니다.

- 안티앨리어싱, 약간의 크기 변화, 버튼의 빛나는 효과 때문에 올바른 템플릿을 계속 놓치면 0.7로 낮추세요.
- 템플릿이 엉뚱한 곳에 일치하면, 예를 들어 비슷한 아이콘 두 개가 나란히 있거나 회색으로 비활성화된 모습으로도 나타나는 버튼이면 0.9로 올리세요.
- 0.7 아래로 내리기 전에 다시 자르세요. 그렇게 낮은 임계값은 같은 색이면 거의 무엇이든 받아들입니다.

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

## find_any_object로 팝업 처리

팝업은 녹화된 매크로가 깨지는 이유입니다. 모든 팝업을 아는 함수 하나를 매크로에 주고, 반복문의 각 회차 맨 위에서 호출하세요.

```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`는 목록을 받아 첫 일치를 반환합니다. `search_strategy="best_match"`는 대신 목록 전체에서 가장 높은 점수를 고르고, `"priority_order"`는 나열한 순서대로 템플릿을 시도합니다. `True`를 반환하면 반복문이 `continue`할 수 있으므로 다음 회차는 깨끗한 화면을 봅니다.

## 상태를 기다리는 LD플레이어 매크로 반복

기다림은 마감이 있는 반복문입니다. 짧은 대기는 `find_object_retry`가 다루고, 1분이 걸릴 수 있는 로딩 화면에는 진행 상황을 기록하고 깔끔하게 포기할 수 있도록 반복문을 직접 쓰세요.

```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()`은 초를 세고 시계가 바뀌어도 건너뛰지 않으므로 마감용 타이머로 알맞습니다. 모든 기다림은 끝나야 합니다. `None`을 반환하고 호출자가 실행을 멈출지 결정하게 하세요.

## 완전한 블루스택 이미지 인식 매크로

스크립트는 화면을 열고, 팝업을 정리하고, 버튼이 활성인 동안 탭하고, "out of energy" 템플릿이 나타나거나 시간 예산이 다하면 멈춥니다. 이미지 ID를 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()
```

Code Editor에서 **Run**(<kbd>F5</kbd>)으로 실행하고 콘솔을 지켜보세요. 템플릿을 놓치면 잘라낸 조각을 먼저 고치고 임계값은 그다음입니다.

## 문제 해결

### 템플릿이 전혀 일치하지 않음

기기가 지금 실행 중인 것과 다른 해상도나 DPI에서 잘라냈거나, 잘라낸 조각에 바뀐 배경이 포함되어 있거나, 요소에 애니메이션이 있습니다. 에뮬레이터 디스플레이 설정을 캡처할 때의 설정과 비교하고, 더 조여서 다시 자르고, `threshold=0.7`을 시도하세요. 요소의 모습이 여러 가지면 각각 캡처하고 `find_any_object`를 쓰세요. `mas.ImageNotFoundError`는 ID가 Image Library에 아예 없다는 뜻입니다. **Assets** 패널에서 다시 복사하세요.

### 엉뚱한 것과 일치함

템플릿이 일반적입니다: 평범한 화살표, 단색 사각형, 두 번 나타나는 단어. 옆의 고유한 것을 자르거나, 임계값을 0.9로 올리거나, `search_region`을 넘겨 요소가 있는 곳에서만 찾게 하세요. `find_objects`는 템플릿이 임계값 이상으로 점수를 얻는 모든 위치를 보여 주므로 비슷한 것을 쉽게 찾아냅니다.

### 한 인스턴스에서는 되고 다른 인스턴스에서는 안 됨

두 번째 에뮬레이터 인스턴스가 다른 해상도나 DPI로 실행 중입니다. 템플릿 매칭은 픽셀 기반이라 540x960에서 캡처한 템플릿은 720x1280이나 다른 DPI에서 일치하지 않습니다. 모든 인스턴스를 같은 디스플레이 설정으로 맞추고 다시 시작하세요. 클라우드 기기에서는 캡처한 것과 같은 디스플레이 프리셋으로 기기를 만드세요. 인스턴스가 꼭 달라야 한다면 해상도별 템플릿 세트를 캡처하고 `mas.get_screen_size()`로 고르세요.

100% 안전한 자동화 도구는 없으므로, 책임감을 가지고 본인의 판단에 따라 자동화하세요.
