벽 달리기, 슬라이딩, 턱걸이(Ledge Mantle) 같은 고속 파쿠르 메커니즘은 현대 액션 게임의 핵심 재미 요소입니다. 기본 Humanoid의 상태만으로 이를 구현하려 하면 벽에 달라붙어 굳거나 속도가 급감하는 버그가 발생합니다.
최신 Luau 물리 제약인 `LinearVelocity`, `VectorForce`, 그리고 방향성 Raycast를 결합하면 모바일, PC, 콘솔 모두에서 부드럽게 작동하는 AAA급 커스텀 물리 컨트롤러를 제작할 수 있습니다.
1. 유연한 파쿠르를 위한 휴머노이드 상태 재정의
로블록스 스튜디오의 커스텀 이동 상태 머신 설계 원칙:
- 충돌 상태 비활성화: 벽 달리기가 시작되면 즉시
Humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)를 호출하고 자유낙하를 비활성화하여 기본 모션이 물리를 방해하지 않게 합니다. - 네트워크 소유권 보장: 클라이언트의 조작 지연을 없애기 위해 서버에서
rootPart:SetNetworkOwner(player)를 호출하여 플레이어 캐릭터의 물리 제어권을 클라이언트에 부여합니다. - 상태 머신 모듈화: 걷기, 질주, 벽 달리기, 매달리기, 넘어가기 등 상태별 진입(Enter), 업데이트(Tick), 퇴장(Exit) 이벤트를 명확히 분리합니다.
2. 방향성 레이캐스트와 표면 법선(Normal) 정렬
벽면 감지 및 최적의 달리기 벡터 산출 공식:
- 다중 레이 탐침(Multi-Raycast): 캐릭터 골반과 어깨 위치에서 좌우로 2.5~3.5 스터드 길이의 수평 레이캐스트를 발사해 벽면 충돌을 감지합니다.
- 표면 법선 검증: 충돌 지점의 법선 벡터를 분석하여 바닥에 수직인 벽면(월드 상향 벡터 대비 75°~105° 각도)인지 확인하여 완만한 경사면을 걸러냅니다.
- 진행 벡터 외적 계산: 벽면 법선과 상향 벡터의 외적(
wallNormal:Cross(Vector3.yAxis))을 계산하여 벽면 곡면을 따라 전진하는 방향 벡터를 생성합니다.
3. 현대적 물리 제약: LinearVelocity & VectorForce
구형 BodyMovers를 대체하는 최신 물리 제약 조건 활용:
- LinearVelocity를 통한 등속 이동: HumanoidRootPart에
LinearVelocity를 연결하고MaxForce를 충분히 높여 벽면 마찰을 이겨내고 일정한 추진력을 유지합니다. - VectorForce를 통한 반중력 구현: 작업 공간의 기본 중력 가속도(196.2 studs/s²)를 상쇄하기 위해 캐릭터 질량에 비례하는 상향
VectorForce를 실시간 적용합니다. - 자연스러운 체공 시간 제한: 1.5~2.5초에 걸쳐 상향력을 점진적으로 감쇠시켜 포물선 궤적으로 떨어지게 만들어 기술적인 타이밍 점프를 유도합니다.
-- 최신 제약 조건 기반 클라이언트 벽 달리기 컨트롤러
local RunService = game:GetService("RunService")
local WallRun = {}
WallRun.__index = WallRun
function WallRun.new(character)
local self = setmetatable({}, WallRun)
self.Char = character
self.RootPart = character:WaitForChild("HumanoidRootPart")
self.Humanoid = character:WaitForChild("Humanoid")
-- LinearVelocity 제약 인스턴스 생성
local att = Instance.new("Attachment", self.RootPart)
local lv = Instance.new("LinearVelocity")
lv.Attachment0 = att
lv.MaxForce = 15000
lv.RelativeTo = Enum.ActuatorRelativeTo.World
lv.Enabled = false
lv.Parent = self.RootPart
self.Attachment = att
self.LinearVelocity = lv
self.IsRunning = false
return self
end
function WallRun:CheckWall(side)
local rightVec = self.RootPart.CFrame.RightVector
local rayDir = (side == "Left" and -rightVec or rightVec) * 3.5
local params = RaycastParams.new()
params.FilterDescendantsInstances = {self.Char}
params.FilterType = Enum.RaycastFilterType.Exclude
return workspace:Raycast(self.RootPart.Position, rayDir, params)
end
function WallRun:Start(hitNormal, forwardDir)
self.IsRunning = true
self.LinearVelocity.VectorVelocity = forwardDir * 32 + Vector3.new(0, 4, 0)
self.LinearVelocity.Enabled = true
self.Humanoid:ChangeState(Enum.HumanoidStateType.Physics)
end
function WallRun:Stop()
self.IsRunning = false
self.LinearVelocity.Enabled = false
self.Humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
end
return WallRun
4. 절차적 모서리 감지 및 턱걸이(Mantle) 전환
모서리 잡기와 부드러운 장애물 극복 애니메이션 연동:
- 2단계 모서리 감지: 가슴 높이에서 전방으로 발사하는 레이(벽 존재 확인)와, 벽 위쪽에서 아래로 발사하는 수직 레이(상단 모서리 높이 계산)를 조합합니다.
- 위치 스냅 및 고정: 모서리 감지 시
AlignPosition제약을 사용하여 캐릭터를 모서리에 정확히 스냅시키고 매달리기 상태로 전환합니다. - 베지에 곡선 턱걸이: 점프 입력 시 턱걸이 애니메이션과 함께 2차 베지에 곡선을 활용해 캐릭터 RootPart를 부드럽게 위로 올린 뒤 전방으로 이동시킵니다.
5. 모바일 터치 최적화와 서버 유효성 검증
멀티 플랫폼 지원과 핵 방지를 동시에 달성하는 보안 설계:
- ContextActionService 터치 바인딩: 벽 점프 및 넘어가기 입력을 화면상 최소 48x48px 크기의 명확한 터치 버튼에 매핑합니다.
- 서버 속도 제한 검증: 클라이언트 복제 틱 간 이동 거리를 계산하여 벽 달리기 상태가 아닐 때 비정상적인 초고속 이동을 차단합니다.
- 벽면 존재 검증: 클라이언트가 벽 달리기를 보고한 시점에 실제로 플레이어 주변 4스터드 이내에 물리적인 벽이 존재했는지 서버가 레이캐스트로 재검증합니다.
Frequently Asked Questions
로블록스에서 BodyVelocity 대신 LinearVelocity를 써야 하는 이유는 무엇인가요?
BodyVelocity는 로블록스에서 사용 중단(Deprecated)된 구형 인스턴스입니다. LinearVelocity는 차세대 물리 제약 엔진으로 질량 계산이 정확하고 멀티플레이어 환경에서 훨씬 안정적입니다.
벽을 달릴 때 캐릭터가 아래로 떨어지지 않게 하려면 어떻게 하나요?
중력(기본 196.2)을 상쇄하는 상향 힘을 VectorForce나 LinearVelocity의 Y축 값으로 인가합니다. 게임 밸런스를 위해 1.5~2초 후 상향력을 서서히 줄여 낙하하도록 설계합니다.
벽 달리기 방향 벡터는 어떻게 계산하나요?
Raycast로 얻은 벽면 충돌 법선 벡터(Normal)와 월드 상향 벡터(0, 1, 0)의 외적(Cross Product)을 구하면 벽면과 평행한 수평 전진 벡터를 산출할 수 있습니다.
서버에서 벽 달리기 핵(Speed/Fly Hack)을 어떻게 방지하나요?
서버가 플레이어 위치 주변에 실제 유효한 벽면이 있는지 자체 Raycast로 확인하고, 벽 달리기 상태가 아닐 때의 최고 이동 속도를 엄격하게 제한합니다.