이제 탑을 오르면 내가 오른 가장 높은 층이 기록으로 남습니다. 그런데 아쉬운 점이 하나 있습니다. 1층이나 3층이나 어렵기가 똑같습니다. 발판은 늘 같은 길이이고, 틈도 늘 같은 범위에서 고릅니다. 높이 올라가도 더 어려워지지 않습니다.

완성본은 다릅니다. 아래층에는 긴 발판이 많고, 위로 올라가면 짧은 발판이 늘어납니다. 발판 사이의 틈도 점점 넓어집니다. 이번 글에서는 이렇게 오를수록 어려워지는 탑을 만듭니다.
어려움을 숫자 하나로
먼저 어려움을 숫자 하나로 나타냅니다. 1층은 0 이고, 가장 어려운 층은 1 입니다. 그 사이 층은 0 과 1 사이의 값을 갖습니다. 이 숫자 하나만 보면 짧은 발판을 몇 개 둘지도, 틈을 얼마나 벌릴지도 정할 수 있습니다.

틈을 넓힐 때는 조심할 점이 하나 있습니다. 4화에서 정한 틈의 한계는 넘지 않습니다. 한계를 넘으면 아무리 잘 뛰어도 건널 수 없는 층이 나옵니다. 그래서 한계는 그대로 두고, 가장 좁은 틈을 한계 쪽으로 올립니다.
순서는 이렇습니다.
- 층마다 어려움을 계산합니다.
- 짧은 발판을 만들어, 오를수록 더 많이 섞습니다.
- 어려움에 맞춰 틈을 넓힙니다.
- 숫자들을 Property 로 꺼내 Maker 에서 조절합니다.
층마다 어려움 계산하기
층을 만드는 곳은 FloorBuilder 의 BuildFloor 함수입니다. BuildFloor 는 몇 번째 층인지를 floor 로 받고, floor 는 0 부터 셉니다. 1층이 0 입니다.
- BuildFloor 맨 위에 hardness 라는 값을 하나 만듭니다. 어려움이라는 뜻입니다.
- floor 를 10 으로 나눕니다. floor 가 0 이면 어려움도 0 이고, floor 가 10 이 되면 어려움은 1 이 됩니다.
- floor 가 10 보다 커지면 어려움이 1 을 넘습니다. 1 보다 어려운 층은 없기로 했으니 math.min 으로 감쌉니다. math.min 은 두 값 가운데 작은 값을 돌려줍니다. 1 과 비교해서 작은 쪽을 쓰니 어려움은 1 에서 멈춥니다.
- 잘 계산되는지 floor 와 어려움을 log 로 한 줄에 같이 찍습니다.
실행하면 Console 에 floor 0 은 0.0, floor 1 은 0.1, floor 2 는 0.2 가 찍힙니다. 층이 오를수록 어려움이 커집니다. 다만 숫자가 아주 작습니다. 탑은 아직 세 층뿐인데 10 을 기준으로 나눴기 때문입니다. 이 10 은 마지막에 Property 로 꺼내서 바꿉니다.
짧은 발판 섞기
먼저 짧은 발판이 필요합니다.
- Preset List 의 Foothold 에서 foothold-425 를 끌어다 놓습니다. 길이가 1칸인 짧은 발판입니다.
- 이름을 Short1 로 바꿉니다.
- IsDynamicFoothold 를 켭니다. 실행 중에 옮기는 발판은 이걸 켜야 밟는 선도 같이 옮겨집니다. 긴 발판은 3화에서 이미 켜 두었습니다.
- 발판 자리는 한 층에 일곱 개, 세 층이면 스물한 자리입니다. 자리마다 짧은 발판도 하나씩 두려고 Short1 을 복사해 Short21 까지 만듭니다. Platform 의 번호와 Short 의 번호가 같은 자리를 가리킵니다.

이제 코드입니다. 세 가지를 바꿉니다.
발판 종류. 짧은 발판이 나올 확률을 어려움으로 정합니다. 0.5 에 어려움 곱하기 0.3 을 더합니다. 어려움이 0 이면 절반이고, 어려움이 1 이면 0.8 입니다. RandomDouble 이 이 값보다 작으면 짧은 발판입니다. 첫 발판은 늘 긴 발판으로 둡니다.
발판 사이의 거리. 지금까지는 다음 발판까지 2.4 를 더했습니다. 긴 발판의 절반인 1.2 가 두 번이었습니다. 이제는 발판마다 길이가 다르니, 앞 발판의 절반에 틈을 더하고 이 발판의 절반을 더합니다. 절반 길이는 테이블 half 에 적어 둡니다. 긴 발판은 1.2, 짧은 발판은 0.5 입니다.

발판 옮기기. 자리 번호로 긴 발판과 짧은 발판을 둘 다 찾습니다. 쓰는 쪽은 그 자리로 옮기고, 안 쓰는 쪽은 맵 밖 멀리 치워 둡니다.
실행하면 층마다 짧은 발판이 섞여 있고, 끝까지 건널 수 있습니다. 다만 아직은 층마다 차이가 거의 없습니다. 어려움이 작아서 확률이 모두 절반 근처이기 때문입니다.
틈 넓히기
지금 틈은 0.3 과 한계 사이에서 고릅니다. 0.3 이 가장 좁은 틈입니다. 어려울수록 이 가장 좁은 틈을 한계 쪽으로 올립니다. 한계는 그대로 두니, 고르는 범위가 좁아지면서 넓은 틈만 나오게 됩니다.

