Добавлен , опубликован

Основы Интерфейса

Содержание:
кратко: здесь рассказывают как отследить юнита интерфейса, и переключения select на других юнитов. Зачем это нужно? мб у вас фреймы появляются, когда выделен ваш юнит. Но когда вы выбор переключаете на что-то другое, возможно тут нужно вернуть все на место. Вам необходимо, знать. Просто это очень важно когда вы создаете кнопки для юнита, и вам необходимо знать выделен он или нет. При переключении юнита фреймы могут остаться, поэтому необходимо знать точно.
По опыту скажу как система работает в группе:
  • если выбор падает на героя, вам выводят его
  • если выбор падает на обычного юнита, вам выводят его тип. Обычно, если в вашем выделенном отряде находятся несколько юнитов одного типа, то выбор падает на первого попавшегося юнита. Самое забавное - эта механика вара. Если у одного юнита абилка находится в перезарядке/маны нет итд, то игра все равно показывает, что юниты этого типа могут кастовать. Есть разные типы абилок: одиночные и групповые. Если отдать им каст, то побежит кто-то один, тот кто проходит по требования каста абилы. Эта механика, пришлось думать как фреймы в выбранной группе корректировать. Вам придется также задуматься.
старая ссылка <- работает не идеально
более свежая ссылка

принцип работы селекта

Юнита можно выделить не только своего, но и принадлежащему союзнику, нейтральному, вражескому. У вас с другим игроком может даже быть расшарен общий контроль, но есть контроль или нет, система показывает лишь какого юнита/группу выделил.
По правилу выбор определяется приоритетом BlzGetUnitRealField(unit, UNIT_RF_PRIORITY). У героев как правило высокий приоритет.
Порядок расположения иконок юнитов в группе определяется приоритетом. У всех героев самый высокий, имеет одинаковый приоритет для всех героев. Они всегда будут в первом в списке. Но герои как правило между собой тоже имеют порядок расположения, как правило, первый занимает тот герой, который старше или создан раньше остальных. Логичнее было предположить искать его по хендлу GetHandleId(unit). Но такое не рекомендуют, неизвестно как работает хэндл.
Далее, в порядке идут обычные юниты. Они тоже распределены приоритетом. Обычно воины ближнего боя, далее юниты дальнего боя. На самом деле это совсем не так, но расположены для управления в мили-сражении. Порядок разный. Если у некоторый типов юнитов одинаковый приоритет, то первый идет тот у кого тип выше (хотя не точно).
Когда вы рамкой выделяете целый отряд, у вас распорядок кнопок будет расположен в зависимости от приоритетов. А если у некоторых видов войск одинаковый приоритет, то тут дело берут две вещи:
  • у героев определить можно по хэндлу
  • у обычных юнитов по типу
рассмотрим какие функции есть в редакторе
проверка boolean "юнит выделен игроком"
Юнит выбран. проверяем кто выбрал его с помощью перечисленной функцией.
Можно циклом Player(i=1-12) пробежаться, и проверить кем выделен данный юнит.
constant native IsUnitSelected takes unit whichUnit, player whichPlayer returns boolean
пик в группу, которая выделяет всех выбранных игроком
Показывает группу юнитов, которых выделил в данный момент игрок
В механике игры максимум юнитов выделить игрок может 12.
Внимание: нужно вставлять перед этим действием SyncSelections(), синхронизирующий выбор игрока локально.
native GroupEnumUnitsSelected takes group whichGroup, player whichPlayer, boolexpr filter returns nothing
нативка синхронизации выбора. часто ее добавляю в группы. Не смог найти где еще ее применяют. Только для GroupEnumUnitsSelected
раскрыть
native SyncSelections takes nothing returns nothing
можно создать группу и запихать
function GetUnitsSelectedAll(whichPlayer)
    local g = CreateGroup()
    SyncSelections()
    GroupEnumUnitsSelected(g, whichPlayer, null)
    return g
