안녕하세요~ 킴영감입니다~


이번 강좌는 비활성 마우스 드래그를 구현하는 방법에 대한 내용으로 FunLecture 홈페이지에서 강의했던 내용을 그대로 옮겨왔습니다~


먼저 이번 강좌의 내용을 이해하기 위해서는 반드시 비활성 입력에 대한 개념과 PostMessage의 사용 방법을 이해하셔야 한다는 것을 말씀드립니다.


오토핫키로 매크로를 만들다보면 마우스 드래그가 필요한 곳이 어느게임에든 있을 것이라고 생각됩니다.


이런 이유로 많은 사람들이 비활성 마우스 드래그를 구현하고 싶어하지만 실패하는 경우가 많은 것으로 알고 있습니다.


사실 블로그를 통해서 강좌를 진행할 때 드래그에 대한 강좌를 하지 않았습니다. 드래그에 대한 자료가 잘 없어서 강좌를 진행했다면 많은 분들에게 도움이 되었겠지만 혹시라도 누군가가 자신의 것인 것처럼 도용한다고 생각하면 제가 고민하고 노력해서 얻은 노하우가 헛된 것이 될 것 같아서 하지 않았던 것입니다.


하지만 홈페이지를 개설하고 몇몇분들이 열심히 활동해주시는 것에 감명을 받아서 큰맘먹고 강좌를 진행하기로 결정했고 시간이 지나 이렇게 블로그로 옮겨오게 되었습니다. 


단, 이 강좌를 보시는 분들은 강좌글의 모든 내용에 저작권이 있으며, 이를 존중해 주셔서 제 블로그를 통해서만 이 내용을 볼 수 있고 다른 곳에서 누군가에게 알려주고 싶으실 경우 이 강좌의 링크를 달아준다 라고 생각해주셨으면 합니다. 꼭 지켜주실 것을 믿고 강좌를 진행하겠습니다.


이번 강좌에서는 제가 경험을 통해 알게된 내용을 가지고 비활성 마우스 드래그를 어떻게 구현할지구현 성공을 위해 고려해야될 사항들에 대해서 말씀드리겠습니다.



이전 강좌에서처럼 비활성 드래그 역시 PostMessage를 사용해서 구현됩니다. 


간단하게 말로 설명드리면 마우스를 클릭하고 원하는 좌표만큼 이동하고 마우스를 때는 동작을 스크립트로 구현하면 되는 것입니다.


이를 성공적으로 구현하기위해서 PostMessage의 동작 원리에 대한 이해가 필요합니다. 아래 그림을 보면서 설명 드리겠습니다.



사용자의 입력 또는 PostMessage를 사용한 비활성 입력은 윈도우에서 처리되기 전 발생된 순서대로 Message 큐 라는 곳에 쌓이게 됩니다. 윈도우는 Message 큐에 쌓여있는 Message 들을 큐에 들어온 순서대로 하나씩 처리합니다.


어렵지 않죠?^^ 이제 노하우로 넘어가겠습니다.


우리가 게임을 할 때 컴퓨터의 사양에 따라서 게임의 동작 속도가 결정되는 것은 다들 알고 계실 것입니다. 


이와 마찬가지로 큐에 쌓여있는 각 Message를 처리하는 속도는 컴퓨터의 사양에 따라서 달라지게 되고 이 두가지가 원인이 되어 Message를 처리하는 도중에 문제가 발생하게 됩니다.


첫번째는 드래그 동작을 했을 때 윈도우의 메세지 처리 속도를 게임에서 따라오지 못하는 문제가 발생합니다. 윈도우입장에서는 단순히 게임에 마우스를 클릭하고 움직이는 동작을 시키지만 게임입장에서는 드래그에 따라 화면구성이 달라지고 연속적으로 표현해야 하기 때문에 더 많은 처리시간이 걸리게 되는 것입니다. 


이런 현상을 해결하는 방법은 게임이 따라올 수 있을만큼 충분한 시간을 두고 드래그 동작을 구현해야 합니다. 다르게 말하면 무조건 빠르게 동작하는 것 보다는 안정적으로 드래그 동작이 구현될 수 있도록 스크립트를 작성해야 한다는 것입니다.


