[2025_12-29] 유닛 떨림 현상 해결 - 유닛 회피 시스템
2025. 12. 29. 20:53ㆍTIL
문제 상황
증상
- 유닛들이 FlowField를 따라 이동하는 중 좌우로 지그재그로 떨림
- 특히 유닛 무리가 함께 이동할 때 심화
- 사용자 경험 저하 (움직임이 부자연스럽고 어지러움)
초기 가설 - "FlowField 방향 벡터가 문제일 것이다"
원인 분석
FlowField 의심
csharp
// UnitFlowDirectionDebugger로 방향 추적
[Warrior_01] #89 방향: (0.028, 1.000), 안정
[Warrior_01] #91 방향: (-0.174, 0.985), 변화 3.2도
[Warrior_01] #92 방향: (0.000, 1.000), 변화 10.0도
결과: FlowField 방향은 10도 내외의 미세한 변화 → 심각한 떨림의 원인이 아님
Local Avoidance 발견
csharp
// MoveState.cs
private float avoidanceCheckInterval = 0.08f; // <- 문제점!
public void PhysicsExecute(UnitFSM unitFSM)
{
lastAvoidanceCheck += Time.fixedDeltaTime;
if (lastAvoidanceCheck >= avoidanceCheckInterval)
{
lastAvoidanceCheck = 0f;
cachedSeparation = CalculateSeparation();
cachedLateralSpread = CalculateLateralSpread();
}
Vector3 finalDirection = (moveDirection + cachedLateralSpread + cachedSeparation).normalized;
// ...
}
- 0.08초마다 회피력 재계산 (초당 12.5회)
- 주변 유닛이 조금만 움직여도 즉시 반응
- 새로 계산된 회피력을 즉시 100% 적용
- 급격한 방향 전환 -> 떨림 발생
해결 과정
시도 1: 체크 주기 증가
csharp
private float avoidanceCheckInterval = 0.2f; // 0.08 -> 0.2초
결과:
- 떨림 감소
- 반응이 느려져서 유닛이 서성거리며 멍청해 보임
시도 2: Smoothing 도입
csharp
// 부드러운 값 저장용 변수 추가
private Vector3 smoothedSeparation = Vector3.zero;
private Vector3 smoothedLateralSpread = Vector3.zero;
private float avoidanceSmoothFactor = 0.3f; // 30%만 적용
public void PhysicsExecute(UnitFSM unitFSM)
{
lastAvoidanceCheck += Time.fixedDeltaTime;
if (lastAvoidanceCheck >= avoidanceCheckInterval)
{
lastAvoidanceCheck = 0f;
cachedSeparation = CalculateSeparation();
cachedLateralSpread = CalculateLateralSpread();
// 이전 값과 새 값을 부드럽게 보간 (Lerp)
smoothedSeparation = Vector3.Lerp(
smoothedSeparation, // 이전 값 (70%)
cachedSeparation, // 새 값 (30%)
avoidanceSmoothFactor
);
smoothedLateralSpread = Vector3.Lerp(
smoothedLateralSpread,
cachedLateralSpread,
avoidanceSmoothFactor
);
}
// smoothed 값 사용
Vector3 finalDirection = (moveDirection + smoothedLateralSpread + smoothedSeparation).normalized;
}
원리:
- 회피력이 급격히 바뀌지 않고 점진적으로 전환
- 이전 프레임과의 연속성 유지
Forward Bias 추가
csharp
private float forwardBias = 1.5f; // 전진 방향에 1.5배 가중치
public void PhysicsExecute(UnitFSM unitFSM)
{
// ... Smoothing 적용 후 ...
// 전진 방향에 가중치 추가
Vector3 biasedMoveDirection = moveDirection * forwardBias;
// 최종 방향 = 전진(가중치) + 횡분산 + 밀어내기
Vector3 finalDirection = (biasedMoveDirection + smoothedLateralSpread + smoothedSeparation).normalized;
}
효과:
- 회피보다 목표를 향해 가는 것을 우선시
- 서성거림 감소
- 더 직진성 있는 움직임
Dead Zone 적용
csharp
private float minAvoidanceForce = 0.05f;
public void PhysicsExecute(UnitFSM unitFSM)
{
// ... Smoothing 후 ...
// 너무 작은 힘은 무시
if (smoothedSeparation.magnitude < minAvoidanceForce)
{
smoothedSeparation = Vector3.zero;
}
if (smoothedLateralSpread.magnitude < minAvoidanceForce)
{
smoothedLateralSpread = Vector3.zero;
}
}
효과:
- 0.05 이하의 미세한 힘 무시
- 불필요한 미세 조정 제거
문제 발생 -> 유닛이 너무 뭉침
현상: 떨림은 해결됐지만 유닛들이 한 덩어리로 몰려다님
원인: Smoothing + Forward Bias로 회피 반응이 약해짐
시도 5: 분산력 증가
csharp
// 회피 강도 조정
private float lateralSpreadStrength = 2.0f; // 1.5 → 2.0 (33% 증가)
private float separationStrength = 1.5f; // 1.2 → 1.5 (25% 증가)
// 체크 주기도 약간 조정
private float avoidanceCheckInterval = 0.12f; // 0.2 → 0.12초
효과:
- 떨림 없음 (Smoothing + Dead Zone)
- 직진성 유지 (Forward Bias)
- 적절한 간격 유지 (강화된 분산력)
- 자연스러운 대형
핵심 개념 정리
Lerp (Linear Interpolation)
csharp
Vector3.Lerp(A, B, t)
// A와 B 사이를 t 비율로 보간
// t = 0.3 → A의 70% + B의 30%
Dead Zone
- 입력/힘이 임계값 이하면 무시
- 조이스틱, 센서 노이즈 제거에도 사용
Forward Bias
- 주 목표에 가중치를 주어 우선순위 부여
- RTS 게임의 자연스러운 움직임에 필수
결론: FlowField가 아닌 Local Avoidance의 과도한 업데이트 빈도가 문제였으며, Smoothing + Forward Bias + Dead Zone + 분산력 조정으로 해결했다.
'TIL' 카테고리의 다른 글
| [2025_12_31] Unity 게임 배속 증가 시 Projectile 충돌 감지 실패 해결 (1) | 2025.12.31 |
|---|---|
| [2025_12_30] Unity UI 이벤트 구독/해제 (0) | 2025.12.30 |
| [2025_12_26] 공격 범위 계산 버그 - 좌표 기준점 설정의 중요점 (0) | 2025.12.26 |
| [2025_12_24] 컴포지션 패턴 (0) | 2025.12.24 |
| [2025_12_23] 에셋 정하기 (0) | 2025.12.23 |