필요한 개념
* 우선 CStateComponent에 Dead 상태 추가
UENUM(BlueprintType)
enum class EStateType : uint8
{
Idle, Roll, Backstep, Equip, Action, Hitted, Dead, Max
};
UFUNCTION(BlueprintPure)
FORCEINLINE bool IsDeadMode() { return Type == EStateType::Dead; }
void UCStateComponent::SetDeadMode()
{
ChangeType(EStateType::Dead);
}
* CEnemy.cpp의 안에 추가
// Hitted() 함수안에 추가
if (Status->GetHealth() <= 0.0f)
{
State->SetDeadMode();
return;
}
void ACEnemy::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
switch (InNewType)
{
case EStateType::Hitted: Hitted(); break;
case EStateType::Dead: Dead(); break;
}
}
void ACEnemy::Dead()
{
// 재동작 방지
CheckFalse(State->IsDeadMode());
// 몽타주 플레이
Montages->PlayDead();
}
* CMontagesComponent.cpp에 추가
void UCMontagesComponent::PlayDead()
{
PlayAnimMontage(EStateType::Dead);
}
Tip) CMontageComponent는 EStateType(CStateComponent가 가지고 있는 상태)에 따라 몽타주를 추가 할 수 있음
* DeadMontage 생성
Tip) 만약 몽타주의 버그가 있다면 (* 노티파이가 호출이 안되는 버그)
애니메이션 복제본을 만들어, 해당 프레임까지 커서를 이동 한 후 우 클릭 -> 시퀸스 편집(프레임 0부터 프레임 ?까지 제거) 클릭하면 애니메이션이 짤라짐(즉, 0 ~ ?까지 프레임이 사라짐)
그 후 몽타주를 만들자(몽타주에서 조정가능하지만, 애니메이션에서 짤라버리는게 좋음)
이렇게 해놓고 몽타주 만들고, 노티파이 세팅하면 그런 버그가 사라짐
* 애니메이션이 계속 엎어진 상태로 유지하도록, 끝 프레임을 늘려준다.
애니메이션에 들어가서, 우클릭 -> 끝 부분에 덧붙임(우리는 120프레임 추가했음)
너무 길어서 우클릭 해서 (131 ~ 190까지 제거 한다.)
마우스는 131프레임에서 우클릭 해야함
애니메이션 결과를 보면 계속 엎어져있는 것 확인가능함
이 애니메이션으로 몽타주 생성하고, 못 움직이게 FullBody로 세팅
* 사망이나 피격(Hitted) 등은 캐릭터 모두가 가지게 될 것이므로 인터페이스 ICharacter에 함수 추가함
// 아직 플레이어는 추가를 안해서 순수가상함수로 처리하지 않는다.(노티파이에 의해서 콜 될 거여서, BeginDead, EndDead가 필요)
virtual void Begin_Dead() {};
virtual void End_Dead() {};
나중에 Hitted()도 이렇게 통합해서 구현하면 된다.(공격하다가 맞으면(Hitted())되면 공격 취소하고 Hitted하고 다시 공격해야함)
그런 처리 때 Hitted()도 인터페이스에 함수로 구현함
* 노티파이를 만듬(CAnimNotifyState_Dead 클래스 생성)
사망은 AnimNotifyState로 만들어 사망 시작시 충돌체를 모두 꺼버리고 종료시 사망 처리를 완료하도록 함
사망 완료시 해당 캐릭터 삭제
CAnimNotifyState_Dead.cpp에 추가
character->Begin_Dead(); // NotifyBegin() 때
character->End_Dead(); // NotifyEnd() 때
* 참고로 GetOwner()는 게임상황이 아니면 안나오는 경우가 있어서 항상 CheckNull()을 해줘야 함
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
IICharacter* character = Cast<IICharacter>(MeshComp->GetOwner());
CheckNull(character);
* 몽타주에 Dead 노티파이 스테이트 추가
시작부터 어느정도 계속 엎어져 있는 것 쯤까지 해줌
* PlayDead()를 할려면 Excel Data를 수정함(DataTable 수정)
2번 Dead 추가한 뒤 DataTable 리임포트
<Enemy Dead() 처리시>
* 캐릭터의 캡슐 충돌체와 함께, Enemy 무기의 모든 충돌체도 꺼줘야 함 -> 다시 맞으면 안됨
CActionComponent가 Datas로 해서 Attachment를 가지고 있고, Attachment가 충돌체를 모두 가지고 있다.
* CActionComponent에 무기 충돌체를 모두 끄는 함수를 만든다.
// 모든 Collision을 꺼달라
void OffAllCollision();
void UCActionComponent::OffAllCollision()
{
// 모든 충돌체를 가져와 끄면 된다.
for (UCActionData * data : Datas)
{
// data가 Null이라면
if (!!data == false)
continue;
// 즉, GetAttachment()가 Null이라면
if (!!data->GetAttachment() == false)
continue;
// 충돌체 꺼라는 명령 내림
data->GetAttachment()->OffCollision();
}
}
CEnemy.cpp
void ACEnemy::Begin_Dead()
{
// 충돌체를 꺼줌(다시 맞으면 안됨, 무기도 꺼야해고, 자기의 캡슐도 꺼야함)
// 즉, 캐릭터의 캡슐 충돌체와 함께, Enemy 무기의 모든 충돌체도 꺼줘야 한다.
// 충돌체 전부 꺼줌
Action->OffAllCollision();
// 자신의 캐릭터 캡슐 컴포넌트(Collision을 꺼줌)
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void ACEnemy::End_Dead()
{
// 자기 자신을 제거
Destroy();
}
<2강 Dead - 2>
* 결과를 보면 타켓팅된 애를 죽이면 타켓팅 해제가 안되고 있음 타켓팅 해제에 대한 처리가 없으므로 타켓팅을 옯겨줘야만 다시 타겟팅이 일어남
TargetComponent.cpp TickComponent() 함수 부분 추가
// 타겟이 사망했는지를 체크해야 하니까
UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(Target);
// 타겟팅이 해제될 수 있는 조건 2개
// 1. 일정거리를 벗어났을때
// 2. DeadMode
// GetDistanceTo() : 거리를 구함
bool b = false;
// b가 false니까 뒤에가 true면 true가 됨
b |= state->IsDeadMode();
b |= Target->GetDistanceTo(OwnerCharacter) >= TraceRadius;
// &로 하게 된다면 b를 true로 주고 하면 된다.
// 옆으로 계속 늘어지는 거 보다 좋음
if (b == true)
{
EndTargeting();
return;
}
CStateComponent.h 추가된 내용
...
UENUM(BlueprintType)
enum class EStateType : uint8
{
// Action : 공격 상태
Idle, Roll, Backstep, Equip, Action, Hitted, Dead, Max
};
...
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCStateComponent : public UActorComponent
{
GENERATED_BODY()
public:
...
UFUNCTION(BlueprintPure)
FORCEINLINE bool IsDeadMode() { return Type == EStateType::Dead; }
public:
...
void SetDeadMode();
};
CStateComponent.cpp 추가된 내용
...
void UCStateComponent::SetDeadMode()
{
ChangeType(EStateType::Dead);
}
CEnemy.h
UCLASS()
class UONLINE_04_ACTION_API ACEnemy : public ACharacter, public IICharacter
{
GENERATED_BODY()
...
private:
void Hitted();
void Dead();
public:
virtual void Begin_Dead() override;
virtual void End_Dead() override;
};
CEnemy.cpp 수정된 내용
...
void ACEnemy::OnStateTypeChanged(EStateType InPrevType, EStateType InNewType)
{
switch (InNewType)
{
case EStateType::Hitted: Hitted(); break;
case EStateType::Dead: Dead(); break;
}
}
void ACEnemy::Hitted()
{
Status->SubHealth(DamageValue);
Cast<UCUserWidget_Health>(HealthWidget->GetUserWidgetObject())->Update(Status->GetHealth(), Status->GetMaxHealth());
DamageValue = 0.0f;
Status->SetStop();
if (Status->GetHealth() <= 0.0f)
{
State->SetDeadMode();
return;
}
Montages->PlayHitted();
FVector start = GetActorLocation();
FVector target = DamageInstigator->GetPawn()->GetActorLocation();
SetActorRotation(UKismetMathLibrary::FindLookAtRotation(start, target));
DamageInstigator = NULL;
FVector direction = target - start;
direction.Normalize();
LaunchCharacter(-direction * LaunchAmount, true, false);
ChangeColor(FLinearColor(1, 0, 0, 1));
UKismetSystemLibrary::K2_SetTimer(this, "RestoreColor", 0.1f, false);
}
void ACEnemy::Dead()
{
// 재동작 방지
CheckFalse(State->IsDeadMode());
Montages->PlayDead();
}
void ACEnemy::Begin_Dead()
{
// 충돌체를 꺼줌(다시 맞으면 안됨, 무기도 꺼야해고, 자기의 캡슐도 꺼야함)
// 즉, 캐릭터의 캡슐 충돌체와 함께, Enemy 무기의 모든 충돌체도 꺼줘야 한다.
// 충돌체 전부 꺼줌
Action->OffAllCollision();
// 자신의 캐릭터 캡슐 컴포넌트(Collision을 꺼줌)
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void ACEnemy::End_Dead()
{
// 자기 자신을 제거
Destroy();
}
CMontagesComponent.h 추가된 내용
...
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UONLINE_04_ACTION_API UCMontagesComponent : public UActorComponent
{
GENERATED_BODY()
...
public:
...
void PlayDead();
};
CMontagesComponent.cpp 추가된 내용
...
void UCMontagesComponent::PlayDead()
{
PlayAnimMontage(EStateType::Dead);
}
ICharacter.h 추가된 내용
...
class UONLINE_04_ACTION_API IICharacter
{
GENERATED_BODY()
public:
...
// 아직 플레이어는 추가를 안해서 순수가상함수로 처리하지 않는다.(노티파이에 의해서 콜 될 거여서, BeginDead, EndDead가 필요)
virtual void Begin_Dead() {};
virtual void End_Dead() {};
};
CAnimNotifyState_Dead.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "CAnimNotifyState_Dead.generated.h"
UCLASS()
class UONLINE_04_ACTION_API UCAnimNotifyState_Dead : public UAnimNotifyState
{
GENERATED_BODY()
public:
FString GetNotifyName_Implementation() const override;
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
};
CAnimNotifyState_Dead.cpp
#include "CAnimNotifyState_Dead.h"
#include "Global.h"
#include "Characters/ICharacter.h"
FString UCAnimNotifyState_Dead::GetNotifyName_Implementation() const
{
return "Dead";
}
void UCAnimNotifyState_Dead::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
Super::NotifyBegin(MeshComp, Animation, TotalDuration);
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
IICharacter* character = Cast<IICharacter>(MeshComp->GetOwner());
CheckNull(character);
character->Begin_Dead();
}
void UCAnimNotifyState_Dead::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::NotifyEnd(MeshComp, Animation);
CheckNull(MeshComp);
CheckNull(MeshComp->GetOwner());
IICharacter* character = Cast<IICharacter>(MeshComp->GetOwner());
CheckNull(character);
character->End_Dead();
}
CActionComponent.h 추가된 내용
...
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class UONLINE_04_ACTION_API UCActionComponent : public UActorComponent
{
GENERATED_BODY()
...
// 무기가 가진 모든 충돌체를 꺼달라
void OffAllCollision();
};
CActionComponent.cpp 추가된 내용
...
void UCActionComponent::OffAllCollision()
{
// 모든 충돌체를 가져와 끄면 된다.
for (UCActionData * data : Datas)
{
// data가 Null이라면
if (!!data == false)
continue;
// 즉, GetAttachment()가 Null이라면
if (!!data->GetAttachment() == false)
continue;
// 충돌체 꺼라는 명령 내림
data->GetAttachment()->OffCollision();
}
}
CActionComponent.cpp 수정된 내용
...
void UCTargetComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
CheckNull(Target);
// 타겟이 사망했는지를 체크해야 하니까
UCStateComponent* state = CHelpers::GetComponent<UCStateComponent>(Target);
// 타겟팅이 해제될 수 있는 조건 2개
// 1. 일정거리를 벗어났을때
// 2. DeadMode
// GetDistanceTo() : 거리를 구함
bool b = false;
// b가 false니까 뒤에가 true면 true가 됨
b |= state->IsDeadMode();
b |= Target->GetDistanceTo(OwnerCharacter) >= TraceRadius;
// &로 하게 된다면 b를 true로 주고 하면 된다.
// 옆으로 계속 늘어지는 거 보다 좋음
if (b == true)
{
EndTargeting();
return;
}
// 즉, 바라보는 방향의 회전값으로 세팅되도록 만듬(left, right target 버그 해결)
FVector start = OwnerCharacter->GetActorLocation();
FVector target = Target->GetActorLocation();
// start에서 target을 바라보는 회전값이 만들어짐
FRotator rotator = UKismetMathLibrary::FindLookAtRotation(start, target);
FRotator current = OwnerCharacter->GetControlRotation();
// RInterpTo : 시작 방향에서 종료 방향까지 시간에 비례한 속도로 자연스럽게 보간해주는 함수
rotator = UKismetMathLibrary::RInterpTo(current, rotator, DeltaTime, interopSpeed);
// SetControlRotation()은 private이어서 Contoller를 통해 가져와야함
OwnerCharacter->GetController()->SetControlRotation(rotator);
}
결과
'Unreal Engine 4 > C++' 카테고리의 다른 글
<Unreal C++> 65 - Action RPG (Throw IceBall) (0) | 2022.05.30 |
---|---|
<Unreal C++> 63 - Action RPG (FireStorm) (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 |
<Unreal C++> 55 - Action RPG (Fist Mode) (0) | 2022.05.16 |