入力デバイスからデータを読み取るためのシステムの設計(パート2)

記事の最初の部分では、エイリアスに基づいて入力デバイスからデータを読み取るためのシステムを作成するための手順が取られました。 しかし、最初の記事では、そのようなシステムを使用する利点を示すアプリケーションの作成プロセスについては説明しませんでした。 この記事では、2人用のゲーム用に設計されたシンプルなPongゲームの作成について検討します。コントロールを再割り当てする機能と、キーボードだけに限らず、1つではなく複数のキーをアクションに割り当てる機能を備えています。 マウスといくつかの接続されたジョイスティックだけでなく、キーの組み合わせ(たとえば、W +左マウスボタン)を割り当てる機能も検討します。 入力デバイスでの作業における最大の柔軟性が実証されます。



ゲームを作成するには、最初の部分で説明したシステムのコードをわずかに変更します。



まず、複数のジョイスティックがシステムに接続されている場合の処理​​を含め、ジョイスティックの使用を検討します。 XIpnutを使用して作業を実装します。 このライブラリの操作は可能な限り簡単です。



ハードウェアエイリアスを記述する次のブロックがファイルに追加されます。



エイリアスの説明
"joystick" : [ { "name" : "JOY_DPAD_UP", "index" : 1 }, { "name" : "JOY_DPAD_DOWN", "index" : 2 }, { "name" : "JOY_DPAD_LEFT", "index" : 4 }, { "name" : "JOY_DPAD_RIGHT", "index" : 8 }, { "name" : "JOY_START", "index" : 16 }, { "name" : "JOY_BACK", "index" : 32 }, { "name" : "JOY_LEFT_THUMB", "index" : 64 }, { "name" : "JOY_RIGHT_THUMB", "index" : 128 }, { "name" : "JOY_LEFT_SHOULDER", "index" : 256 }, { "name" : "JOY_RIGHT_SHOULDER", "index" : 512 }, { "name" : "JOY_A", "index" : 4096 }, { "name" : "JOY_B", "index" : 8192 }, { "name" : "JOY_X", "index" : 16384 }, { "name" : "JOY_Y", "index" : 32768 }, { "name" : "JOY_LEFT_STICK_H", "index" : 100 }, { "name" : "JOY_LEFT_STICK_NEGH", "index" : 101 }, { "name" : "JOY_LEFT_STICK_V", "index" : 102 }, { "name" : "JOY_LEFT_STICK_NEGV", "index" : 103 }, { "name" : "JOY_LEFT_TRIGER", "index" : 104 }, { "name" : "JOY_RIGHT_STICK_H", "index" : 105 }, { "name" : "JOY_RIGHT_STICK_NEGH", "index" : 106 }, { "name" : "JOY_RIGHT_STICK_V", "index" : 107 }, { "name" : "JOY_RIGHT_STICK_NEGV", "index" : 108 }, { "name" : "JOY_RIGHT_TRIGER", "index" : 109 } ]
      
      







状態自体を操作するには、次の配列を定義します



 XINPUT_STATE joy_prev_states[XUSER_MAX_COUNT]; XINPUT_STATE joy_states[XUSER_MAX_COUNT]; bool joy_active[XUSER_MAX_COUNT];
      
      





初期化するとき、アクティブなジョイスティックはないと言います。



 for (int i = 0; i< XUSER_MAX_COUNT; i++) { joy_active[i] = false; }
      
      





更新機能では、現在接続されているジョイスティックから状態を取得します。



 ... for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { if (joy_active[i]) { memcpy(&joy_prev_states[i], &joy_states[i], sizeof(XINPUT_STATE)); } ZeroMemory(&joy_states[i], sizeof(XINPUT_STATE)); if (XInputGetState(i, &joy_states[i]) == ERROR_SUCCESS) { if (!joy_active[i]) { memcpy(&joy_prev_states[i], &joy_states[i], sizeof(XINPUT_STATE)); } joy_active[i] = true; } else { joy_active[i] = false; } } ...
      
      





