icon안동민 개발노트

동적 액터와의 기본 상호작용


 나이아가라 파티클 시스템과 동적으로 움직이는 액터 간의 상호작용은 생동감 있고 반응적인 게임 환경을 만드는 데 중요한 역할을 합니다.

 이 절에서는 파티클과 동적 액터 간의 다양한 상호작용 구현 방법을 살펴보겠습니다.

동적 액터 정보 파티클 시스템에 반영

  1. 데이터 인터페이스 생성
UCLASS()
class UNiagaraDataInterfaceDynamicActor : public UNiagaraDataInterface
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere)
    AActor* TargetActor;
 
    virtual void GetActorData(FVector& OutPosition, FVector& OutVelocity) const;
};
  1. 나이아가라 모듈에서 데이터 사용
void UpdateParticleWithActorData(inout float3 Position, inout float3 Velocity)
{
    float3 ActorPosition, ActorVelocity;
    DynamicActor.GetActorData(ActorPosition, ActorVelocity);
    
    float3 ToActor = ActorPosition - Position;
    float Distance = length(ToActor);
    
    // 액터 주변에 파티클 집중
    Velocity += normalize(ToActor) * 10.0 / (Distance + 1.0);
    
    // 액터 속도에 영향 받음
    Velocity += ActorVelocity * 0.1;
}

동적 액터에 미치는 영향 시뮬레이션

  1. 파티클 데이터 수집
void ANiagaraActor::CollectParticleData(TArray<FVector>& Positions, TArray<FVector>& Velocities)
{
    UNiagaraComponent* NiagaraComp = GetComponentByClass<UNiagaraComponent>();
    if (NiagaraComp)
    {
        NiagaraComp->GetParticleData(Positions, "Position");
        NiagaraComp->GetParticleData(Velocities, "Velocity");
    }
}
  1. 액터에 영향 적용
void ADynamicActor::ApplyParticleEffects(const TArray<FVector>& ParticlePositions, const TArray<FVector>& ParticleVelocities)
{
    FVector TotalForce = FVector::ZeroVector;
    for (int i = 0; i < ParticlePositions.Num(); ++i)
    {
        FVector ToParticle = ParticlePositions[i] - GetActorLocation();
        float Distance = ToParticle.Size();
        TotalForce += ToParticle.GetSafeNormal() * (1.0f / (Distance * Distance));
    }
    
    UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(GetRootComponent());
    if (PrimComp)
    {
        PrimComp->AddForce(TotalForce * ForceMultiplier);
    }
}

동적 콜리전 처리

  1. 콜리전 데이터 업데이트
void UNiagaraDataInterfaceDynamicActor::UpdateCollisionData()
{
    if (TargetActor)
    {
        FTransform ActorTransform = TargetActor->GetTransform();
        // 콜리전 데이터 구조 업데이트
    }
}
  1. 파티클 콜리전 체크
void CheckDynamicCollision(inout float3 Position, inout float3 Velocity)
{
    float3 ClosestPoint, Normal;
    if (DynamicActor.CheckCollision(Position, ClosestPoint, Normal))
    {
        Position = ClosestPoint + Normal * 0.01;
        Velocity = reflect(Velocity, Normal) * 0.8;
    }
}

파티클-액터 간 포스 적용

  1. 파티클에서 액터로의 포스
void ApplyForceToActor(float3 ParticlePosition, float3 ParticleVelocity)
{
    float3 Force = ParticleVelocity * ParticleMass;
    DynamicActor.AddForce(Force);
}
  1. 액터에서 파티클로의 포스
void ApplyActorForceToParticle(inout float3 Velocity)
{
    float3 ActorForce;
    DynamicActor.GetAppliedForce(ActorForce);
    Velocity += ActorForce * ForceTransferFactor;
}

복잡한 상호작용 시나리오 구현

 예 : 캐릭터 주변의 마법 오라 효과

void UpdateMagicAura(inout float3 Position, inout float3 Velocity, inout float4 Color, 
                     float Time, float DeltaTime)
{
    float3 CharacterPosition, CharacterVelocity;
    DynamicCharacter.GetData(CharacterPosition, CharacterVelocity);
    
    // 캐릭터 주변 궤도 운동
    float Angle = Time * 2.0 + InitialAngle;
    float Radius = 100.0 + sin(Time * 3.0) * 20.0;
    float3 Offset = float3(cos(Angle), sin(Angle), 0) * Radius;
    Position = CharacterPosition + Offset;
    
    // 캐릭터 속도에 따른 왜곡
    Velocity = CharacterVelocity * 0.2 + normalize(Offset) * 50.0;
    
    // 캐릭터 상태에 따른 색상 변화
    float CharacterHealth;
    DynamicCharacter.GetHealth(CharacterHealth);
    Color = lerp(float4(1,0,0,1), float4(0,1,0,1), CharacterHealth);
    
    // 주변 적 감지 및 반응
    float3 NearestEnemyPosition;
    if (DynamicCharacter.GetNearestEnemy(NearestEnemyPosition))
    {
        float3 ToEnemy = NearestEnemyPosition - Position;
        Velocity += normalize(ToEnemy) * 30.0;
        Color.g += 0.5; // 방어적 색상 강화
    }
}

