총 4개의 스킬 중 2개의 이펙트를 구현해보자.
실제 데미지를 입히기 직전까지만 디자인 해보고 그 뒤로는 몬스터를 만든 뒤 제작하겠다.
모든 스킬은 메이플의 궁수 스킬을 참고하였다.
일반 공격을 포함한 모든 스킬은 APMProjectile 클래스를 상속받는다.
APMProjectile은 스킬의 데미지를 설정하고 각 클래스(스킬)마다 초기화 하는 가상 함수를 가지고 있어, 우리가 사용하기 편하게 설계한다.
PMProjectile
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PMProjectile.generated.h"
UCLASS()
class PROJECTM_API APMProjectile : public AActor
{
GENERATED_BODY()
public:
APMProjectile();
protected:
int32 Damage;
public:
virtual void FireArrow(AActor* InOwner, FVector InShootLocation, FRotator InShootRotation, int32 InDamage = 0);
};
스킬에는 현재 데미지만 기록할 계획이고 나중에 스킬 컴포넌트를 플레이어에게 달아주어 거기서 최종 데미지를 여기로 넘겨주게 될 것이다.
Skill 1 - ArrowRain
말 그래도 하늘에서 화살이 우수수 떨어지는 스킬이다.
궁수의 입장에서는 멀리서 적을 타격하지만, 주변에 적들이 몰려와 고립이되면 곤란하다. 그때를 타개하기 위해 주변의 다수의 적에게 피해를 주는 다수기를 추가해보자.
ArrowRain Actor
에로우 레인의 액터는 다음과 같이 구성된다.
SphereComponent
- StartEffect
- ArrowRainEffect
- ProjectileMovement
각 역할은 간단하다.
초기에 구상했을 때는 SphereComponent를 통해 주변 적들을 감지하는게 목적이었다. 하지만 구현할 때 연습겸 다른 방식으로 구현했기에 사용하지는 않고 외적인 모습을 보기 위해 남겨두었다.
StartEffect는 말그대로 화살을 하늘로 쏘는 이펙트이다. 맨 처음 화살이 발사 될 때의 이펙트를 말하며, 이 이펙트가 온전히 재생된 후 1초뒤 하늘에서 화살이 떨어지도록 할 것이다.
ArrowRainEffect는 하늘에서 화살이 떨어지는 이펙트로 원하는 타이밍에 화살이 떨어지게 설계할 것이다.
ProjectileMovement는 StartEffect를 하늘로 날려주기 위함이다.
Construct
위에서 설계한대로 생성자를 구성해준다.
MaxHitCount는 에로우 레인이 데미지를 주는 최대 횟수를 의미한다.
APMArrowRain::APMArrowRain()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
TargetArea = CreateDefaultSubobject<USphereComponent>(TEXT("TargetArea"));
RootComponent = TargetArea;
StartEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("StartEffect"));
StartEffect->SetupAttachment(TargetArea);
StartEffect->bAutoActivate = false;
RainOfArrowEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("RainOfArrowEffect"));
RainOfArrowEffect->SetupAttachment(TargetArea);
RainOfArrowEffect->bAutoActivate = false;
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovement"));
ProjectileMovement->SetUpdatedComponent(StartEffect);
ProjectileMovement->InitialSpeed = 3000.f; // Set initial speed
ProjectileMovement->MaxSpeed = 3000.f; // Set maximum speed
ProjectileMovement->bRotationFollowsVelocity = false; // Rotate based on velocity
ProjectileMovement->bShouldBounce = false; // Disable bouncing
ProjectileMovement->ProjectileGravityScale = 0.0f; // Add some gravity
InitialLifeSpan = 6.f;
MaxHitCount = 4;
CurHitCount = 0;
}
화살 생성
이제 플레이어에서 Q 버튼을 누르면 ArrowRain을 원하는 위치에 생성하고 이펙트를 실행해보자.
case EAttackTypes::SkillQ:
if (ArrowRain)
{
if (SkeletalMesh->DoesSocketExist(TEXT("arrow_pos")))
{
FVector SpawnLocation = SkeletalMesh->GetSocketLocation(TEXT("arrow_pos"));
FRotator SpawnRotation = SkeletalMesh->GetSocketRotation(TEXT("arrow_pos"));
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
APMProjectile* Arrow = Cast<APMProjectile>(GetWorld()->SpawnActor<APMArrowRain>(ArrowRain, SpawnLocation, SpawnRotation, SpawnParams));
if (Arrow)
{
Arrow->FireArrow(this, SpawnLocation, SpawnRotation, 0);
}
}
}
break;
FireArrow를 구현 해야한다. 이는 위에서 PMProjectile을 상속받았기 때문에 가능하다.
void APMArrowRain::HitOfRain()
{
// 1초마다 데미지
if (CurHitCount >= MaxHitCount) return;
CurHitCount++;
FVector Center = GetActorLocation();
const float SphereRadius = 500.f;
FCollisionShape CollisionSphere = FCollisionShape::MakeSphere(SphereRadius);
FCollisionQueryParams Params(FName(TEXT("Attack")), false, this);
Params.MobilityType = EQueryMobilityType::Any;
TArray<FOverlapResult> Overlaps;
DrawDebugSphere(GetWorld(), Center, SphereRadius, 12, FColor::Green, false, 1.0f, 0, 2.0f);
bool bIsHit = GetWorld()->OverlapMultiByChannel(
Overlaps, // 결과를 저장할 배열
Center, // 중심점
FQuat::Identity, // 회전
ECC_PMAttack, // 채널
CollisionSphere, // 콜리전 형태
Params // 추가 파라미터
);
if (bIsHit)
{
for (auto& Overlap : Overlaps)
{
AActor* OverlapActor = Overlap.GetActor();
if (OverlapActor == Owner)
{
continue;
}
if (OverlapActor)
{
// 데미지를 받는 인터페이스를 생성해서 인터페이스가 구현됐는지 확인
}
}
}
TWeakObjectPtr<APMArrowRain> WeakThis(this);
GetWorld()->GetTimerManager().SetTimer(RainTimer, FTimerDelegate::CreateLambda([WeakThis]()
{
APMArrowRain* StrongThis = WeakThis.Get();
if (!StrongThis)
{
return;
}
if (StrongThis->CurHitCount >= StrongThis->MaxHitCount) return;
StrongThis->HitOfRain();
}), 1.0f, false);
}
void APMArrowRain::FireArrow(AActor* InOwner, FVector InShootLocation, FRotator InShootRotation, int32 InDamage)
{
Super::FireArrow(InOwner, InShootLocation, InShootRotation, InDamage);
SetOwner(InOwner);
if (ProjectileMovement)
{
StartEffect->SetWorldLocationAndRotation(InShootLocation, InShootRotation);
RainOfArrowEffect->SetWorldLocationAndRotation(InShootLocation, FRotator(0.f,0.f,0.f));
StartEffect->ActivateSystem();
ProjectileMovement->Velocity = InShootRotation.Vector() * ProjectileMovement->InitialSpeed;
TWeakObjectPtr<APMArrowRain> WeakThis(this);
GetWorld()->GetTimerManager().SetTimer(RainTimer, FTimerDelegate::CreateLambda([WeakThis]()
{
APMArrowRain* StrongThis = WeakThis.Get();
if (!StrongThis)
{
return;
}
if (StrongThis->StartEffect)
{
StrongThis->StartEffect->SetVisibility(false);
}
if (StrongThis->RainOfArrowEffect)
{
StrongThis->RainOfArrowEffect->ActivateSystem();
}
StrongThis->HitOfRain();
}), 1.0f, false);
}
}
타이머 시스템을 이용해 1초 뒤에 Rain 이펙트가 작동하게 한다. 이때 WeakPtr을 이용한다. 객체가 혹시모르게 삭제될 가능성을 의심해서 안전하게 체크를 한다.
그리고 HitOfRain 함수를 통해 타격 범위의 구형을 만들고 그 안에서 Overlap이 발생되는 오브젝트들을 가져와 추후 만들 Damage를 입히는 인터페이스를 가지고 있는 오브젝트에만 데미지를 줄 계획이다.
Skill 2 - Strafe
스트레이프는 화살 4발을 연속적으로 빠르게 연사하는 스킬이다.
이는 일반 공격의 화살을 재 사용하고 일반 공격보다 훨씬 빠르게 동작하도록 애니메이션만 수정해주면 일반 공격을 그대로 사용할 수 있다.
case EAttackTypes::SkillE:
if (NormalArrow)
{
FVector SpawnLocation = Center->GetComponentLocation();
FRotator SpawnRotation = Center->GetComponentRotation();
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.Instigator = GetInstigator();
APMProjectile* Arrow = Cast<APMProjectile>(GetWorld()->SpawnActor<APMArrowProjectile>(NormalArrow, SpawnLocation, SpawnRotation, SpawnParams));
if (Arrow)
{
Arrow->FireArrow(this, SpawnLocation, SpawnRotation, 0);
}
}
break;
FireArrow 노티파이 스테이트가 발동할때마다 위의 함수가 작동되는데 그걸 4번 해주면된다. 첫번째 노티파이는 준비자세이므로 따른 행동은 없다.
따라서 위와 같이 일반 공격을 그대로 가져와 4발을 발사하게 해주면된다.
결과
'Unreal Engine 5 > ProjectM' 카테고리의 다른 글
[UE5] ProjectM - 스킬 디자인 구성2 (0) | 2025.01.15 |
---|---|
[UE5] ProjectM - 공격 애니메이션과 화살 발사 (0) | 2025.01.03 |
[UE5] ProjectM - 점프와 타이머, 카메라 범위 제한 (1) | 2024.12.24 |
[UE5] ProjectM - 캐릭터 움직임과 애니메이션 (1) | 2024.12.23 |