와우 인벤 개발자 애드온 포럼

인증글

모바일 상단 메뉴

본문 페이지

[애드온] WeakAuras 소개 - #6-1 Lua와 WoW API 입문

올커니하면서
댓글: 22 개
조회: 7108
추천: 52
2015-04-05 22:41:29
며칠 전에 어떤 분이 쪽지로 "내 캐릭터의 생명력을 % 형식 텍스트로 나타내고 싶다"는 질문을 하셨습니다.
WeakAuras는 이 기능을 프리셋으로 제공하지 않습니다.
그런데 프리셋에 없다 해서 이게 상당히 고급 사용자 전용인가 하면 그건 절대로 아니지요. 피통 %로 보는게 고급 기능이라는게 말이 되나요.

제가 WA 소개글을 처음 쓸 때는 프리셋 기능을 간략하게 소개하면 누구나 프리셋 기능은 사용하며 익힐 수 있고 Lua 코드를 활용한 커스터마이징은 프리셋을 넘어서는, 예외적 상황에 한해 사용하면 되는 것이니 Lua를 아는 사람만 써도 무관하며 WA에 적용하는 방식만 소개하자는 생각을 가지고 있었습니다.
그래서 프리셋 기능 소개는 첫 화에서 거의 다 끝나고 #3에서 프리셋 기능을 변칙적으로 활용하는 예제가 조금 있었을 뿐, 이후로 계속 커스텀 기능 위주로 연재글이 작성되는 동안 '아는 분에 한해 보시면 되는 글'이라는 전제로 이어왔습니다.


그러다가 위 질문을 계기로 WeakAuras는 결국 Lua와 WoW API(와우 인터페이스를 구현하는데 필요한 정보를 얻거나 조작을 가하는 기능들을 블리자드에서 미리 구현해 유저가 사용할 수 있게 만든 명령어 모음입니다)를 조금은 쓸 줄 알아야 좋은 애드온이란 판단을 뒤늦게 내린 바, WA를 활용하기 위한 Lua와 WoW API 기본 지식에 대한 강좌를 시작합니다.
이 연재에는 WoW API는 WeakAuras에 활용하기 위한 수준에 한정해서 다루며 Lua는 어쩔 수 없이 얕은 깊이나마 상당한 범위를 다루게 될 듯합니다.

WoW 애드온 전반에 관하여는
애드온 개발자 포럼에 '달이뜨는밤'님의 연재글을 참고하시기 바랍니다.


WA로 원하는 기능을 구현하는 예제를 보여드리면서 Lua 문법과 WoW API에 대해 필요한 부분만 최소로 설명드린 다음, 거기에 사용한 Lua 관련 지식을 글 맨 뒤에 덧붙이는 형태로 진행할 것입니다.
결과적으로 WA, Lua, WoW API에 대한 설명이 뒤섞인 꽤 난잡한 구조로 쓰게 될 것인데다가 제가 프로그래밍 언어는 최소 못배운 사람이라 용어면에서 국내에서 사용하는 교재들에서 통용되는 번역과 다른 부분이 있을 것입니다. 중요한 오류가 있다면 정정해주시기 바랍니다.



다음 링크의 사이트들을 계속 참조할 것입니다.

World of Warcaft Programming : 와우의 인터페이스에 관련된 기능들을 정리해둔 사이트입니다.
애드온 프로그래밍 관련된 동명의 책이 있는데 거기에 다 싣기 곤란한 자료들을 참고하라고 만든 독자 서비스 페이지 성격이 있습니다.

Programming in Lua - Online edition : Lua 텍스트북의 온라인 버전입니다.
글 맨 뒤에 실을 Lua 강좌는 이 사이트의 목차 구성을 따르며 예제도 상당부분 차용했습니다.






예제 6-1-1 : 생명력 % 표시하기

자기 캐릭터의 현재 생명력을 % 단위로 표시하는 문자 표시기를 만듭니다.

웃기게도 WA는 이 기능을 프리셋으로 지원하지 않기 때문에
- 늘 표시되는 텍스트 표시기를 생성
- 텍스트에 표시할 문자를 생성하는 Lua코드를 작성
하겠습니다.

/wa 로 창을 열고
왼쪽 목록에서 새로운 - 오른쪽에서 텍스트 를 선택합니다.

조건 탭으로 이동합니다.
WA 조건(우으으.. 트리거trigger인데 번역하니 의미가 좀 불명확해졌습니다) 탭에서 하는 일은 본래 숨겨져있던 개체가 특정 조건을 만족하면 나타나고, 조건이 사라지면 다시 숨겨지게 만드는 것입니다.
생명력 % 표시기는 그냥 계속 떠 있으면 되는거죠. 텍스트 내용만 바뀌는건데 이건 디스플레이 탭에서 할거고요.

계속 떠 있게 만드는 방법으로 저는 보통 상태 - Unit Characteristics를 사용합니다.



