[lua] Всё о визуализации способностей в Warcraft3

Содержание:

Рисуем стрелочки и прочие маркеры попиксельно.

Вот этим вот пикселем
Почему попиксельно? да потому что не завезли функцию для поворота изображения, ну а что нам мешает делать свои?
В статье про квадратные изображения, мы рисовали маркеры целиком, теперь же я покажу как это делает отдельными блоками.
Функция для отрисовки конуса
function CreateVisualConusForUnitBySplat(hero,flag,long,step,range,angleSector)
	local image={}
	local image2={}
	local pid=GetPlayerId(GetOwningPlayer(hero))
	local data=HERO[pid]
	local size=step/3
	local r60=70//size
	local r40=50//size
	local LastMouseX=0

	for i=1,long*2 do
		image[i]=CreateImage(ImportPath.."\\pointerORIG",16,16,16,4000,4000,0,0,0,150,4)
		SetImageColor(image[i],0,255,0,128)
		SetImageRenderAlways(image[i], true)
		if GetLocalPlayer()~=Player(pid) then
			ShowImage(image[i],false)
		else
			ShowImage(image[i],true)
		end

	end

	local distance=0
	local mouseMoving=false
	local savedDistance=0
	local lastAngle=0
	local delta=0
	local angle=0
	local function Destroy()
		DestroyTimer(GetExpiredTimer())
		--data.MarkIsActivated=true
		--print("destroy")
		for i=1,#image do
			DestroyImage(image[i])
			DestroyImage(image2[i])
		end
	end
	local curAngle=GetUnitFacing(hero)
	local iter=0
	local curBlock=0
	TimerStart(CreateTimer(), TIMER_PERIOD, true, function()
		local xs,ys=MoveXY(GetUnitX(hero)-16,GetUnitY(hero)-16,0,curAngle)--стартовое смещение и это центр юнита
		iter=iter+1
		angle=AngleBetweenXY(xs, ys, GetPlayerMouseX[pid], GetPlayerMouseY[pid])/bj_DEGTORAD--data.AngleMouse
		curAngle=lerpTheta(curAngle,angle,TIMER_PERIOD*8)
		if LastMouseX == GetPlayerMouseX[pid] then
			mouseMoving=false
		else
			mouseMoving=true
		end
		LastMouseX = GetPlayerMouseX[pid]
		delta=curAngle-lastAngle
		lastAngle=curAngle
		if mouseMoving then
			distance=DistanceBetweenXY(GetPlayerMouseX[pid],GetPlayerMouseY[pid],GetUnitXY(hero))
			savedDistance=DistanceBetweenXY(GetPlayerMouseX[pid],GetPlayerMouseY[pid],GetUnitXY(hero))
		else
			distance=savedDistance
		end
		local block=0
		for _=1,#image do
			distance=distance-step
			if distance>=0 then
				block=block+1
			end
		end
		local k=0
		local k2=0
		local a=0
		local k3=0
		local pointToRange=range
		local pointToRange2=range*.87
		for i=1,#image do
			if i<=angleSector then
				--рисуем полукруг
				a=a+step
				k=k+1
				local nx,ny=MoveXY(xs,ys,range,-angleSector/2+curAngle+a)
				SetImagePosition(image[i],nx,ny,0)
			else
				local rxs,rys=MoveXY(xs,ys,range,-angleSector/2+curAngle+k*step)
				local angleR=AngleBetweenXY(rxs,rys,xs, ys)/bj_DEGTORAD
				k2=k2+1
				pointToRange=pointToRange-step
				if pointToRange>0 then
					local nx,ny=MoveXY(rxs,rys,step*k2,angleR)
					SetImagePosition(image[i],nx,ny,0)
				else
					k3=k3+1
					pointToRange2=pointToRange2-step
					if pointToRange2>0 then
						local nx,ny=MoveXY(rxs,rys,step*k2,angleR)
						local nx2,ny2=MoveXY(nx,ny,step*k3,angleR-angleSector*2)
						SetImagePosition(image[i],nx2,ny2,0)
					end

				end
			end
		end

		if flag==1 then
			if not data.MarkIsActivated then
				Destroy()
			end
		end
	end)
