Начал делать мультишот, но возникла проблема. Можно использовать способность ракеты, но там менее реалистично получаются выстрелы, да и стреляют даже за спину. Поэтому решил сделать свой.
Проблема вот в чем, когда юнит начинает стрелять или кастовать в одного противника, можно определить вектор. Относительно этого сложно подобрать формулу подбора всех юнитов. Не силен в косинусах, синусах, тем более в полярных координатах. Приложен пример в картинке. Помогите.

Принятый ответ

вот система Raised
раскрыть
Загруженные файлы

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
1
29
8 лет назад
1
Читаем пока не поймем.
9
27
6 лет назад
Отредактирован MpW
9
Решил описать как определить находится вражеский юнит в обзоре нашего героя. Когда делал, я не знал как. Но сейчас нашел несколько вариантов.
Прежде всего это работа с группой. Мы выделяем всех юнитов вокруг в группу. Есть фильтры, которыми можно отсеивать не нужных. Проблема в том, что нужно определить кто попал, не попал. Относительно, все работает от угла обзора, точнее угла поворота юнита (куда смотрит юнит). Существует функция GetUnitFacing, относительно нее и будет делать все. Щас надо придумать как определить. находится ли юнит
1) Смещать центр круга от кастера, и выделять всех юнитов в группу, увеличивая каждый раз радиус круга.
Вот сама идея
Проблема в том, что нужно проверять не выделяется ли юнит в группу по несколько раз. Если мне надо просто узнать попал ли в обзор вражеский юнит, то тогда ладно. Если мне надо нанести урон всем, тогда лучше сначала выделить всех на разных позициях в группу и нанести урон. А не так: занес группу, нанес урон, очистил группу, пошел дальше повторять те же действия. Но бывают ситуации, когда нужны две группы: одна для перечисленных действия, другая для проверки.
2) С расчетом диапазона угла
Здесь идея идет на то, что попал ли угол между героем и выбранным юнитом.
Если угол направления стрелочки известен, то проверяй разницу между углом стрелочки и углом от центра круга до координат заносимых в группу юнитов
Есть данные:
-угол поворота юнита GetUnitFacing A - обычно принимается как центр обзора
-ширина обзора W - насколько угол обзора будет широким. Одна половина ширины слева, другая справа. Образует минимальный и максимальный углы.
-углы максимальный и минимальный
Все что нам нужно - это определить угол между двумя точками и затем проверять попал ли он в наш диапазон.
Можно сделать так, чтобы система брала углы от 0 до 360 град. Почему не в радианах? Можно и в радианах. Но легче понимать в градусах, к тому же GetUnitFacing возвращает от 0 до 360 градусов. А угол между двумя точками возвращает угол в радианах - Atan2 от -Pi до Pi.
Выделяются несколько проблем (раз делаю систему от 0 до 360 град.), которые решил
  1. угол между двумя точками возвращает радианы, к тому же отрицательные значения. Решение: преобразовал в градусы, и если отрицательные, прибавил 360 град.
  2. Если значение угла между точками не попадает диапазон от 0 до 360. Такого в принципе не может быть. так как GetUnitFacing возвращает от 0 до 360 градусов. Но проблема возникает из-за ширины обзора, одну половинку прибавил там (A+W/2) , другую отнял (A-W/2). И может получится так, что один из углов выйти за пределы круга: может быть так меньше 0, так и больше 360. Поэтому пришлось делить на два диапазона. Например: диапазон от -20 град до 30 град., то придется диапазон поделить на две части: от 0 град до 30 град и от 340 град до 360 град
код
пример можно посмотреть во вложенной карте E1
function Trig_CW_Cast_Conditions takes nothing returns boolean
    return ( GetSpellAbilityId() == 'A000' ) 
endfunction

//Если текущий угол а перешел за 0 град (точнее отрицат. значение), то функция вернет истину
function TZK takes real a returns boolean
return(a < 0)
endfunction

//Если текущий угол а перешел за 360 град , то функция вернет истину
function TZW takes real a returns boolean
return(a > 360)
endfunction

