ゲームの入力プラクティスInput practices for games

このページでは、ユニバーサル Windows プラットフォーム (UWP) ゲームで入力デバイスを効果的に使用するためのパターンと手法について説明します。This page describes patterns and techniques for effectively using input devices in Universal Windows Platform (UWP) games.

ここでは、次の項目について紹介します。By reading this page, you'll learn:

  • プレイヤーと、そのプレイヤーが現在使用中の入力デバイスとナビゲーション デバイスを追跡する方法how to track players and which input and navigation devices they're currently using
  • ボタンの状態遷移 (押してから離す、離してから押す) を検出する方法how to detect button transitions (pressed-to-released, released-to-pressed)
  • 1 回のテストでボタンの複雑な配置を検出する方法how to detect complex button arrangements with a single test

入力デバイス クラスの選択Choosing an input device class

利用できる入力 API には、ArcadeStickFlightStickGamepad など多くの種類が存在します。There are many different types of input APIs available to you, such as ArcadeStick, FlightStick, and Gamepad. ゲームに使用する API をどのように決定すればよいのでしょうか。How do you decide which API to use for your game?

ゲームに最も適切な入力を提供する API 選択する必要があります。You should choose whichever API gives you the most appropriate input for your game. たとえば、2D プラットフォームのゲームを作成する場合は、通常、Gamepad クラスを使用するだけで対応でき、その他のクラスで利用可能な追加機能を使用する必要はありません。For example, if you're making a 2D platform game, you can probably just use the Gamepad class and not bother with the extra functionality available via other classes. このクラスは、ゲームのサポートをゲームパッドのみに制限し、コードを追加することなく、多くの異なるゲームパッドで動作する一貫したインターフェイスを提供します。This would restrict the game to supporting gamepads only and provide a consistent interface that will work across many different gamepads with no need for additional code.

その一方で、複雑なフライトやレーシングのシミュレーションでは、すべての RawGameController オブジェクトを基準として列挙し、熱心なプレイヤーが所有しているあらゆるニッチ デバイス (シングル プレイヤー用の独立したペダルやスロットルなどのデバイス) を確実にサポートできます。On the other hand, for complex flight and racing simulations, you might want to enumerate all of the RawGameController objects as a baseline to make sure they support any niche device that enthusiast players might have, including devices such as separate pedals or throttle that are still used by a single player.

そこから、入力クラスの FromGameController メソッド (Gamepad.FromGameController など) を使用して、各デバイスが整理されたビューが表示するかどうかを確認できます。From there, you can use an input class's FromGameController method, such as Gamepad.FromGameController, to see if each device has a more curated view. たとえば、デバイスが Gamepad でもある場合、それを反映するようにボタン マッピング UI を調整し、選択可能ないくつかの適切な既定のボタン マッピングを提供できます For example, if the device is also a Gamepad, then you might want to adjust the button mapping UI to reflect that, and provide some sensible default button mappings to choose from. (これは、RawGameController のみを使用している場合、プレイヤーがゲームパッド入力を手動で構成することが必要になるのとは対照的です)。(This is in contrast to requiring the player to manually configure the gamepad inputs if you're only using RawGameController.)

代わりに、RawGameController のベンダー ID (VID) と製品 ID (PID) を参照し (それぞれ HardwareVendorIdHardwareProductId を使用)、一般的なデバイスの推奨されるボタン マッピングを提供できます。その一方で、プレイヤーが手動でマッピングすることにより将来の未知のデバイスとの互換性を維持できます。Alternatively, you can look at the vendor ID (VID) and product ID (PID) of a RawGameController (using HardwareVendorId and HardwareProductId, respectively) and provide suggested button mappings for popular devices while still remaining compatible with unknown devices that come out in the future via manual mappings by the player.

接続されているコントローラーの追跡Keeping track of connected controllers

各コントローラーの型には、接続されているコントローラーのリスト (Gamepad.Gamepads など) が含まれていますが、独自のコントローラーのリストを保持することをお勧めします。While each controller type includes a list of connected controllers (such as Gamepad.Gamepads), it is a good idea to maintain your own list of controllers. 詳細については、「ゲームパッドの一覧」を参照してください (各コントローラーの型には、それぞれのトピックに類似する名前のセクションがあります)。See The gamepads list for more information (each controller type has a similarly named section on its own topic).

