반응형


먼저 주의할 점은 언리얼 구조체를 만들때 구조체 이름 앞에 F를 붙여야 한다는 것에 주의!!

https://wiki.unrealengine.com/Structs,_USTRUCTS(),_They're_Awesome

Overview

Original Author: Rama (talk)

In C++, structs differ from classes only in that properties and methods defined prior to any accessibility keyword (public:protected:,private:) default to public: while in classes these default to private:. Structs are more or less artifacts of the transition from C to C++. In C, object oriented programming did not exist yet, thus structs were only used to organize data and pass that data to functions, for example, but lacked the support for methods. In C++, with the introduction of classes, this purpose became more or less deprecated. Personally I thus find it a good practice Epic is enforcing here.

Structs enable you to create custom variable types to organize your data, by relating other C++ or UE4 C++ data types to each other. The power of structs is extreme organization as well as the ability to have functions for internal data type operations. '

In UE4, structs should be used for simple data type combining and data management purposes. For complex interactions with the game world, you should make a UObject or AActor subclass instead.

Core Syntax

NOTE: Depending on your version of UE4, you'll need to replace GENERATED_BODY() with GENERATED_USTRUCT_BODY(). At least as of version 4.11, using GENERATED_USTRUCT_BODY() will result in a UHT error stating GENERATED_BODY() is expected.

//If you want this to appear in BP, make sure to use this instead
//USTRUCT(BlueprintType)
USTRUCT()
struct FJoyStruct
{
	GENERATED_BODY()
 
	//Always make USTRUCT variables into UPROPERTY()
	//    any non-UPROPERTY() struct vars are not replicated
 
	// So to simplify your life for later debugging, always use UPROPERTY()
	UPROPERTY()
	int32 SampleInt32;
 
	UPROPERTY()
	AActor* TargetActor;
 
	//Set
	void SetInt(const int32 NewValue)
	{
		SampleInt32 = NewValue;
	}
 
	//Get
	AActor* GetActor()
	{
		return TargetActor;
	}
 
	//Check
	bool ActorIsValid() const
	{
		if(!TargetActor) return false;
		return TargetActor->IsValidLowLevel();
	}
 
	//Constructor
	FJoyStruct()
	{
		//Always initialize your USTRUCT variables!
		//   exception is if you know the variable type has its own default constructor
		SampleInt32 	= 5;
		TargetActor = NULL;
	}
};


Additional Note Author: DesertEagle_PWN (talk)
Additional Note: The idea of USTRUCTS() is to declare engine data types that are in global scope and can be accessed by other classes/structs/blueprints. Because of this, it is invalid UE4 syntax to declare a struct inside of a class or other struct if using the USTRUCT() macro. Regular structs can still be utilized inside your classes and other structs; however these cannot be replicated natively and will not be available for UE4 reflective debugging or other engine systems such as Blueprints.

Examples

Example 1

You want to relate a float brightness value with a world space location FVector, both of which are interpolated using an Alpha value.

  • And you want to do this for 100 different game locations simultaneously.
  • And you want to do this process repeatedly over time!
  • You need to store the incremental interpolation values between game events.
  • AActors/UObjects are not involved (You could just subclass AActor/UObject and store the data per instance)


USTRUCT()
struct FMyInterpStruct
{
	GENERATED_BODY()
 
	UPROPERTY()
	float Brightness;
 
	UPROPERTY()
	float BrightnessGoal; //interping to
 
	UPROPERTY()
	FVector Location;
 
	UPROPERTY()
	FVector LocationGoal;
 
	UPROPERTY()
	float Alpha;
 
	void InterpInternal()
	{
		Location = FMath::Lerp<FVector>(Location,LocationGoal,Alpha);
		Brightness = FMath::Lerp<float>(Brightness,BrightnessGoal,Alpha);
	}
 
	//Brightness out is returned, FVector is returned by reference
	float Interp(const float& NewAlpha, FVector& Out)
	{
		//value received from rest of your game engine
		Alpha = NewAlpha;
 
		//Internal data structure management
		InterpInternal();
 
		//Return Values
		Out = Location;
		return Brightness;
	}
	FMyInterpStruct()
	{
		Brightness = 2; 
		BrightnessGoal = 100;
 
		Alpha = 0; 
 
		Location = FVector::ZeroVector;
		LocationGoal = FVector(0,0,200000);
	}
};

Example 2

You want to track information about particle system components that you have spawned into the world through

 UGameplayStatics::SpawnEmitterAtLocation() // returns a UParticleSystemComponent

and you want to track the lifetime of the particle and apply parameter changes from C++. You could write your own class, but if your needs are simple or you do not have project-permissions to make a subclass of UParticleSystemComponent, you can just make a USTRUCT to relate the various data types!

USTRUCT()
struct FParticleStruct
{
	GENERATED_BODY()
 
	UPROPERTY()
	UParticleSystemComponent* PSCPtr;
 
	UPROPERTY()
	float LifeTime;
 
	void SetColor()
	{
		//
	}
	FLinearColor GetCurrentColor() const
	{
		//
	}
 
	//For GC
	void Destroy()
	{
		PSCPtr = nullptr;
	}
 
	//Constructor
	FParticleStruct()
	{
		PSCPtr = NULL;
		LifeTime = -1;
	}
};

Particle Data Tracker