그럼 어떻게 구현하는 것이 가장 안정적일까요?


한가지 말씀드리면, 비활성 드래그에 관해서 모든 답을 알려드릴지 기본 틀만 알려드릴지에 대해서 많은 고민을 했습니다. 


제 강좌를 보시는 분들 중에 대부분이 프로그래밍을 모르시거나 초보단계에 있는 분들이 많은 것 같습니다. 모두들 강좌보시고 연습하시느라 정신이 없으실 것이라고 생각합니다. 공부하시는데 힘내시라는 의미와 더 많은 활동 부탁드리는 의미에서 완전한 답안을 드리겠습니다.


위에서 언급한 가장 큰 문제는 게임속도와 메세지 처리속도를 맞추는 것입니다.


이 문제를 해결하기위한 첫번째 방법은 마우스가 움직이는 범위를 잘게 쪼개는 것입니다. 만약 x좌표를 100만큼 이동해야 한다면 Loop를 사용해서 1씩 100번을 이동하도록 하는 것입니다. 이렇게 하는 것 만으로도 어느정도 문제를 해결할 수 있습니다.


윈도우에서 메세지를 처리하는 속도는 상상을 초월할 정도로 빠릅니다. 그렇기 때문에 컴퓨터의 사양이 좋을 수록 게임화면이 변하는 속도와 메세지를 처리하는 속도간에 차이가 많이 날 수 있으며 이를 해결하는 것이 제가 가진 핵심 노하우입니다.





바로 Sleep을 사용해서 처리 속도의 차이를 매꿔주는 것입니다!


솔직히 말씀드리면 이 문제로 고민할 당시 한달정도의 시간이 걸렸습니다. 해외 사이트를 포함해서 어디에도 위 내용을 언급한 곳이 없었습니다. 문제가 무엇인지 알았을 때에는 정말 허무했습니다.ㅋ 


원인을 알고나서 스크립트에 Sleep 한줄을 추가하는 것으로 간단히 해결했습니다. 알고 보면 허무할 정도로 간단한 것이지만 고생한 한달이라는 시간이 아까워서 더 망설였던 것 같습니다. 그러니 제 한달간의 노력을 지켜주시기를 부탁드립니다.ㅠ


그럼 위 두가지 해결방법을 적용해서 비활성드래그 함수를 만들어보겠습니다. 아래와 같이 스크립트를 작성하세요.



우선 이해하기 쉽도록 주석을 모두 달아놓았으며, 위해서부터 하나씩 설명드리겠습니다.


비활성드래그 함수에서 받아오는 변수들은 다음과 같습니다.


x시작, y시작 - 드래그를 시작하는 지점의 마우스 좌표

x끝, y끝 - 드래그가 끝나는 지점의 마우스 좌표

드래그횟수 - 드래그를 수행할 횟수

이동간격 - 마우스를 움직이는 간격

처리간격 - 마우스를 한번 이동한 후 대기시간


함수가 실행되면 우선 CoordMode를 Window로 바꿉니다. 


그 다음 x이동거리y이동거리계산합니다. 이 두 변수의 값의 부호에 따라 방향이 정해지게 됩니다.


위에서 설명 드렸듯이 총 이동할 거리를 쪼개어서 이동시켜야 한다고 했으며 자신의 컴퓨터 사양에 맞는 이동간격 값을 설정해서 그 값으로 루프를 몇번 반복할 것인지를 결정합니다. abs(x이동간격) 은 x이동간격 변수에 들어있는 값에 절대값을 의미합니다.


이제 루프를 시작합니다. 총 3개의 루프로 구성되며 각각 드래그횟수, x루프횟수, y루프횟수만큼 반복하게 됩니다.


드래그를 시작하는 동작으로 마우스 다운을 PostMessage로 구현하고 그 다음 x와 y루프를 몇번 반복했는지 카운트 할 변수를 초기화 합니다.


두번째와 세번째 루프가 마우스가 이동하는 작업을 하는 루프입니다. 


루프 시작에 Sleep이 있습니다. 



이것이 핵심입니다. 이 부분이 없으면 비활성드래그는 원하는대로 작동하지 않을 것입니다. 마우스를 이동할때마다 게임 화면이 따라올 시간을 기다려주는 것입니다. 보통 처리간격을 1로주시면 안정적으로 동작하며 반드시 1보다 큰 값으로 주셔야 합니다. 


다음으로 마우스 이동 방향에 따라서 이동할 좌표를 if와 else if를 사용해서 계산하게 되고 PostMessage를 사용해서 마우스를 이동합니다. 마우스이동을 의미하는 Message값은 0x200이고 wParam 값은 1 입니다.


마우스를 이동하는 루프를 모두 돌고나면 PostMessage를 사용해서 마우스 업 동작을 함으로 비활성 드래그가 완성됩니다.


이렇게 해서 (제가 생각하기에) 완벽한 비활성 드래그 함수가 완성되었습니다.!


이번강좌에서는 비활성 마우스 왼쪽클릭 강좌에서와 마찬가지로 함수를 사용해서 바로 사용하실 수 있도록 강좌를 진행했습니다. 그냥 바로 쓰셔도 되지만 전체 스크립트를 이해하셔야 응용이 가능하기 때문에 꼭 분석하고 공부하시기 바랍니다.^^



블로그 이미지

킴영감

프로그래밍과 게임공략, 게임과 관련된 프로그램에 대한 내용을 다룹니다.

