스택을 한번 보면 

 

근데 여기에 Scene을 AttachMent할때가 궁금하긴하네 

 

 

문제의 원인 InteractionWidget->SetupAttachment(RootComponent);

 

InteractionWidget->SetupAttachment(Scene);고쳐줬음 

RootComp 이 생성되지 않은 상황에서 걸린건지..

 


원래 아이템을 먹을 때 IMC에 IA를 하나 설정할까 하다가 

에디터에서 Input Mapping을 설정할 수 있다는 사실을 듣게 되고 실습해보게 되었습니다. 


Unreal Editor 상단 메뉴에서 Edit > Project Settings 를 선택합니다.
좌측 메뉴에서 Input 항목 선택 좌측 패널에서 Input을 클릭합니다.

(Action Mappings 추가)
Input 섹션 내에 Bindings 항목이 보입니다.
Action Mappings 부분에서 우측 하단의 + 버튼을 클릭하여 새 액션 매핑을 추가합니다.
새 액션 매핑의 이름을 "Interact" 로 입력합니다. 키 바인딩 설정을 하기 위해
방금 생성한 "Interact" 액션의 드롭다운을 펼치고, 키를 E로 지정합니다.

 

 

코드 설명 :

void AWavePassItem::ActivateItem(AActor* Activator)
{
    GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, FString::Printf(TEXT("When Collect WavePassItem")));
    if (Activator && Activator->ActorHasTag("Player"))
    {
        if (InteractionWidget)
        {
            InteractionWidget->SetVisibility(true);
        }
        APlayerController* PC = UGameplayStatics::GetPlayerController(this, 0);
        if (PC)
        {
            EnableInput(PC);
            if (InputComponent)
            {
                InputComponent->BindAction("Interact", IE_Pressed, this, &AWavePassItem::OnInteractPressed);
            }
        } 
    }
}

여기에서 플레이어 컨트롤러를 불러와서 EnableInput(PC) 를 설정해, 플레이어 컨트롤러에서 인풋을 받을 수 있게 합니다.

후에 InputComponent가 존재한다면 

여기에서 EnableInput과 InputComponent는 

EnableInput() 함수는 액터(Actor) 클래스에 정의되어 있어, 해당 액터가 특정 플레이어 컨트롤러(PC)를 통해 입력을 받을 수 있도록 활성화해줍니다.
그리고 InputComponent는 입력을 처리하기 위한 컴포넌트로, 액터나 컴포넌트에 추가되어 입력 이벤트를 바인딩할 때 사용됩니다.

따라서 BindAction을 하여 상호작용을 할 수 있게 설정해줍니다.

 

TODO : 기존의 IMC에서 IA를 매핑하는 방식이랑은 뭐가다른거지?

성능면에서 한번 알아보자

 

 

 


 

계층구조 : 

다음과 같이 Interface를 상속받은 BaseItem을 만들고 BaseItem에서 Particle을 기본적으로 제거해주고

그를 상속받는 각자의 아이템을 만들게 되었다.

다음은 DestoryItem의 함수입니다.

 

 

if (Particle)
{
    FTimerHandle DestroyParticleTimerHandle;
    GetWorld()->GetTimerManager().SetTimer(
        DestroyParticleTimerHandle,
        [Particle]()
        {
            Particle->DestroyComponent();
        },
        0.1f,
        false
    );
}

 

그렇게하고 GameState에서 Wave 및 Level 시스템을 구현하였는데, 

하나의 레벨 내에서 3개의 Wave를 관리하는 구조였다.

즉 다시말해 , Level이 전환되기 이전에 3개의 Wave를 관리해야하므로 

내가 소환한 액터를 관리하는 로직이 필요해서 다음과 같이 코드를 작성하였다.

 

void AMyGameState::NextWave()
{
	if (WaveCount >= 3)
	{
		WaveCount++;
		EndLevel();
	}
	else {
		if (!SpawnedItems.IsEmpty())
		{
			for (AActor* SpawnedItem : SpawnedItems)
			{
				if (IsValid(SpawnedItem))
				{
					SpawnedItem->Destroy();
				}
			}
			SpawnedItems.Reset();
		}
		WaveCount++;
		SpawnRandom();
		
	}
}

 

여기에서 오류가 발생하였는데, 

 

람다 함수 내부를 보면 만일 Particle이 올바르지 않다면 Crash가 생기게 된다.

잠시 실습한 게임의 로직을 살펴보면, Wave가 변경되면 아이템들이 재 생성된다.

그 사이에 폭탄이 숨어져있는데 폭탄은 5초후에 터지게 되며 Destroy()가 된다

 

그 찰나의 순간에 PassItem을 활용해 다음 Wave로 넘어간다면 

Particle이 올바른 값을 가르키지 않아 Crush가 생긴다.

 

따라서 Particle이 제거되었다면 Particle이 nullptr을 가르키게 해야한다.

만일 nullptr을 가르키지 않는다면 , 존재하지 않는 메모리를 가르켜 오류가 발생한다.

해결 방법으로 WeakObjectPtr을 튜터님에게 배웠다.

WeackObjectPtr을 사용한다면 더이상 사용하지 않는다면 nullptr을 가르킨다는 것을

튜터님에게 배워 적용을 해봤다.

  TWeakObjectPtr<UParticleSystemComponent> ParticleWeak;

    if (PickupParticle)
    {
       ...
        ParticleWeak = Particle;
    }
    if (ParticleWeak.IsValid())
    {
        FTimerHandle DestroyParticleTimerHandle;
        GetWorld()->GetTimerManager().SetTimer(
            DestroyParticleTimerHandle,
            [ParticleWeak]()
            {
               //2중 안전 체크 
                if (ParticleWeak.IsValid())
                {
                    ParticleWeak.Get()->DestroyComponent();
                }
            },
            0.1f,
            false
        );
    }

 

 

