icon안동민 개발노트

사용자 정의 데이터 인터페이스


 나이아가라 시스템에서 사용자 정의 데이터 인터페이스는 파티클 시스템과 게임 로직 사이의 커스텀 데이터 교환을 가능하게 하는 강력한 도구입니다.

 이를 통해 복잡하고 특화된 파티클 효과를 구현할 수 있으며, 게임의 특정 요구사항에 맞는 동적 파티클 시스템을 만들 수 있습니다.

사용자 정의 데이터 인터페이스의 필요성

  1. 게임 특화 데이터 통합
  2. 복잡한 계산의 외부화
  3. 동적 게임 상태 반영
  4. 재사용 가능한 파티클 로직 구현

생성 과정

 블루프린트를 사용한 방법

  1. 콘텐츠 브라우저에서 우클릭
  2. Blueprints > Niagara Data Interface 선택
  3. 새 데이터 인터페이스의 이름 지정 (예 : DI_GameState)
  4. 블루프린트 에디터에서 변수와 함수 정의

 예시

Variables:
  - PlayerPosition (Vector)
  - GameTime (Float)
 
Functions:
  - GetPlayerPosition() : Vector
  - GetGameTime() : Float

 C++을 사용한 방법

  1. 새 C++ 클래스 생성 (UNiagaraDataInterface 상속)
  2. 헤더 파일에 변수와 함수 선언
UCLASS(EditInlineNew, Category = "Game Logic")
class MYGAME_API UNiagaraDataInterfaceGameState : public UNiagaraDataInterface
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game State")
    FVector PlayerPosition;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game State")
    float GameTime;
 
    UFUNCTION(BlueprintCallable, Category = "Game State")
    FVector GetPlayerPosition() const { return PlayerPosition; }
 
    UFUNCTION(BlueprintCallable, Category = "Game State")
    float GetGameTime() const { return GameTime; }
 
    // 나이아가라 VM 함수 선언
    void GetPlayerPosition(FVectorVMContext& Context);
    void GetGameTime(FVectorVMContext& Context);
 
    // 데이터 인터페이스 함수 구현
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
    virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override;
};
  1. 소스 파일에서 함수 구현
void UNiagaraDataInterfaceGameState::GetPlayerPosition(FVectorVMContext& Context)
{
    VectorVM::FExternalFuncInputHandler<float> OutX(Context);
    VectorVM::FExternalFuncInputHandler<float> OutY(Context);
    VectorVM::FExternalFuncInputHandler<float> OutZ(Context);
 
    for (int32 i = 0; i < Context.NumInstances; ++i)
    {
        *OutX.GetDestAndAdvance() = PlayerPosition.X;
        *OutY.GetDestAndAdvance() = PlayerPosition.Y;
        *OutZ.GetDestAndAdvance() = PlayerPosition.Z;
    }
}
 
void UNiagaraDataInterfaceGameState::GetGameTime(FVectorVMContext& Context)
{
    VectorVM::FExternalFuncInputHandler<float> OutTime(Context);
 
    for (int32 i = 0; i < Context.NumInstances; ++i)
    {
        *OutTime.GetDestAndAdvance() = GameTime;
    }
}
 
void UNiagaraDataInterfaceGameState::GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions)
{
    FNiagaraFunctionSignature Sig;
    Sig.Name = TEXT("GetPlayerPosition");
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("GameState")));
    Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Position")));
    OutFunctions.Add(Sig);
 
    Sig.Name = TEXT("GetGameTime");
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition(GetClass()), TEXT("GameState")));
    Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Time")));
    OutFunctions.Add(Sig);
}
 
bool UNiagaraDataInterfaceGameState::CanExecuteOnTarget(ENiagaraSimTarget Target) const
{
    return Target == ENiagaraSimTarget::CPUSim;
}

파티클 시스템에 통합

  1. 나이아가라 에디터에서 파티클 시스템 열기
  2. 'Add Module' 버튼 클릭
  3. 사용자 정의 데이터 인터페이스 모듈 선택
  4. HLSL 코드에서 데이터 인터페이스 함수 호출
float3 PlayerPos;
GameState.GetPlayerPosition(PlayerPos);
 
float GameTime = GameState.GetGameTime();
 
// 파티클 위치를 플레이어 주변으로 설정
Position = PlayerPos + float3(rand() - 0.5, rand() - 0.5, rand() - 0.5) * 100.0;
 
// 게임 시간에 따른 파티클 크기 변화
Size = sin(GameTime * 2.0) * 0.5 + 1.0;

