언리얼 5

230911 싱글톤 / Trace / 사운드 / 수류탄 투사체 제작

슬뷔 2023. 9. 11. 10:14

GameInstance

-> 엔진이 켜질 때 딱 하나만 존재하는 프로그램 종료까지 유지되는 객체

 

교체하고 싶다면?

편집-> 프로젝트 셋팅 -> 맵 & 모드 -> 게임 인스턴스 클래스 지정

 

내가 만든 것으로 지정하고 싶다면?

c++ 클래스 -> 새 c++ 클래스 -> 모든 클래스 -> gameinstance 검색 -> gameinstance 을 상속받아서 자체적인 gameinstance 클래스 제작 (GameInstance_Base)

 

싱글톤 방식 교체해서 GameInstance_Base 구현

포인터 타입이 아닌 단순 멤버로 둔다 -> 객체가 생성되지 않음 -> 가져오는 함수 제작

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Effect/EffectMgr.h"

#include "GameInstance_Base.generated.h"

UCLASS()
class UNREAL_3TH_API UGameInstance_Base : public UGameInstance
{
	GENERATED_BODY()
private:
	UEffectMgr effectmgr;

public:
	UEffectMgr* GetEffectMgr() { return &effectmgr; }

public:
	UGameInstance_Base();
	~UGameInstance_Base();
};

EffectMgr 자체 C++ 클래스로 제작 ( UObject 상속 x ) => 문제 발생

#include "../Header/global.h"

#include "CoreMinimal.h"

class UEffectMgr 
{	
public:
	void CreateEffect(UWorld* _World, EEFFECT_TYPE _Type, ULevel* _Level, FVector _Location);	
};

Fire 테스트

void ACharacter_Base::Fire(const FInputActionInstance& _Instance)
{	
	bool bToggle = _Instance.GetValue().Get<bool>();

	//LOG(Player, Error, TEXT("!! Fire !!"));

	if (!IsValid(GetMesh()->GetAnimInstance()) || FireMontage.IsNull())
	{		
		return;
	}
	
	if (bToggle && !IsReload)
	{		
		GetMesh()->GetAnimInstance()->Montage_Play(FireMontage.LoadSynchronous(), 2.f);
		GetMesh()->GetAnimInstance()->Montage_JumpToSection(FName("Fire"), FireMontage.LoadSynchronous());
		IsFire = true;

		// Explode Effect 재생해보기
        	// 이전 방식
		//UEffectMgr::GetInst(GetWorld())->CreateEffect(EEFFECT_TYPE::EXPLODE, GetLevel(), GetActorLocation() + GetActorForwardVector() * 100);
		
        	// 새로운 방식
        	UGameInstance_Base* pGameInst = Cast<UGameInstance_Base>(UGameplayStatics::GetGameInstance(GetWorld()));
		pGameInst->GetEffectMgr()->CreateEffect(GetWorld(), EEFFECT_TYPE::EXPLODE, GetLevel(), GetActorLocation() + GetActorForwardVector() * 100);
	}
	else
	{		
		IsFire = false;
	}
}

MakeShareable 이용해서 싱글톤? 구현 -> 이걸로 싱글톤 끝

EffectMgr Uobject 상속받았었는데, 바꿈

정적멤버로 자기 자신을 가리키는 것을 생성 ( 정적멤버는 데이터 영역 )

#include "../Header/global.h"

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "EffectMgr.generated.h"


UCLASS()
class UNREAL_3TH_API UEffectMgr : public UObject
{
	GENERATED_BODY()
	
private:
	// 정적멤버는 데이터 영역
    	// 정적멤버로 자기 자신을 가리키는 것을 생성
	static TSharedPtr<UEffectMgr>	m_This;

public:	
	static UEffectMgr* GetInst();	

	UFUNCTION(BlueprintCallable)
	void CreateEffect(UWorld* _World, EEFFECT_TYPE _Type, ULevel* _Level, FVector _Location);	
};

* UObject로 파생된 애들은 ptr을 쓰면 안된다

가비지컬렉터에 의해 관리가 되다가 

sharedptr 로 ..

 

* 스마트 포인터 공부하기 

https://koreanfoodie.me/922

 

언리얼 스마트 포인터(Unreal Smart Pointer) 정리 1 : 종류, 이점과 예제

스마트 포인터 3대장 C++ 에는 대표적인 스마트 포인터 3 가지가 있는데, 언리얼에서는 이를 T로 시작하는 라이브러리로 제공한다. TUniquePtr TSharedPtr TWeakPointer 기본적인 원리와 기능은 C++ 와 비슷

koreanfoodie.me

#include "EffectMgr.h"

#include "../Unreal_3thGameModeBase.h"

#include "Effect_Base.h"

// 정적멤버는 항상 초기화를 해주어야함
TSharedPtr<UEffectMgr> UEffectMgr::m_This = nullptr;

UEffectMgr* UEffectMgr::GetInst()
{
	if (!IsValid(m_This.Get()))
	{
		m_This = MakeShareable(NewObject<UEffectMgr>());
	}

	return m_This.Get();
}


void UEffectMgr::CreateEffect(UWorld* _World, EEFFECT_TYPE _Type, ULevel* _Level, FVector _Location)
{
	FActorSpawnParameters param = {};
	param.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	param.OverrideLevel = _Level;
	param.bDeferConstruction = true;	// 지연생성(BeginPlay 호출 X)
		
	FTransform trans;
	trans.SetLocation(_Location);

	AEffect_Base* pEffect = _World->SpawnActor<AEffect_Base>(AEffect_Base::StaticClass(), trans, param);
	pEffect->SetEffectType(_Type);
	pEffect->FinishSpawning(pEffect->GetTransform());
}
void ACharacter_Base::Fire(const FInputActionInstance& _Instance)
{	
	bool bToggle = _Instance.GetValue().Get<bool>();

	//LOG(Player, Error, TEXT("!! Fire !!"));

	if (!IsValid(GetMesh()->GetAnimInstance()) || FireMontage.IsNull())
	{		
		return;
	}
	
	if (bToggle && !IsReload)
	{		
		GetMesh()->GetAnimInstance()->Montage_Play(FireMontage.LoadSynchronous(), 2.f);
		GetMesh()->GetAnimInstance()->Montage_JumpToSection(FName("Fire"), FireMontage.LoadSynchronous());
		IsFire = true;

		// Explode Effect 재생해보기
        	// 이전 방식 ( 월드에 붙였다 뗐다 )
		//UEffectMgr::GetInst(GetWorld())->CreateEffect(EEFFECT_TYPE::EXPLODE, GetLevel(), GetActorLocation() + GetActorForwardVector() * 100);
		
        	// 새로운 방식
        	UGameInstance_Base* pGameInst = Cast<UGameInstance_Base>(UGameplayStatics::GetGameInstance(GetWorld()));
		pGameInst->GetEffectMgr()->CreateEffect(GetWorld(), EEFFECT_TYPE::EXPLODE, GetLevel(), GetActorLocation() + GetActorForwardVector() * 100);
		
        	// 새로운 방식 2
            	UEffectMgr::GetInst()->
    }
	else
	{		
		IsFire = false;
	}
}

충돌

1. 오브젝트 기반의 충돌 (이전에 제작)

Object Channel 

스폰된 객체가 어느 그룹에 속하는지 정해주고 충돌 프로파일 (프리셋, 미리 작성해둠) 제작

 

2. 추적 채널 ( Trace 기반 ) 충돌

편집 -> 프로젝트 셋팅 -> 콜리전 -> 트레이스 채널 , 프리샛 -> visibility( 가시성 ), camera 이미 엔진 내부에서 만들어져 사용 중

 

기존 프로파일은 상대방 오브젝ㅌ트 어디에 속해있는지 따라 서로 어떻게 반응할지 미리 정의를 해주어야 했다. (항상 발동)

트레이스는 주변을 탐색할 때 사용한다 (한번 발동)

ex) 