//Функция возвращает угол от 0 до 360. Если текущий угол а меньше 0, то функция вернет нужно значение
//например -40 град, а вернет 320 град
function TZE takes real a returns real

if a < 0 then
set a = 360 + a
endif

return a

endfunction

//Функция возвращает угол от 0 до 360. Если текущий угол а больше 360, то функция вернет нужно значение
//например 380 град, а вернет 20 град
function TZQ takes real a returns real

if a > 360 then
set a = a - 360
endif

return a

endfunction


function TZZ takes nothing returns boolean
local real x1 = GetUnitX(udg_Caster) //координаты кастера
local real y1 = GetUnitY(udg_Caster)
local real x2 = GetUnitX(GetFilterUnit()) //координаты пикнутого
local real y2 = GetUnitY(GetFilterUnit())
local real dx = x2 - x1
local real dy = y2 - y1
local real angle1 = TZE(Atan2(dy,dx)* bj_RADTODEG) //угол переводим из радианов в градусы. Это угол между точками кастера и пикнутого юнита
local real angle2 = udg_Angle //угол поворота. Выражен в градусах
local real angle3 = udg_X //Половина ширины угла. Выражен в градусах

local real A1 = TZE(angle2-angle3)
local real A2 = TZQ(angle2+angle3)

//Углы берется от 0 до 360 град. Если угол меньше нуля (например: диапазон от -20 град до 30 град.), то функция TZK вернет истину.  
//из-за того что угол вышел за ноль град. придется диапазон поделить на две части: от 0 град до 30 град и от 340 град до 360 град
local boolean B1 = TZK(angle2-angle3)
//Если угол меньше нуля (например: диапазон от 40 град до 370 град.), то функция TZW вернет истину.  
//из-за того что угол вышел за 360 град. придется диапазон поделить на две части: от 0 град до 40 град и от 350 град до 360 град
local boolean B2 = TZW(angle2+angle3)
local boolean b1 = false
local boolean b2 = false
local real AMin2
local real AMax2
local real Amin 
local real Amax

//устанавливает минимальный и максимальные углы, нужны для условии. Устанавливает правильный порядок диапазона
if B1 or B2 then 
    if A1 > A2 then
        set Amax = A2 //первый диапазон от нуля до ...
        set Amin = 0
        
        set AMax2 = 360 //второй диапазон от ... до 360
        set AMin2 = A1
    elseif A1 < A2 then
        set Amax = A1 //первый диапазон от нуля до ...
        set Amin = 0

        set AMax2 = 360 //второй диапазон от ... до 360
        set AMin2 = A2
    endif
    set b1 = ((Amin < angle1) and (Amax > angle1)) or ((AMin2 < angle1) and (AMax2 > angle1))
elseif not (B1 and B2) then
    if A2 >= A1 then //Здесь делить на два диапазона не нужно, достаточно одного
        set Amax = A2
        set Amin = A1
    elseif A2 < A1 then
        set Amin = A2
        set Amax = A1
    endif
    set b2 = (Amin < angle1) and (Amax > angle1)
endif


if (b1) and udg_Caster != GetFilterUnit() then
call BJDebugMsg("угол "+ R2S(angle1) + " град. попал в диапазон между углами " +R2S(Amin) +" и " + R2S(Amax) + " или в диапазон между углами "+R2S(AMin2) +" и " + R2S(AMax2) )
elseif (b2) and udg_Caster != GetFilterUnit() then
call BJDebugMsg("угол "+ R2S(angle1) + " град. попал в диапазон между углами " +R2S(Amin) +" и " + R2S(Amax))
elseif udg_Caster != GetFilterUnit() then
call BJDebugMsg("угол "+ R2S(angle1) + " град. НЕ ПОПАЛ в диапазоны между углами " +R2S(Amin) +" и " + R2S(Amax)+" или "+R2S(AMin2) +" и " + R2S(AMax2))
endif



return false
endfunction


