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

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

Содержание:
источник
кратко: по словам автора фреймы могут приводить к десинку во время обработки данных.
Фреймы, обрабатывающие user input, не будут синхронизировать свое состояние.
User input - пользовательская информация, которую выдает машина (если говорить про фрейм, то это text frame: string; value frame: integer,real; flag frame: boolean; frame и др).
Warcraft 3 использует для своей сетевой функциональности разновидность технологии, называемой протоколом Lockstep. Это метод, который предоставляет только минимум данных: user input и некоторые тесты синхронизации, чтобы знать, что игроки по-прежнему играют в одну и ту же игру.
Вне общих входов каждый пользовательский компьютер моделирует игру самостоятельно. Это значит, что фреймы создаются на каждой машине свои, и данные тоже свои.

Вы можете делать что угодно с фреймами только для одного пользователя, пока результат при использовании этого фрейма остается одинаковым для всех игроков (пока результат вообще влияет на симуляцию игры warcraft 3). Можете редактировать параметры фреймов через нативки set, по крайне мере, это не вызывает десинхрона на локальной машине.
Это означает, что в GetLocalPlayer можно делать почти все для фреймов (положение, размер, видимость, текстура, цвет, текст ...), но это игра с огнем , при неправильном использовании ваших фреймов будет приводить к десихрону. Вы должны будете знать, где у вас происходит ошибка.
Для синхронизации важно соблюсти, чтобы инфа была одинаковой у каждого игрока. Проблему десинхрона вызывают чтение с помощью get-нативок. Следовательно, не гарантируется, что результаты для этих natives ниже будут одинаковыми для всех игроков:
BlzFrameGetText
BlzFrameGetTextSizeLimit
BlzFrameGetEnable
BlzFrameGetAlpha
BlzFrameGetValue
BlzFrameGetHeight
BlzFrameGetWidth
BlzFrameGetParent
BlzFrameIsVisible
также могут вызывать десинхрон и ниже нативки
--дефолт вроде не вызывает десихрон
BlzGetOriginFrame(frameType, index)
--имя и context кастомных фреймов могут отличаться
BlzGetFrameByName(name, createContext) 
BlzFrameGetName(frame)
--родитель,потомки и их кол-во тоже может отличаться
BlzFrameGetParent(frame) 
BlzFrameGetChild(frame, index) 
BlzFrameGetChildrenCount(frame)
Пример, если у каждого игрока хранится разная инфа, и если заставить прочесть инфу с помощью get нативок выше, у каждого будет она разной. Это тоже рассинхрон вызывает, но не всегда, и если с этими данными ввести в игру, то может и привести.

Как можно решить проблему десинхронизации?

Ответ - использовать события фреймов (FrameEvents) и константы
события фрейма и его константы
регистр событии
---@param whichTrigger trigger
---@param frame framehandle
---@param eventId frameeventtype
---@return event
function BlzTriggerRegisterFrameEvent(whichTrigger, frame, eventId) end	-- (native)
фрейм-триггер, вызвавший триггер
---@return framehandle
function BlzGetTriggerFrame() end	-- (native)
id событие триггера
---@return frameeventtype
function BlzGetTriggerFrameEvent() end	-- (native)
возвращает число. некоторые фреймы могут изменять значения
---@return real
function BlzGetTriggerFrameValue() end	-- (native)
возвращает строку. при вводе текста в фрейм выводит текст
---@return string
function BlzGetTriggerFrameText() end	-- (native)
Используйте эти события и константы. Триггеры фреймов и константы не вызывают десинхронизацию, инфа с констант синхронизируется между машинами, и значит, инфа предоставляется всем игрокам.
В триггерах фреймов обычно фиксируется изменение данных фрейма. Можно сохранять данные в глобальные переменные, массив. Это действие точно будет синхронизировано! А когда нужно будет узнать значение, мы достает из переменной информацию.
Пример. Я хочу узнать, выделяет ли мышь фрейм, т.е. находится ли курсор внутри фрейма. Раньше для такого изобретал велосипед: вешал на фрейм подсказку-пустышку. При наведении на фрейм курсора подсказка становилась видимой. Для этого таймером приходится чекать подсказку BlzFrameIsVisible. Но можно было использовать события наведения: вход/выход, + массив, отображающий состояние фрейма (видим/не видим). Таким образом решает задачу событие. Тут в зависимости от задач, большинство решается событиями. Нужно посмотреть какие события есть у фрейма. Помните, что у разных типов фреймов события могут отличаться. Пример, checkbox (вкл/выкл ли галочка), editbox (какой текст вводит игрок - лучше через событие), slider (состояние шкалы: value лучше получать через ивент) и др. Это лучше, чем просто получет напрямую от get natives

