やること
以前、ハードウェアチートはソフトウェアチートよりもバレにくい、などと不届きなことを申していました。
今回はソフトウェアチートの例としてもぐらたたきゲームを自動化でプレイしてみます。なお、必ずゲームの利用規約を読み、不正なスコア登録などされないようにご注意ください。
実行環境
今回は画面操作があるため、Google ColabやJupyter Notebookなどのクラウド的な環境ではできないはずです。
WinPython3.6をおすすめしています。
pyautoguiもpipしておきましょう。
pip instal pyautogui
もぐらたたきゲーム
こちらのブラウザゲームを使用させていただきます。画像処理の負荷を下げるため、ブラウザのズーム率を67%くらいまで下げました。もっと下げても問題ないと思います。
サンプル画像、テンプレート画像の作成
とりあえずゲームを開始して、材料がたくさん映っている場面をスクリーンショットします。サイズはぴったりでなくても大丈夫です。これをサンプル画像とします。png等の可逆圧縮形式で保存すると良いです。
ペイントで認識したい部分をトリミングします。これをテンプレート画像とします。
キャプチャテスト
pyautogui を使ってゲーム画面をキャプチャしてみます。ゲーム領域の数字は各自の環境に合わせて調整してください。ウィンドウのスナップ機能で毎回同じ座標に固定すると便利です。
import cv2
import numpy as np
from copy import deepcopy
import matplotlib.pyplot as plt
import pyautogui
#表示関数
def show(img):
plt.imshow(img, vmin=0, vmax=255)
plt.show()
#プレイエリアキャプチャ
def get_screen(region):
img = pyautogui.screenshot(region=region) #PIL系でキャプチャされる
return np.array(img, 'uint8') #cv2系で返す
#ゲーム領域
play_area = (610, 160, 450, 350) #x, y, +x, +y
#キャプチャテスト
img = get_screen(play_area)
show(img)
ゲーム領域はぴったりでなくても大丈夫です。安全に少し大きめにするか、あるいは認識したいものが狭い領域にしかなければもっと小さくしても大丈夫です。
テンプレートマッチテスト
サンプル画像とテンプレート画像を読み込みます。
#サンプル画像
sample = cv2.imread('14-31_sample.png')
sample = cv2.cvtColor(sample, cv2.COLOR_BGR2RGB) #RGB
show(sample)
#テンプレート画像
red = cv2.imread('14-31_red.png')
red = cv2.cvtColor(red, cv2.COLOR_BGR2RGB)
show(red)
yellow = cv2.imread('14-31_yellow.png')
yellow = cv2.cvtColor(yellow, cv2.COLOR_BGR2RGB)
show(yellow)
OpenCV を使ってマッチする座標を取得します。
#テンプレートマッチして中心座標を返す
def template_match(img, temp, visible=False):
#類似度
res = cv2.matchTemplate(img, temp, cv2.TM_CCOEFF_NORMED)
#当たり座標 [[x0, y0], [x1, y1], ...]
threshold = 0.95
pos = np.array(np.where(res>=threshold)).T[:, ::-1]
print(pos)
#テンプレートの高さ、幅を取得
h, w = temp.shape[:2]
#枠をつけて表示
if visible:
img_copy = deepcopy(img)
for x, y in pos:
cv2.rectangle(img_copy, (x, y), (x+w, y+h), (255, 0, 0), 2)
show(img_copy)
#当たりの中心座標を返す
pos[:, 0] += w // 2
pos[:, 1] += h // 2
return pos
#サンプル画像とのテンプレートマッチテスト
pos = template_match(sample, red, visible=True)
pos = template_match(sample, yellow, visible=True)
[[269 100]
[193 170]
[194 170]
[117 241]]
[[268 30]
[269 30]
[193 100]
[194 100]
[268 170]
[269 170]
[ 41 241]
[268 241]
[269 241]]
printされているのは「サンプル画像内のマッチ部分のx, y座標」です。いまマッチングのしきい値は0.95としていて赤もぐらは4箇所判定されています。しかし表示された矩形は3つだけなので、どれかは近い座標が二重で判定されて矩形が重なっています。黄もぐらも同様に重複判定があります。
重複判定された部分は複数回クリックすることになってしまうので、できれば避けたいです。しきい値を少し上げて過不足なく判定されるように調整します。0.97くらいでいい感じになりました。ここがうまくいかない場合はテンプレート画像を変えるなど工夫が必要です。
本番
無限ループで、キャプチャ→判定→全部クリックを繰り返します。クリックする座標はPC画面の座標のため、「キャプチャ画像内のマッチx, y座標」に下駄を履かせる必要があることに注意です。
プログラムを実行してからゲームスタートをクリック。ゲーム中はマウスを奪われているのでプログラムの停止が難しいです。強制停止については注釈参照。遊び終わったら忘れずにプログラムを停止してください。
#本番コード
"""
無限ループなので手動停止すること
強制停止は、Ctrl+Alt+Del → 青い画面で「キャンセル」 → キャプチャエラーで停止
"""
while 1:
#キャプチャ
img = get_screen(play_area)
#赤もぐらのテンプレートマッチ
pos_red = template_match(img, red)
#黄もぐらのテンプレートマッチ
pos_yellow = template_match(img, yellow)
#座標を合体
pos = np.vstack([pos_red, pos_yellow])
#座標に下駄
pos[:, 0] += play_area[0]
pos[:, 1] += play_area[1]
#全てクリック
for x, y in pos:
print('(x, y)=({}, {})'.format(x, y))
pyautogui.click(x, y)
おわりに
pyautogui は右クリックやキー入力もできるのでいろいろなゲームに対応できます。
余談ですが、pyautogui の画面キャプチャは十分速いですが、激しいアクションゲームをするには少し不足かもしれません。その場合はより高速な画面キャプチャ方法があるので、キャプチャはそれ、クリックと入力は pyautogui と役割を分けるのが良いと思います。