본문 바로가기

게임 개발/이득우의 언리얼 C++

[이득우의 언리얼 C++ 게임개발의 정석] #9. 충돌 설정과 대미지 전달

반응형

이득우의-언리얼-C++-게임개발의-정석-표지

게임 콘텐츠는 액터와 액터 간의 상호작용을 기반으로 한다.

좋은 게임 콘텐츠는 시각적인 요소뿐 아니라 기획의 의도대로 액터들이 상호작용할 수 있도록

물리 엔진을 제대로 설정하는 것이 중요하다.

물리 엔진을 설정하는 방법을 살펴보고, 기획한 대로 물리 시스템이 동작하는지 파악해 보자. 

<1>. 콜리전 설정 - (콜리전 = 물리적 충돌 영역)

※ 액터의 움직임을 사실적으로 표현하는 데 물리 엔진이 활용된다.

→ 물리 엔진을 활용하려면 콜리전부터 학습해야 한다.

 

-콜리전 제작 방법

  • 스태틱메시 에셋 : 스태틱메시 에셋에 콜리전 영역을 심는 방법.
  • 기본도형 컴포넌트 : 구체, 박스, 캡슐 등의 도형을 사용해 충돌 영역을 지정하는 방법. 스태틱메시와 별도로 충돌 영역을 제작하는 데 사용한다. / 스켈레탈 메시를 움직일 때 주로 사용한다.
  • 피직스 에셋 : 일반적인 캐릭터의 이동은 캡슐 컴포넌트를 사용해 처리한다. 하지만 각 관절이 흐느적거리는 '헝겊 인형' 효과를 구현할 때 피직스 에셋을 사용한다. 각 부위에 기본 도형으로 충돌 영역 설정. 피직스 에셋은 스켈레탈 메시에게만 사용할 수 있다.

콜리전을 설정을 했으면 어떻게 활용할지 지정해야 한다. 스태틱 메시에는 기본적으로 'BlockAll'이라는 설정이 있어서 캐릭터의 이동을 방해할 수 있지만, 그 이상의 콘텐츠를 제작하려면 물리 엔진의 세부적인 설정을 학습해야 한다.

 

-물리 설정

  • 콜리전 채널과 기본 반응
  • 콜리전 채널의 용도
  • 다른 콜리전 채널과의 반응

충돌체에는 반드시 하나의 콜리전 채널을 설정해야 한다.

※ 콜리전 채널 : 게임 오브젝트들이 서로 충돌하는 방식을 제어하기 위한 시스템

 

- 콜리전 채널(8)  ( 오브젝트 채널 - 콜리전 영역에 지정하는 /  트레이스 채널 - 어떤 행동에 설정하는 )

  • WorldStatic : 움직이지 않는 정적인 배경 액터에 사용. 주로 스태틱메시컴포넌트에 사용한다.
  • WorldDynamic : 움직이는 액터에 사용. 블루프린트에 속한 스태틱메시 컴포넌트에 사용한다.
  • Pawn : 플레이어가 조종하는 물체에 사용. 캐릭터의 충돌을 담당하는 캡슐 컴포넌트에 설정된다. 
  • Visibility : 배경 물체가 시각적으로 보이는지 탐지하는 데 사용. 마우스로 물체를 선택하는 피킹기능 구현에 사용.
  • Camera : 카메라와 목표물 간에 장애물이 있는지 탐지하는 데 사용. 장애물이 시야를 가리면 줌인하는 기능.
  • PhysicsBody : 물리 시뮬레이션으로 움직이는 컴포넌트에 설정한다.
  • Vehicle : 차량과 관련된 객체의 충돌 처리 관리
  • Destructible : 파괴 가능한 객체와 관련된 충돌 처리 관리

- 해당 컴포넌트에서 물리 기능을 어떻게 사용할지

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

- 설정된 콜리전 채널이 상대방 컴포넌트의 콜리전 채널과 어떻게 반응할지 지정

  • 무시 : 콜리전이 있어도 아무 충돌이 일어나지 않는다.
  • 겹침 : 물체가 뚫고 지나갈 수 있지만 이벤트를 발생시킨다.
  • 블록 : 물체가 뚫고 지나가지 못하도록 막는다.

※ 게임 기획을 고도화하려면 기본으로 제공되는 콜리전 채널 말고도, 그에 맞게 새로운 콜리전 채널을 추가해야 한다.

 

