[TIL] 251203

๊น€์„ธํฌยท2025๋…„ 12์›” 3์ผ

โœ๏ธToday I Learned

๐Ÿ“… 2025-12-03

  • ๋นŒ๋”ฉ ์ปดํฌ๋„ŒํŠธ
  • ๊ฑด์ถ• ์‹œ์Šคํ…œ ์„ค์น˜ ๊ตฌ์—ญ ๊ฒ€์ฆ ๋กœ์ง
  • LightSource ๋ฒ”์œ„ ์ฒดํฌ๋กœ ์„ค์น˜ ๊ฐ€๋Šฅ ์˜์—ญ ์ œํ•œ
  • ์„œ๋ฒ„ ๊ถŒํ•œ ๊ธฐ๋ฐ˜ ๋ ˆ์‹œํ”ผ ์žฌ๋ฃŒ ๊ฒ€์ฆ ๋ฐ ์†Œ๋น„

๋นŒ๋”ฉ ์ปดํฌ๋„ŒํŠธ

์ƒ์ž๋‚˜ ์ œ์ž‘๋Œ€ ๋“ฑ ๊ฐ๊ฐ ๋‹ค๋ฅธ ์ƒํ˜ธ์ž‘์šฉ์ด ํ•„์š”ํ•œ ์•กํ„ฐ๋ฅผ ์ œ์ž‘ ๋ฐ ์„ค์น˜ํ•˜๋Š” ๋นŒ๋”ฉ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

์„ค์น˜ ๊ตฌ์—ญ ๊ฒ€์ฆ ์‹œ์Šคํ…œ

ValidatePlacement - 3๋‹จ๊ณ„ ๊ฒ€์ฆ

bool UTSBuildingComponent::ValidatePlacement(FHitResult HitResult)
{
    // 1. ์ง€๋ฉด ๊ฒ€์‚ฌ (๊ฒฝ์‚ฌ๋„ ์ฒดํฌ)
    const float DotProduct = FVector::DotProduct(HitResult.Normal, FVector::UpVector);
    if (DotProduct < 0.7f) return false;
    
    // 2. ์ถฉ๋Œ ์ฒดํฌ
    if (!CheckOverlap(HitResult.Location, CheckExtent)) return false;
    
    // 3. LightSource ๋ฒ”์œ„ ์ฒดํฌ
    if (!IsInLightSourceRange(HitResult.Location)) return false;
    
    return true;
}

๊ฒ€์ฆ ์ˆœ์„œ์˜ ์˜๋ฏธ

  • ์—ฐ์‚ฐ ๋น„์šฉ์ด ๋‚ฎ์€ ์ˆœ์„œ๋กœ ๋ฐฐ์น˜ (DotProduct โ†’ BoxOverlap โ†’ SphereOverlap)
  • Early exit ํŒจํ„ด์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ๊ฒ€์‚ฌ ์Šคํ‚ต
  • ๊ฐ ๋‹จ๊ณ„๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํŒจ ๊ฐ€๋Šฅ

IsInLightSourceRange - ๊ด‘์› ๋ฒ”์œ„ ๊ฒ€์‚ฌ

bool UTSBuildingComponent::IsInLightSourceRange(const FVector& Location) const
{
    TArray<AActor*> FoundActors;
    UKismetSystemLibrary::SphereOverlapActors(
        GetWorld(), Location, LightSourceDetectionRadius,
        ObjectTypes, nullptr, IgnoreActors, FoundActors);
    
    for (AActor* Actor : FoundActors)
    {
        AErosionLightSourceSubActor* LightSource = Cast<AErosionLightSourceSubActor>(Actor);
        if (LightSource && LightSource->GetLightscale() > 0.f)
        {
            return true;
        }
    }
    return false;
}

ํ•ต์‹ฌ ๋กœ์ง

  • 5000.f ๋ฐ˜๊ฒฝ ๋‚ด LightSource ํƒ์ƒ‰
  • GetLightscale() > 0.f ์ฒดํฌ๋กœ ํ™œ์„ฑํ™”๋œ ๊ด‘์›๋งŒ ์ธ์ •
  • ๊ฒŒ์ž„ ๋””์ž์ธ: ๋น›์˜ ์˜์—ญ ๋‚ด์—์„œ๋งŒ ๊ฑด์„ค ๊ฐ€๋Šฅ

CheckOverlap - ์ถฉ๋Œ ํ•„ํ„ฐ๋ง

bool UTSBuildingComponent::CheckOverlap(const FVector& Location, const FVector& Extent)
{
    // BoxOverlap์œผ๋กœ Pawn, WorldDynamic, WorldStatic ์ฒดํฌ
    bool bHasOverlap = UKismetSystemLibrary::BoxOverlapActors(...);
    if (!bHasOverlap) return true;
    
    for (AActor* OverlappedActor : OutActors)
    {
        if (OverlappedActor->IsA(APawn::StaticClass())) return false;
        if (OverlappedActor->ActorHasTag(FName("BlockBuilding"))) return false;
    }
    return true;
}

Extent ๊ณ„์‚ฐ

