WarCraft 3: GetLocalPlayer()

» Раздел: Триггеры и объекты
Все мы знаем о существовании native-функции GetLocalPlayer:
constant native GetLocalPlayer takes nothing returns player
Она ничего не берет, но возвращает локального игрока, то есть того, на компьютере которого запущена карта. То есть, если вы запустите ее в мультиплеере, на компьютере первого игрока функция вернет хендл идентичный Player(0), на компьютере второго - Player(1) и так далее.
Все мы когда-нибудь использовали GetLocalPlayer(), к примеру, для перемещения камеры игрока или для показа локального плавающего текста, но знали ли вы, какой на самом деле потенциал скрывает эта простая функция?
Позвольте мне пояснить. Мы знаем, что неправильное использование данной функции приводит к десинхронизации клиентов в мультиплеере и последующему разрыву связи. Но знаете ли вы, какие именно действия являются неправильными? Это любые действия, создающие объекты, наследующие тип handle, будь это юнит или destructable или влекущие за собой их создание. Действия, относящиеся к визуальной части игры, не вызывают десинхронизации.
Приведу пример.
if (GetLocalPlayer() == Player(0)) then
	call CreateUnit(Player(0), 'hpea', 0., 0., 0.)
endif
Вызовет десинхронизацию, так как данным действием мы локально создаем работника для первого игрока, не создавая его у остальных.
if (GetLocalPlayer() == Player(0)) then
	call SetCameraPosition(200., 200.)
endif
Данный код переместит камеру красного игрока в позицию (200;200) в декартовой системе координат. Десинхронизации не будет, так как никаких объектов не создается.
Но это лишь поверхностная сторона, рассмотрим более интересные возможности:
Почти все мы играли в карту DotA и знаем, что некоторые способности в этой карте имеют визуальный эффект, видимый не всем игрокам. Но как реализовать такое, без угрозы десинхронизации? Воспользуемся этим кодом:
function AddLocalEffect takes string eff, real x, real y, player pl returns effect
	local string e = ""
	if (GetLocalPlayer() == pl) then
		set e = eff
	endif
	return AddSpecialEffect(e, x, y)
endfunction
Примечание: до этого вы видели использование GetLocalPlayer только в условиях, но что мешает использовать его и в других местах? Например, так:
local multiboard mb = CreateMultiboard()
call MultiboardDisplay(mb, GetLocalPlayer() == Player(0))
Эти действия создадут мультиборд, видимый только красному игроку.
Или так:
local force f = CreateForce()
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
call ForceAddPlayer(f, Player(1))
call ForceAddPlayer(f, Player(3))
if (IsPlayerInForce(GetLocalPlayer(), f)) then
	call SetUnitVertexColor(u, 255, 255, 255, 128)
endif
Данные действия создадут крестьянина и сделают его полупрозрачным для 2-го и 4-го игрока.
Или вот так:
local multiboard mb = CreateMultiboard()
local integer id = GetPlayerId(GetLocalPlayer())
call MultiboardSetTitleText(GetUnitName(Hero[id])) // Hero - массив юнитов
Эти действия создадут мультиборд и в заголовке для каждого игрока покажут имя его героя.
Примечание: старайтесь не использовать GetLocalPlayer в действиях показа текста. Во-первых в определенных условиях это может вызвать непредвиденную десинхронизацию, а во-вторых - текст, показанный таким образом не будет виден в реплее игры.
Вы уже наверняка знаете про существование Preload Exploit, позволяющего сохранять данные на диск. Но что если, например, нужно сохранить некие данные определенному игроку? GetLocalPlayer поможет нам и тут! Воспользуемся следующим кодом:
if (GetLocalPlayer() == Player(0)) then
	call PreloadGenClear()
	call PreloadGenStart()
	call PreloadGenEnd("\\code.txt")
endif
Это сохранит на диск с игрой первого игрока почти пустой файл code.txt. Довольно полезная возможность для карт типа ORPG, например.
Рассмотрим еще вот какую вещь:
Предположим, мы хотим узнать положение камеры игрока и пользуемся функцией такого рода:
function GetPlayerCameraX takes player p returns real
	if (GetLocalPlayer() == p) then
		return GetCameraTargetPositionX()
	endif
	return 0.