end
событие "юнит выбран игроком"/"юнит не выбран"
есть события выделения, когда игрок выделяет игрока. и снятия выделения, когда юнит перестает быть выделенным. В принципе понятна работа. Как работает выделение группы? тут все просто, игра снимает выделения с одних юнитов, и заново добавляет новых, будь даже те же самые (просто добавить несколько в группу). Можно создать для каждого игрока группу выделения, и фиксировать прибавление/убавления.
Можно записать в один триггер, и с помощью GetTriggerEventId() определять какое событие сработало
TriggerRegisterPlayerUnitEvent( trig, Player( 0 ), EVENT_PLAYER_UNIT_SELECTED, null )
TriggerRegisterPlayerUnitEvent( trig, Player( 0 ), EVENT_PLAYER_UNIT_DESELECTED, null )
к самому юниту можно обратиться как GetTriggerUnit(), игрок GetTriggerPlayer()
регистр для юнита имеет
constant unitevent EVENT_UNIT_DESELECTED = ConvertUnitEvent(58)
constant unitevent EVENT_UNIT_SELECTED = ConvertUnitEvent(57)
GetPlayerSelectable (no idea)
возможно эта проверка показывает выделен ли у игрока кто-либо
native GetPlayerSelectable takes player whichPlayer returns boolean
BlzIsUnitSelectable
native BlzIsUnitSelectable takes unit whichUnit returns boolean
различные действия выбора
очистка выбора
есть действие очистки выбора. удобно, когда не нужно удалять. сразу чистим. Иначе пришлось бы циклом всех юнитов вычищать, а также записать.
native ClearSelection takes nothing returns nothing
есть действия очистки выбора для игрока
function ClearSelectionForPlayer(whichPlayer)
    if (GetLocalPlayer() == whichPlayer) then
        --Use only local code (no net traffic) within this block to avoid desyncs.
        ClearSelection()
    end
end
добавить/убавить юнита игроку
основная нативка, добавляющая юнита игроку
native SelectUnit takes unit whichUnit, boolean flag returns nothing
вариковские нативки добавления юнита
function SelectUnitAdd(whichUnit)
    SelectUnit(whichUnit, true)
end
function SelectUnitRemove(whichUnit)
    SelectUnit(whichUnit, false)
end
есть еще действия выделения одного юнита, правда не локально
function SelectUnitSingle(whichUnit)
    ClearSelection()
    SelectUnit(whichUnit, true)
end
но как обычно нужно указать к какому игроку локально выбор прилепить. А то действия два выше добавляют всем.
((код
--выделить одного юнита для игрока
function SelectUnitForPlayerSingle(whichUnit,whichPlayer)
    if (GetLocalPlayer() == whichPlayer) then
        --Use only local code (no net traffic) within this block to avoid desyncs.
        ClearSelection()
        SelectUnit(whichUnit, true)
    end
end

--добавить юнита из группы выбора игрока
function SelectUnitAddForPlayer(whichUnit,whichPlayer)
    if (GetLocalPlayer() == whichPlayer) then
        --Use only local code (no net traffic) within this block to avoid desyncs.
        SelectUnit(whichUnit, true)
    end
end
--удалить юнита из группы выбора игрока
function SelectUnitRemoveForPlayer(whichUnit,whichPlayer)
    if (GetLocalPlayer() == whichPlayer) then
        --Use only local code (no net traffic) within this block to avoid desyncs.
        SelectUnit(whichUnit, false)
    end
end
))
выделить группу
function SelectGroupBJEnum()
    SelectUnit( GetEnumUnit(), true )
end
function SelectGroupForPlayerBJ(g, whichPlayer)
    if (GetLocalPlayer() == whichPlayer) then
        --Use only local code (no net traffic) within this block to avoid desyncs.
        ClearSelection()
        ForGroup( g, function SelectGroupBJEnum )
    end
end
глобальные параметры селекта
--объекты могут быть выделены/не выделены, круг выбора отображается/не отображается
native EnableSelect takes boolean state, boolean ui returns nothing
--при наведении объекты могут быть выделены/не выделены, визуальные части вроде круга, полоски жизни мана и хп, и подсказки-фреймы над головой
native EnablePreSelect takes boolean state, boolean ui returns nothing
--при drag-select перетаскивании, а точнее выделение юнитов прямоугольной рамкой drag selection. выделяются/не выделяются объекты, включает/отключает визуальную рамку выделения drag selection box
native EnableDragSelect takes boolean state, boolean ui returns nothing
включить/отключить выделение и круг выбора
function BlzIsSelectionEnabled takes nothing returns boolean
function BlzIsSelectionCircleEnabled takes nothing returns boolean
function BlzEnableSelections takes boolean enableSelection, boolean enableSelectionCircle returns nothing
запись группы
по плану этот код создает для каждого игрока свою группу. При выделении несколькиж юнитов он записывает в группу выделенных юнитов. Или наоборот, когда юнит перестает быть выделенным/или выделенный юнит умирает/или сбрасывается каким-то другим образом выделение => удаляет выделенных юнитов из группы.
Это даже будет лучше, поскольку обращение к нативным функциям происходит медленее из-за синха. И некоторые функции пример IsUnitSelected не работают, если игрок выделяет вражеского юнита. Либо через событие или GroupEnumUnitsSelected. Поэтому лукчшим способом будет записать юнита в группу, и проверять на принадлежность к группе игрока. Если юнита в какой-то группе, значит, он выделен этим игроком