댓글을 달아 주세요

  • 2018.08.10 13:46  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  • 마이하트 2018.08.14 18:27  댓글주소  수정/삭제  댓글쓰기

    하.. 정말이지.. 유투브를 왜 가볼 생각을 안했을까요!!!
    유투브 강좌 최고!!! 감사합니다

  • ㄹㄹ 2018.09.02 06:20  댓글주소  수정/삭제  댓글쓰기

    매번 드래그할때 창 넘어가는거 짜증났었는데
    덕분에 비활성 드래그 성공했습니다! 정말 감사합니다!

  • 로오던 2018.11.06 21:36  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 강의 잘 봤습니다. 감사합니다.
    제가 하는 게임을 매크로로 만들어서 배우는거 하나씩 적용하고 잇는데요..
    정상 작동하는 스크립트에서 함수를 만들어서 함수내에서 체크박스를 인식하게 하는게 안되더라구요..
    스크립트 구성은 대략

    #SingleInstance off

    Gui, add, text, x30 y5 w110 h20, 본케
    Gui, add, text, x60 y25 w50 h20 vA, 준비!!!
    Gui, add, Button, x20 y100 w110 h20, 시작
    Gui, add, Button, x20 y130 w110 h20, 종료
    Gui, add, checkbox, x10 y50 h20 w50 h20 v전투일반 Checked, 전투일반
    Gui, add, checkbox, x60 y50 h20 w50 h20 v전투고급 Checked, 고급
    Gui, add, checkbox, x110 y50 h20 w50 h20 v전투희귀 Checked, 희귀
    Gui, add, checkbox, x160 y50 h20 w50 h20 v전투고대 Checked, 고대
    Gui, add, checkbox, x210 y50 h20 w50 h20 v전투전설 Checked, 전설
    Gui, add, checkbox, x260 y50 h20 w50 h20 v전투불멸 Checked, 불멸
    Gui, add, checkbox, x310 y50 h20 w50 h20 v전투신화 Checked, 신화

    Gui, add, checkbox, x10 y80 h20 w50 h20 v포획일반 Checked, 포획일반
    Gui, add, checkbox, x60 y80 h20 w50 h20 v포획고급 Checked, 고급
    Gui, add, checkbox, x110 y80 h20 w50 h20 v포획희귀 Checked, 희귀
    Gui, add, checkbox, x160 y80 h20 w50 h20 v포획고대 Checked, 고대
    Gui, add, checkbox, x210 y80 h20 w50 h20 v포획전설 Checked, 전설
    Gui, add, checkbox, x260 y80 h20 w50 h20 v포획불멸 Checked, 불멸
    Gui, add, checkbox, x310 y80 h20 w50 h20 v포획신화 Checked, 신화
    Gui show

    매크로시작 := false

    return

    Button시작:
    {
    gui, submit, nohide
    guiControl, , A, 시작

    CoordMode, pixel, screen
    coordmode, mouse, screen

    매크로시작 := true
    Loop
    {
    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    매크로 내용
    함수1()

    if(매크로시작 = false)
    {
    break
    }
    } ------여기까지가 매크로 내용이구요
    return
    함수1()
    {

    gui, submit, nohide

    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    매크로내용
    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30
    {
    if (errorlevel = 0)
    {
    함수2()

    if (전투신화 = 1)
    {
    전투()
    }
    if (전투신화 = 0)
    {
    인사()
    }
    if (포획신화 = 1)
    {
    want()
    }
    if 포획신화 = 0)
    {
    sell()
    }
    }
    }-------여기까지가 함수1() 내용 입니다.

    함수2()
    {
    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *10 C:\image\작은화면\4-9.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 6000
    }
    }
    인사()
    {
    gui, submit, nohide

    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    imageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\4-2.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\4.1.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    }

    전투()
    {
    gui, submit, nohide

    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\4-3.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\4-33.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *50 C:\image\작은화면\4-4.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\4-5.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 6000
    }
    }
    want()
    {
    gui, submit, nohide

    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\want.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 6000
    }
    }

    sell()
    {
    gui, submit, nohide

    wingetpos, pos_x, pos_y, Width, Height, LDPlayer-1
    x_right := width + pos_x
    X_bottom := height + pos_y

    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *30 C:\image\작은화면\4-6.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    ImageSearch, FoundX, FoundY, %pos_x%, %pos_y%, %x_right%, %x_bottom%, *50 C:\image\작은화면\4-7.bmp
    if (errorlevel = 0)
    {
    비클(FoundX, FoundY)
    sleep, 2000
    }
    }

    return
    비클(x좌표, y좌표)
    {
    wingetpos, w_x, W_y, w_w, w_h, LDPlayer-1

    내부좌표x := x좌표 - w_x
    내부좌표y := y좌표 - w_y - 37

    1param := 내부좌표x|내부좌표y<<16
    PostMessage, 0X201, 1, %1param%, renderWindow1 ,LDPlayer-1
    PostMessage, 0X202, 0, %1param%, renderWindow1 ,LDPlayer-1
    }
    Button종료:
    {
    매크로시작 :=false
    ExitApp
    }
    return

    F2::
    {
    매크로시작 := false

    gui,submit,NoHide
    GuiControl, , A, 멈춤
    }
    return ----- 매크로 끝

    이렇게 되어있는데요..
    함수1()내에 체크박스가 들어가 있는데 본 스크립트에서 함수1()은 인식을 하는데 체크박스 관련해서는 인식을 못하네요..
    체크박스 관련 내용을 본 스크립트로 옮기면 인식을 잘하구요..
    함수내에서 체크박스를 인식하게 하는 다른 방법이 있는지 궁금합니다.
    감사합니다.

    • 킴영감 2018.11.09 16:33 신고  댓글주소  수정/삭제

      음...이부분을 따로 강의로 다뤄야 겠군요...^^
      오토핫키의 일관성 없는 부분때문에 발생하는 문제입니다.^^
      간단하게 해결할 수 있구요~
      Gui, show 아래에 아래처럼 체크박스에 할당된 변수들을 global로 선언해주시면 됩니다.^^
      global 체크박스변수

    • 로오던 2018.11.09 19:36  댓글주소  수정/삭제

      아.. 해결됫습니다.ㅎㅎ 감사합니다.
      시간이 없어서 유튜브는 아직 못보고 있는데 유튜브에 강의 내용이 있나 궁금하네요 ㅎㅎ
      시간 되면 유튜브 보면서 더 공부하겟습니다. 감사합니다.

  • 넘버원드리블러 2019.07.05 11:09  댓글주소  수정/삭제  댓글쓰기

    혹시 주석을 달때 여러줄을 한번에 하는 명령어는 뭔가요? c에서 /* */ 이런거요

  • 2019.09.07 04:57  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다