→ 기존 콜리전 프리셋들과의 충돌 반응을 조정해야 한다. 우리가 만든 콜리전 채널 'ABCharacter'의 기본 반응은 블록이므로 기존 콜리전 프리셋에서 블록 반응을 하면 안 되는 설정을 찾아서 변경해 두자.

→ 모든 콜리전 프리셋과의 반응 설정을 완료하면, 캡슐 컴포넌트가 해당 프리셋을 사용하도록 코드를 작성한다.

//ABCharacter.cpp

AABCharacter::AABCharacter()
{
	GetCapsuleComponent()->SetCollisionProfileName(TEXT("ABCharacter"));
}

<2>. 트레이스 채널의 활용

언리얼 엔진은 행동에 대한 판정을 위해 '트레이스 채널'이라는 카테고리로 콜리전 채널을 제공한다.

캐릭터의 공격 판정을 위한 트레이스 채널을 추가해 보자.

→ Attack이라는 이름의 트레이스 채널을 추가하고 기본 반응을 '무시'로 설정한다.

 

트레이스 채널의 설정을 완료하면 이제 공격 판정을 내리는 로직을 캐릭터에 추가해 본다.

→ 'SweepSingleByChannel' : 트레이스 채널을 사용해 물리적 충돌 여부를 가리는 함수. / GetWorld() 함수를 사용해 월드에게 명령을 내려야 한다.

- 이 함수는 기본 도형을 인자로 받은 후 시작 지점에서 끝 지점까지 쓸면서 영역 내에 물리 판정이 일어났는지를 조사한다.

- 이 함수에서 사용할 파라미터

  • HitResult : 물리적 충돌이 탐지된 경우 관련된 정보를 담을 구조체
  • Start : 탐색을 시작할 위치
  • End : 탐색을 끝낼 위치
  • Rot : 탐색에 사용할 도형의 회전
  • TraceChannel : 물리 충돌감지에 사용할 트레이스 채널 정보
  • CollisionShape : 탐색에 사용할 기본 도형 정보. ex) 구체, 캡슐, 박스...
  • Params : 탐색 방법에 대한 설정 값을 모아둔 구조체
  • ResponseParams : 탐색 반응을 설정하기 위한 구조체

※콜리전 채널 지정 : TraceChaneel에서 Attack 채널을 쓸 건데, Attack 채널의 값은 Config 폴더에 있는 DefaultEngine.ini에서 확인할 수 있다. → ECC_GameTraceChannel 1~18번 중 하나를 배정받는다. 

 

※ 도형에 대한 정보

  1. 'FCollisionShape::MakeSphere' 함수를 사용해 탐지에 사용할 도형을 제작한다.(반지름 50cm인 구체, 회전값=기본값)
  2. 도형의 탐색 영역 지정 : 시작위치 = 액터 있는 곳 / 끝낼 위치 = 액터 시선 방향으로 200cm 떨어진 곳.
  3. 탐색 방법 설정 : 자기 자신은 이 탐색에 감지되지 않도록 포인터 this를 무시할 액터 목록에 넣어줘야 함. 탐색 반응 설정은 구조체의 기본값 사용
  4. 충돌된 액터에 관련된 정보를 얻기 위해 구조체를 넘겨줘야 함. → 'FHitResult' 구조체로 지역 변수 생성하고, 이를 첫 번째 인자에 넣어주면 탐색 기능 완성!
//ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{
	...
private:
	void AttackCheck();
}

//ABCharacter.cpp

void AABCharacter::PostInitializeComponents()
{
	...
    ABAnim->OnAttackHitCheck.ADDUObject(this, &AABCharacter::AttackCheck);
    //델리게이트에 AttackCheck함수 등록
}

void AABCharacter::AttackCheck()
{
	FHitResult HitResult;  //FHitResult 구조체로 지역 변수 생성하기.
    FCollisionQueryParams Params(NAME_None, false, this);  //탐색 방법 설정, 자기 자신 감지X
    bool bResult = GetWorld()->SweepSingleByChannel{  //탐색 기능을 만들기
    	HitResult,
        GetActorLocation(),   //탐색 시작 위치
        GetActorLocation()+GetActorForwardVector() * 200.0f,  //탐색 끝낼 위치
        FQuat::Identity,          //회전값 = 기본값
        ECollisionChannel::ECC_GameTraceChannel12,   //Attack의 트레이스 채널
        FCollisionShape::MakeSphere(50.0f),          //탐지에 사용할 도형
        Params);
   	
    if(bResult)
    {
    	if(::IsValid(HitResult.GetActor()))
        {
        	ABLOG(Warning, TEXT("Hit Actor Name : %s"), *HitResult.GetActor()->GetName());
        }
    }
}

 

