Добавлен , опубликован
Раздел:
Триггеры и объекты
Для воскрешения героя нужно убедиться что он умер. Для этого можно использовать два события:
  • EVENT_PLAYER_UNIT_DEATH
  • EVENT_PLAYER_HERO_REVIVABLE
Они не реагируют на смерть героя с возможностью перерерождения и можно выбирать любую. Задержку между сообщением о гибели и событием EVENT_PLAYER_HERO_REVIVABLE можно настроить в игровых константах:
(DissipateTime) Время разложения (сек.): исчезновение героя
Теперь напишем простейшее воскрешение героя играющего человека в стартовой локации и в дальнейшем будем использовать как шаблон.
local ReviveTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS do
	local player = Player(i)
	if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
		TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
	end
end
TriggerAddAction(ReviveTrigger, function()
	local hero      = GetTriggerUnit()
	local heroOwner = GetOwningPlayer(hero)
	if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
	local x, y = GetPlayerStartLocationX(heroOwner), GetPlayerStartLocationY(heroOwner)
	ReviveHero(hero, x, y, true) -- здесь в дальнейшем будем воскрешать героев
end)

Таймер

Часто используется в AoS картах. Идея проста, после смерти отсчитываем время зависящее от уровня героя и возрождаем. Реализация тоже не очень сложна:
local timer  = CreateTimer()
local dialog = CreateTimerDialog(timer)
TimerDialogSetTitle(dialog, GetUnitName(hero))
TimerStart(timer, GetHeroLevel(hero) * 2, false, function()
	local x, y   = GetPlayerStartLocationX(heroOwner), GetPlayerStartLocationY(heroOwner)
	ReviveHero(hero, x, y, true)
	DestroyTimerDialog(dialog)
	DestroyTimer(GetExpiredTimer())
end)
TimerDialogDisplay(dialog, true)
Код
do
	local InitGlobalsOrigin = InitGlobals
	function InitGlobals()
		InitGlobalsOrigin()
		
		local ReviveTrigger = CreateTrigger()
		for i = 0, bj_MAX_PLAYER_SLOTS do
			local player = Player(i)
			if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
				TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
			end
		end
		TriggerAddAction(ReviveTrigger, function()
			local hero      = GetTriggerUnit()
			local heroOwner = GetOwningPlayer(hero)
			if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end

			local timer  = CreateTimer()
			local dialog = CreateTimerDialog(timer)
			TimerDialogSetTitle(dialog, GetUnitName(hero))
			TimerStart(timer, GetHeroLevel(hero) * 2, false, function()
				local x, y   = GetPlayerStartLocationX(heroOwner), GetPlayerStartLocationY(heroOwner)
				ReviveHero(hero, x, y, true)
				DestroyTimerDialog(dialog)
				DestroyTimer(GetExpiredTimer())
			end)
			TimerDialogDisplay(dialog, true)
		end)
	
	end
end

Камень Воскрешения

Если вы играли в кампанию Рексара, то наверно видели камни возрождения.
А если вы открывали редактор, то наверно заметили, что их несколько.
Отобразим это в коде. Заодно покроем случай, когда вам захочется сделать несколько типов камней для разных локаций.
local stonesId = { FourCC('nbse'), FourCC('nbsw') }
Так как работать с группой разных юнитов будет удобней, чем каждый раз проверять id в цикле, заодно и создадим группу для камней.
local stonesGroup = CreateGroup()
Исходя из того, что для каждого игрока может быть активным только один камень, создадим таблицу, в которой будем хранить активный камень по id игрока.
local playerStone = {}
Учитывая что у игрока может не быть активного камня, а возрождаться ему где-то нужно, напишем функцию поиска ближайшего камня:
---@param x real
---@param y real
---@return unit
local GetClosestStone = function(x, y)
	local stone
	local distance
	for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
		local unit   = BlzGroupUnitAt(stonesGroup, index)
		local dx, dy = GetUnitX(unit) - x, GetUnitY(unit) - y
		local dist   = math.sqrt(dx * dx + dy * dy)
		if stone == nil or dist <= distance then
			stone    = unit
			distance = dist
		end
	end
	return stone