function Trig_CW_Cast_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local real x1 = GetUnitX(u) //координаты кастера
local real y1 = GetUnitY(u)
local real from = 800
local real a = GetUnitFacing(u) //угол поворота юнита
local real S = 90 //Ширина угла
local real angle = S/2 //Половина ширины угла

    set udg_Angle = a
    set udg_Caster = u
    set udg_X = angle

call BJDebugMsg("Угол поворота при касте: "+ R2S(a))

call GroupEnumUnitsInRange(bj_lastCreatedGroup,x1,y1,from, Condition(function TZZ))
//call ForGroup( bj_lastCreatedGroup, function TZZ )
call GroupClear(bj_lastCreatedGroup)

endfunction

//===========================================================================
function InitTrig_CW_Cast takes nothing returns nothing
    set gg_trg_CW_Cast = CreateTrigger(  )
    set bj_lastCreatedGroup = CreateGroup()
    call TriggerRegisterAnyUnitEventBJ( gg_trg_CW_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_CW_Cast, Condition( function Trig_CW_Cast_Conditions ) )
    call TriggerAddAction( gg_trg_CW_Cast, function Trig_CW_Cast_Actions )
endfunction
3) Математическая функция Warden
Нашел интересный код, определяет лежит ли угол между двумя углами. Получше даже моего варианта 2
код
function IsAngleBetweenAngles takes real angle, real angle1, real angle2 returns boolean
    local real x
    set angle = ModuloReal(angle, 360)
    set angle1 = ModuloReal(angle1, 360)
    set angle2 = ModuloReal(angle2, 360)
    if (angle1 > angle2) then
        set x = angle1
        set angle1 = angle2
        set angle2 = x
    endif
    if (angle2 - angle1) > (angle1 - (angle2-360)) then
        set angle2 = angle2 - 360
        if angle > 180 then
            set angle = angle-360
        endif
        return angle >= angle2 and angle <= angle1
    endif
    return (angle >= angle1) and (angle <= angle2)
endfunction
Есть вложенный пример карты с дебагом - E2
ссылка
4) Найти угол с помощью скалярного произведения векторов (то о чем говорил Doc)
Мне было сложно понять краткий пример дока. Пообщавшись с ним, он дам мне подсказку. Еще помог хабр разобраться с векторами
Сделал собственный вариант на jass,чтобы было понятно
код
function Trig_CW_Cast_Conditions takes nothing returns boolean
    return ( GetSpellAbilityId() == 'A000' ) 
endfunction



function TZZ takes nothing returns boolean
local real x1 = GetUnitX(udg_Caster) //координаты кастера
local real y1 = GetUnitY(udg_Caster)
local real from = 600//точнее отклонение от позиции кастера, нужна для точки взгляда.
local real a = GetUnitFacing(udg_Caster) //угол поворота юнита

local real x3 = GetUnitX(GetFilterUnit()) //координаты выбранного юнита
local real y3 = GetUnitY(GetFilterUnit())


//вычисляем координаты (x2,y2) направления D (вгляд героя, можно сказать вгзляд вперед под 90 градусам)
local real x2 = x1 + from * Cos(a * bj_DEGTORAD) 
local real y2 = y1 + from * Sin(a * bj_DEGTORAD)
//координаты направления V (от героя к выбранному юниту)
local real dx = x3 - x1 
local real dy = y3 - y1

local real X = dx * x2 //вектора DV на оси x
local real Y = dy * y2 //вектора Dv на оси y
local real d1 = SquareRoot(dx*dx + dy*dy) //модуль вектора V
local real d2 = SquareRoot(x2*x2 + y2*y2) //модуль вектора D

//косинус угла между векторами можно вычислить таким способом
local real A = (X+Y)/(d1*d2) 
local real Cosinus = Cos(A) //Cosinus - это совсем не нужная переменная и вычисления. Просто было интересно как ведет себя косинус.
local real theta = Acos(A)* bj_RADTODEG

