지난 글에서는 출구에 닿으면 위층으로 올라가게 만들었습니다. 그런데 완성본을 떠올려 보면, 화면 위쪽에 지금 몇 층인지가 보이고, 떨어지면 몇 층까지 올랐는지 알려 주고, 가장 높이 오른 층이 기록으로 남습니다. 이 세 가지는 모두 같은 값 하나, 지금 몇 층에 있는지에서 나옵니다.
지금 우리 게임은 위층으로 올라가긴 하지만 몇 층인지 기억하는 곳이 아무 데도 없습니다. 출구는 플레이어를 옮기기만 하고 잊어버립니다. 이번 글에서는 층 번호를 기억하게 만들고, 서버에서 바뀐 그 값을 내 화면까지 가져오는 방법을 봅니다.

층 번호는 어디에 둘까
층을 바꾸는 것이 서버이니, 값을 정하는 것도 서버입니다. 그리고 여러 명이 함께 오르면 사람마다 있는 층이 다릅니다. 그래서 층 번호는 플레이어 한 사람 한 사람이 갖고 있어야 합니다.
MSW 에서 접속한 사람의 캐릭터는 DefaultPlayer 라는 모델로 만들어집니다. 이 모델에 스크립트를 붙이면, 들어오는 모든 플레이어가 그 스크립트를 하나씩 갖게 됩니다.

- MyDesk 에서 새 컴포넌트 스크립트를 만들고 이름을 TowerClimber 로 바꿉니다. 탑을 오르는 사람이라는 뜻입니다.
- Property 옆의 + 버튼으로 floor 를 만들고 타입을 integer 로 바꿉니다. Sync 는 일단 None 으로 둡니다(뒤에서 다시 봅니다).
- Workspace 에서 DefaultPlayer 를 고르고, Add Component 의 Script 에서 TowerClimber 를 찾아 붙입니다.
오르는 일을 플레이어에게 (ClimbTo, GetComponent)
지금은 출구가 플레이어를 직접 옮기고 있습니다. 그런데 층 번호는 플레이어가 갖고 있으니, 옮기는 일도 플레이어가 맡는 편이 자연스럽습니다. 출구는 이제 몇 층으로 보내는지만 알려 주면 됩니다. 하는 일은 같게 두고 일을 맡는 자리만 바꾸는 것도 리팩터링의 한 종류입니다.
TowerClimber 에 ClimbTo 라는 함수를 만들고 integer 매개변수 nextFloor 를 붙입니다. 몇 층으로 갈지를 nextFloor 로 넘겨받는 것입니다. 함수 안에는 세 가지를 적습니다.
- nextFloor 가 지금 층보다 높지 않으면 아무것도 하지 않고 끝냅니다.
- floor 를 nextFloor 로 바꿉니다.
- 지난 글과 같은 자리로 플레이어를 옮깁니다. 이번에는 옮길 대상이 이 플레이어 자신, self.Entity 입니다.
첫째 줄이 필요한 이유는, 출구에 닿았다는 알림이 한 번만 온다는 보장이 없기 때문입니다. 같은 출구에 두 번 닿아도 한 층만 올라가야 합니다. 이 함수도 ServerOnly 로 둡니다. 몇 층인지 정하는 것도, 플레이어를 옮기는 것도 서버가 해야 모두에게 같게 보입니다. 확인용으로 log 로 지금 층을 찍어 둡니다.
다음은 FloorExit 입니다. 닿은 플레이어에게서 GetComponent 로 TowerClimber 를 꺼냅니다. GetComponent 는 엔티티에 붙어 있는 컴포넌트를 찾아 주는 함수이고, 찾은 컴포넌트의 함수를 부르면 다른 스크립트에게 일을 시킬 수 있습니다. TowerClimber 가 없으면 플레이어가 아니니 그냥 끝내고, 있으면 ClimbTo 에 nextFloor 를 넘깁니다. 플레이어를 옮기던 두 줄은 FloorExit 에서 지웁니다.

