Programming Tips blog

テキトーにTIPSを紹介します

ラズパイ + Python + PySDL2でPlayStation3ゲームコントローラ(DualShock3)を扱う

留意事項

 PySDL2において、Joystick関係のクラスを使うと機種によりボタン等のインデックス(番号)が異なります。
 よって、DualShockを交換したときなどにインデックスが変わり、それに応じてプログラムの改変など、面倒くさいことになります。
 ですので、GameControllerクラスを使うのが適当かと思われます。

ハードウェア

 主なハードウェア材料は下記の通りです。

ソフトウェア

 主なソフトウェア材料は下記の通りです。

  • Raspberry Pi OS Bullseye Lite
  • Python 3.9
  • PySDL2 0.9.16
  • pysdl2-dll 2.28.2(64bit版の場合に必要)

PySDL2インストール

 まず、PySDL2をインストールします。

pip3 install -U PySDL2

PySDL2-dllインストール(64bit版場合のみ)

 pysdl2-dllをインストールします。

pip3 install pysdl2-dll

Pythonプログラム

インポート

 プログラムの、PySDL2関連のインポートは下記の通りです。

import ctypes
from sdl2 import *

クラス作成

 ゲームコントローラのクラスを作成します。

# ゲームコントローラクラス
class GameController:
	##########
	# クラス変数
	##########
	
	
	
	
	##########
	# コンストラクタ
	##########
	def __init__(self):
		# メンバ変数
		self.kill_flag = False	# プログラム終了フラグ
		self.axis_value = []	# 十字キー値
		self.button_value = []	# ボタン値(TrueかFalse)
		self.device_connected = False
		self.trigger = False	# L2、R2ボタンがデジタルのフラグ
		self.l2 = False			# L2がデジタルの場合のボタンの状態
		self.r2 = False			# R2がデジタルの場合のボタンの状態
		
		# SDL初期化
		SDL_Init(SDL_INIT_GAMECONTROLLER)
	
	
	##########
	# スタティックメソッド
	##########
	
	# イベント処理
	# 引数:なし
	# 返値:なし
	def Update(self):
		# イベントハンドラを取得する
		event = SDL_Event()
		# イベントがある間の処理??
		while SDL_PollEvent(ctypes.byref(event)) != 0:
			# ゲームコントローラデバイスを認識した場合
			if event.type == SDL_CONTROLLERDEVICEADDED:
				# デバイスをオープンする
				self.device = SDL_GameControllerOpen(event.cdevice.which)
				# デバイスIDを記録する
				self.device_id = event.cdevice.which
				# デバイス接続フラグを立てる
				self.device_connected = True
				
				# リスト領域確保と初期化
				# 十字キーについて
				for i in range(SDL_CONTROLLER_AXIS_MAX):
					self.axis_value.append(0)
				# ボタンについて
				for i in range(SDL_CONTROLLER_BUTTON_MAX):
					self.button_value.append(False)
				
				# L2、R2ボタンがON、OFFの場合
				mapping = SDL_GameControllerMapping(self.device).decode("utf-8")
				if (",lefttrigger:a12," in mapping) and (",righttrigger:a13," in mapping):
					self.trigger = True
				return
			# ゲームコントローラデバイスをはずした場合
			elif event.type == SDL_CONTROLLERDEVICEREMOVED:
				# デバイス接続フラグを下ろす
				self.device_connected = False
				# リスト領域の消去
				self.axis_value = []
				self.button_value = []
				self.trigger = False
				self.l2 = False
				self.r2 = False
				return
			# 十字キーを動かした場合
			elif event.type == SDL_CONTROLLERAXISMOTION:
				# -32768-数値-32768を-100-数値-100にマップして値を取得する。これによりそのままデューティー比として使用する。
				self.axis_value[event.caxis.axis] = int(event.caxis.value * 100 / 32768)
				return
			# ボタンを押した場合
			elif event.type == SDL_CONTROLLERBUTTONDOWN:
				self.button_value[event.cbutton.button] = True
				return
			# ボタンを離した場合
			elif event.type == SDL_CONTROLLERBUTTONUP:
				self.button_value[event.cbutton.button] = False
				return
			# L2ボタンを押した場合
			elif self.trigger and (event.cbutton.button == 8) and (event.type == 1539):
				self.l2 = True
				return
			# L2ボタンを離した場合
			elif self.trigger and (event.cbutton.button == 8) and (event.type == 1540):
				self.l2 = False
				return
			# R2ボタンを押した場合
			elif self.trigger and (event.cbutton.button == 9) and (event.type == 1539):
				self.r2 = True
				return
			# R2ボタンを離した場合
			elif self.trigger and (event.cbutton.button == 9) and (event.type == 1540):
				self.r2 = False
				return
			
			# 終了フラグがTrueの場合
			# 注:この処理をwhileの条件に入れてしまうと取得値がおかしくなる。(原因不明)
			if self.kill_flag:
				return
		sleep(0.01)
		return
	
	
	# プログラム終了フラグ設定
	# 引数:設定値
	# 返値:なし
	def SetKill(self):
		self.kill_flag = True
		return
	
	
	# 十字キー値設定
	# 引数:インデックス, 設定値
	# 返値:なし
	def SetAxisValue(self, index, value):
		self.axis_value[index] = value
		return
	
	
	# 十字キー値取得
	# 引数:インデックス
	# 返値:十字キー値
	def GetAxisValue(self, index):
		return self.axis_value[index]
	
	
	# ボタン値設定
	# 引数:インデックス, 設定値
	# 返値:なし
	def SetButtonValue(self, index, value):
		self.button_value[index] = value
		return
	
	
	# ボタン値取得
	# 引数:インデックス
	# 返値:ボタン値
	def GetButtonValue(self, index):
		return self.button_value[index]
	
	
	# ジョイスティックが接続されているかどうか
	# 引数:なし
	# 返値:True:接続されている, False:接続されていない、例外
	# 備考:正論理です
	def IsDeviceConnected(self):
		return self.device_connected
	
	
	# ジョイスティックが接続されていないかどうか
	# 引数:なし
	# 返値:True:接続されていない, False:接続されている、例外
	# 備考:負論理です
	def IsDeviceDisconnected(self):
		return not self.device_connected

