【RaspberryPi】Minecraft Pi Editionのコントローラーとしてmicro:bitを使えないか考えてみる【中編:PyAutoGUI】

前回の続きのエントリーとなります。今回はコントローラーの部分を作っていこうと思います。 ゲームのアプリケーションとの連携になるので、キーボードやコントローラーなどの操作をpythonから操作するものを 調べて使ってみようと思います。

【参考】

uepon.hatenadiary.com

micro:bitからの操作をBluetoothで受信して、そのデータに従って操作制御を行うという形になります。

Minecraft Pi Editionの操作は、キーボードとマウスの組み合わせのみになるので、これらをpython上から制御できるライブラリが必要になります。

いろいろ探してみると、キーボードとマウスの両方が制御できるメジャーなパッケージであるPyAutoGUIがヒットします。 機能的にRPAに便利そうな制御を使用できることから、書籍などでも紹介されるメジャーなライブラリです。その他の内容も含めて以下の書籍は超おすすめです。

デジタル書籍は以下から

www.oreilly.co.jp

こちらのPyAutoGUIを中心に動作テストをしてみたいと思います。

PyAutoGUIのインストール

PyAutoGUIは以下を参照にします。

f:id:ueponx:20200318013104p:plain

pypi.org

github.com

PyAutoGUIはpython2系もpython3系でも使用可能ですが、今回はpython3系を使用するので以下のようにpip3インストールを行います。 インストールと同時に依存性のあるパッケージも同時にインストールされます。

【インストールコマンド】

$ pip3 install PyAutoGUI

【ログ】

$ pip3 install PyAutoGUI
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting PyAutoGUI
  Downloading https://www.piwheels.org/simple/pyautogui/PyAutoGUI-0.9.48-py3-none-any.whl
Collecting pymsgbox (from PyAutoGUI)
  Downloading https://www.piwheels.org/simple/pymsgbox/PyMsgBox-1.0.7-py3-none-any.whl
Collecting mouseinfo (from PyAutoGUI)
  Downloading https://www.piwheels.org/simple/mouseinfo/MouseInfo-0.1.2-py3-none-any.whl
Collecting PyTweening>=1.0.1 (from PyAutoGUI)
  Downloading https://www.piwheels.org/simple/pytweening/PyTweening-1.0.3-py3-none-any.whl
Collecting pygetwindow>=0.0.5 (from PyAutoGUI)
  Downloading https://www.piwheels.org/simple/pygetwindow/PyGetWindow-0.0.8-py3-none-any.whl
Collecting pyscreeze>=0.1.21 (from PyAutoGUI)
  Downloading https://www.piwheels.org/simple/pyscreeze/PyScreeze-0.1.26-py3-none-any.whl
Collecting python3-Xlib; platform_system == "Linux" and python_version >= "3.0" (from PyAutoGUI)
  Downloading https://www.piwheels.org/simple/python3-xlib/python3_xlib-0.15-py3-none-any.whl (110kB)
    100% |████████████████████████████████| 112kB 176kB/s 
Requirement already satisfied: Pillow>=5.2.0; python_version == "3.7" in /usr/lib/python3/dist-packages (from mouseinfo->PyAutoGUI) (5.4.1)
Collecting pyperclip (from mouseinfo->PyAutoGUI)
  Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))': /simple/pyperclip/
  Downloading https://www.piwheels.org/simple/pyperclip/pyperclip-1.7.0-py3-none-any.whl
Collecting pyrect (from pygetwindow>=0.0.5->PyAutoGUI)
  Downloading https://www.piwheels.org/simple/pyrect/PyRect-0.1.4-py2.py3-none-any.whl
Installing collected packages: pymsgbox, python3-Xlib, pyperclip, mouseinfo, PyTweening, pyrect, pygetwindow, pyscreeze, PyAutoGUI
Successfully installed PyAutoGUI-0.9.48 PyTweening-1.0.3 mouseinfo-0.1.2 pygetwindow-0.0.8 pymsgbox-1.0.7 pyperclip-1.7.0 pyrect-0.1.4 pyscreeze-0.1.26 python3-Xlib-0.15

こちらでインストールは完了です。このライブラリはXを前提としているのでX上のコンソールなどで動かさないと起動時に以下のようなエラーが発生するので注意が必要です。

Traceback (most recent call last):
  File "pgui.sample.py", line 1, in <module>
    import pyautogui
  File "/home/pi/.local/lib/python3.7/site-packages/pyautogui/__init__.py", line 94, in <module>
    import mouseinfo
  File "/home/pi/.local/lib/python3.7/site-packages/mouseinfo/__init__.py", line 149, in <module>
    _display = Display(os.environ['DISPLAY'])
  File "/usr/lib/python3.7/os.py", line 678, in __getitem__
    raise KeyError(key) from None