스샷에 지정된 조건은
'player' 유닛이 존재하면 표시
입니다.

늘 나오겠죠.



디스플레이 탭으로 가서 창 안의 값을 지우고 %c%라고 적습니다. 엔터 치지 마시고 밑의 수락을 누르세요.

디스플레이 텍스트를 지정할 때는 실제 텍스트 외에 %p %t %n %i %s %c 의 특수값을 쓸 수 있습니다.
WA가 본래 오라 지속에 관련한 기능을 하던 애드온이었음을 감안하시고
%p는 현재 남은 시간
%t는 전체 지속 시간
%n은 오라 이름
%i는 오라 아이콘의 경로(와우 내부적으로 사용하는 경로입니다)
%s는 오라의 중첩 수
를 의미합니다.

%c는 커스텀. Lua 코드를 써서 어떤 글을 출력할지 직접 만드는 겁니다.
뒤에 %는? 물론 커스텀 코드로는 숫자만 쓰고 뒤에 %라고 덧붙이는 거지요.
오라 추적할 때 텍스트로는 남은 시간 / 전체 시간 으로 하고 싶다면 %p / %t라고 쓰면 되고 뭐 이런 식입니다.


Update Custom Text On...
글 내용을 언제 갱신할지 선택합니다.
조건 업데이트 : 조건 탭에서 지정한 조건이 만족될 때마다 바꿉니다.
매 프레임 : 말 그대로, 매 프레임마다 만들어 넣을 Lua 코드를 실행해서 값을 갱신합니다. fps가 60이면 매 1/60초마다 수행됩니다.

예를 들어서 나한테 희손 버프가 생기면 그 버프를 시전한 사람 아이디를 텍스트로 띄우고 싶다고 한다면 그것은 희손 버프가 들어오는 순간에 결정해서 한 번 지정하면 없어질 때까지 바뀔 일이 없지요. 이 조건으로 충분합니다.
그런데 이 표시기는 '플레이어 캐릭이 있으면' 표시됩니다. 없어졌다 다시 생길 일이 없지요. 그래서 조건 업데이트는 생명력이 바뀌는 상황에 갱신되지 않습니다. 별 수 없이 매 프레임.


코드를 입력하기 위해 텍스트 편집창 확장을 누릅니다.(그냥 옆의 작은 창에 입력해도 되는데 불편하지요)
다음 내용을 적습니다. 줄은 지가 알아서 맞춰주니 신경쓰실 것 없고 대소문자에 주의하세요.


function()
local min = UnitHealth("player")
local max = UnitHealthMax("player")
return floor(min / max * 100)
end

다 입력하고 완료 누른 다음에는 글자 크기가 좀 작으니 적당히 키워주면 일단 생성은 끝입니다.

자, WA에서 커스텀 텍스트 코드는 '실행하면 어떤 값을 돌려주는 함수'로 만들게 됩니다.
위에서 조건 업데이트 / 매 프레임 선택한 것에 따라 텍스트를 갱신해야할 때 우리가 적어넣은 함수를 실행하고 그 결과값으로 텍스트를 지정하는 것입니다.

Lua에서 함수는 '어떤 기능을 수행하도록 대기중인 명령어 덩어리' 정도로 이해하시면 간단하고요.


앞뒤로 function() end 는 함수를 구성하는 형식입니다.
그 사이에 뭔가 명령, 연산 등을 수행한 다음 return 이라고 된 부분의 값을 내어주게 됩니다.

뒤에서부터 봅시다.
return 뒷부분이 반환값이니 우리가 짠 코드에서는 수행하면
floor(min / max * 100)
을 되돌려주는 것이지요.

floor, 혹은 math.floor는 괄호 안에 있는 숫자를 내림합니다. Lua에서 기본으로 제공하는 '함수' 입니다.
floor(3.14) 하면 3을, floor(0.72) 하면 0을 반환합니다.
올림은 ceil을 사용하면 되고 반올림 기능은 기본 기능으로 없습니다.

min / max * 100
최소값 / 최대값 * 100 ?
최소값이라고 되어있지만 와우 애드온 짜는 사람들은 흔히 현재값이란 의미로 min을 씁니다. 현재값 / 전체값 * 100 이니까 백분율 단위로 된 숫자죠.
이걸 그냥 쓰면 결과가 48.2490873298 같이 나올 가능성이 농후하므로 내림한 것입니다.


그러면 min하고 max는 각각 현재 생명력과 전체 생명력을 의미할 것인데 윗줄에서 지정했습니다.

local min = UnitHealth("player")
local max = UnitHealthMax("player")

local 이라는 단어는 min이나 max라는 값들을 요 함수 안에서만 쓸거라는 뜻인데 일단 무시하시고


min = UnitHealth("player")

min이라는 새 변수를 만들고 거기에 UnitHealth("player")라는 값을 지정합니다.
수학문제 풀 때
밑변 : a, 높이 : h
a = 4, h = 6
삼각형 넓이 S = 1/2 * ah = 1/2 * 4 * 6 = 6 
하는 것과 같습니다.

