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

수민·2023년 3월 26일
0
post-thumbnail

이득우의 언리얼 C++ 게임 개발의 정석을 읽고 개인 공부 목적으로 요약 정리한 글입니다!


👀 콜리전 설정

물리 엔진은 게임 세계 내 액터들에게 영향을 준다.
캐릭터의 길을 막아주고, 중력이나 외부의 힘으로부터 작용한 힘을 받은 액터의 움직임을 표현해주고, 액터가 지정한 영역에 들어왔는지 감지도 하고,,

이러한 물리엔진을 활용하려면, 콜리전을 설정해야 한다

콜리전 (Collision) : 물리적 충돌 영역

콜리전 제작 방법

스태틱 메시 에셋

스태틱 메시 에셋에 콜리전 영역을 설정
스태틱 메시 컴포넌트에서 Visual, Collsion 두 가지 기능을 설정할 수 있다

기본 도형 컴포넌트 (Primitive Component)

구체(Sphere), 박스(Box), 캡슐(Capsule)의 기본 도형으로 충돌 영역을 설정
스태틱 메시와 별도로 충돌 영역을 제작할 때 사용
주로 Skeletal Mesh를 움직일 때 사용

피직스 에셋

Ragdoll 효과를 구현할 때 사용
(Ragdoll : 특정 상황에서 캐릭터의 각 관절이 흐느적거리는 효과)
캐릭터의 각 부위에 기본 도형으로 충돌 영역을 설정하고 이를 연결해서 캐릭터의 물리 설정
Skeletal Mesh에만 사용할 수 있당

물리 설정

콜리전 채널과 기본 반응

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

채널의 분류

  • 오브젝트 채널 : 콜리전 영역에 지정하는 콜리전 채널
    • WorldStatic : 스태틱 메시
    • WorldDynamic : 움직이는 액터 (Blueprint에 속한 스태틱 메시)
    • Pawn : 플레이어가 조종하는 물체 (캐릭터의 충돌을 담당하는 Capsule Component에 설정됨)
    • PhysicsBody : 물리 시뮬레이션 (움직이는 컴포넌트)
    • Vehicle
    • Destructible
  • 트레이스 채널 : 어떤 행동에 설정하는 콜리전 채널
    • Visiblity : 배경 물체가 시각적으로 보이는지 (picking 기능 구현 시 사용)
    • Camera : 카메라와 목표물 간에 장애물이 있는지 (GTA 방식의 카메라 줌인 기능)

콜리전 채널 설정


이렇게
캡슐 컴포넌트의 콜리전 프리셋에서,
콜리전 프리셋의 값오브젝트 타입 은 서로 다른 설정값이다
우리가 체크해야할 거는 오브젝트 타입의 값!!!!!!!!!!!!!!!!

콜리전 채널 생성하기

프로젝트 세팅 > 콜리전을 보면

이렇게 ObjectChannel, TraceChannel을 새로 추가할 수 있당.

MyCharacter::MyCharacter()
{
	...
    GetCapsuleComponent()->SetCollisionProfileName(TEXT("MyCharacter"));
}

요렇게 코드에서
캡슐 컴포넌트가 내가 만든 프리셋을 사용하도록 지정해주면 된당

콜리전 채널의 용도 (Collision Enabled)

  • Query : 두 물체의 충돌 영역이 서로 겹치는지 (Overlap) 테스트
    충돌 영역이 겹치면 관련 컴포넌트에 BeginOverlap 이벤트 발생
    Raycast, Sweep 기능 (지정한 영역에 물체가 충돌하는지 탐지)
  • Physics : 물리적인 시뮬레이션 사용시 설정
  • Query and Physics : 위 두 기능을 모두 사용하는 설정

둘 중 하나만 하는게 효과적이다.
계산량이 많아지니까,,

다른 콜리전 채널과의 반응

  • 무시 (Ignore) : Collision이 있어도 아무 충돌이 일어나지 않는다

  • 겹침 (Overlap) : 무시와 동일하게 물체가 뚫고 지나갈 수 있지만 이벤트를 발생시킨다

  • 블록 (Block) : 물체가 뚫고 지나가지 못하도록 막는다

무시 > 겹침 > 블록
순으로 우선된다.

무시가 하나라도 있으면 겹침, 블록은 발생하지 않는다