do
    local InitGlobalsOrigin = InitGlobals
    function InitGlobals()
        InitGlobalsOrigin()
	
        
		function HideEnumUnit(u)
			ShowUnit(u, false )
		end
		function ShowEnumUnit(u)
			ShowUnit(u, true )
		end
		
		selectedUnits = {}
		local triggerSelectedUnit = CreateTrigger()
		
		for a=0, 11 do
			TriggerRegisterPlayerUnitEvent( triggerSelectedUnit, Player(a), EVENT_PLAYER_UNIT_SELECTED,   null )
			TriggerRegisterPlayerUnitEvent( triggerSelectedUnit, Player(a), EVENT_PLAYER_UNIT_DESELECTED,   null )
			TriggerRegisterPlayerUnitEvent( triggerSelectedUnit, Player(a), EVENT_PLAYER_UNIT_DEATH,   null )
		end
		TriggerAddAction( triggerSelectedUnit, function()
			local triggerUnit  =  GetTriggerUnit()
			local playerId     =  GetPlayerId(GetTriggerPlayer())

			if ( GetTriggerEventId( ) == EVENT_PLAYER_UNIT_SELECTED ) then

				if ( selectedUnits[ playerId ] == null ) then
					selectedUnits[ playerId ]  =  CreateGroup( )
				end
                GroupAddUnit( selectedUnits[ playerId ], triggerUnit )
                print( "|cff00ff00"..GetUnitName( triggerUnit ).." selected|r" )
			elseif ( GetTriggerEventId( ) == EVENT_PLAYER_UNIT_DESELECTED ) then
				GroupRemoveUnit( selectedUnits[ playerId ], triggerUnit )
				print( "|cffff0000"..GetUnitName( triggerUnit ).." deselected|r" )

			elseif( GetTriggerEventId( ) == EVENT_PLAYER_UNIT_DEATH ) then

            end
        end)
    end
end
теперь вернемся к нашей механике, которая определяет с помощью фреймов, какой юнит выделен игроком. Поскольку нельзя точно определить какой юнит выделен у игрока в данный момент, когда у него целая группа.
Механика такова:
  1. с помощью фрейма определяем какая кнопка интерфейса выделена. Короче, номер кнопки. Есть в интерфейсе групповая панель, которая отображается, когда у вас выделен отряд. Там есть контейнер кнопок. Максимум можно выделить 12 юнитов, и соответственно, есть 12 кнопок. У каждой такой кнопки существует еще потомок - желтая рамочка. Когда выделен конкретный юнит в группе, у вас и какая то рамочка видима, остальные скрыты. Циклом перебираем 12 кнопок и определяем видимость кнопки BlzFrameIsVisible, точнее желтой рамочки
  2. Берем выделенную группу игрока. Можно записывать, или сразу получить группу игрока. В ней сортируем юнитов. Сначала по приоритету BlzGetUnitRealField(unit, UNIT_RF_PRIORITY). Затем между видами войск с одинаковым приоритетом отсеиваем по:
  • у героева по хэндлу
  • у не-героев по типу.
