ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Unreal] 언리얼 모션 매칭 Motion Matching #3 - Trajectory
    쾌락없는 책임 (공부)/Unreal 2025. 7. 4. 14:19
    반응형

    모션 매칭에 더 도움이 되는 Trajectory

    Trajectory 역시 아직은 실험적인 단계로 여러 오류가 있는 상태입니다. 해당 기능은 플레이어의 속도, 방향에 따라 ‘앞으로 어느 위치로 이동할 것 같은지 예측’ 해주는 기능입니다. 모션 매칭을 활용할 때 앞으로의 예상 위치나 속도 등을 알 수 있으면 애니메이션을 판별하는데 더욱 도움이 되며 개발자들도 예측 정보를 통해 정보들을 가져와 Chooser Table 등을 활용할 수 있습니다. 때문에 사실상 모션 매칭과 함께 사용해야 하는 기능 중 하나입니다.

    Trajectory를 활용하는 방법

    Trajectory는 경로를 나타내는 일반 Trajectory와 충돌 감지도 함께 하는 Collision Trajectory 2가지 종류가 있습니다.

    FPoseSearchQueryTrajectory FPoseSearchTrajectory_WorldCollisionResults
    각 시점에 대한 방향(Facing), 위치(Position) 정보를 들고 있습니다. 시점은 과거, 현재, 미래 시점이며 현재 정보를 통해 과거, 미래의 정보를 예측하는 것입니다. 쿼리를 통해 나온 땅에 떨어질 시간 (TimeToLand), 떨어지는 속도 (LandSpeed) 를 가지고 있습니다.


    블루프린트에서는 간단하게 사용이 가능합니다.

     

    FPoseSearchQueryTrajectory
    FPoseSearchTrajectory_WorldCollisionResults


    코드에서 이들을 사용하기 위해서는 아래 함수들을 사용하면 됩니다.
    -> 7.20 추가 내용 : 기존 코드가 5.4 버전이고 새 코드는 5.6 버전입니다. 5.6 에서 Trajectory 가 FTransformTrajectory로 변경되어 버전업 시 수정을 진행해야 합니다.

     

    [  언리얼 엔진 5.4 코드 ]

    // Generates a prediction trajectory based of the current character intent. For use with Character actors.
    UFUNCTION(BlueprintCallable, Category = "Animation|PoseSearch", meta = (BlueprintThreadSafe, DisplayName = "Pose Search Generate Trajectory (for Character)"))
    static void PoseSearchGenerateTrajectory(const UObject* InAnimInstance, 
        UPARAM(ref) const FPoseSearchTrajectoryData& InTrajectoryData, float InDeltaTime,
        UPARAM(ref) FPoseSearchQueryTrajectory& InOutTrajectory, UPARAM(ref) float& InOutDesiredControllerYawLastUpdate, FPoseSearchQueryTrajectory& OutTrajectory,
        float InHistorySamplingInterval = 0.04f, int32 InTrajectoryHistoryCount = 10, float InPredictionSamplingInterval = 0.2f, int32 InTrajectoryPredictionCount = 8);
    
    
    // Experimental: Process InTrajectory to apply gravity and handle collisions. Eventually returns the modified OutTrajectory.
    // If bApplyGravity is true, gravity from the UCharacterMovementComponent will be applied.
    // If FloorCollisionsOffset > 0, vertical collision will be performed to every sample of the trajectory to have the samples float over the geometry (by FloorCollisionsOffset).
    UFUNCTION(BlueprintCallable, Category="Animation|PoseSearch|Experimental", meta=(BlueprintThreadSafe, WorldContext="WorldContextObject", AutoCreateRefTerm="ActorsToIgnore", AdvancedDisplay="TraceChannel,bTraceComplex,ActorsToIgnore,DrawDebugType,bIgnoreSelf,MaxObstacleHeight,TraceColor,TraceHitColor,DrawTime"))
    static void HandleTrajectoryWorldCollisions(const UObject* WorldContextObject, const UAnimInstance* AnimInstance, UPARAM(ref) const FPoseSearchQueryTrajectory& InTrajectory, bool bApplyGravity, float FloorCollisionsOffset, FPoseSearchQueryTrajectory& OutTrajectory, FPoseSearchTrajectory_WorldCollisionResults& CollisionResult,
        ETraceTypeQuery TraceChannel, bool bTraceComplex, const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, bool bIgnoreSelf = true, float MaxObstacleHeight = 10000.f, FLinearColor TraceColor = FLinearColor::Red, FLinearColor TraceHitColor = FLinearColor::Green, float DrawTime = 5.0f);

     

    [  언리얼 엔진 5.6 코드 ]

     

    #include "Animation/TrajectoryTypes.h"
    // => 5.6 부터 FTransformTrajectory 를 사용하기에 위 헤더를 포함해야 합니다.
    
    // Generates a prediction trajectory based of the current character intent. For use with Character actors.
    UFUNCTION(BlueprintCallable, Category = "Animation|PoseSearch", meta = (BlueprintThreadSafe, DisplayName = "Pose Search Generate Trajectory (for Character)"))
    static UE_API void PoseSearchGenerateTransformTrajectory(const UObject* InAnimInstance, 
        UPARAM(ref) const FPoseSearchTrajectoryData& InTrajectoryData, float InDeltaTime,
        UPARAM(ref) FTransformTrajectory& InOutTrajectory, UPARAM(ref) float& InOutDesiredControllerYawLastUpdate, FTransformTrajectory& OutTrajectory,
        float InHistorySamplingInterval = 0.04f, int32 InTrajectoryHistoryCount = 10, float InPredictionSamplingInterval = 0.2f, int32 InTrajectoryPredictionCount = 8);
    
    
    // Experimental: Process InTrajectory to apply gravity and handle collisions. Eventually returns the modified OutTrajectory.
    // If bApplyGravity is true, gravity from the UCharacterMovementComponent will be applied.
    // If FloorCollisionsOffset > 0, vertical collision will be performed to every sample of the trajectory to have the samples float over the geometry (by FloorCollisionsOffset).
    UE_EXPERIMENTAL(5.6, "Solution for CMC based workflow not Mover.")
    UFUNCTION(BlueprintCallable, Category="Animation|PoseSearch|Experimental", meta=(BlueprintThreadSafe, DisplayName="HandleTrajectoryWorldCollisions", WorldContext="WorldContextObject", AutoCreateRefTerm="ActorsToIgnore", AdvancedDisplay="TraceChannel,bTraceComplex,ActorsToIgnore,DrawDebugType,bIgnoreSelf,MaxObstacleHeight,TraceColor,TraceHitColor,DrawTime"))
    static UE_API void HandleTransformTrajectoryWorldCollisions(const UObject* WorldContextObject, const UAnimInstance* AnimInstance, UPARAM(ref) const FTransformTrajectory& InTrajectory, bool bApplyGravity, float FloorCollisionsOffset, FTransformTrajectory& OutTrajectory, FPoseSearchTrajectory_WorldCollisionResults& CollisionResult,
        ETraceTypeQuery TraceChannel, bool bTraceComplex, const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, bool bIgnoreSelf = true, float MaxObstacleHeight = 10000.f, FLinearColor TraceColor = FLinearColor::Red, FLinearColor TraceHitColor = FLinearColor::Green, float DrawTime = 5.0f);



    저의 경우 이런 식으로 사용하고 있습니다.

     

    [  언리얼 엔진 5.4 코드 ]

    void UHumanAnimInstanceBase::UpdateTrajectory(float DeltaSeconds)
    {
    	// Do Trajectory First
    	FPoseSearchTrajectoryData PoseData;
    	UPoseSearchTrajectoryLibrary::PoseSearchGenerateTrajectory(
    		this,
    		IsMoving() ? TrajectoryGenerationData_Moving : TrajectoryGenerationData_Idle,
    		DeltaSeconds,
    		Trajectory,
    		DesiredControllerYaw,
    		Trajectory,
    		-1.0f,
    		30,
    		0.1f,
    		15
    		);
    
    	// Do Collision Trajectory (No Calculate At Simulated Proxy Due To Optimization)
    	if (IsValid(CachedCharacter) == false || CachedCharacter->GetLocalRole() <= ROLE_SimulatedProxy)
    	{
    		return;
    	}
    	
    	TArray<AActor*> IgnoreActors{ GetOwningActor() };
    	UPoseSearchTrajectoryLibrary::HandleTrajectoryWorldCollisions(
    		GetOwningActor(),
    		this,
    		Trajectory,
    		true,
    		0.01f,
    		Trajectory,
    		TrajectoryCollision,
    		ETraceTypeQuery::TraceTypeQuery1,
    		false,
    		IgnoreActors,
    		EDrawDebugTrace::None,
    		true,
    		150.0f
    	);
    }



    [  언리얼 엔진 5.6 코드 ]

     

    // Do Trajectory First
    FPoseSearchTrajectoryData PoseData;
    UPoseSearchTrajectoryLibrary::PoseSearchGenerateTransformTrajectory(
    this,
    IsMoving() ? TrajectoryGenerationData_Moving : TrajectoryGenerationData_Idle,
    DeltaSeconds,
    Trajectory,
    DesiredControllerYaw,
    Trajectory,
    -1.0f,
    30,
    0.1f,
    15
    );
    
    // Do Collision Trajectory (No Calculate At Simulated Proxy Due To Optimization)
    if (IsValid(CachedCharacter) == false || CachedCharacter->GetLocalRole() <= ROLE_SimulatedProxy)
    {
        return;
    }
    
    TArray<AActor*> IgnoreActors{ GetOwningActor() };
    UPoseSearchTrajectoryLibrary::HandleTransformTrajectoryWorldCollisions(
    GetOwningActor(),
    this,
    Trajectory,
    true,
    0.01f,
    Trajectory,
    TrajectoryCollision,
    TraceTypeQuery1,
    false,
    IgnoreActors,
    EDrawDebugTrace::None,
    true,
    150.0f
    );

     

    Trajectory 에 대한 생각

    (좌) 일반 State 로 구성된 ABP, (우) 모션 매칭에 Trajectory 를 추가한 ABP


    아직까지는 오류되는 사항들이 많아 문제점들이 있어 보입니다. 일단 콜리전 Trajectory의 경우 FloorCollisionOffset을 0 초과해서 주면 앞에 벽이 있다 하더라도 일정 높이는 갈 수 있다고 판단하게 됩니다. 때문에 이로 인해 벽에 플레이어가 비비게 될 때 계속해서 앞에 나가려고 하는 문제가 생기게 됩니다. 이는 다른 여러 조건들을 넣어 선택기 테이블에서 조정이 가능하기는 합니다.

    그리고 제가 보는건 성능적인 문제입니다. 일반 Trajectory의 경우에는 단순 속도와 방향을 기반으로 계산만 하는 거라 큰 문제는 없을 것 같지만 콜리전의 경우 이를 위해 아래쪽으로 Trace를 하는 로직들이 있습니다.

     

    void UPoseSearchTrajectoryLibrary::HandleTrajectoryWorldCollisionsWithGravity(const UObject* WorldContextObject,
    	UPARAM(ref) const FPoseSearchQueryTrajectory& InTrajectory, FVector StartingVelocity, bool bApplyGravity, FVector GravityAccel, float FloorCollisionsOffset, FPoseSearchQueryTrajectory& OutTrajectory, FPoseSearchTrajectory_WorldCollisionResults& CollisionResult,
    	ETraceTypeQuery TraceChannel, bool bTraceComplex, const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, bool bIgnoreSelf, float MaxObstacleHeight, FLinearColor TraceColor, FLinearColor TraceHitColor, float DrawTime)
    {
    	OutTrajectory = InTrajectory;
    	//...중력을 계산합니다.
    	if (!FMath::IsNearlyZero(GravityZ))
    	{
    		//... 기본 변수 설정
    		for (int32 SampleIndex = 1; SampleIndex < NumSamples; ++SampleIndex)
    		{
    			FPoseSearchQueryTrajectorySample& Sample = Samples[SampleIndex];
    			if (Sample.AccumulatedSeconds > 0.f)
    			{
    				// Sample 에서 Gravity 를 통해 위치 계산
    				// 여기서 Trace 가 있습니다.
    				FHitResult HitResult;
    				if (FloorCollisionsOffset > 0.f && UKismetSystemLibrary::LineTraceSingle(WorldContextObject, Sample.Position + (GravityDirection * -MaxObstacleHeight), Sample.Position, TraceChannel, bTraceComplex, ActorsToIgnore, DrawDebugType, HitResult, bIgnoreSelf, TraceColor, TraceHitColor, DrawTime))

     

    이를 통해 위 처럼 Trajectory 가 콜리전에 맞게 나올 수 있습니다


    이를 통해 떨어지는 데 까지의 시간 등을 구할 수 있는 거지만 게임에 플레이어가 많거나 ABP에서 사용하는 객체가 많다면 성능상의 문제가 잡힐 여지가 있습니다. 때문에 저의 경우 플레이어만 콜리전 Trajectory를 사용하게 하고 그 외의 경우는 일반 Trajecctory만 사용하도록 처리하였습니다.


    반응형

    댓글

Designed by Tistory.