로블록스 선박 물리 아키텍처: 다중 프로브 선체 샘플링, 거스트너 파고 벡터 및 유체 동역학 저항

By DopaBrain 물리 및 유체 시뮬레이션 엔지니어링 팀 Updated: 2026-09-30 12 min read

해상 무역, 함대전, 해양 서바이벌 게임—예를 들어 Tradelands, SharkBite 및 대규모 해전 시뮬레이터—에서 현실감 넘치는 선박 물리 엔진은 게임의 몰입감을 결정하는 핵심입니다. 하지만 로블록스의 기본 지형 수면(Terrain Water)은 높이가 평평한 수평 그리드에 고정되어 있고 선체 형상에 따른 부력 조절이 불가능하며 역동적인 파도 너울을 물리적으로 구현할 수 없습니다.

최상위 로블록스 개발팀은 지형 수면 물리 대신 Luau로 자체 수학적 부력 엔진을 구축합니다. 본 가이드에서는 선체 다중 프로브(Multi-Probe) 보트 물리 시스템을 구현합니다. 해석적 거스트너(Gerstner) 파고 벡터 유도, 선체 각 지점별 아르키메데스 부력 연산, 방향성 유체 저항력(Drag) 제어 및 Workspace:GetServerTimeNow() 기반 파도 그래픽-물리 완벽 동기화 기법을 다룹니다.

1. 왜 지형 수면(Terrain Water) 대신 커스텀 부력을 사용하는가?

로블록스 지형 복셀 수면 물리는 역동적인 해양 게임 제작 시 다음과 같은 한계를 가집니다:

2. 수학적 기초: 거스트너 파도(Gerstner Waves)와 아르키메데스 원리

실제 바다 파도는 단순한 사인파가 아니며 파도의 마루(Crest)는 뾰족해지고 골(Trough)은 넓어집니다. 이를 거스트너 파도 공식으로 정밀하게 모델링합니다:

3. 프로덕션 등급 다중 프로브 선박 부력 Luau 구현

다음은 RunService.Heartbeat에서 실행되는 다중 프로브 선체 샘플링 및 거스트너 파력 연산 모듈 전체 코드입니다:

BoatBuoyancyEngine.luau (선박 물리 엔진 코어)
--!strict
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local BoatEngine = {}
BoatEngine.__index = BoatEngine

export type WaveConfig = {
    Direction: Vector2,
    Amplitude: number,
    Wavelength: number,
    Speed: number,
    Steepness: number,
}

export type ProbeAttachment = {
    Attachment: Attachment,
    DisplacementVolume: number,
    MaxDepth: number,
}

export type BoatChassis = {
    RootPart: BasePart,
    Probes: { ProbeAttachment },
    Waves: { WaveConfig },
    WaterDensity: number,
    LinearDragCoeff: number,
    AngularDragCoeff: number,
}

local GRAVITY = Workspace.Gravity

function BoatEngine.GetWaveHeight(waves: { WaveConfig }, worldPos: Vector3, timeSec: number): (number, Vector3)
    local height = 0
    local normalX = 0
    local normalZ = 0

    for _, wave in ipairs(waves) do
        local dir = wave.Direction.Unit
        local k = (2 * math.pi) / wave.Wavelength
        local c = wave.Speed
        local w = math.sqrt(GRAVITY * k)
        local dot = (dir.X * worldPos.X) + (dir.Y * worldPos.Z)
        local phase = (dot * k) - (w * timeSec)

        height += wave.Amplitude * math.sin(phase)
        local dCos = wave.Amplitude * k * math.cos(phase)
        normalX -= dir.X * dCos
        normalZ -= dir.Y * dCos
    end

    local surfaceNormal = Vector3.new(normalX, 1, normalZ).Unit
    return height, surfaceNormal
end

function BoatEngine.Step(boat: BoatChassis, dt: number)
    local root = boat.RootPart
    local currentTime = Workspace:GetServerTimeNow()

    for _, probe in ipairs(boat.Probes) do
        local probePos = probe.Attachment.WorldPosition
        local waveHeight, waveNormal = BoatEngine.GetWaveHeight(boat.Waves, probePos, currentTime)
        local immersion = math.clamp((waveHeight - probePos.Y) / probe.MaxDepth, 0, 1)

        if immersion > 0 then
            -- 아르키메데스 수직 부력 계산
            local buoyantForceMagnitude = probe.DisplacementVolume * boat.WaterDensity * GRAVITY * immersion
            local buoyantForce = waveNormal * buoyantForceMagnitude

            -- 유체 항력 댐핑
            local probeVel = root:GetVelocityAtPosition(probePos)
            local dragForce = -probeVel * (probeVel.Magnitude * 0.5 * boat.WaterDensity * boat.LinearDragCoeff * immersion)

            local totalForce = buoyantForce + dragForce
            root:ApplyImpulseAtPosition(totalForce * dt, probePos)
        end
    end

    -- 각속도 선체 회전 감쇠
    local angVel = root.AssemblyAngularVelocity
    root.AssemblyAngularVelocity = angVel * math.clamp(1 - (boat.AngularDragCoeff * dt), 0, 1)
end

return BoatEngine

4. 유체 동역학 항력, 용골(Keel) 안정성 및 타각(Rudder) 메커니즘

단순 부력만 적용하면 배는 물 위에서 공처럼 튀거나 미끄러집니다. 방향성 유체 역학이 필수적입니다:

5. 멀티플레이어 네트워크 동기화 및 클라이언트 수면 버텍스 셰이더

지연 시간이 다른 여러 클라이언트 환경에서 시각적 수면과 서버 물리 위치를 오차 없이 일치시키는 기법:

Frequently Asked Questions

왜 로블록스 바다 표현에 단순 사인파 대신 거스트너 파도를 써야 하나요?

단순 사인파는 둥근 언덕 형태만 만들어 인공적인 느낌을 줍니다. 거스트너 파도는 수평 벡터 변위를 함께 연산하여 파도의 꼭짓점을 뾰족하게 모으고 골을 넓혀 실제 해양 너울의 물리학을 정확하게 재현하기 때문입니다.

선박 물리가 뒤집히지 않고 안정되려면 몇 개의 프로브가 필요한가요?

안정적인 피치와 롤 모멘트를 생성하기 위해서는 최소 4개의 프로브(선수, 선미, 좌현, 우현)가 필요하며, 대형 전함이나 요트의 경우 6~8개의 프로브를 균등 배치하는 것이 부드러운 승선감을 제공합니다.

서버와 클라이언트 간의 파도 높이가 어긋나지 않게 맞추는 원리는 무엇인가요?

서버와 클라이언트 모두 동기화된 Workspace:GetServerTimeNow()를 시간 인자로 사용해 동일한 시드와 파라미터의 거스트너 공식을 계산하므로, 물리적 충돌과 시각적 파도 높이가 완전히 일치하게 됩니다.

Explore More Interactive Tests & Guides

Discover personalized cognitive assessments, stress evaluations, and game psychology tools on DopaBrain.

Go to Portal Hub