Files
moonwell-client/ai-skills/ui/SKILL.md
T
2026-06-13 11:51:05 +04:00

20 KiB
Raw Blame History

name, description
name description
wow-335a-ui Верстка и разработка UI-аддонов для World of Warcraft 3.3.5a (WotLK, build 12340). Используй этот скилл при любой работе с интерфейсом WoW 3.3.5: создание фреймов через XML или Lua CreateFrame(), позиционирование через SetPoint/якоря, слои (BACKGROUND/ARTWORK/OVERLAY/HIGHLIGHT), страты (frameStrata), текстуры, шрифты, кнопки, ScrollFrame, скрипты-события, структура аддона (TOC + XML + Lua), SavedVariables, работа с Blizzard API патча 3.3.5. Также применяй когда речь идёт об AzerothCore, кастомных интерфейсах для WoW-прайват серверов 3.3.5a, Eluna или модификации FrameXML. ОБЯЗАТЕЛЬНО применяй при задачах pixel-perfect переноса дизайна (Figma/PSD/скриншот) в WoW UI: пересчёт координат с учётом UIParent scale, конвертация цветов HEX→RGBA, подбор шрифтов, подготовка текстур (степени двойки, TexCoord-кроп), устранение расхождений между макетом и игровым интерфейсом.

WoW 3.3.5a — Верстка UI (FrameXML / AddOn Development)

Ключевые ограничения патча 3.3.5a

  • API версии: ## Interface: 30300 (build 12340)
  • Lua версия: Lua 5.1
  • Текстуры: только степени двойки по ширине/высоте (8, 16, 32, 64, 128, 256, 512 px); максимум 512×512
  • Нет :SetSize() — только :SetWidth() / :SetHeight() (или через XML <Size>)
  • Нет hooksecurefunc для защищённых фреймов без taint
  • Отсутствуют многие retail-API: нет C_Timer, нет CreateFramePool, нет PixelUtil
  • Таймеры — через OnUpdate с накоплением elapsed

Pixel-Perfect: перенос дизайна из Figma/PSD в WoW UI

Главная проблема: система координат WoW ≠ пиксели экрана

WoW рисует UI в виртуальных единицах, а не в экранных пикселях. Размер виртуального экрана зафиксирован движком на 768 единиц по высоте, ширина зависит от соотношения сторон:

Разрешение Виртуальная ширина Виртуальная высота
1024×768 1024 768
1280×960 1024 768
1920×1080 1365 768
2560×1440 1365 768

Вывод: высота UIParent всегда = 768. Всё, что сделано в Figma на 768px по высоте, переносится 1:1 без пересчёта. При другой высоте холста в Figma — нужен коэффициент.

Пересчёт координат из Figma в WoW

-- Формула: wow_value = figma_value * (768 / figma_canvas_height)
-- Если макет сделан на 1080px:
local SCALE = 768 / 1080  -- ≈ 0.711

local function px(figma_px)
    return math.floor(figma_px * SCALE + 0.5)  -- округление к ближайшему
end

-- Использование:
frame:SetWidth(px(400))   -- figma: 400px → wow: 284
frame:SetHeight(px(200))  -- figma: 200px → wow: 142
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", px(50), -px(30))

Если макет 768px в высоту — SCALE = 1.0, пересчёт не нужен.

UIParent scale и как он ломает пиксели

Игрок может изменить масштаб UI в настройках. Это сдвигает все координаты.

-- Получить текущий масштаб UIParent
local uiScale = UIParent:GetScale()  -- обычно 0.641.0

-- Перевести экранные пиксели в виртуальные единицы WoW
local function screenPxToWow(px)
    local screenH = select(2, GetPhysicalScreenSize())  -- реальный размер экрана
    return px * (768 / screenH) / uiScale
end

-- Принудительно зафиксировать scale (использовать с умом!)
-- UIParent:SetScale(1.0)  -- сломает все стандартные фреймы

Рекомендация: не трогай UIParent:SetScale(). Вместо этого рисуй в виртуальных единицах и принимай, что у разных игроков масштаб разный. Для абсолютного pixel-perfect на конкретном разрешении — дай опцию ручного масштабирования фрейма в настройках.

-- Масштабируй только свой фрейм
frame:SetScale(0.85)
-- Тогда все дочерние элементы тоже масштабируются

Цвета: Figma HEX/RGBA → WoW RGBA

WoW принимает цвета в диапазоне 0.01.0 (не 0255).

-- Конвертер HEX → WoW RGBA
local function hex(str, a)
    str = str:gsub("#", "")
    local r = tonumber(str:sub(1,2), 16) / 255
    local g = tonumber(str:sub(3,4), 16) / 255
    local b = tonumber(str:sub(5,6), 16) / 255
    return r, g, b, a or 1.0