블루프린트 vs C++ 구현 비교

 블루프린트

 장점

  • 빠른 프로토타이핑
  • 시각적 개발 환경
  • 프로그래밍 지식 없이 사용 가능

 단점

  • 복잡한 로직 구현의 어려움
  • 성능 제한
  • 버전 관리의 어려움

 C++

 장점

  • 높은 성능
  • 복잡한 로직 구현 가능
  • 더 나은 버전 관리
  • 엔진 코어와의 직접적인 통합

 단점

  • 개발 시간 증가
  • 컴파일 필요
  • 높은 진입 장벽

게임 로직과 파티클 시스템 연동

  1. 게임 상태 업데이트 함수 구현
void AMyGameMode::UpdateParticleGameState()
{
    UNiagaraComponent* NiagaraComp = GetNiagaraComponent();
    if (NiagaraComp)
    {
        UNiagaraDataInterfaceGameState* GameStateData = 
            Cast<UNiagaraDataInterfaceGameState>(NiagaraComp->GetOverrideComponentData("GameState"));
        if (GameStateData)
        {
            GameStateData->PlayerPosition = GetPlayerPawn()->GetActorLocation();
            GameStateData->GameTime = GetWorld()->GetTimeSeconds();
        }
    }
}
  1. 주기적으로 또는 필요할 때 UpdateParticleGameState 호출

성능 최적화 전략

  1. 데이터 업데이트 빈도 조절
  • 매 프레임 업데이트 대신 일정 간격으로 업데이트
  1. 데이터 캐싱
  • 자주 변경되지 않는 데이터는 파티클 시스템 내부에 캐시
  1. 데이터 압축
  • 대량의 데이터 전송 시 압축 기법 활용
  1. 멀티스레딩 고려
  • 데이터 처리를 별도 스레드에서 수행

재사용 가능한 데이터 인터페이스 설계 팁

  1. 모듈화
  • 기능별로 분리된 데이터 인터페이스 설계
  1. 일반화
  • 특정 게임에 종속되지 않는 범용적 인터페이스 구현
  1. 확장성
  • 향후 기능 추가를 고려한 유연한 구조 설계
  1. 문서화
  • 인터페이스 사용법과 의도를 명확히 문서화

실제 적용 예시 : 동적 환경 반응형 파티클 시스템

 환경 상태에 따라 변화하는 파티클 효과를 위한 데이터 인터페이스 구현

UCLASS(EditInlineNew, Category = "Environment")
class MYGAME_API UNiagaraDataInterfaceEnvironment : public UNiagaraDataInterface
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environment")
    float Temperature;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environment")
    float Humidity;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environment")
    FVector WindDirection;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Environment")
    float WindStrength;
 
    // 나이아가라 VM 함수
    void GetTemperature(FVectorVMContext& Context);
    void GetHumidity(FVectorVMContext& Context);
    void GetWindVector(FVectorVMContext& Context);
 
    // 데이터 인터페이스 함수
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
    virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override;
};

 HLSL에서의 사용

float Temperature = Environment.GetTemperature();
float Humidity = Environment.GetHumidity();
float3 WindVector = Environment.GetWindVector();
 
// 온도에 따른 파티클 색상 변화
Color = lerp(float4(0, 0, 1, 1), float4(1, 0, 0, 1), (Temperature - 0) / 30.0);
 
// 습도에 따른 파티클 크기 변화
Size *= lerp(0.5, 1.5, Humidity);
 
// 바람에 의한 파티클 이동
Velocity += WindVector;

 이러한 사용자 정의 데이터 인터페이스를 통해 게임 내 환경 변화(예 : 날씨 변화, 시간 경과)에 따라 동적으로 반응하는 파티클 시스템을 구현할 수 있습니다. 이는 더욱 생동감 있고 상호작용적인 게임 환경을 만드는 데 도움이 됩니다.

 사용자 정의 데이터 인터페이스는 나이아가라 시스템의 유연성을 크게 확장합니다. 게임의 특정 요구사항에 맞는 데이터를 파티클 시스템에 효과적으로 통합할 수 있으며, 이를 통해 더욱 복잡하고 역동적인 시각 효과를 만들 수 있습니다. 블루프린트와 C++ 구현 방식 각각의 장단점을 고려하여 프로젝트에 가장 적합한 방식을 선택하고, 성능과 재사용성을 고려한 설계를 통해 효율적이고 확장 가능한 파티클 시스템을 구축하세요.