Создаем небольшой ИИ для нейтралов.
Доброго времени суток уважаемые посетители сайта, думаю вы уже поняли тему статьи. Цель статьи - сделать систему, чтобы NPC - в нашем случае Нейтрально-враждебыне юниты, атаковали ближайших врагов. Вы спросите: "А зачем все это, нейтралы и так на ура бьют ближайших?", у меня есть ответ, у нас своя "фича", вот, например, если у вас есть герой, который имеет очень большую мобильность, и за ним гонится орда "крипов", то они довольно таки длительное время будут гнатся за вашим героем, а тут, они поменяют приоритет на ближайшего врага. Что с некоторой стороны - весьма логично.
Полагаю, вы уже знакомы с GUI, или как называют еще, триггеры. В этой статье, мы сделаем систему, где нейтральные юниты, будут атаковать ближайших врагов. Сам я не являюсь знатоком JASS'а, так же не являюсь опытным писателем, так что попытаюсь максимально доступно пояснить все наши действия.
Думаю, у вас установлен JNGP, так что, писать код(если вы кончено не будете все тупо "копипастить", что я вам крайне не рекомендую) нам будет куда удобнее и приятнее.
Для начала, создаем пустой триггер, назовем его, "Sis12Attack", добавляем "Время - периодическое событие", ставим там 0.50 сек. Самый оптимальный вариант. Теперь "конвентируем триггер в текст", и получаем это:
Код
[code=jass]
function Trig_Sis12Attack_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Sis12Attack takes nothing returns nothing
set gg_trg_Sis12Attack = CreateTrigger( )
call TriggerRegisterTimerEventPeriodic( gg_trg_Sis12Attack, 0.50 )
call TriggerAddAction( gg_trg_Sis12Attack, function Trig_Sis12Attack_Actions )
endfunction
[/code]
Думаю разбирать что да как тут смысла нет, т.к. я уже писал в предыдущей статье, как примерно состоит триггер в JASS'e.
Так же, надо создать глобальную переменную "отряд" для наших нейтралов, которые будут атаковать ближайших врагов. У меня будет "g", просто "g", а в JASS'e будет выглядить "udg_g" - Это для тех переменных, которые были созданны в специальном окне редакторов триггеров, в общем, по умолчанию.
Теперь же, пожалуй можно приступить к аналитике. Для начала, нам надо будет "прогнать" юнитов из нашей группы, потом по нашему "прогнонному" юниту делать главное действие, в действии будем "выбирать" всех врагов в радиусе 1000.0 и сверять дистанцию меж выбранным юнитом и нашим "прогнанным" юнитом. Довольнотаки сложно выглядит, согласен.
Сейчас нам надо будет написать "вспомогательные" функции, это те, без которых наша система не будет работать. Начнем с того, что напишем функцию которая определяла бы дистанцию меж координатами. За основу берем BJ-фнкцию "DistanceBetweenPoints(locationA,locationB)". Как вы видите, она берет аргументы типа "точки"(локации), а в нашем случае это очень не удобно, т.к. мы будем работать с Координатами(real). Теперь пишем код:
[code=jass]
function DistanceBetweenPointsX takes real x,real y,real xx,real yy returns real
local real dx = xx - x
local real dy = yy - y
return SquareRoot(dx * dx + dy * dy)
endfunction
[/code]
Вот и все она берет значения "x","y" - это как раз подойдет для нашего "прогоняемого" юнита, а "xx","yy" для нашей "выбранной" еденицы (то есть, с которым мы будем сверять дистанцию), а возвращает дистанцию меж координатами. Все просто.
Теперь наш триггер выглядит так:
[code=jass]
function DistanceBetweenPointsX takes real x,real y,real xx,real yy returns real
local real dx = xx - x
local real dy = yy - y
return SquareRoot(dx * dx + dy * dy)
endfunction
function Trig_Sis12Attack_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Sis12Attack takes nothing returns nothing
set gg_trg_Sis12Attack = CreateTrigger( )
call TriggerRegisterTimerEventPeriodic( gg_trg_Sis12Attack, 0.50 )
call TriggerAddAction( gg_trg_Sis12Attack, function Trig_Sis12Attack_Actions )
endfunction
[/code]
Теперь нам осталась последняя "вспомогательная" функция, это фильтр, который будет проверять враждебность и живучесть для нашей цели, не будет же наш юнит бить союзника в конце концов.
Вот её код:
[code=jass]
function GetFilterEnemy12 takes nothing returns boolean
return IsPlayerEnemy(Player(12), GetOwningPlayer(GetFilterUnit())) and GetWidgetLife(GetFilterUnit()) > 0.405
endfunction
[/code]
Эта функция возвращает "true" тогда, когда выбранный юнит будет врагом для "Нейтрально Враждебного" и если у него здоровья больше 0.405. Дело в том, что юниты умирают не с нулевым значением здоровья, как полагают многие, а остаются в
~0.405 значением.
Обратите внимание на "Player(12)" - нет, это не игрок 12, это "Нейтрально враждебный". Дело в том, что в JASS все нумерация игроков начинается с нуля, например, игрок 1 в JASS будет Player(0), а игрок 12 будет Player(11).
Все, теперь наш триггер выглядит таким образом:
[code=jass]
function GetFilterEnemy12 takes nothing returns boolean
return IsPlayerEnemy(Player(12), GetOwningPlayer(GetFilterUnit())) and GetWidgetLife(GetFilterUnit()) > 0.405
endfunction
function DistanceBetweenPointsX takes real x,real y,real xx,real yy returns real
local real dx = xx - x
local real dy = yy - y
return SquareRoot(dx * dx + dy * dy)
endfunction
function Trig_Sis12Attack_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Sis12Attack takes nothing returns nothing
set gg_trg_Sis12Attack = CreateTrigger( )
call TriggerRegisterTimerEventPeriodic( gg_trg_Sis12Attack, 0.50 )
call TriggerAddAction( gg_trg_Sis12Attack, function Trig_Sis12Attack_Actions )
endfunction
[/code]
Учтите, что функции надо объявлять до того когда они будут вызваны. Пожалуй я просто приведу не сложный пример вместо тысячи букв. Например, если вызвать нашу недавно созданную функцию "DistanceBetweenPointsX" внутри функции "GetFilterEnemy12" то компилятор выдаст ошибку, что функция не существует, т.к. мы её еще не объявили.
Что ж, теперь пишем непосредственно основное "тело" системы, в нашем пустующей функции Trig_Sis12Attack_Actions:
[code=jass]
function Trig_Sis12Attack_Actions takes nothing returns nothing
call ForGroup(udg_g,function Sis12AttackX)
endfunction
[/code]
Как вы поняли, сейчас как раз мы сделали "прогон". И для этого нам надо будет создать функцию "Sis12AttackX". Естeственно, пишем её выше функции "Trig_Sis12Attack_Actions", тем самым, объявляя её.
____заметка, а вообще, правильнее называть такие функции - процедура, т.к. такие функции ничего не возвращают.
Собственно код функции Sis12AttackX:
[code=jass]
function Sis12AttackX takes nothing returns nothing
local unit u = GetEnumUnit()//наш "програнный" юнит
local real x = GetUnitX(u)//координаты нашего
local real y = GetUnitY(u)//юнита, Х и Y
local real xx//Это координаты для
local real yy//выбранного юнита
local real lastdis=1000.0//изначальный радиус
local boolexpr b = Condition(function GetFilterEnemy12)//Фильтр
local unit t//здесь будем хранить самого ближайшего "врага"
local group g = CreateGroup()//сразу же создаем группу
local unit f//для проверки цикала
call GroupEnumUnitsInRange(g, x, y, 1000.0, b)//Здесь мы добавляем всех юнитов в группу g, которые прошли через наш фильтр
loop
set f = FirstOfGroup(g)//Здесь мы прогоняем юнитов
exitwhen f == null//цикл прекращается как в нашей группе не будет юнитов
set xx = GetUnitX(f)//Тут то мы и придаем значения
set yy = GetUnitY(f)//координат выбраного юнита
if DistanceBetweenPointsX(x,y,xx,yy) < lastdis then
//проверяем дистанцию меж нашим юнитом и врагом, если
//дистанция меньше lastdis, то
set t=f//t = ближайшая цель
set lastdis=DistanceBetweenPointsX(x,y,xx,yy)//сохраняет дистанцию в lastdis
//так что не беспокоимся что первый попавшийся окажется нашей целью.
//таким хитрым образом мы находим ближайшего противника.
endif
call GroupRemoveUnit(g, f)//удаляем "програнного" юнита из группы, что бы цикл небыл бесконечным.
endloop
call IssueTargetOrder(u,"attack",t)//когда все закончилось, можем смело отдать приказ нашему юниту на атаку ближайшей цели.
//далее идет "обнуление"
call DestroyGroup(g)//перед тем как обнулить переменную "g", сначала надо уничтожить созданную группу.
call DestroyBoolExpr(b)//тоже самое с фильтором
set u = null//теперь спело обнуляем handle-переменные
set b = null
set t = null
set g = null
set f = null
endfunction
[/code]
ДЛЯ ТЕХ КОМУ ЛЕНЬ ЧИТАТЬ, ЛИБО ПРОСТО НУЖНЕН КОД, ПОЖАЛУЙСТА:
[code=jass]
function GetFilterEnemy12 takes nothing returns boolean
return IsPlayerEnemy(Player(12), GetOwningPlayer(GetFilterUnit())) and GetWidgetLife(GetFilterUnit()) > 0.405
endfunction
function DistanceBetweenPointsX takes real x,real y,real xx,real yy returns real
local real dx = xx - x
local real dy = yy - y
return SquareRoot(dx * dx + dy * dy)
endfunction
function Sis12AttackX takes nothing returns nothing
local unit u = GetEnumUnit()//наш "програнный" юнит
local real x = GetUnitX(u)//координаты нашего
local real y = GetUnitY(u)//юнита, Х и Y
local real xx//Это координаты для
local real yy//выбранного юнита
local real lastdis=1000.0//изначальный радиус
local boolexpr b = Condition(function GetFilterEnemy12)//Фильтр
local unit t//здесь будем хранить самого ближайшего "врага"
local group g = CreateGroup()//сразу же создаем группу
local unit f//для проверки цикала
call GroupEnumUnitsInRange(g, x, y, 1000.0, b)//Здесь мы добавляем всех юнитов в группу g, которые прошли через наш фильтр
loop
set f = FirstOfGroup(g)//Здесь мы прогоняем юнитов
exitwhen f == null//цикл прекращается как в нашей группе не будет юнитов
set xx = GetUnitX(f)//Тут то мы и придаем значения
set yy = GetUnitY(f)//координат выбраного юнита
if DistanceBetweenPointsX(x,y,xx,yy) < lastdis then
//проверяем дистанцию меж нашим юнитом и врагом, если
//дистанция меньше lastdis, то
set t=f//t = ближайшая цель
set lastdis=DistanceBetweenPointsX(x,y,xx,yy)//сохраняет дистанцию в lastdis
//так что не беспокоимся что первый попавшийся окажется нашей целью.
//таким хитрым образом мы находим ближайшего противника.
endif
call GroupRemoveUnit(g, f)//удаляем "програнного" юнита из группы, что бы цикл небыл бесконечным.
endloop
call IssueTargetOrder(u,"attack",t)//когда все закончилось, можем смело отдать приказ нашему юниту на атаку ближайшей цели.
//далее идет "обнуление"
call DestroyGroup(g)//перед тем как обнулить переменную "g", сначала надо уничтожить созданную группу.
call DestroyBoolExpr(b)//тоже самое с фильтором
set u = null//теперь спело обнуляем handle-переменные
set b = null
set t = null
set g = null
set f = null
endfunction
function Trig_Sis12Attack_Actions takes nothing returns nothing
call ForGroup(udg_g,function Sis12AttackX)
endfunction
//===========================================================================
function InitTrig_Sis12Attack takes nothing returns nothing
set gg_trg_Sis12Attack = CreateTrigger( )
call TriggerRegisterTimerEventPeriodic( gg_trg_Sis12Attack, 0.50 )
call TriggerAddAction( gg_trg_Sis12Attack, function Trig_Sis12Attack_Actions )
endfunction
[/code]
Вопросы и ответы
>>Так как теперь это использовать?
Просто до безобразия, создаете триггер, не важно, инициализация или с событием. Добавляете нейтрально-враждебного юнита в "отряд" "g". А когда юнит умирает, удаляйте этого юнита из группы.
А если серьезно, то это просто попытка дать "мозги" ботам. С вами был
Bornikkeny, до встречи.