それでは、プレイヤーがコントローラーを取り外した場合や、新しいコントローラーを接続した場合は、どうなるでしょうか。However, what happens when the player unplugs their controller, or plugs in a new one? これらのイベントを処理し、リストを適宜更新する必要があります。You need to handle these events, and update your list accordingly. 詳細については、「ゲームパッドの追加と削除」を参照してください (ここでも、各コントローラーの型には、それぞれのトピックに類似する名前のセクションがあります)。See Adding and removing gamepads for more information (again, each controller type has a similarly named section on its own topic).

追加および削除のイベントは非同期的に発生するため、コントローラーの一覧を処理するときに誤った結果を取得する可能性があります。Because the added and removed events are raised asynchronously, you could get incorrect results when dealing with your list of controllers. そのため、コントローラーのリストにアクセスするときは、常に、一度に 1 つのスレッドのみがリストにアクセスできるようにその周囲にロックを配置する必要があります。Therefore, anytime you access your list of controllers, you should put a lock around it so that only one thread can access it at a time. この処理は、<ppl.h>同時実行ランタイム、具体的には、critical_section クラスで行うことができます。This can be done with the Concurrency Runtime, specifically the critical_section class, in <ppl.h>.

もう 1 つ考慮しなければならないのは、接続されているコントローラーのリストは最初は空であり、設定されるのに 1、2 秒かかるという点です。Another thing to think about is that the list of connected controllers will initially be empty, and takes a second or two to populate. start メソッドで、現在のゲームパッドを割り当てるだけでは、null になります。So if you only assign the current gamepad in the start method, it will be null!

この問題を解決するには、メイン ゲームパッドを "更新する" メソッドが必要です (シングル プレイヤー ゲームの場合。マルチプレイヤー ゲームではさらに高度な解決策が必要になります)。To rectify this, you should have a method that "refreshes" the main gamepad (in a single-player game; multiplayer games will require more sophisticated solutions). コントローラーの追加とコントローラーの削除の両方のイベント ハンドラー、または update メソッドで、このメソッドを呼び出す必要があります。You should then call this method in both your controller added and controller removed event handlers, or in your update method.

次のメソッドは、単にリストの最初のゲームパッド (またはリストが空の場合は nullptr) を返します。The following method simply returns the first gamepad in the list (or nullptr if the list is empty). したがって、コントローラーについて何か処理をする場合は、常に nullptr かどうかを確認することを忘れないようにする必要があります。Then you just need to remember to check for nullptr anytime you do anything with the controller. コントローラーが接続されていない場合にゲームプレイをブロックする (たとえば、ゲームを一時停止する) か、入力を無視して単にゲームプレイを続行させるかは、開発者が決定できます。It's up to you whether you want to block gameplay when there's no controller connected (for example, by pausing the game) or simply have gameplay continue, while ignoring input.

#include <ppl.h>

using namespace Platform::Collections;
using namespace Windows::Gaming::Input;
using namespace concurrency;

Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();

Gamepad^ GetFirstGamepad()
{
    Gamepad^ gamepad = nullptr;
    critical_section::scoped_lock{ m_lock };

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(0);
    }

    return gamepad;
}

これらをまとめて、ゲームパッドからの入力を処理する方法の例を次に示します。Putting it all together, here is an example of how to handle input from a gamepad:

#include <algorithm>
#include <ppl.h>

using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Gaming::Input;
using namespace concurrency;

static Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();
static Gamepad^          m_gamepad = nullptr;
static critical_section  m_lock{};

void Start()
{
    // Register for gamepad added and removed events.
    Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(&OnGamepadAdded);
    Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(&OnGamepadRemoved);

    // Add connected gamepads to m_myGamepads.
    for (auto gamepad : Gamepad::Gamepads)
    {
        OnGamepadAdded(nullptr, gamepad);
    }
}

void Update()
{
    // Update the current gamepad if necessary.
    if (m_gamepad == nullptr)
    {
        auto gamepad = GetFirstGamepad();

        if (m_gamepad != gamepad)
        {
            m_gamepad = gamepad;
        }
    }

    if (m_gamepad != nullptr)
    {
        // Gather gamepad reading.
    }
}

// Get the first gamepad in the list.
Gamepad^ GetFirstGamepad()
{
    Gamepad^ gamepad = nullptr;
    critical_section::scoped_lock{ m_lock };

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(0);
    }

    return gamepad;
}

