h662

메이플스토리 월드 강의 / 무한 점프맵 만들기 / 4화

무한 점프맵 만들기 4화, 무작위로 층 만들기

2026년 10월 9일

무한 점프맵 만들기 4화, 무작위로 층 만들기 완성 화면

캐릭터의 점프 거리를 재서 발판 사이 규칙을 정하고, 무작위 함수로 실행할 때마다 모양이 달라지는 층을 만듭니다. ServerOnly 를 빼 보면서 무작위처럼 결과가 달라지는 일을 왜 서버 한 곳에서 정해야 하는지도 확인합니다.

지난 글에서는 표에 적은 자리로 발판을 옮겨 계단을 만들었습니다. 이번에는 그 표를 손으로 적는 대신 규칙으로 만들고, 실행할 때마다 다른 모양의 층이 나오게 합니다. 위 그림처럼 실행할 때마다 발판 배치가 달라집니다.

점프 거리 재기

규칙을 만들려면 먼저 캐릭터가 점프로 얼마나 높이, 얼마나 멀리 가는지 알아야 합니다. 이걸 모르고 발판을 아무 데나 놓으면 아무리 잘 뛰어도 건널 수 없는 층이 생길 수 있습니다.

먼저 제자리에서 뛰면 약 1.2 만큼 올라갑니다. Workspace 에서 DefaultPlayer 를 고르면 Property 창에 RigidbodyComponent 가 보입니다. 캐릭터가 중력을 받아 떨어지고 발판 위를 걷고 뛰게 해 주는 컴포넌트로, 그 안의 WalkJump 1.23 이 점프 높이를 정하는 값입니다.

이번에는 달리다가 발판 끝에서 뛰어 봅니다.

  • 같은 높이로 내려올 때까지 가로로 약 1.2 를 날아갑니다.
  • 한 칸(0.5) 위 발판으로는 약 1 까지 건널 수 있습니다.
  • 한 칸 아래 발판으로는 약 1.3 까지 건널 수 있습니다.

하지만 이건 발판 끝에서 딱 맞춰 뛰었을 때의 이야기입니다. 실제로는 조금 일찍, 조금 늦게 뛰기도 하니 넉넉하게 절반 정도로 잡습니다. 올라갈 때 0.5, 같은 높이 0.6, 내려갈 때 0.8 까지만 발판 사이를 벌립니다.

올라갈 때, 같은 높이, 내려갈 때의 발판 사이 규칙
올라갈 때, 같은 높이, 내려갈 때의 발판 사이 규칙

무작위 (RandomIntegerRange)

이제 발판 자리를 무작위로 정합니다. 주사위를 던지듯 매번 다른 값을 가져와야 합니다. 메이플스토리 월드에는 무작위 수를 뽑는 함수 _UtilLogic:RandomIntegerRange 가 있습니다. 괄호 안에 가장 작은 수와 가장 큰 수를 적으면 그 사이의 정수 하나를 골라 주고, 양 끝의 수도 나올 수 있습니다.

RandomIntegerRange 는 가장 작은 수와 가장 큰 수 사이의 정수 하나를 고른다
RandomIntegerRange 는 가장 작은 수와 가장 큰 수 사이의 정수 하나를 고른다

시험 삼아 OnBeginPlay 맨 위에 1 부터 6 까지 하나를 뽑아 log 로 찍는 줄을 넣고 실행해 봅니다. 실행할 때마다 콘솔에 다른 숫자가 찍힙니다. 이것이 무작위입니다.

이걸 발판에 씁니다. 다음 발판은 지금보다 한 칸 내려가거나, 같은 높이이거나, 한 칸 올라갑니다. 이 셋을 -1, 0, 1 로 정하면 RandomIntegerRange(-1, 1) 로 셋 중 하나를 뽑을 수 있고, 여기에 한 칸 높이인 0.5 를 곱하면 다음 발판이 얼마나 오르내릴지가 정해집니다.

-1, 0, 1 에 0.5 를 곱해 다음 발판의 높이를 정한다
-1, 0, 1 에 0.5 를 곱해 다음 발판의 높이를 정한다

테이블을 코드로 만들기

이제 손으로 적은 표 대신 코드로 표를 만듭니다. 시험으로 넣은 주사위 줄과 손으로 적은 일곱 줄은 지우고, 첫 발판만 늘 같은 자리인 0, 0 에 둡니다. 캐릭터가 처음 서는 곳이니까요.

발판 사이 규칙은 maxGap 이라는 테이블에 담습니다. 이번에는 대괄호 안에 -1, 0, 1 을 적어 그 수를 이름표처럼 씁니다. 그러면 maxGap[1] 은 올라갈 때의 0.5, maxGap[-1] 은 내려갈 때의 0.8 이 됩니다.