Lua에서 등호는 '값을 지정'하는 기호로 쓰입니다.

마찬가지로 max에는 UnitHealthMax("player")라는 값이 지정되었는데 이제 얘들을 살펴봅시다.

UnitHealth와 UnitHealthMax는 와우 API 함수입니다.
WoW Programming 사이트에는 모든 API 함수의 사용법이 정리된 페이지가 있습니다.

에 들어가서 찾아보시면 각각

가 있습니다.

위쪽 것만 보지요.


health = UnitHealth("unit") or UnitHealth("name")

두 가지 방법으로 쓸 수 있고 현재 생명력을 얻게 해줍니다.
뒤쪽 용법은 이름을 넣는 것인데 파티나 공대원 이름만 가능하며 앞쪽 용법으로 쓸 때 unit은 unitID라고 하는 형식입니다.
player target focus pet 처럼 매크로에서 [@focus] 할 때 쓰는 문자열과 같은데 'player'라든가 "player"처럼 따옴표로 둘러싸야 합니다.

뭐, 아무튼 그래서
min = UnitHealth("player")는 내 캐릭터의 현재 생명력을 min에 지정하는 명령이 됩니다.

저기에는 health라고 써 있는데 min으로 써도 물론 됩니다. health로 했으면 나중에 계산할 때 health / max로 했을 것이고 심지어 min max를 바꾸어서 현재값에 max, 전체값에 min을 써도 나중에 계산할 때 max / min으로 위치만 교차되지 않게 하면 됩니다. 변수의 이름과 내용에는 아무런 상관이 없습니다.
그러니까, 타자치기 편하고 내용이 무엇인지 잘 연상되는 적당한 이름을 지으면 됩니다.


아무튼, 전체 코드를 보면
min : 현재 생명력
max : 최대 생명력
(min / max)에 100 곱하고 소수점 버림해서 반환
여기까지고 나중에 뒤에 %를 붙여서 텍스트로 설정되는 것이지요.






예제 6-1-2 : (보호막 수치, 작열값 등) 버프 디버프의 수치 확인하기

양조 탱커가 방어 자세를 쓰면 아이콘이 표시되는데, 남은 흡수량을 아이콘 위에 숫자로 표시하는 기능을 만듭시다.
(수방이나 피보 흡수량이라거나 디버프 쪽으로 가면 작열의 틱당 데미지도 비슷합니다.)
놀랍게도 WA는 이것도 프리셋으로 지원 안합니다. 어휴 노답.

새로운 - 아이콘

조건으로 갑니다.
버프가 떴을 때만 표시하면 되겠지요?



프리셋 기능을 활용합시다.

Own Only는 버프 중에서 내가 시전한 버프만 표시하는 것인데 방어자세 버프는 당연히 내가 시전한 것입니다. 체크할 필요는 별로 없고요, 만약에 대상에 걸리는 디버프라면 내 것만 표시할 상당한 이유가 있습니다. 이 때는 유닛을 대상으로, 종류를 디버프로 하고 Own Only도 체크하셔야지요.


디스플레이 탭으로 가셔서 아까와 비슷하게 합니다.



텍스트는 %c로 해서 커스텀 코드를 입력하게 하고 이번에는 '조건 업데이트'로 충분합니다.
왜냐하면 오라를 추적하는 프리셋 기능은 오라 값이 바뀔 때도 텍스트를 갱신하게 해주기 때문입니다.
어떤 조건이 적절한지는 다양하게 시도해가며 노하우를 쌓으시기 바랍니다.



텍스트 편집창을 열기 전에 UnitAura 계열 함수들의 행태를 좀 보겠습니다.
지정한 유닛의 버프나 디버프의 정보를 가져오는 API는
UnitAura
UnitBuff
UnitDebuff
등이 있습니다. 위의 것은 사용하려면 HELPFUL이라든가 HARMFUL이라는 조건을 추가로 걸어서 각각 버프, 디버프를 나누어 검색하게 되어있고 아래 두 가지는 버프와 디버프 한 가지씩만 찾습니다.

그래서 UnitBuff를 쓸겁니다. 작열이라면 물론 UnitDebuff고요.


name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3 = UnitBuff("unit", index [, "filter"]) or UnitBuff("unit", "name" [, "rank" [, "filter"]])

등호 뒤쪽에 먼저 주목하세요. 
두 가지 방법으로 쓸 수 있습니다. 앞쪽은 '걸려있는 버프 중에 몇 번째 것인지'로 추적하며 뒤쪽은 '걸려있는 버프의 이름이 무엇인지'로 추적합니다.
의외로 주문id로 추적하는 기능이 없습니다. 그래서 주문 id로 추적하려고 하면 Full Scan 기능을 켤거냐고 물어보는데 이건 앞쪽 방식대로 1번 버프, 2번 버프, 3번 버프를 차례로 검색하면서 주문 id가 맞는 버프가 있으면 그걸 찾아야 합니다.(왼쪽에 11번째 값으로 spellID가 있죠?)

