본문 바로가기

Unreal Engine 4/C++

<Unreal C++> 22 - SweepTrace

 


필요한 개념


// ECollisionChannel을 이용해 여러개의 오브젝트 채널을 등록
// ECC_EngineTraceChannel1은 우리가 쓰지 않는다.
Mesh->SetCollisionObjectType(ECollisionChannel::ECC_PhysicsBody);

enum ECollisionChannel
{

ECC_WorldStatic UMETA(DisplayName="WorldStatic"), -> 0
ECC_WorldDynamic UMETA(DisplayName="WorldDynamic"), -> 1
ECC_Pawn UMETA(DisplayName="Pawn"),
ECC_Visibility UMETA(DisplayName="Visibility" , TraceQuery="1"),
ECC_Camera UMETA(DisplayName="Camera" , TraceQuery="1"),
ECC_PhysicsBody UMETA(DisplayName="PhysicsBody"),
ECC_Vehicle UMETA(DisplayName="Vehicle"),
ECC_Destructible UMETA(DisplayName="Destructible"), -> 7번

/** Reserved for gizmo collision */
ECC_EngineTraceChannel1 UMETA(Hidden), -> 8번

ECC_EngineTraceChannel2 UMETA(Hidden), 
ECC_EngineTraceChannel3 UMETA(Hidden),
ECC_EngineTraceChannel4 UMETA(Hidden), 
ECC_EngineTraceChannel5 UMETA(Hidden),
ECC_EngineTraceChannel6 UMETA(Hidden),

ECC_GameTraceChannel1 UMETA(Hidden), -> 14번
ECC_GameTraceChannel2 UMETA(Hidden),
ECC_GameTraceChannel3 UMETA(Hidden),
ECC_GameTraceChannel4 UMETA(Hidden),
ECC_GameTraceChannel5 UMETA(Hidden),
ECC_GameTraceChannel6 UMETA(Hidden),
ECC_GameTraceChannel7 UMETA(Hidden),
ECC_GameTraceChannel8 UMETA(Hidden),
ECC_GameTraceChannel9 UMETA(Hidden),
ECC_GameTraceChannel10 UMETA(Hidden),
ECC_GameTraceChannel11 UMETA(Hidden),
ECC_GameTraceChannel12 UMETA(Hidden),
ECC_GameTraceChannel13 UMETA(Hidden),
ECC_GameTraceChannel14 UMETA(Hidden),
ECC_GameTraceChannel15 UMETA(Hidden),
ECC_GameTraceChannel16 UMETA(Hidden),
ECC_GameTraceChannel17 UMETA(Hidden),
ECC_GameTraceChannel18 UMETA(Hidden),
}


프로젝트 세팅 -> 콜리전 -> Preset -> NoCollision에서 트레이스 유형,  오브젝트 유형보면
WorldStatic, WorldDynamic, Pawn, PhysicsBody, Vehicle, Destructible 이후로 1번, 2번 3번.... 이렇게 간다.
이 것과 위의 ECollisionChannel의 구조가 동일하다.

ECollisionChannel와 동일함



그 다음 오브젝트 타입을 만들게 되면 ECC_GameTraceChannel1, 2, 3, 4 이런식으로 순서대로 지정된다.

Tip)
SpawnEmitter를 사용하게 되면 호출하는 AActor 객체가 자동으로 Component가 생성되고 플레이가 끝나면 자동으로 해제하는 역할을 함
즉, UParticleSystemComponent를 할당해놓고 따로 해제 없이 계속 반복해 호출이 가능하다.(계속 콜하면서 사용하면 됨)
(ParticleSystem을 생성했다가 사라짐, 즉 안쓰고 컴포넌트 만들어놔도 된다는 얘기, SpawnEmitter() 얘는 컴포넌트를 만들어 줬다가 해제하는 역할)

SphereTraceMultiForObjects() : 구형으로 트레이스를 여러개 해서 오브젝트 타입을 여러개 넣어서 해당하는 오브젝트 타입들의 객체를 검출해옴
인자로 들어가는 EObjectTypeQuery형태는 enum형이어서 TEnumAsByte<EObjectTypeQuery>로 해서 값을 전달한다.(타입을 알 수 있도록)

 

 

 

 

 

C05_SweepTrigger.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "C05_SweepTrigger.generated.h"

// 여기다가 04_Explosion을 연결할 것이다.

UCLASS()
class UONLINE_03_CPP_API AC05_SweepTrigger : public AActor
{
	GENERATED_BODY()

private:
	UPROPERTY(VisibleDefaultsOnly)
		class USceneComponent* Scene;

	UPROPERTY(VisibleDefaultsOnly)
		class UBoxComponent* Box;

	UPROPERTY(VisibleDefaultsOnly)
		class UTextRenderComponent* Text;
	
public:	
	AC05_SweepTrigger();

protected:
	virtual void BeginPlay() override;

};

 

 

 

 

 

 

 

C05_SweepTrigger.cpp


