Режим «Разделенного экрана» или Split Screen своими руками

    Многие из нас проводили вечера перед теплым ламповым телевизором с друзьями, играя на приставках. Всегда особенно приятно было играть одновременно вдвоем, не ожидая своей очереди.


    Ностальгия.

    К сожалению, в настоящее время многие разработчики игр не добавляют подобного режима — новые игры со splitscreen можно пересчитать чуть ли не на пальцах одной руки. В один весенний вечер пришла идея в голову попробовать обойти ограничение, накладываемое разработчиками, и сделать игровой режим с разделением экрана более доступным.


    В качестве подопытного кролика игры мною была выбрана «World of Tanks» по нескольким причинам:
    1. Возможность играть вдвоем, данный режим называется "Взвод".
    2. Игра на минимальных настройках достаточна не требовательна — любой средний по производительности ПК должен тянуть 2 копии.
    3. Геймплей танков достаточно незатейлив, хотя разработчиками и позиционируется как «массовая многопользовательская онлайн-игра в жанре action с элементами ролевой игры, шутера и стратегии» (Wikipedia). Но как по мне — аркада для пыщь-пыщь.
    4. Наверное, самая основная причина — я, по несколько часов в неделю, и мой младший сын любим пострелять. Со старшим мы иногда бегаем в Portal2, там режим разделенного экрана для ТВ есть.


    Эти ребята надеюсь многим знакомы ;)

    Более детальное изучение всех составляющих для танков вдвоем на ТВ привело к следующему:
    1. Настроить клиент игры для возможного запуска двух копий
    2. Необходимо разделить экран телевизора на два виртуальных.
    3. Решить проблему отправки нажатий кнопок/отклонения стиков с геймпада в неактивное окно.
    4. Отправить вибрацию в разные геймпады с разных клиентов.

    Подробнее о процессе решения.


    1. Запуск 2-х клиентов.
    По-умолчанию, разработчики из Wargaming убрали возможность одновременного запуска двух копий. Не буду описывать все прелести «песочницы» — Sandboxie Вам в помощь.

    2. Разделение экрана телевизора на две части.
    «WoT» в оконном режиме может иметь минимальное разрешение 1024х768, в случае разделения FullHD телевизора пополам, необходимо разрешение каждого окна минимум 960х1080, а учитывая рамки окна и заголовок и того меньше. Т.е. стандартными «горячими клавишами» через Snap разнеся окна в разные стороны мы получаем частичное перекрытие окон. Любые другие утилиты для разделения рабочего стола на две части используют похожий функционал и никаким образом не могут повлиять на минимальное разрешения игры по ширине.
    Перепробовав огромное количество, натолкнулся на Virtual Display Manager, подкупило отсутствие в названии слова desktop.
    Утилита сделала нужное — добавив конфигурацию двух виртуальных дисплеев и перемещая окно в нужный — игра принимает нужное нам значение, а именно занимает ровно половину экрана. Кстати надо проверить разделение на большее количество.

    3. Отправка нажатий клавиш в неактивное окно.
    Это решение было для моего ума самым сложным. Два клиента запущены, окна разнесены в стороны и не перекрывают друг-дружку, но одно из окон активно, соответственно принимает нажатия кнопок и перемещения мышки, а вот второе не активно со всеми вытекающими.


    Разрешение 1366х768.

    К решению этой проблемы меня подтолкнуло знакомство с AutoHotkey. Вот уж поистине «AutoHotkey — это свободная утилита под Windows с открытыми исходными кодами и скриптовый язык с огромными возможностями, в принципе даже не требующий установки.» (ссылка)

    Первый скрипт, позволяющий даже иногда ездить в бою
    #InstallKeybdHook
    w::
    WinGet, wot, PID, WoT Client
    ControlSend,, {sc11 Down}, ahk_pid %wot%
    KeyWait, w
    ControlSend,, {sc11 Up}, ahk_pid %wot%
    Return
    a::
    WinGet, wot, PID, WoT Client
    ControlSend,, {sc1E Down}, ahk_pid %wot%
    KeyWait, a
    ControlSend,, {sc1E Up}, ahk_pid %wot%
    Return
    s::
    WinGet, wot, PID, WoT Client
    ControlSend,, {sc1F Down}, ahk_pid %wot%
    KeyWait, s
    ControlSend,, {sc1F Up}, ahk_pid %wot%
    Return
    d::
    WinGet, wot, PID, WoT Client
    ControlSend,, {sc20 Down}, ahk_pid %wot%
    KeyWait, d
    ControlSend,, {sc20 Up}, ahk_pid %wot%
    Return

    Причины, почему скрипт срабатывал, мне так и остались неизвестны.

    После многих безуспешных попыток, решение нашлось. Через SendMessage сообщать окну, что оно активно и отправлять нажатия клавиш. Такой своеобразный обман.
    Скрипт отправляет стрелки, WASD и пробел (переназначеный на выстрел в игре) в неактивное окно.
    #SingleInstance
    #InstallKeybdHook
    SetControlDelay -1
    vk49::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk57 Down}, WoT Client
    KeyWait, vk49
    ControlSend,, {vk57 Up}, WoT Client
    Return
    vk4A::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk41 Down}, WoT Client
    KeyWait, vk4A
    ControlSend,, {vk41 Up}, WoT Client
    Return
    vk4B::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk53 Down}, WoT Client
    KeyWait, vk4B
    ControlSend,, {vk53 Up}, WoT Client
    Return
    vk4C::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk44 Down}, WoT Client
    KeyWait, vk4C
    ControlSend,, {vk44 Up}, WoT Client
    Return
    numpadup::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {up Down}, WoT Client
    KeyWait, numpadup
    ControlSend,, {up Up}, WoT Client
    Return
    numpaddown::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {down Down}, WoT Client
    KeyWait, numpaddown
    ControlSend,, {down Up}, WoT Client
    Return
    numpadleft::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {left Down}, WoT Client
    KeyWait, numpadleft
    ControlSend,, {left Up}, WoT Client
    Return
    numpadright::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {right Down}, WoT Client
    KeyWait, numpadright
    ControlSend,, {right Up}, WoT Client
    Return
    NumpadEnter::
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk20 Down}, WoT Client
    KeyWait, NumpadEnter
    ControlSend,, {vk20 Up}, WoT Client
    Return


    Дальше пошло веселее.

    Прошу конструктивно критиковать код совершенству нет предела.
    World_Of_Tanks_Split_screen
    JoyMultiplier = 5
    JoyThreshold = 5
    JoyThresholdUpper := 50 + JoyThreshold
    JoyThresholdLower := 50 — JoyThreshold
    #Persistent
    SetTimer, WatchAxisFirstJoyMoveForwardAndZoom, 10
    SetTimer, WatchAxisFirstJoyMoveRotate, 10
    SetTimer, WatchAxisFirstJoyCameraRotateVert, 10
    SetTimer, WatchAxisFirstJoyCameraRotateHoriz, 10
    SetTimer, WatchAxisFirstJoyShoot, 10
    SetTimer, WatchFirstJoyPOV, 10
    SetTimer, WatchAxisSecondJoyMoveForwardAndZoom, 10
    SetTimer, WatchAxisSecondJoyMoveRotate, 10
    SetTimer, WatchAxisSecondJoyCameraRotate, 10
    SetTimer, WatchAxisSecondJoyShoot, 10
    SetTimer, WatchSecondJoyPOV, 10
    return

    ;;;;;;;;;;;; убираем загловок окон

    ^!+s::
    WinWait, WoT Client
    WinSet, Style, -0xC00000
    WinWait, [#] WoT Client [#]
    WinSet, Style, -0xC00000
    return

    ;;;;;;;;;;;; первый геймпад движение вперед/назад в неактивном окне и зум

    WatchAxisFirstJoyMoveForwardAndZoom:
    GetKeyState, 1JoyY, 1JoyY
    GetKeyState, 1JoyZ, 1JoyZ
    GetKeyState, 1Joy2, 1Joy2
    GetKeyState, 1Joy3, 1Joy3
    FirstJoyMoveForwardAndZoomPrev = %FirstJoyMoveForwardAndZoom%

    if 1Joy2 = D
    GoSub, FirstJoyConsumables
    else if 1Joy3 = D
    GoSub, FirstJoyConsumables
    else
    {
    if 1JoyZ > 70
    {
    if 1JoyY < 30
    FirstJoyMoveForwardAndZoom = PgDn
    else if 1JoyY > 70
    FirstJoyMoveForwardAndZoom = PgUp
    else
    FirstJoyMoveForwardAndZoom =
    }
    else if 1JoyY < 30
    FirstJoyMoveForwardAndZoom = vk57
    else if 1JoyY > 70
    FirstJoyMoveForwardAndZoom = vk53
    else
    FirstJoyMoveForwardAndZoom =
    }

    if FirstJoyMoveForwardAndZoom = %FirstJoyMoveForwardAndZoomPrev%
    return

    SetKeyDelay -1
    if FirstJoyMoveForwardAndZoom
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyMoveForwardAndZoom% down}, WoT Client
    }
    }
    if FirstJoyMoveForwardAndZoomPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyMoveForwardAndZoomPrev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад движение влево/вправо в неактивном окне

    WatchAxisFirstJoyMoveRotate:
    GetKeyState, 1JoyX, 1JoyX
    GetKeyState, 1Joy2, 1Joy2
    GetKeyState, 1Joy3, 1Joy3
    FirstJoyMoveRotatePrev = %FirstJoyMoveRotate%

    if 1Joy2 = D
    GoSub, SecondJoyConsumables
    else if 1Joy3 = D
    GoSub, SecondJoyConsumables
    else
    {
    if 1JoyX > 80
    FirstJoyMoveRotate = vk44
    else if 1JoyX < 20
    FirstJoyMoveRotate = vk41
    else
    FirstJoyMoveRotate =
    }

    if FirstJoyMoveRotate = %FirstJoyMoveRotatePrev%
    return

    SetKeyDelay -1
    if FirstJoyMoveRotate
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyMoveRotate% down}, WoT Client
    }
    }
    if FirstJoyMoveRotatePrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyMoveRotatePrev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад меню расходников в неактивном окне

    FirstJoyConsumables:
    FirstJoyConsumablesPrev = %FirstJoyConsumables%

    if 1JoyX < 20
    {
    if 1JoyY < 20
    FirstJoyConsumables = vk38
    else if 1JoyY between 40 and 60
    FirstJoyConsumables = vk37
    else if 1JoyY > 80
    FirstJoyConsumables = vk36
    else FirstJoyConsumables =
    }
    else if 1JoyX between 40 and 60
    {
    if 1JoyY < 10
    FirstJoyConsumables = vk31
    else if 1JoyY > 90
    FirstJoyConsumables = vk35
    else FirstJoyConsumables =
    }
    else if 1JoyX > 80
    {
    if 1JoyY < 20
    FirstJoyConsumables = vk32
    else if 1JoyY between 40 and 60
    FirstJoyConsumables = vk33
    else if 1JoyY > 80
    FirstJoyConsumables = vk34
    else FirstJoyConsumables =
    }
    else FirstJoyConsumables =

    if FirstJoyConsumables = %SFirstJoyConsumablesPrev%
    return

    SetKeyDelay -1
    if FirstJoyConsumables
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyConsumables% down}, WoT Client
    }
    }
    if FirstJoyConsumablesPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyConsumablesPrev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад обзор влево/вправо в неактивном окне

    WatchAxisFirstJoyCameraRotateVert:
    GetKeyState, 1JoyU, 1JoyU
    GetKeyState, 1Joy5, 1Joy5
    FirstJoyCameraRotateVertPrev = %FirstJoyCameraRotateVert%

    if 1Joy5 = D
    GoSub, FirstJoyCommandMenu
    else
    {
    if 1JoyU > 70
    FirstJoyCameraRotateVert = Right
    else if 1JoyU < 30
    FirstJoyCameraRotateVert = Left
    else
    FirstJoyCameraRotateVert =
    }

    if FirstJoyCameraRotateVert = %FirstJoyCameraRotateVertPrev%
    return

    SetKeyDelay -1
    if FirstJoyCameraRotateVert
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyCameraRotateVert% down}, WoT Client
    }
    }
    if FirstJoyCameraRotateVertPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyCameraRotateVertPrev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад обзор вверх/вниз в неактивном окне

    WatchAxisFirstJoyCameraRotateHoriz:
    GetKeyState, 1JoyR, 1JoyR
    GetKeyState, 1Joy5, 1Joy5
    FirstJoyCameraRotateHorizPrev = %FirstJoyCameraRotateHoriz%

    if 1Joy5 = D
    GoSub, FirstJoyCommandMenu
    else
    {
    if 1JoyR > 70
    FirstJoyCameraRotateHoriz = Down
    else if 1JoyR < 30
    FirstJoyCameraRotateHoriz = Up
    else
    FirstJoyCameraRotateHoriz =
    }

    if FirstJoyCameraRotateHoriz = %FirstJoyCameraRotateHorizPrev%
    return

    SetKeyDelay -1
    if FirstJoyCameraRotateHoriz
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyCameraRotateHoriz% down}, WoT Client
    }
    }
    if FirstJoyCameraRotateHorizPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyCameraRotateHorizPrev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад меню приказов

    FirstJoyCommandMenu:
    FirstJoyCommandMenuPrev = %FirstJoyCommandMenu%

    if 1JoyU < 20
    {
    if 1JoyR < 20
    FirstJoyCommandMenu = Numpad8
    else if 1JoyR between 40 and 60
    FirstJoyCommandMenu = Numpad7
    else if 1JoyR > 80
    FirstJoyCommandMenu = Numpad6
    else FirstJoyCommandMenu =
    }
    else if 1JoyU between 40 and 60
    {
    if 1JoyR < 10
    FirstJoyCommandMenu = vk54
    else if 1JoyR > 90
    FirstJoyCommandMenu = Numpad5
    else FirstJoyCommandMenu =
    }
    else if 1JoyU > 80
    {
    if 1JoyR < 20
    FirstJoyCommandMenu = Numpad2
    else if 1JoyR between 40 and 60
    FirstJoyCommandMenu = Numpad3
    else if 1JoyR > 80
    FirstJoyCommandMenu = Numpad4
    else FirstJoyCommandMenu =
    }
    else FirstJoyCommandMenu =

    if FirstJoyCommandMenu = %FirstJoyCommandMenuPrev%
    return

    SetKeyDelay -1
    if FirstJoyCommandMenu
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyCommandMenu% down}, WoT Client
    }
    }
    if FirstJoyCommandMenuPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyCommandMenuPrev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад выстрел в неактивном окне

    WatchAxisFirstJoyShoot:
    GetKeyState, 1JoyZ, 1JoyZ
    FirstJoyShootPrev = %FirstJoyShoot%

    if 1JoyZ < 30
    FirstJoyShoot = LButton
    else
    FirstJoyShoot =

    if FirstJoyShoot = %FirstJoyShootPrev%
    return

    SetKeyDelay -1
    if FirstJoyShoot
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x201,,,, WoT Client
    }
    }
    if FirstJoyShootPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x202,,,, WoT Client
    }
    }
    return

    ;;;;;;;;;;;;; первый геймпад крестовина в неактивном окне

    WatchFirstJoyPOV:
    GetKeyState, 1JoyPOV, 1JoyPOV
    FirstJoyPOVPrev = %FirstJoyPOV%

    if 1JoyPOV = 0
    FirstJoyPOV = vk52
    else if 1JoyPOV = 18000
    FirstJoyPOV = vk46
    else if 1JoyPOV = 27000
    FirstJoyPOV = vk58
    else if 1JoyPOV = 9000
    FirstJoyPOV = vk43
    else FirstJoyPOV =

    if FirstJoyPOV = %FirstJoyPOVPrev%
    return

    SetKeyDelay -1
    if FirstJoyPOV
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyPOV% down}, WoT Client
    }
    }
    if FirstJoyPOVPrev
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {%FirstJoyPOVprev% up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад LShift в неактивном окне

    1Joy10::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vkA0 Down}, WoT Client
    KeyWait, 1Joy10
    ControlSend,, {vkA0 Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад Space в неактивном окне

    1Joy9::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk20 Down}, WoT Client
    KeyWait, 1Joy9
    ControlSend,, {vk20 Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад выбор снарядов в неактивном окне

    1Joy1::
    Gosub, FirstSubToggle
    Return

    FirstSubToggle:
    FirstToggle++
    If FirstToggle = 1
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk31 down}, WoT Client
    Sleep, 10
    ControlSend,, {vk31 up}, WoT Client
    Sleep, 10
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk31 down}, WoT Client
    Sleep, 10
    ControlSend,, {vk31 up}, WoT Client
    }
    }
    If FirstToggle = 2
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk32 down}, WoT Client
    Sleep, 10
    ControlSend,, {vk32 up}, WoT Client
    Sleep, 10
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk32 down}, WoT Client
    Sleep, 10
    ControlSend,, {vk32 up}, WoT Client
    }
    }
    If FirstToggle = 3
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk33 down}, WoT Client
    Sleep, 10
    ControlSend,, {vk33 up}, WoT Client
    Sleep, 10
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk33 down}, WoT Client
    Sleep, 10
    ControlSend,, {vk33 up}, WoT Client
    }
    FirstToggle = 0
    }
    return

    ;;;;;;;;;;;; первый геймпад огнетушитель в неактивном окне

    1Joy4::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk35 Down}, WoT Client
    KeyWait, 1Joy4
    ControlSend,, {vk35 Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад автоприцел в неактивном окне

    1Joy6::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x204, 1,,, WoT Client
    KeyWait, 1Joy6
    SendMessage, 0x205, 1,,, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад меню в неактивном окне

    1Joy8::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk1B Down}, WoT Client
    KeyWait, 1Joy8
    ControlSend,, {vk1B Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад скрыть мини карту в неактивном окне

    1Joy7::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk4D Down}, WoT Client
    KeyWait, 1Joy7
    ControlSend,, {vk4D Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад ремонт в неактивном окне

    1Joy3::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk34 Down}, WoT Client
    KeyWait, 1Joy3
    ControlSend,, {vk34 Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; первый геймпад лечение в неактивном окне

    1Joy2::
    {
    IfWinNotActive, WoT Client
    {
    SendMessage, 0x06, 1,,, WoT Client
    ControlSend,, {vk36 Down}, WoT Client
    KeyWait, 1Joy2
    ControlSend,, {vk36 Up}, WoT Client
    }
    }
    return

    ;;;;;;;;;;;; второй геймпад

    ;;;;;;;;;;;; второй геймпад движение вперед/назад в активном окне и зум

    WatchAxisSecondJoyMoveForwardAndZoom:
    GetKeyState, 2JoyY, 2JoyY
    GetKeyState, 2JoyZ, 2JoyZ
    GetKeyState, 2Joy2, 2Joy2
    GetKeyState, 2Joy3, 2Joy3
    SecondJoyMoveForwardAndZoomPrev = %SecondJoyMoveForwardAndZoom%

    if 2Joy2 = D
    GoSub, SecondJoyConsumables
    else if 2Joy3 = D
    GoSub, SecondJoyConsumables
    else
    {
    if 2JoyZ > 70
    {
    if 2JoyY < 30
    SecondJoyMoveForwardAndZoom = PgDn
    else if 2JoyY > 70
    SecondJoyMoveForwardAndZoom = PgUp
    else
    SecondJoyMoveForwardAndZoom =
    }
    else if 2JoyY < 30
    SecondJoyMoveForwardAndZoom = vk57
    else if 2JoyY > 70
    SecondJoyMoveForwardAndZoom = vk53
    else
    SecondJoyMoveForwardAndZoom =
    }

    if SecondJoyMoveForwardAndZoom = %SecondJoyMoveForwardAndZoomPrev%
    return

    SetKeyDelay -1
    if SecondJoyMoveForwardAndZoom
    {
    ControlSend,, {%SecondJoyMoveForwardAndZoom% down}, [#] WoT Client [#]
    }
    if SecondJoyMoveForwardAndZoomPrev
    {
    ControlSend,, {%SecondJoyMoveForwardAndZoomPrev% up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад движение влево/вправо в активном окне

    WatchAxisSecondJoyMoveRotate:
    GetKeyState, 2JoyX, 2JoyX
    GetKeyState, 2Joy2, 2Joy2
    GetKeyState, 2Joy3, 2Joy3
    SecondJoyMoveRotatePrev = %SecondJoyMoveRotate%

    if 2Joy2 = D
    GoSub, SecondJoyConsumables
    else if 2Joy3 = D
    GoSub, SecondJoyConsumables
    else
    {
    if 2JoyX > 80
    SecondJoyMoveRotate = vk44
    else if 2JoyX < 20
    SecondJoyMoveRotate = vk41
    else
    SecondJoyMoveRotate =
    }

    if SecondJoyMoveRotate = %SecondJoyMoveRotatePrev%
    return

    SetKeyDelay -1
    if SecondJoyMoveRotate
    {
    ControlSend,, {%SecondJoyMoveRotate% down}, [#] WoT Client [#]
    }
    if SecondJoyMoveRotatePrev
    {
    ControlSend,, {%SecondJoyMoveRotatePrev% up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад меню расходников

    SecondJoyConsumables:
    SecondJoyConsumablesPrev = %SecondJoyConsumables%

    if 2JoyX < 20
    {
    if 2JoyY < 20
    SecondJoyConsumables = vk38
    else if 2JoyY between 40 and 60
    SecondJoyConsumables = vk37
    else if 2JoyY > 80
    SecondJoyConsumables = vk36
    else SecondJoyConsumables =
    }
    else if 2JoyX between 40 and 60
    {
    if 2JoyY < 10
    SecondJoyConsumables = vk31
    else if 2JoyY > 90
    SecondJoyConsumables = vk35
    else SecondJoyConsumables =
    }
    else if 2JoyX > 80
    {
    if 2JoyY < 20
    SecondJoyConsumables = vk32
    else if 2JoyY between 40 and 60
    SecondJoyConsumables = vk33
    else if 2JoyY > 80
    SecondJoyConsumables = vk34
    else SecondJoyConsumables =
    }
    else SecondJoyConsumables =

    if SecondJoyConsumables = %SecondJoyConsumablesPrev%
    return

    SetKeyDelay -1
    if SecondJoyConsumables
    {
    ControlSend,, {%SecondJoyConsumables% down}, [#] WoT Client [#]
    }
    if SecondJoyConsumablesPrev
    {
    ControlSend,, {%SecondJoyConsumablesPrev% up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад обзор и мышь в активном окне

    WatchAxisSecondJoyCameraRotate:
    MouseNeedsToBeMoved := false
    SetFormat, float, 03
    GetKeyState, 2JoyU, 2JoyU
    GetKeyState, 2JoyR, 2JoyR
    GetKeyState, 2Joy5, 2Joy5

    if 2Joy5 = D
    GoSub, SecondJoyCommandMenu
    else if 2Joy5 = U
    {
    if 2JoyU > %JoyThresholdUpper%
    {
    MouseNeedsToBeMoved := true
    DeltaU := 2JoyU — JoyThresholdUpper
    }
    else if 2JoyU < %JoyThresholdLower%
    {
    MouseNeedsToBeMoved := true
    DeltaU := 2JoyU — JoyThresholdLower
    }
    else
    DeltaU = 0
    if 2JoyR > %JoyThresholdUpper%
    {
    MouseNeedsToBeMoved := true
    DeltaR := 2JoyR — JoyThresholdUpper
    }
    else if 2JoyR < %JoyThresholdLower%
    {
    MouseNeedsToBeMoved := true
    DeltaR := 2JoyR — JoyThresholdLower
    }
    else
    DeltaR = 0
    }

    SetKeyDelay -1
    if MouseNeedsToBeMoved
    {
    SetMouseDelay, -1; Makes movement smoother
    x := (DeltaU/30) * (ABS(DeltaU)/30) * JoyMultiplier
    y := (DeltaR/30) * (ABS(DeltaR)/30) * JoyMultiplier
    DllCall(«mouse_event», uint, 1, int, x, int, y, uint, 0, int, 0)
    }
    return

    ;;;;;;;;;;;; второй геймпад меню приказов

    SecondJoyCommandMenu:
    SecondJoyCommandMenuPrev = %SecondJoyCommandMenu%

    if 2JoyU < 20
    {
    if 2JoyR < 20
    SecondJoyCommandMenu = Numpad8
    else if 2JoyR between 40 and 60
    SecondJoyCommandMenu = Numpad7
    else if 2JoyR > 80
    SecondJoyCommandMenu = Numpad6
    else SecondJoyCommandMenu =
    }
    else if 2JoyU between 40 and 60
    {
    if 2JoyR < 10
    SecondJoyCommandMenu = vk54
    else if 2JoyR > 90
    SecondJoyCommandMenu = Numpad5
    else SecondJoyCommandMenu =
    }
    else if 2JoyU > 80
    {
    if 2JoyR < 20
    SecondJoyCommandMenu = Numpad2
    else if 2JoyR between 40 and 60
    SecondJoyCommandMenu = Numpad3
    else if 2JoyR > 80
    SecondJoyCommandMenu = Numpad4
    else SecondJoyCommandMenu =
    }
    else SecondJoyCommandMenu =

    if SecondJoyCommandMenu = %SecondJoyCommandMenuPrev%
    return

    SetKeyDelay -1
    if SecondJoyCommandMenu
    {
    ControlSend,, {%SecondJoyCommandMenu% down}, [#] WoT Client [#]
    }
    if SecondJoyCommandMenuPrev
    {
    ControlSend,, {%SecondJoyCommandMenuPrev% up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад выстрел в активном окне

    WatchAxisSecondJoyShoot:
    GetKeyState, 2JoyZ, 2JoyZ
    SecondJoyShootPrev = %SecondJoyShoot%

    if 2JoyZ < 30
    SecondJoyShoot = LButton
    else
    SecondJoyShoot =

    if SecondJoyShoot = %SecondJoyShootPrev%
    return

    SetKeyDelay -1
    if SecondJoyShoot
    {
    Send, {%SecondJoyShoot% down}
    }
    if SecondJoyShootPrev
    {
    Send, {%SecondJoyShootPrev% up}
    }
    return

    ;;;;;;;;;;;;; второй геймпад крестовина в активном окне

    WatchSecondJoyPOV:
    GetKeyState, 2JoyPOV, 2JoyPOV
    SecondJoyPOVPrev = %SecondJoyPOV%

    if 2JoyPOV = 0
    SecondJoyPOV = vk52
    else if 2JoyPOV = 18000
    SecondJoyPOV = vk46
    else if 2JoyPOV = 27000
    SecondJoyPOV = vk58
    else if 2JoyPOV = 9000
    SecondJoyPOV = vk43
    else SecondJoyPOV =

    if SecondJoyPOV = %SecondJoyPOVPrev%
    return

    SetKeyDelay -1
    if SecondJoyPOV
    {
    ControlSend,, {%SecondJoyPOV% down}, [#] WoT Client [#]
    }
    if SecondJoyPOVPrev
    {
    ControlSend,, {%SecondJoyPOVprev% up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад LShift в активном окне

    2Joy10::
    {
    ControlSend,, {vkA0 Down}, [#] WoT Client [#]
    KeyWait, 2Joy10
    ControlSend,, {vkA0 Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад Space в активном окне

    2Joy9::
    {
    ControlSend,, {vk20 Down}, [#] WoT Client [#]
    KeyWait, 2Joy9
    ControlSend,, {vk20 Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад выбор снарядов в неактивном окне

    2Joy1::
    Gosub, SecondSubToggle
    Return

    SecondSubToggle:
    SecondToggle++
    If SecondToggle = 1
    {
    ControlSend,, {vk31 down}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk31 up}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk31 down}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk31 up}, [#] WoT Client [#]
    }
    If SecondToggle = 2
    {
    ControlSend,, {vk32 down}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk32 up}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk32 down}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk32 up}, [#] WoT Client [#]
    }
    If SecondToggle = 3
    {
    ControlSend,, {vk33 down}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk33 up}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk33 down}, [#] WoT Client [#]
    Sleep, 10
    ControlSend,, {vk33 up}, [#] WoT Client [#]
    SecondToggle = 0
    }
    return

    ;;;;;;;;;;;; второй геймпад огнетушитель в активном окне

    2Joy4::
    {
    ControlSend,, {vk35 Down}, [#] WoT Client [#]
    KeyWait, 2Joy4
    ControlSend,, {vk35 Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад автоприцел в активном окне

    2Joy6::
    {
    Send, {RButton Down}
    KeyWait, 2Joy6
    Send, {RButton up}
    }
    return

    ;;;;;;;;;;;; второй геймпад меню в неактивном окне

    2Joy8::
    {
    ControlSend,, {vk1B Down}, [#] WoT Client [#]
    KeyWait, 2Joy8
    ControlSend,, {vk1B Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад скрыть мини карту в активном окне

    2Joy7::
    {
    ControlSend,, {vk4D Down}, [#] WoT Client [#]
    KeyWait, 2Joy7
    ControlSend,, {vk4D Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад скрыть мини карту в активном окне

    2Joy5::
    {
    ControlSend,, {vk5A Down}, [#] WoT Client [#]
    KeyWait, 2Joy5
    ControlSend,, {vk5A Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад ремонт

    2Joy3::
    {
    ControlSend,, {vk34 Down}, [#] WoT Client [#]
    KeyWait, 2Joy3
    ControlSend,, {vk34 Up}, [#] WoT Client [#]
    }
    return

    ;;;;;;;;;;;; второй геймпад лечение

    2Joy2::
    {
    ControlSend,, {vk36 Down}, [#] WoT Client [#]
    KeyWait, 2Joy2
    ControlSend,, {vk36 Up}, [#] WoT Client [#]
    }
    return


    Разумеется, играть перед ТВ никто не собирался на клавиатурах/мышах. Управление танками происходит с помощью двух геймпадов от Xbox360. За основу было выбрано управление от версии для Xbox 360.
    Настройки управления Xbox 360
    image


    В общем, у меня получилось как-то так.
    Настройки управления PC

    Выбор типа снарядов переключением — один раз нажал — 1-й тип, второй — 2-й, третий — третий и сброс в начало (1-2-3). снаряды применяются сразу — AHK отдает двукратное нажатие в игру.

    Меню приказов — сочетание левого бампера и правого стика, лечение и ремонт кнопки «Х» и «В» в сочетании с левым стиком.


    Видео геймплея в режиме разделенного экрана:

    Видео записывалось с черновым скриптом — когда было только управление танком, башней, снайперский режим и выстрел пробелом.

    4. Настройка вибраций для геймпадов.
    Здесь я уже отписывал о добавлении вибраций в игру на геймпаде. Так как данная модификация игры использует веб-сервис для отправки вибраций, то для отправки во второй геймпад, нужно было просто изменить порт Flask.

    Но, для игры в «Разделенном экране» на вибрирующих геймпадах нужно запускать в «песочнице» полную копию клиента (скопировать папку рядом с другим именем) со своим отдельным модом, также скопировать в «песочницу» Python27.

    Итог.


    Думается, что данное решение можно попробовать применить ко многим играм. Решение получилось очень неудобным — много всяких «но». Но могу сказать что игра для «фана» удалась. Удачи всем в боях!

    Хочу выразить благодарность Серому форуму и отдельно модератору teadrinker, Korean Random и отдельно inj3ct0r. Спасибо!!! А так же всем тем кто советовал, помогал и поддерживал меня.

    P.S. Выкладываю моды (меню приказов, переключение снайперского режима и вибро) для основного и «песочного» клиентов. Так же в архиве скрипт с полноценным убиранием рамок вокруг клиентов по нажатию сочетания клавиш WinL + LButton.
    Поделиться публикацией

    Комментарии 32

      +1
      Внимание, вопрос, который давно мне не даёт покоя. Как, КАК чёрт побери перенести полноэкранное приложение на другой дисплей? Насколько я понимаю, Virtual Display Manager создаёт именно два виртуальных монитора, но особенности винды таковы, что не позволяют поидее задать в каком мониторе открывать какую программу? Или же просто сама игра умеет это делать из настроек?
        +1
        Из готовых решений — www.ibik.ru/index.php/ru/
        Это полноценное разделение одного компьютера на несколько независимых рабочих мест (каждое со своей клавиатурой, мышкой, монитором).

        Успешно было опробовано именно на играх (win7 x64, под каждое рабочее место своя видеокарта, но последнее не обязательно), работало отлично!

        p.s. помню в каком то win32 sdk шла утилита (кажется multidesktop), где был пример создания виртуальных рабочих столов (переключение по горячим клавишам) каждое — свой список приложений. Так вот метод инициализации экрана содержал структуры, где можно было указать монитор.
          +1
          Всё в настройках системы
          Скриншот
            +1
            Вопрос-то про винду, а не про KDE:)
              –2
              На скриншоте не KDE
                +3
                Теперь мне самому любопытно
                  +1
                  Хм, а что?
                    +3
                    Как что? KDE пропатченный под FreeBSD )

                    #irony, если что.
              0
              Единственное, что приходит в голову — запуск приложения в окне, отрезая рамки, с заданными координатами верхнего левого края окна, координаты на втором мониторе. Скриптик по запуску можно написать на том же AutoHotkey, но так как я не волшебник, а только учусь пока не силен в написании скриптов вот так сразу, могу Вам только направление дать…
                0
                Есть два вида полного экрана, один эксклюзивный, другой оконный. Оконный это просто окно без рамок (borderless fullscreen windowed) растянутое на весь экран. Эксклюзивный режим доступен только D3D приложениям, OGL изначально не имеет (даже в *nix) эксклюзивного режима и всегда работает в костыльном оконном полном экране (но в драйверах вроде есть свои хаки на эту тему для повышения производительности). Эксклюзивный даёт больше ресурсов приложению, лучшую синхронизацию кадров, но при этом сворачивание/разворачивание обычно сопровождается тормозами. Тут немного инфы как раз нагуглилось. Кажется нельзя запустить два эксклюзивных полноэкранных приложения.

                Это было вступление, а теперь по теме.

                Чтобы запустить эксклюзивное полноэкранное приложение на втором мониторе есть 4 способа:
                1. Сделать временно второй монитор основным;
                2. Переключить активный монитор в настройках игры (нормальные игры позволяют задать монитор);
                3. Иногда прокатывает запуск в оконном режиме с перетягиванием на второй монитор и ALT+Enter;
                4. Поискать или написать самому программку перехватывающую вызовы и заставляющая запускать тупые полноэкранные приложения на другом мониторе.

                Приложение в оконном полном экране обычно запускается на мониторе откуда был произведён запуск. Либо через AutoIt, Autohotkey, можно переместить такое окно на второй монитор, очень легко. в Autoit например одной строчкой делается:
                WinMove ("Заголовок окна", "", ширинапервогомонитора, 0, ширинавторого, высотавторого, 0)
                Так кстати часто прокатывает и увеличение рабочего разрешения окна игры, когда игра через настройки отказывается задавать большее разрешение.
                  0
                  Вспомнил про одну утилиту, возможно она Вам поможет — релокатор. Запускает игры без рамок с заданным разрешением в нужных координатах экрана.
                  0
                  Win+Shift+стрелочки.
                  Результат зависит от приложения, чаще всего намертво виснет изображение) Бывает спасает вызов окна UAC или выход на экран выбора пользователя, видимо там сообщение какое-то отсылается приложению на перерисовку экрана.
                  Например, swtor намертво вис, но запоминал монитор на котором был запущен и после перезапуска открывался на нужном мониторе.
                  +3
                  Эх, ностальгия… Знаете, а ваш пост побуждает к разработке игры со старым добрым splitscreen. Такое что-нибудь чисто по олдскулу.
                    –3
                    Хотел плюсик поставить, но кармы нет… Сугубо моё личное мнение — что игры с таким форматом намного более «общительнее», чем каждый в своем темном уголке, за своим монитором в гарнитуре.
                    Вам спасибо, за доброе слово.
                      0
                      Да, вот только люди, пожалуй, становятся менее «общительными». И ленивыми. Зачем идти к другу, чтоб поиграть вдвоем, если есть глобальная сеть?..
                        0
                        Интересно мнение минусующих, неужели сидеть за ПК и играть через интернет лучше? Чем просто с друзьями играть на ТВ. Я как раз имел ввиду, что игры с режимом разделения экрана более «общительные», чем многие онлайн сегодня.
                          0
                          Вот тот же самый вопрос: Диванный гейминг как-то вымер совсем. Файтинги, пожалуй, последний оплот в этом жанре.
                            –1
                            Минусуют за упоминание кармы
                          0
                          На консолях такие игры клепают до сих пор. Я с братьями частенько гоняю во все GOW, Army of Two, COD WAW, MW2 и 3. Разумеется как и на ПК splitscreen практически у всех файтингов и спортивных игр.
                          Для желающих тряхнуть стариной на ПК есть эмуляторы Dendy (NES) и Sega (для последнего продают даже сеговский джой с usb приводом).

                          Но как уже говорили в коментах, люди нынче менее общительны, даже для сеги умудрились замутить сетевой режим)
                          +1
                          Только на заглавном изображении все-таки hotseat.
                            0
                            Согласен, но к теме поста выбранное изображение разве не больше подходит?
                              0
                              VOID
                              +2
                              Можно попробовать запустить 2 виртуальные машины в окнах, подключить 2 клавиатуры и 2 мышки. Каждой виртуалке прописать соотвествующее устройство (или просто сделать маппинг USB).
                                0
                                Это кстати, первое, что мне пришло в голову.
                                WoTSplitScreenVMWare

                                Играть невозможно — в обоих виртуалках 5-10 fps.
                                  +1
                                  Прошу прощения — имел ввиду «танки» и написал обоих, конечно же обеих виртуальных машинах.
                                  0
                                  Я пробовал, к сожалению виртуалки выжимают слишком мало производительности из видеокарты.
                                  0
                                  Есть еще такая софтина PPJoy, она создает виртуальные джойстики в системе, на которые можно замапить 2 разные клавы или мыши
                                    0
                                    А этот эмулятор умеет отправлять нажатия клавиш в неактивное окно Windows?
                                      0
                                      Не скажу про все игры, но когда-то я играл с джойстиком за компом в разного рода эмуляторы (ps2, dreamcast). Они продолжали реагировать и когда окно было не в фокусе. Можно было на одной части экрана полноценно сидеть в браузере, когда как на другой резаться в теккен с геймпадов.
                                      Что же касается PPJoy, ответ на вопрос — нет, он просто создает в системе виртуальные джойстики
                                    0
                                    Уважаемые, а как на счёт split-screen, используя для этого 3D-очки с затвором и 120Hz телевизор так, чтобы каждый игрок видел только свой экран? Существует ли уже какой-либо софт(железные решение), показывающий на одном ТВ два разных виртуальных экрана с двумя полноэкранными (или не очень полноэкранными) играми?
                                      0
                                      Насколько я понял из описания технологии, при включении данной опции в ТВ, экран представляется в системе/для консоли как обычный монитор с разрешением 1920х2160 или 3840х1080. Разделением картинки занимается уже ПО в телевизоре. а каждый игрок видит уже свою часть от обычного монитора.

                                      К сожалению проверить данную теорию не на чем мне — так что это просто предположение.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое