이득우의 언리얼 C++ 게임 개발의 정석을 읽고 개인 공부 목적으로 요약 정리한 글입니다!
물리 엔진은 게임 세계 내 액터들에게 영향을 준다.
캐릭터의 길을 막아주고, 중력이나 외부의 힘으로부터 작용한 힘을 받은 액터의 움직임을 표현해주고, 액터가 지정한 영역에 들어왔는지 감지도 하고,,
이러한 물리엔진을 활용하려면, 콜리전을 설정해야 한다
스태틱 메시 에셋에 콜리전 영역을 설정
스태틱 메시 컴포넌트에서 Visual, Collsion 두 가지 기능을 설정할 수 있다
구체(Sphere), 박스(Box), 캡슐(Capsule)의 기본 도형으로 충돌 영역을 설정
스태틱 메시와 별도로 충돌 영역을 제작할 때 사용
주로 Skeletal Mesh를 움직일 때 사용
Ragdoll 효과를 구현할 때 사용
(Ragdoll : 특정 상황에서 캐릭터의 각 관절이 흐느적거리는 효과)
캐릭터의 각 부위에 기본 도형으로 충돌 영역을 설정하고 이를 연결해서 캐릭터의 물리 설정
Skeletal Mesh에만 사용할 수 있당
충돌체에는 반드시 하나의 콜리전 채널을 설정해야 한다.
콜리전 채널 설정
이렇게
캡슐 컴포넌트의 콜리전 프리셋에서,
콜리전 프리셋의 값과 오브젝트 타입 은 서로 다른 설정값이다
우리가 체크해야할 거는 오브젝트 타입의 값!!!!!!!!!!!!!!!!
프로젝트 세팅 > 콜리전
을 보면
이렇게 ObjectChannel, TraceChannel을 새로 추가할 수 있당.
MyCharacter::MyCharacter()
{
...
GetCapsuleComponent()->SetCollisionProfileName(TEXT("MyCharacter"));
}
요렇게 코드에서
캡슐 컴포넌트가 내가 만든 프리셋을 사용하도록 지정해주면 된당
BeginOverlap
이벤트 발생둘 중 하나만 하는게 효과적이다.
계산량이 많아지니까,,
무시 (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 : 원하는 도형을 그려서 수월한 디버깅을 돕는 기능
그래서
탐색을 위해 원이 움직인 궤적을 표현할거다
AActor 클래스가 가진 함수.
손쉽게 액터에 데미지를 전달할 수 있다
인자
데미지에서는 가해자와 피해자가 존재하는데,
가해자 : 피해를 입힌 주체 (폰에게 명령을 내린 플레이어 컨트롤러)
따라서,
EventInstigator에는 컨트롤러의 정보를 넣어줘야 한다
폰은 플레이어가 데미지 전달을 위해 사용한 도구니까
DamageCauser에는 폰을 넣어주면 된다
이렇게 하면
대상 액터에 데미지를 전달해줬다.
그러면 피해를 입은 액터가 처리를 해줘야 한당.
액터가 받은 데미지를 처리하는 로직은
TakeDamage 함수를 오버라이드해서 구현한다.
Super 써야되는거 알쥐..?
#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();
};
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;
}
😊
ㅋㅋㅋ
이번꺼,,
진짜 뭔가 아우
난 항 ~ 상 모든 개발 할 때 충돌이 제일 싫다
역시,, 가장 오래걸리고 하기 싫었떤 부분 ㅠㅠ
그래도 엔진이라 직관적인 것 같다
다양하게 충돌처리 해보면서 더 자세히 익히고, 익숙해지면 될 것 같다