더보기
#include "C05_SweepTrigger.h"
#include "Global.h"
#include "C04_Explosion.h"
#include "Components/BoxComponent.h" 
#include "Components/TextRenderComponent.h"

AC05_SweepTrigger::AC05_SweepTrigger()
{
	CHelpers::CreateComponent<USceneComponent>(this, &Scene, "Scene");
	CHelpers::CreateComponent<UBoxComponent>(this, &Box, "Box", Scene);
	CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Text", Scene);

	Box->SetRelativeScale3D(FVector(3));
	Box->bHiddenInGame = false;

	Text->SetRelativeLocation(FVector(0, 0, 100));
	Text->SetRelativeRotation(FRotator(0, 180, 0));
	Text->SetRelativeScale3D(FVector(2));
	Text->TextRenderColor = FColor::Red;
	Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;
	Text->Text = FText::FromString(GetName());
}

void AC05_SweepTrigger::BeginPlay()
{
	Super::BeginPlay();
	
	TArray<AC04_Explosion*> explosions;
	CHelpers::FindActors(GetWorld(), explosions);

	// 외부것을 연결
	OnActorBeginOverlap.AddDynamic(explosions[0], &AC04_Explosion::ActorBeginOverlap);
	
}

 

 

 

C03_Cube.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "C03_Cube.generated.h"

UCLASS()
class UONLINE_03_CPP_API AC03_Cube : public AActor
{
	GENERATED_BODY()

private:
	UPROPERTY(VisibleDefaultsOnly)
		class UStaticMeshComponent* Mesh;
	
public:	
	AC03_Cube();

protected:
	virtual void BeginPlay() override;
};

 

 

 

 

 

C03_Cube.cpp


더보기
#include "C03_Cube.h"
#include "Global.h"
#include "Components/StaticMeshComponent.h"

AC03_Cube::AC03_Cube()
{
	CHelpers::CreateComponent<UStaticMeshComponent>(this, &Mesh, "Mesh");

	UStaticMesh* mesh;
	CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/M_Cube.M_Cube'");
	Mesh->SetStaticMesh(mesh);
	Mesh->SetSimulatePhysics(true);
	// ECollisionChannel을 이용해 여러개의 오브젝트 채널을 등록
	Mesh->SetCollisionObjectType(ECollisionChannel::ECC_PhysicsBody);
}

void AC03_Cube::BeginPlay()
{
	Super::BeginPlay();
	
}

 

 

SetCollisionObjectType()으로 ObjectType이 세팅됨

 

 

C04_Explosion.h


더보기
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "C04_Explosion.generated.h"

UCLASS()
class UONLINE_03_CPP_API AC04_Explosion : public AActor
{
	GENERATED_BODY()
	
public:	
	AC04_Explosion();

private:
	UPROPERTY(VisibleDefaultsOnly)
		class UParticleSystemComponent* Particle;

protected:
	virtual void BeginPlay() override;

public:
	// 이번에는 밖에다가 함수를 세팅, 다이나믹 델리게이트여서 이름도 일치해야함
	UFUNCTION()
		void ActorBeginOverlap(AActor* OverlappedActor, AActor* OtherActor);
};

 

 

 

 

 

 

 

 

C04_Explosion.cpp


더보기
#include "C04_Explosion.h"
#include "Global.h"
#include "Particles/ParticleSystemComponent.h"

AC04_Explosion::AC04_Explosion()
{
	CHelpers::CreateComponent<UParticleSystemComponent>(this, &Particle, "Particle");

	UParticleSystem* particle;
	CHelpers::GetAsset<UParticleSystem>(&particle, "ParticleSystem'/Game/Effects/P_Explosion2.P_Explosion2'");
	// BP에서 보면 template 부분에 할당해줬다.
	// 파티클 세팅부분
	Particle->SetTemplate(particle);
	// 바로 플레이 안되도록
	Particle->bAutoActivate = false;
}

void AC04_Explosion::BeginPlay()
{
	Super::BeginPlay();
	
}

