Integrating an Existing Project
We're using C++ class names below, but you can do this in BP with the equivalent nodes.
This is a thorough integration guide for the basic set of Redwood features. We don't cover everything (e.g. social features), but this should get you to a point where you can place players into servers and have data synchronized.
Basic Setup
- Enable the
RedwoodCoreplugin (orRedwood MMO Frameworkas listed in the Plugins menu) - If you're using C++, you'll need to add the
Redwoodmodule to yourPublicDependencyModuleNames - All of the server
AGameMode(Base)classes in your game should inherit fromARedwoodGameModeBaseorARedwoodGameModerespectively.- NOTE: You will not use
ARedwoodGameModeComponent.h; this is a common class we use to keep common logic
- NOTE: You will not use
- All of the server
AGameState(Base)classes should addURedwoodGameStateComponentas a default component - All of the server
APlayerStateclasses should inherit fromARedwoodPlayerState
Player Character Data
We chose the APawn class as the ideal place to have character data since network relevancy for open world games is tied closely to character proximity. If you need character data to transfer between one possessed pawn to another, reach out!
If you're using Redwood's semi-automated synced player data for the character (recommended), all of the server APawn classes should add [URedwoodCharacterComponent](https://github.com/RedwoodMMO/RedwoodPlugins/blob/main/RedwoodCore/Source/Redwood/Public/RedwoodCharacterComponent.h) as a default component (yes, this works for any APawnor child class, including andACharacter`)
By default, this component expects your APawn class to have UPROPERTY (ignore this if you're using BP) variables, whose types can be arbitrary USTRUCT (ignore this if you're using BP) structs:
CharacterCreatorDataMetadataEquippedInventoryNonequippedInventoryData
These variables replicated by default, you will need to configure that yourself. See the docs about our recommended replication settings for each.
You may disable these by setting URedwoodCharacterComponent::bUse<default-variable-name> (e.g. bUseCharacterCreatorData) to false.
You may change the name of your class variable by changing URedwoodCharacterComponent::<default-variable-name>VariableName (e.g. CharacterCreatorDataVariableName).
The struct definition for each type must be serializable; any non-serializable fields (e.g. pointers/"object references") will be ignored by persistence.
Every time you want Redwood to save modified values of the above variables to the database, you'll need to call URedwoodCharacterComponent::Mark<default-variable-name>Dirty (e.g. MarkCharacterCreatorDataDirty). Redwood doesn't automatically detect changes. Redwood also batches these saves and doesn't immediately save the value; the batched save call happens every 0.5 seconds by default. You can change this value by setting ARedwoodGameMode(Base)::DatabasePersistenceInterval to the number of seconds to wait per batched call (under your Class Defaults for your respective Game Mode class in BP if you inherited from the Redwood class). See extra notes about the interval here.
Persistent Data and Cross-server Synchronization for any Actor
If you have any non-Pawn actors that need persistence using Redwood's semi-automated synced data aka Sync Item (recommended).
- Similar to the
URedwoodCharacterComponentabove, you'll add the `URedwoodSyncComponent as a default component to all the actors that need this features - Instead of all the fields like in
URedwoodCharacterComponentabove, you only need to define a variable for the variable:Data
- You may disable it with
URedwoodSyncComponent::bUseData(though I'm not sure why you would) or change the expected name withURedwoodSyncComponent::DataVariableName - You need to set
URedwoodSyncComponent::bPersistChangestotrueto have the data persisted to the database for each instance of this actor (we coin these specific Sync Items as Persistent Items in the docs). The default value isfalsewhich is used to sync the data across shards/zones as configured at runtime, but not to consider it as a persisted (aka save to the database) instance - Similarly, you'll need to call
URedwoodSyncComponent::MarkDataDirtyto persist the changes toData; this happens at the same ``ARedwoodGameMode(Base)::DatabasePersistenceIntervalsetting described above for theURedwoodCharacterComponent` - You can instantiate Persistent Items at design-time (aka placing them in the Level Editor) or at run-time (aka spawning the actor from the server). If you're doing it at design-time, you'll need to manually set
URedwoodSyncComponent::RedwoodIdandURedwoodSyncComponent::ZoneNamefor each instance to ensure things are persisted properly. You can read more about this here or see an example in the RPG Template as the single fence near the village spawn point is a design-time Persistent Item (aka it reloads its last position from the database if the servers get rebooted) - For every Sync Item type (regardless if it's persisted), you need to create a new Data Asset that inherits from
URedwoodSyncItemAsset- Set Redwood Type Id to a unique value (e.g.
community-chestorhouse-smallormob-boars) - Set Actor Class to the class that you added the
URedwoodSyncComponentto - You don't need to reference this Data Asset anywhere else; Redwood handles the rest (other than the Asset Manager section below)
- Set Redwood Type Id to a unique value (e.g.
Blob Storage
The alternative persistence method to the above recommended methods is Blob & Document Storage. This feature was originally added to support large documents/pictures, but you can use it to save arbitrary files (e.g. CSV, JSON, SaveGame). You'll have to manually call the load/save functions and deal with the serialization, but definitely an option.
World Data
In your AGameState(Base)class, add URedwoodSyncComponent as a default component to enable world data
- This data is synchronized across all zones/shards in the proxy
- Set
URedwoodSyncComponent::bPersistChangestotrue - Set
RedwoodIdtoproxy - Add a class variable to your
AGameState(Base)class with the nameDataof typeUSTRUCT(following the other notes above aboutURedwoodSyncComponent) - Create a Data Asset that inherits from
URedwoodSyncItemAsset- Set Redwood Type Id to
proxy - Set the Actor Class to your
AGameState(Base)class
- Set Redwood Type Id to
In Project Settings > Game > Asset Manager you will need to add three elements to the Primary Asset Types to Scan:
- Game modes
- Primary Asset Type:
RedwoodGameModeAsset - Asset Base Class:
RedwoodGameModeAsset(orURedwoodGameModeAssetif you're configuring this directly in the INI file) - Has Blueprint Classes:
false - Is Editor Only:
false - Rules > Cook Rule:
Always Cook - Set Directories and Specific Assets as you see fit; I generally have mine all under some common root folder and add that to Directories
- Primary Asset Type:
- Game maps
- Primary Asset Type:
RedwoodMapAsset - Asset Base Class:
RedwoodMapAsset(orURedwoodMapAssetif you're configuring this directly in the INI file) - Has Blueprint Classes:
false - Is Editor Only:
false - Rules > Cook Rule:
Always Cook - Set Directories and Specific Assets as you see fit; I generally have mine all under some common root folder and add that to Directories
- Primary Asset Type:
- Sync items
- Primary Asset Type:
RedwoodSyncItemAsset - Asset Base Class:
RedwoodSyncItemAsset(orURedwoodSyncItemAssetif you're configuring this directly in the INI file) - Has Blueprint Classes:
false - Is Editor Only:
false - Rules > Cook Rule:
Always Cook - Set Directories and Specific Assets as you see fit; I generally have mine all under some common root folder and add that to Directories
- Primary Asset Type:
Backend Config
For every server game mode and map that the Redwood system will know about in the game profiles, you'll need to create a Data Asset inheriting from URedwoodGameModeAsset and URedwoodMapAsset respectively
-
For
URedwoodGameModeAsset:- Redwood Id should be set to some unique identifier that will be used in the game profile
idfield (e.g.instanced-dungeonorelimination) - Show in Front End isn't used by Redwood itself, but it's a convenience flag for you to filter what's displayed to users in a title menu/front end screen (e.g. when selecting a mode for a private server)
- Display Name, Display Description, and Display Icon are all other convenience fields for you but not used by Redwood; they can be left blank
- Game Mode Type should be set to
Game Mode BaseorGame Modedepending on which type of parent class for the respective game mode class - Game Mode [Base] Class will show in the Data Asset editor based on Game Mode Type allowing you to select the corresponding game mode class
- Redwood Id should be set to some unique identifier that will be used in the game profile
-
For
URedwoodMapAsset:- Redwood Id should be set to some unique identifier that will be used in the game profile's zone's
mapsfield (e.g.mine-shaftorred-blue-map) - Map Name is a convenience field for you to display to the user as you see fit and is not used by Redwood
- Map Id should point to the Unreal map/level asset
- Redwood Id should be set to some unique identifier that will be used in the game profile's zone's
-
You will need to configure your Realm Instance Config and game profiles in the backend's config env for your project. You can see examples for our template projects here.
Main Menu / Game Frontend
The title screen/main menu/game frontend will have several Redwood integration points. There's a decent example in the RPG Template at Content/UI/W_MainMenu, but here's the general flow:
- C++ users should not use
URedwoodClientInterfacedirectly, useURedwoodClientGameSubsystemas it provides extra wrappers for backend-less scenarios - C++ users will need to reference
RedwoodPlugins/RedwoodCore/Source/Redwood/Public/RedwoodClientGameSubsystem.hand the various C++ struct types found in `RedwoodPlugins/RedwoodCore/Source/Redwood/Public/Types/ as we'll not provide complete function & struct signatures in these instructions. We also don't have a reference docs site; "the code is the documentation", sorry! - Check if the player is logged in
URedwoodClientGameSubsystem::IsLoggedIn()and/or is connected to a realm to determine what logic skip for players returning to the main menu. We will continue along as if these both were returnedfalse - Call
URedwoodClientGameSubsystem::InitializeDirectorConnection - On success, show login/register widgets
- On player login, call
URedwoodClientGameSubsystem::Login; on player register callURedwoodClientGameSubsystem::Register- If you're using Steam to authenticate, see the docs
- Both login and register functions will call the passed in delegate (or
On Updateexec pin for BP) with aFRedwoodAuthUpdatestruct multiple times based on the authentication flow. TheFRedwoodAuthUpdate::Typeis aERedwoodAuthUpdateTypeenum with options forSuccess,MustVerifyAccount(if you enable email verification),Error, orUnknown, with an accompanyingFRedwoodAuthUpdate::Message(which is blank onSuccess) - Once logged in, you'll want to call
URedwoodClientGameSubsystem::ListRealmswhich returns an array ofFRedwoodRealmstructs (by default, there will only be 1); these can be either shown to the user to select or you can auto select - Once a realm is picked, call
URedwoodClientGameSubsystem::InitiateRealmConnectionwith the correspondingFRedwoodRealmstruct. You can only be connected to 1 realm at a time - You can list the player's characters in the connected realm with
URedwoodClientGameSubsystem::ListCharacters - Players can create characters in connected realm by calling
URedwoodClientGameSubsystem::CreateCharacterwith aNamefor the character aUSIOJsonObject*object reference for theCharacterCreatorDatafieldUSIOJsonObjectcomes from the a forked version of the third-partySocketIOClientplugin which is included in theRedwoodPlugins. It is a BP-friendly wrapper around the Unreal built-inFJsonObjectclass. We're aware UE now has some BP support for JSON objects but older versions did not.- To create this object, you can pass your
CharacterCreatorDatastruct variable into theStruct to Json Objectfunction in BP or call the static functionUSIOJLibrary::StructToJsonObjectin C++ - Note: Malicious users can reverse engineer this network method and set whatever data they want for
CharacterCreatorData, which is why you should not put important fields like Level/XP/Unlocks in this struct
- Players can update the character data (
NameandCharacterCreatorData) by callingURedwoodClientGameSubsystem::SetCharacterDatawith the character's ID returned in the struct fromListCharactersorCreateCharacter- In the future, we'll provide a flag that allows you to disable this function in the backend in case you want players to only update their characters from replicated server functionality
- You can call
URedwoodClientGameSubsystem::GetCharacterif you know the Redwood ID for the character - When the player has selected a character to join a server with, call
URedwoodClientGameSubsystem::SetSelectedCharacterwith the character's ID returned in the struct fromListCharactersorCreateCharacter - Players can join a server 4 different ways. We're using the term "Server" below loosely (I know, bad terminology, but this seemed like the best dev experience), but these actually are GameServerProxies which can have an active GameServerCollection which consist of one or more GameServerInstances
- Creating a Server
- There is an example of this in the Match Template with Lyra's Create Lobby logic; the RPG Template doesn't have an example of this flow
- Calling
URedwoodClientGameSubsystem::CreateServerand setting the first argumentbJoinSessiontotruewill automatically join the server after it's created - If you leave
bJoinSessiontofalse, you can callURedwoodClientGameSubsystem::JoinQueuewith theServerReferencefield in the returned struct
- Joining a Server
- You can find available servers with
URedwoodClientGameSubsystem::ListServers - Join the server by calling
URedwoodClientGameSubsystem::JoinQueue
- You can find available servers with
- Matchmaking
- Call
URedwoodClientGameSubsystem::JoinMatchmakingwhere theProfileIdis the matching game profilesidandRegionsis an array of the regions the player would like to be assessed for (ping times are automatically assessed and sent to the matchmaker). You can get the available regions withURedwoodClientGameSubsystem::GetRegionswhich returns aTMap<FString, float>; the keys of this map are what is passed toJoinMatchmaking.
- Call
- Both
JoinQueueandJoinMatchmakingwill have a similar flow to theLoginfunction where the backend will call the delegate when there's anJoinResponse,Update,TicketError, orUnknownvalue on the suppliedFRedwoodTicketingUpdate::Typeand accompanyingMessagefield
- Creating a Server
- The Redwood plugin is told by the backend to join a server when any of the above succeed and it automatically runs the
open <ip-address>:<port>?<args>command, including an automatic authentication handshake via an ephemeral token so the receiving game server can verify the joining player is meant to be there.
Miscellenaous
- When the player joins a server with a valid auth token (handled for you automatically), the associated character data is automatically fetched from the backend. Your
APlayerStateclass (which should inherit fromARedwoodPlayerState) has a BP-bindable delegateOnRedwoodCharacterUpdatedwhich is triggered on the server only whenever the character data is updated from the backend (currently just on connection) - The
URedwoodCharacterComponentautomatically listens for the associatedARedwoodPlayerState::OnRedwoodCharacterUpdatedon the server and sets your struct data automatically.URedwoodCharacterComponentthen callsURedwoodCharacterComponent::OnRedwoodCharacterUpdatedon the server. In Redwood version4.0+, this event will triggered on clients as well via a multicast message. In the meantime, clients can get notified of it change by using a Rep Notify of the various data structs. - You don't need to use the set of classes we provided for interactables, but they contain some of our own best practices to prevent too much coupling due to putting RPCs in other places automatically. See the docs for details.
- Don't forget to install the prerequisites and do the setup