読者です 読者をやめる 読者になる 読者になる

【SMFL非公式和訳】チュートリアル: ダイナミックキーバインディングマネージャー

この記事は僕がSFMLを学習する為に
Tutorial: Manage dynamic key binding
https://github.com/SFML/SFML/wiki/Tutorial%3A-Manage-dynamic-key-binding
を無断で和訳したものです。訳者がSMFL、英語どちらも学習中の為意訳・誤訳ご了承のほど。


キーバインドとは、ゲーム中のキー操作とアクションを関連付けしたものです。これによって移動キーでキャラクターを動かしたり、エンターキーで攻撃させたりできます。FPSを作りたければマウスの左クリックで銃弾を発射させるという具合です。もう少し具体的に言えば私が言う「アクション」とは、イベントを関連付ける事です。

え? 左ききプレイヤーにはどう対処すればいいかですって? きっと彼らは左クリックの代わりに右クリックで攻撃したいに違いない! ご安心を。キーバインドは簡単にそれを実現します。そのアクションを新しいイベントに関連付けるだけです。

私のこのやり方はベストではありませんが、私にとっては充分機能しています。

まず最初に、SFMLイベントタイプ、 マウス、キーボード、ジョイスティックをそれぞれ検知する必要があります。違う入力デバイスは別々に扱ってやらなくてはなりません。

enum InputType
{
    KeyboardInput,
    MouseInput,
    JoystickInput
};

その次に、バインディングを定義します。

struct MyKeys
{
    InputType myInputType;
    sf::Event::EventType myEventType;
    sf::Keyboard::Key myKeyCode;
    sf::Mouse::Button myMouseButton;
};

ほら、とても簡単な仕組みでアクションを特定のイベントに関連付けることができました。
あなたのメインプログラムに以下のようにstd::mapでバインディングを格納してください。xmlやその他の方法でも実現できます。

    std::map<std::string,MyKeys> Keys;
    MyKeys key;

    // マウス左ボタンに「撃つ」アクションをバインドする
    key.myInputType = MouseInput;
    key.myEventType = sf::Event::MouseButtonPressed;
    key.myMouseButton = sf::Mouse::Left;
    Keys["Shoot"] = key;

    // エンターキーに「ジャンプ」アクションをバインドする
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::Return;
    Keys["Jump"] = key;

    // 左CTRLキーに「使う」アクションをバインドする
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::LControl;
    Keys["Use"] = key;

std::mapを使うと簡単にアクションを名前で管理することができます。

そして、イベントマネージャーをこんなダイナミックなものに改善する事ができました。

    // イベント管理
    while (App.pollEvent(Event))
    {
        bla bla bla...
        // My events
        // 撃つイベント
        if (TestEvent(Keys["Shoot"], Event))
        {
            Shoot();
        }
        if (TestEvent(Keys["Jump"], Event))
        {
            Jump();
        }
        if (TestEvent(Keys["Use"], Event))
        {
            Use ();
        }
    }

結果、アクションを分割して名前で管理する事ができるようになりました。これで関数やコードやらを直感的に扱えます。
TestEvent関数:

bool TestEvent(MyKeys k, sf::Event e)
{
    // マウスイベント
    if (k.myInputType == MouseInput &&
        k.myEventType == e.type &&
        k.myMouseButton == e.mouseButton.button)
    {
        return (true);
    }
    // キーボードイベント
    if (k.myInputType == KeyboardInput &&
        k.myEventType == e.type &&
        k.myKeyCode == e.key.code)
    {
        return (true);
    }
    return (false);
}

アクションが実行出来る時、この関数はtrueを返します。何が実際に出来るかは書く必要はありません。実行できることだけを知っていれば良いのです。

テスト用の完全なサンプルコードはこちら:

#include <iostream>
#include <fstream>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

enum InputType
{
    KeyboardInput,
    MouseInput,
    JoystickInput
};

struct MyKeys
{
    InputType myInputType;
    sf::Event::EventType myEventType;
    sf::Keyboard::Key myKeyCode;
    sf::Mouse::Button myMouseButton;
};

bool TestEvent(MyKeys k, sf::Event e);
void Shoot(void);
void Jump(void);

int main(int argc, char** argv)
{
    // Variables for main
    sf::RenderWindow App;
    bool Running = true;
    sf::Event Event;

    // Variables for demo
    std::map<std::string,MyKeys> Keys;
    MyKeys key;

    // Let's bind the left mouse button to the "Shoot" action
    key.myInputType = MouseInput;
    key.myEventType = sf::Event::MouseButtonPressed;
    key.myMouseButton = sf::Mouse::Left;
    Keys["Shoot"] = key;

    // Let's bind the Return key to the "Jump" action
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::Return;
    Keys["Jump"] = key;

    // Let's bind the Left Control key to the "Use" action
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::LControl;
    Keys["Use"] = key;

    // Window creation
    App.create(sf::VideoMode(640, 480, 16), "config test");

    // Main loop
    while (Running)
    {
        // Manage Events
        while (App.pollEvent(Event))
        {
            // Using Event normally

            // Window closed
            if (Event.type == sf::Event::Closed)
            {
                Running = false;
            }

            // Key pressed
            if (Event.type == sf::Event::KeyPressed)
            {
                switch (Event.key.code)
                {
                    case sf::Keyboard::Escape :
                        Running = false;
                        break;
                    case sf::Keyboard::A :
                        std::cout << "Key A !" << std::endl;
                        break;
                    default :
                        break;
                }
            }

            // Using Event for binding
            // Shoot
            if (TestEvent(Keys["Shoot"], Event))
            {
                // You can use a function
                Shoot();
            }
            if (TestEvent(Keys["Jump"], Event))
            {
                Jump();
            }
            if (TestEvent(Keys["Use"], Event))
            {
                // or only code
                std::cout << "Use !" << std::endl;
            }
        }

        // Display the result
        App.display();
    }

    // End of application
    return EXIT_SUCCESS;
}

bool TestEvent(MyKeys k, sf::Event e)
{
    // Mouse event
    if (k.myInputType == MouseInput &&
        k.myEventType == e.type &&
        k.myMouseButton == e.mouseButton.button)
    {
        return (true);
    }
    // Keyboard event
    if (k.myInputType == KeyboardInput &&
        k.myEventType == e.type &&
        k.myKeyCode == e.key.code)
    {
        return (true);
    }
    return (false);
}

void Shoot(void)
{
    std::cout << "Shoot !" << std::endl;
}

void Jump(void)
{
    std::cout << "Jump !" << std::endl;
}