2
\$\begingroup\$

I have uploaded a game on Steam. I know steam deals the DLC as separate depots; I have also watched and read the documentation on steam documentation page.

The problem is how will I deal the DLC in my game (the coding part)? Is depot a full game and there is download option in the game? Please guide me about it as I am new in steam game development. What would be the global architecture of both my game and the DLC?

\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Short answer: Use AssetBundle plus authoring tools like Asset Bundle Browser (which simplify creating asset bundles)

Here is the longer answer: One could use AssetBundle or the new Addressable system. But here is the catch: the newer Addressable doesn't mean it's better. Let's see the use case.

Steam DLC

As OP has noted, steam DLC works by either just adding a new depot into user download, or no additional file download at all but use it the DLC for a license check.

  • License check (without file download): With a simple IF check with Steam API SteamApps.BIsDlcInstalled( XX ) to see if current logged in user has purchased DLC XX? This could be used in scenario such as:

    • Use Case 1: An exclusive skin for a tank multiplayer game. You would have to include this skin data with the game for all players, most likely with base game download, so it renders the DLC owned by other people correctly. The DLC just enables the owner to wear it.
    • Use Case 2: Or maybe an offline game, that unlock certain item, functionality, maps, which already exist in game data/code. (A bit side topic: some player might consider this a bad practice since they know that content already there in their HDD yet they must pay an extra to unlock it, well, depends on your choice, no one stop you as a developer from doing it though)
    • You can find sample code easily by searching the term SteamApps.BIsDlcInstalled, like this one I found: http://www.mwmdragon.com/2019/03/31/prep-for-a-steam-release/
  • With file download: This is suitable for maps, extra mission pack, etc. And this answer will cover more on this specific type of DLC. More on down below!

    • Practically, even the game sees the extra file downloaded from DLC depots, you should still check for SteamApps.BIsDlcInstalled( XX ) anyway, else player could just copy files from elsewhere and paste in the game folder to enable the DLC. which is probably weird.
    • Again, this wont safe guard against real hack or pirate, but we just make sure that we do thing in proper way :)

Let's look at Addressable first

This is more suitable for a mobile game which updates/distributes content regularly. It works by maintaining/downloading a catalog file, which serves as an index file to other asset bundles. Update and download them smartly by comparing local version and dependencies.

But this is not suitable to our Steam DLC case at all. Let's say you want to sell a new voice pack. For the addressable approach you would need a way to update the catalog file (changing/updating the main app, or host it on our custom server) which defeats all purpose for traditional DLC. Also Steam already handles the download for us. This simplifies things a lot, because the game only has to check for local files.

Making a DLC asset bundle

I used Asset Bundle Browser for this. Despite its name, it also helps with creating asset bundles by simple drag and drop. I wont get into details, because the tool is very easy to use. It is far easier than dealing with Addressable.

enter image description here

The final result will be an asset bundle file sitting in ProjectFolder/AssetBundles beside ProjectFolder/Assets. Which is good, because we are making DLC. We dont want to include them with build from start, nor in StreamingAssets.

enter image description here

The file has no extension by default. Asset bundles do not require extensions, but you could append them if you like. The .manifest file is for editor side reporting. Ignore it when copying to production.

Enumerating extra files

This example just uses file IO to iterate files in a specific location, and then uses the AssetBundle API to load and peek them. It is now up to you how to use or manage the life cycle of these asset bundles. (unload them to release memory)

using System.IO;
using UnityEngine;

public class DlcTest : MonoBehaviour
{
    void Start( )
    {
        // Application.dataPath resolved to
        // - Editor: "ProjectPath/Assets"
        // - Build: "GameBuild/Game_Data" (note that GameBuild/Game.exe would be game executable)
        // I would like to position my DLC in
        // - Editor: "ProjectPath/AssetBundles/PlatformName/"
        // - Build: "GameBuild/AssetBundles/PlatformName/"
        // Of course you could change "AssetBundles" path to something suitable
        string extraPath = Path.Combine( Application.dataPath, "../", "AssetBundles", _ConvertPlatformToBuildTargetName( Application.platform ) );
        string voicePackPath = Path.Combine( extraPath, "voice" );

        // For example this would be
        // - Editor: "ProjectPath/AssetBundles/StandaloneWindows64/voice"
        voicePackPath = Path.GetFullPath( voicePackPath );
        Debug.Log( $"Get voice pack at {voicePackPath}" );

        // In my workflow, I build asset bundle without file extension, so I search all file
        foreach( string file in Directory.EnumerateFiles( voicePackPath ) )
        {
            if( Path.HasExtension( file ) ) continue; // Ignore something else, could be ".manifest" files.

            // For example this would be
            // - Editor: "ProjectPath/AssetBundles/StandaloneWindows64/voice/pack1" (file without extension)
            AssetBundle ab = AssetBundle.LoadFromFile( file );

            // Find ".yaml" info file for this voice pack
            // This would be my own stuff, you replace this with your own stuff
            TextAsset[] allText = ab.LoadAllAssets<TextAsset>( );
            foreach( TextAsset t in allText )
                Debug.Log( $"Found text asset, {t.name}" );

            WaveBankData[] allWb = ab.LoadAllAssets<WaveBankData>( ); // This is my ScriptableObject
            foreach( WaveBankData wb in allWb )
                Debug.Log( $"Found wavebank, {wb.name}" );

            // Normally you would let AssetBundle live throughout its usage, 
            // Probably managed via some manager.
            // I will call this just for example to release memory.
            ab.Unload( true );
        }
    }

    string _ConvertPlatformToBuildTargetName( RuntimePlatform p )
    {
        // Depends on how you name your output platform for asset bundles
        switch( p )
        {
            case RuntimePlatform.WindowsEditor:
            case RuntimePlatform.WindowsPlayer:
                return "StandaloneWindows64";
        }

        return "Unknown";
    }

}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Closing trivial: I'm answering this due to this question appears very early on search query such as Unity steam DLC, I have landed on this too, with blank answer, now 2 days later of trial and error here is my take. I already lost hope in main SO site, with many evil shenanigans golden badger (pun intended) aggressively hammer good questions, BUT I still have very high hope for gamedev SE. I took my time writing an answer, hope this could save someone else time. \$\endgroup\$
    – Wappenull
    Feb 11, 2021 at 8:30
  • \$\begingroup\$ "This way you just check with the Steam API SteamApps.BIsDlcInstalled. This one is trivial, we wont get into it." I can't figure this one out - I'm not advanced enough in these things to find this trivial. Can you elaborate? \$\endgroup\$ Apr 21, 2021 at 17:36
  • \$\begingroup\$ @MaciejJonasz SteamApps.BIsDlcInstalled is just a function taking DLC id to check if current user owns it. You app needs to know those DLC id in advance, whether hardcode it or some way. Just google SteamApps.BIsDlcInstalled and you will found various example. (Ironically including this SE question lol) I found one interesting: mwmdragon.com/2019/03/31/prep-for-a-steam-release \$\endgroup\$
    – Wappenull
    Apr 23, 2021 at 10:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .