Master Engineering & Neuroscience

스텔스 감각 아키텍처: 동적 레이캐스트 시야 콘, 그림자 볼륨 샘플링 및 음향 감쇠 제어

By DopaBrain 인공지능 및 공간 시뮬레이션 엔지니어링 팀 • 2026-10-01
스트레스 지수 경비병 순찰 긴장감, 발각 위기 패닉 및 무음 잠입 침착성 지수 반응속도 테스트 코너 피킹 타이밍, 암살 제압 반사 및 그림자 은폐 스프린트 반응 잠복기 2048 코치 시야각 벡터 내적 미적분, 조도 역제곱 법칙 및 음향 전파 감쇠 연산 뇌 유형 테스트 유령 잠입 대 공격적 제압 전술 스텔스 인지 아키타입

로블록스의 전술 잠입, 서바이벌 호러, 비밀 침투 게임(Entry Point, The Rake 등)에서 사실적인 NPC 감각 지각은 몰입감을 결정짓는 핵심 요소입니다. 경비병이 콘크리트 벽을 뚫고 플레이어를 감지하는 전지전능한 버그가 발생하거나, 반대로 대낮의 밝은 마당을 전력 질주하는 침투자를 보고도 지나친다면 플레이어는 즉시 흥미를 잃게 됩니다.

완성도 높은 잠입 시스템을 구축하려면 시각 원뿔 계산, 물리적 그림자 차폐, 주변 조도 평가, 음향 파동 감쇠가 유기적으로 결합된 다중 모달 감각 파이프라인이 필수적입니다. 본 가이드에서는 Luau를 활용하여 내적 기반 시야각 콘, 태양광 차폐 레이캐스트, 재질별 발소리 전파, 무경계에서 교전 상태로 매끄럽게 전이되는 프로덕션급 스텔스 엔진을 단계별로 구현합니다.

1. 전지전능 감지의 한계: 단순 거리 판정이 실패하는 이유

유클리드 거리 판정(`(guard.Position - player.Position).Magnitude < range`)에만 의존하는 원시적인 감지 방식은 심각한 시스템적 결함을 초래합니다:

2. 수학적 원리: 벡터 내적 시야각, 조도 역제곱 법칙 및 음향 전파 감쇠

사실적인 잠입 감각 시뮬레이션은 벡터 기하학, 광학 역제곱 감쇠 법칙 및 구조적 음향 흡수 공식을 기반으로 동작합니다:

3. 완전한 프로덕션 스텔스 감각 컨트롤러 Luau 구현

경비병의 시야선, 그림자 은폐율, 발소리 전파를 Heartbeat 주기에서 최적화된 RaycastParams로 판정하는 Luau 모듈입니다:

StealthSensoryController.luau (시야각 콘, 조도 샘플링 및 경계 FSM)
--!strict
-- StealthSensoryController.luau
-- 로블록스 스튜디오용 프로덕션 잠입 감각 제어 엔진

local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")

export type AlertState = "Unaware" | "Suspicious" | "Alerted" | "Combat"

export type GuardSensoryConfig = {
    FieldOfViewDeg: number,
    SightRangeMax: number,
    HearingRangeMax: number,
    ShadowDetectionFactor: number, -- 그림자 은폐 시 시각 감지 배율 (예: 0.35)
    AlertBuildRate: number,        -- 초당 경계치 상승량
    AlertDecayRate: number,        -- 초당 경계치 자연 감소량
}

local StealthController = {}
StealthController.__index = StealthController

function StealthController.new(guardModel: Model, config: GuardSensoryConfig)
    local self = setmetatable({}, StealthController)
    self.Guard = guardModel
    self.Config = config
    self.Head = guardModel:WaitForChild("Head") :: BasePart
    self.Awareness = 0 -- 0 ~ 100 경계도 수치
    self.State = "Unaware" :: AlertState

    -- 충돌 필터링이 적용된 재사용 레이캐스트 파라미터
    self.RayParams = RaycastParams.new()
    self.RayParams.FilterType = RaycastFilterType.Exclude
    self.RayParams.FilterDescendantsInstances = {guardModel}
    self.RayParams.IgnoreWater = true

    return self
end

function StealthController:IsTargetInSightCone(targetPos: Vector3): (boolean, number)
    local eyePos = self.Head.Position
    local lookDir = self.Head.CFrame.LookVector
    local toTarget = targetPos - eyePos
    local distance = toTarget.Magnitude

    if distance > self.Config.SightRangeMax then
        return false, 0
    end

    local dirNormalized = toTarget.Unit
    local dotProduct = lookDir:Dot(dirNormalized)
    local minDot = math.cos(math.rad(self.Config.FieldOfViewDeg * 0.5))

    if dotProduct >= minDot then
        return true, distance
    end
    return false, distance
end

