SDK Migration: v0.0.1 → v0.9.0

Beamable SDK migration

Overview

The purpose of this guide is to demonstrate everything needed for game makers to migrate an existing Beamable project to the latest version of the "Beamable SDK for Unity".

Ensure that the Unity project is version 2020 LTS to 2022 LTS.

Steps

Follow these steps to get started:

The majority of the effort will be in the following steps.

Upgrade Beamable SDK

🚧

Backup Your Project!

Ensure that you have a backup of your project, either through source control or otherwise

StepDetail
1. Log out of Beamable
• Open Unity
• Go to the Beamable Toolbox
• Hit the "Log Out" button in the top right
2. Using the Package Manager, uninstall the Beamable package
• At this point, the project will appear to be broken, because all of the DisruptorBeam references won’t exist. Don’t worry!
3. Update your manifest.json file• Open your Packages/manifest.json file located in the project directory
• In the “scopedRegistries” section, change the scope from “com.disruptorbeam” to “com.beamable”
• Optionally, you may want to keep your scoped registries tidy and update the name of the registry from “DisruptorBeam”, to “Beamable”
• Below is an example “scopedRegistries” section in the manifest.json file
"scopedRegistries": [
  {
   "name": "Beamable",
   "url": "https://nexus.beamable.com/nexus/content/repositories/unity",
   "scopes": [ "com.beamable" ]
  }
],
StepDetail
4. Reinstall the Beamable package
• Using the package manager, reinstall the Beamable package
• The version should be at least 0.9.0
• You may need to refresh your package manager a few times before the new Beamable package becomes viewable

❗️

Do not attempt to log in to Beamable yet!

StepDetail
5. Restart Unity and Reimport all packages
• Close and reopen Unity
• Reimport all the packages

🚧

Using Assembly Definitions?

If you're using assembly definitions, make sure to reference the following Beamable assemblies:

  • Unity.Beamable.Runtime.Common
  • Unity.Beamable
  • Beamable.SmallerJSON
  • Beamable.Platform
  • Beamable.Service
  • Beamable.Config
StepDetail
6. Fix Namespace Compiler Bugs
• At this point, you’ll see many namespace compiler bugs
• If you are using Visual Studio or Rider, the IDE should be able to automatically resolve each namespace error for you using a Quick Hints or Intellisense feature
• If you aren’t using one of those IDEs, you can do the process manually. Any namespace mention of DisruptorEngine should be replaced with Beamable
• Below are a few examples

DisruptorEngine used to expose most of its functionality through the DisruptorEngine.Instance singleton. The style is the same, but now we’ve refactored the namespace to Beamable.API.Instance. Most of the time you’ll need to include a using statement for Beamable, and you’ll be able to write it as API.Instance instead. Either will work.

Old WayNew Way
DisruptorEngine.Instance.Then(de => { });Beamable.API.Instance.Then(b => { });

-- or --

using Beamable; // in your using statements
API.Instance.Then(b => { }); // later on...

The DisruptorBeam namespace has been replaced with the namespace Beamable. In most cases, you’ll want two using statements at the top of each file that used to reference DisruptorEngine, and now references Beamable.

Old WayNew Way
using DisruptorBeam;using Beamable;
using Beamable.Common;

If you are using a Subscribe() call on a Beamable service like Inventory, Announcements, Calendars, Mail, Content, Commerce, etc, you may see an error in the form of:

b.InventoryService.Subscribe(_ => {});
'InventoryService' does not contain a definition for 'Subscribe' and no accessible extension method 'Subscribe' accepting a first argument of type 'InventoryService' could be found (are you missing a using directive or an assembly reference?)

In this case, Make sure you’ve added the Beamable using statement to the top of the broken file. The Subscribe method has been moved to an extension method available in the Beamable namespace:

Old WayNew Way
// Not applicableusing Beamable;

Any namespace references to DisruptorBeam.X.Y.Z have now been refactored to Beamable.X.Y.Z. In some cases, like content objects, the namespace has moved into a Common sub namespace. It is highly recommended that you use an IDE to assist in finding the new using statements.

Old WayNew Way
using DisruptorBeam.Content;
using DisruptorBeam.Stats;
using DisruptorBeam.Modules.Console;
using Beamable.Common.Inventory;
using Beamable.Stats;
using Beamable.Console;

Some classes have been renamed from DisruptorEngineXYZ to BeamableXYZ, or more likely, just XYZ.