end
Результат
Конус выдран из адмирала и не адаптирован под импорт
Функция для отрисовки динамической стрелки
---@param hero "герой"
---@param flag "Параметры_завершения"
---@param long "Число_блоков"
---@param step "Ширина_блока"
---@param minlong "Милимальное_число_отображаемых_блоков
function CreateVisualPointerForUnitBySplat(hero, flag, long, step, minlong)
	local image = {}
	local image2 = {}
	local pid = GetPlayerId(GetOwningPlayer(hero))
	--local data = HERO[pid]
	local size = step / 3
	local r60 = 70 // size
	local r40 = 50 // size
	local LastMouseX = 0

	long=long//step
	minlong=minlong//step

	for i = 1, long do
		image[i] = CreateImage("pointerORIG", 16, 16, 9999, 4000, 4000, 0, 0, 0, 0, 4)
		SetImageColor(image[i], 0, 255, 0, 128)
		SetImageRenderAlways(image[i], true)
		if GetLocalPlayer() ~= Player(pid) then
			ShowImage(image[i], false)
		else
			ShowImage(image[i], true)
		end
		image2[i] = CreateImage("pointerORIG", 16, 16, 9999, 4000, 4000, 0, 0, 0, 0, 4)
		SetImageColor(image2[i], 0, 255, 0, 128)
		SetImageRenderAlways(image2[i], true)
		if GetLocalPlayer() ~= Player(pid) then
			ShowImage(image2[i], false)
		else
			ShowImage(image2[i], true)
		end
	end
	local distance = 0
	local mouseMoving = false
	local savedDistance = 0
	local lastAngle = 0
	local delta = 0
	local angle = 0
	local function Destroy()
		DestroyTimer(GetExpiredTimer())
		for i = 1, #image do
			DestroyImage(image[i])
			DestroyImage(image2[i])
		end
	end
	local curAngle = 180 + AngleBetweenXY(GetPlayerMouseX[pid], GetPlayerMouseY[pid], GetUnitXY(hero)) / bj_DEGTORAD--GetUnitFacing(hero)
	local iter = 0
	local curBlock = 0
	TimerStart(CreateTimer(), TIMER_PERIOD, true, function()
		local rxs, rys = GetUnitXY(hero)
		if flag == 2 then
			--rxs, rys = data.xStand, data.yStand
		end
		local xs, ys = MoveXY(rxs - 16, rys - 16, 40, curAngle)--стартовое смещение и это центр юнита
		local xs2, ys2 = 0, 0
		iter = iter + 1
		xs, ys = MoveXY(xs, ys, 40, curAngle + 90)
		xs2, ys2 = MoveXY(xs, ys, 80, curAngle - 90)
		local errAngle = 2.5
		if flag == 2 then
			--errAngle = 5
		end
		angle = errAngle + AngleBetweenXY(xs, ys, GetPlayerMouseX[pid], GetPlayerMouseY[pid]) / bj_DEGTORAD--data.AngleMouse
		local distMouse = DistanceBetweenXY(GetPlayerMouseX[pid], GetPlayerMouseY[pid], rxs, rys)
		--print(distMouse)
		if distMouse >= 90 then
			curAngle = lerpTheta(curAngle, angle, TIMER_PERIOD * 8)
		end
		if LastMouseX == GetPlayerMouseX[pid] then
			mouseMoving = false
		else
			mouseMoving = true
		end
		LastMouseX = GetPlayerMouseX[pid]
		delta = curAngle - lastAngle
		lastAngle = curAngle
		if mouseMoving then
			distance = DistanceBetweenXY(GetPlayerMouseX[pid], GetPlayerMouseY[pid], rxs, rys)
			savedDistance = DistanceBetweenXY(GetPlayerMouseX[pid], GetPlayerMouseY[pid], rxs, rys)
		else
			distance = savedDistance
		end
		local block = 0
		for _ = 1, #image do
			distance = distance - step
			if distance >= 0 then
				block = block + 1
			end
		end
		if block <= 61 then
			block = 61
		end

		curBlock = R2I(lerpTheta(curBlock, block, TIMER_PERIOD * 16))

		if minlong ~= nil then
			if minlong >= curBlock then
				curBlock = minlong
			end
		end

		local k = 0
		local k2 = 0
		for i = 1, #image do
			if i < curBlock then
				local nx, ny = 0, 0
				if i >= curBlock - r60 and i <= curBlock - r40 then
					--print(i.."поворот на 90")
					k = k + 1
					local axs, ays = MoveXY(xs, ys, (curBlock - r60) * step, curAngle)
					nx, ny = MoveXY(axs, ays, step * k, curAngle + 90)
					SetImagePosition(image[i], nx, ny, 0)
					local axs2, ays2 = MoveXY(xs2, ys2, (curBlock - r60) * step, curAngle)
					nx, ny = MoveXY(axs2, ays2, step * k, curAngle - 90)
					SetImagePosition(image2[i], nx, ny, 0)
				else
					if i >= curBlock - r40 then
						local axs, ays = MoveXY(xs, ys, (curBlock - r60) * step, curAngle)
						local axs2, ays2 = MoveXY(xs2, ys2, (curBlock - r60) * step, curAngle)
						local px, py, px2, py2 = 0, 0, 0, 0
						px, py = MoveXY(axs, ays, step * k, curAngle + 90)
						px2, py2 = MoveXY(axs2, ays2, step * k, curAngle - 90)
						k2 = k2 + 1
						if curBlock <= r60 then
							--print("лишняяотрисовка")
							SetImagePosition(image[i], OutPoint, OutPoint, 0)
							SetImagePosition(image2[i], OutPoint, OutPoint, 0)
						else
							nx, ny = MoveXY(px, py, step * (k2), curAngle - 45)
							SetImagePosition(image[i], nx, ny, 0)
							nx, ny = MoveXY(px2, py2, step * (k2), curAngle + 45)
							SetImagePosition(image2[i], nx, ny, 0)
						end
					else
						nx, ny = MoveXY(xs, ys, step * i, curAngle)--вот так всё отлично работает
						SetImagePosition(image[i], nx, ny, 0)
						nx, ny = MoveXY(xs2, ys2, step * i, curAngle)
						SetImagePosition(image2[i], nx, ny, 0)
					end
				end
			else
				SetImagePosition(image[i], OutPoint, OutPoint, 0)
				SetImagePosition(image2[i], OutPoint, OutPoint, 0)
			end
		end
		if flag == 1 then
			if not MarkerIsON[GetHandleId(hero)] then
				Destroy()
			end
		end
	end)
