Quick feedback for WebGL Cloud Build, and why I can't use it

Hello,

Cloud Build is such an awesome feature. But the main thing holding me back from using it, is not using WebGL Templates. I just need to run a little bit of JavaScript outside of the app. The other thing I would really like is to leave the resolution (optionally) at a set resolution, instead of letting it start tiny and having the user select another resolution. Lastly, (optionally) replacing the share link with every new build would be perfect. This way, I don’t have to send out new links every time. I am totally okay with not letting a user go back to view old builds.

In summation, please:

  • Allow WebGL Templates for Cloud Builds
  • Allow a pre-set resolution for WebGL Cloud Builds
  • Allow share links to be constant, always showing the latest builds

Thanks :slight_smile: :slight_smile: :slight_smile:

+1

Is the concern that WebGL Templates could cause security concerns when previewing? In that case, I’d still love to have a packaged up custom WebGL Template option so that we can deploy it.

We have set up WebGL templates in cloud build by “reverse engineering” how Unity internally creates the template.
Unfortunately, i wasn’t able to get permissions for sharing this code.

You can try to do what i did on your own:

  1. Set up a post build method that will execute for your WebGL build.
  2. Use Reflector or any other .NET decompiler to see how Unity internally processes and creates the template (replaces macros inside, etc).
  3. In your post build method, perform the same logic and modify the index.html and any other required files to use the template you want.

Let me know if you need any help. I will try to ping my employers again on this, to see if we can get the clearance to share this (short) but useful code.

1 Like

After looking for a solution online but still getting nowhere, I decided to look into writing a workaround myself.
Too bad Liortal was not willing to share his code since that would have saved me a half day of work.
But sharing is caring, so here’s my solution:

Just put this code anywhere in your project (Preferrably under an /Editor folder) and don’t forget to replace the part with your actual template name.

Although this solution works for now, I still hope Unity will remove this limitation and let developers make the full use of their cloud build system.

#if UNITY_EDITOR && UNITY_WEBGL
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.Callbacks;
using UnityEditor;
using System.IO;
using System;
using System.Text.RegularExpressions;
using System.Linq;

public class PostProcessWebGL
{
    //The name of the WebGLTemplate. Location in project should be Assets/WebGLTemplates/<YOUR TEMPLATE NAME>
    const string __TemplateToUse = "<YOUR TEMPLATE NAME>";

    [PostProcessBuild]
    public static void ChangeWebGLTemplate(BuildTarget buildTarget, string pathToBuiltProject)
    {
        if (buildTarget != BuildTarget.WebGL) return;


        //create template path
        var templatePath = Paths.Combine(Application.dataPath, "WebGLTemplates", __TemplateToUse);

        //Clear the TemplateData folder, built by Unity.
        FileUtilExtended.CreateOrCleanDirectory(Paths.Combine(pathToBuiltProject, "TemplateData"));

        //Copy contents from WebGLTemplate. Ignore all .meta files
        FileUtilExtended.CopyDirectoryFiltered(templatePath, pathToBuiltProject, true, @".*/\.+|\.meta$", true);

        //Replace contents of index.html
        FixIndexHtml(pathToBuiltProject);
    }