void OnGamepadAdded(Platform::Object^ sender, Gamepad^ args)
{
    // Check if the just-added gamepad is already in m_myGamepads; if it isn't, 
    // add it.
    critical_section::scoped_lock lock{ m_lock };
    auto it = std::find(begin(m_myGamepads), end(m_myGamepads), args);

    if (it == end(m_myGamepads))
    {
        m_myGamepads->Append(args);
    }
}

void OnGamepadRemoved(Platform::Object^ sender, Gamepad^ args)
{
    // Remove the gamepad that was just disconnected from m_myGamepads.
    unsigned int indexRemoved;
    critical_section::scoped_lock lock{ m_lock };

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        if (m_gamepad == m_myGamepads->GetAt(indexRemoved))
        {
            m_gamepad = nullptr;
        }

        m_myGamepads->RemoveAt(indexRemoved);
    }
}

ユーザーおよびそのデバイスの追跡Tracking users and their devices

ユーザーの ID をゲームプレイ、成績、設定の変更などのアクティビティにリンクできるように、すべての入力デバイスが User に関連付けられます。All input devices are associated with a User so that their identity can be linked to their gameplay, achievements, settings changes, and other activities. ユーザーは必要に応じてサインインまたはサインアウトできます。一般的には、前のユーザーがサインアウトした後、入力デバイスをシステムに接続したまま別のユーザーがサインインします。ユーザーがサインインまたはサインアウトするときに、IGameController.UserChanged イベントが発生します。Users can sign in or sign out at will, and it's common for a different user to sign in on an input device that remains connected to the system after the previous user has signed out. When a user signs in or out, the IGameController.UserChanged event is raised. このイベントのイベント ハンドラーを登録することで、プレイヤーとプレイヤーが使用中のデバイスを追跡できます。You can register an event handler for this event to keep track of players and the devices they're using.

ユーザー ID は、入力デバイスを対応する UI ナビゲーション コント ローラーに関連付けるための方法でもあります。User identity is also the way that an input device is associated with its corresponding UI navigation controller.

これらの理由から、デバイス クラス (IGameController インターフェイスから継承) の User プロパティを使用して、プレイヤーの入力が追跡され、関係付けられます。For these reasons, player input should be tracked and correlated with the User property of the device class (inherited from the IGameController interface).

UserGamepadPairingUWP (GitHub) サンプルでは、ユーザーとそのユーザーが使用中のデバイスを追跡する方法を示してます。The UserGamepadPairingUWP (GitHub) sample demonstrates how you can keep track of users and the devices they're using.

ボタンの状態遷移の検出Detecting button transitions

ボタンを最初に押したタイミング、または離したタイミングを把握したい場合があります。正確には、離した状態から押した状態への、または押した状態から離した状態へのボタンの状態遷移のタイミングを把握したい場合があります。Sometimes you want to know when a button is first pressed or released; that is, precisely when the button state transitions from released to pressed or from pressed to released. これを判断するには、デバイスの前回読み取り結果を記憶し、現在の読み取り結果と比較して状態変化を確認する必要があります。To determine this, you need to remember the previous device reading and compare the current reading against it to see what's changed.

次の例では、前回の読み取り結果を記憶する基本的な方法を示します。ここで示しているのはゲームパッドですが、アーケード スティック、レース ホイール、その他の種類の入力デバイスでも原理は同じです。The following example demonstrates a basic approach for remembering the previous reading; gamepads are shown here, but the principles are the same for arcade stick, racing wheel, and the other input device types.

Gamepad gamepad;
GamepadReading newReading();
GamepadReading oldReading();

// Called at the start of the game.
void Game::Start()
{
    gamepad = Gamepad::Gamepads[0];
}

// Game::Loop represents one iteration of a typical game loop
void Game::Loop()
{
    // move previous newReading into oldReading before getting next newReading
    oldReading = newReading, newReading = gamepad.GetCurrentReading();

    // process device readings using buttonJustPressed/buttonJustReleased (see below)
}

他の処理を行う前に、Game::Loop では newReading の既存の値 (前回ループの反復でのゲームパッドの読み取り結果) を oldReading に移動して、現在の反復でのゲームパッドの新しい読み取り結果を newReading に格納しています。Before doing anything else, Game::Loop moves the existing value of newReading (the gamepad reading from the previous loop iteration) into oldReading, then fills newReading with a fresh gamepad reading for the current iteration. これにより、ボタンの状態遷移を検出するために必要な情報を提供します。This gives you the information you need to detect button transitions.

