이 글은 Inflearn - Rookiss : 언리얼 엔진4 입문 강의를 듣고 정리한 글입니다.
목차.
서론
Collision은 게임 개발에서 오브젝트 간의 상호작용을 제어하는 중요한 개념입니다.
언리얼에서는 오브젝트 간의 충돌을 감지하고 그 결과를 다양한 방식으로 활용할 수 있습니다.
게임을 시작하면 캐릭터가 낙하하지 않고 바닥 위에 서있는 것을 볼 수 있습니다.
기본적으로 캐릭터는 Capsule Component가 붙어있기 때문에 바닥과 충돌하기 때문입니다.
이 글에서는 언리얼에서 Collision 시스템에 대한 이해와
플레이어가 공격을 하면 Collision으로 이벤트를 발생시키도록 해보겠습니다.
오브젝트 채널 추가
Collsion을 제어하기 위해서는 오브젝트 채널을 추가하고 프리셋을 적용해야 합니다.
프로젝트 세팅 -> 콜리전에 Object Channels와 Trace Channels가 있습니다.
Object Channels : 오브젝트 유형 목록
Trace Channels : 어떤 행동을 지정
이런 차이가 있는데 아직은 크게 어떤 차이인지 감이 오지 않습니다.
오브젝트 채널은 특정 오브젝트를 지정하는 것 같고 트레이스 채널은 공격, 점프 등 특정 행동을 지정하는 것 같습니다.
새 오브젝트 채널을 생성하고 이름은 MyCharacter로 설정하고 기본 반응은 Block으로 해주겠습니다.
Block은 Block 오브젝트와 충돌하면 통과하지 못하고 충돌할 수 있게 해 줍니다.
오브젝트 채널을 새로 만들어줬으니 그에 맞게 프리셋을 새로 만들어주겠습니다.
프리셋 추가
프리셋은 일종의 규칙이라고 볼 수 있습니다.
프리셋을 통해 MyCharacter가 어떤 오브젝트와는 충돌하고 어떤 오브젝트는 충돌하지 않게 설정할 수 있습니다.
하단 Preset 항목에서 새 프로파일을 눌러 프로파일을 새로 추가해 줍니다.
이름은 마찬가지로 MyCharacter로 하고 플레이어는 다른 오브젝트와 충돌해야 하기 때문에
콜리전 켜짐 : Collision Enabled
오브젝트 유형 : MyCharacter
오브젝트 채널을 생성할 때 Block으로 기본반응을 설정해 줬기 때문에
트레이스 유형과 오브젝트 유형들이 모두 블록으로 되어있는 것입니다.
블록 : 물체가 뚫고 지나가지 못함.
겹침 : 뚫고 지나가지만 이벤트를 발생시킴(ex 포탈, 퀘스트)
무시 : 둘 다 발생 x
Pawn이 블록으로 체크되어 있는데 이것은 MyCharacter와 Pawn이 충돌할 수 있다는 뜻입니다.
그런데 이렇게 MyCharacter 한 방향에서 충돌 설정을 해줬다고 끝이 아니라 반대로 다른 프리셋도 적절하게 설정해줘야 합니다.
무슨 뜻이냐면
OverlapAll 프리셋을 보면 MyCharacter만 블록으로 되어있다는 것을 볼 수 있습니다.
OverlapAll은 모든 오브젝트들이 통과할 수 있고 이벤트만 발생할 수 있다는 규칙을 가지고 있는데,
MyCharacter 혼자 충돌이 가능하면 안 되니 겹침으로 바꿔줘야 합니다.
다른 프리셋들도 이렇게 콜리전 반응을 적절하게 수정해 줍니다.
프리셋 적용
프리셋을 새로 만들었으니 이제 캐릭터에 적용해 주겠습니다.
캐릭터 블루프린트 클래스에서 캡슐 콜라이더를 클릭 -> 콜리전 -> 콜리전 프리셋에 보면 Default로 Pawn으로 설정되어 있을 건데
이것을 MyCharacter 프리셋으로 변경해 주겠습니다.
이제 게임을 실행하고 캐릭터의 콜리전 프리셋을 확인해 보면
MyCharacter로 잘 변경된 것을 볼 수 있습니다.
여기까지 캐릭터에게 커스텀으로 콜리전을 추가해주는 작업을 했습니다.
이번에는 캐릭터가 공격하면 콜리전을 생성하고 적에게 충돌 시 이벤트를 발생시키도록 해보겠습니다.
트레이스 채널 추가
아까 말한 것처럼 어떤 행동을 지정할 때는 트레이스 채널을 사용한다고 했으니
공격 충돌판정을 위해 트레이스 채널을 추가해 주겠습니다.
기본 반응은 Ignore로 해줍니다.
MyCharacter의 프리셋에 가보면 Attack이 무시로 되어있는데,
Attack은 MyCharacter와 충돌해야 하기 때문에 겹침 또는 블록으로 변경해 줍니다.
C++ 클래스 코드 수정(공격 판정)
공격을 하고 피격이 되었을 때 이 정보를 전달하기 위해 델리게이트 타입을 하나 만들겠습니다.
MyAnimInstance.h
DECLARE_MULTICAST_DELEGATE(FOnAttackHit);
/**
*
*/
UCLASS()
class TESTUNREALENGINE_API UMyAnimInstance : public UAnimInstance
{
....
public:
FOnAttackHit OnAttackHit;
}
MyAnimInstance.cpp
void UMyAnimInstance::AnimNotify_AttackHit()
{
OnAttackHit.Broadcast();
}
전에 만든 노티파이 코드에
이벤트가 발생했을 때 브로드캐스트를 해서 모두에게 알려줄 수 있도록 코드를 작성했습니다.
이와 같은 방법은 리스너 패턴(구독자 패턴)으로 내가 일을 한 것을 구독한 사람에게 알려줄 수 있습니다.
이제 OnAttackHit라는 이벤트가 발생했을 때 내가 받아주고 싶은 함수를 연동하면 되는데,
이 구독을 BeginPlay에서 하기보단 그보다 전에 하는 것이 좋습니다.
BeginPlay에서 모든 컴포넌트들이 초기화된 다음에 서로 연동시켜 주는 부분을 넣는 것이 맞기 때문입니다.
MyCharacter.h
protected:
....
virtual void PostInitializeComponents() override;
....
public:
....
void AttackCheck();
....
MyCharacter.cpp
void AMyCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());
if (AnimInstance)
{
AnimInstance->OnMontageEnded.AddDynamic(this, &AMyCharacter::OnAttackMontageEnded);
AnimInstance->OnAttackHit.AddUObject(this, &AMyCharacter::AttackCheck);
}
}
void AMyCharacter::AttackCheck()
{
FHitResult HitResult;
FCollisionQueryParams Params(NAME_None, false, this);
float AttackRange = 100.f;
float AttackRadius = 50.f;
bool bResult = GetWorld()->SweepSingleByChannel(
OUT HitResult,
GetActorLocation(),
GetActorLocation() + GetActorForwardVector() * AttackRange,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel12,
FCollisionShape::MakeSphere(AttackRadius),
Params);
if (bResult && HitResult.Actor.IsValid())
{
UE_LOG(LogTemp, Log, TEXT("Hit Actor : %s"), *HitResult.Actor->GetName());
}
}
기존에 BeginPlay에 있던 코드도 모두 옮겨주었고 AddUObject라는 것을 추가해서 OnAttackHit라는 이벤트가 발생했을 때 연동하고 싶은 함수를 추가해 주면 됩니다.
AttackCheck라고 공격이 적중했을 때 Debug를 출력하는 코드를 새로 만들었습니다.
코드가 엄청 긴데
SweepSingleByChannel의 매개변수로
OutHit : 결과물
Start : 판정의 시작 부분
End : 판정의 끝부분
Rot : 피격판정의 도형 회전
TraceChannel : 충돌 탐지에 사용할 채널 정보
CollisionShape : 어떤 형태로 탐지할지
정도가 있습니다..
컴파일 후 레벨에 블루프린트 클래스를 미리 하나 배치합니다.
그리고 게임을 실행하고 때리면,
로그가 찍히며 상대방의 정보를 잘 출력하는 것을 볼 수 있습니다.
하지만 공격이 어느 정도의 범위로 나가는지 확인이 어려운데 시각적으로 디버그를 하는 코드도 추가해 보겠습니다.
MyCharacter.cpp
#include "DrawDebugHelpers.h"
....
....
void AMyCharacter::AttackCheck()
{
FHitResult HitResult;
FCollisionQueryParams Params(NAME_None, false, this);
float AttackRange = 100.f;
float AttackRadius = 50.f;
bool bResult = GetWorld()->SweepSingleByChannel(
OUT HitResult,
GetActorLocation(),
GetActorLocation() + GetActorForwardVector() * AttackRange,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel12,
FCollisionShape::MakeSphere(AttackRadius),
Params);
FVector Vec = GetActorForwardVector() * AttackRange;
FVector Center = GetActorLocation() + Vec * 0.5f;
float HalfHeight = AttackRange * 0.5f + AttackRadius;
FQuat Rotation = FRotationMatrix::MakeFromZ(Vec).ToQuat();
FColor DrawColor;
if (bResult)
DrawColor = FColor::Green;
else
DrawColor = FColor::Red;
DrawDebugCapsule(GetWorld(), Center, HalfHeight, AttackRadius,
Rotation, DrawColor, false, 2.f);
if (bResult && HitResult.Actor.IsValid())
{
UE_LOG(LogTemp, Log, TEXT("Hit Actor : %s"), *HitResult.Actor->GetName());
}
}
코드의 형태는 아까 작성한 코드와 비슷합니다.
결과
이제 공격을 하면 빨간색 캡슐이 그려지고 만약 다른 콜라이더와 충돌했다면 초록색 콜라이더가 생성되는 것을 볼 수 있습니다.
이번에 오브젝트 채널, 트레이스 채널 이렇게 두 개의 채널을 추가하고
프리셋을 통해서 규칙을 설정하고
델리게이트를 통해 이벤트를 구독한 뒤
충돌했을 때 함수가 호출되도록 코드를 작성해 봤습니다.
'언리얼' 카테고리의 다른 글
[UE4] 언리얼 엔진 기초 : 아이템 줍기,콜리전 충돌 (0) | 2023.08.30 |
---|---|
[UE4] Skeletal Mesh Socket 무기 장착 (1) | 2023.08.28 |
[UE4] 언리얼 엔진 기초 : 블렌드 스페이스 (0) | 2023.08.24 |
[UE4] 언리얼 엔진 기초 : 애니메이션 노티파이 (0) | 2023.08.24 |
[UE4] 언리얼 엔진 기초 : 델리게이트(Delegate) (0) | 2023.08.23 |