end
Для реализации смены активного камня воспользуемся функцией TriggerRegisterUnitInRange. Правда этот способ не позволяет определить юнита к которому подходят, но мы уже написали прекрасную функцию GetClosestStone, так что мы молодцы проблема решена заранее.
local RangeTrigger    = CreateTrigger()
TriggerAddAction(RangeTrigger, function()
	local hero        = GetTriggerUnit()
	local heroOwner   = GetOwningPlayer(hero)
	local heroOwnerId = GetPlayerId(heroOwner)
	if not UnitAlive(hero)
			or not IsUnitType(hero, UNIT_TYPE_HERO)
			or GetPlayerController(heroOwner) ~= MAP_CONTROL_USER
			or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING
	then return end
	
	if playerStone[heroOwnerId] ~= nil then
		if heroOwner == GetLocalPlayer() then
			AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', false)
		end
		UnitShareVision(playerStone[heroOwnerId], heroOwner, false)
	end
	playerStone[heroOwnerId] = GetClosestStone(GetUnitX(hero), GetUnitY(hero))
	if heroOwner == GetLocalPlayer() then
		AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', true)
	end
	UnitShareVision(playerStone[heroOwnerId], heroOwner, true)
end)
Если вы внимательно читали, то наверное вспомните, что мы создали группу, но так ничего в неё не добавили. Пора исправить этот недостаток:
GroupEnumUnitsInRect(stonesGroup, bj_mapInitialPlayableArea)
for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
	local stone     = BlzGroupUnitAt(stonesGroup, index)
	local stoneId   = GetUnitTypeId(stone)
	local isExclude = true
	for i = 1, #stonesId do
		if stoneId == stonesId[i] then isExclude = false end
	end
	if isExclude then
		GroupRemoveUnit(stonesGroup, stone)
	else
		TriggerRegisterUnitInRange(RangeTrigger, stone, 256)
	end
end
В завершении добавим триггер воскрешения и можно наслаждаться результатом:
local ReviveTrigger = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS do
	local player = Player(i)
	if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
		TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
	end
end
TriggerAddAction(ReviveTrigger, function()
	local hero        = GetTriggerUnit()
	local heroOwner   = GetOwningPlayer(hero)
	local heroOwnerId = GetPlayerId(heroOwner)
	if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
	local stone = playerStone[heroOwnerId] ~= nil and playerStone[heroOwnerId] or GetClosestStone(GetUnitX(hero), GetUnitY(hero))
	ReviveHero(hero, GetUnitX(stone), GetUnitY(stone), true)
end)
Код
do
	local InitGlobalsOrigin = InitGlobals
	function InitGlobals()
		InitGlobalsOrigin()
		
		local stonesId        = { FourCC('nbse'), FourCC('nbsw') }
		local stonesGroup     = CreateGroup()
		local playerStone     = {}
		
		---@param x real
		---@param y real
		---@return unit
		local GetClosestStone = function(x, y)
			local stone
			local distance
			for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
				local unit   = BlzGroupUnitAt(stonesGroup, index)
				local dx, dy = GetUnitX(unit) - x, GetUnitY(unit) - y
				local dist   = math.sqrt(dx * dx + dy * dy)
				if stone == nil or dist <= distance then
					stone    = unit
					distance = dist
				end
			end
			return stone
		end
		
		local RangeTrigger    = CreateTrigger()
		TriggerAddAction(RangeTrigger, function()
			local hero        = GetTriggerUnit()
			local heroOwner   = GetOwningPlayer(hero)
			local heroOwnerId = GetPlayerId(heroOwner)
			if not UnitAlive(hero)
					or not IsUnitType(hero, UNIT_TYPE_HERO)
					or GetPlayerController(heroOwner) ~= MAP_CONTROL_USER
					or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING
			then return end
			
			if playerStone[heroOwnerId] ~= nil then
				if heroOwner == GetLocalPlayer() then
					AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', false)
				end
				UnitShareVision(playerStone[heroOwnerId], heroOwner, false)
			end
			playerStone[heroOwnerId] = GetClosestStone(GetUnitX(hero), GetUnitY(hero))
			if heroOwner == GetLocalPlayer() then
				AddUnitAnimationProperties(playerStone[heroOwnerId], 'alternate', true)
			end
			UnitShareVision(playerStone[heroOwnerId], heroOwner, true)
		end)
		
		GroupEnumUnitsInRect(stonesGroup, bj_mapInitialPlayableArea)
		for index = BlzGroupGetSize(stonesGroup) - 1, 0, -1 do
			local stone     = BlzGroupUnitAt(stonesGroup, index)
			local stoneId   = GetUnitTypeId(stone)
			local isExclude = true
			for i = 1, #stonesId do
				if stoneId == stonesId[i] then isExclude = false end
			end
			if isExclude then
				GroupRemoveUnit(stonesGroup, stone)
			else
				TriggerRegisterUnitInRange(RangeTrigger, stone, 256)
			end
		end
		
		local ReviveTrigger = CreateTrigger()
		for i = 0, bj_MAX_PLAYER_SLOTS do
			local player = Player(i)
			if GetPlayerController(player) == MAP_CONTROL_USER and GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING then
				TriggerRegisterPlayerUnitEvent(ReviveTrigger, player, EVENT_PLAYER_HERO_REVIVABLE)
			end
		end
		TriggerAddAction(ReviveTrigger, function()
			local hero        = GetTriggerUnit()
			local heroOwner   = GetOwningPlayer(hero)
			local heroOwnerId = GetPlayerId(heroOwner)
			if GetPlayerController(heroOwner) ~= MAP_CONTROL_USER or GetPlayerSlotState(heroOwner) ~= PLAYER_SLOT_STATE_PLAYING then return end
			local stone = playerStone[heroOwnerId] ~= nil and playerStone[heroOwnerId] or GetClosestStone(GetUnitX(hero), GetUnitY(hero))
			ReviveHero(hero, GetUnitX(stone), GetUnitY(stone), true)
		end)
	
	end
