// 권두부록 : 위치 이동 플러그인 - oUF Movable Frame
플러그인을 레이아웃 애드온에 병합해서 사용하는 방법을 소개할 생각이었으나 훑어보니 movable frame 외에는 썩 마음에 드는 플러그인이 없습니다.
대부분이 oUF element로 들어있는데 플러그인으로 먼저 나온 기능이 oUF 자체에 많이 흡수된 것 같습니다.
그래서 위치 이동 기능을 더해주는 플러그인인 oUF: MovableFrames만 간단히 소개하고 가겠습니다.
단순히 설치만 하면 되고 레이아웃쪽에서 특별히 신경쓸 필요가 없습니다.
이 예제에서는 oUF, oUF_MovableFrames, oUF_Tutor 세 개의 애드온 폴더가 모두 AddOns 아래에 있으면 됩니다.
설치하고 /omf 명령어로 이동 모드에 진입합니다.
oUF로 생성된 개체의 위치가 표시되고 드래그로 이동한 후 /omf 로 저장하면 됩니다.
프레임의 위치는 WTF 폴더의 oUF_MovableFrames.lua 파일에 저장됩니다. 미세조정을 원한다면 캐릭 선택창까지 나온 다음 저장파일을 직접 수정하셔도 됩니다.
(왼쪽에 raid25가 있는데 party, raid40 레이아웃도 추가했으나 표시되지 않습니다. 이것은 그룹헤더는 표시할 조건이 충족되어 실제 표시될 때 생성하기 때문입니다. 스샷을 찍기 전에 공찾을 다녀와서 raid25는 생성되어서 표시됩니다.)
oUF: 유닛프레임 제작 #2 :: 파티, 레이드 프레임 생성, tag 수정하기
v0.5 : 파티, 레이드 프레임 생성
v0.6 ~ 0.8 : 개인추가 태그 생성
v0.9 ~ 0.10 : 파티 프레임 표시/숨김 조건 버그 수정
오늘 할 일은:
- 파티와 레이드 프레임을 만듭니다.
- #0의 최초 예제에서는 생명력 수치가 , 콤마로 구분되었으나 oUF의 태그 중에는 이걸 해주는 것이 없습니다. 이걸 해주는 태그를 직접 만들고 레이드 프레임에 이름을 사용하기 위해 글자수를 줄이는 태그도 만듭니다.
* 그룹헤더
보스 유닛프레임을 만들 때 for문을 이용해 boss1~boss4까지의 유닛프레임을 차례로 만들었습니다.
그런데 레이드 프레임을 만들려고 이걸 40개 돌리는 것은 그리 즐겁지 못한 일일 것입니다.
블리자드는 레이드 프레임 애드온 제작용으로 SecureGroupHeaderTemplate라는 형식을 지원합니다(블리자드 자체 레이드 프레임에는 쓰이지 않고 애드온 제작에만 쓰입니다.).
그룹헤더 개체는
- 이 헤더에 포함될 유닛 범주를 지정하고(e.g. 파티1, 2, 3, 4만) 이 그룹이 표시될 조건을 설정해서 생성해두면
- 표시 조건이 만족하고 표시될 유닛이 있을 때 해당 유닛의 유닛프레임을 필요한 것만 생성해줍니다.
oUF에서는
oUF:SpawnHeader("이름", "별도 템플릿", "표시조건", [,이하 attribute 쌍])
로 생성합니다.
v0.10 기준으로 코드를 보겠습니다.
core.lua 150번 줄부터 파티 그룹헤더를 생성합니다.
M.Party = oUF:SpawnHeader(
"oUF_TutorParty",
nil,
"custom [@raid1, exists] hide; [group:party,nogroup:raid] show; hide",
-- attributes
"initial-width", C.party.width,
"initial-height", C.party.height,
"oUF-initialConfigFunction", [[
local header = self:GetParent()
self:SetWidth(header:GetAttribute("initial-width"))
self:SetHeight(header:GetAttribute("initial-height"))
]],
"showRaid", false,
"showParty", true,
"showSolo", false,
"showPlayer", false,
"point", "TOP",
"xOffset", 0,
"yOffset", -50
)
M.Party:SetPoint(unpack(C.party.pos))
첫 세 입력값은 각각 헤더 이름, 별도 템플릿, 표시 조건입니다.
템플릿은 nil을 넣으면 SecureGroupHeaderTemplate이라는, 그룹헤더용 기본 템플릿이 적용됩니다.
표시 조건은
1) 다음 중 표시할 조건을 ,로 나열하거나 ( e.g. 10인 이하 레이드나 파티일 조건이면 "raid10, party" )
raid40, raid25, raid10, raid, party, solo
2) 이 예제처럼 custom 뒤에 매크로 조건문처럼 표시 / 숨김 조건을 직접 작성하면 됩니다.
이 예제에서는 custom으로 앞에서부터
raid1이 있으면 숨김
그룹유형이 파티이고 레이드가 아니면 표시
위 두 조건이 다 아니면 숨김
이제 보니 맨 앞의 것은 불필요하네요. custom이 아니라 제시된 조건을 사용했다면 "party"를 넣었을 것입니다.
이후로는 두 개씩 쌍으로 입력값이 들어가는데 그룹헤더의 설정값들을 넣는 것입니다.
그룹헤더의 설정값 리스트는 다음 페이지에 있습니다.
"initial-width", C.party.width,
"initial-height", C.party.height,
매 프레임의 크기는 config.lua에 설정된 값을 적용합니다.
"oUF-initialConfigFunction", [[
local header = self:GetParent()
self:SetWidth(header:GetAttribute("initial-width"))
self:SetHeight(header:GetAttribute("initial-height"))
]],
이 부분은 개별 유닛프레임이 생성될 때 oUF에서 적용할 초기화 함수입니다. 헤더로 생성한 유닛은 스타일 함수가 적용되기 전에 유닛프레임 크기가 설정됩니다.
사실 이걸 실수로 oUF- 대신 ouF로 쳤는데도 잘 동작했던걸로 보아 없어도 되거나 대소문자를 구분하지 않는 모양입니다.
"showRaid", false,
"showParty", true,
"showSolo", false,
공대일 때 표시안함
파티일 때 표시
솔플시 표시안함
"showPlayer", false,
내 유닛버튼은 제외합니다. 파티원 표시할 조건에서만 동작하며 레이드프레임일 때는 무조건 표시됩니다.
"point", "TOP",
"xOffset", 0,
"yOffset", -50
각 유닛간의 배치법입니다.
이 상황에서는 party2의 위치가 party2:SetPoint("TOP", party1, "BOTTOM", 0, -50) 이런 식으로 설정됩니다. 대상의 위치기준점 BOTTOM은 자동으로 TOP의 반대가 적용되는 것입니다.
!주의 : 테이블은 맨 뒤에도 ,가 들어가도 무관하나 이것은 함수의 인수로 넣는 것이라 맨 뒤에는 ,가 들어가면 안됩니다.
위치 지정은 헤더 자체의 "TOPLEFT" 위치를 지정하면 됩니다.
* 그룹헤더의 스타일 함수
자, 이 상황에서 나 말고 두 명이 더 있는 파티에 들어간다 가정합시다.
그룹헤더는 파티일 때 표시하며 나를 제외하니까 party1과 party2의 유닛프레임을 각각 만들 것이고 oUF는 이게 생성될 때 스타일 함수를 각각에 적용합니다.
그룹헤더로 생성하는 유닛의 스타일 함수에는 유닛명으로
"showRaid"가 true이면 "raid"
"showRaid"는 true가 아니고 "showParty"가 true이면 "party"
가 전달됩니다.
그래서 대상 유닛의 스타일 함수를 M.styles["target"]으로 만든 것처럼 M.styles["party"]로 스타일 함수를 만들면 됩니다.
M.styles["party"] = function(f, unit)
A.InitButton(f, unit)
A.CreateHealth(f)
A.CreatePower(f)
A.CreatePortrait(f)
A.CreateTexts(f)
A.CreateCastBar(f)
A.CreateAura(f)
f.Health.colorDisconnected = true
f.Power.colorDisconnected = true
end
우리는 지금까지 A.InitButton에서 유닛버튼의 크기를 지정해왔습니다.
그런데 헤더로 생성된 유닛은 크기가 설정되어서 스타일 함수로 넘어오니 이건 건드리지 않으면 됩니다. 그래서 A.InitButton 함수가 수정됩니다.
A.InitButton = function(f, unit)
f:SetScript("OnEnter", UnitFrame_OnEnter)
f:SetScript("OnLeave", UnitFrame_OnLeave)
f:SetFrameStrata("LOW")
if unit:match("(boss)%d?$") == "boss" then
f:SetSize(C["boss"]["width"], C["boss"]["height"])
elseif not (unit == "party" or unit == "raid") then
f:SetSize(C[unit]["width"], C[unit]["height"])
end
f:SetBackdrop(backdropTbl)
f:SetBackdropColor(.1, .1, .1)
A.CreateShadow(f)
end
보스이면 boss1, boss2, ... 등이 유닛명이나 실제 설정값은 동일하게 적용하니까 따로 처리했고요, 유닛이 "party", "raid"가 아닐 때만 크기를 설정합니다.
헤더로 생성된 유닛은 이게 적용 안되지요.
나머지는 차이 없고 파티 스타일 함수는 대상 프레임과 거의 같습니다. 크기가 조금 작기 때문에 A.CreatePortrait에서 초상화 폭을 바꾼 정도입니다.
이렇게, 본인을 제외한 4명의 유닛프레임이 화면 왼쪽에 나옵니다.
* 레이드 헤더에 추가되는 attributes
raid25 헤더 생성시 적용하는 속성을 보면 (core.lua 171번 줄)
"showRaid", true,
"showParty", false,
"showSolo", false,
표시조건 레이드일 때
"point", "LEFT",
"xOffset", 4,
한 파티 안에서는 가로로 정렬될 것이니 왼쪽에서 오른쪽으로 4 간격으로 배치합니다.
"maxColumns", 6,
"unitsPerColumn", 5,
최대 6줄(가끔 6파티도 사용하므로)
한 줄당 유닛 수 5명
"columnAnchorPoint", "TOP",
"columnSpacing", 4,
각각의 줄들은 위에서 아래로 성장
간격 4
"groupBy", "GROUP",
파티 순서대로 정렬
"groupFilter", "1,2,3,4,5,6",
"groupingOrder", "1,2,3,4,5,6"
1~6파까지 표시
정렬 순서는 자연스럽게 1,2,3,4,5,6파 순서
레이드 프레임에는 텍스트는 이름만 쓸 것이고 던전역할, 공대장 마크를 추가할 것이라서 A.CreateTexts 대신에 다른 함수를 짜서 사용했습니다.
api.lua 248번 줄
A.CreateRaidElements = function(f)
local name = f.Health:CreateFontString(nil, "OVERLAY")
name:SetFont(font1, 12, "OUTLINE")
name:SetPoint("TOPLEFT", 4, -12)
f:Tag(name, "[nameshort]")
local role = f.Health:CreateTexture(nil, "OVERLAY")
role:SetSize(14, 14)
role:SetPoint("TOPLEFT", 3, 2)
local leader = f.Health:CreateTexture(nil, "OVERLAY")
leader:SetSize(14, 14)
leader:SetPoint("LEFT", role, "RIGHT", 3, 0)
f.Name = name
f.LFDRole = role
f.Leader = leader
f.Range = {
insideAlpha = 1,
outsideAlpha = .4
}
end
이름쪽에 태그로 nameshort를 사용했는데 이름이 너무 길면 프레임 밖으로 나가기 때문에 새로 만든 태그입니다.
뒤에 더 다룹니다.
세 가지 element 기능이 더 쓰였는데
던전역할 LFDRole : texture, 크기와 위치를 지정해두면 역할에 맞는 텍스쳐가 지정됨
파티/공대장 : 아무 형식, 텍스쳐이면 자동으로 왕관, 아니면 생성해둔 개체가 표시 / 숨김
거리체크 : .Range라는 테이블에 위와 같이 거리내 / 거리밖에 적용할 투명도를 설정합니다.
결과로 raid25 유닛프레임은 다음과 같습니다.
(작은 프레임에 적용하니 테두리 그림자가 너무 진해서 눈아프네요. 이거 바꿔야겠습니다)
자, 이게 문제가 중간에 파티가 빌 때 해당 파티를 비워주지 않습니다.
즉 2파티에 한 명이 비면 3파 첫 사람을 2번째 줄에 표시해버립니다.
이걸 해결하려면 매 파티마다 그룹헤더를 따로 만들면 됩니다.
for i = 1, 8 do
oUF:SpawnHeader( ...
end
이렇게 해서 각각의 헤더를 만들되 설정값에서 5명만 표시하도록, groupFilter에서 1~8 파티 중 하나만 지정하는 것이지요.
그런데 귀찮아서 게으름좀 피웠습니다.
* Tag 추가
앞에서 보셨듯 태그는 문자열의 텍스트를 자동으로 업데이트합니다.
새 태그를 추가해서 사용하려고 하면
- 어떤 텍스트가 지정될지는 oUF.Tags.Methods 테이블에 들어있는 함수가,
- 어떤 조건에서 텍스트를 갱신할지는 oUF.Tags.Events 테이블에 들어있는 값이
결정하는 것이니 이 두 가지를 넣어주면 됩니다.
Spawn하기 전에 추가되어야 사용할 수 있을 것이니 embeds.xml에서 좀 앞쪽에 해당 파일을 넣습니다.
다음은 수치를
100만 미만일 때는 천단위 ,콤마만 포함해서 전체
이상일 때는 유효숫자 3개 이상의 mega 단위로
표시하기 위한 함수입니다.
tags.lua 8번 줄
local FormatLargeNumber = function(val)
if not val then return "0" end
if val < 1e6 then
return AbbreviateLargeNumbers(val)
elseif val < 1e7 then
return string.format("%.2fm", val/1e6)
elseif val < 1e8 then
return string.format("%.1fm", val/1e6)
else
return string.format("%dm", val/1e6)
end
end
100만 미만이면 AbbreviateLargeNumbers라는 와우에 기본으로 있는 함수를 사용하고요(100만까지는 ,를 더하고 더 커지면 '...만' 등으로 표시하는, 기본 유닛프레임의 체력 표시에 쓰이는 함수입니다), 그 이상은 100만~1000만까지는 소수점 아래 두 자리까지, 1000만~1억까지는 한 자리, 1억 이상은 소수점 위만 m 단위로 표시합니다.
이걸 이용해서 체력 표시 태그를 추가하려면
oUF.Tags.Methods["curhpabb"] = function(unit)
return FormatLargeNumber(UnitHealth(unit))
end
oUF.Tags.Events["curhpabb"] = "UNIT_HEALTH"
이렇게만 하면 됩니다.
[curhpabb] 라는 태그를 추가한 것이고 태그 이름을 키값으로 넣습니다.
태그 함수에는 유닛이 인수로 전달됩니다.
현재 생명력을 위에서 만든 함수에 적용해서 return하면 됩니다.
생명력 관련 문자열이니 이벤트로는 UNIT_HEALTH 이벤트가 지정되면 됩니다.
이게 적용된 플레이어 / 대상 프레임은
이런 모양이고요.
nameshort 태그는
oUF.Tags.Methods["nameshort"] = function(unit, r)
local name = UnitName(r or unit)
if string.byte(name) > 127 then
if string.len(name) > 18 then
name = string.sub(name, 1, 15) .. ".."
end
return name
else
if string.len(name) > 9 then
name = string.sub(name, 1, 7) .. ".."
end
return name
end
end
oUF.Tags.Events["nameshort"] = "UNIT_NAME_UPDATE"
- 한글일 때 6글자를 넘으면 1~5글자 + .. 으로 바꿔서
- 영문일 때는 9글자를 넘으면 1~7글자 + ..으로
이름을 표시합니다.
한글은 매 글자가 3바이트씩이라서 따로 처리해야 하는데 string.byte로 첫 글자의 ascii 코드를 잡아서 127이 넘으면 1바이트가 넘는 글자입니다. 우리 클라이언트는 2바이트 문자를 안쓰니 자동으로 3바이트가 되는 것이고 실제로는 첫 자리 코드가
- 192~223 : 2바이트
- 224~239 : 3바이트
- 240~247 : 4바이트
- 248~251 : 5바이트
- 252~254 : 6바이트
문자입니다.
string.sub가 원하는 글자수 범위로 자르는 함수인데 2바이트 이상 문자가 섞이면 글자 중간을 잘라서 오류가 발생하기 때문에 취한 조치이고 웹을 찾아보시면 흔히 UTF-8sub라고 부르는, 널리 쓰이는 Lua 코드가 있습니다. 이것을 받아 사용해도 됩니다.
끗.
연재글이 더 나오게 되면 아마 Tukui쪽 유닛프레임을 수정하기 위해 동작 구조 설명하는 실전 예제가 나올 것 같습니다. Elvui는 제가 안쓰는데다 코드 수정하지 않아도 어느 정도는 문자열 추가하고 하는 식의 커스터마이징이 잘 되는 편이라 패스합니다.
상태바에 사용할 텍스쳐나 backdrop edge용 이미지 파일 수정하는 것은 굳이 직접 수정하지 않아도 웹에서 흔히 구할 수 있으니 이것도 설명할 필요가 없을 것 같습니다.