GameInstance reset on level open

I am trying to persist data across levels and everywhere online says to use GameInstace class. So I created a default Third Person Template and a custom GameInstance, then added an int32 variable for testing and set the new GameInstance in my project settings.

I now create two maps, each with a corresponding game mode:
MainMenuMap → GM_MainMenu
GameWorldMap → GM_Gameplay

I have test both OnPostLogin as well as BeginPlay using this method:

  • In the GM_MainMenu, I cast to my custom game instance and add 1 to my variable and print it out.
  • I then press a button I added that will OpenLevelByName ("GameWorldMap ")…
  • The GameWorldMap opens up and thus the GM_Gameplay is loaded.
  • In GM_Gameplay I cast to my custom game instance and print out the value of my variable.

In the main menu, the variable prints out as 1, but in the game world it prints out as 0 (default).

Am I missing something here? Why is my gameinstance being re-created when I open a new level? I verified there is a new instance by printing out the display name, which increments by 1 everytime I load PIE or a new level.

Output
image

Gameplay GameMode

MainMenu GameMode

Play Settings

Project Settings

When you use “open level” by name or object, the game essentially closes and then re opens in a different level. To maintain data you’d need to either save and reload data using a save game or stream the level in from the main menu level

From the screen messages and the play settings, you would appear to be doing something with multiplayer. Your first two output lines are the game instance on your client and the next two are from the game instance on the server. You’ve only set the variable on the server game instance.

If you’re not trying to do multiplayer stuff, try NetMode > Play Standalone instead.

Surely that is not true???

The GameInstance persists throughout the duration of the game, that’s it purpose is it not?

I am having a similar issue to the OP. I created a GameInstance class in CPP then created a BP class from it so I can configure it i the editor, set it as the game’s GameSinstance and then I add a bunch of images to an array, add some integers to an array, add some blueprints to an array, hit play and… what’s this? All my arrays are now empty! :open_mouth:

What magic is going on here? :open_mouth:

GameInstance does retain information across level loads, but it is not replicated. GameMode and GameInstance will get completely overwritten on the client once you connect to a map on the server. In fact, GameMode and GameInstance will become completely unavailable to the client once you connect to the server.

I have no idea how you got the GamePlay gamemode to seemingly print from the client side unless you loaded a client side map. If so, this is broken logic and this thread is moot. If you did connect to a server side map, show us your BP or code that loads the level.

The instace persists yes but any data gets reset, so it might as well reset, that’s why any data or variables you’d want to save and load in savegame. I don’t have experience with multiplayer though I’m speaking only from single player experience

I was printing from the server (see the game mode BPs). From what I’ve read online and even seen videos on, the GameInstance class is precisely what should be used to persist data between levels. I ended up messing with this for 2 days trying every variation I could think of, ( BP only, C++ only, BP + C++, replicated, not replicated, client, listen server, standalone, etc…).

I ended up saying screw it and went with what I know from my day job… just save it to a database and call it a day. I know for sure that the game mode is server only and I’m already connecting to a database so I just persisted the “active” character to the character table as a flag. It seems to work well enough for now. I truly don’t understand the purpose of the GameInstance anymore and will probably never mess with it again after wasting so much time for a trivial task.