visibility, camera 에 블록 check 가 되어 있다. => 사용 한다는 의미

광선을 발사하면서 camera 채널을 붙여 트래이스를 하면 내가 쏘는 광선방향으로 누가 있으면? 걸린다?

소바 e 스킬느낌.. 아니면 사이퍼 덫 느낌? 

 

 

언제 사용 중인지 ?

1. visibility 채널

-> 랜더링 할 때 많이 활용

앞에 있는게 랜더링되면 뒤에 있는게 가려져서 안보이니까 랜더링할 필요가 없는걸 판단

 

2. camera 채널

-> 벽에 가로막혀 있을 때? 벽이 카메라 채널에 반응하게 되어있다면 카메라의 위치를 당긴다

-> 무시로 되어있다면, 물체의 매쉬를 통과한다

자체적인 Trace 채널 만들기

편집 -> 프로젝트 셋팅 -> 콜리전 -> 트레이스 채널 -> 새 트레이스 채널 -> 이름 : PlayerAttack, 기본 반응 : Ignore -> 몬스터만 해당 trace에 반응하게 블록 check

 

ex ) 특정 순간에만 반응하게 or 어느 순간에만 반응하게 ( animnotify 에서 공격시작, 끝을 bool 값 줘서 생성 )

 

Input -> 입력액션 -> IA_QSkill 

IMC_Default -> IA_QSkill -> 키 = Q, 트리거 = 눌림

DA_PlayerSetting -> ATTACK 에 IA_QSkill 맵핑

 

* config -> DefaultEngine.ini 파일

쭉 내리다 보면 자동으로 PlayerAttack 은 ECC_GameTraceChannel5 라고 지정 해둠 ( 자동으로 부여된 enum 값 )

 

구 형태로 채널 검사

FColor color;

if (bCollision)
	color = FColor::Red;

else
	color = FColor::Green;

// 위의 if 문과 동일 
bCollision ? color = FColor::Red : color = FColor::Green;
void ACharacter_Base::Attack(const FInputActionInstance& _Instance)
{
	LOG(Player, Warning, TEXT("Q Skill"));
    
	// 범위 안에 들어온 애들이 여러 명일 수 있다 -> 배열로 받아 둔다
	TArray<FHitResult> ResultArray;
	FCollisionQueryParams query(NAME_None, false, this);
    
    	// 시작과 끝 값을 GetActorLocation() 로 동일하게 한다.
	// FQuat::Identity -> 무회전, 구는 회전해도 똑같아서 무회전 사용
	bool bCollision = GetWorld()->SweepMultiByChannel(ResultArray
						, GetActorLocation(), GetActorLocation()
						, FQuat::Identity
						, ECC_GameTraceChannel5 // PlayerAttack Trace Channel
						, FCollisionShape::MakeSphere(200.f)
						, query);
    
	FColor color;
	bCollision ? color = FColor::Red : color = FColor::Green;
    	// DrawDebugSphere -> 임시로 구를 그리는 함수
	DrawDebugSphere(GetWorld(), GetActorLocation(), 200.f, 40, color, false, 2.f);


	for (int32 i = 0; i < ResultArray.Num(); ++i)
	{
		// Explode Effect 재생해보기		
		UEffectMgr::GetInst()->CreateEffect(GetWorld(), EEFFECT_TYPE::EMP, GetLevel(), ResultArray[i].Location);
	}

}

* HitResult에 Location과 ImpactPoint가 있고 약간의 차이가 있다.

Location은 충돌 대상과 trace shape가 접촉했을 때 trace shape의 원점 월드 좌표이다.

ImpactPoint는 충돌 대상과 trace shape가 접촉하는 월드 좌표이다.

https://docs.unrealengine.com/5.1/ko/traces-in-unreal-engine---overview/

 

트레이스 개요

언리얼 엔진 4 의 트레이스 시스템 개요입니다.

docs.unrealengine.com

 

수류탄 투사체 제작

Projectile 을 상속받은 Granade.h / Granade.cpp 생성

#include "CoreMinimal.h"
#include "Projectile.h"
#include "Granade.generated.h"