겹침 : BeginOverlap 이벤트 발생
블록 : Hit 이벤트 발생
(Generate Overlap Events 항목 양쪽 컴포넌트에 모두 체크되어 있으면 BeginOverlap도 발생시킬 수 있음)


👀 트레이스 채널의 활용

공격이란,,
공격 범위 안에 액터가 있는지 감지하고 감지된 액터한테 데미지를 주는 행위,,자나
행동에 대한 판정이니까 트레이스 채널을 활용해야 한다

트레이스 채널로 충돌 여부 알아내기

SweepSingleByChannel()
: 트레이스 채널을 활용해 물리적 충돌 여부를 가리는 함수
기본 도형을 인자로 받고, 시작 지점부터 끝 지점을 쓸면서 해당 영역 내에 물리 판정이 일어났는지 조사함

인자

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

트레이스 채널값 알아내기

Config/DefaultEngine.ini 를 보면

이렇게 내가 설정한
오브젝트 채널(MyCharacter)과 트레이스 채널(Attack)이 배정받은 채널값을 알아낼 수 있다.

MyCharacter = ECC_GameTraceChannel1
Attack = ECC_GameTraceChannel2
를 배정받았넹


👀 충돌 구현

충돌 검사 조건

반지름이 50cm인 구를 통해
액터가 있는 곳에서 시선 방향으로 200cm동안 탐색하자
자신을 제외한 액터가 있는지 검사하고
충돌이 감지되면 충돌체의 정보를 받아와보자

디버그 드로잉

DrawDebugHelpers.h : 원하는 도형을 그려서 수월한 디버깅을 돕는 기능

그래서
탐색을 위해 원이 움직인 궤적을 표현할거다

데미지 프레임워크

TakeDamage()

AActor 클래스가 가진 함수.
손쉽게 액터에 데미지를 전달할 수 있다

인자

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

데미지에서는 가해자피해자가 존재하는데,
가해자 : 피해를 입힌 주체 (폰에게 명령을 내린 플레이어 컨트롤러)
따라서,
EventInstigator에는 컨트롤러의 정보를 넣어줘야 한다

폰은 플레이어가 데미지 전달을 위해 사용한 도구니까
DamageCauser에는 폰을 넣어주면 된다

이렇게 하면
대상 액터에 데미지를 전달해줬다.

그러면 피해를 입은 액터가 처리를 해줘야 한당.

TakeDamge 오버라이드

액터가 받은 데미지를 처리하는 로직은
TakeDamage 함수를 오버라이드해서 구현한다.
Super 써야되는거 알쥐..?

구현부

MyCharacter.h

#include "Hunt_Prototype.h"
#include "GameFramework/Character.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "MyCharacter.generated.h"

UCLASS()
class HUNT_PROTOTYPE_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()
	... 
private:
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		float AttackRange;

	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
		float AttackRadius;

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


private:
	void AttackCheck();
};

MyCharacter.cpp

AMyCharacter::AMyCharacter()
{
	...
	GetCapsuleComponent()->SetCollisionProfileName(TEXT("MyCharacter"));

	AttackRange = 200.0f;
	AttackRadius = 50.0f;
}

void AMyCharacter::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 (::IsValid(HitResult.GetActor())) {
			HUNT_LOG(Warning, TEXT("Hit Actor Name : %s"), *HitResult.GetActor()->GetName());

			FDamageEvent DamageEvent;
			HitResult.GetActor()->TakeDamage(50.0f, DamageEvent, GetController(), this);
		}
	}
}

float AMyCharacter::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser)
{
	float FinalDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
    // 부모 클래스의 로직 먼저 실행해줘야 함

	HUNT_LOG(Warning, TEXT("Actor : %s took Damage : %f"), *GetName(), FinalDamage);

	if (FinalDamage > 0.0f) {
		MyAnim->SetDeadAnim();				// 죽는 애니메이션 재생
		SetActorEnableCollision(false);
	}

	return FinalDamage;
}

😊

ㅋㅋㅋ
이번꺼,,
진짜 뭔가 아우
난 항 ~ 상 모든 개발 할 때 충돌이 제일 싫다
역시,, 가장 오래걸리고 하기 싫었떤 부분 ㅠㅠ
그래도 엔진이라 직관적인 것 같다
다양하게 충돌처리 해보면서 더 자세히 익히고, 익숙해지면 될 것 같다

profile
우하하

0개의 댓글