https://metaplay.io logo
Using 2 Config SheetBuildSources in Idler example
# questions-and-feedback
a
Hi, I'd like to split our Config sheets so that they are in 2 different sheet-docs. We are using a Config Builder based on the example in Idler, where there are two sources: - Default Source - Live Ops Source Should those work simultaneously? There is the Synthesize Data option too. Or is it that the Live Ops one can be used to modify something which is in the Default, but Default still must include everything? The code is like that:
Copy code
csharp
        private static GoogleSheetBuildSource[] _sheetSources =
        {
            new GoogleSheetBuildSource("Master sheet", "1wXKv4SPD7BZBwTRc7YfAU7oVjuWFDZZFbHZ8PKyCatM"),
        };

        public override IEnumerable<GameConfigBuildSource> GetAvailableGameConfigBuildSources(string sourceProperty)
        {
            if (sourceProperty is nameof(IdlerGameConfigBuildParameters.DefaultSource) or nameof(IdlerGameConfigBuildParameters.LiveOpsSource))
            {
                return _sheetSources;
            }
Maybe I should just modify that to use two sources somehow normally but don't really know how yet, only started to study the builder stuff.
j
Hey! Idler has some testing functionality (like synthesize data) to test things like very large game configs. These are not meant to be used in other projects, so they can be safely removed. To use multiple game configs as build sources, there's a few steps: 1. Define an extra source in your
GameConfigBuildParameters
2. Change the
GameConfigEntryAttributes
in your
SharedGameConfig
to set the optional
configBuildSource
parameter to the name of source that you want to use, if you don't pass anything then it will use the default one 3. Add more sheet sources to
GetAvailableGameConfigBuildSources
And I think that should already work
a
ok, 3. i already did on my own 🙂 for 1 i am trying to reuse the DefaultSource & LiveOpsSource 2. i think i was getting at, seeing the Core GameConfigBuild code can deal with various sources, i'll try what you said now thanks!
hm. so should
GetAvailableGameConfigBuildSources
always return the list of all sources? or maybe the implementation in Idler is weird with the synthesize stuff and I should switch to a normal template .. i don't have the menu option "Metaplay/Enable Default Game Config Build Window", I guess because I already had the config builder from before, but I guess I can copy it from the SDK somewhere
j
GetAvailableGameConfigBuildSources can return different sources based on the source parameter being passed, Idler is just a bad example of this tbh
Which SDK version are you on? The default game config build window is a relatively new addition
a
we upgraded to 29 a few weeks ago, but i was using config before that
and yes this code is weird i have 2 entries in _sheetSources but i don't know how that makes sense
Copy code
csharp

       public override IEnumerable<GameConfigBuildSource> GetAvailableGameConfigBuildSources(string sourceProperty)
        {
            if (sourceProperty is nameof(DecorsocietyGameConfigBuildParameters.DefaultSource) or nameof(DecorsocietyGameConfigBuildParameters.LiveOpsSource))
            {
                return _sheetSources;
            }
i tried using a GameConfigEntry with the other source but got an error about that .mpc missing from archive
maybe i'll try modifying that getter to give the right source
j
Hmm, then you should already have the game config build window in teh Metaplay menu I think
a
somehow it's disabled, maybe because i have an active builder already?
disabled == not visible,

https://pasteboard.co/ozAx6SF7VQAe.png▾

j
That's very weird
Just to sanity check, DeploymentOptions:98 has
major: 29
right?
a
public static DeploymentVersion CurrentMetaplayVersion => new DeploymentVersion(29, 2, 0, null); // add "pre" label in develop when between releases, "null" otherwise
we have at least .3 and maybe .4 fixes in too as Antti from your side did the upgrade
maybe he disabled the config init thing as he knew we were already using config
j
Can you whether
GameConfigBuildWindow.cs
exists in the unity project (should be in the metaplay unity sdk package)?
a
yes
MetaplaySDK/Client/Unity/Editor/GameConfigBuildWindow.cs
is there i'm also building configs all the time
j
And in that file there's the CreateWindow method?
a
yep
Copy code
csharp
        [MenuItem("Metaplay/Game Config Build Window", false, 3)]
        static void CreateWindow()
        {
            Type                  inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll");
            GameConfigBuildWindow window        = GetWindow<GameConfigBuildWindow>("GameConfig Build", desiredDockNextTo: new Type[] {inspectorType});
            window.Show();
        }
j
Oh lmao, turns out it's there, it's the second options 🙈
Right, back to the building issue, you already split your spreadsheets into 2 right?
a
but if i press that, i get the old Idler build window with the Live Ops Source and stuff
yep i have two sheets, but get an error when try to use the other one
j
Can you show me a screenshot?
a

https://pasteboard.co/IZE6k2CdH4BM.png▾

j
I'm assuming that either of your sheets are only compatible with one of the sources right? So LiveOpsSource contains only the sheets that are marked with the
GameConfigEntry.configBuildSource == LiveOpsSource
?
a
yep
so i'm doing
Copy code
csharp
        [GameConfigEntry("ItalianDream", configBuildSource: nameof(DecorsocietyGameConfigBuildParameters.LiveOpsSource))]
        public GameConfigLibrary<HotspotKind, HotspotInfo> Hotspots { get; set; }
`
j
Can you show me your GameConfigBuildParameters?
a
it's unmodified from the Idler so quite big
apparently too big for a discord message
j
Ah, that probably explains. You likely don't need any of that 😅
a
yep
i'd like to get the normal base instead, i think
maybe if i delete my `UnityProject/Assets/_SharedCode/Config/MainGameConfigBuilder.cs`the menu entry works to init?
j
Copy code
[MetaSerializableDerived(1)]
    public class CustomGameConfigBuildParameters : GameConfigBuildParameters
    {
        [MetaMember(2), MetaFormLayoutOrderHint(0), MetaValidateRequired]
        public GameConfigBuildSource LiveOpsSource;

        public override bool IsIncremental => false;
    }
Is likely all you need
a
cool i try that .. but then i still have tons of extra with the synthesize and duplicate sheet etc codes
j
You can also delete
IdlerGameConfigBuild
IdlerGameConfigBuildIntegration
is the only thing you'll need
The GameConfigBuildWindow will automatically find your build parameters and create the required ui controls, so once you get rid of the extra stuff in the BuildParameters it should automatically update
a
woo-hoo thanks now it worked to fetch! i just have formatting errors in the sheet still i think, but looks promising
j
We don't recommend using idler as a base for projects since it also contains a bunch of testing stuff
a
yep. well i think it was someone here some time in June-August who proposed taking Idler as a base, that's why I had it 🙂 but now it's great with the much simpler stuff in use
or maybe it was in May i don't remember
j
Might've been me 🤷‍♂️
Idler does have examples of most features, but not all are great to use as a starting point. Partially because there's testing features but also because it generally implements more (as an example how to do complicated things) than is necessary
a
yep it was nice to see the advanced things there and good to know that such are around
but we'll be fine with the simple basics for long now
@Jurjen can i dynamically add GameConfigEntry s so that I have a sheet that lists all levels, and then the config builder reads the sheet by the name of level, for each? i guess everything is possible by customizing the builder code.. but i only skimmed the docs
j
Can you elaborate on the structure of your sheet and the structure of how you want it represented in the GameConfig class?
a
i have a like a top sheet, Contests, which has a row per contest, Kind #key is first. i'm already using that for basics. then we have additional sheets with detailed information about each Contest (like a level). earlier we've used a quick hack custom JSON export of those to get the level data to the game, but we'd like to switch everything to Config now i could for example use the ContestKind that's the key in the first sheet as the name of the per-level sheets then for the detailed level info i already have IGameConfigData which works, but now i just need to hardcode the level name in my GameConfig to fetch that sheet
so .. instead of manually saying
Copy code
csharp
        [GameConfigEntry("ItalianDream", configBuildSource: nameof(DecorsocietyGameConfigBuildParameters.RoomStylerSource))]
        public GameConfigLibrary<HotspotKind, HotspotInfo> Hotspots { get; set; }
for each level, i'd need to create such entries for each level that's listed in the first sheet as the whole idea is that we can add new contests just via config but it's nice to author the per-level data so that it's in a dedicated sheet
so .. it's like adding GameConfigEntrys programmatically as a part of the build process, after fetching the first sheet, to then fetch all the level sheets too. if that's possible
maybe it's not possible though as those are there at compile time and as members of the SharedConfig .. so i'll need to set it up somehow differently
j
Instead of adding new members, I'd transform the data from multiple sheets into one, so you'd have a
GameConfigLibrary<ContestKind, AggregatedDetails>
. Not 100% sure off-hand what the easiest way to achieve this is, can potentially take a look later.
a
right, that makes sense i was thinking of merging the data to a single sheet in googlesheets, perhaps some function there could do that, but using multiple sheets directly as sources for a single library would be much better
i think i'll hardcode our current contests to those GameConfigEntry s now, and finish using the level data from config overall otherwise, and then will see in 2 days if you have had time and ideas 🙂 or if i get that done earlier i'll try to look into such sheet merging in the config builder too
Claude made me a Google Sheets script which combines all hotspots into a single sheet 🙂
.. adding prefix of the level
to the name column, as they would not be unique otherwise
hey @Jurjen a different thing, is it possible to hook a post-process function to the config builder? i'm supposing so.. i didn't find clues in the docs but i'll check the builder code, perhaps i'll find a place there, i'll get to this tomorrow i think
at least in one of our cases that would be after the config is built or at least it needs having the config data available, one of the unity side devs made it so that he creates prefabs using data in config too
ofc we can make some own button that first executes config build and then does the post-processing, but hooking post-stuff to the builder might be handy
j
There's a few different ways to do postprocessing, however we game configs can also be built outside of Unity (e.g. through the dashboard), so creating prefabs might not always be possible. The easiest way would be to do something like:
Copy code
cs
public class GameConfigBuild : GameConfigBuildTemplate<SharedGameConfig>
{
        protected override async Task BuildAsync(IGameConfigBuilder<SharedGameConfig> shared, IGameConfigBuilder<ServerGameConfigBase> server)
        {
            await base.BuildAsync(shared, server);

            // modify game config here...
            // shared.RawOutput
        }
}
You can also do postprocessing when the game config is loaded by overriding
SharedGameConfigBase.OnLoaded
, but that's also called at runtime.
You can also do this during config build by doing something like:
Copy code
cs

        SpreadsheetContent MergeItems<TKey, TInfo>(VariantConfigItem<TKey, TInfo> a, VariantConfigItem<TKey, TInfo> b)
        {
            // left as exercise to the reader
            return null;
        }
        
        ConfigEntryBuilder ItemMergeEntryBuilder<TKey, TInfo>(string entryName, string sourceName1, string sourceName2) where TInfo : class, IGameConfigData<TKey>, new()
        {
            IGameConfigSourceData source1 = null;
            IGameConfigSourceData source2 = null;
            return new ConfigEntryBuilder()
            {
                FetchDataAsync = () =>
                {
                    source1 = FetchSourceData(sourceName1, BuildParametersBase.DefaultSource);
                    source2 = FetchSourceData(sourceName2, BuildParametersBase.DefaultSource);
                    return Task.CompletedTask;
                },
                BuildAsync = async (builder) =>
                {
                    List<VariantConfigItem<TKey, TInfo>> items = new List<VariantConfigItem<TKey, TInfo>>();
                    items.AddRange(builder.ParseLibrarySheetItems<TKey, TInfo>(entryName, (SpreadsheetContent)await source1.Get()));
                    items.AddRange(builder.ParseLibrarySheetItems<TKey, TInfo>(entryName, (SpreadsheetContent)await source2.Get()));
                    builder.AssignLibraryBuildResult(entryName, MergeItems(items), null);
                }
            };
        }

        protected override ConfigEntryBuilder? GetEntryBuilder(Type configType, string entryName)
        {
            if (configType == typeof(SharedGameConfig) && entryName == "MyLibrary")
                return ItemMergeEntryBuilder<TKey, TInfo>(entryName, "MyLibrarySheet1", "MyLibrarySheet2");

            return base.GetEntryBuilder(configType, entryName);
        }
If you first need to fetch another sheet to figure out the names, you can do something similar to how it's currently fetching the source1 and source 2