Статья предназначается для всех любителей старой-доброй Total Annihilation и ее открытой реализации в виде SpringRTS + Balanced Annihilation.
Несмотря на то, что виджет Air Screen Keeper оказался, по большому счету, бесполезной затеей, на его примере ввиду небольшого размера можно отразить основные идеи построения расширений к играм на основе движка Spring.
Итак, суть виджета (т.е. расширения) — сообщить игроку в той или иной форме о том, что т.н. воздушный экран, состоящий из множества самолетов, выполняющих команду «патруль», атакован врагом с земли. Обычно такие атаки в разгаре битвы (8 на 8 игроков) не очень заметны и можно легко прохлопать, как противник таким образом уничтожит до 70% самолетов, если отвлечься на что-то сиюминутное.
Поэтому в качестве заметного сообщения будем использовать маркер на карте, видимый только игроку, и будем ставить его в месте, где был атакован наш самолет. Кроме того, чтобы не заполонить маркерами все на свете, будем выставлять их с задержкой в несколько секунд.
К сожалению, отсутствует видео, где это можно было бы посмотреть вживую. Вместо этого приведу пример более сложного виджета, управляющего конструкторами для автоматической застройки шахт по добыче металла в пределах периметра базы игрока. Кстати, последний вычисляется автоматически благодаря алгоритму, описанному в замечательной статье о построении минимальных выпуклых оболочек.
Для начала инициализируем виджет и отсортируем имеющиеся воздушные суда, используя арсенал функций, предоставляемых движком:
В таблице units у нас теперь располагаются все самолеты, имеющиеся в распоряжении игрока на момент инициализации виджета. Этого, правда, недостаточно, т.к. нам нужен именно воздушный экран, т.е. самолеты, выполняющие команду патруль. Некоторые могут прохлаждаться на земле, как, например, бомбардировщики, или транспортные самолеты, но нам нужны именно патрулирующие в данный момент.
Пройдемся по имеющимся самолетам и посмотрим их очереди команд на предмет наличия «патруля», после чего поместим найденные самолеты в таблицу airscreen (вне всяких сомнений, есть более эффективный способ реализации этой процедуры, но на момент создания виджета я не придумал ничего лучше, как каждый 128-й кадр игры проделывать такую сортировку. К счастью, это происходит достаточно быстро, чтобы игрок ничего не заметил).
Возможно, более эффективная реализация заключалась бы в обработке события UnitCommand — команды, поступающей для какого-либо юнита. Если это патруль и других команд нет, то можно поместить самолет в таблицу airscreen. Ну да ладно.
Также надо иметь ввиду, что самолеты строятся, уничтожаются, кроме того, кто-то из команды может отдать нам парочку, или, мы сами можем кому-то их отдать, за этим придется следить с помощью соответствующих событий UnitFinished и UnitDestroyed:
При приеме/передаче юнитов между игроками с точки зрения виджета происходит примерно то же самое, что при создании/уничтожении, так что просто сошлемся на уже имеющиеся методы:
Функция notify ставит маркер на карте с задержкой и достаточно проста:
Полный код виджета можно посмотреть здесь: github.com/spike-spb/air-screen-keeper/blob/master/air-screen-keeper.lua
Чтобы установить этот виджет, нужно скопировать его в <ПутьУстанокиSpring>/LuaUI/Widgets. К сожалению, до сих пор многие сталкиваются с проблемой не то, чтобы установки виджетов… многим просто не удается запустить саму игру, поэтому было составлено видео-руководство по этому, в целом, нехитрому процессу: springrts.ru/howto
Кроме того, в сети существует более подробное видео-руководство по созданию виджетов, длиною более часа, созданное Александром Липатовым, которое в свое время помогло мне быстро сориентироваться в процессе разработки расширений для Spring, так что я также приведу его здесь: youtu.be/eMEEa9imx3g
Вообще, надо сказать, что с помощью подобных расширений можно создавать замечательные возможности, временами существенно влияющие на стратегию и тактику игры, благодаря чему у игроков старой школы с навыками программирования имеется замечательная возможность не только играть в старую добрую игру, но и самостоятельно создавать ее.
Ссылки:
Lua scripting (для Spring) — здесь API, где можно найти описание всех функций, упомянутых в статье.
SpringRTS.ru
Сообщество Spring вконтакте
Несмотря на то, что виджет Air Screen Keeper оказался, по большому счету, бесполезной затеей, на его примере ввиду небольшого размера можно отразить основные идеи построения расширений к играм на основе движка Spring.
Итак, суть виджета (т.е. расширения) — сообщить игроку в той или иной форме о том, что т.н. воздушный экран, состоящий из множества самолетов, выполняющих команду «патруль», атакован врагом с земли. Обычно такие атаки в разгаре битвы (8 на 8 игроков) не очень заметны и можно легко прохлопать, как противник таким образом уничтожит до 70% самолетов, если отвлечься на что-то сиюминутное.
Поэтому в качестве заметного сообщения будем использовать маркер на карте, видимый только игроку, и будем ставить его в месте, где был атакован наш самолет. Кроме того, чтобы не заполонить маркерами все на свете, будем выставлять их с задержкой в несколько секунд.
К сожалению, отсутствует видео, где это можно было бы посмотреть вживую. Вместо этого приведу пример более сложного виджета, управляющего конструкторами для автоматической застройки шахт по добыче металла в пределах периметра базы игрока. Кстати, последний вычисляется автоматически благодаря алгоритму, описанному в замечательной статье о построении минимальных выпуклых оболочек.
Для начала инициализируем виджет и отсортируем имеющиеся воздушные суда, используя арсенал функций, предоставляемых движком:
local function dispatchUnit(unitID, unitDefID)
local udef = UnitDefs[unitDefID] -- находим описание юнита по id
-- если юнит воздушный, то помещаем его в глобальный массив
if udef.isAirUnit then
units[unitID] = true
end
end
function widget:Initialize()
local allunits = spGetTeamUnits(spGetMyTeamID())
for _, uid in ipairs(allunits) do
dispatchUnit(uid, spGetUnitDefID(uid))
end
end
В таблице units у нас теперь располагаются все самолеты, имеющиеся в распоряжении игрока на момент инициализации виджета. Этого, правда, недостаточно, т.к. нам нужен именно воздушный экран, т.е. самолеты, выполняющие команду патруль. Некоторые могут прохлаждаться на земле, как, например, бомбардировщики, или транспортные самолеты, но нам нужны именно патрулирующие в данный момент.
Пройдемся по имеющимся самолетам и посмотрим их очереди команд на предмет наличия «патруля», после чего поместим найденные самолеты в таблицу airscreen (вне всяких сомнений, есть более эффективный способ реализации этой процедуры, но на момент создания виджета я не придумал ничего лучше, как каждый 128-й кадр игры проделывать такую сортировку. К счастью, это происходит достаточно быстро, чтобы игрок ничего не заметил).
local function UnitHasPatrolOrder(unitID)
local queue=spGetCommandQueue(unitID,2)
for i,cmd in ipairs(queue) do
if cmd.id==CMD.PATROL then
return true
end
end
return false
end
function updateAirScreenUnits()
for id, v in pairs(units) do
if UnitHasPatrolOrder(id) then
airscreen[id] = true
end
end
end
function widget:GameFrame(frameNum)
if (frameNum % 128 ) == 0 then
updateAirScreenUnits()
end
end
Возможно, более эффективная реализация заключалась бы в обработке события UnitCommand — команды, поступающей для какого-либо юнита. Если это патруль и других команд нет, то можно поместить самолет в таблицу airscreen. Ну да ладно.
Также надо иметь ввиду, что самолеты строятся, уничтожаются, кроме того, кто-то из команды может отдать нам парочку, или, мы сами можем кому-то их отдать, за этим придется следить с помощью соответствующих событий UnitFinished и UnitDestroyed:
function widget:UnitFinished(unitID, unitDefID, unitTeam)
if (unitTeam ~= spGetMyTeamID()) then
return
end
dispatchUnit(unitID, unitDefID) -- сортируем
end
function widget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam)
if airscreen[unitID] then
if attackerID then
notify(attackerID) -- юнит уничтожен, мы ставим маркер на месте атакующего, если известен его id
else
notify(unitID) -- или в месте уничтожения, если id незвестен
end
airscreen[unitID] = nil -- удаляем самолет из воздушного экрана
end
units[unitID] = nil -- удаляем самолет из списка самолетов
end
При приеме/передаче юнитов между игроками с точки зрения виджета происходит примерно то же самое, что при создании/уничтожении, так что просто сошлемся на уже имеющиеся методы:
function widget:UnitTaken(unitID, unitDefID, unitTeam, newTeam)
widget:UnitDestroyed(unitID, unitDefID)
end
function widget:UnitGiven(unitID, unitDefID, unitTeam, oldTeam)
widget:UnitFinished(unitID, unitDefID, unitTeam)
end
Функция notify ставит маркер на карте с задержкой и достаточно проста:
function widget:Update(dt)
lastMarkTime = lastMarkTime - dt
end
function notify(unitID)
if (lastMarkTime < 0) then
lastMarkTime = MARK_DELAY
local msg = "AA"
local x, y, z = Spring.GetUnitPosition(unitID)
spMarkerAddPoint(x, y, z, msg, true)
end
end
Полный код виджета можно посмотреть здесь: github.com/spike-spb/air-screen-keeper/blob/master/air-screen-keeper.lua
Чтобы установить этот виджет, нужно скопировать его в <ПутьУстанокиSpring>/LuaUI/Widgets. К сожалению, до сих пор многие сталкиваются с проблемой не то, чтобы установки виджетов… многим просто не удается запустить саму игру, поэтому было составлено видео-руководство по этому, в целом, нехитрому процессу: springrts.ru/howto
Кроме того, в сети существует более подробное видео-руководство по созданию виджетов, длиною более часа, созданное Александром Липатовым, которое в свое время помогло мне быстро сориентироваться в процессе разработки расширений для Spring, так что я также приведу его здесь: youtu.be/eMEEa9imx3g
Вообще, надо сказать, что с помощью подобных расширений можно создавать замечательные возможности, временами существенно влияющие на стратегию и тактику игры, благодаря чему у игроков старой школы с навыками программирования имеется замечательная возможность не только играть в старую добрую игру, но и самостоятельно создавать ее.
Ссылки:
Lua scripting (для Spring) — здесь API, где можно найти описание всех функций, упомянутых в статье.
SpringRTS.ru
Сообщество Spring вконтакте