void AC04_Explosion::ActorBeginOverlap(AActor* OverlappedActor, AActor* OtherActor)
{
	// ResetParticles() : 이전에 플레이되었던 결과를 초기화 시킴
	Particle->ResetParticles();
	Particle->SetActive(true);


	/*
	bool UKismetSystemLibrary::SphereTraceMultiForObjects(UObject* WorldContextObject, const FVector Start, const FVector End, float Radius, const TArray<TEnumAsByte<EObjectTypeQuery> > & ObjectTypes, bool bTraceComplex, const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, TArray<FHitResult>& OutHits, bool bIgnoreSelf, FLinearColor TraceColor, FLinearColor TraceHitColor, float DrawTime)
	{
		static const FName SphereTraceMultiName(TEXT("SphereTraceMultiForObjects"));
		FCollisionQueryParams Params = ConfigureCollisionParams(SphereTraceMultiName, bTraceComplex, ActorsToIgnore, bIgnoreSelf, WorldContextObject);

		FCollisionObjectQueryParams ObjectParams = ConfigureCollisionObjectParams(ObjectTypes);	
		if (ObjectParams.IsValid() == false)
		{
			UE_LOG(LogBlueprintUserMessages, Warning, TEXT("Invalid object types"));
			return false;
		}

		// UWorld에 있는 추적함수를 사용하게 되면 충돌체의 모양을 정의해서 사용할 수 있으며
		// UkismetSystemLibrary에도 각 모양으로 추적할 수 있도록 함수들이 정의되어 있음
		// Make~()함수로 그릴 수 있다.(그렇게 해서 충돌체를 그려서) 공간을 찾아낼 수 있음
		UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
		bool const bHit = World ? World->SweepMultiByObjectType(OutHits, Start, End, FQuat::Identity, ObjectParams, FCollisionShape::MakeSphere(Radius), Params) : false;
	#if ENABLE_DRAW_DEBUG
		DrawDebugSphereTraceMulti(World, Start, End, Radius, DrawDebugType, bHit, OutHits, TraceColor, TraceHitColor, DrawTime);
	#endif
		return bHit;
	}
	*/
	
	FVector start = GetActorLocation();
	// end와 start가 같으면 안됨
	// 위까지 구가 찬다고 생각하면 됨
	FVector end = FVector(start.X, start.Y, start.Z + 10);

	
	// 여기 들어갈 수 있는 것은 오브젝트 타입들
	TArray<TEnumAsByte<EObjectTypeQuery>> queries;
	// EObjectTypeQuery에 죄다 숫자 밖에 없다. 프로젝트 세팅 -> 콜리전 -> preset -> NoCollision -> 오브젝트 유형에 있는 것들이 1, 2, 3, 4...
	// Cube때의 ECollisionChannel때와 같은 애를 가리키지만, 다른 방식으로 가리키는 것 뿐임 
	// ECollisionChannel는 미리 정의 되어있는 앞에 있는 것을 쓰고, 이후 추가적으로 숫자로 정의된 것을 쓰라는 것(출력할때)
	// ECollisionChannel는 채널 하나만 지정가능
	// EObjectTypeQuery는 오브젝트 타입
	// EObjectTypeQuery은 배열로 넣을 수 있도록 할려고 분할해놓은 거로 보임
	// 결국에는 같은 애
	queries.Add(EObjectTypeQuery::ObjectTypeQuery4);

	TArray<AActor*> ignoreActors;
	ignoreActors.Add(this);

	TArray<FHitResult> hitResults;

	// 얘는 object타입을 여러개 지정가능
	// Sphere말고 box나 다른형으로 쓸거면 다른걸로 쓰면됨
	// 여기선 구형 씀
	// 1. 월드 2,3 어느지점 부터 어느지점까지 찾을 거냐 4. 반지름(구의 넓이) 5. 오브젝트 타입들 6.일일히 삼각형 단위로 충돌할거냐 7.무시할 액터들,  8 드로우모드 지정 (EDrawDebugTrace::ForOneFrame(매프레임마다 그려라)),
	// 9. 리턴될 HitResult 배열(Multi여서 여러개를 리턴받음), 10 : 자기자신 제거여부 
	// 인자 5번 보면 const TArray<TEnumAsByte<EObjectTypeQuery> > & ObjectTypes
	// EObjectTypeQuery가 Enum class가 아니라서 어떤 타입인지 언리얼에서는 어떻게 다뤄야할지 몰른다.
	// 언리얼 에서는
	/*
		enum class A : uint8 -> 이런식으로 뒤에 자료형뭔지 알 수 있도록 정의되어있음
		{
		}

		이래서 그냥 enum 같은 경우는 자료형을 알 수가 없다.
		
		즉, enum class는 정의시 어떤 크기로 정의되는지 명시가 되지만, enum 자체로 사용하게 되면
		해당 enum 자료형의 크기를 알 수 없으므로 TEnumAsByte를 사용해 Byte(uint8)으로 크기를 지정해준다.
	*/

	// 하나라도 검출되면 true나옴
	if (UKismetSystemLibrary::SphereTraceMultiForObjects(GetWorld(), start, end, 200, queries, false, ignoreActors, EDrawDebugTrace::ForOneFrame, hitResults, true))
	{
		for (const FHitResult& hitResult : hitResults)
		{
			UStaticMeshComponent* meshComponent = Cast<UStaticMeshComponent>(hitResult.GetActor()->GetRootComponent());
			// null이 아니라면(!= NULL)
			if (!!meshComponent)
			{
				// 구형으로 퍼져나가는 힘을 받는 함수
				// 2 : 범위, 3 : 강도 4 : 어떻게 퍼질 거냐
				// meshComponent->GetMass()(질량) * 700.0f -> 질량 * 700
				// ERadialImpulseFalloff::RIF_Linear -> 거리에 따라 받는 힘이 줄어들도록
				meshComponent->AddRadialImpulse(GetActorLocation(), 1000, meshComponent->GetMass() * 700.0f, ERadialImpulseFalloff::RIF_Linear);
			}

		}
	}
}

 

 

 

 

결과