CollisionBox
무기로 적을 공격하려면 무기에 충돌 관련 처리를 위한 컴포넌트가 필요하다.
충돌 처리를 위한 박스 컴포넌트를 먼저 추가해준다.// AWeapon.h ... class UBoxComponent; ... private: // 무기 충돌 처리를 위한 BoxComponent UPROPERTY(VisibleAnywhere) UBoxComponent* CollisionBox; ...
// AWeapon.cpp ... #include "Components/UBoxComponent.h" ... AWeapon::AWeapon() { ... // CollisionBox 관련 CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXt("CollisionBox")); CollisionBox->SetupAttachment(GetRootComponent()); ... }
우측의
Shape
탭에서 크기를 수정한다.
일단 Trace를 위한 간단한 DebugSphere를 생성한다.
EventGraph에서On Component Begin Overlap (CollisionBox)
노드를 생성하고Draw Debug Sphere
노드와 연결한다.
캐릭터와 겹칠 경우 구체가 생성되는 것을 확인할 수 있다.
다른 물체와의 충돌을 확인하기 위해 벽을 하나 세우고 충돌 관련 설정을 진행한다.
공격 시도시 좌측 하단 Gizmo가 위치한 곳에 충돌이 생성되는 것을 확인할 수 있다.
Tracing
실제 인게임에서는 CollisionBox가 충돌한 지점에 콜리전이 발생해야 한다. 이를 해결하기 위해서는 Tracing을 해야 한다. Tracing에는 여러가지가 있다.
LIneTracing
LineTrace
는 시작점과 끝 사이에 선을 연결하고, 선에 물체가 충돌할 경우FHitResult
타입의 구조체를 반환한다.FHitResult
는OtherActor
,ImpactPoint
,HitLocation
등의 여러 정보를 포함하고 있으므로, 해당 데이터를 통해 충돌 관련 구현(피격시 피격 이펙트, 피격 위치 여부에 따른 데미지 증감 , 격발시 총 궤적에 연기 fx 추가 등등)이 가능하다.SphereTracing
SphereTrace
는 시작점과 끝점에서 구체를 통해 Trace를 진행한다. 똑같이 물체와 충돌시 FHitResult라는 구조체를 반환하는데, 여기서 중요한건HitLocation
은 충돌시 CollisionSphere의 충돌 지점에서의 중심이고,ImpactPoint
가 실제 물체와 충돌이 발생한 지점이라는 것이다.
Tracing은 박스 형태로도 가능하다.
현재 Weapon클래스는 CollisionBox를 사용중이므로 박스를 이용한 Tracing을 할것이다.Blueprint에서 구현
먼저 시작점과 끝점을 나타내기 위해(시각적인 것이 하나도 없는 SceneComponent이용)SceneComponent
를 두개 추가한다.
기존의Draw Debug Spehre
노드는 삭제하고,Box Trace By Channel
노드를 생성한다.
- Trace Channel
Visibility는 눈에 보이는 모든 것을 Visibility 채널을 막을 수 있다.
Start
와End
로부터GetWorldLocation
노드를 이용해 Trace 시작지점과 끝지점을 연결해주고 박스의 사이즈(원하는 길이가 5일경우 반인 2.5만 입력)를 입력한다.
Draw Debug Type
을For Duration
으로 선택하고 기본 설정 그대로 가져간다.
컴파일 후 실행하면 Tracing 구간은 빨간색, Hit Color는 초록색으로 나타나는 것을 확인할 수 있다.
Out Hit
핀에서 끌고 나와Break Hit Result
노드를 생성하면FHitResult
에 속하는 값들을 전부 확인할 수 있다.Impact Point
를 확인하고 싶다면Draw Debug Spehre
노드를 추가하고 해당 벡터값을Center
에 두고 값을 적절히 입력하면 ImpactPoint 확인도 가능하다.충돌지점인 벽면이 구체의 중심에 있는 것을 확인 가능하다.
충돌한 오브젝트 이름도 확인가능하다.
이상태로 캐릭터를 하나 더 소환하고 공격하면Impact Point
가 발생하지 않는다.CapsuleComponent
가 Mesh와의 충돌을 방해하고 있기 때문이다.벽은Visible
을Block
한다. 그래서 Visible 채널의 BoxTracing에서 충돌이 발생하고,TraceHit
(초록색박스)과ImpactPoint
(파란색구체)가 생성된다.
하지만 캡슐 컴포넌트는Visible
을Ignore
한다. 그래서 Visible 채널의 BoxTracing에서 충돌이 발생하지 않으므로TraceHit
(초록색박스)과ImpactPoint
(파란색구체)가 생성되지 않는다.
Visible 채널의 BoxTracing을 무시하니 캐릭터 메시에 아무것도 생기지 않는 것이다.
이를 해결하기 위해서는 캡슐 컴포넌트가 무기의 CollisinoBox가 overlap되는 이벤트를 발생시키면 안된다.
먼저 무기의Collision Preesets
을Custom
으로 변경하고 캡슐 컴포넌트 타입인Pawn
을 무시하도록 설정한다.
문제는 캐릭터 메시도Pawn
이므로 월드에 세팅해둔 또다른 캐릭터의 메시Collision Preset
을Custom
으로 변경하고Object Type
을WorldDynamic
으로 변경한다(블루프린트나 cpp클래스를 수정하는 것이 아니므로 큰 상관x).
그리고 Visibility Channel을 무시하지 않도록Block
을 체크한다.
또한 Overlap(ComponentBox가 Overlap을 통해 BoxTrace를 함) 확인을 위해Generate Overlap Events
도 활성화시켜준다.
이제 실행하면 정상적으로BoxTrace
를 통한HitPoint
와ImpactPoint
를 확인할 수 있다.cpp에서 구현
// Aweapon.h ... protected: virtual void BeginPlay() override; ... // 무기와 OtherActor 충돌 시 발생시킬 overlap함수 // 동적 멀티캐스트 델리게이트에 바인딩될 것이므로 UFUNCTION()필요 // OnSphereBeginOverlap과 OnSphereEndOverlap은 부모클래스(Item.h)에서 상속받은 것이므로 원본에 UFUNCION()이 있음 UFUNCTION() void OnBoxBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult); ... private: // CollsionBox에서 Trace 시작 지점 UPROPERTY(VisibleAnywhere) USceneComponent* BoxTraceStart; // CollisionBox에서 Trace 끝 지점 UPROPERTY(VisibleAnywhere) USceneComponent* BoxTraceEnd; ...
// AWeapon.cpp ... #include "Kismet/KismetSystemLibrary.h" ... // 생성자 AWeapon::Aweapon() { ... // 해당 오브젝트에 가능한 충돌 관련 컨트롤 // Collision Preset, 쿼리 관련 충돌만 처리한다 CollisionBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly); // BP에 있는 Collision Responses(Ignore, Overlap, Block), 전부 overlap으로 설정 CollisionBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); // 특정 채널에 대한 CollisionResponse 설정, Pawn을 Ignore CollisionBox->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore); // BoxTraceStart BoxTraceStart->CreateDefaultSubobject<USceneComponent>(TEXT("BoxTraceStart")); BoxTraceStart->SetupAttachment(GetRootComponent()); // BoxTraceEnd BoxTraceEnd->CreateDefaultSubobject<USceneComponent>(TEXT("BoxTraceEnd"); BoxTraceEnd->SetupAttachment(GetRootComponent()); } // BeginPlay void AWeapon::BeginPlay() { Super::BeginPlay(); // BoxComponent 델리게이트 CollisionBox->OnComponentBeginOvlap->AddDynamic(this, &AWeapon::OnBoxBeginOverlap); } ... void AWeapon::OnBoxBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult) { /* * UKismetSystemLibrary::BoxTraceSingle( * const UObject* WorldContextObject : World에 존재하는 객체(Weapon이므로 this), * const FVector Start : Trace 시작 지점 * const FVector End : Trace 끝지점 * const FVector HalfSize : TraceBox 사이즈(half) * const FRotator Orientation : TraceBox Rotation * ETraceTypeQuery TraceChannel : 사용자 정의 트레이스유형(필요에 따라 채널을 골라 수정후 사용) * bool bTraceComplex : 충돌 정확도에 관련된 bool변수(trace구현은 복잡한 충돌 필요없음) * const TArray<AActor*>& ACtorsToIgnore : BoxTrace가 무시할 Actor 클래스 배열 * EDrawDebugTrace::Type DrawDebugType : DrawDebug속성, 기간동안, 혹은 영구유지할지에 대한 설정 * FHitResult& OutHit : BP에선 output으로 결과를 받지만, cpp에선 input으로 받음. input으로 받아 타격 결과에 대한 변수값을 채움(const reference가 아닌 일반 reference이므로 값 변경 가능) * bool bIgnoreSelf : 자기자신 무시, 위의 ActorsToIgnore 효과의 일종 * FLinearColor TraceColor : TraceColor * FLinearColor TraceHitColor : TraceHitColor * float DrawTime : DrawDebug 지속시간 */ /** BoxTraceSingle에 사용할 parameter */ // 무시할 Actor class 배열, 무기 자기자신은 무시하도록 추가 TArray<AActor*> ActorsToIgnore; ActorsToIgnore.Add(this); // 충돌 결과에 대한 값 저장 FHitResult CollisionBoxHit; // Trace 시작지점과 끝지점의 위치벡터 const FVector TraceStart = BoxTraceStart->GetComponentLocation(); const FVector TraceEnd = BoxTraceEnd->GetComponentLocation(); UKismetSystemLibrary::BoxTraceSingle(this, TraceStart, TraceEnd, FVector(5.f, 5.f, 5.f), BoxTraceStart->GetComponentRotation(), ETraceTypeQuery::TraceTypeQuery1, false, ActorToIgnore, EDrawDebugTrace::ForDuration, CollisionBoxHit, true);
BP_Weapon으로 돌아와 BoxTraceStart와 BoxTraceEnd를 조절하고 실행하면 잘 실행되는 것을 확인할 수 있다.
공격할때만 Tracing 적용하기
지금까지의 과정은 무기의
CollisionBox
가ActionState
와 상관없이 overlap되는 과정이다.
실제 게임에서는 공격 도중에 충돌이 발생하고, 그 결과에 따른 무언가를 하도록 해야한다.
우선AM_AttackMontage
에서Notifies
트랙과Notify
를 추가한다.
ABP_WraithCharacter로 돌아가서 노티파이 노드를 생성하고, Character가 유효한지 검사한다.
실행부분은 cpp로 구현한다.
먼저 Weapon이 초기에 충돌이 없도록 코드를 수정한다.
캐릭터에서 CollisionBox에 접근가능하도록 getter함수를 만들어주고// Aweapon.h ... public: FORCEINLINE UBoxComponent* GetWeaponBox() const { return CollisionBox; } ...
weapon의 collision을 기본적으로 nocollision으로 변경한다.
// AWeapon.cpp AWeapon::AWeapon() { ... CollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); ... }
WraithCharacter.h 파일도 수정하고
// AWraithCharacter.h ... protected: // EnableBoxCollsion 노티파이 도달시 실행 UFUNCTION(BlueprintCallable) void SetEnableWeaponCollision(ECollisionEnabled::Type CollisionEnabled); // DisableBoxCollision 노티파이 도달시 실행 UFUNCTION(BlueprintCallable) void SetDisableWeaponCollision(ECollisionEnabled::Type CollisionEnabled); ...
마지막으로 WarithCharacter.cpp 도 코드를 수정한다.
... #include "Components/BoxComponent.h" ... // AWraithCharacter.cpp ... // EnableBoxCollision 노티파이 도달시 충돌 가능하도록 변경 void AWraithCharacter::SetEnableBoxCollision(ECollisionEnabled::Type CollisionEnabled) { if(EquippedWeapon && EquippedWeapon->GetCollisionBox()) { EquippedWeapon->GetCollisionBox()->SetCollisionEnabled(CollisionEnabled); } } // DisableBoxCollision 노티파이 도달시 충돌 불가능하도록 변경 void AWraithCharacter::SetDisableBoxComponent(ECollisionEnabled::Type CollisionDisabled) { if(EquippedWeapon && EquippedWeapon->GetCollisionBox()) { EquippedWeapon->GetCollisionBox()->SetCollisionEnabled(CollisionDisabled); } } ...
노드를 연결시켜주면 제대로 작동하는 것을 확인할 수 있다.
<오류>
<오류>
boxtrace 정상적으로 작동하고, ImpactPoint, HitPoint 다 잘 작동되는데 몇몇 위치에서 위->아래 찍기 공격시 collision이 발생하지 않는 문제 있음.