local real width = 90 // угол обзора героя будет равен width градусам. Примерно расставляем какой угол будет в градусах
local real angle = width/2 //Половина ширины обзора, от середины, точнее от угла поворота героя. 
//ВНИМАНИЕ: ЭТО ОЧЕНЬ ВАЖНО. Если угол более angle градусов (половина от угла обзора), то выбранный юнит находится вне поля зрения героя.
//мануал - https://habrahabr.ru/post/131931/




if udg_Caster!= GetFilterUnit() then
call BJDebugMsg("Аргумент (значение) для функции Cos, и Acos - угол в градусах между направлениями вгляда и от кастера к выбранному: "+ R2S(A))
call BJDebugMsg("Cos (угол в градусах, всегда равен единичке): "+ R2S(Cosinus))
call BJDebugMsg("Cos (угол в радианах, всегда равен Пи/180*k): "+ R2S(Cosinus* bj_DEGTORAD))
call BJDebugMsg("ACos (угол theta в градусах): "+ R2S(theta))
call BJDebugMsg("угол angle (угол theta должен быть меньше этого значения): "+ R2S(angle))
endif

return false
endfunction


function Trig_CW_Cast_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local real x1 = GetUnitX(u) //координаты кастера
local real y1 = GetUnitY(u)
local real from = 800
local real a = GetUnitFacing(u) //угол поворота юнита
local real S = 90 //Ширина угла
local real angle = S/2 //Половина ширины угла

    set udg_Angle = a
    set udg_Caster = u
    set udg_X = angle

call BJDebugMsg("Угол поворота при касте: "+ R2S(a))

call GroupEnumUnitsInRange(bj_lastCreatedGroup,x1,y1,from, Condition(function TZZ))
//call ForGroup( bj_lastCreatedGroup, function TZZ )
call GroupClear(bj_lastCreatedGroup)

endfunction

//===========================================================================
function InitTrig_Cast takes nothing returns nothing
    set gg_trg_Cast = CreateTrigger(  )
    set bj_lastCreatedGroup = CreateGroup()
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Cast, Condition( function Trig_CW_Cast_Conditions ) )
    call TriggerAddAction( gg_trg_Cast, function Trig_CW_Cast_Actions )
endfunction
можете посмотреть пример карты - E3 и E6. Скажу прямо делал как было написано в хабре с подсказками Дока. Карта E3 - другой вариант, более легкий, то есть там нет аркосинуса, и нам не вычислить угол. Там учитывает ширину обзора 180 градусов, то есть сзади он не видит. Если в этой карте возвращает больше нуля, значит попал в обзор.
5) попадание в треугольник
Мы рассчитываем попал ли в треугольник
код
function Trig_CW_Cast_Conditions takes nothing returns boolean
    return ( GetSpellAbilityId() == 'A000' ) 
endfunction

//Площадь треугольника по координатам
function TriS takes real x1, real y1, real x2, real y2, real x3, real y3 returns real
    return RAbsBJ(x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2))/2
endfunction

//Принадлежность точки (x;y) треугольнику (x1;y1);(x2;y2);(x3;y3).
function IsCoordsInTriangle takes real x, real y, real x1, real y1, real x2, real y2, real x3, real y3 returns boolean
    return R2I(TriS(x1, y1, x2, y2, x3, y3))==R2I(TriS(x1, y1, x2, y2, x, y)+TriS(x2, y2, x3, y3, x, y)+TriS(x1, y1, x3, y3, x, y))
endfunction

function AddLightningEx2 takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
local lightning lg = LoadLightningHandle(udg_Hash,id,1)

call DestroyLightning(lg)

call FlushChildHashtable(udg_Hash,id)

call DestroyTimer(t)
set t = null
set lg = null

endfunction

function AddLightningEx1 takes lightning lg returns nothing
local timer t = CreateTimer()
local integer id = GetHandleId(t)

call SaveLightningHandle(udg_Hash,id,1, lg)
call TimerStart(t,5,false, function AddLightningEx2)

set lg=null
set t =null

endfunction