- 가장 좁은 틈 smallestGap 을 계산합니다. 0.3 에서 한계까지의 거리에 어려움을 곱해서 0.3 에 더합니다.
- 여기에 0.7 을 한 번 더 곱합니다. 끝까지 다 올리면 가장 어려운 층에서는 늘 한계 틈만 나옵니다. 그래서 조금 남겨 두고 0.7 만큼만 올립니다.
- 틈을 고를 때 0.3 대신 smallestGap 부터 고릅니다.
같은 높이로 건너는 틈을 예로 들면, 한계는 0.6 입니다. 어려움이 0 이면 가장 좁은 틈은 0.3 이고, 어려움이 1 이면 0.51 입니다. 그래서 가장 어려운 층에서는 0.51 과 0.6 사이의 틈만 나옵니다.

한 가지 더 확인할 것이 있습니다. 짧은 발판에서 뛰면 달릴 거리가 짧습니다. 그래서 모든 발판을 짧은 발판으로, 모든 틈을 한계로 놓고 건너 보았습니다. 그래도 모두 건넜습니다. 4화에서 한계를 사람이 뛸 수 있는 거리의 절반 정도로 넉넉하게 잡아 둔 덕분입니다.
Property 로 꺼내 Maker 에서 조절하기
지금 코드에는 10 과 0.7 이 그대로 적혀 있습니다. 이 숫자를 바꾸려면 매번 코드를 고쳐야 합니다. Property 로 꺼내 두면 Maker 에서 바로 바꿀 수 있습니다.
- FloorBuilder 에 Property 를 두 개 만듭니다. 첫째는 hardestFloor 로 가장 어려운 층입니다. 타입은 integer, 기본값은 10 입니다.
- 둘째는 gapSqueeze 로 가장 좁은 틈을 얼마나 올릴지입니다. 타입은 number, 기본값은 0.7 입니다.
- 둘 다 서버만 쓰는 값이라 Sync 는 None 으로 둡니다.
- 코드에서 10 자리에 self.hardestFloor 를, 0.7 자리에 self.gapSqueeze 를 넣습니다.
@Component
script FloorBuilder extends Component
property integer hardestFloor = 10
property number gapSqueeze = 0.7
@ExecSpace("ServerOnly")
method void OnBeginPlay()
for floor = 0, 2 do
self:BuildFloor(floor)
end
end
@ExecSpace("ServerOnly")
method void BuildFloor(integer floor)
local hardness = math.min(1, floor / self.hardestFloor)
log("floor " .. floor .. " 어려움 " .. hardness)
local maxGap = { [-1] = 0.8, [0] = 0.6, [1] = 0.5 }
local half = { Long = 1.2, Short = 0.5 }
local shortChance = 0.5 + hardness * 0.3
local layout = { { kind = "Long", x = 0, y = 0 } }
for i = 2, 7 do
local before = layout[i - 1]
local kind = "Long"
if _UtilLogic:RandomDouble() < shortChance then
kind = "Short"
end
local step = _UtilLogic:RandomIntegerRange(-1, 1)
if before.y + step * 0.5 < 0 then
step = 1
elseif before.y + step * 0.5 > 1.5 then
step = -1
end
local smallestGap = 0.3 + hardness * self.gapSqueeze * (maxGap[step] - 0.3)
local gap = smallestGap + _UtilLogic:RandomDouble() * (maxGap[step] - smallestGap)
layout[i] = { kind = kind, x = before.x + half[before.kind] + gap + half[kind], y = before.y + step * 0.5 }
end
for i, spot in ipairs(layout) do
local long = self.Entity:GetChildByName("Platform" .. (floor * 7 + i))
local short = self.Entity:GetChildByName("Short" .. (floor * 7 + i))
local here = Vector3(spot.x, floor * 4 + spot.y, 0)
local away = Vector3(-30, 0, 0)
if spot.kind == "Short" then
short.TransformComponent.Position = here
long.TransformComponent.Position = away
else
long.TransformComponent.Position = here
short.TransformComponent.Position = away
end
end
if floor < 2 then
local last = layout[7]
local exit = self.Entity:GetChildByName("Exit" .. (floor + 1))
exit.TransformComponent.Position = Vector3(last.x, floor * 4 + last.y, 0)
end
end
end이제 Maker 에서 바꿔 봅니다. map01 을 고르면 Property 창에 FloorBuilder 가 보입니다. 여기서 hardestFloor 를 10 에서 2 로 바꿉니다. 탑이 세 층이라 이제 3층이 가장 어려운 층입니다. 코드에 적은 10 은 기본값이고, 실제로는 Maker 에서 넣은 값이 쓰입니다. 저장한 뒤에는 값이 파일에 들어갔는지 Property 창에서 한 번 더 확인합니다.

실행하면 floor 0 은 0.0, floor 1 은 0.5, floor 2 는 1 이 찍힙니다. 1층부터 3층까지 올라가 보면 위로 갈수록 짧은 발판이 많아지고 틈도 넓어집니다.
정리
- 어려움을 0 에서 1 사이의 숫자 하나로 나타냅니다.
- 짧은 발판이 나올 확률을 어려움으로 정합니다.
- 길이가 다른 발판 사이의 거리는 앞 발판의 절반과 틈과 이 발판의 절반을 더한 값입니다.
- 한계는 그대로 두고, 가장 좁은 틈을 올려서 틈을 넓힙니다.
- 숫자를 Property 로 꺼내면 Maker 에서 바로 바꿀 수 있습니다.
다음 글에서는 탑을 끝없이 쌓습니다. 지금 탑은 세 층뿐이지만, 아무도 없는 아래층의 발판을 위층에 다시 쓰면 몇 층이든 올라갈 수 있습니다.