다행히도 우리는 버프 이름을 아니까 두 번째 방법을 쓸겁니다.

UnitBuff("unit", "name" [, "rank" [, "filter"]])

unit은 아까와 마찬가지로 unitID이며 우리는 "player"라고 쓰면 됩니다. 작열이면 보통 대상 작열값을 보죠. "target". 큰 따옴표 까먹을까봐 친절하게 unit이라고 안쓰고 "unit"이라고 썼습니다.
name에는 버프/디버프 이름을 넣습니다. "방어 자세"
[] 안쪽 값들은 선택적입니다.
rank : 주문 레벨입니다. 지금은 없지만 예전에는 1레벨 피생 뭐 이런게 있었습니다.
filter : 링크 페이지에서 중간쪽을 보면 '취소 가능/불가능', '내가 건 것만', '공대 버프류인지' 등에 해당하는 필터값들이 있습니다.

방어 자세라면 뒤쪽 두 개는 필요 없으니
UnitBuff("player", "방어 자세")라고 쓰면 됩니다.

그런데 대상에 내가 건 디버프라면 "PLAYER"라는 필터를 넣어야 합니다. 그러면
UnitDebuff("target", "작열", nil, "PLAYER")라고 쓰셔야 합니다.
중간에 nil은 '아무 것도 아님'이라는 뜻인데 필터는 네 번째 값으로 넣어야하지만 주문레벨로 필터링하지는 않기 때문에 자릿수를 맞추기 위한 것입니다.


이제는 등호 왼쪽의 복잡한 애들을 봅시다.
name, rank, icon, count, dispelType, duration, expires, caster, isStealable, shouldConsolidate, spellID, canApplyAura, isBossDebuff, value1, value2, value3

아까 UnitHealth는 그냥 값 하나만 나왔는데 이건 여러 가지를 한 번에 출력해 줍니다.
대충 주문 이름, 주문 레벨, 아이콘 경로, 중첩 수, 디버프 종류(마법, 질병 등), 지속시간, 종료시각, 시전자, 마훔가능여부, 등등인데 우리가 관심있는 값은 value1, value2, value3입니다.
애석하게도 판다리아에서 바뀐 부분이 여기에 적용이 안되어 있는데 value1 앞에 반환값이 하나 더 있습니다.
isCastByPlayer


아무튼, 이런 복잡한 애들은 직접 살펴보는게 안전합니다.
게임에서 방어자세를 켜고 채팅창에 /dump UnitBuff("player", "방어 자세")라고 쓰면 다음과 같습니다.



보니까 14번째가 아니라 15번째에서 흡수량이 잡혀있는게 보입니다.


이제 텍스트 편집창을 여시고 입력합니다.


function()
local absorb = select( 15, UnitBuff("player", "방어 자세") )
return floor(absorb / 1000) .. "k"
end

이번에는 앞에서부터 볼까요?

absorb라는 변수를 만들고 여기에 흡수량을 넣는데 select( 15, xxxx ) 형식으로 되어 있습니다.

select는 여러 개의 값을 한 번에 반환하는 함수에서 원하는 위치의 값만을 잡아낼 때 쓰입니다.
WoW API에는 이런 함수가 아주 많이 있으니 자주 쓰시게 될 것입니다.

아까 15번째 값이 흡수량이었으니 select( 15, UnitBuff("player", "방어 자세") )는 수많은 반환값 중에 흡수량을 absorb에 저장합니다.

return 뒤쪽 부분은

floor(absorb / 1000)  <- 킬로 단위로 쓰려고 1000으로 나눕니다
..  <- 앞뒤로 있는 두 값을 하나의 문자열로 합치는 연산자입니다.
"k"  <- k라는 문자를 표시하려면 "k"와 같이 따옴표로 둘러싸야 합니다.
"player" 라든가 "방어 자세" 등도 전부 문자열(string)이라는 형식입니다.

그래서 absorb가 245830이라면
나누기 1000 : 245.83
버림 : 245
뒤에 k 붙임 : "245k"
라는 값이 되어 이것을 반환합니다.




============================================================================

정리 :



WoW API

Unit functions:

다양한 함수들이 마련되어 있는데 대충 이름으로 어떤 역할일지 눈치챌 수 있습니다.
원하는 기능이 있다면 적당한 카테고리로 가서 마음에 드는 이름의 함수를 찾아 들어가 어떤 기능인지 확인하세요.
마나, 기력 등은 UnitPower 뭐 이런 식입니다.




Lua

시작하며:
와우 게임 자체는 아마 C를 썼겠지만 3d모델 외에 보이는 인터페이스는 개발자들도 Lua로 만든 것입니다.
결과적으로 이를 위한 API가 Lua기반으로 작성되어 외부 애드온을 쉽게 만들 수 있게 되었습니다.

배우는 과정에서 이것 저것 해보는 것이 도움이 되므로 루아코드를 한 줄씩 입력하며 즉시 결과를 확인할 수 있는 Standalone Interpreter를 다운받아 설치하세요.


앞서 말한대로 이하 내용은
를 참조하였으며 목차 역시 위 페이지와 같습니다.


1. 시작하기

1.2 전역 변수 Global Variables

프로그램 언어에서 변수는 특정한 값을 저장하거나, 다른 개체로 연결해주는 호출 부호의 역할을 합니다.
Lua의 전역 변수global variable는 선언declare할 필요 없이 값을 지정하는 것만으로 생성됩니다. 아직 생성되지 않은 변수에 접근하는 것도 보통 에러를 발생시키지 않고 생성되지 않은 전역변수는 nil이라는 값을 가진 것과 같습니다.
(전역 변수 <-> 로컬 변수local variable인데 차이는 뒤에서 나올 것입니다)

Program Files (x86)Lua/5.1/lua.exe 을 실행합니다



> print(b)
nil

print(x)는 x라는 값을 텍스트로 출력하는 기능을 수행합니다. print는 루아의 기본 함수function 중 하나입니다. 함수는 나중에 다룰 것이지만 우선 자주 쓰게 될 것입니다.

b의 값을 출력받으니 nil이 결과로 나옵니다. nil은 ‘없음’에 해당하는 값value의 한 형태입니다.
아직 b라는 변수가 생성되지 않았거나 생성된 후에 다시 nil로 지정되어도 같은 결과가 됩니다.

> b=10

아무 일도 일어나지 않습니다만 내부적으로는 b라는 변수에 10이라는 숫자number 값을 지정assign했습니다.
프로그램 언어에서 등호=는 대체로 앞의 변수에 뒤의 값을 지정하는 용도로 사용합니다.

> print(b)
10

이제는 b의 값으로 10이 출력됩니다.

> b=20
> print(b)
20

그냥 덮어쓰면 값을 바꿀 수 있습니다.
다시 말씀드리지만 등호는 같음을 의미하지 않고 지정하는 명령을 수행합니다. 우리가 아는 수학이라면 저 상태까지 b는 10이니 10=20이 되어 잘못된 등식이지만 쓰임이 다릅니다.

> b=nil
> print(b)
nil
변수에 nil을 지정하면 맨 처음, b가 생성되기 전과 같은 상태로 돌아갑니다.




1.3 변수명 제한 사례 Some Lexical Conventions

알파벳 문자, 숫자, 언더바_ 등을 조합해서 변수 이름을 만들 수 있습니다.
다만 숫자로 시작하면 안됩니다.
대소문자를 구분합니다.

x X x1 x2 _x
등등은 모두 별도의 변수명으로 사용할 수 있습니다.

MyOwnVariable FIRST_NUMBER_CAP_VALUE
이렇게도 가능하고요.

다만 언더바로 시작해서 뒤에 대문자로 이루어진 변수명은 사용하지 않는 편이 좋고(Lua 자체가 이 변수명을 쓰는 것이 꽤 됩니다. _G 라거나 _INPUT 등) 대문자로만 이루어진 변수명은 WoW가 사용하는 것들이 꽤 있습니다.
와우에서 채팅창에 /dump NONE 라고 입력하시면 와우 인터페이스가 NONE라는 변수명을 “없음”이라는 번역된 문자열을 나타내는데 사용한다는 것을 볼 수 있습니다.
위에 예로 든 FIRST_NUMBER_CAP_VALUE도 와우가 각 언어 클라이언트 별로 큰 숫자를 다르게 축약하기 위해 사용하는 변수명입니다.(서구권에서는 3자리 단위로 k, m. 우리말이나 중국어는 4자리 단위로 만, 억)

다음 단어들은 Lua가 제어용도로 사용하므로 변수명으로 사용할 수 없습니다.
and break do else elseif
end false for function if
in local nil not or
repeat return then true until
while

대소문자 구분하니 and는 못써도 And, AND, anD 등은 모두 별도의 변수명으로 사용할 수 있습니다.


4.1 지정 Assignment

본래 페이지의 목차와는 다른데 잠깐 뒤쪽부터 보겠습니다.

처음에 다루었듯이 등호=를 사용해서 변수(나 테이블 필드table field, 나중에 더 봅시다)에 값을 새로 만들거나 바꿉니다.
가끔 좀 특이하다 생각하실 수 있는 특성도 있습니다.

Standalone interpreter(lua.exe)를 실행하시고요,



> a=1
> b=2
> print(a,b)
1 2
print의 괄호 안에는 콤마,로 구분된 둘 이상의 값을 넣을 수도 있습니다.

처음 두 줄은 다음과 같이 한 번에 할 수도 있습니다.
> c=3; d=4
> print(c,d)
3 4

