3일내내 개지랄떨면서 우회로 해결한 이슈이다...

 

현재 프로젝트에서 데이터테이블을 20개가량 사용중이고

게임인스턴스::생성자에서 FObjectFinder로 데이터테이블을 불러와서 사용중인 상황이다.

근데 패키징후 프로젝트를 키면 검은화면만 나오고 1~2초후 응답없음으로 진행이 되지 않는다.

이것은 Shipping빌드에서도 마찬가지였다...

 

로그찍어가면서 별별 지랄한 끝에 문제의 지점을 발견했는데

특정 데이터테이블을 FObjectFinder로 불러오는 순간 프로젝트가 멈춰버린다.

FObjectFinder로 불러올 때 성공,실패여부를 확인할 수 있지만 그런 문제가 아니라 그냥 멈춰버린다.

특정 데이터테이블의 정체는 영웅(캐릭터)의 데이터를 모아둔 테이블이였다.

 

영웅 데이터테이블의 변수중 하나는 TSubclassOf<>를 사용해서 영웅클래스를 지정하는 변수를 사용중인데

나는 c++영웅클래스가 아니라 c++에서 상속받은 BP영웅클래스를 데이터테이블에 넣어서 사용중이였다.

테스트를 포함해서 여러모로 영웅클래스는 블루프린트가 굉장히 편하기 때문에 애용중이였는데 혹시 특정 영웅의 블루프린트가 문제가 아닐까 싶어서 데이터테이블의 모든 값(행)의 TSubclassOf<>를 None으로 없애버리니까 게임이 시작된다!!

그래서 특정 영웅 블루프린트가 문제인가 싶어서 계속 디버깅해봤지만 뭔가이상하다 어쩔땐되고 어쩔땐 안되고 지 맘대로 된다. 점점 더 미궁으로 빠져들어가는데 몇시간 디버깅후 또 하나 알아냈다.

1. TSubclassOf<>가 문제인건 맞음

2. 10개행,20개행...그러니까 데이터테이블의 행 개수와 상관없이

TSubclassOf<>변수에 영웅블루프린트 클래스를 각기다른 4개이상의 클래스를 넣으면 문제가 생김.

간단하게 설명하기가 어렵다.. 10개행에 영웅_BP_A클래스로 꽉 채우면 문제가 없지만,

아무 행에 영웅_BP_A, B, C, D의 각기다른 블루프린트 클래스가 4개이상 행에 들어가 있다면 문제발생.

순서상관없고 행위치도 상관없다...

3. 영웅블루프린트가 아니라 영웅C++클래스는 또 문제없음.

4. 영웅클래스가 아니라 AActor를 상속받은 블루프린트도 문제없음.

너무 얼탱이가 없다. 안되면 그냥 안될것이지 왜 4개이상의 각기다른 클래스가 데이터테이블에 들어가면 문제가 생기고, 또 같은클래스를 넣으면 문제없고...

이 지점에서 그냥 영웅클래스를 C++로만 사용할까...도 생각했지만 BP를 사용하는것이 생산성,테스트등 훨~씬 편하기 때문에 BP를 포기할순 없었다.

 

아~혹시 블루프린트클래스가 포함된 데이터테이블이 초기화되는것보다 게임인스턴스생성자가 더 빨리 실행되서 초기화 되지 않는 데이터테이블을 불러오기 때문에 문제가 생긴것일까? 싶었다. 실제로 8개행밖에 없는 영웅데이터테이블을 에디터에서 로드하는데 약간의 딜레이가 생긴다. 임포트시 5~10초정도 걸린다. 영웅 데이터테이블만 생성자에서 불러오지 않고 게임인스턴스::Init() override 함수에서 LoadObject()로 런타임중 데이터테이블을 가져오는 시도를 해보았지만 되지 않았다.

 

이때, 번뜩인 생각이 혹시 영웅 데이터테이블이 패키징시 아예 누락된 것이 아닐까? 싶어서

프로젝트설정->패키징->Pak압축(Pak 파일 사용)->false로 설정해서 패키징된 에셋을 직접보니 역시 영웅데이터테이블만 누락되어 있었다. 미리 말씀드리면 아직도 왜 누락되었는지 모르겠다. 그래서 패키징할때 여러 방법으로 영웅데이터테이블을 강제로 패키징하는 방법을 찾아봤다.

