Sense HAT編、第3回目はジョイスティック編です!
過去記事はこちら↓からどうぞ!
第45回「ラズベリーパイ専用アドオンボード Sense HATで遊ぼう!(2)6種類のセンサ編」
第44回「ラズベリーパイ専用アドオンボード Sense HATで遊ぼう!(1)LEDディスプレイの操作」
さて、宇宙で使うことを想定して作られているSense HATは、入力装置として「ジョイスティック」が搭載されています。
Sense HATの表面の、ラズベリーのイラストの右隣にあるこの部品がジョイスティックです。バーが短いので一見わかりにくいのですが、指で押してみるとカチッと倒れる感触があります(意外と固いです!)。ゲーム機のコントローラーなどで見かけることが多いですね。
今回はこのジョイスティックからの入力を、Python側で受け取る方法をご紹介します。LEDディスプレイと連動させたゲーム作りにも挑戦しました!
ジョイスティックの入力値について
THE JOYSTICK – Raspberry Pi Learning Resources
The Sense HAT joystick is mapped to the four keyboard cursor keys, with the joystick middle-click being mapped to the Return key. This means that moving the joystick has exactly the same effect as pressing those keys on the keyboard. Remember that the down direction is with the HDMI port facing downwards.
ジョイスティックの使い方は、こちらのドキュメントにありました。A Guide to Astro Piとしてまとめられているドキュメントの1ページです。Sense HATの機能別の解説ページも揃っています。
ジョイスティックの向きは、正位置=HDMIポートが下向き、ということなので、
このような向きですね!
上下左右の4方向の他、まっすぐ押し込むこともできるので、合計5パターンの入力が可能です。
それではIDELを起動して、最初のサンプルソースを実行してみましょう!
/home/pi/nas/keyboad_mapping.py
import pygame from pygame.locals import * from sense_hat import SenseHat pygame.init() pygame.display.set_mode((640, 480)) sense = SenseHat() sense.clear() running = True while running: for event in pygame.event.get(): print(event) if event.type == QUIT: running = False print("BYE")
実行すると、「pygame window」という黒い画面が立ち上がります。これは、6~7行目で呼び出しているpygameライブラリの画面です。
Pygame – Wikipedia
pygame は、ビデオゲームを製作するために設計されたクロスプラットフォームのPythonモジュール集であり、Pythonでコンピュータグラフィクスと音声を扱うためのライブラリを含んでいる。
pygame.event
event – Pygameドキュメント 日本語訳
この命令を実行するとイベントキューから全てのイベント情報を取得し、取得されたイベントはイベントキューから削除されます。引数にイベントの種類が設定されていた場合は、指定した種類のイベント情報のみがイベントキューから取得されます。
プログラムの14行目の「pygame.event.get()」でイベントを監視し、15行目で内容を表示しています。
「pygame window」の上でマウスを動かしたり、キーボードのキーを押下したりすると、コンソール画面に「イベント」の内容が表示されます。同様に、Sense HATのジョイスティックからの入力イベントも検知されます。
図3は、ジョイスティック上、キーボード上キー、ジョイスティック右……というように、交互に時計回りに操作してみたところです。どちらも同じイベント内容が表示されているのが分かるでしょうか?
ジョイスティックは4方向の傾きだけでなく、まっすぐ押し込むことができます。これはキーボードのEnterキーと対応していました。
また、1回の入力操作につき「KYEDOWN」と「KEYUP」の2種類のイベントが発生しています。少し長押しするような操作をしてみると、イベント発生のタイミングが分かりやすいです。
このプログラムでは、ウィンドウを閉じようとすると「BYE」と表示して処理を終了させるようにプログラムされているため(16~18行目)、pygameのウィンドウ自体は閉じられません。終了させたい場合は、IDELのコンソール画面を閉じましょう。
ジョイスティックとLEDディスプレイを連動!
ジョイスティックの動きが分かったところで、LEDディスプレイと連動させてみましょう!
ジョイスティックを使って、光っている部分を動かすプログラムを組んでいきます。
必要な処理は大きく分けて2つ。
- 現在点灯しているLEDを消灯する
- 移動先のLEDを点灯させる
同時に2つの処理を行うことで、移動しているように見せることが可能ですね。
LEDを個別に制御するには、第44回「ラズベリーパイ専用アドオンボード Sense HATで遊ぼう!(1)LEDディスプレイの操作」で紹介した「set_pixel」関数が便利です。
set_pixel
Sets an individual LED matrix pixel at the specified X-Y coordinate to the specified colour.
第1引数はX座標(0~7)、第2引数はY座標(0~7)、第3~5引数には色を指定することができます。点灯と消灯は、次のように記述すればOKです。
#点灯(白) sense.set_pixel(0, 0, 255, 255, 255) #消灯(黒) sense.set_pixel(0, 0, 0, 0, 0)
LEDディスプレイの座標は、図5のように、左上を基準に0からはじまります。従って、ジョイスティックの操作とX座標、Y座標の変化は次のようになります。
ジョイスティックの操作 | 座標の変化 |
---|---|
上 | y-1 |
下 | y+1 |
左 | x-1 |
右 | x+1 |
座標はそれぞれ0~7の間となるように制御する必要があります。ちなみに、無効な座標が設定された場合は下記のようなラーが発生してしまいます。
ValueError: Y position must be between 0 and 7
このエラー回避にも対応したサンプルソースが公開されているので、早速実行してみましょう!
import pygame from pygame.locals import * from sense_hat import SenseHat pygame.init() pygame.display.set_mode((640, 480)) sense = SenseHat() sense.clear() running = True x = 0 y = 0 sense.set_pixel(x, y, 255, 255, 255) while running: for event in pygame.event.get(): if event.type == KEYDOWN: sense.set_pixel(x, y, 0, 0, 0) # Black 0,0,0 means OFF if event.key == K_DOWN and y < 7: y = y + 1 elif event.key == K_UP and y > 0: y = y - 1 elif event.key == K_RIGHT and x < 7: x = x + 1 elif event.key == K_LEFT and x > 0: x = x - 1 sense.set_pixel(x, y, 255, 255, 255) if event.type == QUIT: running = False print("BYE")
16行目までは初期処理です。最初は左上のLED(X座標0、Y座標0)が点灯した状態から始まります。
入力があると、まず、現在の位置のLEDをオフにします(21行目)。そして座標が有効範囲(0~7)であるかどうかを判定して、32行目で移動先のLEDを点灯させます(23~30行目)。無効な値が設定された場合(たとえば右端からさらに右に移動させようとした場合)、動きがないように見えますが、同じLEDの消灯・点灯が行われています。
ゲームを作ろう!
Sense HAT Pong | Raspberry Pi Learning Resources
動くボールをバーで打つ、なつかしのレトロゲーム、ポンゲームです。
こちらのドキュメントでは、キーボードの矢印キーを使って操作するプログラムが紹介されていますが、ジョイスティックで操作できるようにカスタマイズしてみたいと思います。
まずはカスタマイズ前のゲームを動かしてみましょう。完成版のプログラムソースは、8章の最後に掲載されています。
/home/pi/nas/pong.py
from time import sleep from sense_hat import SenseHat import curses import threading sense = SenseHat() sense.clear(0,0,0) screen = curses.initscr() screen.keypad(True) curses.cbreak() curses.noecho() y = 4 ball_position=[6,3] ball_speed=[-1,-1] def drawbat(): sense.set_pixel(0,y,255,255,255) sense.set_pixel(0,y+1,255,255,255) sense.set_pixel(0,y-1,255,255,255) def moveball(): global game_over while True: sleep(0.15) sense.set_pixel(ball_position[0],ball_position[1],0,0,0) ball_position[0] += ball_speed[0] ball_position[1] += ball_speed[1] if ball_position[1] == 0 or ball_position[1] == 7: ball_speed[1] = -ball_speed[1] if ball_position[0] == 7: ball_speed[0] = -ball_speed[0] if ball_position[0] == 1 and y-1 <= ball_position[1] <= y+1: ball_speed[0] = -ball_speed[0] if ball_position[0] == 0: break sense.set_pixel(ball_position[0],ball_position[1],0,0,255) game_over = True game_over = False thread = threading.Thread(target=moveball) thread.start() while not game_over: drawbat() key = screen.getch() sense.clear() if key == curses.KEY_UP: if y > 1: y -= 1 if key == curses.KEY_DOWN: if y < 6: y += 1 sense.show_message("You Lose", text_colour=(255,0,0)) screen.keypad(0) curses.nocbreak() curses.echo() curses.endwin()
3行目でインポートしている「curses」は、キーボードからの入力を検知するために使用しています。cursesはIDELでは動作しないようなので、コマンドラインから実行しましょう。
実行コマンド
python3 /home/pi/nas/pong.py
左側の白いバーを、キーボードの上下キーで動かして、青いボールをはね返します。返し損ねると、「You Lose」という文字が流れてゲームオーバーとなります。ボールは0.15秒間隔で移動していくのですが(26行目)、意外と速くてむずかしいです!
それでは、このプログラムを、ジョイスティックから操作できるように調整していきましょう。
/home/pi/nas/pong_joy.py
from time import sleep from sense_hat import SenseHat import pygame from pygame.locals import * import threading sense = SenseHat() sense.clear(0,0,0) pygame.init() pygame.display.set_mode((320, 240)) y = 4 ball_position=[6,3] ball_speed=[-1,-1] def drawbat(): sense.set_pixel(0,y,255,255,255) sense.set_pixel(0,y+1,255,255,255) sense.set_pixel(0,y-1,255,255,255) def moveball(): global game_over while True: sleep(0.5) sense.set_pixel(ball_position[0],ball_position[1],0,0,0) ball_position[0] += ball_speed[0] ball_position[1] += ball_speed[1] if ball_position[1] == 0 or ball_position[1] == 7: ball_speed[1] = -ball_speed[1] if ball_position[0] == 7: ball_speed[0] = -ball_speed[0] if ball_position[0] == 1 and y-1 <= ball_position[1] <= y+1: ball_speed[0] = -ball_speed[0] if ball_position[0] == 0: break sense.set_pixel(ball_position[0],ball_position[1],0,0,255) game_over = True game_over = False thread = threading.Thread(target=moveball) thread.start() while not game_over: drawbat() for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_UP and y > 1: sense.set_pixel(0,y+1,0,0,0) y -= 1 if event.key == K_DOWN and y < 6: sense.set_pixel(0,y-1,0,0,0) y += 1 sense.show_message("You Lose", text_colour=(255,0,0))
実行
python3 /home/pi/nas/pong_joy.py
pygameは、SSH接続やリモートデスクトップ接続ではうまく動作しないので、コマンドラインから直接実行するようにしましょう。
cursesライブラリをpygameライブラリに置き換え、ジョイスティックからの入力を受け取れるように変更しました(キーボードの上下キーも使用できます)。
主な変更点は、3~4行目のpygame呼び出し部分、10~11行目のpygameの初期処理、そして53~60行目の入力値の判定です。どれもこれまでに学んできた内容なので、簡単にカスタマイズできました!
ボールの動きが分かりやすいように、スピードを0.5秒に変えてみました。動きを遅くすると「sense.clear()」によるちらつきが目立ってしまったので(元ソース55行目)、必要な部分だけを書き換えるように修正しています。
まとめ
Sense HATのジョイスティックはボード上に搭載されているので、直接触れるというのが最大の特徴!ラズベリーパイの入力装置といえば外部接続が当たり前だったので、新鮮な印象がありました。また、GPIOポートからボタンを取り付けることもできるので、ディスプレイなしで使っていきたい方にはこちらもオススメです。
さらに今回はゲーム作りにも挑戦しました!
ディスプレイの小ささという制限はありますが、このLEDディスプレイだからこその味わいがあるように思います。スピードを変更できるようにしたり、スコアを出せるようにしたりなどがあると、よりゲームらしいものに仕上がりそうですね!