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

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

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

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
1
29
7 лет назад
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 исправить ошибки. Примеры исправленных ошибок
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.