マッピング

 PlayStation3ゲームコントローラ(DualShock3)とSDL2インデックスとのマッピングは下記の通りです。

左十字キーX軸 : SDL_CONTROLLER_AXIS_LEFTX
左十字キーY軸 : SDL_CONTROLLER_AXIS_LEFTY
右十字キーX軸 : SDL_CONTROLLER_AXIS_RIGHTX
右十字キーY軸 : SDL_CONTROLLER_AXIS_RIGHTY
L2 : SDL_CONTROLLER_AXIS_TRIGGERLEFT
R2 : SDL_CONTROLLER_AXIS_TRIGGERRIGHT

バツ : SDL_CONTROLLER_BUTTON_A
丸 : SDL_CONTROLLER_BUTTON_B
四角 : SDL_CONTROLLER_BUTTON_X
三角 : SDL_CONTROLLER_BUTTON_Y
SELECT : SDL_CONTROLLER_BUTTON_BACK
PlayStation(P3)ボタン : SDL_CONTROLLER_BUTTON_GUIDE
START : SDL_CONTROLLER_BUTTON_START
左十字キープッシュ : SDL_CONTROLLER_BUTTON_LEFTSTICK
右十字キープッシュ : SDL_CONTROLLER_BUTTON_RIGHTSTICK
L1 : SDL_CONTROLLER_BUTTON_LEFTSHOULDER
R1 : SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
上方向ボタン : SDL_CONTROLLER_BUTTON_DPAD_UP
下方向ボタン : SDL_CONTROLLER_BUTTON_DPAD_DOWN
左方向ボタン : SDL_CONTROLLER_BUTTON_DPAD_LEFT
右方向ボタン : SDL_CONTROLLER_BUTTON_DPAD_RIGHT

十字キーの軸の数 : SDL_CONTROLLER_AXIS_MAX
ボタンの数 : SDL_CONTROLLER_BUTTON_MAX

一部サードパーティ製 DualShock3について

DualShock3を認識しない

 /dev/input/js0のようにDualShock3を認識しない場合があります。
 その場合は/boot/cmdline.txtに下記を追記してみてください。

hid.ignore_special_drivers=1

ドキュメント

https://wiki.libsdl.org/

 ランキングに参加しています。下記バナーをクリックしていただけるとありがたいです。