1. 소개


테이블 탑 시뮬레이터의 장점 중 하나라면 점수계산이 필요한 보드게임에서 이를 스크립트로 자동화하여 1초도 안 되는 순간에 점수를 계산해낼 수 있다는 점이 아닐까 합니다. 점수는 여러가지 방법으로 계산되는데, 티츄의 경우를 예시로 들어보겠습니다. 특수카드 4장이 추가된 트럼프 카드 1벌로 게임을 하되, 5는 5점, 10, K는 10점, 특수카드 2장이 각각 25점과 -25점으로 계산되고 나머지는 점수로 계산되지 않습니다. 이를 스크립트로 표현해봅시다.

2. 준비

2-1. 점수 계산 방법

점수를 계산하려면 카드를 한곳에 모아야 합니다. 보통 두 가지 방법이 이용될 수 있는데, 하나는 카드를 주머니에 넣어서 모아두는 것. bblch1016님께서 제작하신 티츄(한글판)(자동점수화)모드가 이러한 방식을 지원합니다. 다른 하나는 점수카드를 모아놓을 장소를 정해놓고 그 장소에 카드를 모아두게 하는 방식. 제가 제작한 하트 오브 크라운 한글 (스크립트)모드가 이러한 방식의 예시라고 할 수 있습니다. 우선 주머니에 카드를 모으는 경우에 대해 구상해 봅시다.

2-2. 테이블 준비

티츄를 예시로 들어서 점수계산 스크립트를 작성해 보겠습니다. Standard 카드 한 벌과 특수 카드 역할을 할 Joker 두 장, 그리고 점수를 계산하기 위해 카드를 모을 Bag 하나를 준비합니다. 점수계산 스크립트는 Bag과 그 안에 있는 내용물만이 중요합니다. Bag에서만 작동하는 스크립트이므로 이번 스크립트는 Global.lua가 아닌 Bag에 작성해보도록 하겠습니다.

오브젝트 스크립트
오브젝트에 스크립트를 작성하면 별도의 스크립트로 인식되어 실행됩니다. Global.lua와는 일반적인 방법으로 서로 정보를 주고받을 수 없게 됩니다. 물론 테이블 탑 시뮬레이터에서는 서로 다른 오브젝트의 스크립트들이 소통할 수 있도록 몇 가지 함수를 제공하고 있습니다. 기능별로 스크립트를 분리하는 이유는 원인모를 충돌 가능성을 최대한 배제하고, 복제의 용이성을 위함입니다. 오브젝트와 기능별로 스크립트를 분리해두면 해당 오브젝트의 복사만으로 그 안에 들어있는 스크립트까지 전부 복사되므로, 다른 게임에서 다시 활용할 수 있게되는 등, 자신만의 라이브러리를 구축할 수 있게됩니다.

가방을 마우스 우클릭하여 Scripting>Scripting Editor를 클릭합니다. Global.lua 밑에 Bag - e7563b.lua 파일이 생성된 것을 확인할 수 있습니다. 이곳에 작성하는 스크립트는 Bag에 적용되는 스크립트입니다.

Description
티츄의 경우 카드의 점수를 계산할 때, 5는 5점, 10, K는 10점, 특수카드 2종은 각각 25점, -25점입니다. 점수를 계산하기 위해서는 해당 카드가 몇 장씩 있는지 알아야 합니다. 우리는 눈으로 카드의 그림을 보고 판단하지만 스크립트의 경우 카드에 그려진 그림이 무엇인지 알지 못합니다. 그러므로 해당 카드가 어떤 카드인지 알려줄 필요가 있습니다. 저는 이럴 때 주로 _Description_을 이용합니다. 각 카드의 Description에 해당 카드의 점수를 적어놓고 가방에 있는 카드들의 Description을 읽어 합치게 하는 것입니다. 5 카드를 꺼내 Description에 '5'를, 10과 K 카드를 꺼내 '10'을, 각각의 조커 카드에는 '25'와 '-25'를 기입합니다.

이후 테이블을 저장합니다.

3. 스크립트 작성