Old WayNew Way
public DisruptorEngineConsole console;public ConsoleFlow console;

If you are using the Beamable Commerce feature, you’ll have had to include a custom Payment Delegate file in your code. You can replace that file with this Gist or see the code block below:

using Beamable.Common;
using Debug = UnityEngine.Debug;
#if UNITY_PURCHASING
using System;
using Beamable.Api;
using Beamable.Coroutines;
using Beamable.Platform.SDK;
using Beamable.Api.Payments;
using Beamable.Service;
using Beamable.Spew;
using UnityEngine;
using UnityEngine.Purchasing;

public class PaymentDelegateUnityIAP : MonoBehaviour
{
    private static IStoreController m_StoreController;          // The Unity Purchasing system.
    private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.

    public void Awake()
    {
        if (!ServiceManager.Exists<PaymentDelegate>())
        {
            Debug.Log("Registering UnityIAP Payment Delegate");
            ServiceManager.ProvideWithDefaultContainer<PaymentDelegate>(new PaymentDelegateImpl());
        }
    }

    private class PaymentDelegateImpl : IStoreListener, PaymentDelegate, IServiceResolver<PaymentDelegate>
    {
        static readonly int[] RETRY_DELAYS = { 1, 2, 5, 10, 20 };

        private long _txid;
        private Action<CompletedTransaction> _success;
        private Action<ErrorCode> _fail;
        private Action _cancelled;
        private Promise<Unit> _initPromise = new Promise<Unit>();

#if SPEW_IAP || SPEW_ALL
   /* Time, in seconds, when initialization started */
   private float _initTime;
#endif

        public Promise<Unit> Initialize()
        {
            var platform = ServiceManager.Resolve<PlatformService>();
            platform.Payments.GetSKUs().Then(rsp =>
            {
                var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
                foreach (var sku in rsp.skus.definitions)
                {
                    builder.AddProduct(sku.name, ProductType.Consumable, new IDs()
                    {
                        { sku.productIds.itunes, AppleAppStore.Name },
                        { sku.productIds.googleplay, GooglePlay.Name },
                    });
                }

#if SPEW_IAP
               _initTime = Time.time;
#endif

                // Kick off the remainder of the set-up with an asynchrounous call, passing the configuration
                // and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
                UnityPurchasing.Initialize(this, builder);
            });

            return _initPromise;
        }

        private void ClearCallbacks()
        {
            _success = null;
            _fail = null;
            _cancelled = null;
            _txid = 0;
        }

        public string GetLocalizedPrice(string skuSymbol)
        {
            var product = m_StoreController?.products.WithID(skuSymbol);
            return product?.metadata.localizedPriceString ?? "???";
        }

        public void startPurchase(
            string listingSymbol,
            string skuSymbol,
            Action<CompletedTransaction> success,
            Action<ErrorCode> fail,
            Action cancelled
        )
        {
            StartPurchase(listingSymbol, skuSymbol)
                .Then(tx => success?.Invoke(tx))
                .Error(err => fail?.Invoke(err as ErrorCode));

            if (cancelled != null) _cancelled += cancelled;
        }

        public Promise<CompletedTransaction> StartPurchase(string listingSymbol, string skuSymbol)
        {
            var result = new Promise<CompletedTransaction>();
            _txid = 0;
            _success = result.CompleteSuccess;
            _fail = result.CompleteError;
            if (_cancelled == null) _cancelled = () =>
            { result.CompleteError(
                new ErrorCode(400, GameSystem.GAME_CLIENT, "Purchase Cancelled"));
            };

            ServiceManager.Resolve<PlatformService>().Payments.BeginPurchase(listingSymbol).Then(rsp =>
            {
                _txid = rsp.txid;
                m_StoreController.InitiatePurchase(skuSymbol, _txid.ToString());
            }).Error(err =>
            {
                Debug.LogError($"There was an exception making the begin purchase request: {err}");
                _fail?.Invoke(err as ErrorCode);
            });

            return result;
        }