Хранить данные фреймов в массив

Как можно использовать только один фрейм, отображающий разные вещи для разных игроков, без рассинхронизации?
Один из способов - разделить отображаемое и данные. Данные существуют для всех игроков, но данные, отображаемые во фрейме, различаются. Допустим, у одного есть Goldcosts, который сохраняется в массиве на playerIndex.
Давайте, поговорим про способы хранения, если в глобалку fr записать фрейм
fr = CreateFrame(...)
то у всех игроков локально создается фрейм и записывается в эту глобалку. При обращении fr у всех разные данные, верно?
А если сделать по-другому, записать в массив, где у каждого игрока записан свой фрейм локально. Пример, у игрока-1 записано в fr[0], а у игрока-2 записано в fr[1]
local a = GetPlayerId(GetLocalPlayer())
fr ={}
fr[a] = CreateFrame(...)
Вместо использования только одного фрейма, можно также создать всем игрокам свой фрейм, которому можно обратиться локально по номеру.
Для этого требуется минимум использования GetLocalPlayer и знаний. Но больше фреймов, и событий. Каждый видит только свой фрейм, но по-прежнему выполняет все действия, которые делают другие.
CreateContext здесь отличный помощник, если кто-то не хочет создавать Frame-Variables (переменные). Автор советует использовать Contex, который есть в нативках. Можете создать свой фрейм локально через contex
и снова здравствуй, contex
Что такое Context? По сути это число. Если заглянуть в нативки. Множество фреймов могут использовать один и тот же шаблон, но у каждого разное содержимое (содержание, или контект)
При создании мы указываем contex
BlzCreateFrame(name, owner, priority, createContext)
BlzCreateSimpleFrame(name, owner, createContext)
BlzCreateFrameByType(typeName, name, owner, inherits, createContext)
При обращении к этому фрейму, мы задает не только имя, но и context
BlzGetFrameByName(name, createContext)
Проще сказать, что так можно пронумеровать фреймы и его потомков.
Зачем нужен Context? Это форма обращения к фрейму, вы можете обратиться по номеру. Все именные фреймы-шаблоны имеют названия "MyName". Если я создам из этого шаблона 10 фреймов, то одноименный фрейм будет переписываться. Если первый фрейм имеет "MyName", то не смогу обратиться к нему по имени BlzGetFrameByName (name, createContext), тк переменная записана на новый, т.е. последний. Поэтому для этого рекомендуется каждому отдельному фрейму при создании задавать новый номер, нумеровать. Возможно, можно локально создавать фрейм, вот, что автор имел ввиду

Примеры

Давайте наглядно представим различные приемы на примере.

Пример 1: у каждого игрока набор переменных

Этот пример показывает то, о чем говорили ранее. Тут используется способ хранения. Только здесь хранится не фреймы, а информация о самих фреймах
Когда игрок выделяет/выбирает юнита, на фрейме iconButton будет отображаться иконка выбранного юнита, а при нажатии на кнопку создает юнит данного типа для игрока, нажавшего на кнопку.
Кнопка должна работать для всех игроков одновременно. Но инфа отображается локально для всех игроков
.
1.Версия: один фрейм с известными всем данными.
код