> e=5 f=6
> print(e,f)
5 6

대부분 경우에 세미콜론;은 별 의미가 없는데 사람이 코드를 눈으로 보기에 편합니다.

하나 이상의 공백은 다 같이 동작하며 구조상 차이가 없으면 공백이 있든 없든 무관한 때도 많습니다. 하지만 적당히 공백을 넣으면 사람이 눈으로 보기에 편합니다.
> g = 7 h=   8
> print( g  ,h)
7 8

그리고, 둘 이상의 지정은 다음처럼도 할 수 있습니다.
> x, y = 1, 2
> print(x, y)
1 2

등호 좌변에 변수, 우변에 값이 들어가는데 갯수 차이가 있다면
값이 넘치면 그냥 무시되며 값이 모자라면 뒤쪽 변수에는nil을 지정한 것과 같습니다.
> x, y, a = 11, 12, 13, 14
> b, c, d = 15, 16
> print(x, y, a, b, c, d)
11 12 13 15 16 nil

아까는 d가 4였는데 값이 없는 지정의 결과 nil이 지정되었습니다.




> a = 0
> print(a)
0
> a = a + 1
> print(a)
1

지정을 할 때는 우변에 지정의 대상이 되는 변수 자신을 사용할 수도 있습니다.
a = a + 1
수학 언어라면 ‘이게 뭐야’지만 프로그래밍에서는 ‘a에 a + 1이라는 값을 지정해라, 즉 a의 값을 1 증가’시키는 작업을 수행하는 명령에 해당합니다. 값이 필요에 따라 변할 수 있어서 변수입니다.

> c, d = 1, 2
> c = d
> d = c
> print(c, d)
2 2

> e, f = 3, 4
> e, f = f, e
> print(e, f)
4 3

위쪽 사례에서
c = d 명령으로 d값인 2가 c에 먼저 지정됩니다.
d = c 명령으로 c 값인 2가 d에 재지정됩니다.(원래 2지만)

아래쪽을 보시면 두 개를 한 번에 지정하면 작업이 완전히 종료되기 전에는 변수의 값이 변하지 않음을 보실 수 있습니다.
e, f = f, e
x, y = y, x
등으로 두 변수의 값을 서로 바꿀 수 있습니다.




2. 타입 Types

위에서 변수에 값value을 지정하는 것을 다루었습니다.
위에서 다룬 값들은 전부 수number였습니다만 Lua의 값에는 8종류의 값이 있습니다.
nil, boolean, number, string, userdata, function, thread, table

어떤 값이 위 8가지 중 어디에 속하는지를 알아보려면 type이라는 함수function을 쓰면 됩니다.
값을 직접 출력받기 위해 print라는 함수를 쓴 것과 같습니다.




> a = 3
> b = type(a)
> print(a, b)
3 number

3이라는 값의 type은 number입니다.

print라는 함수와 다르게 type이라는 함수는 반환값이 있어서 b = type(a) 처럼 그 값을 변수에 지정하거나 다른 함수에 집어넣을 수도 있습니다. 이렇게요.
> print(type(3))
number

> print(type(print))
function
> print(type(type))
function

print, type은 각각 함수입니다. 함수를 수행하려면 그 이름 뒤에 ()를 붙여서 필요한 입력값을 넣는 것이 일반적입니다. 입력값이 1개 뿐일 때에 한해 print 3 이렇게도 가능합니다.


좀 다른걸 볼까요?
> print(type(type(print)))
string

맨 앞의 print가 출력한 값은 type(type(print))입니다.
첫 type의 입력값으로 들어간 괄호 안의 type(print) 는 function이라는 결과를 낼거고요
그런데 이 function이라는 결과는 문자열string타입으로 나오기 때문에 type(type(print))는 다시 string이라는 문자열을 내놓습니다. 그게 출력된거죠.

> print(type(a))
number
> a = print
> print(type(a))
function
> a(type(print))
function

맨 위에 a = 3이었으니 타입이 number인 상태입니다.
a = print
a의 값으로 print를 지정하면 a는 print라는 함수로 지정됩니다.(print라는 변수에는 변화가 없고요. a가 print 함수를 가리킵니다.)
게다가 a로 print와 동등하게 사용할 수 있지요.



2.1 Nil

nil이라는 타입을 갖는 값은 단 하나가 존재하며 nil이 그것입니다.
Lua에서 nil은 값이 없는 상태를 의미하며 특정한 변수를 삭제하기 위해 nil을 지정할 수도 있습니다.

> print(type(XYZZZ))
nil
XYZZZ라는 변수는 정의한 적이 없는데 이러면 nil이라는 타입이 나옵니다.




2.2 불리안 Booleans

boolean 타입의 값은 true, false 두 가지가 있습니다. 논리적인 참, 거짓에 해당합니다.
Lua의 boolean이 약간 특이한 점은 true false 외의 값(즉, boolean 타입이 아닌 값들)에 대해 boolean 테스트를 할 때 false와 nil이 아니면 모두 true의 결과가 나옵니다. 그러니까, 0도 boolean 테스트 결과가 true입니다.
boolean 타입은 조건 판단에 쓰입니다.



