icon안동민 개발노트

동적 오브젝트 및 상호작용


 언리얼 엔진에서 동적으로 변화하고 상호작용하는 오브젝트를 구현하는 것은 생동감 있는 게임 세계를 만드는 데 핵심적입니다.

 이 절에서는 다양한 동적 오브젝트 구현 방법과 상호작용 시스템 설계 전략을 살펴보겠습니다.

동적 오브젝트의 스폰 및 파괴 메커니즘

 스폰 매니저 구현

UCLASS()
class ASpawnManager : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, Category = "Spawning")
    TSubclassOf<AActor> ActorToSpawn;
 
    UFUNCTION(BlueprintCallable, Category = "Spawning")
    AActor* SpawnDynamicObject(FVector Location, FRotator Rotation);
 
    UFUNCTION(BlueprintCallable, Category = "Spawning")
    void DestroyDynamicObject(AActor* ActorToDestroy);
};
 
AActor* ASpawnManager::SpawnDynamicObject(FVector Location, FRotator Rotation)
{
    if (ActorToSpawn)
    {
        return GetWorld()->SpawnActor<AActor>(ActorToSpawn, Location, Rotation);
    }
    return nullptr;
}
 
void ASpawnManager::DestroyDynamicObject(AActor* ActorToDestroy)
{
    if (ActorToDestroy)
    {
        ActorToDestroy->Destroy();
    }
}

실시간으로 변화하는 오브젝트 속성 구현

 동적 속성 변경

UCLASS()
class ADynamicObject : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dynamic Properties")
    float HealthPoints;
 
    UFUNCTION(BlueprintCallable, Category = "Dynamic Properties")
    void UpdateHealth(float DeltaHealth);
 
    UFUNCTION(BlueprintImplementableEvent, Category = "Dynamic Properties")
    void OnHealthChanged();
};
 
void ADynamicObject::UpdateHealth(float DeltaHealth)
{
    HealthPoints += DeltaHealth;
    OnHealthChanged();
 
    if (HealthPoints <= 0)
    {
        // 오브젝트 파괴 또는 비활성화 로직
    }
}

플레이어 및 AI와의 복잡한 상호작용 시스템 설계

 상호작용 인터페이스 정의

UINTERFACE(MinimalAPI, Blueprintable)
class UInteractableInterface : public UInterface
{
    GENERATED_BODY()
};
 
class IInteractableInterface
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
    void Interact(AActor* Interactor);
};
 
UCLASS()
class AInteractableObject : public AActor, public IInteractableInterface
{
    GENERATED_BODY()
 
public:
    virtual void Interact_Implementation(AActor* Interactor) override;
};
 
void AInteractableObject::Interact_Implementation(AActor* Interactor)
{
    // 상호작용 로직 구현
    UE_LOG(LogTemp, Log, TEXT("%s interacted with %s"), *Interactor->GetName(), *GetName());
}

물리 기반 동적 오브젝트 구현

 흔들리는 나무 구현

UCLASS()
class ASwayingTree : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(VisibleAnywhere, Category = "Components")
    UStaticMeshComponent* TreeMesh;
 
    UPROPERTY(EditAnywhere, Category = "Sway")
    float SwayStrength;
 
    UPROPERTY(EditAnywhere, Category = "Sway")
    float SwaySpeed;
 
    virtual void Tick(float DeltaTime) override;
};
 
void ASwayingTree::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    FVector NewLocation = GetActorLocation();
    NewLocation.X += FMath::Sin(GetGameTimeSinceCreation() * SwaySpeed) * SwayStrength;
    NewLocation.Y += FMath::Cos(GetGameTimeSinceCreation() * SwaySpeed) * SwayStrength;
 
    SetActorLocation(NewLocation);
}

 부서지는 구조물

