로블록스 유도 미사일 아키텍처: 비례 항법(PN) 유도 벡터, 3축 PID 조향 및 근접 신관 폭발

By DopaBrain 물리 및 탄도학 엔지니어링 팀 Updated: 2026-10-01 12 min read

공중전 시뮬레이터, 해전 함대 게임, SF 메카 슈팅 장르의 로블록스 게임—예를 들어 Aero Fighters, Warship Tycoon 등—에서 유도 미사일의 궤적 품질은 게임의 긴장감을 결정합니다. 단순 CFrame.lookAt(target)이나 선형 보간(Lerp)에 의존하는 초보적인 유도 로직은 이동하는 목표를 맞추지 못하고 뒤꽁무니만 쫓다가 빗나가며, 회전각이 튀어 매우 인공적인 느낌을 줍니다.

실제 공대공 미사일(AIM-9 사이드와인더, 암람)의 위협적인 포물선 요격 궤적을 구현하려면 비례 항법(Proportional Navigation, PN)과 이산 PID 컨트롤러를 결합해야 합니다. 본 가이드에서는 Luau를 활용해 프로덕션 등급의 유도 미사일 엔진을 구축합니다. 시선(Line of Sight) 회전율 도출, PID 자세 제어 토크, 추진 및 공기 항력 탄도학, 최근접점(CPA) 기반 근접 신관을 체계적으로 다룹니다.

1. 순수 추적(Pure Pursuit)의 결함: 왜 비례 항법인가?

단순히 표적의 현재 위치를 바라보며 쫓아가는 순수 추적(Pure Pursuit) 방식은 다음과 같은 치명적 물리 결함을 낳습니다:

2. 수학적 기초: 비례 항법 벡터 및 이산 PID 제어

요격 기하학은 미사일과 표적 간의 상대 운동학 벡터 방정식으로 계산됩니다:

3. 프로덕션 등급 비례 항법 유도 미사일 Luau 구현

다음은 RunService.Heartbeat에서 정확한 물리 단위로 구동되는 모듈형 유도탄 컨트롤러 전체 코드입니다:

HomingMissileEngine.luau (비례 항법 유도탄 엔진 코어)
--!strict
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local MissileEngine = {}
MissileEngine.__index = MissileEngine

export type MissileConfig = {
    RootPart: BasePart,
    TargetPart: BasePart,
    ThrustForce: number,
    MotorBurnDuration: number,
    DragCoefficient: number,
    NavConstant: number,
    MaxTurnG: number,
    ProximityRadius: number,
}

export type MissileState = {
    Config: MissileConfig,
    ElapsedTime: number,
    PreviousDistance: number,
    IsDetonated: boolean,
    RayParams: RaycastParams,
}

local GRAVITY = Vector3.new(0, -Workspace.Gravity, 0)

function MissileEngine.New(missilePart: BasePart, target: BasePart): MissileState
    local rayParams = RaycastParams.new()
    rayParams.FilterType = RaycastFilterType.Exclude
    rayParams.FilterDescendantsInstances = { missilePart.Parent or missilePart }

    local state: MissileState = {
        Config = {
            RootPart = missilePart,
            TargetPart = target,
            ThrustForce = 350.0,
            MotorBurnDuration = 3.5,
            DragCoefficient = 0.002,
            NavConstant = 4.0,
            MaxTurnG = 25.0 * 32.2,
            ProximityRadius = 10.0,
        },
        ElapsedTime = 0,
        PreviousDistance = math.huge,
        IsDetonated = false,
        RayParams = rayParams,
    }
    return state
end

function MissileEngine.Step(state: MissileState, dt: number)
    if state.IsDetonated then return end

    local root = state.Config.RootPart
    local target = state.Config.TargetPart
    local cfg = state.Config

    state.ElapsedTime += dt
    local missilePos = root.Position
    local targetPos = target.Position
    local rangeVec = targetPos - missilePos
    local distance = rangeVec.Magnitude

    -- 근접 신관 감지: 최근접점(CPA) 통과 확인
    if distance < cfg.ProximityRadius or (state.PreviousDistance < cfg.ProximityRadius and distance > state.PreviousDistance) then
        state.IsDetonated = true
        root:Destroy()
        return
    end
    state.PreviousDistance = distance

    local missileVel = root.AssemblyLinearVelocity
    local targetVel = target.AssemblyLinearVelocity
    local relVel = targetVel - missileVel

    -- 시선(LOS) 회전 각속도 계산
    local losRate = rangeVec:Cross(relVel) / (distance * distance)
    local closingSpeed = -rangeVec:Dot(relVel) / distance

    -- 비례 항법(PN) 지령 횡가속도 계산
    local navAccel = Vector3.zero
    if closingSpeed > 0 and missileVel.Magnitude > 10 then
        local missileHeading = missileVel.Unit
        local rawAccel = losRate:Cross(missileHeading) * (cfg.NavConstant * closingSpeed)
        local clampedMagnitude = math.min(rawAccel.Magnitude, cfg.MaxTurnG)
        if rawAccel.Magnitude > 0.001 then
            navAccel = rawAccel.Unit * clampedMagnitude
        end
    end

    -- 로켓 추진력 및 공기역학 항력 연산
    local isBurning = state.ElapsedTime < cfg.MotorBurnDuration
    local forwardThrust = isBurning and (root.CFrame.LookVector * cfg.ThrustForce) or Vector3.zero
    local speed = missileVel.Magnitude
    local dragForce = -missileVel * (speed * cfg.DragCoefficient)

    local totalAccel = forwardThrust + navAccel + dragForce + GRAVITY
    root.AssemblyLinearVelocity = missileVel + (totalAccel * dt)

    -- 미사일 헤딩 노즈를 속도 벡터 방향으로 정렬
    if speed > 5 then
        root.CFrame = CFrame.lookAt(missilePos, missilePos + root.AssemblyLinearVelocity)
    end
end

return MissileEngine

4. 공기역학 항력, 연료 소진 및 회피 기동 메커니즘

현실적인 미사일 시스템은 강력한 요격력과 정교한 유저 회피 플레이가 균형을 이루어야 합니다:

5. 멀티플레이어 네트워크 동기화 및 근접 폭발 연출

고속 비행 발사체를 멀티플레이어 환경에서 대역폭 낭비 없이 매끄럽게 동기화하는 구조:

Frequently Asked Questions

왜 단순 예측 사격 대신 비례 항법(Proportional Navigation)을 사용하나요?

단순 예측 사격은 표적이 등속 직선 운동을 한다고 가정하므로 표적이 방향을 꺾으면 빗나갑니다. 비례 항법은 표적의 시선 각속도 변화를 실시간으로 추적하여 선회 기동 중인 비행체도 정확히 요격하기 때문입니다.

항법 상수 N(Navigation Constant)의 크기는 미사일에 어떤 영향을 주나요?

N이 3일 때는 완만하고 에너지 효율적인 요격 궤적을 그리며, N이 5일 때는 발사 직후 표적 미래 위치로 급격히 머리를 꺾어 빠르게 요격하지만 공기 저항으로 속도가 더 빨리 줄어듭니다.

미사일이 너무 빨라 표적을 스쳐 지나가는 버그는 어떻게 막나요?

표적과의 거리가 좁혀지다가 다시 멀어지기 시작하는 순간(최근접점 CPA)을 감지하는 근접 신관(Proximity Fuze) 로직을 적용하여 직접 물리 충돌하지 않아도 치명적 반경 내에서 즉시 폭발하도록 처리합니다.

Explore More Interactive Tests & Guides

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

Go to Portal Hub