본문 바로가기

진리는어디에

[Lua] 루아 스크립트 기본

주석

function foo()
    -- 한 줄 주석
    
    --[[ 
        구간(block) 주석
    --]]
end

REMARK. 구간 주석 끝을 나타내는 '--]]'는 사실 ']]'만 쓰면 되지만 가독성면이나 편의성면에서 관용적으로 '--]]' 로 사용한다.

변수

루아에서는 기본적으로 모든 변수가 전역 변수 처리된다. 특정 스코프(scope)에서만 유효한 변수는 선언 앞에 local 한정자를 추가한다.

function foo()
    a = 10 -- foo라는 함수 안에서 선언 되었지만 전역 변수
    local b = 10 -- 지역 변수. foo 함수 내에서만 유효하다.
end

함수

기본 문법은 아래와 같다. function 키워드로 함수임을 선언하고 end로 함수의 끝을 나타낸다.

function 함수명([인자1,]...)
    ...
end

루아 함수는 한번에 여러 개의 리턴 값을 가질 수 있다.

function sum(x, y)
    local ret = x + y
    return x, y, ret
end

x, y, ret = sum(10, 10)

print(x, y, ret)

테이블

테이블은 루아(Lua)의 특별한 자료 구조다. 배열, 세트, 딕셔너리 등 다양한 자료 구조를 테이블이라는 하나의 자료 구조로 표현한다.

배열로써 테이블

-- 배열 스타일
a = {} -- 테이블 선언
a[1] = "a"
a[2] = "b"
a[3] = "c"

for i = 1, 3 do
    print(a[i])
end

딕셔너리로써 테이블

user = {}

user["name"] = "Jason"
user["age"] = 10
user["job"] = "officer"

print(user["name"])
print(user["age"])
print(user["job"])

user = nil -- 사용이 끝나면 GC(Garbage Collector)에게 메모리를 반환하기 위해 nil을 대입한다

REMARK. 테이블 변수의 사용이 끝나면 꼭 nil 처리를 하도록 한다. 그렇지 않으면 GC에서 메모리 수거를 하지 못한다.

테이블의 키값을 멤버 변수와 같은 형태로 사용하는 것도 가능하다.

user = {} -- 테이블 변수 선언

user.name = "Jason"
user.age = 10

print(user["name"])
print(user["age"])

테이블 초기화

중괄호({})에서 바로 초기화 가능

// 배열 형식 초기화
a = { "A", "B", "C" }

// 맵 형식 초기화
user = { 
    name = "Jason",
    age = 10,
    job = "officer"
}

테이블 순회

a = { "A", "B", "C", nil, nil }

