개요
**---**
FMarkerSyncData::CollectMarkersInRange() 내부에서 loop애니메이션 wrap 대응이 되어있지 않습니다.
그래서 적절한 sync marker를 찾지 못하고, 이것이 크래시를 유발합니다.
원인
---
void FMarkerSyncData::CollectMarkersInRange(float PrevPosition, float NewPosition, TArray<FPassedMarker>& OutMarkersPassedThisTick, float TotalDeltaMove)
{
for (const auto& Marker : AuthoredSyncMarkers)
{
if (Marker.Time >= PrevPosition && Marker.Time < NewPosition) // loop 발생으로 NewPosition < PrevPosition 일 떄 가 고려되어있지 않음
{
float TimeToMarker = Marker.Time - PrevPosition;
int32 PassedMarker = OutMarkersPassedThisTick.Add(FPassedMarker());
OutMarkersPassedThisTick[PassedMarker].PassedMarkerName = Marker.MarkerName;
OutMarkersPassedThisTick[PassedMarker].DeltaTimeWhenPassed = TotalDeltaMove - TimeToMarker;
}
}
}
leader가 wrap하면(PrevPosition은 끝 근처, NewPosition은 0 근처) PrevPosition > NewPosition이 되어, 어떤 마커도 Marker.Time >= PrevPosition && Marker.Time <
NewPosition을 만족할 수 없습니다 → 통과 마커 목록이 빈 채로 반환됩니다.
반면 leader의 start/end sync position은 GetMarkerIndicesForTime / GetMarkerSyncPositionFromMarkerIndicies로 계산되며, 이들은 wrap을 처리하므로 end position은 통과한
마커를 정확히 보고합니다. 이 모순(end position은 "통과"라는데, 통과 마커 목록은 “없음”)이 follower로 전파되어 정합성 check()를 실패시킵니다.
이 버그는 bShouldMatchSyncPhases == false일 때는 드러나지 않습니다. 그 경로는 leader가 UAnimSequence::AdvanceMarkerPhaseAsLeader를 사용하는데, 이 함수는 wrap을
명시적으로 처리합니다(마커 인덱스를 0으로 되돌리고 MarkerTimeOffset = GetPlayLength()로 한 바퀴 보정). bShouldMatchSyncPhases == true 경로(= CollectMarkersInRange
사용)만 영향을 받습니다.
재현 방법
Blendspace내 bShouldMatchSyncPhases, 켜고 싱크마커를 심은다음 해당 BS가 재생되도록 합니다. ex) 걷기/달리기
루프가 변경되는 구간이 포함될 때 걷기/달리기 전환을 하면 크래시가 발생합니다.
m0nguss
(Changhyun Park)
3
안녕하세요 오원창님
전사 휴무 및 내부 시스템의 오류로 문의 주신 내용의 답변이 지체되고 있습니다.
최대한 빠르게 정리하여 답변드릴 수 있도록 하겠습니다.
너른 양해 부탁드립니다.
감사합니다.
안녕하세요.
전달해 주신 내용을 바탕으로 에디터에서 재현 조건을 구성하고 테스트를 진행하였으며, 동일한 현상을 확인하고 원인을 분석하기까지 다소 시간이 소요되었습니다.
확인 결과, UAnimSequence::AdvanceMarkerPhaseAsFollower()의 마커 정합성 assertion이 실패하여 크래시가 발생하는 것으로 보입니다.
해당 이슈는 엔진의 루프 구간 Sync Marker 수집 문제로 판단되어, 내부에 버그 리포트 제출 예정입니다.
제보해 주셔서 감사합니다.
확인 감사합니다.
저희는 최소 수정으로 다음과 같이 CollectMarkersInRange()의 매개변수를 확장하고 Prev/New Position을 기준으로 분기하여 대응해두었습니다.
// 루프 wrap(PrevPosition > NewPosition) 처리 추가
void FMarkerSyncData::CollectMarkersInRange(float PrevPosition, float NewPosition, TArray<FPassedMarker>& OutMarkersPassedThisTick, float TotalDeltaMove, float SequenceLength)
{
if (PrevPosition <= NewPosition)
{
// 전진(루프 wrap 없음): 기존 로직 그대로
for (const auto& Marker : AuthoredSyncMarkers)
{
if (Marker.Time >= PrevPosition && Marker.Time < NewPosition)
{
float TimeToMarker = Marker.Time - PrevPosition;
int32 PassedMarker = OutMarkersPassedThisTick.Add(FPassedMarker());
OutMarkersPassedThisTick[PassedMarker].PassedMarkerName = Marker.MarkerName;
OutMarkersPassedThisTick[PassedMarker].DeltaTimeWhenPassed = TotalDeltaMove - TimeToMarker;
}
}
}
else
{
// 루프 wrap(PrevPosition > NewPosition): [PrevPosition, 끝) 구간 먼저, 그 다음 되감긴 [0, NewPosition) 구간.
// 마커가 통과 순서대로 OutMarkersPassedThisTick 에 담기도록 두 구간을 순서대로 처리한다.
for (const auto& Marker : AuthoredSyncMarkers)
{
if (Marker.Time >= PrevPosition)
{
float TimeToMarker = Marker.Time - PrevPosition;
int32 PassedMarker = OutMarkersPassedThisTick.Add(FPassedMarker());
OutMarkersPassedThisTick[PassedMarker].PassedMarkerName = Marker.MarkerName;
OutMarkersPassedThisTick[PassedMarker].DeltaTimeWhenPassed = TotalDeltaMove - TimeToMarker;
}
}
for (const auto& Marker : AuthoredSyncMarkers)
{
if (Marker.Time < NewPosition)
{
// 끝까지 남은 거리 + 되감긴 후 마커까지 = 한 바퀴를 가로지른 실제 이동 거리
float TimeToMarker = (SequenceLength - PrevPosition) + Marker.Time;
int32 PassedMarker = OutMarkersPassedThisTick.Add(FPassedMarker());
OutMarkersPassedThisTick[PassedMarker].PassedMarkerName = Marker.MarkerName;
OutMarkersPassedThisTick[PassedMarker].DeltaTimeWhenPassed = TotalDeltaMove - TimeToMarker;
}
}
}
}
작성해주신 수정사항을 버그 리포트에 추가하여 제출하도록 하겠습니다.
감사합니다.
안녕하세요.
제보해 주신 내용이 버그 트래커에 등록되어 안내드립니다.
아래 링크에서 해당 이슈를 확인하실 수 있습니다.
https://issues.unrealengine.com/issue/UE-387801
감사합니다.