次の例では、ボタンの状態遷移を検出する基本的な方法を示します。The following example demonstrates a basic approach for detecting button transitions:

bool ButtonJustPressed(const GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (oldReading.Buttons & selection));

    return newSelectionPressed && !oldSelectionPressed;
}

bool ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased =
        (GamepadButtons.None == (newReading.Buttons & selection));

    bool oldSelectionReleased =
        (GamepadButtons.None == (oldReading.Buttons & selection));

    return newSelectionReleased && !oldSelectionReleased;
}

上記の 2 つの関数は、まず newReadingoldReading からボタンの選択状態をブール値で求めています。次に、ブール値の論理演算を実行し、対象となるボタンの状態遷移が発生しているかどうかを判断します。These two functions first derive the Boolean state of the button selection from newReading and oldReading, then perform Boolean logic to determine whether the target transition has occurred. この 2 つの関数は、新しい読み取り結果が目的の状態 (それぞれ押した状態または離した状態) を含み、かつ、 前回の読み取り結果が目的の状態を含まない場合にのみ true を返します。それ以外の場合は false を返します。These functions return true only if the new reading contains the target state (pressed or released, respectively) and the old reading does not also contain the target state; otherwise, they return false.

ボタンの複雑な配置の検出Detecting complex button arrangements

入力デバイスの各ボタンは、ボタンが押されている (ダウン) か、離されている (アップ) かどうかを示すデジタルの読み取り結果を提供します。Each button of an input device provides a digital reading that indicates whether it's pressed (down) or released (up). 効率を高めるため、ボタンの読み取り結果は個別のブール値としては表現しません代わりに、読み取り結果はすべて、GamepadButtons などのデバイス固有の列挙型で表されるビットフィールドにパックされます。For efficiency, button readings aren't represented as individual boolean values; instead, they're all packed into bitfields represented by device-specific enumerations such as GamepadButtons. 特定のボタンを読み取るには、ビット単位のマスクを使用して、目的のボタンの値を切り分けます。To read specific buttons, bitwise masking is used to isolate the values that you're interested in. 対応するビットが設定されているときはボタンが押されており (ダウン)、それ以外の場合はボタンが離されています (アップ)。A button is pressed (down) when its corresponding bit is set; otherwise, it's released (up).

次の例では、1 つのボタンが押されている状態か離されている状態かを判断する方法を示します。ここで示しているのはゲームパッドですが、アーケード スティック、レース ホイール、その他の種類の入力デバイスでも原理は同じです。Recall how single buttons are determined to be pressed or released; gamepads are shown here, but the principles are the same for arcade stick, racing wheel, and the other input device types.

GamepadReading reading = gamepad.GetCurrentReading();

// Determines whether gamepad button A is pressed.
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
    // The A button is pressed.
}

// Determines whether gamepad button A is released.
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
    // The A button is released (not pressed).
}

1 つのボタンの状態は、簡単ですが、複数のボタンが押された状態またはリリースされるかどうかを確認することがありますを決定する、表示できるか、ボタンが特定の方法で配置された場合、一連の—いくつか押すと、失敗します。As you can see, determining the state of a single button is straight-forward, but sometimes you might want to determine whether multiple buttons are pressed or released, or if a set of buttons are arranged in a particular way—some pressed, some not. 複数のボタンをテストするは 1 つのボタンをテストするよりもさらに複雑な—混合ボタンの状態の可能性を特に—が同じテストを単一および複数のボタンに適用されるこれらのテスト用の単純な数式。Testing multiple buttons is more complex than testing single buttons—especially with the potential of mixed button state—but there's a simple formula for these tests that applies to single and multiple button tests alike.

次の例では、ゲームパッドのボタン A とボタン B が両方押されているかどうかを判断します。The following example determines whether gamepad buttons A and B are both pressed:

if ((GamepadButtons::A | GamepadButtons::B) == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
    // The A and B buttons are both pressed.
}

次の例では、ゲームパッドのボタン A とボタン B が両方離されているかどうかを判断します。The following example determines whether gamepad buttons A and B are both released:

if ((GamepadButtons::None == (reading.Buttons & GamepadButtons::A | GamepadButtons::B))
{
    // The A and B buttons are both released (not pressed).
}

次の例では、ゲームパッドのボタン A が押されていてボタン B が離されているかどうかを判断します。The following example determines whether gamepad button A is pressed while button B is released:

if (GamepadButtons::A == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
    // The A button is pressed and the B button is released (B is not pressed).
}

上記の例すべての式に共通しているのは、テストするボタンの配置を等号演算子の左辺で指定し、状態を検討するボタンを等号演算子の右辺のマスク式で選択していることです。The formula that all five of these examples have in common is that the arrangement of buttons to be tested for is specified by the expression on the left-hand side of the equality operator while the buttons to be considered are selected by the masking expression on the right-hand side.

次の例では、上記の例を書き直して、式をより分かりやすく表現する例を示しています。The following example demonstrates this formula more clearly by rewriting the previous example:

auto buttonArrangement = GamepadButtons::A;
auto buttonSelection = (reading.Buttons & (GamepadButtons::A | GamepadButtons::B));

if (buttonArrangement == buttonSelection)
{
    // The A button is pressed and the B button is released (B is not pressed).
}

上記の式は、ボタンの数、配置、テストする状態を問わず、どのような場合でも当てはめることができます。This formula can be applied to test any number of buttons in any arrangement of their states.

バッテリーの状態を取得するGet the state of the battery

IGameControllerBatteryInfo インターフェイスを実装しているゲーム コントローラーの場合は、コントローラー インスタンスで TryGetBatteryReport を呼び出すことによって、コントローラーのバッテリーに関する情報を提供する BatteryReport オブジェクトを取得できます。For any game controller that implements the IGameControllerBatteryInfo interface, you can call TryGetBatteryReport on the controller instance to get a BatteryReport object that provides information about the battery in the controller. バッテリーの充電速度 (ChargeRateInMilliwatts)、新しいバッテリーの電力容量の見積もり (DesignCapacityInMilliwattHours)、現在のバッテリーの完全に充電した場合の電力容量 (FullChargeCapacityInMilliwattHours) などのプロパティを取得することができます。You can get properties like the rate that the battery is charging (ChargeRateInMilliwatts), the estimated energy capacity of a new battery (DesignCapacityInMilliwattHours), and the fully-charged energy capacity of the current battery (FullChargeCapacityInMilliwattHours).

詳細なバッテリーのレポートをサポートするゲーム コントローラーの場合、「バッテリー情報の取得」で説明されているように、この情報に加えて、バッテリーに関するさらに詳しい情報を取得できます。For game controllers that support detailed battery reporting, you can get this and more information about the battery, as detailed in Get battery information. ただし、ほとんどのゲーム コントローラーはこのレベルのバッテリー レポートをサポートしておらず、代わりに低コストのハードウェアを使用しています。However, most game controllers don't support that level of battery reporting, and instead use low-cost hardware. このようなコントローラーでは、次の考慮事項に留意する必要があります。For these controllers, you'll need to keep the following considerations in mind:

  • ChargeRateInMilliwattsDesignCapacityInMilliwattHours は常に NULL になります。ChargeRateInMilliwatts and DesignCapacityInMilliwattHours will always be NULL.

  • バッテリ残量の割合は、RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHours を計算することによって取得できます。You can get the battery percentage by calculating RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHours. これらのプロパティの値を無視して、計算される割合のみを処理する必要があります。You should ignore the values of these properties and only deal with the calculated percentage.

  • 前の項目の割合は、常に次のいずれかになります。The percentage from the previous bullet point will always be one of the following:

    • 100% (フル)100% (Full)
    • 70% (中)70% (Medium)
    • 40% (低)40% (Low)
    • 10% (バッテリー切れ)10% (Critical)

コードが、バッテリー残量の割合に基づいて何かアクションを実行する (UI の描画など) 場合、上記の値に準拠しているかどうかを確認します。If your code performs some action (like drawing UI) based on the percentage of battery life remaining, make sure that it conforms to the values above. たとえば、コントローラーのバッテリー残量が少ないときにプレイヤーに警告を表示する場合は、10% に達したときに表示します。For example, if you want to warn the player when the controller's battery is low, do so when it reaches 10%.

関連項目See also