Download

Where to put a random generator?

I have a need to randomly generate items in a game. I am trying to do seeded runs (think Spelunky) where the “game” starts with a random seed.

I created a class:


UCLASS()
class URNGesus : public UObject

Which I added to my custom GameInstance:


UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Game", DisplayName = "RNGesus")
    class URNGesus* RNGesus;

and then I initalize in ctor:


RNGesus = NewObject<URNGesus>(URNGesus::StaticClass());

However I quickly found out that I cannot spawn objects in my world:


if(GetWorld() == nullptr)
{
    PRINT("Error:  GetWorld() == nullptr")
    return nullptr;
}

This got me thinking that I have the wrong approach. My first biggest issue here is that it is not tied to any specific session, but rather the global game instance. Therefore I would have to rely on manually changing it when entering / exiting a game session. It also would not get saved when saving my game session, which I need to do since it needs to store several states.

A “game session” here may be singleplayer or multiplayer. It can also span multiple levels as players move from one randomly generated level to the next. I need my generator to persist across multiple levels.

I had the idea that I could add the actual generation to my GameMode class, with a seed that defines the actions going on during that level. I could then “start” with a new seed, and then essentially pass the torch when it is time to switch levels. However I would still like to have a state that is tracked until the game finishes and the player goes back to the main menu.

How can I best accomplish this?

Edit: Just learned about level streaming. It looks like I could get away with putting everything in a persistent level and then streaming the various areas in. Would it be good to then put the desired functionality in the GameMode and work with it this way?

I ended up using a simple FRandomStream in the GameState (I have a persistent level with streaming levels branching off of it). I re-seed on new game start and I save/load the CurrentSeed so the game is always in the proper state. Many other RandomStreams are seeded from that main game RandomStream, so that I can keep other systems/levels isolated from changing the original random stream (this is mostly to help avoid different results in procedural generation if the rng is used more often in new versions).

If you end up needing to transition to other levels, you can maybe store the seed in the game instance when traveling, and place it back in GameState once the level is loaded (game instance persists accross levels, GameState can be saved more easily).

This sounds like something that a function library would be useful for. Here’s some implementation code to help you get started:



UCLASS()
class UGameLib : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:

	UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GameLib")
		static FVector PolarToRect(float Distance, float Theta);

	/*Creates a random spawn position relative to the origin*/
	UFUNCTION(BlueprintPure, BlueprintCallable, Category = "GameLib")
		static FVector GetRandomSpawn(float Radius);
};


Aha, so GameState is a good place for this.

I did exactly that, and then threw my generator logic into GameMode (spawn random item, etc…), and it appears to be working well!

I did happen to notice that seamless travel (through UWorld::ServerTravel) keeps the same GameMode instance. Does this mean that I can use standard maps to organize everything, and I can expect that GameMode/GameState will continue working well as my theoretical character travels from map to map?

The alternative that I’ve found is to use one large persistent map, but this would involve teleporting players and unloading/loading sub-levels to switch from one map to another.