основной код
do
do
    -- возвращает локально текущего главного выделенного юнита в группе,
    -- автор пишет, что использование соответствующим образом для синхронизации с игровым состоянием ломает игру
    -- возможно попозже потестить нужно на предмет синхронизации
    -- илииспользовать нативки синхронизации
    function GetMainSelectedUnitEx()
        return GetMainSelectedUnit(GetSelectedUnitIndex())
    end
    
    local containerFrame --контейнер кнопок групповой панели
    local frames = {} --в этой таблице записаны желтые рамки кнопок
    
    local group --эта группа юнитов
    local units = {} --в этой таблице записывают юнитов, динамично переопределяют порядок юнитов
    
    --фильтр переопределяет порядок, короче, сортирует юнитов в нужном порядке
    local filter = Filter(function()
        local unit = GetFilterUnit()
        local prio = BlzGetUnitRealField(unit, UNIT_RF_PRIORITY)
        local found = false
        
        --короче, определяет порядок расположения юнитов в units
        -- сравнивает текущего юнита unit с уже найденным, чтобы поместить его в нужный слот
        --тут короче цикл перебирает каждого юнита value, и сравнивает с текущим юнитом
        for index, value in ipairs(units) do
            -- юнит с высоким приоритетом будет находится выше, чем у выбранного юнита  value
            if BlzGetUnitRealField(value, UNIT_RF_PRIORITY) < prio then
                table.insert(units, index, unit)
                found = true
                break
            -- среди двух юнитов с одинаковым приоритетом выбирают нужного юнита 
            elseif BlzGetUnitRealField(value, UNIT_RF_PRIORITY) == prio and GetUnitOrderValue(value) > GetUnitOrderValue(unit) then
                table.insert( units, index, unit)
                found = true
                break
            end
        end
        -- если в таблице units ничего не записано, то текущий юнит units является первом юнитом, записанный в группу
        -- сравнить просто не с чем
        if not found then
            table.insert(units, unit)
        end

        unit = nil
        return false
    end)

    --возвращает номер выделенной кнопки
    --есть групповая панель, если у вас выделено несколько юнитов
    --в механике вара максимум 12 юнитов выделить в группу можно
    --соответственно и 12 кнопок, и у 12 кнопок есть еще и 12 желтых рамочек (контейнер кнопок => containerFrame)
    --когда выделяешь одного юнита среди всех в группе, только 1 рамочка видима frames[int]
    function GetSelectedUnitIndex()
        -- выделена локально у игрока групповая панель containerFrame
        if BlzFrameIsVisible(containerFrame) then
            -- find the first visible yellow Background Frame
            for int = 0, #frames do
                if BlzFrameIsVisible(frames[int]) then
                    return int
                end           
            end
        end

        return nil
    end

    --возвращает нужного юнита (если герой, то по хэндлу. если обычный юнит, возвращает тип)
    --возвращает либо хэндл героя, либо тип
    function GetUnitOrderValue(unit)
        --heroes use the handleId
        if IsUnitType(unit, UNIT_TYPE_HERO) then
            return GetHandleId(unit)
        else
        --units use unitCode
        return GetUnitTypeId(unit)
        end
    end

	--выделяет по номеру юнита
    function GetMainSelectedUnit(index)
        if index then
            GroupEnumUnitsSelected(group, GetLocalPlayer(), filter)
            local unit = units[index + 1]
            --clear table
            repeat until not table.remove(units)
            return unit
        else
            GroupEnumUnitsSelected(group, GetLocalPlayer(), nil)
            return FirstOfGroup(group) or nil
        end
    end
    
    

    --init
    do
        local real = MarkGameStarted
        function MarkGameStarted()
            real()
        
            --пришлось создать отдельную функцию инициализации
            --тк при сэйв/лоад нужно заново все пересоздавать/инициировать
            function InitGlobalsSelect() 
                console = BlzGetFrameByName("ConsoleUI", 0)
                bottomUI = BlzFrameGetChild(console, 1)
                groupframe = BlzFrameGetChild(bottomUI, 5)
                containerFrame = BlzFrameGetChild(groupframe, 0)

                group = CreateGroup()
                -- give this frames a handleId
                for int = 0, BlzFrameGetChildrenCount(containerFrame) - 1 do
                    local buttonContainer = BlzFrameGetChild(containerFrame, int)
                    frames[int] = BlzFrameGetChild(buttonContainer, 0)
                end
            end
            InitGlobalsSelect()
        end
    end
end
таймером периодически обновлять инфу
вся информация хранится обычно в массиве. какой юнит выделен у каждого игрока. Чтобы фиксировать изменения. пример я храню инфу об последнем выделенном юните в массивной таблице LastSelectUnit