        // Restore purchases previously made by this customer. Some platforms automatically restore purchases, like Google.
        // Apple currently requires explicit purchase restoration for IAP, conditionally displaying a password prompt.
        public void RestorePurchases()
        {
            // If we are running on an Apple device ...
            if (Application.platform == RuntimePlatform.IPhonePlayer ||
                Application.platform == RuntimePlatform.OSXPlayer)
            {
                // ... begin restoring purchases
                InAppPurchaseLogger.Log("RestorePurchases started ...");

                // Fetch the Apple store-specific subsystem.
                var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
                // Begin the asynchronous process of restoring purchases. Expect a confirmation response in
                // the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
                apple.RestoreTransactions(result => {
                    // The first phase of restoration. If no more responses are received on ProcessPurchase then
                    // no purchases are available to be restored.
                    InAppPurchaseLogger.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
                });
            }
            // Otherwise ...
            else
            {
                // We are not running on an Apple device. No work is necessary to restore purchases.
                InAppPurchaseLogger.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
            }
        }


        //
        // --- IStoreListener
        //
        public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
        {
            // Purchasing has succeeded initializing. Collect our Purchasing references.
            InAppPurchaseLogger.Log("OnInitialized: PASS");

            // Overall Purchasing system, configured with products for this application.
            m_StoreController = controller;
            // Store specific subsystem, for accessing device-specific store features.
            m_StoreExtensionProvider = extensions;

            _initPromise.CompleteSuccess(PromiseBase.Unit);

            RestorePurchases();

#if SPEW_IAP || SPEW_ALL
         var elapsed = Time.time - _initTime;
         InAppPurchaseLogger.LogFormat("Initialization complete, after {0:#,0.000} seconds.", elapsed);
#endif
        }


        public void OnInitializeFailed(InitializationFailureReason error)
        {
            // Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
            InAppPurchaseLogger.Log("OnInitializeFailed InitializationFailureReason:" + error);
        }


        public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
        {
            string rawReceipt;
            if (args.purchasedProduct.hasReceipt)
            {
                var receipt = JsonUtility.FromJson<UnityPurchaseReceipt>(args.purchasedProduct.receipt);
                rawReceipt = receipt.Payload;
                InAppPurchaseLogger.Log($"UnityIAP Payload: {receipt.Payload}");
                InAppPurchaseLogger.Log($"UnityIAP Raw Receipt: {args.purchasedProduct.receipt}");
            }
            else
            {
                rawReceipt = args.purchasedProduct.receipt;
            }

            var transaction = new CompletedTransaction(
                _txid,
                rawReceipt,
                args.purchasedProduct.metadata.localizedPrice.ToString(),
                args.purchasedProduct.metadata.isoCurrencyCode,
                "",
                ""
            );
            FulfillTransaction(transaction, args.purchasedProduct);

            return PurchaseProcessingResult.Pending;
        }

        private void FulfillTransaction(CompletedTransaction transaction, Product purchasedProduct)
        {
            ServiceManager.Resolve<PlatformService>().Payments.CompletePurchase(transaction).Then(_ =>
            {
                m_StoreController.ConfirmPendingPurchase(purchasedProduct);
                _success?.Invoke(transaction);
                ClearCallbacks();
            }).Error(ex =>
            {
                Debug.LogError($"There was an exception making the complete purchase request: {ex}");
                var err = ex as ErrorCode;

                if (err == null)
                {
                    return;
                }

                var retryable = err.Code >= 500 || err.Code == 429 || err.Code == 0;   // Server error or rate limiting or network error
                if (retryable)
                {
                    ServiceManager.Resolve<CoroutineService>().StartCoroutine(RetryTransaction(transaction, purchasedProduct));
                }
                else
                {
                    m_StoreController.ConfirmPendingPurchase(purchasedProduct);
                    _fail?.Invoke(err);
                    ClearCallbacks();
                }
            });
        }

        // Copied from TransactionManager.cs
        private System.Collections.IEnumerator RetryTransaction(CompletedTransaction transaction, Product purchasedProduct)
        {
            // This block should only be hit when the error returned from the request is retryable. This lives down here
            // because C# doesn't allow you to yield return from inside a try..catch block.
            var waitTime = RETRY_DELAYS[Math.Min(transaction.Retries, RETRY_DELAYS.Length - 1)];
            InAppPurchaseLogger.Log($"Got a retryable error from platform. Retrying complete purchase request in {waitTime} seconds.");

            // Avoid incrementing the backoff if the device is definitely not connected to the network at all.
            // This is narrow, and would still increment if the device is connected, but the internet has other problems

            if (Application.internetReachability != NetworkReachability.NotReachable)
            {
                transaction.Retries += 1;
            }

            yield return new WaitForSeconds(waitTime);

            FulfillTransaction(transaction, purchasedProduct);
        }