end

-- Использование:
tex:SetVertexColor(hex("#FF6A00"))         -- непрозрачный
tex:SetVertexColor(hex("#FF6A00", 0.8))    -- 80% непрозрачность
fs:SetTextColor(hex("#FFD100"))
frame:SetBackdropColor(hex("#1A1A2E", 0.95))
frame:SetBackdropBorderColor(hex("#4A90D9"))

Figma opacity → WoW alpha: значение из Figma (например, 75%) делится на 100 → 0.75.

-- Figma: opacity 60%
frame:SetAlpha(0.60)
tex:SetAlpha(0.60)

Шрифты: как приблизить к дизайну

WoW 3.3.5a поддерживает только .ttf шрифты, помещённые в папку аддона.

-- Кастомный шрифт из аддона
fs:SetFont("Interface\\AddOns\\MyAddon\\fonts\\MyFont.ttf", 14, "OUTLINE")
-- Флаги: "", "OUTLINE", "THICKOUTLINE", "MONOCHROME", "OUTLINE,MONOCHROME"

Соответствие Figma → WoW размер шрифта:

Figma (макет 1080px) WoW pt (при SCALE=0.711)
12 px 9
14 px 10
16 px 11
18 px 13
24 px 17
32 px 23
-- Автоматический пересчёт размера шрифта
local function fontSize(figma_pt)
    return math.max(8, math.floor(figma_pt * SCALE + 0.5))
end
fs:SetFont("Fonts\\FRIZQT__.TTF", fontSize(18), "OUTLINE")

Минимальный читаемый размер в WoW: 8 pt. Ниже — нечитаемо.

Встроенные шрифты WoW 3.3.5a:

Fonts\FRIZQT__.TTF    — основной интерфейсный (без кириллицы!)
Fonts\MORPHEUS.ttf    — декоративный (заголовки)
Fonts\ARIALN.TTF      — Arial Narrow
Fonts\skurri.ttf      — шрифт повреждений

Кириллица: встроенные шрифты WoW не поддерживают кириллицу. Положи в аддон .ttf с кириллицей (например, PT Sans, Roboto) и подключи явно.


Текстуры: подготовка под ограничения движка

Требования к текстурам WoW 3.3.5a

  • Ширина и высота — степени двойки: 2, 4, 8, 16, 32, 64, 128, 256, 512
  • Максимум: 512×512 px
  • Формат: .tga (RGBA, 32-bit) или .blp
  • Текстура не обязана быть квадратной: 512×64 — ок, 256×128 — ок

Как уместить большой элемент дизайна

Если элемент из Figma больше 512px — разрезай на части:

[512×256] [512×256]   ← верхняя половина (левая + правая)
[512×256] [512×256]   ← нижняя половина
-- Сборка из 4 текстур
local tl = frame:CreateTexture(nil, "BACKGROUND")
tl:SetTexture("Interface\\AddOns\\MyAddon\\bg_tl")
tl:SetPoint("TOPLEFT", frame, "TOPLEFT")
tl:SetWidth(256); tl:SetHeight(128)

local tr = frame:CreateTexture(nil, "BACKGROUND")
tr:SetTexture("Interface\\AddOns\\MyAddon\\bg_tr")
tr:SetPoint("TOPRIGHT", frame, "TOPRIGHT")
tr:SetWidth(256); tr:SetHeight(128)
-- ... и так далее

TexCoord — показать только часть текстуры

Если в одном файле несколько элементов (спрайт-лист), используй SetTexCoord:

-- SetTexCoord(left, right, top, bottom) — от 0.0 до 1.0
-- Пример: иконка занимает правый нижний квадрант 256×256 атласа
tex:SetTexCoord(0.5, 1.0, 0.5, 1.0)

-- Формула для ячейки в сетке (col/row с нуля):
local cols, rows = 4, 4
local col, row = 2, 1  -- третья колонка, вторая строка
tex:SetTexCoord(
    col/cols,         -- left
    (col+1)/cols,     -- right
    row/rows,         -- top
    (row+1)/rows      -- bottom
)

Важно: при SetTexCoord текстура тайлится внутри заданного Size фрейма. SetAllPoints растягивает — SetWidth/SetHeight + SetPoint = точный размер.


Позиционирование: от Figma к SetPoint

В Figma позиция элемента — это X,Y от левого верхнего угла холста (или родителя). В WoW — якорная система.

Алгоритм переноса:

  1. Определи ближайший угол/сторону в Figma (от чего считать)
  2. Вычисли отступ от этой стороны
  3. Пересчитай через px()
-- Figma (холст 1365×768):
-- Элемент: X=50, Y=30, W=200, H=100
-- → привязка от TOPLEFT UIParent