KeyError: 'DISPLAY'

また、コンソールなどでのキー入力を行う場合にはウエイトなどは必要はないのですが、長押しなどの操作がある場合にはウエイト(sleep)が必要になるので import timeを追加することが多いかと思います。

PyAutoGUIをつかってみる

今回必要になるのはキーボード操作とマウス操作の2系統になりますのでそれぞれを考えてみることにします。

キーボード入力

Minecraftで使用するキーボード操作は以下のようなものになります。すべては網羅していませんが、こんなところかなと思います。

操作キー アクション
前へ
s 後ろへ
a 左へ
d 右へ
SPACE ジャンプ 2回押しで飛行へ、飛行中は高度上昇
SHIFT しゃがみ(スニーク) 飛行中は高度降下
e インベントリを開く(持ち物を開く)
1 ~ 9 画面中央下部から、数字の位置に該当するアイテムを選択
q 選択中のアイテムを投げる(投げるのは1つ)
ESC メニュー画面を開く

これらを操作することになります。

PyAutoGUIでキーボード操作は以下を使用します。

メソッド 使用例 意味
typewrite() pyautogui.typewrite('Hello world!\n', interval=0.25) Hello world!を出力(文字間に0.25秒のインターバル)
keyDown() pyautogui.keyDown('shift') シフトキーを押しっぱなしにする(処理後も押されている状態へ
keyUp() pyautogui.keyUp('shift') シフトキーを放す(処理後も押していない状態へ)
press() pyautogui.press('esc') ESCキーを押す
write() pyautogui.write('Hello world!', interval=0.25) Hello world!を出力(文字間に0.25秒のインターバル)
hotkey() pyautogui.hotkey('ctrl', 'c') CTRL+Cを押す(Windowsであればコピー)

また、PyAutoGUIで使用可能なキー文字列(引数可能な)は抜粋すると以下のようになっています。

キー文字列 対象のキー
a, A, 1, @ など 各キーの文字
enter, return, \n Enter
esc Esc
shiftleft, shiftright 左右のShift
altleft, altright 左右のAlt
ctrlleft, ctrlright 左右のCtrl
tab, \t Tab
backspace バックスペース
delete デリート
pageup, pagedown ページアップ・ページダウン
up, down, left, right 上下左右
f1, f2, f3 など ファンクションキー(F1, F2など)
capslock CapsLock
printscreen Print Screen
winleft, winright 左右のWindowsキー

ここまでを抑えて処理を考えてみます。以下の処理では操作時のウインドウをアクティブにすることは行っていないので、 処理開始後にWaitを3秒入れてその間にマウスを動かしてアクティブにしてください。

#!/usr/bin/env python3
import pyautogui
import time

# このウエイト間にウインドウをアクティブにすること
time.sleep(3)

# 前進
pyautogui.keyDown('w')
time.sleep(0.153846)
pyautogui.keyUp('w')

time.sleep(2)

# 右へ
pyautogui.keyDown('d')
time.sleep(0.153846)
pyautogui.keyUp('d')

time.sleep(2)

# ジャンプ
pyautogui.keyDown('space')
time.sleep(0.15)
pyautogui.keyUp('space')

time.sleep(2)

# 持ち物をチェンジ
pyautogui.keyDown('2')
time.sleep(0.15)
pyautogui.keyUp('2')

time.sleep(2)

# 処理が終わったらTABキーを押して、ウインドウを非アクティブ化する
pyautogui.press('tab')

基本的には、keyDown()keyUp()の組み合わせしか使っていません。というのも、移動に関しては基本長押しができることもあるので、その後のtime.sleep()でキーを押している調整しています。 サンプルコードのtime.sleep()の引数に0.153846という数値を入れていますが、この値にすることでだいたい一歩(1ブロック分)の移動ができるようです。(この時間は色々移動させて計算しました)特に一歩という単位にこだわりがないのであれば、おおよその値を入れておけばいいかなと思います。

ただ、長押しが関係ない操作の場合にはtypewrite()を使っても大丈夫です。(例えばチャットでメッセージを送る場合やコマンドを送信する場合など)

Minecraftで使用する操作としては

  • typewrite()
  • keyDown()
  • keyUp()
  • hotkey()

などを組み合わせることでコントロールできます。でも、ウエイトの時間の計算などはちょっと面倒ですね。

マウス入力

Minecraftではマウスの操作で視界方向を操作したり、クリックでブロックを壊したりすることができます。 PyAutoGUIでもマウス操作を行うことができます。以下まとめておきます。

ちなみにマウスの座標系は画面の左上のピクセルを原点として以下の画像の用になっています。

f:id:ueponx:20200318012643p:plain

Minecraftの座標系では水平面はx-z座標系になっているので、混乱しないように注意が必要です。

メソッド 使用例 意味
position() currentMouseX, currentMouseY = pyautogui.position() 現在のマウスカーソルの座標の取得
moveTo() pyautogui.moveTo(100, 150) マウスカーソルの移動。x=100、y=150の位置へ移動
moveRel() pyautogui.moveRel(300, 400) マウスの相対移動。現在のマウスカーソルの座標からx=300、y=400の位置へ移動
click() pyautogui.click(200, 220)

pyautogui.click(clicks=3, interval=0.5)
マウスクリック(指定座標でのクリックも可能)

0.5秒間隔でのトリプルクリック
doubleClick() pyautogui.doubleClick() マウスのダブルクリック
dragTo() pyautogui.dragTo(100, 200, 3, button='left') マウスの左ボタンを押しながら3秒かけてx=100、y=200の位置へドラッグ
dragRel() pyautogui.dragRel(-50, 0, 3, button='left') マウスの左ボタンを押しながら3秒かけて現在のマウスカーソルの座標からx=-50、y=0の位置へドラッグ
mouseDown() pyautogui.mouseDown(button='right') マウスの右ボタンを押下(し続ける)
mouseUp() pyautogui.mouseUp(button='right') マウスの右ボタンを放す
vscroll() pyautogui.vscroll(-10) マウスによる垂直方向-10(ピクセル?)スクロール
hscroll() pyautogui.hscroll(20) マウスによる水平方向20(ピクセル?)スクロール

基本的にはマウスの移動とクリック、ボタンの長押しぐらいができれば操作できますが、マウスの移動はそこまで必要ではありません。

アクション 内容
マウス(カーソル)の移動 視点移動(カメラ操作)
左クリック ブロックを壊す(長押し)/ 攻撃する

インベントリ(持ち物)内のアイテムをダブルクリックをすれば、同一アイテムをまとめられる
右クリック ブロックを配置する

アイテムを使う(食べる・飲む)

ドアの開閉

弓を放つ

乗り物に乗る

作業台・チェストなど特殊ブロックを使用など
センターホイール アイテム切り替え
#!/usr/bin/env python3
import pyautogui
import time

time.sleep(3)

#マウス現在位置取得
x,y = pyautogui.position()
pyautogui.click()

# 前進
pyautogui.keyDown('w')
time.sleep(0.153846)
pyautogui.keyUp('w')
time.sleep(2)

# ジャンプ
pyautogui.keyDown('space')
time.sleep(0.15)
pyautogui.keyUp('space')
time.sleep(2)

#現在位置からの相対移動、かける時間(duration)を秒で指定は同じ
# pyautogui.moveRel(0, 5, duration=1)

# 壊す
pyautogui.mouseDown(button='left')
time.sleep(0.5)
pyautogui.mouseUp(button='left')
time.sleep(2)

#マウスホイール(手にもつアイテムを1つ右へ)
pyautogui.scroll(-1, x, y)
time.sleep(0.3)

# アイテムを使う
pyautogui.mouseDown(button='right')
time.sleep(0.3)
pyautogui.mouseUp(button='right')

time.sleep(2)

pyautogui.press('tab')

【操作前:実行前は水平な視点にしていても…】

f:id:ueponx:20200318011304p:plain

【操作後:実行後は視点が真下を向いている】

f:id:ueponx:20200318011316p:plain

実際に実行して、マウスカーソルの移動を行うと、キャラクターの視点が真下を向いてしまい、最後にマウスの右クリックで少しでアイテムを使用する処理が視点が真下であるために使用できなくなってしまいます。これでは操作としては今一つの印象です。この操作がそもそも必要なのかに関しては検討の余地はあるかもしれませんが。

他の制御手段としてMinecraft PI Editionを操作するライブラリであるMCPI( Minecraft: Pi edition API Python Library)があります。そちらと併用することで何らかの解決手段はあるのかもしれません。ちなみにMCPIのソースを追ってみたのですが、Minecraft Pi Editionではキャラクターの向きや方向を設定するAPIが無いようです。※MCPIについては【後編】で取り上げます。PyAutoGUIではマウスのボタンを操作するだけのほうが良さそうな感じです。

おわりに

PyAutoGUIを使うことでMinecraftPython上から動かすことができました。ただ、これではあまりにもコントロールが微妙な部分が多いので、Minecraftを操作するライブラリであるMCPI( Minecraft: Pi edition API Python Library)を組合わせてみようと思います。また、最後ということでmicro:bitからのBluetooth制御も行ってみたいと思います。

【参考】

uepon.hatenadiary.com

/* -----codeの行番号----- */