59. ゲームパッド¶
ゲームパッドによる入力を扱う方法を学びます。
59.1 XInput 対応コントローラ¶
- Windows PC に接続されている XInput 対応コントローラは
XInput
を通してアクセスできます - 最大 4 台までの接続を同時に扱えます
# include <Siv3D.hpp>
Polygon MakeGamePadPolygon()
{
Polygon polygon = Ellipse{ 400, 480, 300, 440 }.asPolygon(64);
polygon = Geometry2D::Subtract(polygon, Ellipse{ 400, 40, 220, 120 }.asPolygon(48)).front();
polygon = Geometry2D::Subtract(polygon, Circle{ 400, 660, 240 }.asPolygon(48)).front();
polygon = Geometry2D::Subtract(polygon, Rect{ 0, 540, 800, 60 }.asPolygon()).front();
return polygon;
}
void Main()
{
constexpr ColorF backgroundColor{ 0.6, 0.8, 0.7 };
Scene::SetBackground(backgroundColor);
constexpr Ellipse buttonLB{ 210, 150, 50, 24 };
constexpr Ellipse buttonRB{ 590, 150, 50, 24 };
const Polygon gamepadPolygon = MakeGamePadPolygon();
constexpr Circle logo{ 400, 250, 25 };
constexpr RectF leftTrigger{ 210, 16, 40, 100 };
constexpr RectF rightTrigger{ 550, 16, 40, 100 };
constexpr Circle leftThumb{ 230, 250, 35 };
constexpr Circle rightThumb{ 480, 350, 35 };
constexpr Circle dPad{ 320, 350, 40 };
constexpr Circle buttonA{ 570, 300, 20 };
constexpr Circle buttonB{ 620, 250, 20 };
constexpr Circle buttonX{ 520, 250, 20 };
constexpr Circle buttonY{ 570, 200, 20 };
constexpr Circle buttonView{ 330, 250, 15 };
constexpr Circle buttonMenu{ 470, 250, 15 };
// プレイヤーインデックス (0 - 3)
size_t playerIndex = 0;
const Array<String> options = Range(1, 4).map([](int32 i) {return U"{}P"_fmt(i); });
// デッドゾーンを有効にするか
bool enableDeadZone = false;
// 振動 (0.0 - 1.0)
XInputVibration vibration;
while (System::Update())
{
// 指定したプレイヤーインデックスの XInput コントローラを取得
auto controller = XInput(playerIndex);
// デッドゾーン
if (enableDeadZone)
{
// それぞれデフォルト値を設定
controller.setLeftTriggerDeadZone();
controller.setRightTriggerDeadZone();
controller.setLeftThumbDeadZone();
controller.setRightThumbDeadZone();
}
else
{
// デッドゾーンを無効化
controller.setLeftTriggerDeadZone(DeadZone{});
controller.setRightTriggerDeadZone(DeadZone{});
controller.setLeftThumbDeadZone(DeadZone{});
controller.setRightThumbDeadZone(DeadZone{});
}
// 振動
controller.setVibration(vibration);
// L ボタン、R ボタン
{
buttonLB.draw(ColorF{ controller.buttonLB.pressed() ? 1.0 : 0.7 });
buttonRB.draw(ColorF{ controller.buttonRB.pressed() ? 1.0 : 0.7 });
}
// 本体
gamepadPolygon.draw(ColorF{ 0.9 });
// Xbox ボタン
{
if (controller.isConnected())
{
Circle{ logo.center, 32 }
.drawPie((-0.5_pi + 0.5_pi * controller.playerIndex), 0.5_pi, ColorF{ 0.6, 0.9, 0.3 });
}
logo.draw(ColorF{ 0.6 });
}
// 左トリガー
{
leftTrigger.draw(ColorF{ 1.0, 0.25 });
leftTrigger.stretched((controller.leftTrigger - 1.0) * leftTrigger.h, 0, 0, 0).draw();
}
// 右トリガー
{
rightTrigger.draw(ColorF{ 1.0, 0.25 });
rightTrigger.stretched((controller.rightTrigger - 1.0) * rightTrigger.h, 0, 0, 0).draw();
}
// 左スティック
{
leftThumb.draw(ColorF{ controller.buttonLThumb.pressed() ? 0.85 : 0.5 });
Circle{ leftThumb.center + 25 * Vec2{ controller.leftThumbX, -controller.leftThumbY }, 20 }.draw();
}
// 右スティック
{
rightThumb.draw(ColorF{ controller.buttonRThumb.pressed() ? 0.85 : 0.5 });
Circle{ rightThumb.center + 25 * Vec2{ controller.rightThumbX, -controller.rightThumbY }, 20 }.draw();
}
// 方向パッド
{
dPad.draw(ColorF{ 0.75 });
Shape2D::Plus(dPad.r * 0.9, 25, dPad.center).draw(ColorF{ 0.5 });
const Vec2 direction{
controller.buttonRight.pressed() - controller.buttonLeft.pressed(),
controller.buttonDown.pressed() - controller.buttonUp.pressed() };
if (!direction.isZero())
{
Circle{ dPad.center + direction.withLength(25), 15 }.draw();
}
}
// A, B, X, Y ボタン
{
buttonA.draw(ColorF{ 0.0, 1.0, 0.3, controller.buttonA.pressed() ? 1.0 : 0.3 });
buttonB.draw(ColorF{ 1.0, 0.0, 0.3, controller.buttonB.pressed() ? 1.0 : 0.3 });
buttonX.draw(ColorF{ 0.0, 0.3, 1.0, controller.buttonX.pressed() ? 1.0 : 0.3 });
buttonY.draw(ColorF{ 1.0, 0.5, 0.0, controller.buttonY.pressed() ? 1.0 : 0.3 });
}
// View (Back), Menu (Start) ボタン
{
buttonView.draw(ColorF(controller.buttonView.pressed() ? 1.0 : 0.7));
buttonMenu.draw(ColorF(controller.buttonMenu.pressed() ? 1.0 : 0.7));
}
SimpleGUI::RadioButtons(playerIndex, options, Vec2{ 20, 20 });
SimpleGUI::CheckBox(enableDeadZone, U"DeadZone", Vec2{ 320, 20 });
SimpleGUI::Slider(U"leftMotor", vibration.leftMotor, Vec2{ 280, 420 }, 120, 120);
SimpleGUI::Slider(U"rightMotor", vibration.rightMotor, Vec2{ 280, 460 }, 120, 120);
}
}
59.2 Joy-Con¶
- PC に Bluetooth 接続されている Nintendo Switch Joy-Con の情報を、
JoyConL
またはJoyConR
を通して取得できます - macOS では正常に動作しない場合があります
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.9 });
Window::Resize(1280, 720);
Effect effect;
Vec2 left{ 640 - 100, 100 }, right{ 640 + 100, 100 };
double angle = 0_deg;
double scale = 400.0;
bool covered = true;
while (System::Update())
{
Circle{ Vec2{ 640 - 300, 450 }, scale / 2 }.drawFrame(scale * 0.1);
Circle{ Vec2{ 640 + 300, 450 }, scale / 2 }.drawFrame(scale * 0.1);
// Joy-Con (L) を取得
if (const auto joy = JoyConL(0))
{
joy.drawAt(Vec2{ 640 - 300, 450 }, scale, -90_deg - angle, covered);
if (auto d = joy.povD8())
{
left += Circular{ 4, *d * 45_deg };
}
if (joy.button2.down())
{
effect.add([center = left](double t) {
Circle{ center, 20 + t * 200 }.drawFrame(10, 0, ColorF{ 1.0, (1.0 - t) });
return t < 1.0;
});
}
}
// Joy-Con (R) を取得
if (const auto joy = JoyConR(0))
{
joy.drawAt(Vec2{ 640 + 300, 450 }, scale, 90_deg + angle, covered);
if (auto d = joy.povD8())
{
right += Circular{ 4, *d * 45_deg };
}
if (joy.button2.down())
{
effect.add([center = right](double t) {
Circle{ center, 20 + t * 200 }.drawFrame(10, 0, ColorF{ 1.0, (1.0 - t) });
return t < 1.0;
});
}
}
Circle{ left, 30 }.draw(ColorF{ 0.0, 0.75, 0.9 });
Circle{ right, 30 }.draw(ColorF{ 1.0, 0.4, 0.3 });
effect.update();
SimpleGUI::Slider(U"Rotation: ", angle, -180_deg, 180_deg, Vec2{ 20, 20 }, 120, 200);
SimpleGUI::Slider(U"Scale: ", scale, 100.0, 600.0, Vec2{ 20, 60 }, 120, 200);
SimpleGUI::CheckBox(covered, U"Covered", Vec2{ 20, 100 });
}
}
59.3 Pro コントローラーの入力を扱う¶
- PC に Bluetooth 接続されている Nintendo Switch Pro コントローラーの情報を、
ProController
を通して取得できます - macOS では正常に動作しない場合があります
# include <Siv3D.hpp>
void Main()
{
while (System::Update())
{
ClearPrint();
// Pro コントローラーを取得
if (const auto pro = ProController(0))
{
// 各ボタンの状態を表示
Print << U"A: " << pro.buttonA.pressed();
Print << U"B: " << pro.buttonB.pressed();
Print << U"X: " << pro.buttonX.pressed();
Print << U"Y: " << pro.buttonY.pressed();
Print << U"L: " << pro.buttonL.pressed();
Print << U"R: " << pro.buttonR.pressed();
Print << U"ZL: " << pro.buttonZL.pressed();
Print << U"ZR: " << pro.buttonZR.pressed();
Print << U"-: " << pro.buttonMinus.pressed();
Print << U"+: " << pro.buttonPlus.pressed();
Print << U"LS: " << pro.buttonLStick.pressed();
Print << U"RS: " << pro.buttonRStick.pressed();
Print << U"Screenshot: " << pro.buttonScreenshot.pressed();
Print << U"Home: " << pro.buttonHome.pressed();
Print << U"LStick: " << pro.LStick();
Print << U"RStick: " << pro.RStick();
Print << U"POV: " << pro.povD8();
}
else
{
Print << U"No Pro Controller found";
}
}
}
59.4 ゲームパッドの入力を扱う¶
- あらゆる種類のゲームパッドの情報を取得できる汎用的なクラスが
Gamepad
です - 最大 16 台までの接続を同時に扱えます
# include <Siv3D.hpp>
void Main()
{
Window::Resize(800, 800);
const Array<String> indices = Range(0, (Gamepad.MaxPlayerCount - 1)).map(Format);
// ゲームパッドのプレイヤーインデックス
size_t playerIndex = 0;
while (System::Update())
{
ClearPrint();
if (const auto gamepad = Gamepad(playerIndex)) // 接続されていたら
{
const auto& info = gamepad.getInfo();
Print << U"{} (VID: {}, PID: {})"_fmt(info.name, info.vendorID, info.productID);
for (auto&& [i, button] : Indexed(gamepad.buttons))
{
Print << U"button{}: {}"_fmt(i, button.pressed());
}
for (auto&& [i, axe] : Indexed(gamepad.axes))
{
Print << U"axe{}: {}"_fmt(i, axe);
}
Print << U"POV: " << gamepad.povD8();
}
SimpleGUI::RadioButtons(playerIndex, indices, Vec2{ 500, 20 });
}
}
59.5 接続されているゲームパッドを列挙する¶
- PC に接続されているゲームパッドの一覧を
System::EnumerateGamepads()
で取得できます - 結果は
Array<GamepadInfo>
型で返されます GamepadInfo
型のメンバ変数は次のとおりです:
コード | 説明 |
---|---|
.playerIndex |
Gamepad で使うプレイヤーインデックス |
.vendorID |
ベンダー ID |
.productID |
プロダクト ID |
.name |
ゲームパッドの名称 |
# include <Siv3D.hpp>
void Main()
{
for (const auto& info : System::EnumerateGamepads())
{
Print << U"[{}] {} ({:#x} {:#x})"_fmt(info.playerIndex, info.name, info.vendorID, info.productID);
}
while (System::Update())
{
}
}
出力例
[0] Controller (XBOX 360 For Windows) (0x45e 0x28e)
[1] Wireless Gamepad (0x57e 0x2006)
[2] Wireless Gamepad (0x57e 0x2007)
[3] Wireless Controller (0x54c 0x9cc)
[4] Wireless Gamepad (0x57e 0x2009)
59.6 Input 型との連係¶
Gamepad
のbuttons
要素や、XInput の各ボタン、JoyCon の各ボタンはInput
型です- チュートリアル 42 のキーコンフィグにも組み込むことができます
- 次のサンプルコードの操作は次のとおりです:
- ← キー、マウスの左ボタン、XInput の A ボタンのいずれかが押されていれば円を描画
- → キー、マウスの右ボタン、XInput の B ボタンのいずれかが押されていれば正方形を描画
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// 円を表示する操作
const InputGroup input1 = (KeyLeft | MouseL | XInput(0).buttonA);
// 正方形を表示する操作
const InputGroup input2 = (KeyRight | MouseR | XInput(0).buttonB);
while (System::Update())
{
if (input1.pressed())
{
Circle{ 200, 300, 100 }.draw(ColorF{ 0.1 });
}
if (input2.pressed())
{
RectF{ Arg::center(600, 300), 200 }.draw(ColorF{ 0.1 });
}
}
}