본문 바로가기
언리얼 최고/언리얼 c++

이득우의 언리얼 C++ 정리 - 9 (충돌 설정과 대미지 전달)

by Lee_story_.. 2022. 8. 31.
728x90

 

 

*  아래의 책의 내용을 정리한 글입니다!  *

 

 

이번장은 충돌에 대해서 정리해놓은 단원으로 몇달전에 한 번 설정해본 기능들이 나와 이해하기 편했습니다!

 

 

언리얼엔진(UE4)/멀티플레이/collision 채널을 사용해 조건부 충돌 설정하기!

이번엔 어떤 특정 엑터를 캐릭터가 통과할수 있도록 한번 만들어 보겠습니다! 아래는 제가 이번 프로젝트에서 이 방법을 사용한 부분입니다. 저는 큰 맵을 여러 스테이지로 나누어 플레이하는

ljhyunstory.tistory.com

 

언리얼엔진에서는 콜리전을 크게 3가지 방법으로 제작할수있는데

 

  • 스태틱메시 에셋 : 스태틱메시 에셋에 콜리전 영역을 심는 방법, 스태틱메시 컴포넌트에서 비주얼과 충돌이라는 두 가지 기능을 설정할 수 있어 관리가 편리하다.

 

  • 기본 도형(Primitive) 컴포넌트 : 구체, 박스, 캡슐 등의 기본 도형을 사용해 스태틱메시와 별도로 충돌 영역을 지정한다. 스켈레탈 메시를 움직일 때 주로 사용한다.

 

  • 피직스 애셋 : 일반적으로 캐릭터의 이동은 캡슐 컴포넌트를 사용해 처리한다. 하지만 특정 상황에서 캐릭터의 각 관절이 흐느적거리는 헝겊 인형 효과를 구현할 때 이 피직스 애셋을 사용한다. 캐릭터의 각 부위에 기본 도형으로 충돌 영역을 설정하고 이를 연결해 캐릭터의 물리를 설정한다. 피직스 애셋은 스켈레탈 메시에만 사용할 수 있다.

 

 

위 처럼 콜리전을 만들었다면 이제 채널에 따라 설정 해줄수있습니다! (각각의 채널에 따라 어떻게 반응 할건지 설정가능)

 

각각의 채널도 아래처럼 정해져 있는 채널이 있고

 

 

  • WorldStatic : 움직이지 않는 정적인 배경 액터에 사용하는 콜리전 채널이다. 주로 스태틱메시 액터에 있는 스태틱메시 컴포넌트에 사용한다.
  • WorldDynamic : 움직이는 액터에 사용하는 콜리전 채널이다. 블루프린트에 속한 스태틱메시 컴포넌트에 사용한다.
  • Pawn : 플레이어가 조종하는 물체에 주로 사용한다. 캐릭터의 충돌을 담당하는 캡슐 컴포넌트에 설정된다.
  • Visibility : 배경 물체가 시각적으로 보이는지 탐지하는데 사용한다. 탐지에서 폰은 제외된다. 마우스로 물체를 선택하는 피킹(Picking) 기능을 구현할 때 사용한다.
  • Camera : 카메라 설정을 위해 카메라와 목표물 간에 장애물이 있는지 탐지하는데 사용한다. 이전 GTA 방식으로 캐릭터를 조작할 때 장애물이 시야를 가리면 카메라를 장애물 앞으로 줌인하는 기능(SpringArm->bCollisionTest = true)이 있었다. 이때 사용하는 채널이 Camera 채널이다.
  • PhysicsBody : 물리 시뮬레이션으로 움직이는 컴포넌트에 설정한다.

 

 

 

사용자가 직접 만들수도 있습니다.

 

 

 

그리고 물체마다 어떤 물리 기능을 적용시킬지도 정해줄수있습니다

  • Query : 두 물체의 충돌 영역이 서로 겹치는지 테스트하는 설정. 충돌 영역의 겹침을 감지하는 것을 오버랩(Overlap) 이라고 부르며, 충돌 영역이 겹치면 관련 컴포넌트에 BeginOverlap 이벤트가 발생한다. 지정한 영역에 물체가 충돌하는지 탐지하는 레이캐스트(Raycast) 나 스윕(Sweep) 기능도 Query 에 속한다.
  • Physics : 물리적인 시뮬레이션을 사용할 때 설정한다.
  • Query and Physics : 위의 두 기능을 모두 사용하는 설정이다.

 

 

여기까지가 기본적인 콜리젼 끝!

 

 

이제 콜리젼 채널을 하나 만들어 봅시다.  프로젝트 세팅 >> 콜리전

 

그다음 새 프로파일도 만들어 봅싣다.

 

 

트리거 프로파일 편집

 

 

ABCharacter.cpp 에 추가

AABCharacter::AABCharacter()// 안에
{

//~~
    GetCapsuleComponent()->SetCollisionProfileName(TEXT("ABCharacter"));
}

 

 

 

아래처럼 컴포넌트콜리전프리셋이 변경됩니다!

 

 

 

트레이스 채널에 아래처럼 하나 추가하고

 

 

캐릭터 프리셋을 수정해줍시다.

 

 

 

아래 코드 추가해주면

 

ABCharacter.h

UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
private:
    
    //~
    
    void AttackCheck();
}

 

ABCharacter.cpp

void AABCharacter::PostInitializeComponents()
{
    //~~~
    
   
    ABAnim->OnAttackHitCheck.AddUObject(this, &AABCharacter::AttackCheck);
}




void AABCharacter::AttackCheck()
{
    FHitResult HitResult;
    FCollisionQueryParams Params(NAME_None, false, this);
    bool bResult = GetWorld()->SweepSingleByChannel(
        HitResult,
        GetActorLocation(),
        GetActorLocation() + GetActorForwardVector() * 200.0f,
        FQuat::Identity,
        ECollisionChannel::ECC_GameTraceChannel2,
        FColiisionShape::MakeSphere(50.0f),
        Params);
    
    if (bResult)
    {
        if (HitResult.Actor.IsValid())
        {
            ABLOG(Warning, TEXT("Hit Actor Name : %s"), *HitResult.Actor->GetName());
        }
    }

}

 

 

콜리전에따라 로그가 찍히게 됩니다!

 

 

이렇게 로그로도 확인가능하지만!

 

언리얼에서는 게임을 플레이하면서 콜리전을 확인할수있도록 만들어 놓은 매크로가 있다!

 

ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{
    //~~~
    
    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
    float AttackRange;
    
    UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
    float AttackRadius;
};

 

 

 

ABCharacter.cpp

#include "DrawDebugHelpers.h"

//~~

ABCharacter::ABCharacter()
{
    //~~
    
    AttackRadius = 50.0f;
    AttackRange = 200.0f;
}

void AABCharacter::AttackCheck()
{
	FHitResult HitResult;
	FCollisionQueryParams Params(NAME_None, false, this);
	
	bool bResult = GetWorld()->SweepSingleByChannel(
		HitResult,
		GetActorLocation(),
		GetActorLocation() + GetActorForwardVector() * AttackRange,
		FQuat::Identity,
		ECollisionChannel::ECC_GameTraceChannel2,
		FCollisionShape::MakeSphere(AttackRadius),
		Params);

#if ENABLE_DRAW_DEBUG

	FVector TraceVec = GetActorForwardVector() * AttackRange;
	FVector Center = GetActorLocation() + TraceVec * 0.5f;
	float HalfHeight = AttackRange * 0.5f + AttackRadius;
	FQuat CapsuleRot = FRotationMatrix::MakeFromZ(TraceVec).ToQuat();
	FColor DrawColor = bResult ? FColor::Green : FColor::Red;
	float DebugLifeTime = 5.0f;

	DrawDebugCapsule(GetWorld(),
		Center,
		HalfHeight,
		AttackRadius,
		CapsuleRot,
		DrawColor,
		false,
		DebugLifeTime);
#endif

	if (bResult)
	{
		if (HitResult.Actor.IsValid())
		{
			ABLOG(Warning, TEXT("Hit Actor Name : %s"), *HitResult.Actor->GetName());
		}
	}



}

 

 

아래와 같이 콜리전을 보여준다!

 

 

 

다음은 데미지!

 

언리얼 엔진의 액터 클래스 AActor 에는 4개의 인자를 갖고 있는 TakeDamage 라는 함수가 이미 구현되어있는데

  • DamageAmount : 전달할 대미지의 세기
  • DamageEvent : 대미지 종류
  • EventInstigator : 공격 명령을 내린 가해자
  • DamageCauser : 대미지 전달을 위해 사용한 도구

다음과 같은 인자를 가집니다!

 

이제 데미지를 구현해 봅시다

 

 

 

ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{
//~~
   virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;

}

대미지는 피해를 입은 액터에 관련 로직을 설정해 주어야 한다.

 

ABCharacter.cpp

void AABCharacter::AttackCheck()
{
    //~~
    if (bResult)
    {
        if (HitResult.Actor.IsValid())
        {
            ABLOG(Warning, TEXT("Hit Actor Name : %s"), *HitResult.Actor->GetName());
        }
        
        FDamageEvent DamageEvent;
        HitResult.Actor->TakeDamage(50.0f, DamageEvent, GetController(), this);
    }

}


float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const & DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
    float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
    ABLOG(Warning, TEXT("Actor : %s took Damage : %f"), *GetName(), FinalDamage);
    return FinalDamage;
}

 

 

 

이제 데미지에 대한 로그도 생기게됩니다!

 

 

이제 죽는 모션도 만들어 봅시다.

 

ABAnimInstance.h

public:
     //~
    void SetDeadAnim() { IsDead = true; }
    
private:
    
     //~
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Pawn, Meta = (AllowPrivateAccess = true))
    bool IsDead;

 

 

ABAnimInstance.cpp 수정

UABAnimInstance::UABAnimInstance()
{
	//~~~
	IsDead = false;
}

void UABAnimInstance::NativeUpdateAnimation(float DeltaSeconds)// 전체수정
{
	Super::NativeUpdateAnimation(DeltaSeconds);

	auto Pawn = TryGetPawnOwner();

	if (!::IsValid(Pawn)) return;

	if (!IsDead)
	{
		CurrentPawnSpeed = Pawn->GetVelocity().Size();
		auto Character = Cast<ACharacter>(Pawn);
		if (Character)
		{
			IsInAir = Character->GetMovementComponent()->IsFalling();
		}
	}

}

void UABAnimInstance::PlayAttackMontage() // 일반 실행
{

	ABCHECK(!IsDead);
	//~~
}


void UABAnimInstance::JumpToAttackMontageSection(int32 NewSection) // 모션 나누는 부분
{
	ABCHECK(!IsDead);
	//~~
}

 

 

 

 

ABCharacter.cpp

float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	//~~~~
	if (FinalDamage > 0.0f) {
		ABAnim->SetDeadAnim();
		SetActorEnableCollision(false);
	}

//~~~~
}

 

 

이제 마지막으로 애님 블루프린트를 고쳐주면! 

 

 

 

 

끝!

 

 

 

틀린점이 있다면 댓 달아주세요!

댓글