추가)) 여기서 만일 SpawnItem->Destroy()가 없다면

문제는 Timer에서 InvalidType에서 중단점이 찍히게 되는데,

해당 오류는 Timer에서 내부의 함수를 찾을 수 없게 되어 일어나는 오류이다.

 

추가 )) 뿐만아니라 지금 Particle을 제거하는 로직은 BaseItem과 MineItem 둘다 달려있기 때문에

두 부분 전부 WeakPtr로 변경을 해주었더니 더이상 중단점이 걸리지 않았다.

만일 DestroyItem을 설정하지 않는다면 다음과 같은 코드에서 중단점이 걸리게 될것이다.

case FTimerDelegateVariant::IndexOfType<FTimerFunction>():
		{
			if (const FTimerFunction& TimerFunction = VariantDelegate.Get<FTimerFunction>())
			{
				QUICK_SCOPE_CYCLE_COUNTER(STAT_FTimerUnifiedDelegate_Execute);
				TimerFunction();
			}
			break;
		}

 

TODO :  이 부분 설계의 의도를 한번 여쭤보자


궁금증 해결 ) 왜 게임 인스턴스에서 타이머를 사용하면 안좋을까?

궁금증 해결 ) 레벨이 변환되면 캐릭터도 초기화가 될까?

 

가장 먼저 Item을 습득하게되면 Activate가 일어나, Chracter에게 Duration과 SpeedBuff를 적용한다.

근데 Duration을 캐릭터 내부적으로 관리하였더니 

레벨이 변경될 때마다 초기화가 일어나는 것을 목격하였다.

 

레벨이 변경되면 State,Pawn,Item.. 등은 초기화가 된다.

// Todo : 갑자기 쓰다보니깐 또 궁금해지네 World도 초기화되는걸로 본것같은데 사실체크 한번 하자 

그래서 Pawn에서 관리하는 Duration은 초기화가 되어 캐릭터의 BuffDuration을 확인할 수 없게 되었다.

그래서 Duration을 GameInstance에 올려서 관리하려 했는데, 그 과정에서 타이머를 사용하면 안된다는 이야기를

Gpt를 통해 알게 되었다. 그 이유를 튜터님에게 여쭤봐 해결할 수 있었다.

 

가장 먼저 게임 인스턴스는 게임이 끝날 때까지 유지가 되는 클래스이다.

이런 게임 인스턴스에서 타이머를 실행하게 된다면

레벨 및 월드가 변환될 때 잠재적 버그를 일으킬 요소가 된다.

따라서 타이머는 GameInstace에서 사용하지 말자 !

 

원래는 BuffDuration을 GameInstace에 올려 타이머로 관리하려하였으나

생각을 바꿔 Pawn 클래스에서 타이머를 관리해주었다.


오류3 ) WidgetSpace 설정을 Screen이 아닌 World로 할 경우 생기는 오류 

 

//.h
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI")
	UWidgetComponent* WidgetComp;

//.cpp
WidgetComp->SetWidgetSpace(EWidgetSpace::Screen);

 

블루 프린트에 올려서 디폴트를 바꾸는 방법 : 

월드 설정 : 

 

안보이는것 같지만 한쪽면에만 다음과 같이 Press E key가 생성된것을 볼 수 있습니다.

 

스크린 설정 : 

 

스크린으로 설정해두면 해당 Text는 카메라를 항상 쳐다보게 됩니다.

 

 

Screen이 아닌 World로 머티리얼 설정을 양면설정을 해주더라도 ,

Text가 앞면과 뒷면에서 뒤집어져 보일수있다. 예를들어서 양면 투명종이에 

2를 쓰면 앞에서는 2지만 뒤에서는 s같이 보인입니다.


궁금증)

요새 자주 드는 생각이 객체 지향적인 관점에서 생각을 하려 노력하게 되는것 같습니다.

예를들어서 Item에서 Activate를 하면 Character의 TakeDamage를 호출한다고 생각을 해보면

다시말해 ApplyDamage의 로직을 적용한다고 생각해보겠습니다.

그러면 Cpp에서 Character.h의 헤더파일을 선언하지 않고도 ApplyDamage를 사용할 수 있게 됩니다.

 

물론 GameplayStatics의 라이브러리를 추가하여 GameplayStatics 에서 Actor와 접점이 있을 수 있지만

ApplyDamage를 사용하면 Actor끼리 모르는건지 (값에 의해 전달되는 구조인건지)

아니면 인자를 통한 호출인건지 모르겠습니다.

 

HealingItem도 Character->AddHealth()를 통해 전달된다고 생각해보면

 

void HealingItem :: Activate가 Character->AddHealth(amount) 를 호출하게 됩니다.

근데 만일 Activate가 되었을 경우 return을 통해 

 

아..갑자기  확실히 이해했습니다. 언리얼엔진에서의 Main이 뭔지 모르겠지만,

Main이 아닌이상은 둘중에 하나의 Cpp 내의 메서드에서 서로는 필연적으로 하나는 포함될 수 밖에 없네요 

 

Item과 Character는 캐릭터 자체를 인자로 받기보다는 캐릭터 내부의 메서드만 호출하면 되는것 같습니다.

TIL을 통해 배운 내용을 정리하다보면 2배로 학습의 효율이 증가하는 것같네요 

 

 


 


오류 4 ) 다음과 같이 화면이 깨지는 현상이 발견되었다

NE

//앵커 : 해상도 맞춰주고 이쁘게해줌 ,SafeZone

+ Recent posts