        public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
        {
            // A product purchase attempt did not succeed. Check failureReason for more detail. Consider sharing
            // this reason with the user to guide their troubleshooting actions.
            InAppPurchaseLogger.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
            var platform = ServiceManager.Resolve<PlatformService>();
            var reasonInt = (int) failureReason;
            if (failureReason == PurchaseFailureReason.UserCancelled)
            {
                platform.Payments.CancelPurchase(_txid);
                _cancelled?.Invoke();
            }
            else
            {
                platform.Payments.FailPurchase(_txid, product.definition.storeSpecificId + ":" + failureReason);
                var errorCode = new ErrorCode(reasonInt, GameSystem.GAME_CLIENT, failureReason.ToString() + $" ({product.definition.storeSpecificId})");
                _fail?.Invoke(errorCode);
            }

            ClearCallbacks();
        }

        #region ServiceResolver
        void IServiceResolver.OnTeardown()
        {
            m_StoreController = null;
            m_StoreExtensionProvider = null;
        }

        bool IServiceResolver<PaymentDelegate>.CanResolve() => true;
        bool IServiceResolver<PaymentDelegate>.Exists() => true;
        PaymentDelegate IServiceResolver<PaymentDelegate>.Resolve() => this;
        #endregion
    }
}

[Serializable]
public class UnityPurchaseReceipt
{
    public string Store;
    public string TransactionID;
    public string Payload;
}

#endif  // UNITY_PURCHASING

If you are using the Beamable Facebook sign-in feature, you’ll have had to create a Facebook wrapper class to connect Facebook and Beamable. You can replace that file with this Gist or see the code block below:

using System.Collections.Generic;
using Beamable.AccountManagement;
using Beamable.Api;
using Beamable.Common.Api.Auth;
using Facebook.Unity;
using UnityEditor;
using UnityEngine;
public class FacebookWrapper : MonoBehaviour
{
    private AccountManagementSignals _signals;
    private bool _hasAttachedListener;
    private void Update()
    {
        if (!_hasAttachedListener && _signals.ThirdPartyLoginAttempted != null)
        {
            _signals.ThirdPartyLoginAttempted.AddListener(StartFacebookLogin);
            _hasAttachedListener = true;
        }
    }
    // Awake function from Unity's MonoBehavior
    void Awake ()
    {
        _signals = gameObject.AddComponent<AccountManagementSignals>();
        if (!FB.IsInitialized) {
            // Initialize the Facebook SDK
            FB.Init(InitCallback, OnHideUnity);
        } else {
            // Already initialized, signal an app activation App Event
            FB.ActivateApp();
        }
    }
    private void InitCallback ()
    {
        if (FB.IsInitialized) {
            // Signal an app activation App Event
            FB.ActivateApp();
            // Continue with Facebook SDK
            // ...
            Debug.Log("FB Didnt die");
        } else {
            Debug.Log("Failed to Initialize the Facebook SDK");
        }
    }
    private void OnHideUnity (bool isGameShown)
    {
        if (!isGameShown) {
            // Pause the game - we will need to hide
            Time.timeScale = 0;
        } else {
            // Resume the game - we're getting focus again
            Time.timeScale = 1;
        }
    }
    private void AuthCallback (ThirdPartyLoginPromise promise, ILoginResult result) {
        if (!string.IsNullOrEmpty(result.Error))
        {
            promise.CompleteError(new ErrorCode(0, GameSystem.GAME_CLIENT, "Facebook Error", result.Error));
            return;
        }
        if (FB.IsLoggedIn) {
            // AccessToken class will have session details
            var aToken = PlayerSettings.Facebook.Unity.AccessToken.CurrentAccessToken;
            // Print current access token's User ID
            Debug.Log(aToken.UserId);
            // Print current access token's granted permissions
            foreach (string perm in aToken.Permissions) {
                Debug.Log(perm);
            }
            promise.CompleteSuccess(new ThirdPartyLoginResponse()
            {
                AuthToken = aToken.TokenString
            });
        } else {
            Debug.Log("User cancelled login");
            promise.CompleteSuccess(ThirdPartyLoginResponse.CANCELLED);
        }
    }
    public void StartFacebookLogin(ThirdPartyLoginPromise promise)
    {
        if (promise.ThirdParty != AuthThirdParty.Facebook) return;
        var perms = new List<string>(){"public_profile", "email"};
        FB.LogInWithReadPermissions(perms, result => AuthCallback(promise, result));
    }
}

