まえがき
それはすべて、進行中の修理を背景にした週末の1つであるという事実から始まり、私はWarcraft IIIのストリームを見ることにしました。 現時点では十分なサイトがありますが、私の好みはwww.goodgame.ru (広告ではありません)に関連しています 。 当時は何も面白いものが放送されていなかったことに失望しました。 そして、思考が生まれました-ブラックジャックなどで独自のストリームを作ってみませんか
関連ソフトウェア
ブロードキャストには、とりわけ、コンテンツをキャプチャするアプリケーションが必要です。 現時点では、 xsplitとopenbroadcasterの 2つを区別できます。 正直なところ、私は最初のものを使用しませんでした。 無料版では、基本的な機能が利用できます。 ただし、基本バージョンをダウンロードするには、必須の登録を行う必要があります(問題になるわけではありませんが...)。 GPLライセンスと、それに応じたソースコードの可用性は、2番目のオプションに傾いています。 私はopenbroadcasterに立ち寄った。
難しさ
OBSのインストールと構成に問題はありませんでした。 しかし、実行中のゲームは、推奨されるゲームキャプチャモードでキャプチャされることを望みませんでした(これはおそらく、ゲームの開発時に古いバージョンのdirectxを使用したためです)。 他のキャプチャモードを試してみたところ、必要な動作を提供するモニターキャプチャとウィンドウキャプチャの2つが見つかりました。
1つ目は、非常に強い愛情パフォーマンスです。 ゲーム中に感じる。 しかし、それは「箱から出して」と呼ばれるものの作業バージョンでした。
2番目のオプションは、ゲーム中に不快感をもたらしました-カーソルは常にウィンドウを超えています。 一般的に、それは絶対にプレイできませんでした。
解決策
2番目のオプションが選択され、上記の不快感を解消するユーティリティを作成することが決定されました。
最初、Warcraft IIIはフルスクリーンモードで実行されます。
ウィンドウモードで実行するには、アプリケーションの起動コマンドで「-window」スイッチを使用する必要があります。これにより、Windowsキャプチャモードでキャプチャできるようになります。
ウィンドウのクライアント領域内にカーソルを保持するために、ユーティリティの最初のバージョンが作成されました。 彼女の仕事の主なサイクルは次のとおりです。
/* polling version */ void Controller::RunPollingLoop() { while (true) { HWND activeWindow = GetForegroundWindow(); HWND requiredWindow = FindRequiredWindow(m_className, m_winTitle, 5); if (requiredWindow == NULL) throw std::runtime_error("Required window not found"); m_fullScreen.Init(requiredWindow); m_clipHelper.Init(requiredWindow); if (activeWindow == requiredWindow) { if (m_clipHelper.IsClipped() || !CursorInClientArea(requiredWindow)) { Sleep(g_SleepTimeOut); continue; } if (m_fullScreen.Enter()) { DEBUG_TRACE("EnterFullscreen success"); m_clipHelper.Clip(); DEBUG_TRACE("Clip"); } else { DEBUG_TRACE("EnterFullscreen failed"); } } else { if (m_clipHelper.IsClipped()) { if (m_fullScreen.Leave()) { DEBUG_TRACE("LeaveFullscreen success"); } else { DEBUG_TRACE("LeaveFullscreen failed"); } m_clipHelper.UnClip(); DEBUG_TRACE("UnClip"); } Sleep(g_SleepTimeOut); } } }
ClipHelperヘルパークラスを使用してカーソル保持プロセスを制御し、FullScreenクラスを使用してフルスクリーンモードへの移行を制御し、そこから復元します。 ループ自体は、500ミリ秒のタイムアウトでアクティブウィンドウポーリングアルゴリズムを実装します。 この瞬間はすぐには気に入らなかったのですが、先に進むにはコンセプト全体を確認してから最適化を行う必要がありました。
ユーティリティを使用する過程で、次のウィッシュリストがすぐに表示されました。
-ウィンドウをドラッグできるようにするには、クライアント領域でクリック(ポーリングバージョンを保持)した場合にのみクリップを実行する必要があります。
-ゲーム中のタスクバーの表示に悩まされました(修正された場合に関連)。 最初の考えは、プログラムで非表示にすることでした。 ただし、この場合、ユーザーがゲームを終了した瞬間を追跡し、タスクバーを表示する必要があります。 ユーザーをタスクバーなしで放置するリスクが増加しました。 そのため、ゲームウィンドウのサイズをこのウィンドウに割り当てられているモニター解像度サイズに変更することで、フルスクリーン実装を行うことにしました。
bool FullScreen::Enter() { if (m_fullScreen) return true; assert(m_hwnd); if (m_hwnd == NULL) return false; HMONITOR hmon = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi = { sizeof(mi) }; if (!GetMonitorInfo(hmon, &mi)) return false; if (!GetWindowRect(m_hwnd, &m_origWindowRect)) { SecureZeroMemory(&m_origWindowRect, sizeof(m_origWindowRect)); return false; } if (!SetWindowPos(m_hwnd, HWND_TOPMOST, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_SHOWWINDOW)) return false; m_fullScreen = true; return true; }
最適化
ユーティリティの2番目のバージョンでは、アクティブウィンドウのポーリングがWM_ACTIVATEおよびWM_LBUTTONDOWNメッセージフックに置き換えられました。 このために、WH_CALLWNDPROCとWH_MOUSEの2種類のフックを使用しました。 一番下の行は、ゲームウィンドウの必要なイベントを追跡し、サーバーウィンドウを通じてユーティリティに通知することです。 フックは、ゲームプロセスのためだけに掛けられました。 したがって、ユーティリティの前にゲームを実行する必要があります。
BOOL SetWinHook(HWND hWnd, DWORD threadId) { if (g_hWndSrv != NULL) return FALSE; //already hooked g_hCallWndHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndHookProc, g_hInst, threadId); if (g_hCallWndHook != NULL) { g_hMouseHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseHookProc, g_hInst, threadId); if (g_hMouseHook != NULL) { g_hWndSrv = hWnd; return TRUE; } ClearWinHook(); } return FALSE; }
また、作業のメインサイクルは次の手順に短縮されました。
LRESULT CALLBACK Controller::MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_ACTIVATE) { switch (wParam) { case WA_ACTIVE: DEBUG_TRACE("WA_ACTIVE"); gs_ActivateClip = true; break; case WA_CLICKACTIVE: DEBUG_TRACE("WA_CLICKACTIVE"); gs_ActivateClip = true; break; case WA_INACTIVE: DEBUG_TRACE("WA_INACTIVE"); gs_ActivateClip = false; if (g_ControllerPtr->ClipCursorHelper().IsClipped()) { if (g_ControllerPtr->FullScreenHelper().Leave()) { DEBUG_TRACE("LeaveFullscreen success"); } else { DEBUG_TRACE("LeaveFullscreen failed"); } g_ControllerPtr->ClipCursorHelper().UnClip(); DEBUG_TRACE("UnClip"); } break; } return 0; } else if (uMsg == WM_LBUTTONDOWN) { DEBUG_TRACE("WM_LBUTTONDOWN"); if (!gs_ActivateClip) return 0; if (g_ControllerPtr->ClipCursorHelper().IsClipped()) return 0; if (g_ControllerPtr->FullScreenHelper().Enter()) { DEBUG_TRACE("EnterFullscreen success"); g_ControllerPtr->ClipCursorHelper().Clip(); DEBUG_TRACE("Clip"); } else { DEBUG_TRACE("EnterFullscreen failed"); } return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
使用されるヘルパークラスは、最初のバージョンと同じです。 この関数は、ユーティリティサーバーウィンドウのウィンドウプロシージャです。 カーソルをキャプチャして全画面に移動するには、ウィンドウをアクティブにし、クライアント領域を左クリックする必要があります。 ウィンドウがアクティブでなくなると、ウィンドウは元のサイズと位置に復元され、カーソルは保持されなくなります。
あとがき
お気に入りのゲームをストリーミングするプロセスを「箱から出して」提案された作業オプションよりも快適にするためのユーティリティが開発されました。 誰かが自分にとって面白いものを描いてくれたら嬉しいです。 すべてのソースコードはgithub WinClipCursorにアップロードされます。