나머지 여섯 발판은 반복문으로 만듭니다.

  1. for i = 2, 7 은 i 를 2 부터 7 까지 하나씩 늘려 가며 반복하라는 뜻입니다.
  2. before 는 바로 앞 발판, 표의 i - 1 번째 줄입니다.
  3. step 은 RandomIntegerRange 로 -1, 0, 1 중 하나를 뽑습니다. 첫 발판보다 아래로 내려가게 되면 step 을 1 로 바꿔 올라가게 합니다.
  4. 틈 gap 은 0.3 부터 그 높이의 최대값 사이에서 고릅니다. RandomDouble 은 0 과 1 사이의 소수를 뽑아 주니, 여기에 범위를 곱하고 0.3 을 더하면 됩니다.
  5. 새 발판 자리를 표의 i 번째 줄에 넣습니다. X 는 앞 발판에서 발판 길이 2.4 와 틈을 더한 곳, Y 는 앞 발판 높이에 step 곱하기 0.5 를 더한 곳입니다.

가장 작은 틈을 0.3 으로 둔 이유는, 그보다 가까우면 두 발판이 한 덩어리처럼 붙어 보이기 때문입니다.

틈이 0.3 보다 작으면 두 발판이 붙어 보인다
틈이 0.3 보다 작으면 두 발판이 붙어 보인다
새 발판의 X, Y 를 앞 발판에서 계산한다
새 발판의 X, Y 를 앞 발판에서 계산한다

완성된 코드는 이렇습니다.

RootDesk/MyDesk/FloorBuilder.mlua
@Component
script FloorBuilder extends Component

    @ExecSpace("ServerOnly")
    method void OnBeginPlay()
        local maxGap = { [-1] = 0.8, [0] = 0.6, [1] = 0.5 }
        local layout = { { x = 0, y = 0 } }
        for i = 2, 7 do
            local before = layout[i - 1]
            local step = _UtilLogic:RandomIntegerRange(-1, 1)
            if before.y + step * 0.5 < 0 then
                step = 1
            end
            local gap = 0.3 + _UtilLogic:RandomDouble() * (maxGap[step] - 0.3)
            layout[i] = { x = before.x + 2.4 + gap, y = before.y + step * 0.5 }
        end
        for i, spot in ipairs(layout) do
            local platform = self.Entity:GetChildByName("Platform" .. i)
            platform.TransformComponent.Position = Vector3(spot.x, spot.y, 0)
        end
    end

end

여러 번 실행해 보면 실행할 때마다 층의 모양이 달라집니다.

실행할 때마다 다른 모양으로 나오는 층
실행할 때마다 다른 모양으로 나오는 층

ServerOnly 를 빼면

지난 글에서 OnBeginPlay 에 ServerOnly 를 붙였습니다. 이걸 빼면 무슨 일이 생기는지 보면, 서버와 클라이언트가 각각 하는 일에 감을 잡을 수 있습니다.

  1. OnBeginPlay 의 Execution Space Setting 에서 사용하지 않음을 고릅니다.
  2. 발판을 옮기는 반복문 안에, 몇 번째 발판을 어디에 놓았는지 찍는 log 를 넣습니다.

실행하면 콘솔에 일곱 줄이 두 번 찍힙니다. 앞의 일곱 줄은 서버, 뒤의 일곱 줄은 클라이언트입니다. 숫자를 비교해 보면 두 번째 발판부터 자리가 서로 다릅니다. 실행 공간을 정하지 않으면 이 코드는 서버에서도 클라이언트에서도 한 번씩 돌아서, 각자 주사위를 따로 던진 것입니다.

서버와 클라이언트가 각자 만든 layout 이 서로 다르다
서버와 클라이언트가 각자 만든 layout 이 서로 다르다

그런데 화면의 발판은 서버가 정한 자리에 놓여 있습니다. 발판 자리는 서버가 정한 값이 클라이언트로 전해져 클라이언트의 값을 덮어쓰기 때문입니다. 결국 클라이언트는 쓰이지도 않을 층을 한 번 더 만든 셈이고, 클라이언트가 가진 layout 표는 서버의 것과 다릅니다. 나중에 이 표로 출구 자리 같은 것을 정하면 서버와 클라이언트가 서로 다른 곳을 가리키게 됩니다.

그래서 층은 서버 한 곳에서만 만들고 그 결과를 모두가 함께 봅니다. 이것이 ServerOnly 를 붙인 이유입니다. log 를 지우고 실행 공간을 다시 ServerOnly 로 돌려 놓습니다.

정리

  • 점프 거리를 재서 발판 사이 규칙을 정했습니다. 올라갈 때 0.5, 같은 높이 0.6, 내려갈 때 0.8 까지입니다.
  • RandomIntegerRange 와 RandomDouble 로 실행할 때마다 다른 모양의 층을 만들었습니다.
  • 무작위처럼 결과가 달라지는 일은 서버 한 곳에서 정해야 한다는 것을 ServerOnly 를 빼 보며 확인했습니다.

다음 글에서는 이 층을 위로 쌓습니다. 층 끝에 출구를 만들고, 출구에 닿으면 위층으로 올라가게 합니다.

막힌 곳이 있으면 디스코드에 몇 화의 어느 부분인지 같이 적어 주세요.