일단 야구공처럼 날아가게 변경 + 조준점만 표시하게 변경 

 

UpdateTrajectory에서 선 그리는 루프 지우기

void UFDItemInventoryComponent::UpdateTrajectory()
{
    FVector StartLoc;
    FVector LaunchVelocity;
    GetThrowStartAndVelocity(StartLoc, LaunchVelocity);

    if (LaunchVelocity.IsNearlyZero()) return;

    FPredictProjectilePathParams PathParams;
    PathParams.StartLocation = StartLoc;
    PathParams.LaunchVelocity = LaunchVelocity;
    PathParams.ProjectileRadius = 20.f;
    PathParams.OverrideGravityZ = GetWorld()->GetGravityZ() * ThrowGravityScale;
    PathParams.bTraceWithCollision = true;
    PathParams.TraceChannel = ECC_Visibility;
    PathParams.MaxSimTime = 3.f;
    PathParams.SimFrequency = 15.f;
    PathParams.DrawDebugType = EDrawDebugTrace::None;
    PathParams.ActorsToIgnore.Add(GetOwner());

    FPredictProjectilePathResult PathResult;
    const bool bHit = UGameplayStatics::PredictProjectilePath(this, PathParams, PathResult);

    // 착지 지점만 표시
    if (bHit)
    {
       DrawDebugSphere(
          GetWorld(),
          PathResult.HitResult.Location,
          30.f,               // 반지름
          16,                 // 세그먼트
          FColor::Cyan,
          false,
          -1.f);              // 매 프레임 다시 그림
    }
}

 

값 변경 

 

1) BP_FDThrowItem -> ProjectileMovement 컴포넌트

  • Initial Speed: 2500
  • Max Speed: 2500
  • Projectile Gravity Scale: 0.5

2) BP_FDHiderCharacter -> ItemInventoryComp

  • Throw Speed: 2500
  • Throw Gravity Scale: 0.5
 

술래 스턴 효과 만들기

투사체에 술래가 맞으면
AFDThrowItem::OnHit 호출
술래 확인
술래를 StunDuration(5초)간 이동 불가로
5초 뒤 자동 해제
 
 

투사체는 맞자마자 사라짐 -> 타이머를 투사체에 걸면 안 됨

이동만 막는 건 스턴이 아님 -> 포획도 같이 막아야 함

 

1. 타이머

투사체(AFDThrowItem)는 Hit 즉시 Destroy()

-> 투사체에 5초 타이머를 걸면 액터가 사라지면서 타이머도 같이 사라짐 -> 해제 함수가 안 불림 -> 술래가 계속 못 움직임

지속되는 상태는 그 상태를 겪는 액터가 관리해야 함

 

AFDHiderCharacter::SetInvincible이 무적 상태와 타이머를 자기가 들고 있는 것과 같은 구조

-> 스턴 로직 + 타이머를 AFDTaggerCharacter에 둠 -> 투사체는 ApplyStun(StunDuration) 한 번 부르고 사라지기만 함

 

2. 이동과 포획 둘다 막기

Client_LockMovement() = SetIgnoreMoveInput(true)

-> WASD만 막힘 -> 마우스 좌클릭(포획)은 그대로 먹힘 -> 술래가 제자리에서 계속 하이더 잡을 수 있음 = 스턴 의미 없음

포획도 막아야 함

  • 입력 단계(Input_Attack)에서 막기 -> 클라이언트 조작으로 우회 가능
  • 서버 판정(Server_TryCapture)에서 막기 <- 이걸로

3. 중복 스턴은 무시

하이더 여럿이 연속으로 던지면 술래가 15초, 20초 계속 묶임

-> 이미 스턴 중이면 무시 (연장 없음)

 

FDTaggerCharacter.h

public:
    // 투사체에 맞았을 때 스턴 적용 (서버 전용)
    void ApplyStun(float Duration);

    // 현재 스턴 상태인지 (복제되므로 클라/서버 모두 사용 가능)
    bool IsStunned() const { return bIsStunned; }

    virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

private:
    // 스턴 상태 - 지금은 이펙트 없지만 나중에 연출 위해 복제해둠
    UPROPERTY(ReplicatedUsing = OnRep_bIsStunned)
    bool bIsStunned = false;

    UFUNCTION()
    void OnRep_bIsStunned();

    // 스턴 해제 타이머
    FTimerHandle StunExpireTimerHandle;

    void OnStunExpired();

 