<3>. 디버그 드로잉

공격할 때마다 로그 창을 보는 것은 매우 번거롭다. 이를 시각적으로 표현하는 것을 '디버그 드로잉'이라고 한다.

 

※ 'DrawDebugCapsule' : 캡슐 모양을 그리는 기능을 가진 함수.

 

이 함수를 사용해서 탐색을 위해 원이 움직인 궤적을 표현해 보자.

→ 캡슐의 반지름 50으로 설정 →  탐색 시작 위치에서 끝 위치로 향하는 벡터를 구한다 → 벡터의 중점 위치와 벡터 길이의 절반을 대입하면 우리가 원하는 캡슐 모양을 구할 수 있다.

→  이렇게 되면 캡슐이 상하로 서있는 모습이므로, 회전 행렬을 적용해 캐릭터 시선 방향으로 눕힌 후 공격 범위에 맞게 길이를 설정한다.

(캐릭터 시선 방향으로 눕는다는 것 = 캡슐의 벡터 Z가 캐릭터의 시선방향과 일치한다는 것)

 

//ABCharacter.cpp

#include "DrawDebugHelpers.h"

AABCharacter::AABCharacter()
{
	AttackRange = 200.0f;
    AttackRadius = 50.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_GameTraceChannel12,
        FCollisionShape::MakeSphere(AttackRadius),
        Params);
 
#if ENABLE_DRAW_DEBUG   //디버그 모드에서만 디버그 캡슐을 그리도록 조건부 컴파일
	FVector TraceVec = GetActorForwardVector() * AttackRange; //액터의 앞 방향 벡터 * 공격 범위
    FVector Center = GetActorLocation() + TraceVec * 0.5f; //Center 구하기
    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("...");
         }
    }
}

 

이렇게 하면, 공격 판정이 발생하면 녹색, 판정되지 않으면 붉은색으로 표시된다.

<4>. 대미지 프레임워크

이제까지 물리 엔진을 사용해 공격 영역을 설정하고, 공격을 받을 액터를 감지하는 부분까지 구현했다.

→ 감지된 액터에 대미지를 전달하게 해 보자.

→ 액터 클래스 AActor는 'TakeDamage'라는 함수가 구현돼 있다. 총 4개의 인자를 가지고 있다.

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

여기서 가해자는 폰이 아니라, 플레이어 컨트롤러이다.

//ABCharacter.cpp

#include "Engine/DamageEvents.h"  //책에선 안나와있지만, 이게 없으면 FDamageEvent가 선언이 안됨

if(bResult)
    {
    	if(HitResult.Actor.IsValid())
        {
        	ABLOG(Warning, TEXT("...");
            
            FDamageEvent DamageEvent;
            HitResult.Actor->TakeDamage(50.0f, DamageEvent, GetController(), this);
         }
    }

 

해당 로직을 사용해 대상 액터에 대미지 전달까진 완료했다.

하지만 대미지라는 것은 피해를 입은 액터에 관련 로직을 구성해 줘야 최종 완성된다.

→ 액터의 'TakeDamage' 함수를 오버라이드해 액터가 받은 대미지를 처리하는 로직을 추가한다.

→ TakeDamage 함수는 부모 클래스인 AActor에 기본적인 대미지 로직이 구현돼 있기 때문에, Super 키워드를 사용해 부모 클래스의 로직을 먼저 실행해줘야 한다. ★ ★ ★

 

//ABCharacter.h

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

//ABCharacter.cpp

float AABCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
                       AController* EventInstigator, AActor* DamageCauser)
{
	float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
    return FinalDamage;
}

 

이제 대미지를 받으면 캐릭터가 사망하는 애니메이션을 설정한다.

 

  1. AnimInstance에서 IsDead라는 속성을 추가한다.
  2. 애님 그래프를 설계한다.
  3. 죽은 후에는 SetActorEnableCollision 함수를 사용해 액터의 충돌 설정을 끈다.

 

 

[이득우의 언리얼 C++ 게임개발의 정석] #10. 아이템 상자와 무기 제작

언리얼 엔진의 '소켓 시스템'을 활용해 캐릭터 에셋에 액새서리를 부착하는 방법을 학습한다.물리 엔진 기능을 활용해 플레이어 캐릭터만 감지하는 아이템 상자를 제작하고상자를 먹으면 아이

lbto.tistory.com

 

반응형