do
    local real = MarkGameStarted
 function MarkGameStarted()
        real()
    -- Give Vision
    FogMaskEnable(false)
    FogEnable(false)
    EnableWorldFogBoundary(false)
    
    local button = BlzCreateFrameByType("BUTTON", "MyIconButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "ScoreScreenTabButtonTemplate", 0)
    local buttonIconFrame = BlzCreateFrameByType("BACKDROP", "MyIconButtonIcon", button, "", 0)
    BlzFrameSetAllPoints(buttonIconFrame, button)
    BlzFrameSetAbsPoint(button, FRAMEPOINT_CENTER, 0.4, 0.5)
    BlzFrameSetSize(button, 0.03, 0.03)
    BlzFrameSetTexture(buttonIconFrame, "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn", 0, false)
 
    -- setup the selection data sharing
    local selectedUnitType = __jarray(0)
    local selectionTrigger = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(selectionTrigger, EVENT_PLAYER_UNIT_SELECTED)
    TriggerAddAction(selectionTrigger, function()
        -- get the unitTypeId from the selected Unit
        local unitCode = GetUnitTypeId(GetTriggerUnit())
        -- save it onto the active player, all players do that.
        selectedUnitType[GetTriggerPlayer()] = unitCode
        -- update the displayed Texture for the button this only happens for the active player
        if GetLocalPlayer() == GetTriggerPlayer() then
            BlzFrameSetTexture(buttonIconFrame, BlzGetAbilityIcon(unitCode), 0, false)
        end
    end)

    -- the button clicking
    local trigger = CreateTrigger()    
    BlzTriggerRegisterFrameEvent(trigger, button, FRAMEEVENT_CONTROL_CLICK)
    TriggerAddAction(trigger, function()
        local player = GetTriggerPlayer()
        CreateUnit(player, selectedUnitType[player] , GetPlayerStartLocationX(player), GetPlayerStartLocationY(player), 0)
    end)
 end
end

Пример 2: использование события синхронизации

В рефордже ввели новые функции и события синхронизации.
основные нативки синха
---@param whichTrigger trigger
---@param whichPlayer player
---@param prefix string
---@param fromServer boolean
---@return event
function BlzTriggerRegisterPlayerSyncEvent(whichTrigger, whichPlayer, prefix, fromServer) end	-- (native)

---@param prefix string
---@param data string
---@return boolean
function BlzSendSyncData(prefix, data) end	-- (native)

---@return string
function BlzGetTriggerSyncPrefix() end	-- (native)

---@return string
function BlzGetTriggerSyncData() end	-- (native)
Придется создавать отдельный триггер синха. И из какого-нибудь кода вызывать работу триггера BlzSendSyncData. Но перед этим нужно выставить информацию, которую хотите, чтоьы синхронизировалась с остальными. Вроде не трудно
2.Версия: все данные из фреймов можно вызвать локально по клику, чтобы поделиться ими. Можно было брать запрещенные get native , и получать от них инфу. А потом синхронизировать с помощью событий синха. Только учтите, что мб десинхи. Хотя я не сталкивался с проблемами
Этот пример на самом деле не очень хорош, потому что все данные и события итак синхронизируются по умолчанию. Просто для примера показываю. Но имейте в виду, что использовать его тоже можно
код

do
    local real = MarkGameStarted
 function MarkGameStarted()
    real()    -- Give Vision
    FogMaskEnable(false)
    FogEnable(false)
    EnableWorldFogBoundary(false)
    
    local button = BlzCreateFrameByType("BUTTON", "MyIconButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "ScoreScreenTabButtonTemplate", 0)
    local buttonIconFrame = BlzCreateFrameByType("BACKDROP", "MyIconButtonIcon", button, "", 0)
    BlzFrameSetAllPoints(buttonIconFrame, button)
    BlzFrameSetAbsPoint(button, FRAMEPOINT_CENTER, 0.4, 0.5)
    BlzFrameSetSize(button, 0.03, 0.03)
    BlzFrameSetTexture(buttonIconFrame, "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn", 0, false)
 
    -- setup the selection trigger
    local selectedUnitType = 0
    local selectionTrigger = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(selectionTrigger, EVENT_PLAYER_UNIT_SELECTED)
    TriggerAddAction(selectionTrigger, function()
        -- do only something for the active player
        if GetLocalPlayer() == GetTriggerPlayer() then
            -- get the unitTypeId from the selected Unit
            local unitCode = GetUnitTypeId(GetTriggerUnit())
            -- save it onto the active player, all players do that.
            selectedUnitType = unitCode
            -- update the displayed Texture for the button
            BlzFrameSetTexture(buttonIconFrame, BlzGetAbilityIcon(unitCode), 0, false)
        end
    end)

    -- the button clicking
    local trigger = CreateTrigger()    
    BlzTriggerRegisterFrameEvent(trigger, button, FRAMEEVENT_CONTROL_CLICK)
    TriggerAddAction(trigger, function()
        
        if GetLocalPlayer() == GetTriggerPlayer() then
            BlzSendSyncData("CreateUnitByButton", selectedUnitType)
        end
    end)

    local syncTrigger = CreateTrigger()
    for int = 0, bj_MAX_PLAYERS - 1 do
        BlzTriggerRegisterPlayerSyncEvent(syncTrigger, Player(int), "CreateUnitByButton", false)
    end
    TriggerAddAction(syncTrigger, function()
        local player = GetTriggerPlayer()
        CreateUnit(player, BlzGetTriggerSyncData() , GetPlayerStartLocationX(player), GetPlayerStartLocationY(player), 0)        
    end)
 end
end

Пример 3

Все фреймы создаются и обновляются для всех игроков: однако, этот пример использует GetLocalPlayer () только один раз, сразу после создания.
код

do
    local real = MarkGameStarted
 function MarkGameStarted()
    real()    -- Give Vision

    -- Give Vision
    FogMaskEnable(false)
    FogEnable(false)
    EnableWorldFogBoundary(false)

    local buttonTrigger = CreateTrigger()

    -- create one button for each player
    for int = 0, bj_MAX_PLAYERS - 1 do
        local button = BlzCreateFrameByType("BUTTON", "MyIconButton", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "ScoreScreenTabButtonTemplate", int)
        local buttonIconFrame = BlzCreateFrameByType("BACKDROP", "MyIconButtonIcon", button, "", int)
        BlzFrameSetVisible(button, GetLocalPlayer() == Player(int))
        BlzFrameSetAllPoints(buttonIconFrame, button)
        BlzFrameSetAbsPoint(button, FRAMEPOINT_CENTER, 0.4, 0.5)
        BlzFrameSetSize(button, 0.03, 0.03)
        BlzFrameSetTexture(buttonIconFrame, "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn", 0, false)      
        BlzTriggerRegisterFrameEvent(trigger, button, FRAMEEVENT_CONTROL_CLICK)
    end
    
    -- setup the selection data sharing
    local selectedUnitType = __jarray(0)
    local selectionTrigger = CreateTrigger()
    TriggerRegisterAnyUnitEventBJ(selectionTrigger, EVENT_PLAYER_UNIT_SELECTED)
    TriggerAddAction(selectionTrigger, function()
        local player = GetTriggerPlayer()
        -- get the unitTypeId from the selected Unit
        local unitCode = GetUnitTypeId(GetTriggerUnit())
        -- save it onto the active player, all players do that.
        selectedUnitType[player] = unitCode
        -- update the displayed Texture for the button
        BlzFrameSetTexture(BlzGetFrameByName("MyIconButtonIcon", GetPlayerId(player)), BlzGetAbilityIcon(unitCode), 0, false)
        
    end)

    TriggerAddAction(buttonTrigger, function()
        local player = GetTriggerPlayer()
        CreateUnit(player, selectedUnitType[player] , GetPlayerStartLocationX(player), GetPlayerStartLocationY(player), 0)
    end)
 end
end

Содержание
`
ОЖИДАНИЕ РЕКЛАМЫ...