로봇 무기 구조 설계
- 무기는 근거리, 원거리 크게 두 범주로 분리
- Base 클래스에서 공통되는 부분 구현
- Base 클래스를 공통으로 상속하여 근거리, 원거리 무기 분리 구현
- AMeleeWeapon: 근거리
- ARangedWeapon: 원거리
- ABaseBullet
- 원거리 무기에서만 사용할 발사체 클래스
- 해당 클래스를 상속한 BP로 특정 발사체 클래스 구현하여 사용 예정 ㅡ> 크게 달라지는 부분이 있다면 새로 클래스 생성
- Projectile 컴포넌트 사용 예정
- 근거리 데미지 판정의 경우 이전에 구현한 WeaponTrace(AnimNotifyState)와 CombatComponent를 이용
- 원거리 데미지 판정의 경우 발사체 클래스에서 직접 데미지 적용
ABaseBullet (기본 발사체 클래스)
헤더
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BaseBullet.generated.h"
class UBoxComponent;
class UNiagaraComponent;
class URadialForceComponent;
class UProjectileMovementComponent;
UCLASS()
class CR4S_API ABaseBullet : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ABaseBullet();
public:
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
UFUNCTION()
void OnOverlapBegin(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult & SweepResult
);
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category="Damage")
float BaseDamage;
#pragma region Components
protected:
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category="Components")
USceneComponent* SceneComponent;
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category="Components")
UBoxComponent* CollisionComponent;
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category="Components")
UNiagaraComponent* NiagaraComponent;
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category="Components")
UProjectileMovementComponent* ProjectileMovementComponent;
#pragma endregion
#pragma region Effects
protected:
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category="Effects")
UParticleSystem* ImpactParticle;
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category="Effects")
USoundBase* ImpactSound;
#pragma endregion
};
소스
#include "BaseBullet.h"
#include "CR4S.h"
#include "NiagaraComponent.h"
#include "Character/Characters/ModularRobot.h"
#include "Components/BoxComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "PhysicsEngine/RadialForceComponent.h"
// Sets default values
ABaseBullet::ABaseBullet()
{
PrimaryActorTick.bCanEverTick = false;
SceneComponent=CreateDefaultSubobject<USceneComponent>(FName("Root"));
SetRootComponent(SceneComponent);
CollisionComponent=CreateDefaultSubobject<UBoxComponent>(TEXT("Collision"));
CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
CollisionComponent->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
CollisionComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
CollisionComponent->SetGenerateOverlapEvents(true);
CollisionComponent->SetupAttachment(RootComponent);
NiagaraComponent=CreateDefaultSubobject<UNiagaraComponent>(TEXT("Niagara"));
NiagaraComponent->SetupAttachment(CollisionComponent);
ProjectileMovementComponent=CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
ProjectileMovementComponent->InitialSpeed=2000.f;
ProjectileMovementComponent->MaxSpeed=3000.f;
ProjectileMovementComponent->bRotationFollowsVelocity=true;
ProjectileMovementComponent->bShouldBounce=false;
ProjectileMovementComponent->UpdatedComponent=RootComponent;
ImpactParticle=nullptr;
ImpactSound=nullptr;
BaseDamage=100.f;
}
// Called when the game starts or when spawned
void ABaseBullet::BeginPlay()
{
Super::BeginPlay();
if (CollisionComponent)
{
CollisionComponent->OnComponentBeginOverlap.AddDynamic(this,&ABaseBullet::OnOverlapBegin);
if (AActor* OwnerActor=GetOwner())
{
CollisionComponent->IgnoreActorWhenMoving(OwnerActor,true);
}
}
}
void ABaseBullet::OnOverlapBegin(
UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult & SweepResult
)
{
FVector OverlapLocation=GetActorLocation();
if (ImpactParticle)
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
ImpactParticle,
OverlapLocation,
FRotator::ZeroRotator,
true
);
}
if (ImpactSound)
{
UGameplayStatics::PlaySoundAtLocation(
this,
ImpactSound,
OverlapLocation,
1.0f,
1.0f
);
}
if (OtherActor&&OtherActor!=this)
{
AActor* OwnerActor=GetOwner();
float FinalDamage=BaseDamage;
if (OwnerActor)
{
if (AModularRobot* OwnerRobot=Cast<AModularRobot>(OwnerActor))
{
float AttackMultiplier=OwnerRobot->GetAttackPowerMultiplier();
FinalDamage*=AttackMultiplier;
}
}
UGameplayStatics::ApplyDamage(
OtherActor,
FinalDamage,
OwnerActor ? OwnerActor->GetInstigatorController():nullptr,
this,
UDamageType::StaticClass()
);
}
Destroy();
}
// Called every frame
void ABaseBullet::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
- SceneComponent: 루트 컴포넌트로 사용할 컴포넌트
- CollisionComponent: 충돌 판정을 위해 사용할 콜리전 컴포넌트
- NiagaraComponent: 메시 없이 나이아가라 이펙트로 발사체 표현 예정
- ProjectileMovementComponent: 발사체 관련 움직임을 담당할 컴포넌트
- BaseDamage: 무기에서 총알 생성 과정에 설정될 데미지 기본값
- ProjectileMovementComponent->UpdatedComponent=RootComponent: 발사체가 움직일 때, 해당 컴포넌트를 함께 옮김
ㅡ> 이를 통해 루트 컴포넌트에 부착되어 있는 콜리전, 이펙트 컴포넌트들이 함께 이동
ㅡ> 해당 설정을 하지 않으면 발사체가 생성된 지점에 컴포넌트가 그대로 고정되어 원하는 지점에서 이펙트 발생하지 않음 - OnOverlapBegin(): 오버랩 이벤트에 바인딩될 함수
- 오버랩이 발생된 지점(피격 지점)을 기준으로 파티클, 사운드 이펙트 생성
- 자신을 제외한 액터에 데미지 적용(ApplyDamage)
ㅡ> 그 과정에서 로봇으로부터 공격력 계수를 받아 데미지 연산 (추후 변경 가능)
참고자료