게임용 입력 시스템Input practices for games

이 항목에서는 UWP (유니버설 Windows 플랫폼) 게임에서 입력 장치를 효과적으로 사용 하기 위한 패턴 및 기술에 대해 설명 합니다.This topic describes patterns and techniques for effectively using input devices in Universal Windows Platform (UWP) games.

이 항목을 참조 하 여 다음에 대해 알아봅니다.By reading this topic, 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)
  • 단일 테스트를 사용 하 여 복잡 한 단추 정렬을 검색 하는 방법how to detect complex button arrangements with a single test

입력 장치 클래스 선택Choosing an input device class

ArcadeStick, FlightStick게임 패드와 같이 다양 한 유형의 입력 api를 사용할 수 있습니다.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 플랫폼 게임을 수행 하는 경우 다른 클래스를 통해 사용할 수 있는 추가 기능을 사용 하지 않고 게임 패드 클래스를 사용 하는 것이 좋습니다.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. 이렇게 하면 gamepads만 지원 하도록 게임을 제한 하 고 추가 코드를 요구 하지 않고 여러 다른 gamepads에서 작동 하는 일관적인 인터페이스를 제공 합니다.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와 같은 입력 클래스의 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. 예를 들어 장치가 게임 패드이기도 한 경우 단추 매핑 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 (각각 HardwareVendorIdHardwareProductId를 사용 하 여)의 VID (공급 업체 ID) 및 PID (제품 id)를 살펴보고, 자주 사용 하는 장치에 대해 제안 된 단추 매핑을 제공할 수 있습니다 .이 경우 플레이어의 수동 매핑을 통해 나중에 제공 되는 알 수 없는 장치와 계속 호환 됩니다.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

각 컨트롤러 유형에는 연결 된 컨트롤러 목록 (예: 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. 자세한 내용은 gamepads 목록을 참조 하십시오. 각 컨트롤러 형식에는 해당 항목에 비슷한 이름의 섹션이 있습니다.See The gamepads list for more information (each controller type has a similarly named section on its own topic).

그러나 플레이어가 자신의 컨트롤러를 unplugs 새 항목을 연결 하는 경우 어떻게 되나요?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. 자세한 내용은 Gamepads 추가 및 제거 를 참조 하세요. 자세한 내용은 각 컨트롤러 형식에 유사한 이름의 섹션이 있습니다.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. 따라서 컨트롤러 목록에 액세스할 때마다 한 번에 하나의 스레드만 해당 컨트롤러에 액세스할 수 있도록 잠금을 설정 해야 합니다.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 > **의 critical_section 클래스를 사용 하 여 수행할 수 있습니다.This can be done with the Concurrency Runtime, specifically the critical_section class, in <ppl.h>.

또 다른 고려 사항은 연결 된 컨트롤러의 목록이 처음에는 비어 있으며 두 번째 또는 두 번째를 사용 하 여 채우는 것입니다.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). 그런 다음 컨트롤러 추가 및 컨트롤러에서 제거 된 이벤트 처리기 또는 업데이트 메서드에서이 메서드를 호출 해야 합니다.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가 게임, 성과, 설정 변경 내용 및 기타 작업에 연결 될 수 있도록 사용자 와 연결 됩니다.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 이벤트가 발생 합니다.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 인터페이스에서 상속)의 사용자 속성과 상관 관계를 지정 해야 합니다.For these reasons, player input should be tracked and correlated with the User property of the device class (inherited from the IGameController interface).

UserGamepadPairingUWP 샘플은 사용자와 사용자가 사용 하는 장치를 추적할 수 있는 방법을 보여 줍니다.The UserGamepadPairingUWP 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.

다음 예제에서는 이전 읽기를 기억 하는 기본적인 방법을 보여 줍니다. gamepads는 여기에 표시 되지만 해당 원칙은 아케이드 스틱, 레이스 휠 및 기타 입력 장치 유형에 대해 동일 합니다.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;
}

이러한 두 함수는 먼저 및에서 단추 선택의 부울 상태를 파생 시킨 newReading oldReading 다음, 부울 논리를 수행 하 여 대상 전환이 발생 했는지 여부를 확인 합니다.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. 이러한 함수는 새 읽기가 대상 상태 (각각 눌린 상태 또는 릴리즈됨)를 포함 하 고 이전 읽기에 대상 상태가 포함 되지 않은 경우에만 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

입력 장치의 각 단추는 누름 (다운) 또는 릴리스 (up) 여부를 나타내는 디지털 읽기를 제공 합니다.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. 해당 비트가 설정 된 경우 단추가 눌러져 있습니다 (down). 그렇지 않으면 해제 됩니다 (up).A button is pressed (down) when its corresponding bit is set; otherwise, it's released (up).

단일 단추가 눌러져 있거나 해제 되도록 결정 되는 방식을 기억 합니다. gamepads는 여기에 표시 되지만 해당 원칙은 아케이드 스틱, 레이스 휠 및 기타 입력 장치 유형에 대해 동일 합니다.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).
}

여기에서 볼 수 있듯이 단일 단추의 상태를 결정 하는 것은 매우 간단 하지만 때로는 여러 단추를 눌렀는지 또는 눌렀다 놓았을 지 또는 단추 집합이 특정 방법으로 몇 번의 단추를 눌렀는지를 확인 하는 것이 좋습니다 — .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. 여러 단추를 테스트 하는 것은 특히 혼합 단추 상태를 사용 하 여 단일 단추를 테스트 하는 것 보다 복잡 — — 하지만, 단일 및 여러 단추 테스트에 동일 하 게 적용 되는 이러한 테스트에 대 한 간단한 수식이 있습니다.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).
}

다음 예제에서는 단추 B가 해제 된 상태에서 게임 패드 단추 A를 눌렀는지 여부를 결정 합니다.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.

  • RemainingCapacityInMilliwattHoursFullChargeCapacityInMilliwattHours를 계산 하 여 배터리 비율을 얻을 수 있습니다 / 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