2.3 수 Numbers

정수, 16진법 숫자, 소수, 분수 등이 모두 number 타입에 해당합니다.
(기술적으로 Lua가 다루는 수는 64비트 double precision floating point 형식이며 c와는 달리 수의 형식을 지정할 필요가 없이 15자리까지의 수를 그냥 다룰 수 있습니다.)

다음과 같은 값의 타입은 모두 number입니다.
4 0.4 6.02e23 6.673e-11 0xEE



2.4 문자열 Strings

공백을 포함할 수 있는 하나 이상의 문자character 조합입니다.
Lua에서 문자열은 큰 따옴표””나 작은 따옴표’’ 혹은 대괄호 두 개[[ ]] 사이에 지정됩니다.



> a = “A string!!”
> print(a, type(a))
A string!! string

문자열을 만들 때 백슬래쉬(한글 폰트에서는 보통 원화 표시)는 약간 다르게 동작합니다.
> print(“ab\ncd”)
ab
cd
> print(“ab\\cd”)
ab\cd
> print(“ab\”cd”)
ab”cd

\n : 줄바꿈
\\ : \
\” : “
\’ : ‘
\[ : [
등등

큰 따옴표나 따옴표 대신 [[ ]]를 쓰는 것의 이점은
> print([[ab
>> cd]])
ab
cd
와 같이 줄바꿈 문자를 문자열 안에 직접 입력할 수 있다는 것입니다. 중간에 엔터를 치면 ‘아직 작성중’이라는 의미로 >>라는 프롬프트가 뜨고 거기에서 입력을 완성했지요.

> print(‘a”bc”d’)
a”bc”d
문자열을 작은 따옴표로 시작했으니 그 안에서 큰 따옴표를 일반 문자처럼 쓸 수 있습니다. 큰따옴표로 시작했다면 다시 입력한 큰따옴표가 문자열을 종료해버려서 홀수개 쓸 때 문제가 됩니다.

Lua의 연산 기능은 값의 type에 대단히 관용적입니다. 그래서 이런 일이 가능합니다.
> a = “11”
> b = a + 1
> print(a, type(a), b, type(b))
11 string 12 number
a는 분명히 문자열인데 여기에 직접 + 1이라는 연산이 가능하고 결과는 숫자로 나옵니다. 그래도 a는 변함 없이 문자열이고요.
(연산은 가능한데 조건 판단에는 이러면 에러가 납니다)


2.5 표 Tables

오늘은 건너뜁니다. 목차 순서대로 함수 앞이라 간단히 넣으려고 했는데 양이 너무 많네요.



2.6 함수 Functions

특정한 형태로 쓰여서 생성되는 명령들의 모음입니다.
생성될 때는 아무 일도 하지 않고 그 함수를 호출call하면 지정된 명령을 수행합니다.

계속 쓰던 print, type 등이 Lua가 기본으로 제공하는 함수들입니다.


수학에서 함수는 서로 종속적인 변수들간의 관계를 나타내고 초등학교에서 함수의 개념을 배울 때는 ‘x를 넣으면 y가 나오는 관계’라는 식으로 배웁니다.

Lua에서도 그렇게 입력값input parameter을 넣으면 결과값return parameter이 나오는 함수들이 있으나

입력값과 결과가 다 없이 어떤 명령을 수행하기도 하고
입력값이 있어도 결과 없이 어떤 명령만 수행하기도 하고
입력값은 있으되 그에 따른 명령만 수행하고 결과는 내어놓지 않을 수도 있습니다.

type은 입력과 결과가 다 있습니다.
a = type(“3”) 이라면 “3”이라는 문자열이 입력이며 결과로 “string”이라는 값을 내어놓는데 이걸 a에 지정하지요.

print는 입력은 있으되 결과값이 없습니다.
결과가 있는 것 같지만 화면에 특정 문구를 출력하는 것은 단순히 명령을 수행한 것이고 a = print(“3”)처럼 쓰이지 않는다는 뜻입니다.

os.date()는 입력이 없고 결과가 있습니다. 와우에서는 date()로 써야 하는데 채팅창에 ‘/dump date()’라고 입력해보시면 날짜가 나옵니다.



함수를 만드는 형태는
function name()
end

혹은
name = function()
end
와 같습니다.

사이에 수행할 명령을 넣습니다.

그냥
function()
end
로도 함수를 만들 수 있습니다만, 이러면 이걸 나중에 실행할 방법이 없습니다. 테이블을 만든 다음 연결된 변수에 nil을 지정한 것과 같지요.




> a = 1
> function Increase()
>> a = a + 1
>> end
> print(a)
1
> Increase()
> print(a)
2
> Increase(); Increase() Increase()
> print(a)
5

Increase라는 함수를 만듭니다.
만들어도 수행하기 전에는 아무 일도 없어 a = 1이 그냥 나오지만 수행할 때마다 a가 1씩 증가합니다.
function을 입력하고 나면 상응하는 end가 나올 때까지 엔터를 쳐도 계속 함수 생성하는 과정에 있고 프롬프트가 >>를 유지하는 것을 볼 수 있습니다.


입력값과 반환값을 갖는 함수는 다음처럼 만듭니다.

> function Square(x)
>> return x * x
>> end
> print(Square(3))
9
> print(x)
nil

함수명 뒤의 괄호 안에 오는 부분이 입력값, return 이라는 명령어 뒤에 오는 부분이 함수의 반환값입니다.
이 때 주목할 점은 x라는 파라미터parameter는 함수 안에서만 쓰인다는 것입니다.

Square(3)을 수행하면 내부에서는
x = 3
return x * x
를 한 것과 같은 결과가 나옵니다.

밖에서는 x가 지정된 바 없으므로 nil입니다.


만약 위에서 a가 1로 지정되고 Increase() 함수로 5까지 된 상태에서

function Square(a)
 return a * a
end

로 함수를 만들었다면 어떻게 될까요?

print(Square(3))은 여전히 9를 출력하며 그 후에 print(a)는 5를 출력합니다.

함수를 만들 때 지정한 parameter 변수들은 그 함수가 수행될 때만 국지적으로 유효하며 함수 안에서는 Square(3)을 통해 a = 3으로 지정된 이상, 함수 안에서는 밖에서 지정한 a = 5는 무시됩니다.
게다가 함수 수행이 끝나면 a는 여전히 밖에 있던 5가 되고요.

이런 로컬 변수local variables의 특성은 나중에 Statements에서 더 다루겠습니다.



print에서 둘 이상의 입력이 가능함을 알고 있습니다. 둘 이상의 입력, 둘 이상의 반환값에 대해 살펴보겠습니다.



> a = “Banana”
> b, c = string.find(a, “an”)
> print(b, c)
2 3

(문자열 다루는 함수들을 string이라는 테이블 안에 넣어둔거고 그 중에 검색에 쓰이는 함수의 테이블 키값이 find인데 2.5에 있어야할 테이블을 건너뛰었습니다. 테이블은 나중에 소개하겠습니다.)


string.find는 둘 이상의 입력값을 가집니다. 형식은

start, end = string.find(“string”, “pattern” [, range])

마지막은 선택적이고 

수색의 범위 문자열
수색 목표 문자열

을 넣으면 원래 문자열에서 몇 번째부터 몇 번째 글자 사이에 목표 문자열이 있는지 반환하는 것입니다. 마지막은 수색을 시작할 위치. 예를 들어 string.find(a, “an”, 4)를 수행하면 네 번째 글자부터 시작하는, “ana”에서 “an”을 찾았을거에요.

string.find(a, “an”)은 string.find(“Banana”, “an”)과 같으니까 처음 얻어걸리는 an의 위치를 반환합니다.
두 번째와 세 번째 글자니까 2와 3을 차례로 반환합니다.
반환값이 몇 개인지 잘 아니까 b, c = string.find(a, “an”)으로 두 개를 다 잡아낸 것입니다.

하나만 필요하다면

> d = string.find(a, “ana”)
> print(d)
2

원래는 2와 4를 반환했겠는데 하나만 d로 지정하죠.

> e, e = string.find(a, “na”)
> print(e)
3

같은 문자를 두 번 쓸 수도 있는데 처음 것만 남아요.

자, 이렇게 다중 반환값을 여러 변수로 지정하는 방식은 우변에서 함수가 맨 뒤에 있어야 합니다.

> f, g, h = 0, string.find(a, “an”)
> print(f, g, h)
0 2 3

> i, j, k = string.find(a, “an”), 0
> print(i, j, k)
2 0 nil

처음 것은 두 개가 g, h에 잘 들어갔는데 다음 것은 반환값 두 개 중에 처음 것만 i에 들어가고 뒤에 있는 0이 j에 지정되죠. k는 아무 것도 지정받지 못하고요.




=============================================================================

일단 여기까지 정도면 넉넉한 것 같습니다.

테이블은 목차에는 들어있어야 하지만 빠져있고 어쩌면 마지막 예제 정도에 나올 것입니다.
문자열 다루는 기술은 좀 깊이 들어갈 여지가 있는데 아마 이 시리즈에서는 안나올 것 같습니다.

Lv83 올커니하면서

모바일 게시판 하단버튼

댓글

새로고침
새로고침

모바일 게시판 하단버튼

지금 뜨는 인벤

더보기+

모바일 게시판 리스트

모바일 게시판 하단버튼

글쓰기

모바일 게시판 페이징

최근 HOT한 콘텐츠

  • 와우
  • 게임
  • IT
  • 유머
  • 연예