end
Результат

Пояснения к коду

Через параметр step, задаётся плотность наполнения пикселями, чем этот параметр меньше, тем ярче и плотнее стрелка. Фигуры можно рисовать любые главное придумать формулу и каждый кадр перерисовывать её относительно угла поворота курсора.
Заполнение 1
Заполнение 5
Заполнение 10
Чем меньше пикселей, тем слабее нагрузка, можно использовать для слабых ПК, хотя и так общее падение fps на моем ПК 10 летней давности с 360, до 340, на step=1
В общем вызываем функцию через
CreateVisualPointerForUnitBySplat(hero,1,900,5,900)
где
long (900) - максимальная длина стрелки
step (5) - шаг и качество
minlong (900) - минимальная длина стрелки
Если максимальное и минимальное значения указать разными, то стрелка будет трансформироваться согласно положению курсора, в нашем случае для волны силы это не нужно, ведь её длина всегда максимальна
И карта пример по кнопке получить

`
ОЖИДАНИЕ РЕКЛАМЫ...
0
32
4 года назад
0
Если кому то нужны другие фигуры, обращайтесь

И чуть не забыл про минусы, это артефакты, которые видны на высоком рельефе с обратной стороны камеры, поэтому этот способ я не считаю своим любимым, но зато он идеально работает на плоском рельефе в тумане войны.
Если есть способы как избежать артефактов, то буду рад этой помощи
Чтобы оставить комментарий, пожалуйста, войдите на сайт.