콘텐츠로 이동

UE5 Water System — 물 시스템 구현

UE5의 Water Plugin은 물리 기반 물 시스템으로 강(River), 호수(Lake), 바다(Ocean), 커스텀 WaterBody를 제공합니다. Gerstner Wave 시뮬레이션, 부력, 수중 포스트 프로세스를 내장하며 Nanite Tessellation(UE 5.3+)과 결합해 사실적인 수면을 렌더링합니다.


Edit > Plugins > Water → 활성화
Edit > Plugins > Water Extras → 활성화 (선택)
프로젝트 재시작 후:
Place Actors > Water > WaterBodyOcean / WaterBodyLake / WaterBodyRiver

// WaterBodyOcean 기본 설정 (BP에서도 가능)
// 파도 설정
// Details > Wave Settings:
// - Gerstner Wave: 진폭, 파장, 속도, 방향 설정
// - Wave Source: 단일 방향 / 멀티 방향
// 머티리얼 설정
// Water Surface Material: M_Ocean (내장)
// Underwater Post Process Material: 수중 뷰 색조
// 물리 설정
// Collision: Water 채널 활성화
// Buoyancy: 자동 부력 지원

#include "BuoyancyComponent.h"
// 배 또는 부유 오브젝트에 부력 추가
UCLASS()
class ABoat : public APawn
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* HullMesh;
UPROPERTY(VisibleAnywhere)
UBuoyancyComponent* BuoyancyComp;
public:
ABoat()
{
HullMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Hull"));
HullMesh->SetSimulatePhysics(true);
BuoyancyComp = CreateDefaultSubobject<UBuoyancyComponent>(
TEXT("Buoyancy"));
// 부력 폰툰 포인트 설정 (선체의 4개 모서리)
FSphericalPontoon Pontoon;
Pontoon.Radius = 50.f;
Pontoon.CenterSocket = FName("PontoonFL");
BuoyancyComp->Pontoons.Add(Pontoon);
Pontoon.CenterSocket = FName("PontoonFR");
BuoyancyComp->Pontoons.Add(Pontoon);
Pontoon.CenterSocket = FName("PontoonRL");
BuoyancyComp->Pontoons.Add(Pontoon);
Pontoon.CenterSocket = FName("PontoonRR");
BuoyancyComp->Pontoons.Add(Pontoon);
}
};

#include "WaterBodyActor.h"
#include "WaterSubsystem.h"
// 특정 위치의 수면 높이 조회
float GetWaterHeightAt(UWorld* World, FVector Location)
{
UWaterSubsystem* WaterSubsystem =
UWaterSubsystem::GetWaterSubsystem(World);
if (!WaterSubsystem) return 0.f;
FWaterBodyQueryResult Result;
EWaterBodyQueryFlags Flags =
EWaterBodyQueryFlags::ComputeLocation |
EWaterBodyQueryFlags::ComputeNormal |
EWaterBodyQueryFlags::ComputeDepth;
WaterSubsystem->QueryWaterBodyAtLocation(Location, Flags, Result);
if (Result.IsInWater())
{
return Result.GetWaterSurfaceLocation().Z;
}
return 0.f;
}
// 물 안에 있는지 확인
bool IsUnderwater(AActor* Actor)
{
auto* WaterSub = UWaterSubsystem::GetWaterSubsystem(
Actor->GetWorld());
if (!WaterSub) return false;
FWaterBodyQueryResult Result;
WaterSub->QueryWaterBodyAtLocation(
Actor->GetActorLocation(),
EWaterBodyQueryFlags::ComputeDepth,
Result);
return Result.IsInWater() && Result.GetWaterPlaneDepth() > 0.f;
}

// 수중 진입/탈출 감지 및 포스트 프로세스 적용
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere)
UPostProcessComponent* UnderwaterPP;
bool bIsUnderwater = false;
virtual void Tick(float DeltaTime) override
{
Super::Tick(DeltaTime);
bool bNowUnderwater = CheckUnderwater();
if (bNowUnderwater != bIsUnderwater)
{
bIsUnderwater = bNowUnderwater;
UnderwaterPP->bEnabled = bIsUnderwater;
if (bIsUnderwater)
OnEnterWater();
else
OnExitWater();
}
}
void OnEnterWater()
{
// 수중 음향 스냅샷 적용
// 이동 속도 감소
GetCharacterMovement()->MaxWalkSpeed *= 0.5f;
}
void OnExitWater()
{
GetCharacterMovement()->MaxWalkSpeed *= 2.f;
}
};

// 홍수 / 댐 열기 시뮬레이션
UCLASS()
class AFloodController : public AActor
{
GENERATED_BODY()
UPROPERTY(EditInstanceOnly)
AWaterBodyLake* TargetLake;
UPROPERTY(EditAnywhere)
float FloodRate = 10.f; // cm/s
float _targetHeight;
public:
void StartFlood(float TargetWaterHeight)
{
_targetHeight = TargetWaterHeight;
GetWorldTimerManager().SetTimer(
_floodTimer,
this, &AFloodController::UpdateFloodLevel,
0.1f, true);
}
private:
FTimerHandle _floodTimer;
void UpdateFloodLevel()
{
if (!TargetLake) return;
FVector Loc = TargetLake->GetActorLocation();
float currentZ = Loc.Z;
if (FMath::Abs(currentZ - _targetHeight) < 1.f)
{
GetWorldTimerManager().ClearTimer(_floodTimer);
return;
}
float newZ = FMath::FInterpTo(
currentZ, _targetHeight, 0.1f, FloodRate);
TargetLake->SetActorLocation(
FVector(Loc.X, Loc.Y, newZ));
}
};

// 날씨 시스템 연동: 폭풍 시 파도 증가
void AWeatherSystem::SetStormIntensity(float Intensity)
{
TArray<AActor*> WaterBodies;
UGameplayStatics::GetAllActorsOfClass(
GetWorld(), AWaterBody::StaticClass(), WaterBodies);
for (AActor* Actor : WaterBodies)
{
if (auto* Ocean = Cast<AWaterBodyOcean>(Actor))
{
// 파도 진폭 조정 (머티리얼 파라미터)
Ocean->GetWaterMeshComponent()
->SetScalarParameterValueOnMaterials(
"WaveAmplitude",
FMath::Lerp(0.3f, 2.0f, Intensity));
}
}
}

UE5 Water System은 WaterBody 액터 배치만으로 물리 기반 물을 즉시 얻을 수 있습니다. 부유 오브젝트는 UBuoyancyComponent에 폰툰 포인트를 설정하고, 수면 높이는 UWaterSubsystem::QueryWaterBodyAtLocation으로 정확하게 쿼리하세요. 수중 포스트 프로세스와 음향 스냅샷을 수위 체크와 연동하면 완성도 높은 수중 경험을 구현할 수 있습니다.