いくつかのジョイスティックを使用できます。そのため、ハードウェアエイリアスをポーリングする場合、データの読み取り元のデバイスのインデックスを転送する必要があります。 メソッドは次のようになります。



 bool GetHardwareAliasState(int alias, AliasAction action, int device_index); float GetHardwareAliasValue(int alias, bool delta, int device_index);
      
      





同時に、device_indexが-1の場合、2つのジョイスティックが接続されていて、ボタンAが押されているかどうかを尋ねている場合、両方のジョイスティックからのクリックが考慮される、つまり、 接続されたジョイスティックからアクティブな値を取得します。



そして、ハードキーのジョイスティックエイリアスを処理するためのコードを示します。



エイリアスの処理
 bool Controls::GetHardwareAliasState(int index, AliasAction action, int device_index) { HardwareAlias& halias = haliases[index]; switch (halias.device) { case Joystick: { if (halias.index<100 || halias.index > 109) { for (int i = 0; i < XUSER_MAX_COUNT; i++) { if (!joy_active[i]) { continue; } bool res = false; if (device_index != -1 && device_index != i) { continue; } int index = i; if (action == Activated) { res = (!(joy_prev_states[index].Gamepad.wButtons & halias.index) && joy_states[index].Gamepad.wButtons & halias.index); } if (action == Active) { res = joy_states[index].Gamepad.wButtons & halias.index; } if (res) { return true; } } } else { float val = GetHardwareAliasValue(index, false, device_index); if (action == Active) { return val > 0.99f; } float prev_val = val - GetHardwareAliasValue(index, true, device_index); return (val > 0.99f) && (prev_val < 0.99f); } break; } ... } return false; } inline float GetJoyTrigerValue(float val) { return val / 255.0f; } inline float GetJoyStickValue(float val) { val = fmaxf(-1, (float)val / 32767); float deadzone = 0.05f; val = (abs(val) < deadzone ? 0 : (abs(val) - deadzone) * (val / abs(val))); return val /= 1.0f - deadzone; } float Controls::GetHardwareAliasValue(int index, bool delta, int device_index) { HardwareAlias& halias = haliases[index]; switch (halias.device) { case Joystick: { if (halias.index >= 100 && halias.index <= 109) { float val = 0.0f; for (int i = 0; i < XUSER_MAX_COUNT; i++) { if (!joy_active[i]) { continue; } if (device_index != -1 && device_index != i) { continue; } int index = i; if (halias.index == 100 || halias.index == 101) { val = GetJoyStickValue((float)joy_states[index].Gamepad.sThumbLX); if (delta) { val = val - GetJoyStickValue((float)joy_prev_states[index].Gamepad.sThumbLX); } if (halias.index == 101) { val = -val; } } else if (halias.index == 102 || halias.index == 103) { val = GetJoyStickValue((float)joy_states[index].Gamepad.sThumbLY); if (delta) { val = val - GetJoyStickValue((float)joy_prev_states[index].Gamepad.sThumbLY); } if (halias.index == 103) { val = -val; } } else if (halias.index == 104) { val = GetJoyTrigerValue((float)joy_states[index].Gamepad.bLeftTrigger); if (delta) { val = val - GetJoyTrigerValue((float)joy_prev_states[index].Gamepad.bLeftTrigger); } } else if (halias.index == 105 || halias.index == 106) { val = GetJoyStickValue((float)joy_states[index].Gamepad.sThumbRX); if (delta) { val = val - GetJoyStickValue((float)joy_prev_states[index].Gamepad.sThumbRX); } if (halias.index == 106) { val = -val; } } else if (halias.index == 107 || halias.index == 108) { val = GetJoyStickValue((float)joy_states[index].Gamepad.sThumbRY); if (delta) { val = val - GetJoyStickValue((float)joy_prev_states[index].Gamepad.sThumbRY); } if (halias.index == 108) { val = -val; } } else if (halias.index == 109) { val = GetJoyTrigerValue((float)joy_states[index].Gamepad.bRightTrigger); if (delta) { val = val - GetJoyTrigerValue((float)joy_prev_states[index].Gamepad.bRightTrigger); } } if (fabs(val) > 0.01f) { break; } } return val; } else { return GetHardwareAliasState(index, Active, device_index) ? 1.0f : 0.0f; } break; } ... } return 0.0f; }
      
      







