메시지
OpenCV 기반 시각 인식으로 픽셀 좌표에 의존하지 않습니다. 해상도가 바뀌어도, UI가 조금 달라져도 정확하게 작동합니다.
다른 사용자가 만든 자동화 프리셋을 다운로드하세요. 내가 만든 프리셋을 공유하고 커뮤니티에 기여하세요.
조건문, 반복문, 변수를 드래그 앤 드롭으로 구성하세요. 프로그래밍 경험 없이도 고급 자동화를 구현할 수 있습니다.
MuMu Player, LDPlayer 등 주요 에뮬레이터를 완벽 지원. DLL 캡처로 게임 감지 없이 백그라운드에서 작동합니다.
직관적인 인터페이스로 누구나 쉽게
에뮬레이터 연결 및 실시간 화면 캡처
조건/액션 기반 자동화 설정
시각적 시퀀스 편집
세부 조건 및 액션 설정
입력 녹화 및 재생
예약 실행 설정
성능과 효율성을 동시에
3단계로 시작하는 게임 자동화
ZIP 파일을 다운로드하고 압축을 풀어주세요. UniBot.exe를 실행하면 끝!
MuMu Player나 LDPlayer를 실행하고 새로고침(🔄) 버튼을 눌러 장치를 선택하세요.
커뮤니티 프리셋을 다운로드하거나 직접 규칙을 만들고 ▶️ 시작!
화면에서 특정 이미지를 찾아 자동으로 클릭. 스크린샷으로 템플릿을 캡처하세요.
화면의 텍스트를 읽어 조건 분기. "HP가 50% 이하면 포션 사용" 같은 로직 구현.
여러 동작을 순서대로 실행. 스킬 콤보, 퀘스트 자동화에 활용하세요.
특정 시간에 자동 실행. 매일 오전 6시에 출석체크 등 반복 작업 자동화.
Python, Lua, JavaScript로 고급 자동화 구현
tap(x: int, y: int) → bool
화면의 특정 좌표를 탭합니다.
▼
x | int | X 좌표 |
y | int | Y 좌표 |
# 화면 중앙 탭
tap(540, 960)
# 버튼 위치 탭
if match_template("button.png"):
tap(result["center"][0], result["center"][1])
-- 화면 중앙 탭
tap(540, 960)
-- 버튼 위치 탭
local result = match_template("button.png")
if result then
tap(result.center[1], result.center[2])
end
// 화면 중앙 탭
tap(540, 960);
// 버튼 위치 탭
const result = match_template("button.png");
if (result) {
tap(result.center[0], result.center[1]);
}
double_tap(x: int, y: int, interval: int = 50) → bool
화면의 특정 좌표를 더블 탭합니다.
▼
x | int | X 좌표 |
y | int | Y 좌표 |
interval | int | 탭 간격 (ms), 기본값 50 |
# 50ms 간격으로 더블 탭
double_tap(540, 960, 50)
# 더블 점프 구현
double_tap(540, 1500, 100)
-- 50ms 간격으로 더블 탭
double_tap(540, 960, 50)
-- 더블 점프 구현
double_tap(540, 1500, 100)
// 50ms 간격으로 더블 탭
double_tap(540, 960, 50);
// 더블 점프 구현
double_tap(540, 1500, 100);
swipe(x1: int, y1: int, x2: int, y2: int, duration: int = 200) → bool
화면에서 스와이프합니다.
▼
x1, y1 | int | 시작 좌표 |
x2, y2 | int | 끝 좌표 |
duration | int | 이동 시간 (ms) |
# 아래에서 위로 스와이프 (스크롤 업)
swipe(540, 1200, 540, 600, 300)
# 좌에서 우로 스와이프
swipe(100, 960, 900, 960, 200)
-- 아래에서 위로 스와이프
swipe(540, 1200, 540, 600, 300)
-- 좌에서 우로 스와이프
swipe(100, 960, 900, 960, 200)
// 아래에서 위로 스와이프
swipe(540, 1200, 540, 600, 300);
// 좌에서 우로 스와이프
swipe(100, 960, 900, 960, 200);
long_press(x: int, y: int, duration: int = 1000) → bool
화면의 특정 좌표를 길게 누릅니다.
▼
x | int | X 좌표 |
y | int | Y 좌표 |
duration | int | 누르는 시간 (ms) |
# 2초간 길게 누르기
long_press(540, 960, 2000)
-- 2초간 길게 누르기
long_press(540, 960, 2000)
// 2초간 길게 누르기
long_press(540, 960, 2000);
key_press(key: str) → bool
키를 누릅니다.
▼
key | str | 키 이름 (ENTER, BACK, HOME 등) |
# 엔터 키 누르기
key_press("ENTER")
# 뒤로가기 버튼
key_press("BACK")
-- 엔터 키 누르기
key_press("ENTER")
-- 뒤로가기 버튼
key_press("BACK")
// 엔터 키 누르기
key_press("ENTER");
// 뒤로가기 버튼
key_press("BACK");
text_input(text: str) → bool
텍스트를 입력합니다.
▼
text | str | 입력할 텍스트 |
# 텍스트 입력
text_input("Hello World")
# 채팅 메시지 전송
tap(100, 1800) # 채팅창 열기
text_input("/판매 아이템명")
key_press("ENTER")
-- 텍스트 입력
text_input("Hello World")
-- 채팅 메시지 전송
tap(100, 1800) -- 채팅창 열기
text_input("/판매 아이템명")
key_press("ENTER")
// 텍스트 입력
text_input("Hello World");
// 채팅 메시지 전송
tap(100, 1800); // 채팅창 열기
text_input("/판매 아이템명");
key_press("ENTER");
capture() → ndarray | None
현재 화면을 캡처합니다.
▼
# 화면 캡처
screen = capture()
if screen is not None:
log(f"해상도: {screen.shape[1]}x{screen.shape[0]}")
-- 화면 캡처
local screen = capture()
if screen then
log("캡처 성공")
end
// 화면 캡처
const screen = capture();
if (screen) {
log("캡처 성공");
}
match_template(template: str, threshold: float = 0.8, roi: tuple = None) → dict | None
화면에서 템플릿 이미지를 찾습니다.
▼
template | str | 템플릿 이미지 경로 |
threshold | float | 매칭 임계값 (0.0 ~ 1.0) |
roi | tuple | 검색 영역 (x, y, w, h) |
{"center": (x, y), "rect": (x, y, w, h), "score": float} 또는 None
# 버튼 찾아서 클릭
result = match_template("templates/button.png", 0.85)
if result:
tap(result["center"][0], result["center"][1])
log(f"매칭 점수: {result['score']:.2f}")
# 특정 영역에서만 검색
result = match_template("icon.png", roi=(0, 0, 500, 500))
-- 버튼 찾아서 클릭
local result = match_template("templates/button.png", 0.85)
if result then
tap(result.center[1], result.center[2])
log("매칭 점수: " .. result.score)
end
// 버튼 찾아서 클릭
const result = match_template("templates/button.png", 0.85);
if (result) {
tap(result.center[0], result.center[1]);
log(`매칭 점수: ${result.score.toFixed(2)}`);
}
wait_template(template: str, timeout: float = 10.0, threshold: float = 0.8) → dict | None
템플릿이 나타날 때까지 대기합니다.
▼
template | str | 템플릿 이미지 경로 |
timeout | float | 최대 대기 시간 (초) |
threshold | float | 매칭 임계값 |
# 로딩 화면 종료 대기
result = wait_template("main_screen.png", 30.0)
if result:
log("로딩 완료!")
else:
log("타임아웃 - 로딩 실패")
-- 로딩 화면 종료 대기
local result = wait_template("main_screen.png", 30.0)
if result then
log("로딩 완료!")
else
log("타임아웃 - 로딩 실패")
end
// 로딩 화면 종료 대기
const result = wait_template("main_screen.png", 30.0);
if (result) {
log("로딩 완료!");
} else {
log("타임아웃 - 로딩 실패");
}
match_and_tap(template: str, threshold: float = 0.8, offset: tuple = (0, 0)) → bool
템플릿을 찾아서 탭합니다.
▼
# 간단하게 버튼 클릭
match_and_tap("ok_button.png", 0.85)
# 오프셋 적용 (버튼 아래쪽 클릭)
match_and_tap("header.png", offset=(0, 50))
-- 간단하게 버튼 클릭
match_and_tap("ok_button.png", 0.85)
-- 오프셋 적용 (버튼 아래쪽 클릭)
match_and_tap("header.png", 0.8, {0, 50})
// 간단하게 버튼 클릭
match_and_tap("ok_button.png", 0.85);
// 오프셋 적용 (버튼 아래쪽 클릭)
match_and_tap("header.png", 0.8, [0, 50]);
get_pixel_color(x: int, y: int) → tuple | None
특정 좌표의 픽셀 색상을 가져옵니다.
▼
color = get_pixel_color(100, 200)
if color:
r, g, b = color
log(f"RGB: {r}, {g}, {b}")
local color = get_pixel_color(100, 200)
if color then
local r, g, b = color[1], color[2], color[3]
log("RGB: " .. r .. ", " .. g .. ", " .. b)
end
const color = get_pixel_color(100, 200);
if (color) {
const [r, g, b] = color;
log(`RGB: ${r}, ${g}, ${b}`);
}
check_pixel_color(x: int, y: int, color: tuple, tolerance: int = 20) → bool
픽셀 색상이 지정 색상과 일치하는지 확인합니다.
▼
# HP 바 빨간색 체크
if check_pixel_color(100, 50, (255, 0, 0), 30):
log("HP 낮음 - 포션 사용!")
match_and_tap("potion.png")
-- HP 바 빨간색 체크
if check_pixel_color(100, 50, {255, 0, 0}, 30) then
log("HP 낮음 - 포션 사용!")
match_and_tap("potion.png")
end
// HP 바 빨간색 체크
if (check_pixel_color(100, 50, [255, 0, 0], 30)) {
log("HP 낮음 - 포션 사용!");
match_and_tap("potion.png");
}
ocr_read(roi: tuple = None) → str
화면 영역에서 텍스트를 읽습니다.
▼
roi | tuple | 읽을 영역 (x, y, w, h), None이면 전체 화면 |
# 특정 영역에서 텍스트 읽기
text = ocr_read((100, 200, 300, 50))
log(f"읽은 텍스트: {text}")
# 전체 화면에서 읽기
full_text = ocr_read()
-- 특정 영역에서 텍스트 읽기
local text = ocr_read({100, 200, 300, 50})
log("읽은 텍스트: " .. text)
-- 전체 화면에서 읽기
local full_text = ocr_read()
// 특정 영역에서 텍스트 읽기
const text = ocr_read([100, 200, 300, 50]);
log(`읽은 텍스트: ${text}`);
// 전체 화면에서 읽기
const fullText = ocr_read();
ocr_find(text: str, roi: tuple = None) → dict | None
화면에서 특정 텍스트를 찾습니다.
▼
{"text": str, "center": (x, y), "rect": (x, y, w, h)} 또는 None
# "확인" 버튼 찾아서 클릭
result = ocr_find("확인")
if result:
tap(result["center"][0], result["center"][1])
-- "확인" 버튼 찾아서 클릭
local result = ocr_find("확인")
if result then
tap(result.center[1], result.center[2])
end
// "확인" 버튼 찾아서 클릭
const result = ocr_find("확인");
if (result) {
tap(result.center[0], result.center[1]);
}
ocr_tap_text(text: str, roi: tuple = None) → bool
화면에서 텍스트를 찾아서 탭합니다.
▼
# "시작하기" 버튼 클릭
ocr_tap_text("시작하기")
# 메뉴 항목 클릭
ocr_tap_text("설정")
-- "시작하기" 버튼 클릭
ocr_tap_text("시작하기")
-- 메뉴 항목 클릭
ocr_tap_text("설정")
// "시작하기" 버튼 클릭
ocr_tap_text("시작하기");
// 메뉴 항목 클릭
ocr_tap_text("설정");
ocr_read_number(roi: tuple = None) → int | None
화면 영역에서 숫자를 읽습니다.
▼
# HP 수치 읽기
hp = ocr_read_number((50, 100, 100, 30))
if hp and hp < 50:
log("HP 낮음! 포션 사용")
match_and_tap("potion.png")
-- HP 수치 읽기
local hp = ocr_read_number({50, 100, 100, 30})
if hp and hp < 50 then
log("HP 낮음! 포션 사용")
match_and_tap("potion.png")
end
// HP 수치 읽기
const hp = ocr_read_number([50, 100, 100, 30]);
if (hp && hp < 50) {
log("HP 낮음! 포션 사용");
match_and_tap("potion.png");
}
get_var(name: str, default: Any = None) → Any
변수 값을 가져옵니다.
▼
# 카운터 가져오기 (없으면 0)
count = get_var("loop_count", 0)
log(f"현재 카운트: {count}")
-- 카운터 가져오기 (없으면 0)
local count = get_var("loop_count", 0)
log("현재 카운트: " .. count)
// 카운터 가져오기 (없으면 0)
const count = get_var("loop_count", 0);
log(`현재 카운트: ${count}`);
set_var(name: str, value: Any) → None
변수 값을 설정합니다.
▼
# 카운터 증가
count = get_var("loop_count", 0)
set_var("loop_count", count + 1)
# 상태 저장
set_var("last_status", "success")
-- 카운터 증가
local count = get_var("loop_count", 0)
set_var("loop_count", count + 1)
-- 상태 저장
set_var("last_status", "success")
// 카운터 증가
const count = get_var("loop_count", 0);
set_var("loop_count", count + 1);
// 상태 저장
set_var("last_status", "success");
list_vars() → dict
모든 변수 목록을 가져옵니다.
▼
# 모든 변수 출력
all_vars = list_vars()
for name, value in all_vars.items():
log(f"{name} = {value}")
-- 모든 변수 출력
local all_vars = list_vars()
for name, value in pairs(all_vars) do
log(name .. " = " .. tostring(value))
end
// 모든 변수 출력
const allVars = list_vars();
for (const [name, value] of Object.entries(allVars)) {
log(`${name} = ${value}`);
}
wait(seconds: float) → None
지정된 시간만큼 대기합니다.
▼
# 1.5초 대기
wait(1.5)
# 로딩 대기
tap(start_button_x, start_button_y)
wait(3.0) # 로딩 완료까지 대기
-- 1.5초 대기
wait(1.5)
-- 로딩 대기
tap(start_button_x, start_button_y)
wait(3.0) -- 로딩 완료까지 대기
// 1.5초 대기
wait(1.5);
// 로딩 대기
tap(start_button_x, start_button_y);
wait(3.0); // 로딩 완료까지 대기
wait_random(min_sec: float, max_sec: float) → float
랜덤 시간만큼 대기합니다.
▼
# 0.5~1.5초 사이 랜덤 대기 (자연스러운 동작)
actual = wait_random(0.5, 1.5)
log(f"실제 대기: {actual:.2f}초")
-- 0.5~1.5초 사이 랜덤 대기 (자연스러운 동작)
local actual = wait_random(0.5, 1.5)
log(string.format("실제 대기: %.2f초", actual))
// 0.5~1.5초 사이 랜덤 대기 (자연스러운 동작)
const actual = wait_random(0.5, 1.5);
log(`실제 대기: ${actual.toFixed(2)}초`);
log(message: str, level: str = 'info') → None
로그 메시지를 출력합니다.
▼
info, warning, error, debug
log("작업 시작!", "info")
log("경고: HP 낮음", "warning")
log("오류 발생", "error")
log("작업 시작!", "info")
log("경고: HP 낮음", "warning")
log("오류 발생", "error")
log("작업 시작!", "info");
log("경고: HP 낮음", "warning");
log("오류 발생", "error");
notify(title: str, message: str) → bool
Windows 알림을 전송합니다.
▼
# 중요 알림 전송
notify("경고", "HP가 50% 이하입니다!")
# 작업 완료 알림
notify("완료", "자동화 작업이 끝났습니다.")
-- 중요 알림 전송
notify("경고", "HP가 50% 이하입니다!")
-- 작업 완료 알림
notify("완료", "자동화 작업이 끝났습니다.")
// 중요 알림 전송
notify("경고", "HP가 50% 이하입니다!");
// 작업 완료 알림
notify("완료", "자동화 작업이 끝났습니다.");
screenshot(filename: str = None) → str
스크린샷을 저장합니다.
▼
# 에러 발생 시 스크린샷 저장
try:
do_something()
except:
path = screenshot("error_screen.png")
log(f"에러 스크린샷: {path}")
-- 에러 발생 시 스크린샷 저장
local ok, err = pcall(do_something)
if not ok then
local path = screenshot("error_screen.png")
log("에러 스크린샷: " .. path)
end
// 에러 발생 시 스크린샷 저장
try {
do_something();
} catch (e) {
const path = screenshot("error_screen.png");
log(`에러 스크린샷: ${path}`);
}
get_screen_size() → tuple
화면 해상도를 가져옵니다.
▼
width, height = get_screen_size()
log(f"해상도: {width}x{height}")
# 화면 중앙 좌표 계산
center_x, center_y = width // 2, height // 2
local size = get_screen_size()
log("해상도: " .. size[1] .. "x" .. size[2])
-- 화면 중앙 좌표 계산
local center_x = math.floor(size[1] / 2)
local center_y = math.floor(size[2] / 2)
const [width, height] = get_screen_size();
log(`해상도: ${width}x${height}`);
// 화면 중앙 좌표 계산
const centerX = Math.floor(width / 2);
const centerY = Math.floor(height / 2);
get_current_app() → str | None
현재 실행 중인 앱 패키지명을 가져옵니다.
▼
app = get_current_app()
if app == "com.nexon.maplem":
log("메이플스토리M 실행 중")
else:
start_app("com.nexon.maplem")
local app = get_current_app()
if app == "com.nexon.maplem" then
log("메이플스토리M 실행 중")
else
start_app("com.nexon.maplem")
end
const app = get_current_app();
if (app === "com.nexon.maplem") {
log("메이플스토리M 실행 중");
} else {
start_app("com.nexon.maplem");
}
start_app(package: str) → bool
앱을 시작합니다.
▼
# 게임 시작
start_app("com.nexon.maplem")
wait(10) # 로딩 대기
-- 게임 시작
start_app("com.nexon.maplem")
wait(10) -- 로딩 대기
// 게임 시작
start_app("com.nexon.maplem");
wait(10); // 로딩 대기
stop_app(package: str) → bool
앱을 종료합니다.
▼
# 게임 강제 종료 후 재시작
stop_app("com.nexon.maplem")
wait(2)
start_app("com.nexon.maplem")
-- 게임 강제 종료 후 재시작
stop_app("com.nexon.maplem")
wait(2)
start_app("com.nexon.maplem")
// 게임 강제 종료 후 재시작
stop_app("com.nexon.maplem");
wait(2);
start_app("com.nexon.maplem");
pause_macro() → None
매크로 실행을 일시 중지합니다.
▼
# HP 위험 시 일시 중지
hp = ocr_read_number((50, 100, 100, 30))
if hp and hp < 10:
notify("경고", "HP 위험! 수동 개입 필요")
pause_macro()
-- HP 위험 시 일시 중지
local hp = ocr_read_number({50, 100, 100, 30})
if hp and hp < 10 then
notify("경고", "HP 위험! 수동 개입 필요")
pause_macro()
end
// HP 위험 시 일시 중지
const hp = ocr_read_number([50, 100, 100, 30]);
if (hp && hp < 10) {
notify("경고", "HP 위험! 수동 개입 필요");
pause_macro();
}
stop_macro() → None
매크로 실행을 완전히 중지합니다.
▼
# 오류 횟수 초과 시 중지
error_count = get_var("error_count", 0)
if error_count > 10:
log("오류 횟수 초과, 매크로 중지", "error")
stop_macro()
-- 오류 횟수 초과 시 중지
local error_count = get_var("error_count", 0)
if error_count > 10 then
log("오류 횟수 초과, 매크로 중지", "error")
stop_macro()
end
// 오류 횟수 초과 시 중지
const errorCount = get_var("error_count", 0);
if (errorCount > 10) {
log("오류 횟수 초과, 매크로 중지", "error");
stop_macro();
}
call_sequence(name: str) → bool
다른 시퀀스를 호출합니다.
▼
# HP 낮으면 회복 시퀀스 호출
hp = ocr_read_number((50, 100, 100, 30))
if hp and hp < 50:
call_sequence("heal_routine")
# 보스 발견 시 보스 시퀀스 호출
if match_template("boss_icon.png"):
call_sequence("boss_battle")
-- HP 낮으면 회복 시퀀스 호출
local hp = ocr_read_number({50, 100, 100, 30})
if hp and hp < 50 then
call_sequence("heal_routine")
end
-- 보스 발견 시 보스 시퀀스 호출
if match_template("boss_icon.png") then
call_sequence("boss_battle")
end
// HP 낮으면 회복 시퀀스 호출
const hp = ocr_read_number([50, 100, 100, 30]);
if (hp && hp < 50) {
call_sequence("heal_routine");
}
// 보스 발견 시 보스 시퀀스 호출
if (match_template("boss_icon.png")) {
call_sequence("boss_battle");
}
네, 무료로 사용하실 수 있습니다. 무료 버전은 하루 1시간 사용, 1개 프리셋, 5개 자동화 규칙 제한이 있습니다. 커뮤니티 프리셋은 목록 열람만 가능하며, 다운로드와 공유는 프리미엄 기능입니다.
다른 사용자가 만든 자동화 프리셋을 다운로드하거나, 내가 만든 프리셋을 공유할 수 있는 기능입니다. 검색과 필터링으로 원하는 프리셋을 쉽게 찾을 수 있으며, 다운로드 수를 확인할 수 있습니다.
MuMu Player 12, LDPlayer 9을 공식 지원합니다. MuMu Extras(DLL 캡처)를 사용하면 게임 감지 없이 백그라운드에서 작동하며, GDI 캡처로 일반 PC 프로그램도 자동화할 수 있습니다.
Discord DM으로 구매 문의 주세요. 결제 확인 후 라이선스 키를 발급해 드립니다.
무료로 다운로드하고 게임 자동화의 새로운 경험을 시작하세요
다운로드 →