@Component
script FloorExit extends Component
property integer nextFloor = 0
@ExecSpace("ServerOnly")
@EventSender("Self")
handler HandleTriggerEnterEvent(TriggerEnterEvent event)
local player = event.TriggerBodyEntity
---@type TowerClimber
local climber = player:GetComponent("script.TowerClimber")
if climber == nil then
return
end
climber:ClimbTo(self.nextFloor)
end
end실행해서 1층 출구에 닿으면 Console 에 floor 1 이, 2층 출구에 닿으면 floor 2 가 찍힙니다. 코드의 층 번호는 0 부터 세기 때문에 1층이 0, 2층이 1 입니다. 줄 앞의 SERVER 는 이 값을 서버가 바꾸고 서버가 찍었다는 뜻입니다.
2층에서 발을 헛디뎌 1층으로 떨어진 뒤 1층 출구에 다시 닿으면 이번에는 올라가지 않습니다. 1 은 지금 층 1 보다 높지 않으니까요. 떨어졌을 때의 규칙은 다음 글에서 게임오버를 만들며 정리합니다.
서버와 클라이언트 (OnSyncProperty, Sync)
MSW 게임은 서버 하나와 클라이언트 여럿으로 돌아갑니다. 서버는 게임의 진짜 상태를 갖고 있는 컴퓨터이고, 클라이언트는 플레이어 한 사람 한 사람의 화면입니다. 서버에도, 클라이언트에도 같은 플레이어 엔티티와 같은 TowerClimber 가 하나씩 있습니다.
floor 는 서버에서 바뀌었습니다. 그럼 내 화면, 클라이언트의 floor 는 몇일까요? 알아보려면 클라이언트에서 값을 찍어 봐야 합니다.
- TowerClimber 의 + 버튼에서 OnSyncProperty 를 고릅니다. 서버에서 바뀐 값이 클라이언트에 도착했을 때 불리는 함수라서 ClientOnly 에서만 실행됩니다.
- 매개변수 name 에는 바뀐 Property 의 이름이, value 에는 새 값이 들어옵니다. name 이 floor 일 때 value 를 log 로 찍습니다.
실행해서 1층 출구에 닿아 보면, SERVER 의 floor 1 은 찍히는데 CLIENT 쪽은 아무것도 찍히지 않습니다. floor 의 Sync 가 None 이기 때문입니다. None 은 값을 서버 안에만 두고 클라이언트로 보내지 않습니다. 처음에 None 으로 둔 것은 이 장면을 먼저 보기 위해서였습니다.
floor 위의 Sync 글자를 눌러 Sync 로 바꾸고 다시 실행하면, 이번에는 SERVER 다음에 CLIENT 의 floor 1 도 찍힙니다. 서버의 값이 내 화면까지 온 것입니다.

Sync 글자는 누를 때마다 셋 중 하나로 바뀝니다.
- None 아무에게도 보내지 않고 서버 안에서만 씁니다.
- Sync 접속한 모든 클라이언트에 보냅니다.
- TargetUserSync 그 플레이어 한 사람의 클라이언트에만 보냅니다. 내 층을 나만 알면 되는 게임이라면 보내는 양이 더 적습니다.
우리는 다른 사람이 몇 층에 있는지도 쓸 수 있게 Sync 로 둡니다.
@Component
script TowerClimber extends Component
@Sync
property integer floor = 0
@ExecSpace("ServerOnly")
method void ClimbTo(integer nextFloor)
if nextFloor <= self.floor then
return
end
self.floor = nextFloor
log("floor " .. self.floor)
local start = Vector3(0, nextFloor * 4 + 0.5, 0)
_TeleportService:TeleportToMapPosition(self.Entity, start, "map01")
end
@ExecSpace("ClientOnly")
method void OnSyncProperty(string name, any value)
if name == "floor" then
log("floor " .. value)
end
end
end한 가지 꼭 기억할 것이 있습니다. 클라이언트에서 값을 바꿔도 서버로는 가지 않습니다. 보내는 길은 서버에서 클라이언트, 한 방향뿐입니다. 값은 서버가 정하고, 화면에 필요한 값만 Sync 로 클라이언트에 보냅니다.

정리
- DefaultPlayer 에 TowerClimber 를 붙여 플레이어마다 층 번호 floor 를 갖게 했습니다.
- 위층으로 옮기는 일은 ClimbTo 함수로 플레이어에게 맡겼습니다.
- 출구는 GetComponent 로 TowerClimber 를 찾아 몇 층으로 갈지만 알려 줍니다.
- 서버에서 바뀐 값은 Sync 로 클라이언트에 보냅니다.
다음 글에서는 게임오버를 만듭니다. 발을 헛디뎌 떨어지면 탑 1층부터 다시 시작하게 할 텐데, 서버는 플레이어가 떨어졌다는 것을 어떻게 알 수 있을까요?