FDTaggerCharacter.cpp

#include "Net/UnrealNetwork.h"

void AFDTaggerCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME(AFDTaggerCharacter, bIsStunned);
}

void AFDTaggerCharacter::ApplyStun(float Duration)
{
    if (!HasAuthority()) return;

    // 이미 스턴 중이면 무시 (연장 안 됨)
    if (bIsStunned)
    {
        UE_LOG(LogTemp, Log, TEXT("[술래] 이미 스턴 중 - 중복 스턴 무시"));
        return;
    }

    bIsStunned = true;
    OnRep_bIsStunned(); // 서버 수동 호출

    // 이동 입력 차단
    if (AFDPlayerController* FDPC = Cast<AFDPlayerController>(GetController()))
    {
        FDPC->Client_LockMovement();
    }

    GetWorldTimerManager().SetTimer(
        StunExpireTimerHandle,
        this,
        &AFDTaggerCharacter::OnStunExpired,
        Duration,
        false);

    UE_LOG(LogTemp, Warning, TEXT("[술래] 스턴 적용 - %.1f초"), Duration);
}

void AFDTaggerCharacter::OnStunExpired()
{
    if (!HasAuthority()) return;

    bIsStunned = false;
    OnRep_bIsStunned();

    // 이동 입력 복구
    if (AFDPlayerController* FDPC = Cast<AFDPlayerController>(GetController()))
    {
        FDPC->Client_UnlockMovement();
    }

    GetWorldTimerManager().ClearTimer(StunExpireTimerHandle);

    UE_LOG(LogTemp, Warning, TEXT("[술래] 스턴 해제"));
}

void AFDTaggerCharacter::OnRep_bIsStunned()
{
    // TODO: 스턴 이펙트(별 빙빙, 머티리얼 변화 등) 여기에 추가
}

 

포획 차단 - Server_TryCapture 수정

void AFDTaggerCharacter::Server_TryCapture_Implementation()
{
    // 스턴 중엔 포획 불가
    // 이동만 막으면 제자리에서 계속 잡을 수 있어서 스턴 의미가 없어짐
    if (bIsStunned)
    {
        UE_LOG(LogTemp, Log, TEXT("[술래] 스턴 중 - 포획 시도 무시"));
        return;
    }

    CaptureCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);

    FTimerHandle TempHandle;
    GetWorldTimerManager().SetTimer(TempHandle, [this]()
    {
        CaptureCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    }, 0.2f, false);
}

 

FDThrowItem.cpp - OnHit

void AFDThrowItem::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor,
    UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
    AFDTaggerCharacter* Tagger = Cast<AFDTaggerCharacter>(OtherActor);
    if (Tagger)
    {
        // 스턴 적용은 술래 자신이 관리 (타이머 포함)
        // 투사체는 바로 Destroy되니까 타이머를 여기서 걸면 같이 사라져버림
        Tagger->ApplyStun(StunDuration);

        UE_LOG(LogTemp, Warning, TEXT("[투사체] 술래 명중 - 스턴 %.1f초"), StunDuration);
    }
    else
    {
        UE_LOG(LogTemp, Log, TEXT("[투사체] 지형/기타 대상에 맞음"));
    }

    Destroy();
}

 

 

알게 된 것

"이 로직을 어디에 둘까"는 취향이 아니라 생명주기 문제

투사체는 즉시 사라지는 일회용 액터 -> 5초짜리 상태를 관리할 자격이 없음 상태를 겪는 쪽이 상태를 소유해야 함

캡슐화가 실제로 값어치를 발휘한 순간

포획으로 들어가는 통로가 Server_TryCapture 하나뿐이라 if (bIsStunned) return; 한 줄이면 끝 포획 로직이 여러 군데 흩어져 있었다면 전부 찾아서 막아야 했을 것

만들어둔 스텁이 쓰인 순간

Client_UnlockMovement는 "아직 쓰이는 곳은 없음"이라는 주석과 함께 만들어뒀던 함수 대칭성 지키려고 짝으로 만들어둔 게 이번에 실제로 쓰임

 

+ Recent posts