UCLASS()
class ADestructibleStructure : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(VisibleAnywhere, Category = "Components")
    UDestructibleComponent* DestructibleMesh;
 
    UFUNCTION(BlueprintCallable, Category = "Destruction")
    void ApplyDamage(float Damage, FVector HitLocation, FVector ImpactDirection);
};
 
void ADestructibleStructure::ApplyDamage(float Damage, FVector HitLocation, FVector ImpactDirection)
{
    if (DestructibleMesh)
    {
        DestructibleMesh->ApplyDamage(Damage, HitLocation, ImpactDirection, 0.0f);
    }
}

시간 또는 환경 조건에 따라 변화하는 오브젝트

 일주기 반응 식물

UCLASS()
class ADayCyclePlant : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(VisibleAnywhere, Category = "Components")
    UStaticMeshComponent* PlantMesh;
 
    UPROPERTY(EditAnywhere, Category = "Day Cycle")
    UMaterialInterface* DayMaterial;
 
    UPROPERTY(EditAnywhere, Category = "Day Cycle")
    UMaterialInterface* NightMaterial;
 
    UFUNCTION(BlueprintCallable, Category = "Day Cycle")
    void UpdateAppearance(bool bIsDay);
};
 
void ADayCyclePlant::UpdateAppearance(bool bIsDay)
{
    if (PlantMesh)
    {
        PlantMesh->SetMaterial(0, bIsDay ? DayMaterial : NightMaterial);
    }
}

다중 상태를 가진 상호작용 가능한 기계 구현

UENUM(BlueprintType)
enum class EMachineState : uint8
{
    Idle,
    Working,
    Broken,
    Repairing
};
 
UCLASS()
class AInteractiveMachine : public AActor, public IInteractableInterface
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Machine")
    EMachineState CurrentState;
 
    UFUNCTION(BlueprintCallable, Category = "Machine")
    void ChangeState(EMachineState NewState);
 
    virtual void Interact_Implementation(AActor* Interactor) override;
};
 
void AInteractiveMachine::ChangeState(EMachineState NewState)
{
    CurrentState = NewState;
    // 상태 변경에 따른 로직 (예 : 외관 변경, 사운드 재생 등)
}
 
void AInteractiveMachine::Interact_Implementation(AActor* Interactor)
{
    switch (CurrentState)
    {
    case EMachineState::Idle:
        ChangeState(EMachineState::Working);
        break;
    case EMachineState::Broken:
        ChangeState(EMachineState::Repairing);
        break;
    // 다른 상태에 대한 처리
    }
}

대규모 레벨에서의 동적 오브젝트 관리 전략

  1. 스트리밍 레벨을 활용한 동적 로딩
ULevelStreaming* StreamingLevel = UGameplayStatics::GetStreamingLevel(GetWorld(), FName("LevelToStream"));
if (StreamingLevel)
{
    StreamingLevel->SetShouldBeLoaded(true);
    StreamingLevel->SetShouldBeVisible(true);
}
  1. 거리 기반 활성화 / 비활성화
void ADynamicObjectManager::UpdateObjectsVisibility()
{
    FVector PlayerLocation = GetWorld()->GetFirstPlayerController()->GetPawn()->GetActorLocation();
    for (AActor* DynamicObject : ManagedObjects)
    {
        float Distance = FVector::Dist(PlayerLocation, DynamicObject->GetActorLocation());
        DynamicObject->SetActorHiddenInGame(Distance > VisibilityThreshold);
        DynamicObject->SetActorTickEnabled(Distance <= VisibilityThreshold);
    }
}

성능 최적화 기법

  1. LOD (Level of Detail) 시스템 활용
UCLASS()
class ADynamicLODActor : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY(VisibleAnywhere, Category = "LOD")
    UStaticMeshComponent* HighDetailMesh;
 
    UPROPERTY(VisibleAnywhere, Category = "LOD")
    UStaticMeshComponent* LowDetailMesh;
 
    void UpdateLOD(float DistanceToCamera);
};
 
