머리가 지나온 거리를 기록해
긴 몸통의 간격을 지킵니다
각 세그먼트는 머리 뒤의 누적 거리에서 자신의 위치를 찾고 앞뒤 소켓 방향에 맞춰 정렬됩니다.

몸통은 머리 뒤의 누적 거리에서 위치를 찾습니다
문제와 해결 방식
속도에 따라 간격이 흔들렸습니다
프레임 위치를 순서대로 따라가면 속도 변화와 프레임 간격에 따라 몸통이 늘어나거나 겹쳤습니다.
거리로 경로를 기록했습니다
머리의 이동 거리가 기준을 넘을 때만 샘플을 남기고, 각 세그먼트는 자신의 누적 거리에서 위치를 찾습니다.
Transform 연쇄 보간
긴 체인에서 오차가 누적되고, 큰 위치 보정이 뒤쪽까지 그대로 전파돼 사용하지 않았습니다.
- 01조향 입력수동·자동 방향
- 02머리 이동속도·넉백
- 03경로 기록최소 거리 검사
- 04빈 구간 복원제한된 직선 샘플
- 05몸통 배치누적 거리·소켓
- 06궤도 갱신체인 길이 반영
자동 궤도마지막 세그먼트까지의 경로 길이와 넥서스 Collider 반경을 비교해 필요한 회전 공간을 구합니다.
코드에서 처리한 내용
- 정상 이동은 최소 간격을 넘긴 위치만 경로에 추가합니다.
- 넉백이나 위치 보정이 만든 큰 간격은 제한된 수의 중간 샘플로 채웁니다.
- 몸통 길이를 원둘레로 환산하고 넥서스 반경과 조작 여유를 더합니다.
- 길이 변화가 임계값을 넘으면 현재 위치에서 새 자동 궤도로 다시 진입합니다.
각 세그먼트는 머리에서 떨어진 누적 거리에 해당하는 경로 위치를 읽습니다.
큰 위치 차이를 중간 샘플로 채웁니다
비정상 간격을 한 점으로 남기면 꼬리가 그 지점을 통과할 때 순간이동합니다. RepairedPathSampleStep 간격으로 중간 지점을 만들고 MaxRepairedPathSamples로 한 번의 작업량을 제한했습니다.
몸통이 길어지면 궤도도 넓어집니다
체인 길이를 원둘레로 환산한 반경, 넥서스 충돌체 반경, 최소 조작 반경 중 가장 큰 값을 사용합니다.
직선 샘플로 빈 구간을 채웁니다
직선 보정은 꼬리의 순간이동을 막는 용도입니다. 이동 직전의 곡선 정보는 남기지 않습니다.
긴 체인의 경로 조회 비용
세그먼트 수가 늘어나는 구간에서는 경로 검색 횟수와 오래된 샘플 정리 비용을 계측 대상으로 둡니다.
관련 코드
큰 위치 차이를 연속 경로로 복원ConvoyController.Path.cs
private void AddPathSample(
Vector3 last, Vector3 anchorPosition, float sampleDistance)
{
if (!ShouldRepairAbnormalPathSample(sampleDistance))
{
path.Add(anchorPosition);
return;
}
AddRepairedStraightPathSamples(
last, anchorPosition, sampleDistance);
}
private void AddRepairedStraightPathSamples(
Vector3 last, Vector3 anchorPosition, float sampleDistance)
{
float step = Mathf.Max(
MinPathSampleDistance, RepairedPathSampleStep);
int count = Mathf.Clamp(
Mathf.CeilToInt(sampleDistance / step),
1,
Mathf.Max(1, MaxRepairedPathSamples));
for (int i = 1; i <= count; i++)
path.Add(Vector3.Lerp(last, anchorPosition, (float)i / count));
}몸통 길이에 맞춰 자동 궤도 반경 계산ConvoyController.AutoOrbit.cs
private float CalculateAutoOrbitRadius(Transform center)
{
float spacing = Mathf.Max(0.1f, SegmentSpacing);
float chainLength = segments.Count > 0
? GetSegmentDistanceBehindHead(segments.Count - 1)
: 0f;
float bufferedLength = chainLength
+ spacing * (1 + Mathf.Max(0, AutoOrbitExtraSegmentBuffer));
float tailSafeRadius = bufferedLength / (Mathf.PI * 2f);
float nexusSafeRadius = GetNexusHorizontalRadius(center)
+ Mathf.Max(0f, AutoOrbitNexusClearance);
float baseRadius = Mathf.Max(
AutoOrbitMinimumRadius, tailSafeRadius, nexusSafeRadius);
return baseRadius + Mathf.Max(0f, AutoOrbitComfortRadiusBonus);
}