FBoxSphereBounds Bounds = PreviewMeshComp->GetStaticMesh()->GetBounds();
CheckExtent = Bounds.BoxExtent * 0.9f; // 90% ํฌ๊ธฐ๋กœ ์ฒดํฌ
  • 0.9๋ฐฐ๋กœ ์ค„์—ฌ์„œ ๊ฒฝ๊ณ„์„  false positive ๋ฐฉ์ง€
  • ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ๋” ๊ด€๋Œ€ํ•œ ์„ค์น˜ ๊ฒฝํ—˜

์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ๋ฐฑ - ํ”„๋ฆฌ๋ทฐ ์ƒ‰์ƒ

void UTSBuildingComponent::UpdatePreviewMesh(float DeltaTime)
{
    bCanPlace = ValidatePlacement(HitResult);
    
    // ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ๋จธํ‹ฐ๋ฆฌ์–ผ ์—…๋ฐ์ดํŠธ
    if (bCanPlace != bLastCanPlace && CachedDynamicMaterial)
    {
        CachedDynamicMaterial->SetVectorParameterValue(FName("Color"),
            bCanPlace ? FLinearColor::Green : FLinearColor::Red);
        bLastCanPlace = bCanPlace;
    }
}
  • bLastCanPlace ํ”Œ๋ž˜๊ทธ๋กœ ๋ถˆํ•„์š”ํ•œ ์—…๋ฐ์ดํŠธ ๋ฐฉ์ง€
  • ๋งค ํ”„๋ ˆ์ž„ ๊ฒ€์ฆํ•˜์ง€๋งŒ ์ƒ‰์ƒ์€ ๋ณ€๊ฒฝ ์‹œ์—๋งŒ

๋ ˆ์‹œํ”ผ ๊ธฐ๋ฐ˜ ์žฌ๋ฃŒ ๊ฒ€์ฆ

bool UTSBuildingComponent::CanBuild(int32 RecipeID, int32& OutResultID)
{
    if (!GetOwner()->HasAuthority()) return false; // ์„œ๋ฒ„์—์„œ๋งŒ
    
    FBuildingRecipeData RecipeData;
    if (!BuildingRecipeDataSub->GetBuildingRecipeDataSafe(RecipeID, RecipeData))
        return false;
    
    OutResultID = RecipeData.ResultItemID;
    
    // ์ธ๋ฒคํ† ๋ฆฌ ๊ฒ€์ฆ
    for (const FBuildingIngredientData& Ingredient : RecipeData.Ingredients)
    {
        int32 ItemCount = PlayerInventoryComp->GetItemCount(Ingredient.MaterialID);
        if (ItemCount < Ingredient.Count) return false;
    }
    return true;
}
  • ์„œ๋ฒ„ ๊ถŒํ•œ ์ฒดํฌ ํ•„์ˆ˜
  • ๋ ˆ์‹œํ”ผ ๋ฐ์ดํ„ฐ์™€ ์ธ๋ฒคํ† ๋ฆฌ ๋™์‹œ ๊ฒ€์ฆ
  • ConsumeIngredients์—์„œ ์‹ค์ œ ์žฌ๋ฃŒ ์†Œ๋น„

๐Ÿ’ก ๋А๋‚€ ์  (What I Felt)

๊ฒ€์ฆ ํŒŒ์ดํ”„๋ผ์ธ์˜ ์ˆœ์„œ๊ฐ€ ์ƒ๊ฐ๋ณด๋‹ค ์ค‘์š”ํ–ˆ๋‹ค. ๋น„์šฉ์ด ๋‚ฎ์€ ๊ฒ€์‚ฌ๋ถ€ํ„ฐ ์ˆ˜ํ–‰ํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ์—ฐ์‚ฐ์„ ์ค„์ด๋Š” ๊ฒŒ ์„ฑ๋Šฅ๊ณผ ์ง๊ฒฐ๋œ๋‹ค๋Š” ๊ฑธ ์ฒด๊ฐํ–ˆ๋‹ค.

ActorHasTag๋ฅผ ํ™œ์šฉํ•œ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ๋ฐฉ์‹์ด ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ํ™•์žฅ์„ฑ์ด ์ข‹๋‹ค. ๋ณต์žกํ•œ ํƒ€์ž… ์ฒดํฌ ๋Œ€์‹  ํƒœ๊ทธ ํ•˜๋‚˜๋กœ ์˜๋ฏธ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด์„œ ํŽธํ–ˆ๋‹ค.

๋งค ํ‹ฑ๋งˆ๋‹ค ์˜ค๋ฒ„๋žฉ์œผ๋กœ LightSource๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ถ€๋ถ„์ด ๋น„ํšจ์œจ์ ์ธ๊ฒƒ ๊ฐ™๋‹ค. ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ตœ์ ํ™”ํ•ด์•ผ ํ”ผ๋“œ๋ฐฑ๋„ ๋Šฆ์ง€ ์•Š๊ณ  ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ์„์ง€ ๋” ์ฐพ์•„๋ด์•ผ๊ฒ ๋‹ค.


0๊ฐœ์˜ ๋Œ“๊ธ€