frame:SetWidth(px(200))
frame:SetHeight(px(100))
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", px(50), -px(30))
--                                              ^^^      ^^^
--                                              X offset  Y offset (отрицательный! вниз)

Знаки осей WoW: X растёт вправо (+), Y растёт вверх (+). Отступ вниз от TOPLEFT — отрицательный.

Figma anchor WoW SetPoint
Top-Left "TOPLEFT", UIParent, "TOPLEFT", x, -y
Top-Right "TOPRIGHT", UIParent, "TOPRIGHT", -x, -y
Bottom-Left "BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y
Bottom-Right "BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -x, y
Center "CENTER", UIParent, "CENTER", x - W/2_canvas, y

Вспомогательный модуль: px-утилиты (вставь в начало Lua)

-- PixelHelper — pixel-perfect вёрстка из Figma
local PH = {}

PH.CANVAS_HEIGHT = 768  -- высота холста в Figma (измени если другая!)

function PH.scale()
    return 768 / PH.CANVAS_HEIGHT
end

-- Пересчёт px из Figma → WoW единицы
function PH.px(v)
    return math.floor(v * PH.scale() + 0.5)
end

-- Цвет из HEX (#RRGGBB или #RRGGBBAA)
function PH.color(hex, alpha)
    hex = hex:gsub("#","")
    local r = tonumber(hex:sub(1,2),16)/255
    local g = tonumber(hex:sub(3,4),16)/255
    local b = tonumber(hex:sub(5,6),16)/255
    local a = alpha or (hex:len() == 8 and tonumber(hex:sub(7,8),16)/255) or 1
    return r, g, b, a
end

-- Размер шрифта из Figma → WoW
function PH.font(pt)
    return math.max(8, math.floor(pt * PH.scale() + 0.5))
end

-- Установка якоря с автопересчётом
function PH.point(frame, point, relativeTo, relPoint, x, y)
    frame:SetPoint(point, relativeTo, relPoint, PH.px(x or 0), PH.px(y or 0))
end

-- Размер фрейма из Figma
function PH.size(frame, w, h)
    frame:SetWidth(PH.px(w))
    frame:SetHeight(PH.px(h))
end

-- Экспорт глобально (или оставь локальным)
MyAddon_PH = PH

Использование:

local px = MyAddon_PH.px
local color = MyAddon_PH.color
local pxfont = MyAddon_PH.font

MyAddon_PH.size(frame, 400, 250)
MyAddon_PH.point(frame, "TOPLEFT", UIParent, "TOPLEFT", 50, -30)
frame:SetBackdropColor(color("#1A1A2E", 0.95))
fs:SetTextColor(color("#FFD100"))
fs:SetFont("Interface\\AddOns\\MyAddon\\fonts\\MyFont.ttf", pxfont(16), "OUTLINE")

Чеклист pixel-perfect переноса

  • Холст Figma = 768px в высоту (или задан PH.CANVAS_HEIGHT)
  • Все размеры и отступы пересчитаны через px()
  • Цвета конвертированы через color()
  • Текстуры нарезаны до 512×512, размеры кратны степени двойки
  • Шрифты .ttf подключены из папки аддона (если кириллица или кастомный шрифт)
  • Размеры шрифтов пересчитаны через font()
  • Y-отступы от верхних якорей — отрицательные
  • Проверено на scale UIParent ≠ 1.0 (изменить в настройках игры для теста)
  • Текстуры без SetAllPoints — точный SetWidth/SetHeight + SetPoint

MyAddon/
├── MyAddon.toc       ← обязателен, имя файла = имя папки
├── MyAddon.xml       ← верстка фреймов (опционально)
└── MyAddon.lua       ← логика

TOC-файл (3.3.5a)

## Interface: 30300
## Title: My Addon
## Notes: Описание аддона
## Author: Ilyas
## Version: 1.0
## SavedVariables: MyAddonDB

MyAddon.xml
MyAddon.lua

Файлы загружаются в порядке перечисления. XML раньше Lua — фреймы существуют к моменту выполнения скриптов.


XML-верстка

Минимальный шаблон XML-файла

<Ui xmlns="http://www.blizzard.com/wow/ui/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">

  <Script file="MyAddon.lua"/>

  <Frame name="MyAddon_Frame" parent="UIParent" toplevel="true" enableMouse="true" movable="true">
    <Size>
      <AbsDimension x="300" y="200"/>
    </Size>
    <Anchors>
      <Anchor point="CENTER"/>
    </Anchors>
    <Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background"
              edgeFile="Interface\DialogFrame\UI-DialogBox-Border"
              tile="true">
      <BackgroundInsets>
        <AbsInset left="11" right="12" top="12" bottom="11"/>
      </BackgroundInsets>
      <TileSize><AbsValue val="32"/></TileSize>
      <EdgeSize><AbsValue val="32"/></EdgeSize>
    </Backdrop>
    <Layers>
      <Layer level="ARTWORK">
        <FontString name="$parentTitle" inherits="GameFontNormalLarge" text="Заголовок">
          <Anchors>
            <Anchor point="TOP"><Offset><AbsDimension x="0" y="-16"/></Offset></Anchor>
          </Anchors>
        </FontString>
      </Layer>
    </Layers>
    <Frames>
      <Button name="$parentClose" inherits="UIPanelCloseButton">
        <Anchors>
          <Anchor point="TOPRIGHT"><Offset><AbsDimension x="4" y="4"/></Offset></Anchor>
        </Anchors>
      </Button>
    </Frames>
    <Scripts>
      <OnLoad>MyAddon_Frame_OnLoad(self)</OnLoad>
      <OnMouseDown>self:StartMoving()</OnMouseDown>
      <OnMouseUp>self:StopMovingOrSizing()</OnMouseUp>
    </Scripts>
  </Frame>

</Ui>

Точки якоря (FRAMEPOINT)

Точка Расположение
TOPLEFT Верхний левый угол
TOP Верхний центр
TOPRIGHT Верхний правый угол
LEFT Левый центр
CENTER Центр
RIGHT Правый центр
BOTTOMLEFT Нижний левый угол
BOTTOM Нижний центр
BOTTOMRIGHT Нижний правый угол

SetPoint через Lua

-- frame:SetPoint(point, relativeTo, relativePoint, offsetX, offsetY)
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 10, -10)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:SetPoint("BOTTOMRIGHT", anotherFrame, "TOPRIGHT", 0, 5)