function TZZ takes nothing returns boolean
local real x1 = GetUnitX(udg_Caster) //координаты кастера, будет одной из точек треугольника
local real y1 = GetUnitY(udg_Caster)
local real from = 600//точнее отклонение от позиции кастера, нужна для точки взгляда.

local real a = GetUnitFacing(udg_Caster) //угол поворота юнита
local real width = 90 // угол обзора героя будет равен width градусам. Примерно расставляем какой угол будет в градусах
local real angle = width/2 //Половина ширины обзора, от середины, точнее от угла поворота героя. 

//диапазон обзора героя от Amin до Amax
local real Amin = a - angle
local real Amax = a + angle

//определяем две оставшиеся крайние точки тругольника
local real x2 = x1 + from * Cos(Amin * bj_DEGTORAD) 
local real y2 = y1 + from * Sin(Amin * bj_DEGTORAD)
local real x3 = x1 + from * Cos(Amax * bj_DEGTORAD) 
local real y3 = y1 + from * Sin(Amax * bj_DEGTORAD)

local real x = GetUnitX(GetFilterUnit()) //координаты выбранного юнита
local real y = GetUnitY(GetFilterUnit())

//это молнии нужны для видимости границ треугольника, типа попала ли в треугольник. Так они вообще не нужны
set bj_lastCreatedLightning = AddLightningEx("CLPB", true, x1, y1, 0., x2, y2, 0.)
call AddLightningEx1(bj_lastCreatedLightning)
set bj_lastCreatedLightning = AddLightningEx("CLPB", true, x1, y1, 0., x3, y3, 0.)
call AddLightningEx1(bj_lastCreatedLightning)
set bj_lastCreatedLightning = AddLightningEx("CLPB", true, x2, y2, 0., x3, y3, 0.)
call AddLightningEx1(bj_lastCreatedLightning)


call BJDebugMsg("Угол а: "+ R2S(a))
if IsCoordsInTriangle(x,y,x1,y1,x2,y2,x3,y3) and udg_Caster!= GetFilterUnit() then
call BJDebugMsg("точка выбранного юнита лежит в треугольнике")
endif

return false
endfunction


function Trig_CW_Cast_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local real x1 = GetUnitX(u) //координаты кастера
local real y1 = GetUnitY(u)
local real from = 800
local real a = GetUnitFacing(u) //угол поворота юнита
local real S = 90 //Ширина угла
local real angle = S/2 //Половина ширины угла

    set udg_Angle = a
    set udg_Caster = u
    set udg_X = angle

call BJDebugMsg("Угол поворота при касте: "+ R2S(a))

call GroupEnumUnitsInRange(bj_lastCreatedGroup,x1,y1,from, Condition(function TZZ))
//call ForGroup( bj_lastCreatedGroup, function TZZ )
call GroupClear(bj_lastCreatedGroup)

endfunction

//===========================================================================
function InitTrig_Cast takes nothing returns nothing
    set gg_trg_Cast = CreateTrigger(  )
    set bj_lastCreatedGroup = CreateGroup()
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Cast, Condition( function Trig_CW_Cast_Conditions ) )
    call TriggerAddAction( gg_trg_Cast, function Trig_CW_Cast_Actions )