Bag에 작성하는 스크립트이므로 이번에 한해서는 Bag 오브젝트를 정의할 필요가 없습니다. 오브젝트 스크립트에서는 self라는 변수가 오브젝트가 작성된 자기자신을 지칭하기 때문입니다.

3-1. 버튼 만들기

우선, 점수계산을 위한 버튼을 만들어봅시다. 카드를 넣고 버튼을 누르면 점수를 계산하도록 하기 위함입니다. 버튼을 만들기 위해서는 Parameter가 필요합니다. 다음과 같이 작성합니다.

button_parm = {
    click_function = 'calculateScore',
    function_owner = self,
    label = '계산',
    width = 800,
    height = 800,
    position = {0, 0.5, 2.5},
    font_size = 300,
}

지난 강의와 다르게 function_owner에 self라고 적힌 것을 알 수 있습니다. calculatorScore()라는 함수를 이 스크립트가 작성되고 있는 Bag오브젝트 안에 작성할 것이기 때문입니다. 이제 아래쪽에 게임이 불려오면 버튼을 만들도록 하는 함수를 작성합니다. onload()에 작성해야겠죠?

function onload()
    self.createButton(button_parm)
end
3-2. Bag내용물 살펴보기

가방 혹은 덱, 스크립트 구역의 내용물을 살펴보는 함수는 getObjects()를 사용합니다. 스크립트 구역에 사용하면 해당 구역에 있는 오브젝트들이 들어 있는 테이블이 반환됩니다. 가방 혹은 덱에 사용하면 안에 들어 있는 내용물과 각각의 정보가 담긴 테이블이 반환됩니다. calculateScore()함수를 작성하여 해당 내용을 추가해 봅시다.

function calculateScore()
    bagContents = self.getObjects()
end

calculateScore()함수가 실행되면 bagContents라는 변수에 Bag에 담긴 내용물의 모든 정보가 담기게 됩니다. bagContents는 다음과 같은 형태를 갖습니다.

{(첫 번째 내용물의 정보들), (두 번째 내용물의 정보들), (세 번째 내용물의 정보들) ... (마지막 내용물의 정보들)}

여기까지 오면 점수계산 스크립트는 다음과 같은 목적을 갖습니다. bagContents의 첫번째 내용물을 확인해서 description부분 읽기, bagContents의 두 번째 내용물을 확인해서 description부분 읽기 ... bagContents의 마지막 내용물을 확인해서 description부분 읽기.
단순한 작업의 반복입니다. 반복문을 활용할 차례입니다. 이번에는 좀 더 직관적인 숫자형 for문을 활용해 보겠습니다.

숫자형 for문
테이블의 첫 번째 내용물부터 확인할 것이므로 초기값은 1 입니다. 그렇다면 마지막 숫자는 몇으로 해야할까요? 가방에 있는 내용물의 갯수가 되야 합니다. 이를 위한 연산자는 #입니다. #bagContents라고 입력하면 Lua에서는 bagContents 안에 있는 내용물의 갯수를 반환합니다. 이를 이용해 for문을 작성하면, 1부터 bagContents의 내용물 갯수만큼 코드를 반복 수행합니다. 반복 수행할 내용은 bagContents의 요소들 중 'description'을 읽어내는 것입니다. 아래와 같이 작성하여 calculateScore()에 추가합니다.

for i=1, #bagContents do
    descriptionValue = bagContents[i]['description']
end
3-3. 각 카드의 점수 합치기

각 내용물의 description을 읽어냈다면 이제 점수가 있는 카드들의 점수를 합쳐 총점을 계산하는 일만 남았습니다. 첫째로 할 일은 bagContents의 내용물을 읽어낼 때, 이 카드에 점수가 있는지 없는지를 판단하는 것입니다. 점수가 있다면 'description'의 내용이 있을것이고, 점수가 없다면 'description'의 내용이 비어있을 것입니다. 조건문을 작성합니다. 이 카드의 'description'이 비어있지 않다면 이라는 조건이 됩니다. for문 안에 추가하여 작성합니다.

if descriptionValue ~= '' then
    --조건이 참이면 실행되는 코드
end