end

Заключение

Как видите, воскрешать героя не так уж и сложно, но если у вас есть вопросы или вы знаете интересные способы воскрешения, то пишите их в комментариях. Также можете скачать карту и поэксперементировать.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
2
24
5 лет назад
Отредактирован prog
2
Использовать поиск ближайшего камня в триггере входа в радиус это немного оверкил - я бы скорее хранил координаты всех камней в списке и перебирал его простой проверкой на нахождение точки в прямоугольнике - мы ведь гарантированно находимся на определенном расстоянии от камня в момент когда происходит эта проверка и нам нет необходимости реально искать ближайший камень, достаточно определить возле какого мы находимся - минус лишняя математика вычисления расстояния и минус лишние итерации в цикле если наш камень не последний в массиве.
0
29
5 лет назад
Отредактирован nazarpunk
0
Использовать поиск ближайшего камня в триггере входа это немного оверкил
Поиск ближайшего камня ещё используется для случая, если у героя ещё нет активного камня. Так что это обыкновенное переиспользование кода. Притом подход к камню не такое частое событие, а перебор группы не такой ресурсоёмкий, чтоб экономить на спичках во время пожара)
1
24
5 лет назад
1
NazarPunk, я понимаю где еще это используется, но между экономией лишних десяти строк кода или экономией долей миллисекунды выполнения кода я выберу второе ;)
Ну и да, воскрешение у ближайшего камня не всегда хорошая идея - если, например, ближайший камень находится в каком-то секретном или временно непроходимом месте и у игрока еще нет активного камня, то результат будет плачевным - я скорее принудительно выдавал бы игроку какой-то камень на старте и не парился обработкой кейса "камня нет", а в таком случае и поиск ближайшего камня в коде не появляется и можно смело "экономить на спичках" т.к. нет реюза функции поиска ближайшего камня ;)
0
29
5 лет назад
Отредактирован nazarpunk
0
Преждевременная оптимизация — корень всех зол.
В статье показан принцип действия с минимальным количеством кода. В реальной карте могут быть дополнительные условия и ограничения, только после которых есть смысл заниматься оптимизацией.
0
24
5 лет назад
Отредактирован prog
0
NazarPunk, если бы я занимался оптимизацией - подумал бы как затолкать эти камни в подобие бинарного дерева чтобы ускорить поиск xD

или вы знаете интересные способы воскрешения
Чтобы разбавить критику конструктивом - расскажу свой способ воскрешения.
Это вариация воскрешения у камней, но с нюансами. Главное отличие - триггер привязки камня реализован через покупку предмета в магазине, а не через вход в радиус. Это позволяет ходить возле "камня" не боясь случайно перепривязаться в неудобном месте и заодно позволяет добавить стоимость к процессу привязки камня. А в моем случае я использую еще и перезарядку продажи, чтобы несколько игроков не могли без задержки привязаться к одному камню.
0
29
5 лет назад
0
Главное отличие - триггер привязки камня реализован через покупку предмета в магазине
Тогда всё конечно проще - используешь камень на камень и ненужно искать ближайший камень))
0
24
5 лет назад
0
Тогда всё конечно проще - используешь камень на камень и ненужно искать ближайший камень))
Все еще проще - камень продает предметы и привязкой считается момент продажи, после чего предмет удаляется)
0
29
5 лет назад
Отредактирован nazarpunk
0
Все еще проще - камень продает предметы и привязкой считается момент продажи, после чего предмет удаляется)
Таким образом можно сделать привязку к другим камням. А потом всего-то нужно умереть для телепортации))
0
24
5 лет назад
0
Таким образом можно сделать привязку к другим камням. А потом можно умереть вместо телепортации))
Герой покупает специальный предмет (или юнита) в точке воскрешения. В момент покупки купленый предмет удаляется, а герой привязывается к точке где купил предмет - никакого способа привязаться к точке не находясь в радиусе действия её магазина.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.