    //Replaces %...% defines in index.html
    static void FixIndexHtml(string pathToBuiltProject)
    {
        //Fetch filenames to be referenced in index.html
        string
            webglBuildUrl,
            webglLoaderUrl;
      
        if (File.Exists(Paths.Combine(pathToBuiltProject, "Build", "UnityLoader.js")))
        {
            webglLoaderUrl = "Build/UnityLoader.js";
        }
        else
        {
            webglLoaderUrl = "Build/UnityLoader.min.js";
        }

        string buildName = pathToBuiltProject.Substring(pathToBuiltProject.LastIndexOf("/") + 1);
        webglBuildUrl = string.Format("Build/{0}.json", buildName);

        //webglLoaderUrl = EditorUserBuildSettings.development? "Build/UnityLoader.js": "Build/UnityLoader.min.js";
        Dictionary<string, string> replaceKeywordsMap = new Dictionary<string, string> {
                {
                    "%UNITY_WIDTH%",
                    PlayerSettings.defaultWebScreenWidth.ToString()
                },
                {
                    "%UNITY_HEIGHT%",
                    PlayerSettings.defaultWebScreenHeight.ToString()
                },
                {
                    "%UNITY_WEB_NAME%",
                    PlayerSettings.productName
                },
                {
                    "%UNITY_WEBGL_LOADER_URL%",
                    webglLoaderUrl
                },
                {
                    "%UNITY_WEBGL_BUILD_URL%",
                    webglBuildUrl
                }
            };

        string indexFilePath = Paths.Combine(pathToBuiltProject, "index.html");
       Func<string, KeyValuePair<string, string>, string> replaceFunction = (current, replace) => current.Replace(replace.Key, replace.Value);
        if (File.Exists(indexFilePath))
        {
           File.WriteAllText(indexFilePath, replaceKeywordsMap.Aggregate<KeyValuePair<string, string>, string>(File.ReadAllText(indexFilePath), replaceFunction));
        }

    }

    private class FileUtilExtended
    {
      
        internal static void CreateOrCleanDirectory(string dir)
        {
            if (Directory.Exists(dir))
            {
                Directory.Delete(dir, true);
            }
            Directory.CreateDirectory(dir);
        }

        //Fix forward slashes on other platforms than windows
        internal static string FixForwardSlashes(string unityPath)
        {
            return ((Application.platform != RuntimePlatform.WindowsEditor) ? unityPath : unityPath.Replace("/", @"\"));
        }



        //Copies the contents of one directory to another.
       public static void CopyDirectoryFiltered(string source, string target, bool overwrite, string regExExcludeFilter, bool recursive)
        {
            RegexMatcher excluder = new RegexMatcher()
            {
                exclude = null
            };
            try
            {
                if (regExExcludeFilter != null)
                {
                    excluder.exclude = new Regex(regExExcludeFilter);
                }
            }
            catch (ArgumentException)
            {
               UnityEngine.Debug.Log("CopyDirectoryRecursive: Pattern '" + regExExcludeFilter + "' is not a correct Regular Expression. Not excluding any files.");
                return;
            }
            CopyDirectoryFiltered(source, target, overwrite, excluder.CheckInclude, recursive);
        }
       internal static void CopyDirectoryFiltered(string sourceDir, string targetDir, bool overwrite, Func<string, bool> filtercallback, bool recursive)
        {
            // Create directory if needed
            if (!Directory.Exists(targetDir))
            {
                Directory.CreateDirectory(targetDir);
                overwrite = false;
            }

            // Iterate all files, files that match filter are copied.
            foreach (string filepath in Directory.GetFiles(sourceDir))
            {
                if (filtercallback(filepath))
                {
                    string fileName = Path.GetFileName(filepath);
                    string to = Path.Combine(targetDir, fileName);

                  
                    File.Copy(FixForwardSlashes(filepath),FixForwardSlashes(to), overwrite);
                }
            }

            // Go into sub directories
            if (recursive)
            {
                foreach (string subdirectorypath in Directory.GetDirectories(sourceDir))
                {
                    if (filtercallback(subdirectorypath))
                    {
                        string directoryName = Path.GetFileName(subdirectorypath);
                       CopyDirectoryFiltered(Path.Combine(sourceDir, directoryName), Path.Combine(targetDir, directoryName), overwrite, filtercallback, recursive);
                    }
                }
            }
        }

        internal struct RegexMatcher
        {
            public Regex exclude;
            public bool CheckInclude(string s)
            {
                return exclude == null || !exclude.IsMatch(s);
            }
        }

    }

    private class Paths
    {
        //Combine multiple paths using Path.Combine
        public static string Combine(params string[] components)
        {
            if (components.Length < 1)
            {
                throw new ArgumentException("At least one component must be provided!");
            }
            string str = components[0];
            for (int i = 1; i < components.Length; i++)
            {
                str = Path.Combine(str, components[i]);
            }
            return str;
        }
    }

}

#endif

Good luck!

1 Like