필요한 개념
캐릭터 주변으로 박스 콜리전에 붙은 불기동을 플레이 시킨 후 불기둥이 적에 닿으면 연속적으로 피격을 일으키는 무기를 제작할 것임
* BP_CAttachment_FireStorm 생성
* DA_FireStorm 세팅
- BP_CAttachment_FireStorm으로 세팅
- 사방으로 뛰어 다닐 수 있게 PawnControl 해제
- 파티클 이펙트 세팅
- CanMove false해서 움직일 수 없게 해주고, 나중에는 움직일 수 있게 풀어줄 것이다.
- FireStorm 몽타주 세팅
- CDoAction_FireStorm 클래스 세팅
* FireStorm_Montage 세팅
BeginAction으로 FireStorm 등장시킴
EndAction으로 종료
Tip) BeginAction과 EndAction은 CDoAction을 상속받은 클래스에서 원하는 대로 정의할 수 있음
* CDoAction을 상속 받는 CDoAction_FireStorm 클래스 생성
* CActionComponent에 FireStorm모드 추가
UENUM(BlueprintType)
enum class EActionType: uint8
{
Unarmed, Fist, OneHand, TwoHand, Warp, FireStorm, Max,
};
UFUNCTION(BlueprintPure)
FORCEINLINE bool IsFireStormMode() { return Type == EActionType::FireStorm; }
void UCActionComponent::SetFireStormMode()
{
SetMode(EActionType::FireStorm);
}
Tip) 컴파일을 자주해줘야 속도가 빨라짐
그나마 컴파일할 크기가 줄어드니까 속도가 빨라짐
* 프로젝트 세팅에 액션 매핑 키 추가
FireStorm -> G
* CPlayer에 입력 이벤트 연결을 해준다.
PlayerInputComponent->BindAction("FireStorm", EInputEvent::IE_Pressed, this, &ACPlayer::OnFireStorm);
void ACPlayer::OnFireStorm()
{
CheckFalse(State->IsIdleMode());
Action->SetFireStormMode();
}
* BP_CPlayer에 Action에 DA_FireStorm 세팅
Tip) 객체 소유 관련 문제
박스를 찾아오고 박스에 다가 파티클을 붙일 것이다.
그런데 소유하지 않는게 좋다. 그런데 소유해야 되는 경우는 어쩔 수 없이 소유하게 되는데,
언리얼엔진에 있는 타입들은 소유해도 상관 없다.
언리얼엔진 타입들은 수정되지 않을 거여서,
반면 우리가 만든 것들은 수정될 여지가 있다.
ex) CDoAction_FireStorm에서는 BoxCollision을 사용하기 위해 CAttachment를 소유하는 것 보다, CAttachment가 소유한 BoxComponent를 소유하는게 맞다.
Attachment를 찾아서 콜하는것은 문제가 없음(소유하지 않는다면)
BoxComponent는 소유해도 됨(언리얼엔진꺼여서)
불기둥이 플레이어 주위를 회전한다.
박스에다 파티클을 붙이고 박스를 회전시켜줄 것이다.
CDoAction_FireStorm
// BP에서 세팅가능하게 처리
private:
UPROPERTY(EditAnywhere)
float Time = 5.0f; // 불기둥의 시간
UPROPERTY(EditAnywhere)
float Distance = 200.0f; // 불기둥과 플레이어 사이의 간격
UPROPERTY(EditAnywhere)
float Speed = 100.0f; // 불기둥이 도는 속도
void ACDoAction_FireStorm::Begin_DoAction()
{
// Box콜리젼에다가 파티클 이펙트 붙여줌
Attached = UGameplayStatics::SpawnEmitterAttached(Datas[0].Effect, Box, "");
Attached->SetRelativeLocation(Datas[0].EffectTransform.GetLocation());
Attached->SetRelativeScale3D(Datas[0].EffectTransform.GetScale3D());
// Box의 Owner로 attachment가져옴
//ACAttachment* attachment = Cast<ACAttachment>(Box->GetOwner());
//
//// 시작때 꺼줬었다.
//attachment->OnCollision(); // 충돌체를 켜줌
}
void ACDoAction_FireStorm::End_DoAction()
{
// 불기둥을 봐야하니까 이동허용
Status->SetMove();
// 일정 시간뒤에 IdleMode로 돌려준다.(불기둥이 끝나야 IdleMode)
FTimerDynamicDelegate timerDelegate;
timerDelegate.BindUFunction(this, "Finish");
// 우리는 이전의 UKismetSystemLibrary::K2_SetTimer()를 썼었는데
// 이번에는 델리게이트로 써봄
// 1. 델리게이트 2. 몇 초뒤 실행 3. 반복여부
UKismetSystemLibrary::K2_SetTimerDelegate(timerDelegate, Time, false);
}
void ACDoAction_FireStorm::Finish()
{
// IdleMode 상태로 돌려줌
State->SetIdleMode();
// 불기둥을 날려줌
Attached->DestroyComponent();
}
그림 3_1 참고
void ACDoAction_FireStorm::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
CheckFalse(*bEquipped);
// 액션모드일때만 실행(불기둥 등장할때는 액션모드)
CheckFalse(State->IsActionMode());
FVector location = OwnerCharacter->GetActorLocation();
// 일정한 속도를 유지하기 위해 DeltaTime을 곱해준다.
Angle += Speed * DeltaTime;
// angle이 360과 가까운지(오차가 실수형이기에 무조건 있다.) -> 짐벌락이 발생할 수 있기에
// 오차값이 쌓여서...
// 축의 오차값이 누적되서, 한쪽은 도는데, 한쪽은 안돌 수 있음(짐벌락 발생)
if (FMath::IsNearlyEqual(Angle, 360))
Angle = 0.0f;
// 회전 값(어느거리에서 어느 방향으로 회전할지)
// x축에 넣었다.
// x축 전방이다. 전방에서 Distance값만큼 떨어트리기 위해
FVector axis = FVector(Distance, 0, 0);
// RotateAngleAxis : 지정한 방향으로 지정한 각도만큼 회전된 위치를 리턴
// 1. 얼마만큼 회전할건지, 2. 어느방향으로 회전할건지(회전할 축)
// 즉, Z축(수직축)인 오른쪽으로 빙글빙글 Angle만큼 돌게 된다.
// 빙글빙글 도는 것은 수직축 회전이다.
FVector value = axis.RotateAngleAxis(Angle, FVector(0, 0, 1));
// 즉, 수직축 회전을 하는데 Distance만큼 떨어져서 회전을 해서 그 위치를 구함
// 플레이어 위치를 기준으로 회전
location += value;
// 박스에다 위치 세팅
Box->SetWorldLocation(location);
}
* BP_CDoAction_FireStorm 생성
얘는 변수값을 우리가 BP에서 세팅가능하도록 해놔서, BP로 따로 만든것임
- Time : 유지시간
- Distance : 거리
- Speed : 회전속도
- DA_FireStorm에 BP_CDoAction_FireStorm 세팅
// Begin_DoAction()
// Box의 Owner로 attachment가져옴
ACAttachment* attachment = Cast<ACAttachment>(Box->GetOwner());
// 시작때 꺼줬었다.
attachment->OnCollision(); // 충돌체를 켜줌
// Finish()
ACAttachment* attachment = Cast<ACAttachment>(Box->GetOwner());
attachment->OffCollision(); // 충돌체를 꺼줌
충돌한 애들에 대해서 계속 데미지를 주어야 한다.
그런데 생각해보면 Box는 BeginOverlap(), EndOverlap() 밖에 없다.
즉, 충돌중은 없다.
-> 그래서, 충돌이 유지되는 이벤트는 없으므로 충돌이 시작하면 배열에 넣고 충돌이 끊나면 배열에서 제거한다.
일정 시간마다 배열에 있는 캐릭터들에 데미지를 준다.
우리가 정의했던 OnAttachmentBeginOverlap(), OnAttachmentEndOverlap()을 사용함
TArray<class ACharacter*> HittedCharacters;
UPROPERTY(EditAnywhere)
float HittedTime = 0.25f; // 0.25초마다 Hitted됨
UFUNCTION()
void Hitted();
void ACDoAction_FireStorm::OnAttachmentBeginOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOtherCharacter)
{
// AddUnique : 기존에 추가 되어있는 값이 있다면 추가하지 않음으로 하나의 값만 유지됨
HittedCharacters.AddUnique(InOtherCharacter);
}
void ACDoAction_FireStorm::OnAttachmentEndOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOtherCharacter)
{
// 제거됨
HittedCharacters.Remove(InOtherCharacter);
}
// Begin_DoAction()
// Hitted 일정시간 만큼 반복해서 콜 됨
UKismetSystemLibrary::K2_SetTimer(this, "Hitted", HittedTime, true);
// Finish()
// 제거 시켜줌
UKismetSystemLibrary::K2_ClearTimer(this, "Hitted");
* 맞으면 Launch되는 것을 없애려면 받은 Causer가 뭐냐에 따라 정의하면 된다.
기존에는 끝난 Angle에 이어서 FireStorm이 생성되었다.
* Angle의 초기화를 랜덤으로 입력해 랜덤한 위치에서 시작할 수 있도록 처리함
// Begin_DoAction()
// 불기둥의 값이 랜덤한 값으로 시작되게
Angle = UKismetMathLibrary::RandomFloatInRange(0, 360);
CActionComponent.h 추가된 내용
...
UENUM(BlueprintType)
enum class EActionType: uint8
{
Unarmed, Fist, OneHand, TwoHand, Warp, FireStorm, Max,
};
...
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class UONLINE_04_ACTION_API UCActionComponent : public UActorComponent
{
GENERATED_BODY()
...
public:
...
UFUNCTION(BlueprintPure)
FORCEINLINE bool IsFireStormMode() { return Type == EActionType::FireStorm; }
public:
...
void SetFireStormMode();
};
CActionComponent.cpp 추가된 내용
...
void UCActionComponent::SetFireStormMode()
{
SetMode(EActionType::FireStorm);
}
CDoAction_FireStorm.h
#pragma once
#include "CoreMinimal.h"
#include "Actions/CDoAction.h"
#include "CDoAction_FireStorm.generated.h"
UCLASS()
class UONLINE_04_ACTION_API ACDoAction_FireStorm : public ACDoAction
{
GENERATED_BODY()
// BP에서 세팅가능하게 처리
private:
UPROPERTY(EditAnywhere)
float Time = 5.0f; // 불기둥의 시간
UPROPERTY(EditAnywhere)
float Distance = 200.0f; // 불기둥과 플레이어 사이의 간격
UPROPERTY(EditAnywhere)
float Speed = 100.0f; // 불기둥이 도는 속도
UPROPERTY(EditAnywhere)
float HittedTime = 0.25f; // 0.25초마다 Hitted됨
protected:
virtual void BeginPlay() override;
public:
virtual void DoAction() override;
virtual void Begin_DoAction() override;
virtual void End_DoAction() override;
virtual void Tick(float DeltaTime) override;
private:
virtual void OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) override;
virtual void OnAttachmentEndOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter) override;
private:
UFUNCTION()
void Finish();
UFUNCTION()
void Hitted();
private:
class UBoxComponent* Box;
class UParticleSystemComponent* Attached; // 어느시간이 되면 제거하기 위해
float Angle; // 각도
TArray<class ACharacter*> HittedCharacters;
};
CDoAction_FireStorm.cpp
#include "CDoAction_FireStorm.h"
#include "Global.h"
#include "CAttachment.h"
#include "GameFramework/Character.h"
#include "Components/BoxComponent.h"
#include "Components/CStateComponent.h"
#include "Components/CStatusComponent.h"
#include "Particles/ParticleSystemComponent.h"
void ACDoAction_FireStorm::BeginPlay()
{
Super::BeginPlay();
for (AActor* actor : OwnerCharacter->Children)
{
if (actor->IsA<ACAttachment>() && actor->GetActorLabel().Contains("FireStorm"))
{
Box = CHelpers::GetComponent<UBoxComponent>(actor);
break;
}
}
}
void ACDoAction_FireStorm::DoAction()
{
CheckFalse(*bEquipped);
CheckFalse(State->IsIdleMode());
State->SetActionMode();
OwnerCharacter->PlayAnimMontage(Datas[0].AnimMontage, Datas[0].PlayRatio, Datas[0].StartSection);
Datas[0].bCanMove ? Status->SetMove() : Status->SetStop();
}
void ACDoAction_FireStorm::Begin_DoAction()
{
// 불기둥의 값이 랜덤한 값으로 시작되게
Angle = UKismetMathLibrary::RandomFloatInRange(0, 360);
// Box콜리젼에다가 파티클 이펙트 붙여줌
Attached = UGameplayStatics::SpawnEmitterAttached(Datas[0].Effect, Box, "");
Attached->SetRelativeLocation(Datas[0].EffectTransform.GetLocation());
Attached->SetRelativeScale3D(Datas[0].EffectTransform.GetScale3D());
// Box의 Owner로 attachment가져옴
ACAttachment* attachment = Cast<ACAttachment>(Box->GetOwner());
// 시작때 꺼줬었다.
attachment->OnCollision(); // 충돌체를 켜줌
// Hitted 일정시간 만큼 반복해서 콜 됨
UKismetSystemLibrary::K2_SetTimer(this, "Hitted", HittedTime, true);
}
void ACDoAction_FireStorm::End_DoAction()
{
// 불기둥을 봐야하니까 이동허용
Status->SetMove();
// 일정 시간뒤에 IdleMode로 돌려준다.(불기둥이 끝나야 IdleMode)
FTimerDynamicDelegate timerDelegate;
timerDelegate.BindUFunction(this, "Finish");
// 우리는 이전의 UKismetSystemLibrary::K2_SetTimer()를 썼었는데
// 이번에는 델리게이트로 써봄
// 1. 델리게이트 2. 몇 초뒤 실행 3. 반복여부
UKismetSystemLibrary::K2_SetTimerDelegate(timerDelegate, Time, false);
}
void ACDoAction_FireStorm::Finish()
{
// IdleMode 상태로 돌려줌
State->SetIdleMode();
// 불기둥을 날려줌
Attached->DestroyComponent();
ACAttachment* attachment = Cast<ACAttachment>(Box->GetOwner());
attachment->OffCollision(); // 충돌체를 꺼줌
// 제거 시켜줌
UKismetSystemLibrary::K2_ClearTimer(this, "Hitted");
}
void ACDoAction_FireStorm::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
CheckFalse(*bEquipped);
// 액션모드일때만 실행(불기둥 등장할때는 액션모드)
CheckFalse(State->IsActionMode());
FVector location = OwnerCharacter->GetActorLocation();
// 일정한 속도를 유지하기 위해 DeltaTime을 곱해준다.
Angle += Speed * DeltaTime;
// angle이 360과 가까운지(오차가 실수형이기에 무조건 있다.) -> 짐벌락이 발생할 수 있기에
// 오차값이 쌓여서...
// 축의 오차값이 누적되서, 한쪽은 도는데, 한쪽은 안돌 수 있음(짐벌락 발생)
if (FMath::IsNearlyEqual(Angle, 360))
Angle = 0.0f;
// 회전 값(어느거리에서 어느 방향으로 회전할지)
// x축에 넣었다.
// x축 전방이다. 전방에서 Distance값만큼 떨어트리기 위해
FVector axis = FVector(Distance, 0, 0);
// RotateAngleAxis : 지정한 방향으로 지정한 각도만큼 회전된 위치를 리턴
// 1. 얼마만큼 회전할건지, 2. 어느방향으로 회전할건지(회전할 축)
// 즉, Z축(수직축)인 오른쪽으로 빙글빙글 Angle만큼 돌게 된다.
// 빙글빙글 도는 것은 수직축 회전이다.
FVector value = axis.RotateAngleAxis(Angle, FVector(0, 0, 1));
// 즉, 수직축 회전을 하는데 Distance만큼 떨어져서 회전을 해서 그 위치를 구함
// 플레이어 위치를 기준으로 회전
location += value;
// 박스에다 위치 세팅
Box->SetWorldLocation(location);
}
void ACDoAction_FireStorm::OnAttachmentBeginOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOtherCharacter)
{
// AddUnique : 기존에 추가 되어있는 값이 있다면 추가하지 않음으로 하나의 값만 유지됨
HittedCharacters.AddUnique(InOtherCharacter);
}
void ACDoAction_FireStorm::OnAttachmentEndOverlap(ACharacter* InAttacker, AActor* InAttackCauser, ACharacter* InOtherCharacter)
{
// 제거됨
HittedCharacters.Remove(InOtherCharacter);
}
void ACDoAction_FireStorm::Hitted()
{
// 데미지를 전달함
FDamageEvent e;
for (ACharacter* character : HittedCharacters)
character->TakeDamage(Datas[0].Power, e, OwnerCharacter->GetController(), this);
}
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
<Unreal C++> 70 - Action RPG (UserWidget(무기선택)) (0) | 2022.05.30 |
---|---|
<Unreal C++> 65 - Action RPG (Throw IceBall) (0) | 2022.05.30 |
<Unreal C++> 61 - Action RPG (Dead) (0) | 2022.05.30 |
<Unreal C++> 58 - Action RPG (Targeting) (0) | 2022.05.16 |
<Unreal C++> 56 - Action RPG (Warp Mode) (0) | 2022.05.16 |