endfunction
пример можно посмотреть в карте E7
ссылка на формулу
о
Загруженные файлы
0
21
6 лет назад
0
Перенесите в статьи, потеряется ведь материал, а написано много полезного.
0
27
6 лет назад
Отредактирован MpW
0
JaBeN_Симфер, даже не знаю. Просто проверить надо, иногда кажется ошибки где-то. С проверочными молниями вот все проверил, норм. А скалярное произведение векторов иногда не робит, если боком стать. Может кажется, только 99% норм срабатывает. Я так думаю. что мне кажется.
Хорошо, мб я тупанул и работает норм в карте E6. А вот в карте E3 что то не так робит =( пример с хабра. Ну он работает (85% на 15%), но бывает и не работает. В обзорку в 180 град не попадает.
Вот конусные заклинания, добавил изменения, теперь молниями отображается ширина обзора кастера (будет понятно, кто попадает). Ничего сильно не исправлял, добавил молнии
Загруженные файлы
1
32
6 лет назад
1
Steal nerves, мб пригодится?
function Is2cc takes real r, real cx, real cy, real px1, real py1, real px2, real py2 returns boolean
        local real dx = 0.00 
        local real dy =  0.00 
        local real a =  0.00 
        local real b = 0.00  
        local real c = 0.00 
        
        set px1 = px1 - cx
        set py1 = py1 - cy
        set px2 = px2 - cx
        set py2 = py2 - cy
        set dx = px2 - px1
        set dy = py2 - py1
        set a = dx * dx + dy * dy
        set b = 2.00 * ( px1 * dx + py1 * dy )
        set c = px1 * px1 + py1 * py1 - r * r
        
        if ( -b < 0.00 ) then
            return ( c < 0.00 )
        elseif ( -b < ( 2.00 * a ) ) then
            return ( ( 4.0 * a * c - b * b ) < 0 )
        endif
        return ( a + b + c < 0 )
    endfunction
Определяет пересекает ли вектор окружность или нет.
Юзал для ИИ чтобы определяет попадает ли путь юнита в зону агра врагов...
0
27
6 лет назад
0
Все-таки в 4 варианте были ошибки. Я не правильно код сделал. Мне помог Doc исправить ошибки. Примеры исправленных ошибок
0
27
6 лет назад
Отредактирован MpW
0
Обзор_в_180_градусов сделано для новичков
На основе взяты выше перечисленные примеры. Можете проверить стоит ли впереди или нет.
Кстати, формула Warden работает не так как хотелось бы. Тестировал. И выяснил, что работает если обзор не слишком большой ( angle < 180, если хотите 180 тогда придется ставить приблизительно 179). Если слишком большой, то не будет работать.
Короче тут формула берет минимальный промежуток между углами, а не большой. Пример на скрине
А так все в норме
Еще не забудьте поставить вместо udg_Target переменную GetSpellTargetUnit()
0
19
6 лет назад
0
Короче тут формула берет минимальный промежуток между углами, а не большой. Пример на скрине
Так для "не смотрит" нужно чтобы юнит попал в большой промежуток? Если способность, скажем, через всю карту используется, а тут указывается радиус - если кастер будет за радиусом, что тогда?
0
27
6 лет назад
Отредактирован MpW
0
много букв
Так для "не смотрит" нужно чтобы юнит попал в большой промежуток?
да, тебе выше скрин даже нарисовал. в твоем случае, как определить видит ли цель кастера, то очень подойдет. для твоего скила "удар в спину". Есть другие примеры, может они то тебе подойдут. Загляни на хабр по ссылке, там точь твой вариант, описывают пример про стражника (если не понятно, смотри у меня скалярные векторы в комменте 6, 7). Конечно, тебе немного переделать надо будет, вместо кастера цель.
Если способность, скажем, через всю карту используется, а тут указывается радиус - если кастер будет за радиусом, что тогда?
если ты про молнии, то тут у меня никакого ограничительного радиуса нет. Есть радиус круга = 800, но это чисто для молнии, не стал молнии растягивать на всю карту.
В первом случае, работы со многими юнитами включает с группой, там есть ограничение радиуса - сам радиус. Когда ты пикаешь вокруг юнитов в группу, есть функции, которые выбирают всех вокруг точки с радиусом. Вот это я и использовал. Если надо убрать ограничение, используй другие функции пика на всю карту, или ставь радиус 99999
В случае, с треугольником. Можно, вместо треугольника вставлять прямоугольник =)
Во втором случае, там с одним юнитом (не работаю с группой). Коммент 7 скинул пример. Там нет ограничения, и можно на всю карту. Есть надо ограничить, тогда надо еще проверять расстояние между двумя юнитами. Если расстояние между двумя юнитами меньше 800, то делать ...
Загруженные файлы
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.