Progress Update #2

Star Properties


    It seems to have become tradition that whenever I set out to do something that I think will be easy, it ends up being the opposite. Making properties for the stars was no different. There were two main parts to this process. Making changes to variables apply to the material, and making those changes affect the lighting of the planets.


    Making the variables apply to the material was not that hard as I had done that before. It just ended up being a time consuming process to do for all the properties. All that I had to do was implement a PostEditChangeProperty for every property and set a vector or scalar parameter value for each on a UMaterialInstanceDynamic. Here's what that looks like for radius and mass:
void AStar::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent)
{
	if (PropertyChangedEvent.Property != nullptr)
	{
		const FName PropertyName(PropertyChangedEvent.Property->GetName());

		if (PropertyName == GET_MEMBER_NAME_CHECKED(AStar, radius))
		{
			sphere->SetRelativeScale3D(FVector(starProperties.radius, starProperties.radius, starProperties.radius));
		}
		if (PropertyName == GET_MEMBER_NAME_CHECKED(AStar, mass))
		{
			this->mass = starProperties.mass;
		}
                ...

    Something weird happened when I tried to do the color and luminosity variables. Unreal claimed that AStar didn't have a property called color or luminosity. This confused me quite a bit as the mass and radius worked fine. After some messing around, I found the following.
  1. The PropertyName is a single string (so no star.color)
  2. Inherited variables will count as a changed variable
  3. Variables part of structs do not trigger the PostEditChangeProperty function
    Points 1 and 2 are why the radius and mass worked and point 3 is why the color and luminosity didn't work. I had the star header file set up like this:
USTRUCT(BlueprintType)
struct FStarProperties : public FTableRowBase 
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, meta = (ClampMin = "1"))
	int radius = 50;

	UPROPERTY(EditAnywhere, meta = (ClampMin = "1"))
	int mass = 1000;

	UPROPERTY(EditAnywhere, meta = (ClampMin = "0"))
	int luminosity = 50;

	UPROPERTY(EditAnywhere)
	FColor color = FColor(128, 0, 0);
};

UCLASS(hidecategories = ("Default"))
class CPPGAME_API AStar : public ACelestialBody
{
	GENERATED_BODY()

public:
	AStar();

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	UStaticMeshComponent* sphere;

	UPROPERTY(EditAnywhere)
	FStarProperties starProperties;

	UPROPERTY(EditDefaultsOnly)
	UDataTable* starTypeData;

	UMaterialInstanceDynamic* dynamicMaterial;

	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
};

    This way all of the star properties could be stored in a struct in a way that a data table can change all of them at once to certain values (I did this so I could have some default values based on real stars). However, this had the unintended effect of the color and luminosity not being visible under GET_MEMBER_NAME_CHECKED(AStar, color), because AStar did not have any variable named color; it belonged to FStarProperties. This meant that if I replaced AStar with FStarProperties it would be able to detect the change.
    Great, now it should all be working right!? Well, it was working, but now I was confused about why radius and mass were working when I didn't have any variables with those names. This is where point 2 from earlier comes back. I realized that ACelestialBody, of which AStar inherits from, contains variables named mass and radius. This made total sense when I figured it out, and since it worked I just changed GET_MEMBER_NAME_CHECKED from AStar to FStarProperties. And because I was using the FStar properties for radius and mass, I added hidecategories = ("Default") to the AStar UCLASS to hide those inherited variables so the user will only use the AStar derived variables.

    Now that that the basis of the properties were done, I went on to implement the color and luminosity. As I stated before there were two parts to this Making changes to variables apply to the material (which I had already done with the planets), and making those changes affect the lighting of the planets. Because changes to the material have already been covered before, I will skip that part of the explanation and get right to the second part.
    Getting changes to the star material affect the lighting of the planets was a royal pain in my ass, and it was entirely my own fault. I was initially unaware of the use case of material parameter collections to solve my exact problem, but I will explain the two other attempts I made before discovering this.
    The first attempt involved setting a variable on the gamemode that would use the same PostEditChangeProperty event to update all planet materials with the new color and luminosity of the star. On top of this being a really stupid and over-complicated way to solve the problem, it straight up just didn't work. This was because, in the editor, I was always getting a nullptr returned when I tried to get the gamemode. I think with 99% certainty this is because the gamemode object doesn't exist until the game actually starts. I was unable to prove this, but because of it I ended up scratching this approach. 
    The second approach was when I discovered material parameter collections. The problem was I was just using them incorrectly. I was trying to edit the parameters directly on the collection but it wouldn't let me. Oh, how could I have been so foolish, Unreal wants you to make an instance of the collection to edit the parameters on. Why? I still don't know. Probably some funky backend bs that I don't understand. Also, NONE OF THIS IS IN THE DOCUMENTATION! The documentation only has this one reference that it even exists. Not to mention I only found it after sifting through all of the other results that weren't to do with the instances. Even with that one page of documentation, it was this forum thread that introduced me to the function I needed to use to get it working: GetWorld()->GetParameterCollectionInstance(whatever collection you want an instance of). I'm not going to show all the final code in here so if you want to see all of that go to the github page.

    Out of all that, this is the final result:

To be honest, I think it looks pretty similar to stock Unreal, which is kind of what I was going for. 

Comments

Popular posts from this blog

Polishing the Foundation

Progress Update #13

A New Chapter