- 마법사의 원거리 공격(기본 공격) 구현
- 우클릭으로 에너지를 모으는 동작 추가(추후 스킬에 사용 예정)
- 기존 포스팅에서 구현했던 궁수와 유사합니다.
- 애니메이션을 추가하고, InputAction을 추가하는 부분까지는 생략하도록 하겠습니다.
- 스태프 무기를 장착하고, 기본 공격을 위한 소켓 3가지를 추가해주도록 합니다.
- 마법사는 기본 공격으로 에너지볼을 스폰하여 발사합니다.
- 스태프는 기존 검과 활을 절반씩 섞은 느낌입니다.
- 에너지 볼을 발사하기 위한 클래스 정보를 가지고 있으며 공격 시 에너지 볼을 스폰하여 발사합니다.
MMStaffWeapon Class
- 기존 화살을 소환하고 발사하는 로직과 유사합니다.
- 발사 동작에 시간이 걸리기에 스폰과 동시에 발사할 방향을 저장합니다.
// MMStaffWeapon Header
#pragma once
#include "CoreMinimal.h"
#include "Item/MMWeapon.h"
#include "MMStaffWeapon.generated.h"
/**
*
*/
UCLASS()
class MYSTICMAZE_API AMMStaffWeapon : public AMMWeapon
{
GENERATED_BODY()
public:
AMMStaffWeapon();
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
public:
// 생성 및 발사 함수
void SpawnEnergyBall();
void ShootEnergyBall();
protected:
// 타겟 위치를 구하기 위한 함수
void SetFireLocation();
// 에너지볼 클래스
UPROPERTY(VisibleAnywhere, Category = "Quiver", meta = (AllowPrivateAccess = "true"))
TSubclassOf<AActor> EnergyBallClass;
// 현재 스폰된 에너지볼 객체
UPROPERTY()
TObjectPtr<class AMMEnergyBall> TempEnergyBall;
// 에너지 볼을 스폰하기 위한 소켓
FName EnergyBallSocket;
private:
FVector FireLocation;
};
// MMStaffWeapon Cpp
#include "Item/MMStaffWeapon.h"
#include "Item/MMEnergyBall.h"
#include "Interface/MMPlayerVisualInterface.h"
#include "Collision/MMCollision.h"
#include "GameFramework/Character.h"
#include "Camera/CameraComponent.h"
#include "DrawDebugHelpers.h"
AMMStaffWeapon::AMMStaffWeapon()
{
//PrimaryActorTick.bCanEverTick = true;
static ConstructorHelpers::FClassFinder<AActor>EnergyBallRef(TEXT("/Script/Engine.Blueprint'/Game/MysticMaze/Items/Weapons/BP_EnergyBall.BP_EnergyBall_C'"));
if (EnergyBallRef.Succeeded())
{
EnergyBallClass = EnergyBallRef.Class;
}
WeaponType = EWeaponType::WT_Staff;
BaseSocketName = TEXT("StaffPosition");
DrawSocketName = TEXT("StaffSocket");
EnergyBallSocket = TEXT("EnergyBallSocket");
}
void AMMStaffWeapon::BeginPlay()
{
Super::BeginPlay();
}
void AMMStaffWeapon::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
}
void AMMStaffWeapon::SpawnEnergyBall()
{
TempEnergyBall = Cast<AMMEnergyBall>(GetWorld()->SpawnActor(EnergyBallClass));
if (TempEnergyBall)
{
// 에너지 볼을 소켓에 부착합니다.
ACharacter* PlayerCharacter = Cast<ACharacter>(GetOwner());
if (PlayerCharacter)
{
TempEnergyBall->SetOwner(Owner);
TempEnergyBall->AttachToComponent(PlayerCharacter->GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, EnergyBallSocket);
}
// 발사할 위치를 선정합니다.
SetFireLocation();
}
}
void AMMStaffWeapon::ShootEnergyBall()
{
if (TempEnergyBall)
{
// 부모 액터로부터 부착 해제
TempEnergyBall->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
TempEnergyBall->Fire(FireLocation);
TempEnergyBall = nullptr;
}
}
void AMMStaffWeapon::SetFireLocation()
{
// 플레이어의 카메라에서 화면의 중앙으로 LineTrace를 진행합니다.
IMMPlayerVisualInterface* PlayerCharacter = Cast<IMMPlayerVisualInterface>(GetOwner());
if (PlayerCharacter)
{
// 충돌 결과 반환용
FHitResult HitResult;
// 시작 지점 (카메라의 위치)
FVector Start = PlayerCharacter->GetPlayerCamera()->GetComponentLocation();
// 종료 지점 (카메라 위치 + 카메라 전방벡터 * 20000)
float Distance = 20000;
FVector End = Start + (PlayerCharacter->GetPlayerCamera()->GetForwardVector() * Distance);
// 파라미터 설정
FCollisionQueryParams Params(SCENE_QUERY_STAT(Shoot), false, Owner);
// 충돌 탐지
bool bHasHit = GetWorld()->LineTraceSingleByChannel(
HitResult,
Start,
End,
CHANNEL_VISIBILITY,
Params
);
if (bHasHit)
{
FireLocation = HitResult.ImpactPoint;
}
else
{
FireLocation = End;
}
// 플레이어를 회전시켜줍니다.
ACharacter* Character = Cast<ACharacter>(GetOwner());
if (Character)
{
FRotator PlayerRotator = Character->GetActorRotation();
Character->SetActorRotation(FRotator(PlayerRotator.Pitch, Character->GetControlRotation().Yaw, PlayerRotator.Roll));
}
}
}
MMEnergyBall Class
- 마법사의 평타 공격에 사용될 에너지 볼 클래스를 제작합니다.
- 스폰 시 파티클 시스템 컴포넌트를 활성화 시켜줍니다.
- 히트 시 Hit 파티클을 재생하고 비활성화 시켜줍니다.
// MMEnergyBall Header
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MMEnergyBall.generated.h"
UCLASS()
class MYSTICMAZE_API AMMEnergyBall : public AActor
{
GENERATED_BODY()
public:
AMMEnergyBall();
protected:
virtual void BeginPlay() override;
virtual void PostInitializeComponents() override;
public:
void Fire(FVector TargetLocation);
protected:
UFUNCTION()
void OnBeginOverlap(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UPROPERTY(VisibleAnywhere, Category = "Particle", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UParticleSystemComponent> EnergyBallParticleSystemComponent;
UPROPERTY(VisibleAnywhere, Category = "Particle", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UParticleSystemComponent> HitParticleSystemComponent;
UPROPERTY(VisibleAnywhere, Category = "Collision", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class USphereComponent> EnergyBallCollision;
UPROPERTY(VisibleAnywhere, Category = "Movement", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UProjectileMovementComponent> MovementComponent;
private:
float Speed = 3000.0f;
uint8 bIsHit : 1;
};
// MMEnergyBall Cpp
#include "Item/MMEnergyBall.h"
#include "Collision/MMCollision.h"
#include "Components/SphereComponent.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Particles/ParticleSystemComponent.h"
// Sets default values
AMMEnergyBall::AMMEnergyBall()
{
EnergyBallCollision = CreateDefaultSubobject<USphereComponent>(TEXT("EnergyBallCollision"));
EnergyBallCollision->SetSphereRadius(8.0f);
EnergyBallCollision->SetCollisionProfileName(MMWEAPON);
RootComponent = EnergyBallCollision;
EnergyBallParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("EnergyBallParticleSystemComponent"));
EnergyBallParticleSystemComponent->SetupAttachment(EnergyBallCollision);
HitParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("HitParticleSystemComponent"));
HitParticleSystemComponent->SetupAttachment(EnergyBallCollision);
MovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("MovementComponent"));
MovementComponent->bRotationFollowsVelocity = true;
MovementComponent->InitialSpeed = Speed;
bIsHit = false;
}
// Called when the game starts or when spawned
void AMMEnergyBall::BeginPlay()
{
Super::BeginPlay();
// Projectile Movement Component 비활성화
MovementComponent->SetActive(false);
HitParticleSystemComponent->SetActive(false);
}
void AMMEnergyBall::PostInitializeComponents()
{
Super::PostInitializeComponents();
// Event Mapping
EnergyBallCollision->OnComponentBeginOverlap.AddDynamic(this, &AMMEnergyBall::OnBeginOverlap);
}
void AMMEnergyBall::Fire(FVector TargetLocation)
{
FVector LaunchDirection;
// 방향 구하기
if ((TargetLocation - Owner->GetActorLocation()).Length() < 300.0f)
{
LaunchDirection = (TargetLocation - GetActorLocation()).GetSafeNormal();
LaunchDirection.Z = 0.0f;
}
else
{
LaunchDirection = (TargetLocation - GetActorLocation()).GetSafeNormal();
}
// 방향 지정 및 Projectile Movement Component 활성화
MovementComponent->Velocity = LaunchDirection * MovementComponent->InitialSpeed;
MovementComponent->Activate();
// 3초 후 자동 삭제
SetLifeSpan(3.0f);
}
void AMMEnergyBall::OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (bIsHit) return;
if (Owner == OtherActor) return;
// TODO : 데미지 전달
UE_LOG(LogTemp, Warning, TEXT("%s"), *OtherActor->GetName());
UE_LOG(LogTemp, Warning, TEXT("%s"), *OverlappedComp->GetName());
EnergyBallParticleSystemComponent->SetActive(false);
HitParticleSystemComponent->SetActive(true);
bIsHit = true;
}
- 좌클릭으로 플레이어의 기본 콤보 공격을 사용 가능했습니다.
- Maze 콤보 공격 몽타주와 데이터를 등록해주도록 합니다.
- 기존과 같이 좌클릭으로 에너지 볼을 발사할 수 있습니다.
애님 노피파이 2종
- 지금까지 전사는 가드 동작, 궁수는 화살 발사의 동작을 추가하였습니다.
- 마법사의 경우 기를 모으는 동작을 추가해보도록 하겠습니다.
- 추후 스킬 데미지 증가에 사용
입력 액션을 추가하고 함수를 매핑해주도록 합니다.
기모으기 행동 시 스폰할 파티클 시스템 컴포넌트를 추가합니다.
// Header
// Particle Section
protected:
UPROPERTY(VisibleAnywhere, Category = "Particle", meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UParticleSystemComponent> ChargeParticleSystemComponent;
// CPP
AMMPlayerCharacter::AMMPlayerCharacter()
{
...
// Effect
{
ChargeParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("UParticleSystemComponent"));
ChargeParticleSystemComponent->SetupAttachment(RootComponent);
ChargeParticleSystemComponent->SetRelativeLocation(FVector(0.0f, 0.0f, -90.0f));
static ConstructorHelpers::FObjectFinder<UParticleSystem>ChargeParticleRef(TEXT("/Script/Engine.ParticleSystem'/Game/FXVarietyPack/Particles/P_ky_healAura.P_ky_healAura'"));
if (ChargeParticleRef.Object)
{
ChargeParticleSystemComponent->Template = ChargeParticleRef.Object;
}
}
}
입력 액션과 매핑한 함수들을 구현합니다.
차지하고 있는 동안 Tick 함수에서 값을 증가시켜줍니다.
- 구현 후 테스트용 무기 코드를 제거하고, Merge하여.. 영상을 남기지 못하였습니다.
- 마법사는 우클릭 시 아래 이펙트를 방출하며 ChargeNum을 증가시키도록 구현이 완료되었습니다.