1. 소개


스크립트가 잘 짜여진 모드들을 하다보면 테이블 세팅이 굉장히 편리하게 작동합니다. 버튼 한번으로 필요한 오브젝트들이 각기 제자리로 순식간에 옮겨지곤 합니다. 이번 시간에는 Bag에 버튼을 달아 버튼을 눌렀을 때, Bag 안에 있는 물건들이 지정된 위치로 꺼내지는 스크립트를 작성해보고자 합니다. Lestat님께서 업로드하신 엘드리치 호러 (풀확장 스크립트)모드에서 조사자 선택이나 고대의 존재 준비가 이와 같은 방법으로 작동하는 예시입니다.

2. 테이블 준비


우선 빈 테이블을 준비합니다. 그리고 Bag하나, Blue Rectangle 하나, White Checker 하나, Joker 카드 한장을 생성합니다. 이번 스크립트도 Bag에 작성할 예정이므로 Bag을 마우스 우클릭하여 Scripting>Scripting Editor를 통해 'Bag - a7e13a.lua' 문서를 생성해 줍니다.

가방 속 오브젝트
테이블 탑 시뮬레이터에서 현재 게임에 '존재'하는 오브젝트는 모두 고유한 값인 GUID를 갖는다는 점은 기초 가이드에서 설명드린 바 있습니다. 그렇다면 Bag에 들어있는 오브젝트나, 덱 안에 존재하는 카드 낱장은 어디에 '존재'하는 걸까요? 답은 'Bag에 들어있는 오브젝트 혹은 덱 안에 들어있는 카드 낱장은 게임 내에 존재하지 않는다'입니다. 이 말이 무슨 말이냐면, Bag안에 들어있거나 덱에 들어있는 낱장의 카드를 스크립트를 통해 정의하고, 이를 조작할 수 없다는 뜻입니다. 테이블 탑 시뮬레이터에서 부피가 큰 게임을 돌리다보면 Bag에서 물건을 꺼낼 때, 분명 게임 시작때 로딩을 다 했음에도 불구하고 다시 로딩을 하는 경우가 있습니다. 이는 Bag에 있던 물건은 게임에 존재하지 않으므로 꺼내질 때 비로소 로딩을 하기 때문입니다.

Bag안에 있는 오브젝트는 직접 조작할 수 없습니다. 그러므로 우리는 Bag안에 있는 오브젝트를 식별할 수 있도록 각각 오브젝트에 이름을 적어야 합니다. 각 오브젝트를 우클릭하여 Name란에 이름을 적어줍니다. Blue Rectangle은 'Blue Rectangle', White Checker는 'White Checker', Joker 카드는 'Joker'라고 적겠습니다. 그리고 셋 모두 Bag안으로 넣어줍니다. 여기까지 완료했다면 테이블을 저장합니다.

3. 스크립트 작성

3-1. 버튼 만들기

함수를 작동하게 할 버튼을 만들어 봅시다. Parameter를 정의하고 onload()함수에 createButton()함수를 이용해 게임이 불려오면 버튼을 생성하도록 합니다. 지난 글과 동일하므로 설명은 생략하도록 하겠습니다.

button_parm = {
    click_function = 'takeFromBag',
    function_owner = self,
    label = '세팅',
    width = 800,
    height = 800,
    position = {0, 0.5, 2.5},
    font_size = 300,
}

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

takeObject()
Bag이나 카드덱에서 내용물을 꺼내기 위해서는 takeObject(Parameter)함수를 이용합니다. 버튼을 생성하는 것과 비슷하게 takeObject()함수도 Parameter를 정의하여 꺼내는 오브젝트의 조건을 설정할 수 있습니다. 몇가지 설명하자면,

  • position = 좌표 : 꺼낸 오브젝트가 올 위치입니다.
  • rotation = 좌표 : 꺼낸 오브젝트의 회전값입니다.
  • flip = boolean : 꺼낸 오브젝트가 카드라면 뒤집을지 뒤집지 않을지를 정합니다. (rotation을 입력했다면 무시됩니다)
  • guid = 문자열 : 꺼낼 오브젝트의 GUID입니다. 이를 이용해 무엇을 꺼낼지 정할 수 있습니다.
  • index = 숫자 : 몇 번째 오브젝트를 꺼낼지 정합니다. index는 0부터 시작합니다. 이를 이용해 무엇을 꺼낼지 정할 수 있습니다. (guid와 index는 동시에 입력할 수 없습니다)