For anyone interested, I’m using NetDBPlugin (https://www.unrealengine.com/marketplace/en-US/product/netdb) and this is the sql I’m using to create my tables.

CREATE OR REPLACE FUNCTION manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
    EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
                    FOR EACH ROW EXECUTE PROCEDURE set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$
BEGIN
    IF (
        NEW IS DISTINCT FROM OLD AND
        NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
    ) THEN
        NEW.updated_at := current_timestamp;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;


CREATE TABLE player (
	id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
	net_id VARCHAR(128) NOT NULL UNIQUE,
	created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
SELECT manage_updated_at('player');

CREATE TABLE character (
	id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
	player_id UUID NOT NULL REFERENCES player (id) ON DELETE CASCADE,
	name VARCHAR(128) NOT NULL UNIQUE,
	x real NOT NULL DEFAULT 0,
	y real NOT NULL DEFAULT 0,
	z real NOT NULL DEFAULT 0,
	yaw real NOT NULL DEFAULT 0,
	pitch real NOT NULL DEFAULT 0,
	roll real NOT NULL DEFAULT 0,
        -- Whatever else you want to track like attribute sets, etc...
	active BOOLEAN NOT NULL DEFAULT FALSE,
	created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
	updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX idx_active_player ON character (player_id, active)
WHERE active = TRUE;

SELECT manage_updated_at('character');

And a little info of the game code… way too much to post here but essentially it goes:

  • MainMenuPlayerController class
  • CoreGameMode class
    • MainMenuGameMode class
    • GameplayGameMode class

CoreGameMode: has some helper functions and common state, such as the players connected mapped to their ID, or a utility to get their unique net ID as a string.

MainMenuGameMode : Has all the functionality to create a new player, character, etc… basic crud stuff, as well as “log a character” into the game. This essentially just updates the DB with the flag I was talking about and returns the IpAddress that should be used to open the level with.

MainMenuPlayerController: works as my interface between UI and game mode and creates some delegates and utilties

GameplayGameMode: validates the player and character in the DB on post login, tracks player, creates character, possesses and spawns.

For something like a main menu I think this will suffice for now, my in-game code uses a HUD, controllers, etc but that seemed like overkill for this. :person_shrugging:

It feels weird to mark my own post as the solution so if someone can explain how to use the GameInstance class to persist data between levels in a multiplayer scenario I’ll update the solution to theirs :smile:

@doctorpepperdan GameInstance data remains across level loads. It does not get reset.

2 Likes

You still haven’t shows how you’re loading the level.

As to your print statements, the first one does say Server, but the one in red does not. I’m not sure how printing strings from the server works. But if it adds “Server:” automatically and you’re not manually adding it, would that not mean that the second one is on the client side? If so, then everything is working as designed. It would imply that you’re loading a level on the client side instead of on the server side. This is why we need to see your level loading code or blueprint.

@AlienRenders I’ve updated things to work using my database but the previous situation was using a variety of different attempts of which I couldn’t get any of them to work. There isn’t anything particularly unique about any of the BP or C++ code I used and I don’t have it saved that way anymore but I’ll try to remake it a bit for images. Either way here is a rundown of what I thought would work:

My Sever default map is WorldMap. The client starts on the MainMenuMap.

User Widget (MainMenu) in BP: OnButtonClick (Login) → GetPlayerController → Cast to MainMenuPlayerController → call Server RPC to CharacterLogin;

MainMenuPlayerController (within server RPC) → GetGameMode → Cast to MainMenuGameMode → call function on the game mode

MainMenuGameMode fn → get and cast to custom game instance and set the variable (this is where I thought storing it on the server only would be fine).

MainMenuPlayerController (within server RPC) → after the MainMenuGameMode function is finished, call a Client RPC UFUNCTION(Client, Reliable) which then:

  • I tried broadcasting a dynamic multicast delegate and calling Open Level By Name node in the UserWidget
  • I tried to also call ClientTravel() in C++

Both of which would travel the Client to the WorldMap (were my gameplay area is). This is using the GameplayGameMode.

GameplayGameMode → Get the custom GameInstance and check the variable (that I thought I set on the server)

So the Blueprint Node in the user widget was and in C++ it was ClientTravel(LevelName, bAbsolute).

It would be helpful to know what you’re expecting to see in the blueprints or C++ to make this work that I haven’t already tried? Is there a proven method for using the GameInstance in this way? I’ve watched and read a dozen tutorials and forum posts all of which didn’t work for me. Also FWIW, I tried setting the variables in the GameInstance to Replicated.

I’d love to just use the game instance b/c that seems like the “proper” way to do things, but i’m truly confused and haven’t found any way to make this work as expected. Using the DB approach everything is working as expected using this flow of data, whether or not this flow is right I guess is another question :person_shrugging:

Would it be the case that I’ll need to load the level using the map name and server travel in the editor but client travel and the ip address when in packaged versions?

I uploaded videos showing my current approach that I think is working:

Part 1

Part 2

I tried broadcasting a dynamic multicast delegate and calling Open Level By Name node in the UserWidget

Ummm… is this not a multiplayer game? Why are you switching back to single player mode with a local GameInstance and local GameMode on the client side?

Ummm… is this not a multiplayer game?

It is

Why are you switching back to single player mode with a local GameInstance and local GameMode on the client side?

I’m not sure what I’m doing (apparently wrong) or what you mean by this. How am I switching to single player mode? I thought that the client used ClientTravel() to 127.0.0.1:7777 to get into the server? Do you mean when I use WorldMap for the editor version?

What are you using as the level name in the OpenLevel node?

I tried a few variations:

  • WorldMap
  • 127.0.0.1:7777
  • 127.0.0.1:7777/WorldMap
  • 0.0.0.0:7777
  • etc…

It might be helpful to write out my expectation, and the solution might be apparent to you from this:

  • Player opens the game client and is greeted by the main menu map
  • Player selects a character and clicks login
  • This is where the information of which character the player clicked needs to be saved somewhere persistent across levels and the server needs to know about it to get data about the character.
  • The player is then transported to the game world (WorldMap) and the character is spawned and possessed

The 3rd and 4th point are what I’m struggling with it seems… this is within the context of a multiplayer game

Testing my approach on a packaged build seems to be working. Here is a video where I login and log out to the server and client the playercontroller display name:

I think part of my confusion is being unable to connect to 127.0.0.1:7777 when running in client mode. Isn’t unreal’s default backgrounded server running at that address?

Sorry. By the print screenshot, it really looked like the second printouts in red were client side. This can happen if you load a client side map by not using an IP, but I’d have to test this and I don’t have the time right now.

Since you are using a server, game mode should only exist on the server. I still think the red printouts are client side as that would explain the difference. But since the BP is in a GameMode, I can’t explain it at the moment.

Just to follow up on this b/c I think I know what is happening… It seems that running in standalone net mode with the dedicated server option ticked in the advanced settings for play menu, then selecting play as standalone game maybe works? It may have something to do with PIE potentially always creating a new game instance for every PIE instance, which is X number = to # of players :thinking: