발사속도 나중에 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에 투사체 연결해야 스폰됨
- BP_FDThrowItem 만들기
- 메시 설정
- 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키 입력 세팅
- IA_UseThrowItem 인풋 액션 애셋 생성 (Digital)
- IMC에 IA_UseThrowItem + X키 매핑 추가
- FDPlayerController 블루프린트의 Input 카테고리에서 IA Use Throw Item 슬롯에 애셋 할당
알게된 것
UProjectileMovementComponent (엔진 제공 컴포넌트)
- 액터에 붙이면 그 액터를 자동으로 날아가게 만들어줌
- 초기 속도만 정해주면 중력 받아서 포물선 그리고 벽에 튕기게 할 수도 있음
UGameplayStatics::Blueprint_PredictProjectilePath (엔진 제공 함수)
- 이 속도로 이 방향에 던지면 어디로 날아갈지를 미리 시뮬레이션해서 경로 점들을 돌려주는 함수
- 실제로 던지기 전에 궤적을 계산할 때 씀 조준 궤적 미리보기의 핵심
- 조준 중 매 프레임 이 함수로 궤적을 계산해서 화면에 선으로 그림
USplineComponent 또는 UNiagaraComponent (궤적을 화면에 그릴 때, 엔진 제공)
- 계산한 궤적 점들을 실제 눈에 보이는 선/이펙트로 만드는 컴포넌트
GetControlRotation() (엔진 제공)
- 캐릭터가 카메라로 보는 방향을 돌려줌
- 이 회전값으로 투사체를 스폰하면 AFDThrowItem의 ProjectileMovement가 그 방향(스폰 시 액터의 forward)으로 날려줌
'프로젝트 > Funny Or Die (멀티플레이 술래잡기 게임)' 카테고리의 다른 글
| 투사체 명중 시 술래 스턴 (0) | 2026.07.13 |
|---|---|
| 투사체 조준 및 발사 (X키 조준 -> 좌클릭 발사) (0) | 2026.07.13 |
| 투명화 아이템 사용 기능 구현 (Z키) (0) | 2026.07.10 |
| 인벤토리 개편 (0) | 2026.07.10 |
| 아이템 데이터 정의 (구조체) (0) | 2026.07.09 |