function StealthController:IsTargetInShadow(targetPart: BasePart): boolean
    -- 태양 방향 벡터 (게임 환경 설정에 맞게 동적 바인딩 가능)
    local sunDir = Vector3.new(0.3, 0.9, 0.2).Unit
    local rayOrigin = targetPart.Position + Vector3.new(0, 1, 0)
    local rayDir = sunDir * 100

    local result = Workspace:Raycast(rayOrigin, rayDir, self.RayParams)
    -- 상공에 구조물이 있어 태양광이 차단되면 그림자 볼륨 내부에 위치함
    return result ~= nil
end

function StealthController:EvaluateSensory(playerCharacter: Model, dt: number)
    local targetTorso = playerCharacter:FindFirstChild("HumanoidRootPart") :: BasePart?
    local targetHead = playerCharacter:FindFirstChild("Head") :: BasePart?
    if not targetTorso or not targetHead then return end

    local inCone, dist = self:IsTargetInSightCone(targetHead.Position)
    local canSee = false

    if inCone then
        -- 시야 차폐 검증 레이캐스트
        local eyePos = self.Head.Position
        local toHead = targetHead.Position - eyePos
        local hitResult = Workspace:Raycast(eyePos, toHead, self.RayParams)

        if hitResult and hitResult.Instance:IsDescendantOf(playerCharacter) then
            canSee = true
        end
    end

    if canSee then
        local inShadow = self:IsTargetInShadow(targetTorso)
        local lightFactor = inShadow and self.Config.ShadowDetectionFactor or 1.0
        local distanceFactor = 1.0 - math.clamp(dist / self.Config.SightRangeMax, 0, 1)

        -- 근거리이거나 밝은 조명 아래 있을수록 경계 게이지가 빠르게 상승
        local buildIncrement = self.Config.AlertBuildRate * distanceFactor * lightFactor * dt
        self.Awareness = math.clamp(self.Awareness + buildIncrement, 0, 100)
    else
        -- 시야에서 사라졌을 때 경계치 자연 감소
        self.Awareness = math.clamp(self.Awareness - (self.Config.AlertDecayRate * dt), 0, 100)
    end

    -- 상태 머신 전이 판정
    if self.Awareness >= 100 then
        self.State = "Combat"
    elseif self.Awareness >= 70 then
        self.State = "Alerted"
    elseif self.Awareness >= 25 then
        self.State = "Suspicious"
    else
        self.State = "Unaware"
    end
end

return StealthController

4. 음향 전파 및 경계 단계별 행동 전이 상태 머신

시각뿐만 아니라 소리는 은신 잠입 플레이에서 긴장감 넘치는 두뇌 싸움을 유도하는 핵심 요소입니다:

5. 모바일 최적화 및 레이캐스트 연산 부하 분산 전략

수십 명의 경비병 AI가 동작하는 대규모 잠입 맵에서 60 FPS를 유지하기 위한 최적화 기법입니다:

Frequently Asked Questions

철망 울타리나 반투명 유리창을 통해 플레이어가 감지되도록 하려면 어떻게 하나요?

RaycastParams의 필터 테이블을 조정하거나 투명도 속성을 검사합니다. 레이가 투명도 0.5 이상의 유리를 통과할 경우 광선을 완전히 차단하지 않고 감지 속도에 0.4배의 저하 계수를 적용하여 투과 시야를 구현할 수 있습니다.

레이캐스트를 쏘기 전에 왜 항상 벡터 내적을 먼저 계산해야 하나요?

레이캐스트는 3D 지형 물리 지오메트리를 탐색하는 무거운 연산입니다. 반면 벡터 내적은 3회의 곱셈으로 끝나는 초경량 연산이므로, 시야 밖 75%의 대상을 즉각 걸러내어 CPU 프레임타임을 비약적으로 절약할 수 있습니다.

순찰 중인 경비병의 손전등 불빛에 들어갔을 때의 판정은 어떻게 구현하나요?

손전등의 위치에서 플레이어 몸통으로 광선을 투사하여 스팟 조명의 투사 각도와 유효 거리 내에 있는지 검사합니다. 장애물에 가려지지 않았다면 즉시 조도 계수를 최댓값으로 설정하여 경계도를 급격히 상승시킵니다.

여러 명의 플레이어가 동시에 달릴 때 발소리 감지는 어떻게 처리하나요?

StealthController 내부에 음향 이벤트 큐를 유지하고 매 프레임 청취 반경 내에서 가장 데시벨이 높은 소리를 우선 처리합니다. 이를 통해 먼 곳의 발소리보다 가까운 곳의 총성이나 폭발 소리에 경비병이 먼저 반응하도록 제어합니다.

잠입 게임에서 가장 이상적인 경계도 자연 감소 속도는 얼마인가요?

초당 10~15포인트의 감소율을 설정할 때 플레이어가 엄폐물 뒤로 숨은 후 4~6초 내에 경비병의 의심을 풀 수 있어 가장 쾌적하고 긴장감 넘치는 잠입 루프를 제공합니다.

Explore More Interactive Tests & Guides

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

Go to Portal Hub