endfunction
Но что делать, если мы хотим, например, создать в позиции камеры юнита? Данные-то будут локальны. В таком случае, нам придется воспользоваться синхронизацией локальных данных. Вещь это довольно сложная и, что довольно важно, не мгновенная. Воспользуемся следующим кодом:
globals
	gamecache cache = InitGameCache("cache")
endglobals

function SyncReal takes player p, real val returns real
	if (GetLocalPlayer() == p) then
		call StoreReal(cache, "", "", val)
	endif
	call TriggerSyncStart()
	if (GetLocalPlayer() == p) then
		call SyncStoredReal(cache, "", "")
	endif
	call TriggerSleepAction(2.)
	call TriggerSyncReady()
	return GetStoredReal(cache, "", "")
endfunction

// Используем примерно так:
local player p = Player(0)
local real local_val = GetPlayerCameraX(p)
local real result_var = SyncReal(p, local_val)
call CreateUnit(p, 'hfoo', result_var, 0., 0.)
Внимание: Код функции SyncReal очень примерный и неуниверсальный, читателю рекомендуется самому доработать и протестировать данную функцию для получения возможности, например, одновременной синхронизации нескольких переменных.
Вот возможность, которая была бы полезна, например, в картах жанра "Мафия". В таких картах мы имеем некоего убийцу и граждан, которым нужно найти убийцу. Чтобы сохранять анонимность, убийца имеет абсолютно идентичную с гражданами модель и имя. Но что, если мы хотим, чтобы игрок-убийца визуально воспринимал своего героя по-другому? Поступим так:
Создадим двух абсолютно идентичных юнитов на основе нашего "гражданина". Пускай их равкоды будут 'h000' и 'h001' соответственно. Изменим у второго юнита имя на "Убийца" и модель на грабителя.
При создании юнитов, воспользуемся следующим кодом:
local integer i = 'h000'
local integer l = 0
loop
	exitwhen (l == 11)
        if (l == 0 and GetLocalPlayer() == Player(0)) then
               set i = 'h001'
        endif
	call CreateUnit(Player(l), i, 0., 0., 0.)
        set i = 'h000'
	set l = l + 1
endloop
Для каждого игрока в центре карты будут созданы юниты. Но, внимание, первый игрок будет видеть у своего юнита модель грабителя и имя "Убийца", остальные же игроки этого видеть не будут.
Внимание: у этого метода есть опасная сторона: если мы будем проводить сравнения с GetUnitTypeId(unit), выдающей равкод юнита или с GetUnitName(unit), будут выданы локальные значения, пользуйтесь этим с осторожностью.
То же самое работает с предметами, декорациями, разрушаемыми объектами и способностями. Причем способности могут использовать даже разные баффы, что дает возможность одному игроку видеть, например, буран, а второму - огненный дождь.
Надеюсь, данная статья помогла вам понять поистине огромный потенциал использования локального игрока в вашем коде и понять, какие возможности может дать эта функция. Удачи в ваших начинаниях; но помните, что главное - осторожность и рациональность.

Просмотров: 25 698

» Лучшие комментарии