아무런 Parameter도 지정하지 않는다면 첫 번째 오브젝트를 꺼내 Bag의 살짝 위쪽에 둡니다. 이후 아무것도 하지 않는다면 다시 아래로 떨어져 Bag 안으로 들어가게 됩니다.
이번 목적은 일단 주머니에서 오브젝트를 꺼내고, 그 뒤에 이것이 무엇인가를 판별해 원하는 위치로 이동시킬 것이므로 takeObject()의 Parameter는 따로 지정하지 않을 것입니다.

둘째로, takeObject()는 오브젝트를 반환합니다. 무슨 뜻이냐면 어떤 변수에 takeObject()를 대입하면 자동적으로 해당 변수에는 꺼내진 오브젝트가 들어간다는 뜻입니다. 이를 이용하면 Bag에서 꺼낸 오브젝트를 스크립트에서 정의하고자 할 때, 따로 코드를 작성할 필요 없이 간단하게 정의할 수 있게 됩니다.

takeFromBag()함수를 작성합니다. Bag안에 있는 모든 오브젝트를 꺼낼 것이므로, Bag안에 있는 요소의 개수가 필요합니다. 이를 위해 우선 getObjects()함수를 이용하여 Bag안의 요소를 불러옵니다. 이후 for문을 통해 Bag안에 있는 요소의 개수만큼 takeObject를 실행하도록 합니다. 이때, takenObject 변수에 takeObject()함수를 대입하여, Bag에서 꺼낸 오브젝트를 takenObject에 정의해줍니다.

function takeFromBag()
    bagObjects = self.getObjects()

    for i=1, #bagObjects do
        takenObject = self.takeObject()
    end
end

이제 Bag에서 꺼낸 오브젝트가 무엇이냐에 따라 다르게 위치를 지정해 줄 필요가 있습니다. '어떠어떠한 오브젝트라면 ~ 해라'라는 문장이 필요하므로 자연스레 if문을 떠올릴 수 있습니다.

if, elseif
if문을 작성할 때 여러가지 경우를 처리해야 할 때가 있습니다. 예를 들어, A이면 a해라, B이면 b해라, C이면 c해라 ... 와 같은 경우입니다. 이런식으로 여러 조건을 처리할 때 이용하는 것이 elseif 입니다. if-elseif는 아래와 같이 작성합니다.

if 조건1 then
    --조건 1이 참이면 실행되는 코드
elseif 조건2 then
    --조건 2가 참이면 실행되는 코드
elseif 조건3 then
    --조건 3이 참이면 실행되는 코드
end

elseif의 갯수는 무한히 늘려 사용할 수 있습니다. 주의할 점은 조건을 판단하는 순서가 조건1 -> 조건2 -> 조건3 순서라는 점입니다. 만약 조건1이 참이라면 조건2, 조건3은 비교하지 않고 넘어갑니다.

이를 이용해 꺼낸 오브젝트의 이름을 비교하여 해당 이름에 맞는 자리로 옮겨주도록 함수를 작성해 봅시다. 오브젝트의 이름을 받아오려면 getName()함수를 이용합니다.
오브젝트의 위치를 옮기는 함수는 두 가지가 있습니다. setPosition(좌표)setPositionSmooth(좌표)입니다. setPosition()은 오브젝트를 순간이동 시키며, setPositionSmooth()는 물체를 처음 위치부터 목표 위치까지 움직이게 합니다. 이번에는 움직이는 것이 눈에 보이도록 setPositionSmooth()를 이용해 봅시다.
아래와 같이 if문을 작성하여 for문 안에 넣어줍니다.