void ADynamicLODActor::UpdateLOD(float DistanceToCamera)
{
    bool bUseHighDetail = DistanceToCamera <= LODSwitchDistance;
    HighDetailMesh->SetVisibility(bUseHighDetail);
    LowDetailMesh->SetVisibility(!bUseHighDetail);
}
  1. 인스턴싱 기법
UPROPERTY(VisibleAnywhere, Category = "Instancing")
UInstancedStaticMeshComponent* InstancedMeshComponent;
 
// 많은 수의 동일한 오브젝트 생성 시
InstancedMeshComponent->AddInstance(FTransform(Location, Rotation, Scale));

흥미롭고 몰입감 있는 상호작용 시스템 설계 팁

  1. 다단계 상호작용
  • 단순한 온 / 오프가 아닌 여러 단계의 상호작용 구현
  • 예 : 문을 열 때 손잡이를 돌리고, 밀어서 여는 2단계 과정
  1. 환경 반응형 상호작용
  • 주변 환경 조건에 따라 다르게 반응하는 오브젝트 설계
  • 예 : 비 오는 날에만 작동하는 기계, 특정 시간에만 열리는 포탈
  1. 연쇄 반응 시스템
  • 하나의 상호작용이 여러 오브젝트에 영향을 미치도록 설계
  • 예 : 하나의 스위치로 여러 기계가 순차적으로 작동

동적 오브젝트와 상호작용이 게임 경험에 미치는 영향

  1. 세계의 생동감 증대
  • 동적으로 변화하는 오브젝트들로 게임 세계가 살아있는 듯한 느낌 제공
  • 플레이어 행동에 반응하는 환경으로 현실감 강화
  1. 탐험 동기 부여
  • 상호작용 가능한 요소들로 플레이어의 호기심 자극
  • 숨겨진 기능이나 효과 발견의 즐거움 제공
  1. 게임플레이의 다양성 확대
  • 다양한 상호작용으로 플레이 스타일의 폭 확장
  • 창의적 문제 해결 방식 유도
  1. 몰입감과 현실감 향상
  • 물리적으로 그럴듯한 반응으로 게임 세계의 일관성 증가
  • 직관적 상호작용으로 플레이어의 이해도 향상
  1. 내러티브 강화
  • 오브젝트 상호작용을 통한 스토리 전개 및 백스토리 전달
  • 환경을 통한 효과적인 스토리텔링 구현
  1. 학습 곡선 최적화
  • 점진적 복잡도 증가로 자연스러운 게임 시스템 학습 유도
  • 초보자와 숙련자 모두를 위한 깊이 있는 상호작용 제공
  1. 감정적 연결 강화
  • 반응하는 오브젝트로 플레이어와 게임 세계 간 유대감 형성
  • 특정 오브젝트에 대한 애착 발생 가능
  1. 재플레이 가치 증가
  • 다양한 상호작용 가능성으로 새로운 경험 제공
  • 이전에 발견하지 못한 요소들로 인한 재미 창출
  1. 플레이어 에이전시 강화
  • 게임 세계에 실질적 영향을 미치는 경험 제공
  • 플레이어 선택과 행동의 중요성 부각
  1. 퍼즐과 도전 요소 제공
  • 복잡한 상호작용 시스템을 통한 흥미로운 퍼즐 요소 구현
  • 환경 상호작용 기반의 새로운 형태의 도전 과제 제시

 동적 오브젝트와 풍부한 상호작용 시스템은 게임의 전반적인 품질과 플레이어 경험을 크게 향상시킵니다. 이는 단순한 시각적 요소나 게임플레이 메커닉을 넘어, 플레이어가 게임 세계와 깊이 있게 연결되고 교감할 수 있게 하는 핵심적인 요소입니다. 잘 설계된 동적 오브젝트와 상호작용 시스템은 게임에 생명력을 불어넣어, 플레이어에게 더욱 몰입감 있고 기억에 남는 경험을 제공할 수 있습니다.