UCLASS()
class UNREAL_3TH_API AGranade : public AProjectile
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Info")
	TSoftObjectPtr<USoundBase>	m_Sound;


	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Info")
	float				m_LifeTime;

private:
	float		m_AccTime;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

public:
	AGranade();
	~AGranade();
};
#include "Granade.h"
#include "../Effect/EffectMgr.h"

AGranade::AGranade()
	: m_LifeTime(1.f)
	, m_AccTime(0.f)
{
}

AGranade::~AGranade()
{
}


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

void AGranade::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	m_AccTime += DeltaTime;

	if (m_LifeTime < m_AccTime)
	{
		// 이펙트 재생
		CEffectMgr::GetInst()->CreateEffect(GetWorld(), EEFFECT_TYPE::EXPLODE, GetLevel(), GetActorLocation());

		// 사운드 재생
		if (!m_Sound.IsNull())
		{
			UGameplayStatics::PlaySoundAtLocation(GetWorld(), m_Sound.LoadSynchronous(), GetActorLocation());
		}
		
		// 객체 삭제
		Destroy();
	}
}

투사체 상속받아서 쏘면 툭툭 떨어지는 수류탄 형태의 투사체를 생성

주변 원형으로 sweap? 체크해서 데미지 받은 몬스터에게 물리적인 힘을 줘서 밀어내면서 폭발시키는? 거 생성

BPC_Granade

class UNREAL_3TH_API ACharacter_Base : public ACharacter
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
	TSubclassOf<AProjectile>				m_Granade;
}
void ACharacter_Base::Attack(const FInputActionInstance& _Instance)
{
	// 수류탄 쏘기
	if (IsValid(m_Granade))
	{
		FActorSpawnParameters param = {};
		param.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		param.OverrideLevel = GetLevel();
		param.bDeferConstruction = false;	// 지연생성(BeginPlay 호출 X)

		// 카메라 위치 + 카메라 전방방향 * 10미터
		FVector CamForwardPos = m_Cam->GetComponentLocation() + m_Cam->GetForwardVector() * 1000;

		// 투사체 생성위치	
		FVector ProjectileLocation = GetMesh()->GetSocketLocation(FName(TEXT("Light02")));

		// 투사체 위치에서 카메라 전방 10미터 위치를 향하는 방향벡터 구하기
		FVector vDir = CamForwardPos - ProjectileLocation;
		vDir.Normalize();

		ProjectileLocation += vDir * 50.f;

		AProjectile* pProjectile = GetWorld()->SpawnActor<AProjectile>(m_Granade, ProjectileLocation, FRotator(), param);
		pProjectile->m_ProjtileMovement->Velocity = vDir * 500.f;
	}
}

예제 영상

 

사운드

사운드에 공간감을 주려면 ? (현재는 거리가 멀어져도 똑같은 크기로 들림)

순수 사운드 리소스 타입 => 사운드 웨이브

클래스화 시켰을 때는 SoundBase 로 가리키게 되어있다.

 

블루프린트

사운드->사운드큐 

SQ_Explosion
SA_Default

어테뉴에이션 -> 감쇠

사운드->사운드 어테뉴에이션

내부반경 -> 소리 최대치가 들릴 수 있는 거리

 

정해진 거리까지 감쇠해서 들리다가 그 거리를 벗어나면 아예 안들리게 된다.

 

* .USoundCue 사용하려면 #include "Sound/SoundCue.h" 참조해야한다.

#include "Sound/SoundCue.h"

class UNREAL_3TH_API AGranade : public AProjectile
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Info")
	TSoftObjectPtr<USoundCue>	m_Sound;
}

 

 

예제 영상

 

사운드 큐를 따로 제작하지 않고 c++ 에서 조합하는 것도 가능하다.

UGameplayStatics::PlaySoundAtLocation() 괄호 안에서 표현 가능

 

언리얼에서 제공하는 "앰비언트 사운드"

actor 상속받아서 sound 제작할 수 있는 sound component 들고 있다