Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
11
Help us understand the problem. What is going on with this article?
@safin

PythonでWindows APIを使ったマウス操作

きっかけ

去年の終わり頃にTwitterで某FPSゲームのアンチリコイルマクロを紹介した動画を見まして、マクロの地道な調整は大変だろうな不正ツールに近いと思ったのですが同時に、予め設定された動きをゲーム内で実行できれば何か面白いことができるのではないかと閃いたわけです。

試行錯誤

Pythonではマウスとキーボード操作をするPyAutoGUIやpynputなどのライブラリがあり、これらを利用することで簡単に操作、自動化ができます。しかし、私が遊んでいるゲームは操作を受け付けませんでした。DirectXが絡んでる?
色々検索すると、Serpent.AIというAIにゲームをさせる代物を発見しました。Serpent.AIはWin32APIでゲームの操作をしているようで、私が遊んでいるゲームはWin32APIで操作できました。

環境

  • Windows 10 64bit
  • Python 3.7.4

コードを書く

Win32APIでマウスを操作するには2つの方法があります。
1.SetCursorPosを使う
2.SendInputを使う

1.SetCursorPosを使う

PythonからWin32APIを呼び出すために標準ライブラリのctypesを使います。

input_1.py
import ctypes

#SetCursorPos(x, y)
ctypes.windll.user32.SetCursorPos(200, 300)

x,y (横,縦)で指定するだけです。(0,0)が画面左上で右下に向かって値が大きくなります。
このコードではx:200 y:300にカーソルを移動させます。
しかし、SetCursorPosはゲームの操作ができません。

2.SendInputを使う

マウスカーソルの移動、クリック、キー入力などができます。
詳しい使い方はこちらを見てください。
コードはこれこれを参考にさせていただきました。

SendInputに必要なINPUT構造体と、マウス操作の情報を入れるMOUSEINPUT構造体を宣言します。

input_2.py
import ctypes

ULONG_PTR = ctypes.POINTER(ctypes.c_ulong)

# マウスイベントの情報
class MOUSEINPUT(ctypes.Structure):
    _fields_ = [("dx", ctypes.c_long),
                ("dy", ctypes.c_long),
                ("mouseData", ctypes.c_ulong),
                ("dwFlags", ctypes.c_ulong),
                ("time", ctypes.c_ulong),
                ("dwExtraInfo", ULONG_PTR)]


class INPUT(ctypes.Structure):
    _fields_ = [("type", ctypes.c_ulong),
                ("mi", MOUSEINPUT)]

SendInputの引数と戻り値の型を指定します。

input_2.py
LPINPUT = ctypes.POINTER(INPUT)

SendInput = ctypes.windll.user32.SendInput
SendInput.argtypes = (ctypes.c_uint, LPINPUT, ctypes.c_int)
SendInput.restype = ctypes.c_uint

座標を変換してSendInputに突っ込めば、マウスカーソルが動きます。

input_2.py
x, y = 200, 300
x = x * 65536 // 1920
y = y * 65536 // 1080
_mi = MOUSEINPUT(x, y, 0, (0x0001 | 0x8000), 0, None)
SendInput(1, INPUT(0, _mi), ctypes.sizeof(INPUT))

詳しい説明は長くなるのですが、
この2行ではカーソルの座標(200,300)を0~65,535の座標(6826,18204)に変換しています。
画面解像度によって除数は変わります。
割り算は切り捨てにしていますが四捨五入と大差ないので気にしません。
後述しますが、絶対座標での移動にのみ必要です。

input_2.py
x = x * 65536 // 1920 # x * 65536 // 画面解像度(横)
y = y * 65536 // 1080 # y * 65536 // 画面解像度(縦)

ホイールの回転量は120で標準的なマウスの1ノッチです。120の倍数以外の整数でも問題ありません。横スクロールもできます。
マウスイベントはビット演算子の|演算子で複数設定できます。
よく使うものを表にまとめました。
詳細はこちら

意味
0x8000 絶対座標を使う、指定しない場合は相対座標
0x0001 カーソルを移動させるか
0x0002 左ボタンを押す
0x0004 左ボタンを離す
0x0008 右ボタンを押す
0x0010 右ボタンを離す
0x01000 横スクロールするか
0x0800          縦スクロールするか

ここで一つ注意が必要です。絶対座標を使うか、相対座標を使うかで座標の形式が変わります。
絶対座標を使う場合は0~65,535の座標に変換する必要がありますが、相対座標を使う場合は座標の変換は必要ありません。今回の場合は(200,300)のままです。

input_2.py
# MOUSEINPUT(x座標, y座標, ホイールの回転量, マウスイベント, 0, None)
_mi = MOUSEINPUT(x, y, 0, (0x0001 | 0x8000), 0, None)

入力の種類は3つあり、下表のようになっています。

意味
0 マウス入力
1 キーボード入力
2 ハードウェア入力
input_2.py
# SendInput(1, INPUT(入力の種類, _mi), ctypes.sizeof(INPUT))
SendInput(1, INPUT(0, _mi), ctypes.sizeof(INPUT))

まとめ

SendInputではマウス操作の他にキーボード操作もできますが、簡略化のためマウス操作に絞りました。
次回があれば、キーボード操作を含めた説明にするつもりです。

最後に

この記事を読まれている方には関係ないかと思いますが、プログラム自体はかなり前に完成しています。
プログラムを書いている時は動けばヨシと深く考えずにやっていたので、後から見返すとトンデモナイことになっていました。コメントは入れてました。
なので、整理ついでにプログラムの要を記事にしました。
こうして記事にすると忘れないようになりますね。

11
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
safin
Pythonで色々やってます。メモ書きを公開できるレベルに編集して投稿しているだけです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
11
Help us understand the problem. What is going on with this article?