1. 프로젝트설정->패키징->패키징할 추가 비에셋 디렉토리에 경로추가

-> 실패

2. 프로젝트설정->패키징->복사할 추가 비에셋 디렉토리에 경로추가

-> 실패

3. 프로젝트설정->패키징->쿠킹할 추가 에셋 디렉토리에 경로추가

 -> 성공했으나 너무 Raw한 모양의 데이터테이블에셋이 패키징경로에 추가되어있어서 상당히 찝찝했다.

그리고 경로를 추가하는방법이라 같은 폴더에 있는 모든 데이터테이블이 Pak압축되지 않고 파일명이 그대로 노출되기 때문에 영웅데이터테이블만 따로 다른폴더에서 관리해야하는데 여러모로 참 귀찮다. 결국 이 방법은 사용하지 않는다.

 

이렇게 테스트를 해보니 패키징용량이 왔다갔다하는것이 보였다.

Pak압축기준 약2기가정도 되는 패키징파일이 영웅데이터테이블을 불러오지 않으면 450메가정도로 확 줄어든다. 아마 영웅데이터테이블에 참조된 영웅클래스와 관련된 메쉬, 머터리얼등의 에셋이 빠져서 그런듯하다.

이때 과거 패키징관련 글을 읽은 기억이 났다.

 

패키징은 프로젝트에 참조된 에셋만 패키징이 되고, 프로젝트에 에셋이 있더라도 어디서도 참조형식으로 사용되지 않으면 패키징에 추가되지 않는다.

 

이 생각이 나면서 혹시 c++코드로만 참조하고 있던 데이터테이블을 블루프린트에 변수로 넣으면 더 확실하게 참조(진짜 더 확실한건진 모름)가 되고, 그러면 어떤식으로든 패키징시 데이터테이블이 패키징에 누락되지 않고 제대로 되지 않을까?라는 생각이 들면서 곧바로 시도했다.

게임인스턴스C++에

UPROPERTY(EditAnywhere)

UDataTable* _hero_datatable = nullptr;

이런식으로 변수를 하나 만들고 게임인스턴스BP에서 _hero_datatable에 영웅 데이터테이블을 넣고 패키징을 해봤다.

 

드디어... 패키징에 영웅 데이터테이블이 누락되지않고 잘 들어가있었다. 감격의 순간이었다.

사소한 단점은 이상하게 생성자에서는 여전히 불러오진 못한다...

분명 Pak압축을 하지 않은 패키징파일에서 분명! 영웅 데이터테이블에셋이 있는걸 두눈으로 봤는데 생성자에서 불러오진 못한다. 그래서 _hero_datatable변수를 직접 사용하는데 아직까지 문제는 없다.

#if WITH_EDITOR 를 이용해서 에디터에서는 생성자에서 불러오고, 에디터가 아니라면 _hero_datatable을 사용하는 방법을 채택했다. 

 

아직도 왜 패키징에서 누락되는지 모르겠다. 열심히 구글링해봤지만 동일한 문제를 겪는 글은 찾지못했고 비슷한 글을봐도 유의미한 내용은 없었다. 

 

혹시 같은 문제를 겪는다면 이런방법으로 우회로 해결하는 방법으로 도움이 되셨으면 좋겠다.

 

------------------------------------------------------------------------------------------------------------------------------------------------

TSubclassOf<>를 TSoftClassPtr<>로 변경하니까 잘된다...

결국 우회해서 해결한 방법은 없애고 TSoftClassPtr<>를 사용해서 정상?적으로 데이터테이블을 불러오고있다

 

암만 구글링해도 TSoftClassPtr<>와 패키징관련한 글이 안보여서 정확한원인은 찾지못한 상태...

구글링해도 안나와서 UUserWidget클래스를 참고했다

 

블루프린트에서 제목에 있는 함수들을 사용하려면 함수탭에서 오버라이드를 눌러서 만들면 된다.

이걸 C++로 옮기고 싶어서 시도해봤는데 OnTouch~()의 함수원형은 이렇게 생겼다

/**
	 * Called when a touchpad touch is ended (finger lifted)
	 * 
	 * @param MyGeometry    The geometry of the widget receiving the event.
	 * @param InTouchEvent	The touch event generated
	 */
UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, Category="Touch Input")
FEventReply OnTouchEnded(FGeometry MyGeometry, const FPointerEvent& InTouchEvent);