--почему-то только в InitGlobals норм таймер работает
do
    local real = MarkGameStarted
    function MarkGameStarted()
        real()
        
        LastSelectUnit = {} --инициируем таблицу

        function SelectUnits()
                    
            local index = GetSelectedUnitIndex() --номер выделенной кнопки или номер выделенного юнита
            local n = GetPlayerId(GetLocalPlayer()) --номер игрока, чтобы записывать игрока. у каждого игрока локально отображается интерфейс
            local u = GetMainSelectedUnit(index) --по номеру получаем выделенного игрока из группы

            
            --если выбран новый юнит, значит, обновляем информацию
            if LastSelectUnit[n]~= u then
                --если юнит не существует, или это вовсе не юнит, вернет ноль
                if not(u==nil) then --GetUnitTypeId( u ) < 1
                    --перезаписываем
                    LastSelectUnit[n]= u

                    if IsUnitType(u, UNIT_TYPE_HERO) then
                        if index then
                            print("|cffff00ffвыбран |cffffcc00"..GetUnitName(u)..' '..GetHeroProperName(u)..'|cffff00ff, index group:|r '..tostring(index))
                        else
                            --если GetSelectedUnitIndex() не вернет номер, значит, что у юнита не выделена групповая панель, а выделен один юнит
                            print("|cffff00ffвыделен |cffffcc00"..GetUnitName(u)..' '..GetHeroProperName(u)..'|r')
                        end
                        
                    else
                        if index then
                            print("|cffff00ffвыбран |cffffcc00"..GetUnitName(u)..'|cffff00ff, index group:|r '..tostring(index))
                        else
                            --если GetSelectedUnitIndex() не вернет номер, значит, что у юнита не выделена групповая панель, а выделен один юнит
                            print("|cffff00ffвыделен |cffffcc00"..GetUnitName(u)..'|r')
                        end
                    end
                else
                    print("|cffFF0000юнит не выделен|r")
                    --перезаписываем, хоть это и не юнит. все равно записываем
                    LastSelectUnit[n]= u
                end
            end
        end


        TimerStart(CreateTimer(), 1/32, true, function()
            SelectUnits()
        end)
        
    end
end


do
    local real = MarkGameStarted
    function MarkGameStarted()
    real()    
        print('start')
        
        local triggerLoad = CreateTrigger()
        timer = CreateTimer()
        TriggerRegisterGameEvent(triggerLoad, EVENT_GAME_LOADED)
        TriggerAddAction(triggerLoad, function()
            TimerStart(timer, 0, false, function()
                --инициируем выбор
                InitGlobalsSelect()
                LastSelectUnit[GetPlayerId(GetLocalPlayer())]=nil
            end)
        end)
    end
end
возможно тут стоит проверить на десинх функцию GetMainSelectedUnitEx() тк она возвращает номер локально. а это значит, придеться добавить нативки по синхронизации, чтобы номера всегда синхронизировались

Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
27
1 год назад
Отредактирован MpW
0
Кто хочет избежать багов, когда наводишь на предмет или декор на карте. У вас высвечивается центральная панель информации об итеме или об декоре. Панели информации одиночного юнита или групповая скрывается. Можно внести больше правок в код, добавить проверки
С помощью нативки
BlzFrameIsVisible(frame) - можно определить какая панель выделена в данный момент у игрока. Но эта нативка мб ассихронной, но ей точно также чекаем фрейм на группу. Лично вылетов не наблюдаю.
Какие фреймы можно проверить
панель 1: инфа одиночный юнит
BlzGetFrameByName("SimpleInfoPanelUnitDetail", 0) - одиночный юнит
панель 2: инфа о процессе здания
BlzGetFrameByName("SimpleInfoPanelBuildingDetail", 1) - одиночное здание, которое либо строится, либо кто-то в нем тренируется, воскрешается. Или исследование в этом здание проводится. Короче, панель показывает процесс стройки/обучения/исследования, происходящие в этом здании
панель 3: инфа о пассажирах транспортника или здания
BlzGetFrameByName("SimpleInfoPanelCargoDetail", 2) - одиночный юнит, транспорт. Отображены какие юниты сидят в транспорте.
панель 4: инфа о выделенном итеме
BlzGetFrameByName("SimpleInfoPanelItemDetail", 3) - отвечает за информацию об итеме на карте
панель 5: инфа о выделенном декоре
BlzGetFrameByName("SimpleInfoPanelDestructableDetail", 4) - информация об выделенной декорации
панель 6: инфа о группе
infoPanel = BlzFrameGetParent(BlzGetFrameByName("SimpleInfoPanelUnitDetail", 0)) - можно и по-другому через ORIGIN_FRAME_SIMPLE_UI_PARENT. Но так будет длиннее на одно поколение
selectgroup = BlzFrameGetChild(infoPanel, 5)
Можно отсекать проверками на панель итема или панель декорации.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.