[ 메모 ]
[ WIP : 해당 게시글은 아직 연구중인 주제이므로 내용의 신뢰를 보장할 수 없고 임의 추가/삭제될 예정입니다. ]
[ Overview ]
FPS 게임이나 액션 게임에서, 캐릭터의 특정 애니메이션 프레임에서 공격 스킬이 실행되어야 하거나, 캐릭터의 히트박스를 기반으로 정교하게 피격 판정을 해야하는 경우가 있다.
이 경우 피격 판정 당시의 캐릭터의 히트박스의 정보나, Montage의 AnimNotify를 기반으로 자주 판정하게 된다.
그런데 언리얼엔진에선, 기본적으로 Server에서 AnimNotify 호출 또는 애니메이션의 업데이트가 원활하지 못하다.
서버 입장에선 매 틱마다 캐릭터들의 본의 위치를 정교하게 업데이트 하거나 해당 프레임에서 AnimNotify를 호출하는 것이 비싼 연산이기 때문이다.
개인 프로젝트에선, 해당 해결법으로 'Visibility Based Anim Tick Option'을 'Always Tick Pose and Refresh' 로 바꾼다거나, 'AnimNotify Event'의 'Montage Tick Type'을 'Queued'에서 'Branching Point'로 변경하는 방식으로 일단 작동되게 하지만, 근본적인 해결책이 되지 못한다.
특히 전자의 경우에는 서버에 CPU intensive한 작업이 될 것이다.
AnimNotify의 문제를 넘어서, 피격 판정 동기화에도 서버측 Animation 문제는 여전히 존재한다.
클라이언트가 공격을 했을 때 Server RPC로 서버에 공격 명령을 보낸다면 Client가 봤던 애니메이션이 아닐 가능성이 높다. 그러므로 피격 판정을 할 때 시간 X에서 Client가 봤던 애니메이션을 임의의 시간이 흐른 후, Server에서 동일하게 재실행해야한다는 문제로 바꿔 생각할 수 있다.
그렇기 때문에 몇몇 상용화된 게임의 경우 Animation Update를 최대한 피하고, 피격 판정 또는 AnimNotify의 호출이 필요한 시점에만 일시적으로 업데이트하거나 호출하는 방법을 사용할 가능성이 높다.
하지만 이 방식을 사용하려면 Animation의 작동 방식과 엔진코드 분석이 필수적으로 선행되어야 할 것이다. Animation의 기본적인 작동 방식 분석후 다양한 Approach들을 살펴보고, 게임 장르에 따라 어떤 방식을 사용할 수 있을지 기록할 예정이다.
[ Approach 1 : Unreal Source's Q&A - AnimNotify in server ]
A : I worked on an FPS. We saved the animation timings offline so we didn't have to run the animation blueprint on the server.
But there can be timing differences, especially if you run animations at a reduced rate on the server
A: 저는 FPS에서 작업했습니다. 애니메이션 타이밍을 오프라인으로 저장했기 때문에 서버에서 애니메이션 블루프린트를 실행할 필요가 없었습니다. 하지만 타이밍 차이가 있을 수 있는데, 특히 서버에서 애니메이션을 낮은 빈도로 실행하는 경우 그렇습니다.
B : Why would you run animations at a reduced rate on the server, but not on the client?
B: 서버에서는 낮은 빈도로 애니메이션을 실행하지만 클라이언트에서는 그렇지 않은 이유는 무엇일까요?
A : Because you probably don't need high fidelity animation on the server
A: 서버에서는 고품질 애니메이션이 필요하지 않을 가능성이 높기 때문입니다.
C : Just to clarify, are you referring to latency or skipping notify ticks due to server tickrate?
C: 명확히 하자면, 레이턴시를 말하는 건가요 아니면 서버 틱레이트로 인한 노티파이 틱을 스킵하는걸 말하는 건가요?
A : On the dedicated server the character animation update rate was about 5Hz, and the first person animations didn't run at all. And reload timings were based on first person animations. So since they didn't run, they just used presaved timings. The animation sequences didn't even need to be loaded.
A: 데디 서버에서 캐릭터 애니메이션 업데이트 레이트는 약 5Hz였고, 1인칭 애니메이션은 전혀 실행되지 않았습니다. 그리고 재장전 타이밍은 1인칭 애니메이션을 기반으로 했습니다. 그러니까 실행되지 않았기 때문에 미리 저장된 타이밍을 사용했습니다. 애니메이션 시퀀스를 로드할 필요조차 없었습니다.
B :
do you have a suggestion as to how to solve my problem? I've just been trying to align hitboxes up visually with timing in PIE. I'm cool with a different solution, or just fixing the current problem: the timer for the hitbox activation/deactivation gets out of sync with the animation if I pause in PIE.
B: 문제를 해결하는 방법에 대한 제안이 있나요? 저는 PIE에서 타이밍에 맞춰 히트박스를 시각적으로 정렬하려고 했습니다. 다른 해결책이 있어도 괜찮고, 아니면 현재 문제를 해결하면 됩니다. PIE에서 일시 정지하면 히트박스 활성화/비활성화 타이머가 애니메이션과 동기화되지 않습니다.
C :
I'm using AnimNotifies for a continuous hitbox (swords basically), but the server only really needs to know a socket location from the skeletal mesh during montages. Haven't had any issues yet.
The timing is consistent, but of course latency will have it's own effect on top.
What server model are you using? Not sure why pausing would be an issue for an online game, unless it's a listen server and window focus somehow affects the timers/animations.
I don't really know though, just asking. Feel free to ignore
저는 연속 히트박스(기본적으로 검)에 AnimNotifies를 사용하고 있지만, 서버는 몽타주 중에 스켈레탈 메시에서 소켓 위치만 알면 됩니다. 아직 문제가 없습니다. 타이밍은 일관성을갖지만, 물론 레이턴시는 그 위에 자체적인 영향을 미칩니다. 어떤 서버 모델을 사용하고 있습니까? pausing이온라인 게임에서 문제가 될 이유를 잘 모르겠습니다. 리슨 서버이고 윈도우 포커스가 타이머/애니메이션에 영향을 미치는 경우가 아니라면요. 하지만 잘 모르겠습니다. 그냥 물어보는 겁니다. 무시해도 됩니다.
B :
It's literally just an issue in PIE. I think timers just look at delta time, so once you unpause, the timers will probably expire. But animations pause and resume normally. I can't think of a way around this issue if anim notifies are an even worse solution (because they will actually bork in a production scenario)
I guess the root of the problem that I'm asking everyone who will listen is: Is separating the hitbox from the animation the way to go? If it is, what's a good way to sync them?
말 그대로 PIE의 문제일 뿐입니다. 타이머는 델타 시간만 보는 것 같은데, 그래서 일시 정지를 일단 풀면, 타이머가 만료될 가능성이 큽니다. 하지만 애니메이션은 정상적으로 일시 정지되고 다시 시작됩니다. 애님 노티파이가 더 나쁜 해결책이라면 이 문제를 해결할 방법이 생각나지 않습니다(실제로 프로덕션 시나리오에서는 망가지기 때문입니다) 제가 모든 사람에게 묻는 문제의 근원은 다음과 같습니다. 히트박스를 애니메이션에서 분리하는 것이 해결책일까요? 그렇다면, 이를 동기화하는 좋은 방법은 무엇일까요?
A :
Looks like for a melee system you'd need the transform data of whatever point you're reading at the notify in the animation
근접전 시스템의 경우 애니메이션에서 노티파이를 읽는 지점의 트랜스폼 데이터가 필요할 것 같습니다.
[ Approach 2 : simulate animation on server in Valorant : Server-side Rewind ]
현재 공개된 가장 가능성높은 Approach.
매 프레임마다 서버에서 해당 캐릭터들의 "animation input"을 저장해두고, server-side rewind가 실행될 때, 저장된 animation input을 꺼내서 해당 애니메이션으로 되감은 뒤, 피격 판정을 하고 다시 이전 상태의 애니메이션으로 복구한다.
문제는 캐릭터들의 순간적인 "animation state"를 저장하고, 다시 그 지점으로 되감는 것이다.
캐릭터의 최종적인 애니메이션은 여러 애니메이션들이 "블렌드"된 상태 이므로, 순정상태의 엔진에선 이를 추출하는 것이 현재까지 알아본 바로는, 불가능하다. 또한, 해당 상태를 추출하는 것을 지원하지 않으니, 이를 되감아서 해당 애니메이션으로 실행하는 것도 가능하지 않을 것이다.
그렇기 때문에 여기에서도 "각 애니메이션 노드의 인풋에 대한 이해가 필요한 대규모 엔진 수정 작업"이라고 명시되어 있다. 하지만, 만약 이 방식을 구현할 수 있다면 매우 좋은 성능을 가질 것같다.
왜냐하면 FPS에 한해서, 클라이언트의 피격을 감지하고 서버에서 되감아서 다시 판정하는 것과 달리, 바로 서버에서 직접 되감아서 판정하므로 network latency와 packet loss에 따른 Character Movement Component의 실제 서버 위치와의 불일치로 피격 판정의 정확성이 떨어지는 현상을 감소시킬 수 있기 때문이다.
(CMC는 extrapolation가 포함되어 있어 곧이곧대로 클라이언트 피격 판정을 믿고 server side rewind를 적용한다면 서버의 위치와 오차가 발생한다는 실험 자료가 존재한다. @참조 : https://snapnet.dev/blog/performing-lag-compensation-in-unreal-engine-5/)
추가적으로 미래에 이 방식을 구현한다면, 매 번 모든 액터들을 Server 측에서 무조건 되감는 것은 비용이 크니 SphereTrace에 감지된 액터들을 잠재적으로 되감을 대상을 1차적으로 가지치기(pruning)하는 사전작업을 해야하고, 텔레포트같은 스킬을 쓴 대상도 예외를 핸들링해야 할 것이다.
그리고 Server Tick Rate가 높을수록 Animation을 저장하는 빈도가 높아지므로 서버 최적화 정도에 따라 간접적으로 플레이어는 High Network Latency 상황에서 좀 더 공정한 플레이를 기대할 수 있을 것이다.
[ Optimization - Animation ]
Animation was a huge cost for us on the server side. To properly figure out if a shot hit or not, we need to run the same animations on the server that players see on their clients. Hit registration in VALORANT works by saving player positions and animation state in a historical buffer. When the server receives a shot packet from the client, it rewinds the player positions and animation state using the historical buffer to calculate if the shot hit. Initially we were computing animation and filling this buffer every frame. However, after careful testing and comparisons we found that we could animate every 4th frame. In the event of a rewind we could lerp between the saved animations. This effectively cut animation costs down by 75%.
애니메이션은 서버 측에서 우리에게 엄청난 비용이었습니다. 총격이 명중했는지 아닌지를 제대로 파악하려면 플레이어가 클라이언트에서 보는 것과 동일한 애니메이션을 서버에서 실행해야 합니다. VALORANT의 히트 판정은 플레이어 위치와 애니메이션 상태를 과거 버퍼에 저장하여 작동합니다. 서버가 클라이언트로부터 총격 패킷을 받으면 과거 버퍼를 사용하여 플레이어 위치와 애니메이션 상태를 되감아 총격이 명중했는지 계산합니다. 처음에는 애니메이션을 계산하고 이 버퍼를 매 프레임마다 채웠습니다. 그러나 신중한 테스트와 비교를 거친 후 4번째 프레임마다 애니메이션을 적용할 수 있다는 것을 알게 되었습니다. 되감기가 발생하는 경우 저장된 애니메이션 사이를 lerp할 수 있습니다. 이를 통해 애니메이션 비용을 효과적으로 75%까지 줄일 수 있었습니다.
[ Reducing animation overhead on the game server ]
VALORANT’s game servers run single-threaded at 128 ticks per second with three games per CPU core, making each server instance’s frametime budget just 2.6 ms. Reaching this target requires a ton of optimization across the game, and
character animation provides a good example of the work involved in one area.
VALORANT의 게임 서버는 CPU 코어당 3개의 게임으로 초당 128틱의 단일 스레드로 실행되므로 각 서버 인스턴스의 프레임 시간 예산은 2.6ms에 불과합니다. 이 목표를 달성하려면 게임 전체에서 많은 최적화가 필요하며, 캐릭터 애니메이션은 한 영역에서 관련된 작업의 좋은 예를 제공합니다.
The game server skips animation for all skeletal meshes not involved in hit registration. When combat isn’t possible (For example, during the weapon-purchasing phase of each round), animations are disabled entirely.
게임 서버는 타격 등록과 관련되지 않은 모든 스켈레탈 메시에 대한 애니메이션을 건너뜁니다. 전투가 불가능한 경우(예: 각 라운드의 무기 구매 단계) 애니메이션은 완전히 비활성화됩니다.
To accurately check for hits, any actor that can be hit by weapon projectiles must evaluate animation and set correct bone positions. When a gun is fired, the game server rewinds the server simulation to the point in time where the client fired, accounting for network latency and client/server frametimes. Rewinding interpolates between past bone positions for sub-frame server time offsets and then checks for hits against targetable actors to compute damage. After checking for hits, the rewinder restores the server simulation to the present time and proceeds onward.
타격을 정확하게 확인하려면 무기 발사체에 맞을 수 있는 모든 액터가 애니메이션을 평가하고 올바른 본 위치를 설정해야 합니다. 총이 발사되면 게임 서버는 네트워크 지연 및 클라이언트/서버 프레임 시간을 고려하여 클라이언트가 발사한 시점으로 서버 시뮬레이션을 되감습니다. 되감기는 서브 프레임 서버 시간 오프셋에 대한 과거 본 위치 사이를 보간한 다음 타겟팅 가능한 액터에 대한 타격을 확인하여 피해를 계산합니다. 적중을 확인한 후, 리와인더는 서버 시뮬레이션을 현재 시간으로 복원하고 계속 진행합니다.
Measurements show animation updates and evaluations on one of our characters can take up to 0.1 ms. If we animate all 10 characters every server frame, nearly 40 percent of our total frame budget is gone. Most of the cost comes from the evaluation step; the update step is cheap by comparison.
측정 결과, 캐릭터 중 하나에 대한 애니메이션 업데이트 및 평가는 최대 0.1ms가 걸릴 수 있습니다. 모든 10개 캐릭터를 서버 프레임마다 애니메이션화하면 총 프레임 예산의 약 40%가 사라집니다. 비용의 대부분은 평가 단계에서 발생하며 업데이트 단계는 비교적 저렴합니다.
Initially, we tried rate-limiting animation to every few frames and capturing snapshots of final bone positions after each evaluation. Rewinds always force an animation evaluation and snapshot for the current frame, and we interpolate between the two closest snapshots for hit testing.
처음에는 애니메이션을 몇 프레임마다 속도 제한하고 각 평가 후에 최종 본 위치의 스냅샷을 캡처하려고 했습니다. 리와인드는 항상 현재 프레임에 대한 애니메이션 평가 및 스냅샷을 강제로 실행하고, 적중 테스트를 위해 가장 가까운 두 스냅샷 사이를 보간합니다.
Next, we added logic to prune unnecessary rewinds by excluding actors that couldn’t possibly be hit by a shot. This eliminates the forced animation evaluation for irrelevant actors. To determine relevance, the server sends a sphere cast from the player firing down their weapon’s aim vector with a sphere that’s sufficiently large to capture any recent movement and weapon error. Any characters that collide with the sphere are then rewound and evaluated.
다음으로, 샷에 맞을 가능성이 없는 액터를 제외하여 불필요한 되감기를 제거하는 논리를 추가했습니다. 이렇게 하면 관련 없는 액터에 대한 강제 애니메이션 평가가 제거됩니다. 관련성을 확인하기 위해 서버는 플레이어가 무기의 조준 벡터를 향해 발사한 구체를 보내 최근의 움직임과 무기 오류를 캡처할 만큼 충분히 큰 구체로 보냅니다. 구체와 충돌하는 모든 캐릭터는 되감기고 평가됩니다.
By pruning rewinds, we’re only performing the on-demand animation evaluation for characters that could be hit, but we’re still paying for periodic evaluations to create snapshots of bone positions for all characters. To remove those evaluations, we run the update step and snapshot the inputs to each animgraph node rather than snapshotting the final bone positions. We can then rewind the animgraph to an arbitrary point in the past and evaluate the graph to get bone positions.
되감기를 가지치기함으로써, 우리는 타격을 받을 수 있는 캐릭터에 대해서만 온-디맨드 애니메이션 평가를 수행하지만, 모든 캐릭터의 본 위치 스냅샷을 생성하기 위한 주기적 평가에 여전히 비용을 지불하고 있습니다. 이러한 평가를 제거하기 위해, 우리는 업데이트 단계를 실행하고 최종 본 위치를 스냅샷하는 대신 각 애니메이션 그래프 노드에 대한 인풋을 스냅샷합니다. 그런 다음 애니메이션 그래프를 과거의 임의의 지점으로 되감고 그래프를 평가하여 본 위치를 얻을 수 있습니다.
Snapshotting animgraph node inputs is a hefty engine modification as it requires understanding the necessary state for each type of node, but the speedups are worthwhile for our scenario. With all of these optimizations in place, we now spend an average of 0.3 ms per server frame processing animation.
애니메이션 그래프 노드 인풋을 스냅샷하는 것은 노드의 각 타입에 필요한 상태를 이해해야 하기 때문에 엔진을 크게 수정해야 하지만, 우리의 시나리오에서는 속도 향상이 가치가 있습니다. 이러한 모든 최적화가 적용되면, 우리는 이제 애니메이션을 처리하는 데 서버 프레임당 평균 0.3ms를 소비합니다.
[ Approach 3 : UE4 Unreal Tournament ] : from https://github.com/JimmieKJ/unrealTournament
공개되어 있는 거의 유일한, 상용화된 게임수준의 오픈소스.
저장된 플레이어의 중앙 위치인 FVector를 저장한뒤, 확인함
아마도 Capsule 단위만 판정 가능하고, 정교한 애니메이션 판정을 하지 못하는듯 보임.
[ Approach 4 : Counter Strike, Valve Community ] : https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization#Lag_Compensation
2001년에 작성된 문서에는, 자세하게 구현 방식이 나와있다. 멀티플레이어 게임 프로그래밍 책에 따르면, Server side Rewind를 최초로 사용한 게임 또한 Counter Strike라고 나와있다. 하지만, Tick 마다 기록하는 정보에 플레이어가 앉기 상태인지도 포함하는 것으로 보아, 이 문서가 기록될 때에도 3번처럼 Animation 단위의 정교한 판정은 하지 못한 것으로 보인다.
sub-tick : https://youtu.be/GqhhFl5zgA0?si=-NwrLt7sdF1BMeJT
현재의 Counter Strike 2는, 모종의 이유(최적화 불가능 또는 서버 비용 문제)로 128tick에서 64tick으로 서버 틱레이트를 낮췄고, Sub tick이라고 하는 (정확한 정보를 알려주지 않았지만, Tick에 구애받지않고 피격판정을 한다는 듯하다.) 방식을 도입하여 High Latency 상황에서 더욱 정확한 판정을한다고 홍보한다. 하지만, Reddit에서는 해외서버에서 플레이하는 유저 대다수가 CS:GO의 128tick server에 비해 피격 판정이 더 부정확해 졌다고 하는 불만이 적지 않다.
→ 유저들의 경험이 꽤나 갈린다. 결국 CS:GO의 128tick vs CS2의 64 Sub tick이어서, CS:GO에서 사람들이 피격판정을 긴 시간동안 "체득"한 것과 달라져 불공평하다고 느낀 것인지, 아니면 진짜로 64 Sub tick은 단순히 128tick 보다 좋지 않은 마케팅 용어인지는 아직 잘 모르겠다.
[ A bunch of Q&A and Approaches ]
[ Get Current Animation - 1 ] : from UnrealSource
일반적인 방식으론 몽타주가 아닌 현재 애니메이션의 상태를 받아올 방법은 없다.
왜냐하면 애니메이션 블루프린트를 보면 알 수 있듯이, 최종적인 애니메이션은 여러 애니메이션들 혼합(블렌드)된 결과물이기 때문이다.
[ 단 하나의 애니메이션이 실행된 상태일 때 ]
Q) Is there a way to get the current animation on a skeletal mesh componenent? (I know there's a Set Animation but didn't find a GetAnim...)
A)
Mesh->GetSingleNodeInstance()->GetAnimationAsset()
obviously only works if your in AnimationSingleNode mode (aka "Use Animation Asset")
if you're in animation blueprint mode then effectively no, since poses can be blended
[ Get Current Animation - 2 ] : from froums
For Montages:
UAnimMontage *Montage = MyAnimInstance->GetCurrentActiveMontage();
For animation sequences in general, I believe there’s no easy way. The anim graph and anim states together can have any number of sequences being blended into a final pose to different degrees (eg, there could be 10 different animations being blended in, some 0%, some 10%, some 50%, etc). “What animation is playing?” is a very ambiguous question at that point. “Give me a list of all playing sequences” is more reasonable but I don’t believe that has an easy answer either. When adding a sequence to the mix, the sequence tracks get added, not the sequence per-se. I don’t think references to the sequences that originated the track pools survive to the end of the process.
일반적으로 애니메이션 시퀀스의 경우 쉬운 방법이 없다고 생각합니다. 애니메이션 그래프와 애니메이션 상태를 함께 사용하면 여러 시퀀스가 최종 포즈로 다른 정도로 블렌딩될 수 있습니다(예: 10개의 다른 애니메이션이 블렌딩될 수 있으며, 일부는 0%, 일부는 10%, 일부는 50% 등). "어떤 애니메이션이 재생되고 있나요?"라는 질문은 그 시점에서 매우 모호합니다. "재생 중인 모든 시퀀스 목록을 주세요"가 더 합리적이지만 쉬운 답이 있다고 생각하지 않습니다. 믹스에 시퀀스를 추가하면 시퀀스 트랙이 추가되고 시퀀스 자체는 추가되지 않습니다. 트랙 풀을 만든 시퀀스에 대한 참조가 프로세스가 끝날 때까지 살아남지 않는다고 생각합니다.
You can get info about what’s happening in a particular state machine:
특정 스테이트 머신에서 무슨 일이 일어나고 있는지에 대한 정보를 얻을 수 있습니다.
MyAnimInstance->GetRelevantAnimTimeRemainingFraction
MyAnimInstance->GetRelevantAnimLength
FAnimNode_AssetPlayerBase* AssetPlayer = MyAnimInstance->GetRelevantAssetPlayerFromState(MachineIndex, StateIndex);
AssetPlayer->GetAnimAsset()->GetAllAnimationSequencesReferred()
[ ] ....
'UE5 > Network' 카테고리의 다른 글
[NDC2022] SPICA 어디까지 왔나? 강연 필기 (0) | 2024.11.13 |
---|---|
[UE5] Replication without Compromise (0) | 2024.10.10 |
[UE5] SetPawn() : OnPossess in Client (0) | 2024.09.28 |
Conditional Property Replication에 관한 짧은 분석 (0) | 2024.09.15 |
[UE5] Replication Graph Deep Dive (0) | 2024.08.29 |