예전부터 한 번 만들어보고 싶었다.
모바일 게임 중 PC에서 돌아가는 게임들이 많다.
나는 Java의 Robot과 UI를 이용해서 간단한 매크로를 만들어보았다.
대충 만든 기능 흐름도
큰 덩어리는 두 개다.
1. 설정
- Swing을 이용해서 간단한 UI를 만들었다.
- 클릭해야 하는 게임 내의 버튼을 overlay 한 java ui 위의 마우스 클릭을 통해 영역을 선택하게 만들었다.
- 관리자 권한으로 실행해야 한다.
2. 매크로
- 설정 화면에서 반복할 횟수를 정한다.
- 현재 화면을 캡처한다.
- if 특정 이미지가 캡처에 존재하면 해당 이미지를 클릭한다.
- 화면을 드래그하여 남은 영역을 출력한다.
- 현재 화면을 캡처한다.
- if 특정 이미지가 캡처에 존재하면 해당 이미지를 클릭한다.
- 정해진 반복 횟수만큼 위의 내용을 반복한다.
일단은 설정하는 화면과 마우스 이벤트를 처리하는 개략 클래스 다이어그램
Swing으로 UI 만드는 건 학원 다닐 때 이후로 처음인 것 같다.
하나씩 클릭해서 모든 영역이 설정되면 매크로 시작 버튼이 활성화된다.
매크로는 위의 절차대로 단순 하드코딩을 진행했다.
이 프로젝트는 더 발전시킬 생각은 없기 때문에... 사용도 안 할 거고... 이 이상 필요는 없을 듯하다.
이미지 매칭은 java의 라이브러리인 SikuliX를 사용했다.
public class ImageMatcher {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final Map<String, BufferedImage> templates;
private final Rectangle area;
public ImageMatcher(Rectangle area) throws IOException {
this.templates = new HashMap<>();
this.area = area;
templates.put("1.png", loadImageFromResources("1.png"));
// templates.put("2.png", loadImageFromResources("2.png"));
}
public BufferedImage loadImageFromResources(String imageName) throws IOException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream inputStream = loader.getResourceAsStream(imageName);
if (inputStream == null) {
throw new IOException("리소스를 찾을 수 없습니다: " + imageName);
}
BufferedImage image = ImageIO.read(inputStream);
inputStream.close();
return image;
}
public Point match(BufferedImage capture) {
Finder finder = new Finder(capture);
for (Map.Entry<String, BufferedImage> entry : templates.entrySet()) {
String imageName = entry.getKey();
BufferedImage templateImage = entry.getValue();
Pattern pattern = new Pattern(templateImage).similar(0.7);
finder.find(pattern);
if (finder.hasNext()) {
Match match = finder.next();
log.info("성공: {} 이미지를 캡처된 영역 내에서 찾았습니다!", imageName);
// match에서 반환되는 xy 좌표는 패턴이 겹치는 시작지점 즉, 상대좌표이다. 따라서 전체 영역의 x,y 좌표를 더해야 한다.
int absoluteX = area.x + match.getX();
int absoluteY = area.y + match.getY();
return new Point(absoluteX, absoluteY);
} else {
log.info("실패: 이미지를 캡처된 영역에서 찾지 못했습니다.");
}
}
return null;
}
}
resource 폴더에서 불러온 이미지 파일!
이 파일과 탐색 영역의 유사도(0.7)에 따라 검색한다.
이렇게 작성하고 실행하면...
SLF4J(W): Class path contains multiple SLF4J providers.
SLF4J(W): Found provider [org.slf4j.nop.NOPServiceProvider@52d455b8]
SLF4J(W): Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@4f4a7090]
SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J(I): Actual provider is of type [org.slf4j.nop.NOPServiceProvider@52d455b8]
Exception in thread "main" java.lang.IllegalStateException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.helpers.NOPLoggerFactory loaded from file:/C:/Users/%ea%b5%ac%ed%98%84%ec%9a%b0/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.17/d9e58ac9c7779ba3bf8142aff6c830617a7fe60f/slf4j-api-2.0.17.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml
at org.springframework.util.Assert.state(Assert.java:101)
...
오래간만에 보는 의존성 충돌...
maven에서는 많이 제거해 봤는데, gradle에서는 처음 해보는 것 같다.
나는 Spring을 기반으로 프로젝트를 생성해서 Logback을 기본으로 사용하고 있다.
그런데 새로 추가한 라이브러리에서도 NOPServiceProvider라는 로깅 시스템을 사용하고 있었나 보다.
스프링은 충돌하는 두 개의 Slf4j 구현체를 인지하고, 에러를 발생시켰다.
아래와 같이 제거한다고 한다.
implementation('com.sikulix:sikulixapi:2.0.5') {
exclude group: 'org.slf4j', module: 'slf4j-nop'
}
다시 실행!
'공부' 카테고리의 다른 글
[이미지변환] HEIC To JPG (1) | 2025.06.20 |
---|---|
[CI/CD] Gitea + Act_Runner (1) | 2025.06.13 |
[GCP] Nginx SSL 자동갱신 (1) | 2025.06.13 |
[REST] API 요청, 응답 (2) | 2025.06.12 |
[BE] SSE 재연결과 관련된 시간 (0) | 2025.06.12 |