보아하니 BluprintImplementableEvent로 인해 블루프린트에서 오버라이드를 할 수는 있지만

가상함수(virtual)가 아니라서 c++에서는 오버라이드가 불가능하다.

 

쫌~더 알아보니까 이런함수가 있더라

virtual FReply NativeOnTouchEnded( const FGeometry& InGeometry, const FPointerEvent& InGestureEvent );
FReply UUserWidget::NativeOnTouchEnded( const FGeometry& InGeometry, const FPointerEvent& InGestureEvent )
{
	return OnTouchEnded( InGeometry, InGestureEvent ).NativeReply;
}

같은 OnTouchEnded지만 다른점은 함수명앞에 Native가 붙고, 리턴값이 조금다르고 내가 원하던 가상함수다.

 

고대로 이 함수를 오버라이드를 해보니 잘 작동된다.

오버라이드 결과물

FReply OnTouchMoved( const FGeometry& InGeometry, const FPointerEvent& InGestureEvent ) override;
FReply UMy_UI::OnTouchMoved( const FGeometry& InGeometry, const FPointerEvent& InGestureEvent )
{
	Super::OnTouchMoved(InGeometry, InGestureEvent);
    
    //FReply::Unhandled();
    return FReply::Handled();
}

 

구조체를 통채로 세이브하고 불러왔더니

특정값이 계속 비어있길래 확인해보았다.

 

비어있는 멤버변수만 UPROPERTY()매크로가 없었는데

매크로를 적으니까 세이브가 제대로 되었다.

구조체를 통채로 세이브후 해당 구조체 변경되었다면

로드할 때 값이 제대로 들어가는지 확인해봤는데 잘 된다. 

 

세이브로드의 값은 개발중, 라이브중에서도 계속 변경될 여지가 있으므로

틀을 잘 잡아놔야한다.

1. 데이터테이블 양식에 맞게 구조체를 생성

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "UObject/NoExportTypes.h"
#include "HD_SharedStruct.generated.h"

/**
 * 
 */
UCLASS()
class HDCLIENT_API UHD_SharedStruct : public UObject
{
	GENERATED_BODY()
	
};

USTRUCT(BlueprintType)
struct FDataWave : public FTableRowBase
{
	GENERATED_BODY()

protected:
	UPROPERTY(EditAnywhere)
		uint8 _stage = 0;
	UPROPERTY(EditAnywhere)
		uint8 _wave = 0;
};

#include "Engine/DataTable.h" 

구조체는 FTableRowBase로부터 상속받았다

 

나는 웨이브와 관련된 데이터테이블을 만들기 위해 이렇게 구조체를 간단하게 짜놨다

 

2. 에디터에서 데이터테이블 생성

우클릭을해서 데이터 테이블을 선택한다

C++에서 만든 구조체를 선택한다

잘 생성된 모습을 확인할 수 있다

 

3. C++에서 생성된 데이터테이블 불러오기

헤더

UCLASS()
class HDCLIENT_API UHD_GI : public UGameInstance
{
	GENERATED_BODY()
	
protected:
	UHD_GI();

private:
	UDataTable* _dt_wave = nullptr;
};

.cpp

UHD_GI::UHD_GI()
{
	static ConstructorHelpers::FObjectFinder<UDataTable> DT_GAME(TEXT("/Game/_HDClient/ReadOnly/Data/HDDT_Wave.HDDT_Wave"));
	if (DT_GAME.Succeeded()) { _dt_wave = DT_GAME.Object; }
}

 

나는 게임인스턴스 생성자에서 불러왔지만

게임모드에서 불러와도 된다.

->게임모드에서 불러와도 되지만 데이터는 레벨이 이동해도 그대로 남아 있어야 하기 때문에 레벨이 이동되면 변경되는 게임모드보다는 게임인스턴스가 더 올바른? 선택이라고 본다

 

-----------------------------------------------------------------------------------------------------------------------------------

1. csv나 json으로 익스포트, 임포트 하는법

만들어진 데이터테이블을 우클릭하면 csv, json으로 익스포트할 수 있음

csv,json파일을 수정해서 데이터테이블에 다시 적용시키기 위해 리임포트로 변경된 데이터를 업데이트

csv, json파일로 새로운 데이터테이블을 생성하려면...

우선 추가하려는 데이터의 구조에 맞게 구조체를 먼저 생성해주고 밑에 스샷처럼 추가하면 된다

 

+ Recent posts