Now you can have an array of these USTRUCTS for each particle that you spawn!

//Particle Data Tracking Array
TArray<FParticleStruct> PSCArray;

Garbage Collection

Because all of your USTRUCT variables are UPROPERTY(), you must be careful to null them out when you are ready to destroy the particle system component.

Structs With Struct Member Variables

The struct that wants to use another struct must be defined below the struct it wants to include.

USTRUCT()
struct FFlowerStruct
{
	GENERATED_BODY()
 
	UPROPERTY()
	int32 NumPetals;
 
	UPROPERTY()
	FLinearColor Color;
 
	UPROPERTY()
	FVector Scale3D;
 
	void SetFlowerColor(const FLinearColor& NewColor)
	{
		Color = NewColor;
	}
 
	FFlowerStruct()
	{
		NumPetals 	= 5;
		Scale3D 		= FVector(1,1,1);
		Color 			= FLinearColor(1,0,0,1);
	}
};
 
USTRUCT()
struct FIslandStruct
{
	GENERATED_BODY()
 
	UPROPERTY()
	int32 Type;
 
	UPROPERTY()
	TArray<FVector> StarLocations;
 
	UPROPERTY()
	float RainAlpha;
 
	//Dynamic Array of Flower Custom USTRUCT()
	UPROPERTY()
	TArray<FFlowerStruct> FlowersOnThisIsland;
 
	void SetRainAlpha(const float& NewAlpha) 
	{
		RainAlpha = NewAlpha;
	}
 
	int32 GetStarCount() const
	{
		return StarLocations.Num();
	}
	FIslandStruct()
	{
		Type = 0;
		Percent = 1;
	}
};

Struct Assignment

My personal favorite thing about structs is that unlike UObject or AActor classes, which must be utilized via pointers (AActor*) you can directly copy the entire contents of a USTRUCT to another USTRUCT of the same type with a single line of assignment!

FFlowerStruct ExistingFlower;
 
// ... create ExistingFlower here
 
FFlowerStruct NewFlower;
NewFlower = ExistingFlower;

Caveat: Deep Copy

A misconception of the original author is probably that Epic created code in the GENERATED_BODY() macro that would deep-copy a struct upon assigning it to another. In reality, struct assignment has always been a shallow copy in native C. Thus if you were to assign one struct to another like so:

USTRUCT()
struct FMyStruct {
    int32* MyIntArray;
};
 
FMyStruct MyFirstStruct, MySecondStruct;
 
// Create the integer array on the first struct
MyFirstStruct.MyIntArray = new int32[10];
for( int i = 0; i < 10; ++i ) {
  MyFirstStruct.MyIntArray[i] = i;
}
 
GEngine->AddOnScreenMessage(-1, 10.f, FColor::Blue, FString::FromInt(MyFirstStruct.MyIntArray[4]));
 
// Assign the first struct to the second struct, i.e. create a shallow copy
MySecondStruct.MyIntArray[4] = 6;
 
GEngine->AddOnScreenMessage(-1, 10.f, FColor::Blue, FString::Printf(TEXT("%d %d"), MyFirstStruct.MyIntArray[4], MySecondStruct.MyIntArray[4]));

On screen the output will be

 4
 6 6

instead of the expected

 4
 4 6

This is because the data stored in MyStruct::MyIntArray is not actually stored inside of MyStruct. The new keyword creates the data somewhere in RAM and we simply store a pointer there. The address the pointer stores is copied over to MySecondStruct, but it still points to the same data. In fact, it would be counterproductive to remove this functionality since there are cases where you want exactly that. Additionally the Unreal Property System does not support non-UObject pointers, which is why MyIntArray is not marked with UPROPERTY().

However, copying arrays of integers (e.g. int32[10] instead of int32*) means the data is stored directly inside the struct and as such "deep copied". However, if you store a pointer to a UObject, this object is NOT deep copied! Once again only the pointer is copied and the original UObject left unchanged. Which is good because otherwise you might manipulate the wrong instance thinking you only had one to begin with leaving the original UObject unaffected, thus resembling a very nerve-wrecking and very difficult to track down bug!


이 부분이 핀 분할/ 재결합 부분으로

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Flower Struct") 이 한줄이 중요함

Automatic Make/Break in BP

Marking the USTRUCT as BlueprintType and adding EditAnywhere, BlueprintReadWrite, Category = "Your Category" to USTRUCT properties causes UE4 to automatically create Make and Break Blueprint functions, allowing to construct or extract data from the custom USTRUCT.

Special thanks to Community member Iniside for pointing this out. :)

USTRUCT(BlueprintType)
struct FFlowerStruct
{
	GENERATED_BODY()
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Flower Struct")
	int32 NumPetals;
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Flower Struct")
	FLinearColor Color;
 
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Flower Struct")
	FVector Scale3D;
};

CustomUStructMakeBreak.jpg

Replication




 Remember that only UPROPERTY() variables of USTRUCTS() are considered for replication!



So if your USTRUCT is not replicating properly, the first thing you should check is that every member is at least UPROPERTY()! The struct does not have be a BlueprintType, it just needs UPROPERTY() above all properties that you want replicated.

Related Links

UStruct data member memory management

Thank You Epic for USTRUCTS()

I love USTRUCTS(), thank you Epic!

반응형

+ Recent posts