성능 최적화 전략

 1. 데이터 버퍼링

  • 매 프레임이 아닌 일정 간격으로 동적 액터 데이터 업데이트

 2. 공간 분할

  • 옥트리 또는 균일 그리드를 사용하여 상호작용 체크 최적화

 3. LOD (Level of Detail) 시스템

void ApplyLOD(inout bool PerformInteraction, float DistanceFromCamera)
{
    PerformInteraction = DistanceFromCamera < InteractionThreshold;
}

 4. 배치 처리

  • 유사한 상호작용을 그룹화하여 한 번에 처리

 5. GPU 가속

  • 가능한 경우 상호작용 계산을 GPU로 오프로드

효과적인 디버깅 방법

 1. 시각적 디버깅

void VisualizeParticleActorInteraction(UWorld* World, const FVector& ParticlePos, const FVector& ActorPos)
{
    DrawDebugLine(World, ParticlePos, ActorPos, FColor::Red, false, -1.0f, 0, 1.0f);
}

 2. 로깅 시스템 구현

void LogInteractionData(const FString& InteractionType, const FVector& Position, const FVector& Force)
{
    UE_LOG(LogNiagara, Verbose, TEXT("Interaction: %s, Pos: %s, Force: %s"), 
            *InteractionType, *Position.ToString(), *Force.ToString());
}

 3. 파라미터 튜닝 인터페이스

  • 런타임에 상호작용 파라미터를 조정할 수 있는 UI 구현

 4. 성능 프로파일링

void ProfileInteractionPerformance()
{
    SCOPE_CYCLE_COUNTER(STAT_NiagaraActorInteraction);
    // 상호작용 코드
}

적용 예시 : 물리 기반 눈보라 효과

 다음은 동적 캐릭터와 상호작용하는 물리 기반 눈보라 효과의 구현 예시입니다.

void UpdateSnowstormParticle(inout float3 Position, inout float3 Velocity, inout float Size,
                             inout float4 Color, float DeltaTime)
{
    // 기본 눈보라 동작
    float3 WindForce = float3(sin(Time * 0.1), cos(Time * 0.15), -0.1) * 100.0;
    Velocity += WindForce * DeltaTime;
    
    // 캐릭터와의 상호작용
    float3 CharacterPosition, CharacterVelocity;
    DynamicCharacter.GetData(CharacterPosition, CharacterVelocity);
    
    float3 ToCharacter = CharacterPosition - Position;
    float DistanceToCharacter = length(ToCharacter);
    
    // 캐릭터 주변 눈 회오리 효과
    if (DistanceToCharacter < 200.0)
    {
        float3 TangentialForce = cross(normalize(ToCharacter), float3(0,0,1)) * 150.0;
        Velocity += TangentialForce * DeltaTime;
        
        // 캐릭터 속도에 의한 영향
        Velocity += CharacterVelocity * 0.5;
        
        // 눈 입자 크기 및 색상 변화
        Size *= 1.2;
        Color.a *= 0.8;
    }
    
    // 지형 콜리전
    float3 TerrainNormal;
    float TerrainHeight = Terrain.SampleHeight(Position.xy, TerrainNormal);
    if (Position.z < TerrainHeight)
    {
        Position.z = TerrainHeight + 0.1;
        Velocity = reflect(Velocity, TerrainNormal) * 0.3;
        Size *= 0.9;
    }
    
    // 최종 위치 업데이트
    Position += Velocity * DeltaTime;
    
    // 파티클 생존 영역 제한
    if (abs(Position.x) > 1000.0 || abs(Position.y) > 1000.0 || Position.z > 500.0)
    {
        Position = float3(Random.x * 2000.0 - 1000.0, Random.y * 2000.0 - 1000.0, 500.0);
        Velocity = float3(0, 0, -50.0);
        Size = lerp(0.5, 2.0, Random.z);
        Color = float4(0.9, 0.95, 1.0, 1.0);
    }
}

 이 예시에서는 눈보라 파티클이 바람의 영향을 받으면서 동시에 캐릭터와 상호작용합니다.

 캐릭터 주변에서는 눈이 회오리치는 효과가 생기며, 캐릭터의 움직임에 따라 눈 입자들이 영향을 받습니다.

 또한 지형과의 콜리전을 처리하고 파티클의 생존 영역을 제한하여 최적화를 고려하고 있습니다.

 동적 액터와 파티클 시스템의 상호작용을 효과적으로 구현하면 더욱 생동감 있고 몰입도 높은 게임 환경을 만들 수 있습니다.