Pull to refresh

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

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


Ностальгия.

К сожалению, в настоящее время многие разработчики игр не добавляют подобного режима — новые игры со 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.
Tags:
Hubs:
+30
Comments 32
Comments Comments 32

Articles