print("length of table:"..#a)
for i = i, #a do
    print(a[i])
end

출력 : 
    length of table:3
    A
    B
    C
    
-- key-value pair 형태 순회
local dict = {name="Tom", age = 30, job = "Officer"}

for key, value in pairs(dict) do
    print(key..","..value)
end

REMARK. 루아는 테이블의 끝을 nil값으로 판단한다. 위 예제 테이블은 5개의 요소를 가지고 있지만 lenth 연산자(#)로는 3번째 까지 밖에 접근할 수 없다.

조건문

if 조건1 then
    -- 조건1이 true면 then과 end(또는 elseif) 사이의 내용 실행
elseif 조건2 then
    -- 조건2가 true면 then과 end 사이의 내용 실행
else
    -- if와 elseif 모두 false면 else와 end 사이의 내용 실행
end

반복문

while

while 조건
do
    ...
end

repeat

C/C++의 do ~ while 문 처럼, 선 실행 후 조건을 판단한다.

repeat
    ...
until 조건

for

-- 수치 for문
for 변수 = 초기값, 종료값 [,증가값] do
    -- 수치 for문에서는 초기값, 종료값, 증가값을 정의
end

-- 일반(Generic) 배열 for문. ipairs에 배열 형태 테이블 사용
local arr = { "A", "B", "C", "D", "E" }

for index, value in ipairs(arr) do
    print(index, value)
end

-- 일반(Generic) 딕셔너리 for문. pairs에 딕셔너리 형태 테이블 사용
local dict = {name="Jason", age=10, job="Officer"}

for key, value in pairs(dict) do
    print(key, value)
end

반복 종료

반복을 종료할 때는 break

연산자

산술 연산자

연산자 설명
+ 더하기
- 빼기
* 곱하기
/ 나누기
// 나눈 후 몫만 취함
% 나눈 후 나머지만 취함
^ 지수

관계 연산자

연산자 설명
= 선언
== 같다
~= 다르다
> 좌항이 우항 보다 크다
< 좌항이 우항 보다 작다
>= 좌항이 우항 보다 크거나 같다
<= 좌항이 우항 보다 작거나 같다

논리 연산자

연산자 설명
# 테이블의 길이(nil은 포함되지 않는다)
.. 문자열 합치기
local a = {"Hello", "World"}

print(#a) -- 2
print(a[1].."! "..a[2]) -- Hello! World

메타 테이블

function Enum(t)
  local enum = { _props = {} }
  
  for key, value in pairs(t) do
    enum._props[key] = value
  end

  local meta = {
    __index = function(self, key)
      local value = self._props[key]
      if nil == value then
        error("<"..key.."> is not exist")
        return
      end
      return value
    end,
    __newindex = function(self, key, value)
      error("Enum is not mutable")
    end
  }
  
  setmetatable(enum, meta)
  
  return enum
end

OrderType = Enum({
  None = 0,
  First = 1,
  Second = 2
})

print(OrderType.None) -- 0
print(OrderType.First) -- 1
print(OrderType.Second) -- 2

OrderType["Third"] = 10 -- error : Enum is not mutable
print(OrderType.Third) -- error : <Third> is not exist

Rect = {
    Create = function(self, x, y, width, height)
        local rect = {
            x = x or 0,
            y = y or 0,
            width = width or 0,
            height = height or 0,
            
            Contains = function(self, point)
                return true
            end,

            Overlaps = function(self, other)
                if self.xMax < other.xMin then return false end
                if self.xMin > other.xMax then return false end
                if self.yMax < other.yMin then return false end
                if self.yMin > other.yMax then return false end
        
                return true
            end
        }

        setmetatable(rect, self)
        return rect
    end,

    xMin = function(self)
        return self.x
    end,
    xMax = function(self)
        return self.x + self.width
    end,
    yMin = function(self)
        return self.y
    end,
    yMax = function(self)
        return self.y + self.height
    end,
    center = function(self)
        -- The position of the center of the rectangle.
        return 0
    end,
    max = function(self)
        -- The position of the maximum corner of the rectangle.
        return 1
    end,
    min = function(self)
        -- The position of the minimum corner of the rectangle.
        return 2
    end,
    position = function(self)
        -- The X and Y position of the rectangle.
        return 3
    end,
    size = function(self)
        -- The width and height of the rectangle.
        return 4
    end,
    __index = function(self, key)
        local prop = Rect[key]
        return prop(self)
    end
}


local rect = Rect:Create(10, 2, 20, 30)
print(rect.xMin, rect.xMax)
print(rect.yMin, rect.yMax)
print(rect.center, rect.position, rect.size)

local a = Rect:Create(10, 10, 10, 10)
local b = Rect:Create(15, 15, 10, 10)
local c = Rect:Create(21, 21, 10, 10)

print(a:Overlaps(b))
print(a:Overlaps(c))
print(b:Overlaps(c))

setmetatable을 통해 기존 테이블에 새로운 메타 테이블을 할당하면, 덮어 쓰는 것이 아니라 기존 메타 테이블에 추가하게 된다.

local x = { name = "Jason" }
local meta = { __index = {age = 10} }

setmetatable(x, meta)

print(x.name) -- 원래 x가 가지고 있던 name
print(x.age) -- meta 데이터에 의해 추가된 age

부록 1. 같이 읽으면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!