-- Сброс якорей перед переустановкой
frame:ClearAllPoints()
frame:SetPoint("CENTER", UIParent, "CENTER")

Правило: фрейм должен иметь достаточно якорей для однозначного определения позиции и размера. Либо Size + один якорь, либо два якоря задающих оба измерения.


Слои и страты

Draw Layers (внутри фрейма, сверху вниз по видимости)

BACKGROUND (0) → BORDER (1) → ARTWORK (2) → OVERLAY (3) → HIGHLIGHT (4)
  • HIGHLIGHT — автоматически показывается при наведении мыши (требует enableMouse="true")
  • FontString всегда рисуется поверх Texture в том же слое
  • Порядок текстур в одном слое не гарантирован → разноси по слоям

Frame Strata (между фреймами)

WORLD → BACKGROUND → LOW → MEDIUM → HIGH → DIALOG → FULLSCREEN → FULLSCREEN_DIALOG → TOOLTIP
frame:SetFrameStrata("DIALOG")
frame:SetFrameLevel(5)  -- внутри страты (0–255)

Виджеты, события, Blizzard API

Полный справочник — в references/widgets-and-api.md. Читай его когда нужны: типы фреймов, Button/EditBox/ScrollFrame/StatusBar, события OnEvent/OnUpdate, SavedVariables, пути к текстурам, типичные ошибки.


Шаблонный аддон «с нуля» (минимальный рабочий пример)

MyAddon.toc

## Interface: 30300
## Title: MyAddon
## SavedVariables: MyAddonDB
MyAddon.lua

MyAddon.lua

local ADDON_NAME = "MyAddon"

local f = CreateFrame("Frame", ADDON_NAME.."_Frame", UIParent)
f:SetWidth(250)
f:SetHeight(150)
f:SetPoint("CENTER")
f:SetFrameStrata("DIALOG")
f:SetMovable(true)
f:EnableMouse(true)
f:SetBackdrop({
    bgFile   = "Interface\\DialogFrame\\UI-DialogBox-Background",
    edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
    tile = true, tileSize = 32, edgeSize = 32,
    insets = {left=11, right=12, top=12, bottom=11}
})
f:Hide()

-- Заголовок
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOP", f, "TOP", 0, -12)
title:SetText("My Addon")

-- Закрытие
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", 4, 4)
close:SetScript("OnClick", function() f:Hide() end)

-- Перетаскивание
f:SetScript("OnMouseDown", function(self, btn)
    if btn == "LeftButton" then self:StartMoving() end
end)
f:SetScript("OnMouseUp", function(self) self:StopMovingOrSizing() end)

-- Инициализация
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", function(self, event, name)
    if event == "ADDON_LOADED" and name == ADDON_NAME then
        MyAddonDB = MyAddonDB or {}
        print("|cff00ff00"..ADDON_NAME.."|r загружен. /myaddon")
    end
end)

-- Слэш-команда
SLASH_MYADDON1 = "/myaddon"
SlashCmdList["MYADDON"] = function()
    if f:IsShown() then f:Hide() else f:Show() end
end