본문 바로가기

Unreal Engine 4/C++

<Unreal C++> 53 - Action RPG(Juice(타격감))

 

필요한 개념


적을 Hitted 시킬때 플레이어의 타격감

 

타격감을 구현하는 요소
- 사운드(PlaySound)
- 애니메이션(AnimMontage)
- 이펙트(파티클, ParticleSystem)
- 카메라 흔들림(CameraShake)
- 진동
- 경직(HitStop)
- 칼 Trail


<HitEffect>
적이 Hitted된 떄에 Effect가 나타남

* 보정된 위치에 Effect 플레이 시킴

// ACDoAction_Melee.cpp의 OnAttachmentBeginOverlap()

UParticleSystem* hitEffect = Datas[Index].Effect;

if (!!hitEffect)
{
	FTransform transform = Datas[Index].EffectTransform;
	// EffectTransform 보정 만큼 이동
	transform.AddToTranslation(InOtherCharacter->GetActorLocation());
	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), hitEffect, transform);
}



<HitStop>
일반적인 PC게임에서 진동은 어려움, 스마트폰은 가능
사운드 중요, 애니메이션..
HitStop이 제일 중요

HitStop(경직) : 타격을 주면 일시적으로 잠깐 멈추는 느낌

* 경직을 어떤 아이디어로 구현해야 하냐?
UGameplayStatics::SetGlobalTimeDilation() : 지정한 배속으로 게임의 속도를 조정(게임을 느리게 해줌)

ex) 게임에서 무기 선택할때, 게임자체가 느리게 진행되면서 무기 선택가능

// 원본 속도 1, 0.1이라면 10배 느려짐
// 0.1배 만큼 느리게 만듬
UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 0.1f);



ex) 우리는 적이 칼에 충돌될때 100배 정도 느리게 한다음 충돌이 멈추면 다시 원본 1로 돌아오게 만듬

우리는 DataAsset에 hitStop값을 넣어놓았었다.
이 값을 불러들인 다음 사용
ex) hitStop값이 1이라면 1초 이따가 다시 GlobalTimeDilation이 원래 1로 돌아옴..

-> 이렇게 되면 적은 1초간 경직 상태가 됨

 

// CDoAction_Melee.cpp
float hitStop = Datas[Index].HitStop;

// 0 이랑 가깝지 않으면 함
if (FMath::IsNearlyZero(hitStop) == false)
{
	// 1e-3f -> 10^-3 -> 1 / 1000초 -> 0.001
	// 1000배 정도 느리게 만듬
	UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1e-3f);
	// 월드가 느려지만 타이머도 느려진다. 그래서 다시 그 시간만큼 곱해서 회복시킴
	// 즉, hitStop만큼 딜레이 시킴
	UKismetSystemLibrary::K2_SetTimer(this, "RestoreDilation", hitStop * 1e-3f, false);
}



void ACDoAction_Melee::RestoreDilation()
{
	UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
}



hitStop을 0.075로 주었을때, 살짝 끊기면서 잘나옴

그러나 결과를 보면 뭔가 심심?
-> 카메라 효과(CameraShake) BP를 만들어서 추가

 

<카메라 효과 세팅>

언리얼 좌표계에 의하면 Y가 좌우, Z가 상하

 

* Y, Z 중심으로 흔듬

BP_OneHand_3_CameraShake 값 세팅

 

 

 

* 3번째 동작이 옆으로 공격하는 동작이라, 좌우(Y)로 더 흔들어야 있어보임

* DA_OneHand에다가 CameraShake 세팅



* CDoAction_Melee.cpp OnAttachmentBeginOverlap()에 CameraShake 플레이

TSubclassOf<UCameraShake> shake = Datas[Index].ShakeClass;
if (!!shake)
	UGameplayStatics::GetPlayerController(GetWorld(), 0)->PlayerCameraManager->PlayCameraShake(shake);




* 지금 경직이 먼저 실행되고, 이따가 Effect가 플레이되어서 어색한 현상이 있다.
-> 경직은 타격 이후 약간의 시간 후에 경직을 주는 것이 좀 더 자연스럽다.
-> Effect 먼저 주고 일정 시간 뒤에 HitStop(경직)주면 된다.

 

 

 

 

 

CDoAction_Melee.h 수정된 내용


더보기
...

UCLASS()
class UONLINE_04_ACTION_API ACDoAction_Melee : public ACDoAction
{
	GENERATED_BODY()
	
    ...

private:
	UFUNCTION()
		void RestoreDilation();
};

 

 

 

 

 

 

 

CDoAction_Melee.cpp 수정된 내용


더보기
...

void ACDoAction_Melee::OnAttachmentBeginOverlap(class ACharacter* InAttacker, class AActor* InAttackCauser, class ACharacter* InOtherCharacter)
{
	Super::OnAttachmentBeginOverlap(InAttacker, InAttackCauser, InOtherCharacter);
	CheckNull(InOtherCharacter);

	for (const ACharacter* other : HittedCharacters)
	{
		if (InOtherCharacter == other)
			return;
	}
	HittedCharacters.Add(InOtherCharacter);


	UParticleSystem* hitEffect = Datas[Index].Effect;
	if (!!hitEffect)
	{
		FTransform transform = Datas[Index].EffectTransform;
		// EffectTransform 보정 만큼 이동
		transform.AddToTranslation(InOtherCharacter->GetActorLocation());
		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), hitEffect, transform);
	}
	
	float hitStop = Datas[Index].HitStop;
	// 0 이랑 가깝지 않으면 함
	if (FMath::IsNearlyZero(hitStop) == false)
	{
		// 1e-3f -> 10^-3 -> 1 / 1000초 -> 0.001
		// 1000배 정도 느리게 만듬
		UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1e-3f);
		// 월드가 느려지만 타이머도 느려진다. 그래서 다시 그 시간만큼 곱해서 회복시킴
		// 즉, hitStop만큼 딜레이 시킴
		UKismetSystemLibrary::K2_SetTimer(this, "RestoreDilation", hitStop * 1e-3f, false);
	}

	TSubclassOf<UCameraShake> shake = Datas[Index].ShakeClass;
	if (!!shake)
		UGameplayStatics::GetPlayerController(GetWorld(), 0)->PlayerCameraManager->PlayCameraShake(shake);

	// 데미지 추가 정보를 넘길때
	FDamageEvent e;

	// 1. 데미지, 2. 데미지 이벤트, 3 : Instigator(컨트롤러), 4 : DamageCursor(데미지 야기하는 친구)
	InOtherCharacter->TakeDamage(Datas[Index].Power, e, OwnerCharacter->GetController(), this);
}


void ACDoAction_Melee::RestoreDilation()
{
	UGameplayStatics::SetGlobalTimeDilation(GetWorld(), 1.0f);
}

 

 

 

 

 

결과