複数のデバイスを使用した完全な作業に必要な最後のことは、エイリアス自体に必要なデバイス番号を設定する機能です。 したがって、構造を更新する必要があります。



 struct AliasRefState { std::string name; int aliasIndex = -1; bool refer2hardware = false; int device_index = -1; //   };
      
      





エイリアスの読み取りは次のようになります。



エイリアスの読み取り
 bool Controls::LoadAliases(const char* name_aliases) { JSONReader* reader = new JSONReader(); bool res = false; if (reader->Parse(name_aliases)) { res = true; while (reader->EnterBlock("Aliases")) { std::string name; reader->Read("name", name); int index = GetAlias(name.c_str()); Alias* alias; if (index == -1) { aliases.push_back(Alias()); alias = &aliases.back(); alias->name = name; aliasesMap[name] = (int)aliases.size() - 1; } else { alias = &aliases[index]; alias->aliasesRef.clear(); } while (reader->EnterBlock("AliasesRef")) { alias->aliasesRef.push_back(AliasRef()); AliasRef& aliasRef = alias->aliasesRef.back(); while (reader->EnterBlock("names")) { string name; if (reader->IsString("") && reader->Read("", name)) { aliasRef.refs.push_back(AliasRefState()); aliasRef.refs.back().name = name; } else { if (aliasRef.refs.size() != 0) { reader->Read("", aliasRef.refs.back().device_index); } } reader->LeaveBlock(); } reader->Read("modifier", aliasRef.modifier); reader->LeaveBlock(); } reader->LeaveBlock(); } ResolveAliases(); } reader->Release(); return res; }
      
      







2つのジョイスティックからのスティックの逸脱を処理するエイリアスを記述したファイルは次のようになります。



エイリアスの説明
 { "Aliases" : [ { "name" : "Player1.Up", "AliasesRef" : [ { "names" : [ "JOY_LEFT_STICK_V", 0 ] } ] }, { "name" : "Player1.Down", "AliasesRef" : [ { "names" : [ "JOY_LEFT_STICK_NEGV", 0 ] } ] }, { "name" : "Player2.Up", "AliasesRef" : [ { "names" : [ "JOY_LEFT_STICK_V", 1 ] } ] }, { "name" : "Player2.Down", "AliasesRef" : [ { "names" : [ "JOY_LEFT_STICK_NEGV", 1 ] } ] } ] }
      
      







同じタイプのデバイスが複数ある場合に調査の作業を整理する方法を示すために、ジョイスティックで機能するコードの入力を詳細に説明しました。 4つのキーボードと3つのマウスの操作をサポートする必要がある場合、これを行う方法について質問はありません。



次に、管理オーバーライドの実装に必要な機能を追加することを検討してください。 必要な最初の方法:



 const char* Controls::GetActivatedKey(int& device_index) { for (auto& halias : haliases) { int index = &halias - &haliases[0]; int count = 1; if (halias.device == Joystick) { count = XUSER_MAX_COUNT; } for (device_index = 0; device_index<count; device_index++) { if (GetHardwareAliasState(index, Activated, device_index)) { return halias.name.c_str(); } } } return nullptr; }
      
      





メソッドはすべてのハードウェアエイリアスを通過し、エイリアスがアクティブになると、その文字列名が返されます。 このメソッドは、ユーザーがコントロールキー設定メニューでキーを押すと予想される瞬間に押されたキーを追跡するために必要です。



そして今、私たちは反対のことを可能にするメカニズムを説明します:どの正確なボタンがエイリアスに割り当てられているかを言うことです。 これを行うために、構造を説明します。



 struct AliasMappig { std::string name; int alias = -1; struct BindName { int device_index = -1; std::string name; }; std::vector<std::vector<BindName>> bindedNames; AliasMappig(const char* name); bool IsContainHAlias(const char* halias); };
      
      





この構造には、エイリアス自体の名前、そのID、関連付けられているすべてのエイリアス(たとえば、WキーとUpキーが前方に移動する責任があります)およびエイリアスの組み合わせ(横にフリップするには、左ShiftキーとAキーを押します)が格納されます。 これらはすべてコンストラクターに入力されます。 また、この構造体のIsContainHAliasメソッドは、このエイリアスのハードウェアエイリアスがバインドかどうかを理解するために定義されています。 このメソッドは、たとえば、すでに割り当てられているハードウェアエイリアスの割り当てを避けるために必要になる場合があります。 これらのメソッドの実装は次のとおりです。



 Controls::AliasMappig::AliasMappig(const char* name) { this->name = name; this->alias = controls.GetAlias(name); if (this->alias != -1) { Alias& alias = controls.aliases[this->alias]; int count = alias.aliasesRef.size(); if (count) { bindedNames.resize(count); for (auto& bindedName : bindedNames) { int index = &bindedName - &bindedNames[0]; int bind_count = alias.aliasesRef[index].refs.size(); if (bind_count) { bindedName.resize(bind_count); for (auto& bndName : bindedName) { int bind_index = &bndName - &bindedName[0]; bndName.name = alias.aliasesRef[index].refs[bind_index].name; bndName.device_index = alias.aliasesRef[index].refs[bind_index].device_index; } } } } } } bool Controls::AliasMappig::IsContainHAlias(const char* halias) { for (auto bindedName : bindedNames) { for (auto bndName : bindedName) { if (StringUtils::IsEqual(bndName.name.c_str(), halias)) { return true; } } } return false; }
      
      





次に、ゲーム自体の実装に目を向けます。 スタートメニュー、コントロールオーバーライドのメニュー、ゲーム自体の一時停止メニューなど、いくつかの画面で構成されています。 なぜなら すべての画面にメニューがあり、メニューの基本クラスを説明します。これには、メニュー項目を移動したり、メニュー項目をアクティブにしたりする機能が含まれます。 各画面のロジックは、Menuクラスから派生したクラスで実装されます。



まず、メニュー自体で使用されるエイリアスを説明するファイルを提示します。



メニューのエイリアスの説明
 { "Aliases" : [ { "name" : "Menu.Up", "AliasesRef" : [ { "names" : ["KEY_UP"]}, { "names" : ["JOY_LEFT_STICK_V"] } ] }, { "name" : "Menu.Down", "AliasesRef" : [ { "names" : ["KEY_DOWN"]}, { "names" : ["JOY_LEFT_STICK_NEGV"] } ] }, { "name" : "Menu.Action", "AliasesRef" : [ { "names" : ["KEY_RETURN"]}, { "names" : ["JOY_A"] } ] } , { "name" : "Menu.AddHotkey", "AliasesRef" : [ { "names" : ["KEY_LCONTROL"]} ] } , { "name" : "Menu.StopEdit", "AliasesRef" : [ { "names" : ["KEY_ESCAPE"]} ] } , { "name" : "Menu.PauseGame", "AliasesRef" : [ { "names" : ["KEY_ESCAPE"]} ] } ] }
      
      







プレーヤーのビットを制御するために使用されるエイリアスのファイル:



エイリアスプレーヤー管理の説明
 { "Aliases" : [ { "name" : "Player1.Up", "AliasesRef" : [ { "names" : [ "JOY_LEFT_STICK_V", 0 ] } ] }, { "name" : "Player1.Down", "AliasesRef" : [ { "names" : [ "JOY_LEFT_STICK_NEGV", 0 ] } ] }, { "name" : "Player2.Up", "AliasesRef" : [ { "names" : [ "KEY_P", 0 ] } ] }, { "name" : "Player2.Down", "AliasesRef" : [ { "names" : [ "KEY_L", 0 ] } ] } ] }
      
      







次に、基本クラスMenuの実装を示します。



クラスメニュー
 class Menu { public: typedef void(*MunuItemAction)(); static int alias_menu_up; static int alias_menu_down; static int alias_menu_act; static int alias_add_hotkey; static int alias_pause_game; static int alias_stop_edit; int sel_elemenet = 0; struct Item { Vector2 pos; std::string text; int data = -1; MunuItemAction action; Item(Vector2 pos, const char* text, MunuItemAction action, int data = -1) { this->pos = pos; this->text = text; this->action = action; this->data = data; } }; std::vector<Item> items; virtual void Work(float dt) { DrawElements(); if (controls.GetAliasState(alias_menu_down)) { sel_elemenet++; if (sel_elemenet >= items.size()) { sel_elemenet = 0; } } if (controls.GetAliasState(alias_menu_up)) { sel_elemenet--; if (sel_elemenet < 0) { sel_elemenet = items.size() - 1; } } if (controls.GetAliasState(alias_menu_act) && items[sel_elemenet].action) { items[sel_elemenet].action(); } } void DrawElements() { for (auto& item : items) { int index = &item - &items[0]; Color color = COLOR_WHITE; if (index == sel_elemenet) { color = COLOR_GREEN; } render.DebugPrintText(item.pos, color, item.text.c_str()); } } };
      
      







最初の画面、つまり開始画面の実装に移ります。 開始とコントロールの2つの項目のみがあります。 この画面では、基本的な機能で十分であるため、各項目を押したときに初期化とコールバックのみを提供します。



 void ShowControls() { cur_menu = &controls_menu; } void ShowGame() { cur_menu = &game_menu; game_menu.ResetGame(); } .. start_menu.items.push_back(Menu::Item(Vector2(365, 200), "Start", ShowGame)); start_menu.items.push_back(Menu::Item(Vector2(350, 250), "Controls", ShowControls));
      
      





実装を検討する2番目の画面は、コントロール設定画面です。 なぜなら 一緒にゲームを行うために設計されたシンプルなゲームPongを検討しています。次に、各プレーヤーのキューボールを上下に移動するアクションを再定義することがタスクです。 合計4つのアクションがあります。 したがって、エイリアスマッピングにデータを格納する配列を定義し、それを初期化します。



クラスControlsMenu
 vector<Controls::AliasMappig> controlsMapping; ... controlsMapping.push_back(Controls::AliasMappig("Player1.Up")); controlsMapping.push_back(Controls::AliasMappig("Player1.Down")); controlsMapping.push_back(Controls::AliasMappig("Player2.Up")); controlsMapping.push_back(Controls::AliasMappig("Player2.Down")); controlsMapping.push_back(Controls::AliasMappig("Menu.AddHotkey")); controls_menu.items.push_back(Menu::Item(Vector2(300, 100), "Up", nullptr, 0)); controls_menu.items.push_back(Menu::Item(Vector2(300, 150), "Down", nullptr, 1)); controls_menu.items.push_back(Menu::Item(Vector2(300, 300), "Up", nullptr, 2)); controls_menu.items.push_back(Menu::Item(Vector2(300, 350), "Down", nullptr, 3)); controls_menu.items.push_back(Menu::Item(Vector2(370, 450), "Back", HideControls)); ... class ControlsMenu : public Menu { int sel_mapping = -1; bool first_key = false; bool make_hotkey = false; public: virtual void Work(float dt) { if (sel_mapping == -1) { Menu::Work(dt); if (controls.GetAliasState(alias_menu_act)) { sel_mapping = items[sel_elemenet].data; if (sel_mapping != -1) { first_key = true; } } } else { make_hotkey = controls.GetAliasState(alias_add_hotkey, Controls::Active); DrawElements(); if (controls.GetAliasState(alias_stop_edit)) { sel_mapping = -1; } else { int device_index; const char* key = controls.GetActivatedKey(device_index); if (key && !controlsMapping[4].IsContainHAlias(key)) { bool allow = true; if (first_key) { controlsMapping[sel_mapping].bindedNames.clear(); first_key = false; } else { allow = !controlsMapping[sel_mapping].IsContainHAlias(key); } if (allow) { Controls::AliasMappig::BindName bndName; bndName.name = key; bndName.device_index = device_index; if (first_key || !make_hotkey) { vector<Controls::AliasMappig::BindName> names; names.push_back(bndName); controlsMapping[sel_mapping].bindedNames.push_back(names); } else { controlsMapping[sel_mapping].bindedNames.back().push_back(bndName); } } } } } if (sel_mapping != -1) { render.DebugPrintText(Vector2(180, 510), COLOR_YELLOW, "Hold Left CONTROL to create key combination"); render.DebugPrintText(Vector2(200, 550), COLOR_YELLOW, "Press ESCAPE to stop adding keys to alias"); } render.DebugPrintText(Vector2(360, 50), COLOR_WHITE, "Player 1"); render.DebugPrintText(Vector2(360, 250), COLOR_WHITE, "Player 2"); for (auto& item : items) { int index = &item - &items[0]; if (item.data != -1) { Color color = COLOR_WHITE; if (index == sel_elemenet) { color = COLOR_GREEN; } char text[1024]; text[0] = 0; if (item.data != sel_mapping || !first_key) { for (auto& bindedName : controlsMapping[item.data].bindedNames) { if (text[0] != 0) { StringUtils::Cat(text, 1024, ", "); } for (auto& bndName : bindedName) { int index = &bndName - &bindedName[0]; if (index != 0) { StringUtils::Cat(text, 1024, " + "); } StringUtils::Cat(text, 1024, bndName.name.c_str()); } } } if (item.data == sel_mapping) { if (text[0] != 0) { if (!make_hotkey) { StringUtils::Cat(text, 1024, ", "); } else { StringUtils::Cat(text, 1024, " + "); } } StringUtils::Cat(text, 1024, "_"); } render.DebugPrintText(item.pos + Vector2(80, 0), color, text); } } } };
      
      







このコードは、GetActivatedKeyポーリングによるエイリアスの予約を実装します。 エイリアスMenu.AddHotkey(左コントロール)がアクティブな場合、キーの組み合わせが設定されます。 エイリアスMenu.StopEdit(エスケープ)がアクティブになると、エイリアスジョブは終了します。 メインメニューに戻るときは、マッピングを保存する必要があり、コールバックでこれを行います。



マッピングの保存
 void SaveMapping() { JSONWriter* writer = new JSONWriter(); writer->Start("settings/controls/game_pc"); writer->StartArray("Aliases"); for (auto cntrl : controlsMapping) { writer->StartBlock(nullptr); writer->Write("name", cntrl.name.c_str()); writer->StartArray("AliasesRef"); for (auto& bindedName : cntrl.bindedNames) { writer->StartBlock(nullptr); writer->StartArray("names"); for (auto& bndName : bindedName) { writer->Write(nullptr, bndName.name.c_str()); writer->Write(nullptr, bndName.device_index); } writer->FinishArray(); writer->FinishBlock(); } writer->FinishArray(); writer->FinishBlock(); } writer->FinishArray(); writer->Release(); } void HideControls() { cur_menu = &start_menu; SaveMapping(); controls.LoadAliases("settings/controls/game_pc"); }
      
      







最後のステップは、ゲーム画面を実装するクラスを記述することです:



クラスGameMenu
 class GameMenu : public Menu { bool paused = false; float player_speed = 500.0f; float player_size = 16.0f * 4.0f; Vector2 ball_start_pos = Vector2(400.0f, 300.0f); float ball_speed = 450.0f; float ball_radius = 8.0f; float player1_pos; float player2_pos; Vector2 ball_pos; Vector2 ball_dir; int player1_score; int player2_score; public: void ResetBall() { ball_pos = ball_start_pos; ball_dir.x = rnd_range(-1.0f, 1.0f); ball_dir.y = rnd_range(-1.0f, 1.0f); ball_dir.Normalize(); } void ResetGame() { player1_pos = 300.0f - player_size * 0.5f; player2_pos = 300.0f - player_size * 0.5f; player1_score = 0; player2_score = 0; ResetBall(); paused = false; } void UpdatePlayer(float dt, int index, float &pos) { if (controls.GetAliasState(controlsMapping[index + 0].alias, Controls::Active)) { pos -= dt * player_speed; if (pos < 0.0f) { pos = 0.0f; } } if (controls.GetAliasState(controlsMapping[index + 1].alias, Controls::Active)) { pos += dt * player_speed; if (pos > 600.0f - player_size) { pos = 600.0f - player_size; } } } void UpdateBall(float dt) { ball_pos += ball_dir * ball_speed * dt; if (ball_pos.y < ball_radius) { ball_pos.y = ball_radius; ball_dir.y = -ball_dir.y; } if (ball_pos.y > 600 - ball_radius) { ball_pos.y = 600 - ball_radius; ball_dir.y = -ball_dir.y; } if (player1_pos < ball_pos.y && ball_pos.y < player1_pos + player_size && ball_pos.x < 15.0f + ball_radius) { ball_pos.x = 16.0f + ball_radius; ball_dir.x = 1.0; ball_dir.y = (ball_pos.y - (player1_pos + player_size * 0.5f)) / player_size; ball_dir.Normalize(); } if (player2_pos < ball_pos.y && ball_pos.y < player2_pos + player_size && ball_pos.x > 785.0f - ball_radius) { ball_pos.x = 784.0f - ball_radius; ball_dir.x = -1.0; ball_dir.y = (ball_pos.y - (player2_pos + player_size * 0.5f)) / player_size; ball_dir.Normalize(); } if (ball_pos.x < 0) { player2_score++; ResetBall(); } if (ball_pos.x > 800) { player1_score++; ResetBall(); } } void DrawPlayer(Vector2 pos) { for (int i = 0; i < 4; i++) { render.DebugPrintText(pos + Vector2(0, i * 16.0f), COLOR_WHITE, "8"); } } virtual void Work(float dt) { if (paused) { Menu::Work(dt); } else { UpdatePlayer(dt, 0, player1_pos); UpdatePlayer(dt, 2, player2_pos); UpdateBall(dt); if (controls.GetAliasState(alias_pause_game)) { paused = true; } } DrawPlayer(Vector2(3, player1_pos)); DrawPlayer(Vector2(785, player2_pos)); render.DebugPrintText(ball_pos - Vector2(ball_radius), COLOR_WHITE, "O"); char str[16]; StringUtils::Printf(str, 16, "%i", player1_score); render.DebugPrintText(Vector2(375, 20.0f), COLOR_WHITE, str); render.DebugPrintText(Vector2(398, 20.0f), COLOR_WHITE, ":"); StringUtils::Printf(str, 16, "%i", player2_score); render.DebugPrintText(Vector2(415, 20.0f), COLOR_WHITE, str); } };
      
      







以上です。 単純な例を使用して、入力デバイスをポーリングするシステムを操作する際の単純さと柔軟性を実証しました。 システムはコードで散らかっておらず、不必要なメソッドでいっぱいではありません。



稼働中のシステムの使用例へのリンク



また、このシステムはAtumというエンジン用に作成されました。 すべてのエンジンソースのリポジトリ -彼らは多くの興味深いものを持っています。



All Articles