📘

Calendars Service

The Calendars service has been moved to an Experimental namespace

A few content field names have changed.
The Icon field on CurrencyContent and ItemContent has been renamed to icon.

Old WayNew Way
var icon = await currency.Icon.LoadAssetAsync().Task;var icon = await currency.icon.LoadAssetAsync().Task;
write_selfwriteSelf
start_datestartDate
end_dateendDate
partition_sizepartitionSize
StepDetail
7. Migrate data from Assets/DisruptorEngine folder to Assets/Beamable folder• In order to move all the data from the old folder to the new one, we’ve created a helper script to automate the process below:

❗️

Use Our Script!

Do not attempt to manually move the contents of the old folder

Create a temporary code file in your /Assets folder called BeamableMigrationHelper.cs. Paste the code from this Gist or see the code block below:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.VersionControl;
using UnityEngine;

public class BeamableMigrationHelper : EditorWindow
{
   [MenuItem(
      "Window/Beamable/Migration Tool",
      priority = 0)]
   public static void RunTool(){
      Debug.Log($"Starting migration tool.... {BEAMABLE_FOLDER}");

      RenameDisruptorEngineToBeamable();
      FixContentScriptGuids();


      AssetDatabase.Refresh();
   }

   private static readonly string DISRUPTOR_ENGINE_FOLDER = Path.Combine(Application.dataPath, "DisruptorEngine");
   private static readonly string BEAMABLE_FOLDER = Path.Combine(Application.dataPath, "Beamable");

   private static readonly ContentGuidFixData[] GuidFixes = new[]
   {
      new ContentGuidFixData
      {
         Name = "Item",
         NewGuid = "4e65df06d8f8e499788b3ed5b359d54a",
         OldGuid = "6e27788e2a314236a140dc4a409b9249"
      },
      new ContentGuidFixData
      {
         Name = "Currency",
         NewGuid = "7071e886d4789435d9ad361e916b2207",
         OldGuid = "5531181e6a08e4d6882d1e0bf64aeb5e"
      },
      new ContentGuidFixData
      {
         Name = "Announcements",
         NewGuid = "8b8612187b1a246e5b691c90486deb34",
         OldGuid = "ec1a162a275224c81b4dc66174e0abfb"
      },
      new ContentGuidFixData
      {
         Name = "Calendars",
         NewGuid = "0c222bf6b195348e5be37e5cf0169f08",
         OldGuid = "485745a1357640d0999ad440992934fb"
      },
      new ContentGuidFixData
      {
         Name = "Listings",
         NewGuid = "fd68497a79b464de0b4e09ab2d5a5bbf",
         OldGuid = "840eefedb06f44a35ba9ffd558ba8840"
      },
      new ContentGuidFixData
      {
         Name = "SKU",
         NewGuid = "b5bfeeca7cf3246b3a89418e98719de7",
         OldGuid = "8813c741626bd459797986cbf75812db"
      },
      new ContentGuidFixData
      {
         Name = "Store",
         NewGuid = "a658558ea86c749979b310e828e3892e",
         OldGuid = "4e4344ba97e6d4cbeb1a7d24ad6f778b"
      },
      new ContentGuidFixData
      {
         Name = "Leaderboard",
         NewGuid = "ebf776d7c945048daab1e231d27dcdeb",
         OldGuid = "7bd5890ede5a415ba93437f0d0ce7914"
      },
      new ContentGuidFixData
      {
         Name = "Tournaments",
         NewGuid = "6da5f82a5df104fd4b67a842fafa5c5a",
         OldGuid = "1d36333db684547d09c3cbbfff05d3e1"
      },
      new ContentGuidFixData
      {
         Name = "Events",
         NewGuid = "11f962489e5a44d7c877b4a5b71f4d0e",
         OldGuid = "e62f0767b71f47740b2ab2b9a03aa03f"
      },
      new ContentGuidFixData
      {
         Name = "GameType",
         NewGuid = "404f8a85a397642f5b56ce1f2d8cebea",
         OldGuid = "3e764f5e52bc4814ad80326864ba4aff"
      },
      new ContentGuidFixData
      {
         Name = "Email",
         NewGuid = "b5d38e7e955cc47f9933ec9a3a1e2c5a",
         OldGuid = "3b9f329a7c08143f19cad1d65673e105"
      }
   };

