걸음의 기준 속도와 감속 원인을 따로 둔다
DCB의 걷기·달리기·질주는 이동 상태가 기준 속도를 고른다. 같은 달리기라도 인벤토리 무게가 운반 한도에 가까운지, 갈증이 낮은지, 얼마나 깊은 물을 지나고 있는지에 따라 다른 배율이 적용된다.
현재 구조는 원인마다 최종 속도를 덮어쓰는 방식이 아니다. 상태 기준 속도에 원인별 배율을 곱해 스탯 속도를 구하고, 이동 컨트롤러가 가감속과 방향·경사 등의 조건을 추가로 처리한다. 물에서 나왔다고 갈증이나 무게의 보정까지 지우면 안 되고, 걸음이 바뀌었다고 기존 보정이 사라져서도 안 된다.
2026년 10월 19일 DCB의 인벤토리·속성·플레이어 스탯·이동 컨트롤러·갈증·물 접촉 계산과 프리팹, 관련 커밋을 대조했다. 글 날짜는 물속 감속을 기존 속도 수정자에 연결한 9월 26일 작업의 작성일이다. 이번에는 게임 코드나 설정을 바꾸지 않았고, Unity 컴파일·검사·Play Mode와 실제 이동 시간 측정도 새로 하지 않았다.
원인별 키로 등록한 배율을 곱한다
PlayerStat은 기준 속도와 최종 속도를 나누어 보관한다. 상태머신이 MoveSpeed에 값을 넣으면 기준값을 바꾸고 현재 수정자를 다시 계산한다. SetMoveSpeedModifier()는 원인을 키로 사용해 보정값을 저장하며, RecalculateMoveSpeed()가 다음 값을 만든다.
스탯 이동 속도 = 상태 기준 속도
× 무게 배율
× 갈증 배율
× 현재 적용된 물 배율
× 지게 적재 배율
각 배율 = 1 + 해당 키의 보정값
| 수정자 키 | 배율을 결정하는 값 | 등록·해제 책임 |
|---|---|---|
WeightMoveSpeed | 인벤토리 무게 ÷ 플레이어 운반 한도의 곡선 | 속성 컨트롤러의 초기 계산과 무게·한도 변경 리스너 |
ThirstPenalty | 현재 갈증 단계의 속도 보정 | 갈증 시스템이 단계에 맞게 등록·해제 |
WaterWading | 수심·걸음·이동 방향·유속으로 구한 배율 | 물속 감속 컴포넌트가 평활해 갱신·해제 |
JigeCargoWeight | 지게 적재율의 속도 곡선 | 지게 부담 갱신이 적용·제거. 현재 곡선은 배율 1 |
예를 들어 20% 감속은 보정값 -0.2, 적용 배율 0.8이다. 계산 결과가 배율로 나오는 무게·물 코드에서는 배율 - 1을 등록한다. 이 변환 없이 0.8을 그대로 보정값에 넣으면 1.8배로 해석된다.
같은 키에 다시 등록하면 그 원인의 값을 교체한다. 반복 호출마다 같은 감속이 누적되는 구조는 아니다. RemoveMoveSpeedModifier(WaterWading)는 물 항목만 없애고 무게·갈증은 유지한다. 기준 속도를 걷기에서 달리기로 바꿔도 같은 수정자들을 새 기준에 곱한다.
고정된 배율끼리는 곱셈 순서가 결과를 바꾸지 않는다. 다만 실제로는 무게 변경 이벤트, 갈증의 고정 스텝 갱신, 물의 Update가 서로 다른 시점에 값을 바꾼다. 여러 조건이 같은 화면 프레임에 바뀌었을 때 중간값이 언제 이동 갱신에 반영되는지는 별도 실행 확인이 필요하다.
인벤토리 무게는 한도 전후의 곡선으로 계산한다
PlayerInventoryInfo.GetCurrentWeight()는 일반 아이템 목록과 별도 보관 목록인 DummyItemUIInfos를 순회한다. 일반 항목은 아이템 단위 무게에 개수를 곱하고, 조합 항목은 파츠 트리를 순회해 무게를 합친다. 목록 변경 경로는 이 값을 RuntimeStat.Weight에 넣는다. 이름이나 주석만 보고 캐릭터의 모든 장비·지게 화물을 같은 무게로 계산한다고 가정하지 않았다.
속성 컨트롤러는 현재 무게와 PlayerStat.WeightCapacity를 WeightMoveSpeedCalculator에 전달한다. 무게가 바뀌거나 운반 한도가 바뀌면 다시 계산해 RuntimeStat.WeightMoveSpeed에 넣고, 그 결과를 PlayerStat의 무게 수정자에 연결한다. 두 곳에 같은 배율이 있다고 이를 두 번 곱하는 것은 아니다.
코드 기본값과 실제 프리팹의 곡률이 다르다
계산기의 코드 기본값은 하한 mMin=0.35, 가벼움 보너스 mMax=1.10, 한도 이하 곡률 p=6, 초과 곡률 h=12다. 하지만 현재 PlayerAttributeController 프리팹은 p=20, h=20을 저장한다. 계산기는 해당 컴포넌트를 참조한다. 현재 곡선을 설명할 때 6·12를 그대로 사용하면 한도 부근의 변화량이 달라진다.
양의 운반 한도를 전제로 무게 비율을 r = 현재 무게 ÷ 운반 한도라고 하면, 현재 저장값의 두 곡률이 같아 식을 다음처럼 정리할 수 있다.
현재 프리팹의 무게 배율 = 0.35 + 0.75 ÷ (1 + r^20 ÷ 6.5)
이는 현재 매개변수로 원래 계산식을 정리한 것이며 새로운 게임 코드를 작성한 예시는 아니다. 아래는 같은 식을 별도로 계산한 결과다. 실제 플레이나 Unity 함수 실행의 측정값은 아니다.
| 무게 ÷ 운반 한도 | 현재 저장 곡선의 배율, 약 |
|---|---|
| 0 | 1.100 |
| 0.8 | 1.099 |
| 1.0 | 1.000 |
| 1.1 | 0.719 |
| 1.2 | 0.459 |
| 1.5 | 0.351 |
한도 이하는 가벼움 보너스가 남고, 한도를 넘으면 곡선이 빠르게 낮아져 하한 0.35에 가까워지는 설정이다. r=1에서는 배율 1이다. 운반 한도가 0에 가까운 예외에서는 계산기가 배율 1을 반환한다. 무게 비율을 계산할 수 없다고 즉시 이동 불가로 만드는 처리는 아니다.
배고픔은 무게 배율에 간접적으로 영향을 준다
현재 배고픔이 10 이하이면 플레이어 운반 한도에 ×0.7을 적용한다. 물건을 더 줍지 않았어도 같은 무게를 더 작은 한도로 나누므로 무게 배율이 달라질 수 있다. 속성 컨트롤러가 한도 변경에도 리스너를 두는 이유와 연결된다.
예를 들어 가상의 무게 80·기존 한도 100은 비율 0.8, 배율 약 1.099다. 한도가 70이 되면 비율 약 1.143, 배율 약 0.583으로 바뀐다. 이는 현재 곡선을 적용한 계산 예다. 배고픔의 운반 한도 보정과 지게 화물의 적재 용량은 서로 다른 값이다.
초기 생성의 시트 값·능력치 보정 순서, 아이템 추가·삭제·장착 경로가 모두 원하는 시점에 변경 이벤트를 보내는지는 현재 Play Mode에서 다시 확인하지 않았다. 무게 합산 함수와 해당 리스너가 있다는 사실을 모든 인벤토리 조작의 검증 완료로 사용하지 않는다.
갈증은 별도 키로 0.8배를 적용한다
현재 카탈로그의 갈증 2단계 문턱은 현재 수치 10 이하다. 최대치의 10%가 아니다. 이 단계와 0에서 ThirstSystem.ApplyPhase2Penalty()가 ThirstPenalty=-0.2를 등록한다. 문턱을 넘겨 회복하면 해당 수정자를 제거한다.
이 보정은 무게 곡선을 대체하지 않는다. 무게 배율이 0.719인 상태에서 갈증 배율 0.8을 적용하면 두 값을 곱한다. 배고픔은 한도를 통해 무게 곡선에 간접적으로, 갈증은 이동 속도 수정자로 직접 들어간다는 차이가 있다.
갈증 25 이하의 회복 시간 패널티는 별도 스태미너 회복 보정이다. 이를 이동 속도에 또 곱하지 않는다. 자원별 전체 보정과 해제 조건은 행동 비용·회복과 배고픔·갈증에서 설명한다.
물속 감속은 등록된 수면의 수심·물살을 읽는다
PlayerWadingSlowdown은 발 역할을 하는 플레이어 루트 위치에서 WaterWakeBodyRegistry.TryGetWadeContact()를 조회한다. 등록된 사용 가능한 수역의 범위·표면 샘플을 확인하고, 흐르는 수역은 젖은 정도도 확인한다. 반환한 수면 높이와 발 위치의 차이가 수심이 된다.
물처럼 보이는 셰이더가 있다는 이유만으로 이 감속이 자동 적용되는 것은 아니다. 물결 시스템의 수역 등록과 표면 샘플이 필요하다. 감속 컴포넌트는 VFX_MudWater_Footstep 프리팹에 있고, Wanderer 프리팹이 이를 자식 프리팹으로 참조한다. 저장된 설정은 물살 적용 켜짐, 응답 시상수 0.25초다. 현재 실행 중인 씬의 모든 물과 등록 상태까지 검증한 것은 아니다.
수심과 걸음에 따라 목표 물 배율을 구한다
WadingSlowdown은 수심 0.02m 이하를 감속 없는 조건으로 취급한다. 그보다 깊으면 캐릭터의 측정된 무릎 높이로 수심을 환산한다. 측정값이 없으면 기본 무릎 높이 0.62m를 사용하며, 계산 함수의 기준 무릎 높이는 0.6m다.
정지한 물의 수심 곡선은 걷기와 달리기를 다르게 계산한다. MoveVectorWeight 0.5는 걷기 쪽, 1 이상은 달리기 쪽이며 질주도 달리기 쪽으로 제한한다. 물살은 이동 방향과 유속의 내적·가로 성분으로 거슬러 오름·따라감·가로지름을 나누어 추가 배율을 만든다. 방향은 실제 수평 속도를 우선하고 충분히 움직이지 않으면 카메라 기준 입력 방향을 사용한다.
순수 계산의 최종 물 배율은 수심 배율 × 물살 배율을 0.33~1.10으로 제한한다. 0.33은 물 배율의 하한이며 전체 이동 속도의 하한이 아니다. 예를 들어 무게 배율이 0.35에 가깝고 갈증 0.8, 물 0.33이 겹치면 곱은 약 0.0924다. 다른 조건을 제외해도 상태 기준의 약 9.24%까지 낮아지는 계산이다. 현재 플레이에서 그 조합의 이동·발 접지를 측정한 값은 아니다.
목표 물 배율과 지금 적용된 배율을 구분한다
컴포넌트는 수심이 흔들릴 때 속도가 바로 튀지 않도록 목표를 평활한다. 계속 적용 중일 때의 보간 계수는 다음과 같다.
보간 계수 = 1 - exp(-deltaTime / responseSeconds)
적용 후보 = 이전 값에서 목표 값으로 위 계수만큼 보간
시상수 0.25초는 그 시간이 지나면 반드시 목표에 도달한다는 뜻이 아니다. 첫 적용과 한 프레임의 루트 이동이 1m를 넘는 경우에는 목표를 바로 사용한다. 배율 변화가 0.001을 넘을 때 수정자에 다시 쓰는 조건도 있다. 따라서 같은 순간의 TargetFactor와 AppliedFactor가 다를 수 있다.
물 밖에서는 목표가 1이 되고, 기존 감속이 남아 있으면 평활하며 풀린다. 목표와 적용 후보가 모두 0.999 이상이면 물 수정자를 제거한다. 반면 사망·데이터 미준비·비활성이나 적용 대상 스탯 교체 같은 정리 경로에서는 이전 스탯의 물 항목을 제거한다. 물 밖 이동과 비활성 정리를 모두 같은 즉시 해제로 설명하지 않는다.
순수 함수에는 1.10 상한이 있지만 플레이어 적용기는 위 조건에서 물 수정자를 없앤다. 이 때문에 현재 캐릭터가 물살을 타면 무조건 최대 10% 가속한다고 쓰지 않았다. 약한 물살이 수심 감속을 일부 줄이는 계산과 실제 플레이어에게 1보다 큰 배율이 유지되는 결과를 구분해야 한다.
중첩 계산 예: 같은 기준 속도에서 무엇이 줄었는가
다음 표는 지게 달리기 기준 4.19m/s에, 가상의 무게 110·한도 100과 적용 중인 물 배율 0.6을 넣은 예다. 무게 배율은 현재 프리팹 곡선에서 약 0.71855다. 물 배율 0.6은 특정 수역을 새로 측정하거나 특정 수심의 결과라고 지정한 값이 아니다. 지게 적재 배율은 현재 저장값 1로 두었다.
| 계산 단계 | 배율 적용 | 스탯 속도, 약 |
|---|---|---|
| 상태 기준만 | 4.19 | 4.190m/s |
| 무게 포함 | 4.19 × 0.71855 | 3.011m/s |
| 갈증 10 이하 포함 | 위 값 × 0.8 | 2.409m/s |
| 적용 물 배율 포함 | 위 값 × 0.6 | 1.445m/s |
최종 배율은 약 0.3449다. 감속률들을 단순히 더해 빼는 계산과 다르다. 물 항목이 제거되면 이 예의 무게·갈증 조건이 그대로인 동안 약 2.409m/s로 돌아간다. 4.19로 모두 복구되는 것이 아니다.
지게를 내려놓으면 기준 속도와 걸음 규칙이 바뀌고 적재 보정도 정리된다. 이 역시 무게·갈증·물 보정까지 일괄 초기화하는 동작으로 설명할 수 없다. 현재 적재 곡선이 배율 1인 이유와 별도의 운반 계산은 지게 운반의 속도·스태미너에서 연결된다.
스탯 속도와 실제 이동 결과는 다르다
PlayerController.CalcFinalMoveSpeed()는 최종 스탯 속도를 음수 아닌 값으로 읽는다. 이동 입력이 있으면 이를 목표로 가감속 램프를 갱신하고, 조준·락온 방향, 가드·패리 배율, 입력 크기와 경사 처리를 거쳐 Rigidbody 속도를 만든다. 움직이는 표면의 속도도 별도로 더해진다.
flowchart TD
A[착용 상태와 걸음의 기준 속도] --> B[무게·갈증·물·적재 배율 곱]
B --> C[가감속 램프와 입력·행동·경사 처리]
C --> D[Rigidbody 이동과 충돌·표면 이동]
D --> E[실제 변위 기반 보행 속도와 애니메이션]
MeasuredGroundSpeed는 실제 위치 차이에서 표면에 실려 간 몫을 빼고 접지 평면에 투영한 뒤 시간으로 나누어 평활한다. 스탯 속도나 ResultMoveSpeed의 명령 벡터 크기와 같은 값은 아니다. 가속 중이거나 벽에 막힌 상태를 포함해 스탯 숫자만으로 이동 시간을 확정하면 안 된다.
물 수정자는 일반 보행 속도 경로에 들어간다. 구르기·회피·공격의 루트 모션 변위에 같은 물 배율을 곱하는 구조는 아니다. 물속에서 모든 행동의 이동 거리가 같은 비율로 줄어든다고 설명하지 않는다.
물 감속을 추가할 때 이동 소음도 별도로 처리했다. 컨트롤러는 소음 계산 전에 ResultMoveSpeed를 적용된 물 배율로 나누어 물 감속 영향을 되돌리는 경로를 둔다. 물에서 느려진 만큼 조용해지는 결과를 그대로 사용하지 않으려는 변경이다. 이 경로가 물 감속과 가감속·충돌을 모두 완전히 분리하는 측정이라는 뜻은 아니며, 실제 소음과 몬스터 반응은 다시 확인할 항목이다.
커밋에서 확인한 연결 과정
| 작업 작성일·커밋 | 확인한 변경 | 현재 설명에서 구분할 점 |
|---|---|---|
2025-09-12 · 74673a3e2 | 무게 계산기를 초기화·무게·한도 변경에 연결 | 무게 곡선과 변경 이벤트의 출발점 |
2026-04-05 · 7682ce084 | 갈증 속도 보정과 원인별 곱셈 구조, 무게 배율의 스탯 연결 | 계산한 무게 비율을 실제 이동 속도에 반영한 경로 |
2026-09-26 · b07aab272 | 물 배율을 별도 키로 추가, 물 접촉·평활·해제와 소음 보정 | 기존 무게·갈증을 덮어쓰지 않는 확장. 이 글의 대표 작업 |
2026-10-01 · 0fe68c0b4 | 지게 적재 속도 곡선을 별도 수정자로 연결 | 현재 곡선 배율 1과 지원 구조를 구분 |
물 감속 커밋에는 당시 EditMode 검사와 수역별 이동·소음 확인의 통과 기록이 있다. 이는 해당 시점의 환경·속도·입력 조건 기록이며 최신 전체 중첩의 실행 결과가 아니다. 원본 실행 보고서를 새로 확보해 대조한 것은 아니고, 이번에는 커밋·현재 소스·설정과 검사 코드를 확인했다.
실행 검증이 남은 부분
기존 WadingSlowdownTests는 수심·방향·걸음별 곡선과 물 배율 하한 등을 검사한다. WaterWakeAssetWiringTests는 발걸음 프리팹의 감속·물결 컴포넌트 연결을 검사한다. 가감속과 보행 애니메이션의 계산 검사도 별도로 있다. 이번에는 이 Unity 검사들을 실행하지 않았다.
특히 물 테스트에 남은 과거 걷기·달리기 속도나 물 하한 검사를 현재의 모든 감속 중첩·애니메이션 안정성 근거로 사용하지 않았다. 물 하나의 하한을 검증해도 무게·갈증이 더 곱해지는 조합의 발 미끄러짐까지 입증하지는 않는다. 다음은 미실행 확인 계획이다.
| 비교 조건 | 함께 기록할 내용 | 현재 상태 |
|---|---|---|
| 같은 걸음에서 무게 0·한도 전후·초과 | 실제 합산 무게·한도·저장 곡률, 등록 배율과 스탯 속도 | 이벤트·초기 생성 실행 미검증 |
| 같은 짐에서 배고픔 10 전후 | 한도 축소와 무게 배율 재계산·과적 조건 | 간접 보정 실행 미검증 |
| 갈증 10 전후와 문턱을 넘는 보충 | 갈증 키의 등록·해제, 다른 수정자 유지 | 단계 전환 실행 미검증 |
| 같은 수역의 깊이·걸음·물살 방향 변경 | 등록된 표면, 수심·유속·무릎 높이, 목표/적용 물 배율 | 수역별 실행 미검증 |
| 입수·출수·사망·비활성·데이터 교체 | 평활 중 값·물 항목 제거·이전 스탯 정리 | 수명·해제 실행 미검증 |
| 무게·갈증·물 동시 적용과 하나씩 해제 | 네 수정자와 기준 속도, 갱신 시점·이동/입력 값 | 중첩 실행 미검증 |
| 평지·경사·벽·움직이는 다리, 보행·구르기 | 목표·램프·실제 변위·표면 속도, 보행 화면과 소음·몬스터 반응 | 이동·체감·애니메이션 실행 미검증 |
비교 기록에는 코드 ref, 적용 프리팹·카탈로그·수역, 시작 자원·아이템·적재, 같은 동선·입력·시간 조건을 남긴다. 밸런싱 도구의 상태·실측 속도·자원 표시를 활용하되, 기존 창이 원인별 모든 중간값과 이벤트 시각까지 기록해 주는 것은 아니다.
담당 범위와 관련 글
이 글에서 다룬 범위는 원인별 속도 보정, 무게·운반 한도 변경의 연결, 물 감속의 등록·해제와 보행·소음 경로다. 물 감속과 지게 부담 커밋에는 Claude 공동 작성 기록이 있다. AI 보조를 받은 구현과 실제 확인 근거를 구분했고, 이번 글 조사에서 새로운 게임 수정이나 물리·이동 성능 개선을 수행했다고 쓰지 않았다.
걸음 선택과 스태미너 고갈 후 입력 처리는 이동 전환·재입력, 자원 단계와 회복은 행동 비용과 회복에서 이어진다. 다음 행동 중 이동·입력 제약 분석은 게임플레이 밸런싱과 제어 목록에 연결한다.
DCB 개발 기록
- 01IK 트레버설 구현: StepUp, Climb, SideStep
- 02Samurai의 그룹 전투: 포위·압박·공격 패턴
- 032D·3D 장비 파츠 조합 구현
- 04물 캐시 재생과 메시 업로드
- 05불 이펙트의 레이어와 품질 단계
- 06AI를 사용한 구현과 검증 과정
- 07지게 운반의 이동 속도와 스태미너 계산 구조
- 08병렬 AI 세션을 위한 공통 지침과 Git 워크트리 운영
- 09Unity Behavior 그래프를 코드 기반 FSM·DSL로 바꾼 과정
- 10장비 조합 UI의 불티·연기와 코일 연출
- 11DCB의 장애물 이동: 턱 오르기·벽 타기·옆걸음
- 12DCB의 장비 조합: 파츠 선택부터 완성 무기 보관까지
- 13DCB의 급류: 수면·포말·물보라를 함께 재생하는 방식
- 14DCB의 환경 화염: 모양·연기·잔불과 화면 크기별 표현
- 15몬스터 행동 DSL·그룹 제어의 문제 해결 과정
- 16DCB의 시네마틱: 구간 진입에서 매복 전투로 이어지는 흐름
- 17지게 짐의 출렁임: 받침 가속도와 감쇠 스프링으로 만든 2차 운동
- 18밧줄 다리의 물리 흔들림: 발 접지·바람과 충돌면·플레이어 이동 연결
- 19DCB의 게임플레이 밸런싱: 이동·스태미너·행동 제어
- 20플레이어 밸런싱 도구 제작: 값 편집·결과 예측·플레이 중 확인
- 21걷기·달리기·질주 전환과 스태미너 고갈 후 재입력 제어
- 22공격·구르기·패리의 스태미너 비용과 회복 지연·재개
- 23인벤토리 무게·갈증·물속 감속이 이동 속도에 중첩되는 방식