발사속도 나중에 DT로 뺄 수 있으면 빼기

 

궤적은 로컬에서만

매 프레임 갱신되는 걸 복제하면 네트워크 낭비가 엄청남

// FDThrowItem.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "FDThrowItem.generated.h"

class USphereComponent;
class UProjectileMovementComponent;

UCLASS()
class FUNNYORDIE_API AFDThrowItem : public AActor
{
    GENERATED_BODY()
    
public: 
    AFDThrowItem();
    
    // 스턴 지속시간 (인벤토리에서 데이터 테이블 Duration을 넘겨받아 설정)
    void SetStunDuration(float Duration) { StunDuration = Duration; }
    
protected:
    virtual void BeginPlay() override;

    // 충돌 판정용 구체
    UPROPERTY(VisibleAnywhere, Category = "Projectile")
    USphereComponent* CollisionSphere;

    // 투사체 외형
    UPROPERTY(VisibleAnywhere, Category = "Projectile")
    class UStaticMeshComponent* ProjectileMesh;

    // 포물선 이동 처리 - UE가 제공하는 UProjectileMovementComponent 사용
    // 초기 속도만 주면 중력 받아 알아서 날아감
    UPROPERTY(VisibleAnywhere, Category = "Projectile")
    UProjectileMovementComponent* ProjectileMovement;

    // 맞았을 때 호출되는 콜백 (Hit 이벤트)
    UFUNCTION()
    void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor,
       UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit);

private:
    // 스턴 지속시간 (초) - 스폰 시 인벤토리가 SetStunDuration으로 설정
    float StunDuration = 5.f;
};
// FDThrowItem.cpp

#include "Item/FDThrowItem.h"
#include "Character/FDTaggerCharacter.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"

AFDThrowItem::AFDThrowItem()
{
    PrimaryActorTick.bCanEverTick = false;
    bReplicates = true; // 서버가 스폰하면 클라이언트에도 날아가는 게 보이게

    CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
    SetRootComponent(CollisionSphere);
    CollisionSphere->SetSphereRadius(20.f);
    
    // 프리셋: 막히는 충돌이어야 Hit 이벤트가 발생함
    CollisionSphere->SetCollisionProfileName(TEXT("BlockAllDynamic"));
    
    // 물리 시뮬레이션 콜리전에서 Hit 이벤트를 받겠다는 설정
    CollisionSphere->SetNotifyRigidBodyCollision(true);

    // 외형 메시 - 콜리전에 부착, 자체 충돌은 없음 (판정은 구체가 담당)
    ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ProjectileMesh"));
    ProjectileMesh->SetupAttachment(RootComponent);
    ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);

    // 포물선 이동 컴포넌트
    ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
    ProjectileMovement->InitialSpeed = 1200.f;   // 발사 초기 속도
    ProjectileMovement->MaxSpeed = 1200.f;
    ProjectileMovement->ProjectileGravityScale = 1.f; // 1이면 정상 중력 (포물선), 0이면 직선
    ProjectileMovement->bRotationFollowsVelocity = true; // 날아가는 방향으로 메시가 회전
}

void AFDThrowItem::BeginPlay()
{
    Super::BeginPlay();

    // Hit 판정은 서버 권한으로만
    if (HasAuthority())
    {
       CollisionSphere->OnComponentHit.AddDynamic(this, &AFDThrowItem::OnHit);
    }
}

void AFDThrowItem::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor,
    UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
    // 맞은 대상이 술래인지 확인
    AFDTaggerCharacter* Tagger = Cast<AFDTaggerCharacter>(OtherActor);
    if (Tagger)
    {
       // 나중에 술래 멈추는 거 여기에 넣을 거임
       
       UE_LOG(LogTemp, Warning, TEXT("[투사체] 술래 맞춤]"));
    }
    else
    {
       UE_LOG(LogTemp, Log, TEXT("[투사체] 술래 아닌 대상/지형에 맞음: %s"),
          OtherActor ? *OtherActor->GetName() : TEXT("None"));
    }

    // 술래든 벽이든 한 번 맞으면 투사체는 사라짐
    Destroy();
}

 

 

에디터 작업 - TaggerStun 행에 투사체 클래스 연결

DT_ItemData의 TaggerStun 행에서 ThrowItemClass에 투사체 연결해야 스폰됨

  1. BP_FDThrowItem 만들기
  2. 메시 설정
  3. DT_ItemData -> TaggerStun 행 -> ThrowItemClass에 BP_FDThrowItem 선택

 

주요 설정

1) SetCollisionProfileName("BlockAllDynamic") + SetNotifyRigidBodyCollision(true)
Hit 이벤트는 콜리전이 막히는(Block) 설정이어야 발생

박스 줍기는 통과하는 Overlap이라 QueryOnly였지만 투사체는 부딪혀야 하니 Block 프리셋을 쓰고, SetNotifyRigidBodyCollision(true)로 켬 (Hit 이벤트를 알려줌 -> OnHit 불림)

 

2) bReplicates = true + 서버에서만 Hit 바인딩
투사체가 날아가는 모습은 모두가 봐야 하니 복제하고(bReplicates), 스턴을 적용할지 판정하는 Hit은 서버만 처리 

 


FDPlayerController.h

	// 투사체(스턴) 사용 키 (X) - 하이더 전용
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* IA_UseThrowItem;

 

콜백 함수 선언

	// 투사체 사용 입력 처리 -> 인벤토리에 투사체 사용 요청
	void Input_UseThrowItem(const FInputActionValue& Value);

 

FDPlayerController.cpp

바인딩 (SetupInputComponent에 추가):

	// 투사체 사용 입력 바인딩 (하이더 전용)
	if (IA_UseThrowItem)
	{
		EIC->BindAction(IA_UseThrowItem, ETriggerEvent::Started, this, &AFDPlayerController::Input_UseThrowItem);
	}

 

콜백 구현

void AFDPlayerController::Input_UseThrowItem(const FInputActionValue& Value)
{
	AFDHiderCharacter* Hider = Cast<AFDHiderCharacter>(GetPawn());
	if (!Hider) return;

	UFDItemInventoryComponent* Inventory =
		Hider->FindComponentByClass<UFDItemInventoryComponent>();
	if (!Inventory) return;

	// 서버에 투사체 사용 요청
	Inventory->Server_UseItem(EFDItemEffect::TaggerStun);
}

 

 

 

<여기는 이후 변경 예정>

FDItemInventoryComponent.cpp의 ExecuteEffect TaggerStun 채우기

	case EFDItemEffect::TaggerStun:
	{
		if (!Data.ThrowItemClass) break; // 던질 클래스가 없으면 중단

		// 하이더 앞쪽 약간 위에서 스폰 (자기 자신과 안 부딪히게)
		const FVector SpawnLoc = Hider->GetActorLocation()
			+ Hider->GetActorForwardVector() * 100.f
			+ FVector(0.f, 0.f, 50.f);

		// 하이더가 보는 방향으로 발사
		const FRotator SpawnRot = Hider->GetControlRotation();

		FActorSpawnParameters Params;
		Params.Instigator = Hider;
		Params.Owner = Hider;
		Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;

		AFDThrowItem* Throw = GetWorld()->SpawnActor<AFDThrowItem>(
			Data.ThrowItemClass, SpawnLoc, SpawnRot, Params);

		if (Throw)
		{
			Throw->SetStunDuration(Data.Duration);
		}
		break;
	}
#include "Item/FDThrowItem.h"

 

에디터 작업

X키 입력 세팅

  1. IA_UseThrowItem 인풋 액션 애셋 생성 (Digital)
  2. IMC에 IA_UseThrowItem + X키 매핑 추가
  3. FDPlayerController 블루프린트의 Input 카테고리에서 IA Use Throw Item 슬롯에 애셋 할당

 

알게된 것

UProjectileMovementComponent (엔진 제공 컴포넌트)

  • 액터에 붙이면 그 액터를 자동으로 날아가게 만들어줌
  • 초기 속도만 정해주면 중력 받아서 포물선 그리고 벽에 튕기게 할 수도 있음

UGameplayStatics::Blueprint_PredictProjectilePath (엔진 제공 함수)

  • 이 속도로 이 방향에 던지면 어디로 날아갈지를 미리 시뮬레이션해서 경로 점들을 돌려주는 함수
  • 실제로 던지기 전에 궤적을 계산할 때 씀 조준 궤적 미리보기의 핵심
  • 조준 중 매 프레임 이 함수로 궤적을 계산해서 화면에 선으로 그림

USplineComponent 또는 UNiagaraComponent (궤적을 화면에 그릴 때, 엔진 제공)

  • 계산한 궤적 점들을 실제 눈에 보이는 선/이펙트로 만드는 컴포넌트

GetControlRotation() (엔진 제공)

  • 캐릭터가 카메라로 보는 방향을 돌려줌
  • 이 회전값으로 투사체를 스폰하면 AFDThrowItem의 ProjectileMovement가 그 방향(스폰 시 액터의 forward)으로 날려줌

 

+ Recent posts