日記とか、工作記録とか

自分に書けることを何でも書いてゆきます。作った物、買ったもの、コンピュータ系の話題が多くなるかもしれません。

Raspberry pi + gamepad (XBOXコンントローラー)で画面表示

前回の続きで、ゲームパッドの各ボタンの状態を読み取るサンプルプログラムを作っていきたいと思います。ゲームパッドは、前回と同じくxboxコントローラです。
f:id:WindVoice:20160417090647j:plain

windvoice.hatenablog.jp

ボタンの種類

私の手元にはxboxのコントローラがあるので、これを使う前提で書きますが、ゲームパッドには性質が異なるボタンがいくつもあり、それぞれデータの取り方が変わります。以下、各ボタンの名前と位置は公式サイトに説明がありますのでこれを見ながら説明します。

support.xbox.com

ジョイスティック(軸:axis)

まず、ジョイスティック。よくキャラクタを動かしたり(右スティック)、カメラ視点を移動する(左スティック)のに使われます。すべての方向に、わずかな傾きも検出できます。プログラム的にはどんな仕組みでできているのかというと、ジョイスティックの状態を、左右方向、上下方向にそれぞれ-1から1までの少数値で取得することができます。

つまり、スティックを上方向に完全に倒すと、上下方向のデータが-1になり、下方向に完全に倒すと1になります。半分だけ倒せば0.5になります。スティックに触れない状態は0です。左右方向も同様です。xboxコントローラのジョイスティックは押し込む動作も可能ですが、これはあとで説明するボタンの一種として扱われています。

ところで、ジョイスティックとは書きましたが、プログラムの中では「軸(axis)」と呼ばれます。そして、左右の人差し指で押すボタン(LTとRT)も、押し込みの強さによって強弱をつけることができるので、軸の一種として扱われています。結局、合計6個のデータが取れることになります。順番は右スティック左右、右スティック上下、LT、左スティック左右、左スティック上下、RT、の順です。

ボタン

ボタンの仕組みは説明不要と思います。プログラム的には、押されている(1)、押されていない(0)の二値データとして取得することができます。データは配列として取得できるのですが、順番はA、B、X、Y、LB、RB、Back、Start、ガイドボタン、左スティックを押す、右スティックを押す、 の順です。

ハットスイッチ

ハットスイッチというのは、あまり聞きなれない用語なのですが、飛行機の操縦桿で、スティックの上についている小さなボタンのことを言うのだそうです。xboxコントローラでは、方向パッドがこれに当たります。上下方向で1データ、左右方向で1データとなっていて、-1, 0, 1の3種類の値を取得できます。

悩ましいのはデータの示す意味で、ジョイスティックとは逆に、上が押されていると1、下が押されていると-1になります。右は1、左は-1です。

プログラムの仕組み

プログラムの要素としては、ゲームパッドからデータをとる部分と、取れたデータを画面に表示する部分、という大きく2つの要素があります。前回に引き続き、pygameというモジュールを使います。

ゲームパッドのデータの取り方

pygameモジュールでは、ゲームパッドの状態の変化をイベントという形で取得できます。イベントが発生していれば何らかの操作が行われたということなので、ゲームパッドの状態を確認して、それをゲーム内の動作に反映することになります。

whileループが無限に繰り返される部分を作り、イベント発生の有無を取得、イベントがあれば取得した値を画面に表示します。

画面表示

pygameにはゲーム画面を操作するための仕組みも含まれています。ごく初歩的な部分だけですが、このサンプルでも使ってみています。ゲーム画面は「パラパラマンガ」的な仕組みで動いています。つまり、まずある1つの画面(静止画)を描画して、それをウインドウに表示、次に少しだけ変化した画面を描画して、それを前の画面と置き換え…… というように。

画面をプレイヤーに見せながら書き換えると、書き換え途中の画面が見えてしまうため、全体にチラチラして見づらくなります。そこで、データ的には2画面分を用意しておき、そのうち1つの画面を表示させ、その「裏」で次の画面を描画し、描画が終わったら「パラッ」と差し替える(flip)という操作をします。ダブルバッファリングという手法です。