   static void RenameDisruptorEngineToBeamable()
   {
      if (!Directory.Exists(DISRUPTOR_ENGINE_FOLDER))
      {
         Debug.Log("folder already renamed");
         return;
      }
      Debug.Log("renaming folder...");
      try
      {
         DirectoryCopy(DISRUPTOR_ENGINE_FOLDER, BEAMABLE_FOLDER, true);
      }
      catch (Exception ex)
      {
         Debug.LogError($"migration failed at rename folder step. error=[{ex.Message}] stack=[{ex.StackTrace}]");
         Debug.LogError(ex);
         throw;
      }
   }

   static void FixContentScriptGuids()
   {
      Directory.CreateDirectory(BEAMABLE_FOLDER);
      Debug.Log("checking content files");
      var filePaths = Directory.GetFiles(BEAMABLE_FOLDER, "*.asset", SearchOption.AllDirectories);

      Debug.Log($"found {filePaths.Length} content files");
      var modifiedFiles = 0;
      foreach (var filePath in filePaths)
      {
         var text = File.ReadAllText(filePath);

         var modifications = new HashSet<string>();
         foreach (var fix in GuidFixes)
         {
            if (text.Contains(fix.OldGuid))
            {
               text = text.Replace(fix.OldGuid, fix.NewGuid);
               modifications.Add(fix.Name);
            }
         }

         if (modifications.Count > 0)
         {
            modifiedFiles++;
            File.WriteAllText(filePath, text);
            Debug.Log($"Fixed content file fixes=[{string.Join(",",modifications)}] file=[{filePath}]");
         }
      }
      Debug.Log($"modified {modifiedFiles} files");
   }

   struct ContentGuidFixData
   {
      public string Name,OldGuid, NewGuid;
   }

   private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
   {
      // Get the subdirectories for the specified directory.
      DirectoryInfo dir = new DirectoryInfo(sourceDirName);

      if (!dir.Exists)
      {
         throw new DirectoryNotFoundException(
            "Source directory does not exist or could not be found: "
            + sourceDirName);
      }

      DirectoryInfo[] dirs = dir.GetDirectories();

      // If the destination directory doesn't exist, create it.
      Directory.CreateDirectory(destDirName);

      // Get the files in the directory and copy them to the new location.
      FileInfo[] files = dir.GetFiles();
      foreach (FileInfo file in files)
      {
         string tempPath = Path.Combine(destDirName, file.Name);
         var isUsingVersionControl = Provider.enabled && Provider.isActive;
         if (isUsingVersionControl)
         {
            var checkoutTargetFileTask = Provider.Checkout(tempPath, CheckoutMode.Both);
            checkoutTargetFileTask.Wait();
            if (checkoutTargetFileTask.success)
            {
               Debug.LogWarning($"Unable to checkout file {tempPath}");
            }
            var checkoutSourceFileTask = Provider.Checkout(file.Name, CheckoutMode.Both);
            checkoutSourceFileTask.Wait();
            if (checkoutSourceFileTask.success)
            {
               Debug.LogWarning($"Unable to checkout file {file.Name}");
            }
         }
         file.CopyTo(tempPath, true);
      }

      // If copying subdirectories, copy them and their contents to new location.
      if (copySubDirs)
      {
         foreach (DirectoryInfo subdir in dirs)
         {
            string tempPath = Path.Combine(destDirName, subdir.Name);
            DirectoryCopy(subdir.FullName, tempPath, copySubDirs);
         }
      }
   }
}

Now, in Unity, you should see a menu option called Migration Tool. Click it. Monitor the logs to make sure no errors are thrown, except for errors of the form “Asset could not be unloaded”.

970

Use our migration tool to migrate data between folders

Verify Success

StepDetail
1. Verify Logging in• Log in to the Beamable Toolbox with your existing credentials
2. Verify Content• Verify that your content is intact from the Content Manager window
• You’ll notice that the Content Manager looks very different - we’ve completely overhauled the experience

🚧

Delete Those Unnecessary Files

Once you’ve verified that everything is working again, please delete the BeamableMigrationHelper.cs file, and also delete the /Assets/DisruptorEngine folder.

📘

Having Issues?

If you have migration issues, please reach out to us on whatever channel makes the most sense (i.e. Slack, Discord, or email [email protected]). We also have a diagnostic tool that generates a diagnostic file which you can then send to us that will greatly assist us in debugging any issues that arise

1402

Send us diagnostic info to help us debug any issues that arise