CeDiL #1 - 103 месяца назад 4
отличная статья) хотелось бы ешё прочитать про функцию!
Rewenger #2 - 103 месяца назад 5
Могу сказать лишь "как вовремя!" по данной статье. Но, тем не менее, она полезна.
Для создателей карт в мультиплеере.
VetsaN #3 - 103 месяца назад 7
О ужас! Одна ошибка и десинка не избежать! Придется напрячь сразу два мозга...
Fatal #4 - 103 месяца назад 2
То что нужно! Спасибо!
Fakov #5 - 103 месяца назад 1
Типа теперь можно сохранять код героя в орпг в текстовый файл и загружать из него же?
Doc #6 - 103 месяца назад 2
Всегда можно было. Ну загружать дело опасное, +нужны включенные локальные файлы у игрока. Я, например, только сохраняю.
Suite #7 - 103 месяца назад 2
док малоч. Статья то что искал, особенно про мультиборд.
ZeToX2007 #8 - 103 месяца назад 2
Привел бы примеры: как определить хоста(если он есть), как узнать игрока, нажавший на trackable (причем если он 1, а игроков 12, а не оборот) а так ничего нового, всё давно обсуждалось.
Doc #9 - 103 месяца назад 2
ZeToX2007, на то и статьи, чтобы свести все воедино ! а насчет тех примеров, что ты привел, я таких хитростей не знаю, честно говоря. Поделись-ка.
SLI #10 - 103 месяца назад 2
супер, большое спасибо! Как раз были с этим проблемы
Rewenger #11 - 103 месяца назад 2
ZeToX2007, продолжу список вопросов: как задействовать шейдеры в варе с использованием одного лишь редактора триггеров, как сделать чтобы один объект корректно освещался произвольно большим числом источников освещения...
agentex #12 - 103 месяца назад 2
загружать нельзя
Hellfim #13 - 103 месяца назад 3
Дельная статья. Узнал кое-что новое =)
Doc #14 - 103 месяца назад 2
agentex, да емае у меня работает датаменеджер 8 секунд загрузка, 30 значений, все идеально.
Helpmeplz #15 - 102 месяца назад 2
globals
constant player LocalPlayer = GetLocalPlayer()
endglobals
у меня после такого на экране предосмотра карты варкрафт крашится
Rewenger #16 - 102 месяца назад (отредактировано ) 2
Вы уверены, что это нужно заносить в константы, а не переменные? Я не проверял, но выглядит сомнительно.
Doc #17 - 102 месяца назад 2
все нормально и в игре и на экране предпросмотра.
bee #18 - 102 месяца назад 2
ставлю сердечко, мне нравиться.
ZeToX2007 #19 - 102 месяца назад 2
Doc, по таймеру и ТриггерСлипАктион, и по событию, допустим выбор юнита, считаем разницу во времени старта и конца. Это ещё было известно 2 года назад(по крайней мере мне)
Чтобы узнать кто отдал юниту приказ, общему юниту или нажатия на тркбл опр игроку тут сложнее, теоретически возможно. Суть в том что нужно создавать особый триггер, для каждого игрока индивидуально передаются данные, связь между хостом и игроком сделавший действия, после чего записываем переменную значение, потом начинаем синхронизировать её между всеми игроками, тестил в батл нете... между 2 игроками работает это криво, но 3 - 4 норм.
agentex, ты не умеешь синхронизировать данные -_- поэтому не можешь загрузить. 200 значений загружал прекрасно. Просто не юзай ту что на форуме, а создай свою.
Hares #20 - 102 месяца назад (отредактировано ) 3
» Не губите внешний вид комментов
local integer i = 0
local integer a = 255
local unit u = CreateUnit(GetOwningPlayer(GetSpellAbilityUnit()), x, y, angle)

if not(GetLocalPlayer() == GetOwningPlayer(GetSpellTargetUnit())) then
	set a = 0
	set i = 'Aloc'
endif
call UnitAddAbility(u,i)
call UnitAddAbility(u,l)
call SetUnitVertexColor(u, 255, 255, 255, a)

call TriigerSleepAction(time)

call RemoveUnit(u)
set u = null
Поясню:
Мне нужно сделать юнита невидимым, невыбераемым и неатакумым для всех игроков, кроме владельца цели заклинания.
А ещё мне нужно, чтоб цель заклинания могла бить врагов-галлюцинаций. (а другие на ето спокойно(!) смотрели)
Будет работать, а?
сори, невыбИраемым
Doc #21 - 102 месяца назад 2
Hares, работать будет, но будет и десинк, такие дела.
ZeToX2007 #22 - 102 месяца назад 2
Можно уменьшить модель до 0, полностью прозрачным сделать, а вот чтобы не выбираемый, была давненько тема на форуме там и ищи.
Кстати можно попробовать, чтобы переместить локально юнита, но тут нужно учесть много условий, чтобы его никаким образом не воспринимали другие юниты, обзор его по 0, желательно чтобы он был нейтральный. Короче нужно сделать так, чтобы он не воспринимался игрой никаким образом.. Это можно пригодится, если делаешь Дгуи для нормальной камеры.
Suite #23 - 102 месяца назад 2
даешь пример
Msey #24 - 101 месяц назад 2
пример залей например
wetalq #25 - 94 месяца назад 1
Классная статья :)
Leiva #26 - 94 месяца назад 1
В последнем примере кода (где создаётся модель убийцы в "мафии") допущена ошибка: на компьютере игрока-убийцы все созданные юниты будут иметь модель убийцы.
Doc #27 - 94 месяца назад 1
Согласен, исправил.
Leiva #28 - 94 месяца назад 1
Видимо неудачно, поскольку я как ни старался, отличий не нашёл))
Doc #29 - 94 месяца назад 1
Не знаю, может ты смотрел старую версию статьи. Посмотри сейчас, должно быть все нормально.
Leiva #30 - 94 месяца назад 1
Да, сейчас всё нормально.
AlisherYch #31 - 86 месяцев назад (отредактировано ) 0
Вы, конечно, простите, но я так и не понял как и где это использовать:
Примечание: вообще, довольно полезно было бы занести значение GetLocalPlayer() в переменную, дабы избежать лишние вызовы. Например, так:
globals
constant player LocalPlayer = GetLocalPlayer()
endglobals
Далее, с помощью cJass'а, мы можем оптимизировать уже имеющуюся карту, без ручной замены переменных, так:
#define <GetLocalPlayer()> = LocalPlayer
читал про cJass , вопросов не поубавилось ...
Nerevar #32 - 86 месяцев назад 0
ну таким образом из буханки хлеба все вызовы функции GetLocalPlayer() получается заменяются троллейбус на обращение к переменной LocalPlayer ,но зачем?,в которой записано значение функции GetLocalPlayer()
AlisherYch #33 - 86 месяцев назад 0
Это -то понятно. Но каким именно "таким образом"?
Romeno #34 - 85 месяцев назад (отредактировано ) 1
Статья интересная и полезная, но про синхронизацию локальных данных я практически не понял. Что делает TriggerSyncStart(), SyncStoredReal() и т.п. осталось загадкой..!
Doc #35 - 85 месяцев назад 0
Romeno, для меня тоже. Но это работает.
beril #36 - 85 месяцев назад 0
интересненько
map_maiker #37 - 85 месяцев назад 0
суупер
Danial #38 - 81 месяц назад 0
Зачем вы намерено вносите ошибки в программу?
Зачем вы поставили лишние точки в строке call CreateUnit(Player(0), 'hpea', 0., 0., 0.) ?
Для того, чтобы создать лишний геморрой начинающим джассерам? Вы далеко не первый, кто этим злоупотребляет. Что за дуратская мода? Может хотя бы вы мне это объясните?
Doc #39 - 81 месяц назад 0
Danial, и где же тут ошибка?
ScorpioT1000 #40 - 81 месяц назад 0
Danial, эти точки не лишние, они обозначают, что мы передаем float вместо int
читайте Передача и хранение данных в варкрафте
SomeFire #41 - 81 месяц назад 0
Doc, весь комментарий надо читать, а не первое предложение) По-моему, он ясно указал на то, что считает ошибкой: "лишние точки"
Doc #42 - 81 месяц назад 0
SomeFire, я и читал весь, только "лишние" точки ошибкой никогда не были.
SomeFire #43 - 81 месяц назад 0
Doc, он сказал, что считает их лишними.
ScorpioT1000 #44 - 81 месяц назад 0
Я тут поделал вещи на практике и узнал много нового про локального игрока =) мб что-то напишу потом
Razefon2000 #45 - 77 месяцев назад -1
Отличная статья, теперь знаю как сделать спецэффекты, фильтры для игрока.
Daro #46 - 70 месяцев назад 0
Спасибо за статью,теперь я знаю не только как сделать локальный спецэффект
CaptainFox #47 - 70 месяцев назад 0
Daro, если это первая твоя изученная статья на сайте, то ты явно не с того начал.
3 комментария удалено