if takenObject.getName() == 'Blue Rectangle' then
    takenObject.setPositionSmooth({0.00, 1.46, -6.00})
elseif takenObject.getName() == 'White Checker' then
    takenObject.setPositionSmooth({-6.00, 1.46, -6.00})
elseif takenObject.getName() == 'Joker' then
    takenObject.setPositionSmooth({6.00, 1.46, -6.00})
end

여기까지 했다면 원하는 기능은 완성입니다.

4. 스크립트 전문


button_parm = {
    click_function = 'takeFromBag',
    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 takeFromBag()
    bagObjects = self.getObjects()

    for i=1, #bagObjects do
        takenObject = self.takeObject()

        if takenObject.getName() == 'Blue Rectangle' then
            takenObject.setPositionSmooth({0.00, 1.46, -6.00})
        elseif takenObject.getName() == 'White Checker' then
            takenObject.setPositionSmooth({-6.00, 1.46, -6.00})
        elseif takenObject.getName() == 'Joker' then
            takenObject.setPositionSmooth({6.00, 1.46, -6.00})
        end
    end
end

5. 응용


여기서 끝내자니 글이 짧아 조금 아쉽습니다. 몇 가지 다른 함수와 함께 이를 응용해보도록 하겠습니다.
setLock()
setLock()함수는 인게임에서 오브젝트에 마우스를 올려놓고 L 버튼을 눌렀을 때와 동일하게 작동합니다. 물체의 위치를 잠글 수 있습니다. 잠긴 오브젝트는 마우스로 클릭해도 손에 들려지지 않아 게임판과 같이 일반적인 방법으로는 움직여선 안 되는 오브젝트에 적용하기 좋습니다.
위 스크립트에서 White Checker를 고정해봅시다. if문에서 White Checker를 움직이는 코드 아래에 추가합니다.

takenObject.setLock(true)

그리고 스크립트를 다시 실행해보면 White Checker는 잠겨서 손에 들리지 않습니다.

setRotation()
setRotation(좌표)함수는 물체의 회전각을 조절하는 함수입니다. 꺼내는 물체를 뒤집거나, 돌리거나 하는데 이용할 수 있습니다. 카드를 뒷면으로 혹은 앞면으로 꺼내야 할 때 유용하게 사용할 수 있습니다. 위 스크립트에서 Joker카드를 뒷면으로 꺼내도록 해 봅시다. if문에서 Joker를 움직이는 코드 아래에 추가합니다.

takenObject.setRotation({0.00, 180.00, 180.00})

interactable
interactable은 해당 오브젝트와의 상호작용 여부를 결정하는 요소입니다. setLock()함수로 잠긴 오브젝트여도 여전히 마우스를 올려 이름을 보거나 마우스 우클릭하여 잠금을 풀거나, 색상을 변경하거나 하는 등의 상호작용이 가능합니다. interactable을 해제하게 되면 상효작용 자체가 불가능해져 마우스를 올리거나 우클릭해도 아무런 반응이 없게 되어 마치 게임 테이블과 같은 요소처럼 작용합니다.
interactable을 사용할 때 주의할 점은 해당 오브젝트는 다른 오브젝트를 뚫고 지나간다는 점입니다. 그러므로 반드시 setLock()과 함께 사용하여 위치를 고정해야 합니다. 그렇지않으면 테이블을 뚫고 한없이 아래로 떨어지게 됩니다. if문에서 Blue Rectangle을 움직이는 코드 아래에 추가합니다. interactable은 함수가 아닌 오브젝트를 구성하는 요소이므로 수정방법이 다릅니다.

takenObject.setLock(true)
takenObject.interactable = false

6. 마무리


이것으로 takeObject()함수를 활용한 강좌를 마칩니다. 다음 글에는 takeObject()함수의 Parameter를 활용하여 원하는 오브젝트만 꺼내는 방법을 알아보도록 하겠습니다. 궁금하신 점 댓글로 달아주시면 아는 범위에서 최대한 답변드리도록 하겠습니다. 읽어주셔서 감사합니다.

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