안녕하세요.
라그나로스 서버에서 게임 중인 흑마법사 유저 Apoptosis 입니다.
저 역시 흑마법사 게시판에서 좋은 정보를 많이 얻어가고 있습니다.
그래서 이번 기회에 제가 알고있는 정보를 공유해보면 어떨까 해서 흑게 여러분들의 즐거운 와우라이프를 위해
도움이 되는 몇가지 정보를 알려드리려고 미약한 지식에도 불구하고 흑게에 처음 글을 남겨봅니다.
와우는 사용자 인터페이스(UI)를 XML로 정의한 개체와 이벤트, 그에 따라 동작하는 스크립트 언어인 'Lua'로
구성해놨고, 이를 사용자의 편의를 위해 사용자가 얼마든지 수정할 수 있도록 공개해놨습니다.
이로 인해 다양한 애드온들이 나오게 되고 자신만의 UI 환경을 꾸밀 수 있는 것이 와우의 강력한 재미 요소 중
하나라고 할 수 있습니다.
여기까지는 소개글이고 본론에서 다룰 내용은 'Lua'라는 것입니다.
'Lua', 와우하면서 애드온을 이용해보셨다면 당연히 쉽게 접할 수 있는 단어 입니다.
보통 와우저에게 Lua는 애드온 파일의 확장자(xxx.lua) 또는 애드온을 구성해주는 나는 몰라도되는 신비한 거
정도가 되겠습니다. (저도 보통 와우저이므로 이렇게 생각했습니다...)
하지만 Lua는 프로그램에 'P'자도 모르는 평범한 와우저들도 몇가지 패턴만 알게 된다면 쉽게 응용하여 사용이
가능합니다. 이를 이용하여 자신이 원하는 매크로를 만들어서 게임을 더 재밌게 즐길 수 있게 되는거죠.
매크로를 하나 보겠습니다.
/run SendChatMessage("hi")
채팅창에 해당 명령어를 입력해 보면 어떨까요? 일반대화로 'hi'가 출력됩니다.
<안녕하세요?>
여기서 SendChatMessage()가 " " 안에 원하는 단어를 입력하면 일반대화로 출력되게 하는 기능이구나, 라는 것을
알 수 있습니다. 쉽게 말해서 SendChatMessage()는 Lua 내에서 우리가 사용하는 매크로와 같은 역할을 맡고 있다고
생각하시면 됩니다.
응용해서 SendChatMessage("hi","yell") 로 쓰면 외치기로, SendChatMessage("hi","party")로 쓰면 파티말로,
"party" 대신에 "raid"를 넣으면 공격대말로, "battleground"를 넣으면 전장말로 입력한 내용을 출력합니다.
<"yell" 적용 상태. 옷이 약간 바뀐것 같지만 그건 님의 착각>
매크로를 하나 더 보겠습니다.
/run local t="target" SendChatMessage("Drain Soul "..UnitName(t)..", Health Status "..(UnitHealth(t)/UnitHealthMax(t)*100).."%")
/cast 영혼 흡수
이 매크로는 자신이 영혼 흡수를 시전한 대상의 체력 수치를 퍼센트(%) 단위로 일반 대화창에 출력합니다.
흑게 여러분들은 한번쯤 이런 경우를 겪어보신 적이 있으실 겁니다.
고흑으로 PvP나 레이드 때 상대방(또는 네임드)의 피가 마격 타임인 25%에 접어들자마자 칼같이 영흡을 꼽았는데
이게 정말 제대로 꼽혔는지...? 너무 일찍 꼽아서 25.01%에 꼽힌거 아냐...? 다시 확실하게 꼽아야 하나...?;
이런 경우에 위의 매크로를 이용하면 자신이 영흡을 시전할 당시에 대상의 피 수치를 %로 채팅창에 출력해 주기
때문에 제대로 꼽았는지 재차 확인이 가능합니다.
<허수아비에게 매크로 사용중>
하지만! 이 매크로는 헛점 투성이의 매크로입니다. 매크로를 완벽하게 다듬어주기 전에 구문을 간단하게 분석해봅시다.
local t = "target"
t 라는 임의의 변수를 선언해서 "target"이라는 값을 대입 해준 부분입니다. 앞으로 t 라고 쓰면 "target"을 의미하는
거다, 내가 그리 정했노라... 라는 겁니다. 그 뒤에 구문을 보시면 선언해준 변수 t 가 계속 사용되고 있습니다.
아아, 앞에서 t = "target" 이라고 정의해서 전체적으로 구문 길이를 간결하게 하고 있구나, 를 알 수 있습니다.
변수로 정한 단어가 구문 전체에 많이 나올수록 더욱 구문 길이를 단축시키는 효과가 있습니다.
이는 편의성도 있지만 와우 매크로 버튼 하나당 255자 제한이기 때문에 255자 이내로 작성할 수 없는 매크로도
변수를 이용해 축약 하므로써 원버튼 내로 쑤셔넣을 수 있다는 장점이 있습니다.
<변수 선언이 없었다면 255자 이내로 완성할 수 없었을 것이다.>
변수 t 앞에 'local'은 변수의 적용 범위를 이 실행문 내에서만 사용가능하게 지정해주는 역할입니다.
지역 변수라고 하는데, 매크로 여러개를 순서대로 눌러야 적용되는 매크로를 본적이 있을 겁니다. 이런 매크로에는
변수를 선언해 줄 때 변수 앞에 local 을 붙이지 않아서 전체 매크로가 변수를 공유할 수 있도록 합니다.
<고대인님의 상대방 급장 체크 매크로, 총 5개의 버튼을 다 눌러줘야 제대로 매크로가 작동하며 앞매크로에서
설정한 변수를 뒷매크로도 가져가 사용하고 있다.>
UnitName(t)
UnitName("target")은 " "안에 넣은 대상의 이름을 출력하는 기능을 합니다. "%t" 도 같은 기능을 하는데,
~("Drain Soul "...UnitName(t).."~ 대신에 ~("Drain Soul %t"~ 이렇게 써도 같은 결과가 출력됩니다.
(UnitHealth(t)/UnitHealthMax(t)*100)
UnitHealth(t) 는 대상의 현재 체력을 출력해주는 기능
UnitHealthMax(t) 는 대상의 체력의 최대치를 출력해 주는 기능입니다.
현재 체력 / 최대 체력 * 100 이니까 결국에는 대상의 현재 체력을 Percent(%)로 출력하는 기능을 하게됩니다.
간단하게 구문을 살펴봤습니다. 생각보다 간단하죠?
자, 이제 이 매크로가 헛점 투성이라고 아까 말했는데 그부분에 대해서 살펴보겠습니다.
이 매크로를 마구 눌러봅시다. 결과는 어떤가요?
네, 그렇습니다. 매크로를 누른만큼 마구 출력되어 채팅창을 도배하게 됩니다.
<마구 누르시면, 마구 출력됩니다.>
심지어 아군, 적군, 시전이 가능한 대상인지 따지지도 않고 범위에 상관없이 대상만 클릭하고 있으면 작동합니다.
<강제적으로 실험에 참가한 가로쉬, 아군인데도 작동한다. 사실 적군인가?>
<스킬 시전범위와 전혀상관없이 대상만 잡혀있으면 무조건 작동한다.>
딱 봐도 매크로의 문제가 몇가지 보입니다. 하나씩 조건을 추가해서 다듬어 보겠습니다.
1. 영혼 흡수 스킬이 '전역 재사용 대기시간(일명 글쿨)' 일 때 매크로도 같이 작동되지 않게 하기
영혼 흡수는 따로 쿨이 없기 때문에 글쿨만 아니면 사용이 가능합니다. 글쿨 때 영흡이 시전 안되는 것과 같이
동시에 매크로도 작동하지 않게 조건을 주는 것입니다.
/run local t="target" if GetSpellCooldown("영혼 흡수")==0 then SendChatMessage("Drain Soul "..UnitName(t)..", Health Status "..(UnitHealth(t)/UnitHealthMax(t)*100).."%!");end
/cast 영혼 흡수
GetSpellCooldown() : 입력한 기술의 현재 쿨을 가져오는 기능입니다.
if 문을 써서 ~ 조건이면 then ~ 을 한다. end 가 됩니다. 구문을 이해하기 쉽도록 나눠보겠습니다.
local t = "target" -- 변수 t를 선언하여 "target"을 대입
if GetSpellCooldown("영혼 흡수")==0 then -- 영혼 흡수의 쿨이 0초 이면,
SendChatMessage("Drain ~ %"); -- 대화창에 "Drain ~ %" 출력한다.
end
당연히 if 조건에 맞지 않으면 (기술이 쿨다운 상태일때) then 이하는 실행되지 않습니다. 문제점 한가지가 쉽게
해결이 되었습니다. 참 쉽죠? 그럼 계속 다듬어 보겠습니다.
2. 영혼 흡수 스킬의 시전 범위내에서만 매크로 작동되게 하기
앞서 말했듯이 영흡은 따로 쿨이 없기 때문에 글쿨이 안돌면 매크로는 실행됩니다. 그럼 타겟과의 거리를 40미터
밖으로 벗어나서 영흡이 시전되지 않아 글쿨이 돌지 않는 경우 매크로는 누른만큼 착실하게 실행되서 채팅창을 또
도배하게 됩니다.
그러므로 해결법은 매크로를 영혼 흡수 시전범위 내에서만 작동하도록 조건을 주는 것입니다.
/run local t="target" ds="영혼 흡수" if GetSpellCooldown(ds)==0 and IsSpellInRange(ds,t)==1 then SendChatMessage("Drain Soul "..UnitName(t)..", Health Status "..(UnitHealth(t)/UnitHealthMax(t)*100).."%!");end
/cast 영혼 흡수
"영혼 흡수" 라는 말이 두번 반복되기 때문에 깔끔하게 변수 ds를 선언해서 "영혼 흡수"를 대입해주고 if문 뒤에 and
를 쓰고 IsSpellInRange(ds,t) 조건을 추가 해줬습니다.
IsSpellInRange(ds,t) : t(target)가 ds(영혼 흡수)의 시전 범위에 포함되어 있는지 판단하는 기능을 합니다.
'1'을 주면 시전 범위안에 있을 때 조건을 만족하게 되고 '0'을 주면 시전 범위 밖에 있을 때 조건에 만족하게 됩니다.
지금 까지 다듬은 매크로의 기능을 풀어 얘기하자면,
영혼 흡수의 글쿨이 돌지 않고 시전 범위(40미터)내에 대상이 존재하면 "Drain Soul (대상), Health Status XX %!"
라고 대화창에 출력하며 영혼 흡수 시전
여기서 영혼 흡수는 오직 적대적 대상에게만 시전되는 스킬라서 상관 없지만 사제의 '회개' 기술처럼 아군/적군
모두에게 시전되는 스킬인데 아군/적군 둘중 특정집단에게만 실행되는 매크로를 만들고 싶다면
지금 했던 방법처럼 조건절에 UnitIsEnemy() (적인지 판단) 이나 UnitIsFriend() (아군인지 판단) 조건을 따로 추가
해주시면 되겠습니다.
자, 여기까지 했지만 아직 매크로가 다 완성된 것은 아닙니다. 마무리를 해줘야 합니다. 뭐냐구요? 이 매크로를
허수아비 말고 실전에서 써봅시다.
<야호! 뜨거운 바위 정령의 현재 체력은 69.19647184259% 네!>
네, 뭔가 굉장합니다. UnitHealth(t)/UnitHealthMax(t)*100 계산이 딱 떨어지지않아 소수점 밑으로 굉장하게 나갑니다.
이런 경우에 직접 출력될 형태를 format 해줘야 합니다.
format( "%.2f", (UnitHealth(t)/UnitHealthMax(t)*100) )
체력계산 부분을 format()으로 감싸주고 출력형태를 %.2f 로 지정해줬습니다. %.2f 는 소수점 두번째 자리까지 출력
한다는 뜻입니다. %.0f 로 해주면 소수점을 없이 정수부분만 출력하게 됩니다.
/run local t="target" ds="영혼 흡수" if GetSpellCooldown(ds)==0 and IsSpellInRange(ds,t)==1 then SendChatMessage("Drain Soul "..UnitName(t)..", Health Status "..format("%.2f", (UnitHealth(t)/UnitHealthMax(t)*100)).."%!");end
/cast 영혼 흡수
<제대로 소수점 두번째 자리까지 깔끔하게 표시된다!>
매크로 하나를 완성해봤습니다. 어떠신가요? 참 쉽죠? ...ㅋ?
Lua를 이용한 매크로 하나 분석해보았더니 Lua, 그거 별거 아니라는 결론이 나왔습니다. (...는 구라)
자, 그럼 본격적으로 제목에서 언급했듯이 Lua를 이용한 매크로로 기본 UI를 강화시켜보겠습니다.
저는 기본 UI를 선호하는 사람 중 한명으로 애드온을 하나도 사용하고 있지 않습니다.
애드온을 사용하지 않고 기본 UI를 사용하면 오는 장점으로는 어디서든 애드온에 구애받지 않고 접속해서 와우를
즐길 수 있다는 점과 대규모 패치로 인한 애드온 대란은 남얘기라는 겁니다. 훗.
주위 아는 지인분들 중에는 애드온에 너무 의존하게되서 사용하고 있는 애드온이 작동하지 않거나 없으면 와우를
즐기는 것 자체에 크게 영향을 받는 분들이 종종 계시곤 합니다.
물론 애드온을 사용하는 것의 옳고 그름을 논하려는 것은 아닙니다. 서두에서 말씀드렸듯이 애드온은 와우의 재미를
한층 더 높여주는 자랑거리 중 하나입니다.
말씀드리고 싶은 것은 불편해도 남이 해줄 때 까지 기다리는 의존적인 앤드 유저가 되기 보다는, 가려운 곳이 있으면
자신이 바로 긁었을 때 오는 시원함을 느낄 수 있는 능동적인 와우저가 되었으면 좋겠습니다. 그것이 와우를 한층 더
진정으로 재밌게 하는 요소 중 하나라고 저는 굳게 믿습니다. 물론 정말 가려운데 긁지 못하는 등짝 가운데는
전문의의 도움을 받는게 현명합니다.
제가 기본 UI를 사용한다고 했는데 기본 UI는 그대로 쓰기엔 불편한게 한두가지가 아닙니다. 그대로 꾹 참고 쓰기엔
제 몸이 너무 근질 거립니다. 마치 3년동안 샤워안한 것처럼요. 그래서 너무 가려워서 한번 긁어보기로 했습니다.
<우선 제일 마음에 안드는 부분은 이겁니다. 체력만 따로 % 로 표시해주는 설정은 자네가 먹었능가?>
영혼 흡수가 마격 스킬이긴 하지만 25%가 된다고 자동으로 스킬에 불이 들어오거나 하진 않습니다. 유저가 신경을
써줘서 알아서 대상의 체력이 25% 이하로 내려가면 영흡을 꽂아줘야 합니다. 그렇다면 대상의 체력만 따로 %로 하는
설정이 필요합니다. 하지만 그런 설정은 없고 모든 화면의 나타나는 수치를 전부 싸잡아서 모두 백분율로 표시하는
옵션만이 존재합니다.
<체력만 백분율로 표시하고 싶은 남자의 심정을 매도하지마.>
그래서 가려운 곳을 긁으니까 바로 해결법이 나왔습니다.
/run TF=CreateFrame("Frame")TFHB=TargetFrameHealthBar.TextString;
/run TF:SetScript("OnUpdate", function(target)TFHB:SetText(format("%.0f", ((UnitHealth("target")/UnitHealthMax("target"))*100)).."%")end)
<매크로를 적용한 상태>
네, 어때요? 참 쉽죠?
원래 프로그램의 세계란 1+1을 알려주고나서 다음 단계는 바로 미분/적분입니다.
그런것 치고는 위에서 함께 분석한 간단한 매크로에서 쓰였던 부분들이 많이 보입니다. 매크로를 이해하는게 어려움은
없어보이네요. 요 부분은 무슨 기능이지? 라고 궁금한 부분은 와우위키(www.wowwiki.com/API)를 참고하시면 되겠습니다.
그럼 더 나아가 체력수치는 그대로 보여주고 그옆에 백분율을 추가로 표시해주는건 어떻게 할까요?
1번 매크로
/run TF=CreateFrame("Frame")TFHB=TargetFrameHealthBar.TextString;TTSB=TextStatusBar_CapDisplayOfNumericValue
2번 매크로
/run TF:SetScript("OnUpdate", function(target)TFHB:SetText(TTSB(UnitHealth("target")).."/"..TTSB(UnitHealthMax("target")).." ("..format("%.0f", ((UnitHealth("target")/UnitHealthMax("target"))*100)).."%)") end)
네, 고민하려는 찰나에 매크로가 어디서 뚝딱 하고 튀어나와버렸습니다. 매크로가 길어져서 1번 매크로, 2번 매크로
로 나누어 졌습니다. 차례대로 눌러주시면 제대로 동작합니다.
<현재 체력 / 최대 체력 (백분율 %)>
처음 매크로와 차이점은 TextStatusBar_CapDisplayOfNumericValue 를 이용해서 출력부분에
TTSB(UnitHealth("target")).."/"..TTSB(UnitHealthMax("target")).." 현재 체력과 최대 체력을 처리해준 부분이 추가
되었습니다. 여기서 최대 체력을 없애고 싶으신 분들은 TTSB(UnitHealthMax(... 부분을 지워 주시면 되겠죠?
추가 요청 사항
"체력표시 13455/13455(100%) 이렇게 되는거 쓰고 싶긴한데 너무 길어져서 체력바 전체를 가려버리는 느낌
이 있어서요. 어느 영상에선가 13000을 1.3K 이런식으로 표현하는 것을 본적이 있는데 이건 불가능할까요?"
13000을 1.3K로 바꾸는 것처럼 자신이 원하는 형태로 출력하고 싶은 경우에는 format()을 이용합니다.
format() : 값을 임의의 정해진 형태대로 출력하는 기능을 합니다.
format()의 자세한 기능을 알고 싶으신 분들은 와우위키 : API format (http://www.wowwiki.com/API_format)을
참고하시고 본문에서는 format()을 간단하게 알아보겠습니다.
기본적으로 쓰이는 형태는
입니다.
13000을 13K로 바꾸고 싶다면 format("%.0fK", 13000/1000) 이런식으로 쓰면 되겠습니다.
(여기서 잠깐, 질문에는 13000을 1.3k로 표현해놓으셨는데 이는 잘못된 표현입니다. k는 1000(kilo)를 의미하는
단위로 13k가 정확한 표현입니다. 13000을 1.3k로 표현되는경우가 종종있는데 이는 숫자를 셀때 만 단위로 끊어
쓰는 한국사람들만의 잘못된 습관입니다. 13000을 1.3으로 표현하고 싶다면 '1.3만'이라고 한국어로 표현하는게
맞습니다.)
표현의 종류에는 여러가지가 있지만 와우에서 주로사용되는 것만 살펴보면 다음과 같습니다.
%d : 'Decimal' 10진 정수로 출력한다.
%s : 'String' 문자열을 출력한다.
%f : 'float' 실수형으로 출력한다. %.nf (소수점 n번째 자리까지 출력한다는 의미)
%% : % 문자 출력 방법
또한 여러 값을 차례차례 받아서 순서대로 원하는 표현으로 출력가능합니다.
format("표현1, 표현2", "값1", "값2")
자, 말로는 설명해놨지만 구체적으로 어떤식으로 이용하는지 알고싶습니다.
와우 내에서 실제로 format이 어떻게 이루어지는지 확인하기 위해서 /run SendChatMessage()를 이용해서 채팅창
에 출력해 보겠습니다.
/run SendChatMessage(format("%.0f", 12.345))
<%.0f로 표현식을 정해준 매크로 실행화면>
표현식을 %.0f 로 했기때문에 값을 받아 소수점 0번째 자리까지 출력해서 12가 출력되었습니다. 그럼 표현식을
%d로 바꿔보면 어떨까요?
/run SendChatMessage(format("%d", 12.345))
<%d로 표현식을 정해준 매크로 실행화면>
%d는 10진 정수로 출력하므로 같은 결과값 12가 출력됩니다. 와우 매크로는 축약할수록 좋다고 했는데
그럼 %.0f 보다 약간더 길이가 짧은 %d를 표현식으로 사용하면 전체적인 문장길이를 줄일 수 있지 않나요? 라고
생각하실껍니다.
%.0f과 %d의 차이점을 살펴보겠습니다.
/run SendChatMessage(format("%.0f, %d", 1.99999, 1.99999))
<%f.0f와 %d의 각각 값의 표현 결과>
값 1.99999 는 거의 2와 같은 숫자크기인데 %.0f는 결과값 2로 반올림으로 받아줬고, %d는 결과값이 1로
소수점 이하값을 절사합니다. 와우 캐릭터의 체력수치와 같은 민감한 값은 %f로 받아주는게 더 좋은방법이겠죠?
계속해서 매크로를 써보겠습니다.
/run SendChatMessage(format("%.0f", UnitHealth("player")/1000))
<매크로가 실행된 채팅창>
이 매크로는 플레이어 자신의 현재 체력수치를 1000으로 나눈 값을 %.0f 형태로 받아서 출력하는 매크로입니다.
표현식 뒤에 k를 붙여서 1000 단위 표현을 붙여봅시다.
/run SendChatMessage(format("%.0fk", UnitHealth("player")/1000))
어느정도 점점 원하는 형태로 접근이 되고있습니다. 이번엔 자신의 최대 체력수치까지 표시해서
"현재 체력 / 최대 체력" 형태로 표현해봅시다.
/run SendChatMessage(format("%.0fk/%.0fk", UnitHealth("player")/1000, UnitHealthMax("player")/1000))
<"현재 체력 / 최대 체력" 형태로 출력된 화면>
결과적으로 질문했던 부분에 완전히 접근했습니다. 이제 "현재 체력 / 최대 체력 (%표시)" 매크로에서
현재 체력 / 최대 체력 출력을 담당하는 부분을 fomat()으로 감싸주면 끝입니다.
기존 1번 매크로
/run TF=CreateFrame("Frame")TFHB=TargetFrameHealthBar.TextString;TTSB=TextStatusBar_CapDisplayOfNumericValue
기존 2번 매크로
/run TF:SetScript("OnUpdate", function(target)TFHB:SetText(TTSB(UnitHealth("target")).."/"..TTSB(UnitHealthMax("target")).." ("..format("%.0f", ((UnitHealth("target")/UnitHealthMax("target"))*100)).."%)") end)
2번 매크로에서 굵은 글씨로 표시한 곳이 "현재 체력 / 최대 체력" 출력을 담당하는 부분입니다.
이 부분을 format()으로 감싸주시면 되겠습니다.
/run t="target";TF=CreateFrame("Frame");TFHB=TargetFrameHealthBar.TextString;TTSB=TextStatusBar_CapDisplayOfNumericValue
변경된 2번 매크로
/run TF:SetScript("OnUpdate",function(target)TFHB:SetText(TTSB(format("%.0fk",UnitHealth(t)/1000)).."/"..TTSB(format("%.0fk",UnitHealthMax(t)/1000)).." ("..format("%.0f",(UnitHealth(t)/UnitHealthMax(t))*100).."%)")end)
<변경된 매크로를 실행한 화면>
2번 매크로에서 굵게 처리된 부분이 format()을 이용해서 변경한 부분입니다. 매크로의 길이가 좀더 길어
졌으므로 2번 매크로에서 반복되는 "target"을 1번 매크로에서 변수 t로 선언해서 전체적인 매크로 길이를 축약
시켰습니다.
제대로 잘 되시죠? 차근차근 알아보니 금방 원하는 매크로가 나왔습니다.
주의! 소스에 Lua 오류가 있음을 제보받고 "변경된 2번 매크로"의 소스를 임시 변경해 놓았습니다. 제대로 정상적으로 오류없이 동작을 하므로 사용하시는 데에는 지장이 없지만 정확한 소스를 위해 수정 중이오니 사용하는 분들은 글상자의 임시 소스를 일단 사용해 주세요.