~= 기호는 '같지 않다'를 뜻합니다. 비어 있는 문자열('')이 아니다 라는 조건을 작성한 것입니다. 여기서 조건이 참이라면 총점에 합산하여야 합니다. 그러려면 우선 총점이 들어갈 변수를 정의해야 합니다. 점수는 0에서부터 각 카드의 점수를 더해가는 방식으로 계산합니다. calculateScore()함수 가장 윗줄에 총점 = 0이라는 내용의 코드를 추가합니다.

totalScore = 0

재귀호출
조건이 참이라면 총점에 현재 점수를 더해야 합니다. 이를 스크립트로 표현하기 위해서는 재귀호출이라는 개념을 이용합니다. 자기 자신의 값에 새로운 값을 더해서 다시 자기 자신으로 만드는 개념입니다. 예를 들어, 현재 총점이 10점이고 5점을 추가한다면 총점이 15점이 되어야 합니다. 이를 표현하는 방법은 '총점 = 총점 + 5'라는 형태로 작성하는 것입니다. 이는 '='기호가 같다의 뜻이 아닌, 왼쪽의 변수에 오른쪽의 값을 대입한다는 표현이기 때문입니다.
주의: C++등 기타 프로그래밍 언어에 지식이 있는 분이라면 +=을 생각하실 수 있습니다만, Lua는 +=기호를 제공하지 않습니다.
if문의 조건이 참이면 실행되는 코드 부분에 다음과 같이 추가합니다.

totalScore = totalScore + tonumber(descriptionValue)

자료형
뜬금없이 tonumber()라는 함수가 사용되었습니다. 그 이유는 descriptionValue의 자료형이 문자열이기 때문입니다. descriptionValue는 각 카드에 입력된 Description의 내용입니다. 저희는 5, 10, 25, -25 등의 숫자를 입력해 둔 상태입니다. 그러나 입력은 숫자로 했더라도 Description에 적힌 내용은 그 내용이 무엇이든 문자열로 인식됩니다. 그러므로 tonumber()함수를 이용하여 문자열로 된 내용울 숫자로 바꿔야 덧셈을 할 수 있게 됩니다.

3-4. 점수 출력

여기까지 작성했다면 calculateScore()함수는 총점을 0으로 시작하여, 가방 안의 내용물을 하나씩 찾아보면서 Description이 비어있지 않은(점수가 적혀있는) 카드의 점수를 합산합니다. 이제 합산한 점수를 발표하면 됩니다. print()를 통해 점수를 알리도록 합시다. calculateScore()함수의 가장 아랫줄에 코드를 추가합니다.

print('점수는 ' .. totalScore .. '점 입니다.')

문자열 이어붙이기
Lua에서 ..은 내용과 내용을 이어붙여 문자열을 만들 때 사용됩니다. 예를 들어 totalScore가 10이라면, '점수는 ' .. totalScore .. '점 입니다.'의 결과는 '점수는 10점 입니다.'라는 문자열이 됩니다.
여기까지 했다면 완성입니다.

4. 스크립트 전문


button_parm = {
    click_function = 'calculateScore',
    function_owner = self,
    label = '계산',
    width = 800,
    height = 800,
    position = {0, 0.5, 2.5},
    font_size = 300,
}

function onload()
    self.createButton(button_parm)
end

function calculateScore()
    totalScore = 0
    bagContents = self.getObjects()

    for i=1, #bagContents do
        descriptionValue = bagContents[i]['description']

        if descriptionValue ~= '' then
            totalScore = totalScore + tonumber(descriptionValue)
        end
    end

    print('점수는 ' .. totalScore .. '점 입니다.')
end

5. 마무리


위 스크립트는 여러 방법으로 활용될 수 있습니다. 하트 오브 크라운처럼 가방이 아닌 스크립트 구역에 놓인 카드들의 점수를 합산한다거나, onObjectEnterContainer()를 활용하여 버튼 없이 가방에 카드를 넣을때마다 자동으로 점수가 계산되어 표시되거나 하는 식으로도 활용해볼 수 있을 것입니다.
테이블 탑 시뮬레이터 스크립팅에 도전하는 분들께 조금이나마 도움이 되었으면 합니다. 궁금하신 점 댓글로 달아주시면 아는 범위에서 최대한 답변드리도록 하겠습니다. 읽어주셔서 감사합니다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기