数値をテキストとして表示するのですが、これは表示する場所を座標として指定して、文字をレンダリングする、という手順になります。

サンプル

さて、そんなわけで作ってみた動作確認のためのプログラムが以下です。

pi@raspberrypi:~/gamepad$ cat gamepaddemo.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import os
import pygame
from pygame import locals
import time

# 初期化
pygame.init()
pygame.joystick.init()

# ゲームパッドを認識しているか?
if pygame.joystick.get_count() == 0:
	print "ゲームパッドがありません。"
	sys.exit("終了")
else:
	print "ゲームパッドが" + str(pygame.joystick.get_count()) + "個見つかりました。"

# 最初の一個だけ初期化
joystick = pygame.joystick.Joystick(0)
joystick.init()

# ゲームパッドは6軸11ボタン
n_ax = joystick.get_numaxes()
n_bu = joystick.get_numbuttons()
n_ha = joystick.get_numhats()

# 800x600のウインドウを用意する
size = width, height = 600, 400
black = 0, 0, 0
screen = pygame.display.set_mode(size)
pygame.display.set_caption("gamepad demo")
os.environ['SDL_VIDEO_WINDOW_POS'] = str(0) + "," + str(0)

# フォント
font = pygame.font.Font(None, 30)

# 状態データの保管場所
axis   = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
button = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
hat    = [0, 0]

# 軸は左親指の左右、上下、左中指、右親指の左右、上下、右中指
axispos   = [ ( 10,150), ( 90,150), (170,150), (250,150), (330,150), (410,150)]

# ボタンは順に、A, B, X, Y, LB, RB, Back, Start, xboxガイドボタン、
# 右スティック押し込み、左スティック押し込み
buttonpos = [ ( 30,200), ( 50,200), ( 70,200), ( 90,200), (110,200),
              (130,200), (150,200), (170,200), (190,200), (210,200),
              (230,200) ]

# ハットスイッチ
# いわゆる十字ボタンのこと。左右、上下で二つデータがある。
# 左または下が押されると-1、右または上が押されると1になる。
# 上下が不自然な仕様になっている。
hatpos    = [ ( 30, 250), ( 50, 250) ]

# ここから無限ループ
while 1:
	# ゲームパッドのイベントを確認する
	event = False
	for e in pygame.event.get():
		event = True
		if e.type == pygame.locals.JOYAXISMOTION:
			# 軸
			for a in range(6):
				axis[a] = joystick.get_axis(a)
		elif e.type == pygame.locals.JOYBUTTONDOWN or e.type == pygame.locals.JOYBUTTONUP:
			# ボタン
			for b in range(11):
				button[b] = joystick.get_button(b)
		elif e.type == pygame.locals.JOYHATMOTION:
			# ハットスイッチ(十字ボタン)
			hat[0], hat[1] = joystick.get_hat(0)

	if event == False:
		# 100分の1秒だけスリープする。CPUの利用率がこれでかなり下がる。
		time.sleep(0.01)
		continue

	# 画面更新の準備。まずは真っ暗なスクリーンを用意。
	screen.fill(black)

	# 軸の情報を表示
	for a in range(6):
		label = font.render("%+5.3f" % ( axis[a] ), 1, (225,225,225))
		screen.blit(label, axispos[a])

	# ボタンの情報を表示
	for b in range(11):
		if button[b] == 0:
			label = "-"
		else:
			label = "*"
		label = font.render(label, 1, (225,225,225))
		screen.blit(label, buttonpos[b])

	# ハットスイッチの情報を表示
	for h in range(2):
		label = font.render("%d" % ( hat[h] ), 1, (225, 225, 225))
		screen.blit(label, hatpos[h])
	
	# 描画を切り替える
	pygame.display.flip()

pi@raspberrypi:~/gamepad$ 

動作している様子を撮影したのが下の動画です。ところどころピントが合っていなくて申し訳ないですが、雰囲気はつかんでもらえると思います。動画の取り方ももう少し覚えないといけませんね。
youtu.be

Raspberry Pi3 Model B (Element14)

Raspberry Pi3 Model B (Element14)