Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Resolved webGL Template

Discussion in 'Unity Build Automation' started by Riolis, Oct 23, 2015.

  1. Riolis

    Riolis

    Joined:
    Aug 23, 2014
    Posts:
    8
    It seems like the cloud build doesn't use my selected template for building, and I can't seem find the setting for it to select it anywhere.

    Would be great if someone knows how to set this up could advice me. Else I would have to move tons of html and js code to jslib or eval it at the start scene which is not fun thing to do.

    Thanks.
     
  2. dannyd

    dannyd

    Unity Technologies

    Joined:
    Jun 3, 2014
    Posts:
    789
    This is not something we plan to support at the moment. We always build with the default template so that we can host the html properly and so the styling is consistent with the rest of our site, and the window sizing acts correctly. If your project requires more complex interaction with the page, unfortunately your only real option is to self host builds.

    Thinking about how this might work in the future - what kinds of things are you doing in your template?
     
    tonialatalo likes this.
  3. Riolis

    Riolis

    Joined:
    Aug 23, 2014
    Posts:
    8
    nothing fancy, just some bootstrap modal and input="file" forms since you can't seem to trigger it from unity itself (Being block by all browsers popup blocker by default), large file upload/download script, drag and drop file js event, parse.com js sdk and its custom helper script currently.

    In a nutshell we are experimenting with more of a commercial cms type of system rather then games (and to see how far we can go with it, and hoping to make a better user experience), which unfortunately needs more interaction with the pages.
     
    tonialatalo likes this.
  4. jinxed_byte

    jinxed_byte

    Joined:
    Mar 29, 2014
    Posts:
    17
    We would also like to select a WebGL template and connect to the javascript code. Especially, we are working on integration of Parse.com and PeerJS.

    Hope you can make the template available soon.
     
    CNicoF and tonialatalo like this.
  5. samurai926

    samurai926

    Joined:
    Jun 30, 2014
    Posts:
    14
    +1 for option to use custom template during cloudbuild. Ours has a custom loading script and expands the canvas to full screen.
     
    CNicoF and tonialatalo like this.
  6. Kmausser

    Kmausser

    Joined:
    Feb 17, 2016
    Posts:
    23
    Why aren't custom templates supported yet?
     
    chaosmaker likes this.
  7. PlayItSafe_Fries

    PlayItSafe_Fries

    Joined:
    Jan 17, 2018
    Posts:
    11
    After looking for a solution online but still getting nowhere, I decided to look into writing a workaround myself.

    Just put this code anywhere in your project (Preferrably under an /Editor folder) and don't forget to replace the <YOUR TEMPLATE NAME> 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.

    Code (CSharp):
    1. #if UNITY_EDITOR && UNITY_WEBGL
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor.Callbacks;
    5. using UnityEditor;
    6. using System.IO;
    7. using System;
    8. using System.Text.RegularExpressions;
    9. using System.Linq;
    10.  
    11. public class PostProcessWebGL
    12. {
    13.     //The name of the WebGLTemplate. Location in project should be Assets/WebGLTemplates/<YOUR TEMPLATE NAME>
    14.     const string __TemplateToUse = "<YOUR TEMPLATE NAME>";
    15.  
    16.     [PostProcessBuild]
    17.     public static void ChangeWebGLTemplate(BuildTarget buildTarget, string pathToBuiltProject)
    18.     {
    19.         if (buildTarget != BuildTarget.WebGL) return;
    20.  
    21.  
    22.         //create template path
    23.         var templatePath = Paths.Combine(Application.dataPath, "WebGLTemplates", __TemplateToUse);
    24.  
    25.         //Clear the TemplateData folder, built by Unity.
    26.         FileUtilExtended.CreateOrCleanDirectory(Paths.Combine(pathToBuiltProject, "TemplateData"));
    27.  
    28.         //Copy contents from WebGLTemplate. Ignore all .meta files
    29.         FileUtilExtended.CopyDirectoryFiltered(templatePath, pathToBuiltProject, true, @".*/\.+|\.meta$", true);
    30.  
    31.         //Replace contents of index.html
    32.         FixIndexHtml(pathToBuiltProject);
    33.     }
    34.  
    35.     //Replaces %...% defines in index.html
    36.     static void FixIndexHtml(string pathToBuiltProject)
    37.     {
    38.         //Fetch filenames to be referenced in index.html
    39.         string
    40.             webglBuildUrl,
    41.             webglLoaderUrl;
    42.      
    43.         if (File.Exists(Paths.Combine(pathToBuiltProject, "Build", "UnityLoader.js")))
    44.         {
    45.             webglLoaderUrl = "Build/UnityLoader.js";
    46.         }
    47.         else
    48.         {
    49.             webglLoaderUrl = "Build/UnityLoader.min.js";
    50.         }
    51.  
    52.         string buildName = pathToBuiltProject.Substring(pathToBuiltProject.LastIndexOf("/") + 1);
    53.         webglBuildUrl = string.Format("Build/{0}.json", buildName);
    54.  
    55.         //webglLoaderUrl = EditorUserBuildSettings.development? "Build/UnityLoader.js": "Build/UnityLoader.min.js";
    56.         Dictionary<string, string> replaceKeywordsMap = new Dictionary<string, string> {
    57.                 {
    58.                     "%UNITY_WIDTH%",
    59.                     PlayerSettings.defaultWebScreenWidth.ToString()
    60.                 },
    61.                 {
    62.                     "%UNITY_HEIGHT%",
    63.                     PlayerSettings.defaultWebScreenHeight.ToString()
    64.                 },
    65.                 {
    66.                     "%UNITY_WEB_NAME%",
    67.                     PlayerSettings.productName
    68.                 },
    69.                 {
    70.                     "%UNITY_WEBGL_LOADER_URL%",
    71.                     webglLoaderUrl
    72.                 },
    73.                 {
    74.                     "%UNITY_WEBGL_BUILD_URL%",
    75.                     webglBuildUrl
    76.                 }
    77.             };
    78.  
    79.         string indexFilePath = Paths.Combine(pathToBuiltProject, "index.html");
    80.        Func<string, KeyValuePair<string, string>, string> replaceFunction = (current, replace) => current.Replace(replace.Key, replace.Value);
    81.         if (File.Exists(indexFilePath))
    82.         {
    83.            File.WriteAllText(indexFilePath, replaceKeywordsMap.Aggregate<KeyValuePair<string, string>, string>(File.ReadAllText(indexFilePath), replaceFunction));
    84.         }
    85.  
    86.     }
    87.  
    88.     private class FileUtilExtended
    89.     {
    90.      
    91.         internal static void CreateOrCleanDirectory(string dir)
    92.         {
    93.             if (Directory.Exists(dir))
    94.             {
    95.                 Directory.Delete(dir, true);
    96.             }
    97.             Directory.CreateDirectory(dir);
    98.         }
    99.  
    100.         //Fix forward slashes on other platforms than windows
    101.         internal static string FixForwardSlashes(string unityPath)
    102.         {
    103.             return ((Application.platform != RuntimePlatform.WindowsEditor) ? unityPath : unityPath.Replace("/", @"\"));
    104.         }
    105.  
    106.  
    107.  
    108.         //Copies the contents of one directory to another.
    109.        public static void CopyDirectoryFiltered(string source, string target, bool overwrite, string regExExcludeFilter, bool recursive)
    110.         {
    111.             RegexMatcher excluder = new RegexMatcher()
    112.             {
    113.                 exclude = null
    114.             };
    115.             try
    116.             {
    117.                 if (regExExcludeFilter != null)
    118.                 {
    119.                     excluder.exclude = new Regex(regExExcludeFilter);
    120.                 }
    121.             }
    122.             catch (ArgumentException)
    123.             {
    124.                UnityEngine.Debug.Log("CopyDirectoryRecursive: Pattern '" + regExExcludeFilter + "' is not a correct Regular Expression. Not excluding any files.");
    125.                 return;
    126.             }
    127.             CopyDirectoryFiltered(source, target, overwrite, excluder.CheckInclude, recursive);
    128.         }
    129.        internal static void CopyDirectoryFiltered(string sourceDir, string targetDir, bool overwrite, Func<string, bool> filtercallback, bool recursive)
    130.         {
    131.             // Create directory if needed
    132.             if (!Directory.Exists(targetDir))
    133.             {
    134.                 Directory.CreateDirectory(targetDir);
    135.                 overwrite = false;
    136.             }
    137.  
    138.             // Iterate all files, files that match filter are copied.
    139.             foreach (string filepath in Directory.GetFiles(sourceDir))
    140.             {
    141.                 if (filtercallback(filepath))
    142.                 {
    143.                     string fileName = Path.GetFileName(filepath);
    144.                     string to = Path.Combine(targetDir, fileName);
    145.  
    146.                  
    147.                     File.Copy(FixForwardSlashes(filepath),FixForwardSlashes(to), overwrite);
    148.                 }
    149.             }
    150.  
    151.             // Go into sub directories
    152.             if (recursive)
    153.             {
    154.                 foreach (string subdirectorypath in Directory.GetDirectories(sourceDir))
    155.                 {
    156.                     if (filtercallback(subdirectorypath))
    157.                     {
    158.                         string directoryName = Path.GetFileName(subdirectorypath);
    159.                        CopyDirectoryFiltered(Path.Combine(sourceDir, directoryName), Path.Combine(targetDir, directoryName), overwrite, filtercallback, recursive);
    160.                     }
    161.                 }
    162.             }
    163.         }
    164.  
    165.         internal struct RegexMatcher
    166.         {
    167.             public Regex exclude;
    168.             public bool CheckInclude(string s)
    169.             {
    170.                 return exclude == null || !exclude.IsMatch(s);
    171.             }
    172.         }
    173.  
    174.     }
    175.  
    176.     private class Paths
    177.     {
    178.         //Combine multiple paths using Path.Combine
    179.         public static string Combine(params string[] components)
    180.         {
    181.             if (components.Length < 1)
    182.             {
    183.                 throw new ArgumentException("At least one component must be provided!");
    184.             }
    185.             string str = components[0];
    186.             for (int i = 1; i < components.Length; i++)
    187.             {
    188.                 str = Path.Combine(str, components[i]);
    189.             }
    190.             return str;
    191.         }
    192.     }
    193.  
    194. }
    195.  
    196. #endif
    Good luck!
     
    Last edited: Jan 25, 2018
    Lavi424, xaldin-76, Karsten and 5 others like this.
  8. StaffanEk

    StaffanEk

    Joined:
    Jul 13, 2012
    Posts:
    380
    What are you talking about? I don't want you to host my WebGL build, I want you to build it. It's called Cloud Build not Cloud Host. What a ridiculous excuse.

    What the hell am I paying for?
     
    Last edited: Jan 25, 2018
  9. PlayItSafe_Fries

    PlayItSafe_Fries

    Joined:
    Jan 17, 2018
    Posts:
    11
    Exactly.
    I don't see why anyone would want to host their game on Unity using the default template.
    While cloud build is supposed to make a developer's life easier (quote: "Cloud Build - Build games faster") It's actually giving us more work because we'd have to:
    1. Press "Start Cloud Build"
    2. Wait 35 minutes for Unity to build the project.
    3. download and unzip the generated build
    4. replace the templatedata folder and index.html with the correct one
    5. Upload the contents to our ftp
    6. Hope that we didn't break anything and that we won't have to go back to 1.
    Anyway, right now we finally managed to fully automate our our build pipeline for WebGL and Android:
    After cloud build, Unity will notify a webhook I have set up, running in Azure, which will download and extract the .zip file and then upload it to our own FTP.
     
  10. Ponytech

    Ponytech

    Joined:
    Jun 13, 2016
    Posts:
    33
    Seriously? This makes either cloud build or custom templates useless.
    We use Cloud Build for building our WebGL version and webhooks to download and deploy the game on our own servers once the build is complete. Please remove this limitation.
     
    chriszul and StaffanEk like this.
  11. Ponytech

    Ponytech

    Joined:
    Jun 13, 2016
    Posts:
    33
    chriszul, StaffanEk and Deleted User like this.
  12. Deleted User

    Deleted User

    Guest

  13. Ponytech

    Ponytech

    Joined:
    Jun 13, 2016
    Posts:
    33
  14. Deleted User

    Deleted User

    Guest

    I didn’t see it either, at first, but I noticed I had ten new votes available (problem with unity feedback is I run out of votes quickly!) and started browsing through all the issues...
     
  15. pravusjif

    pravusjif

    Joined:
    Jul 24, 2012
    Posts:
    18
    Thanks a lot for this! it seems to be the only workaround for now.
     
    PlayItSafe_Fries likes this.
  16. bourriquet

    bourriquet

    Joined:
    Jul 17, 2012
    Posts:
    181
    Thanks @PlayItSafe_Fries for your code!
    I modified it a bit to make it more generic.
    Now the Custom Template is automatically taken from your build settings.
    Also, if it's not using a custom template, we don't do anything (as built-in templates don't have issues).
    Also, I made the regex to filter based on the local path only. I use Jenkins, and all this happens in a ".jenkins" folder, so every file was filtered out because of the parent hidden folder.
    So here is the new version:

    Code (CSharp):
    1. #if UNITY_EDITOR && UNITY_WEBGL
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor.Callbacks;
    5. using UnityEditor;
    6. using System.IO;
    7. using System;
    8. using System.Text.RegularExpressions;
    9. using System.Linq;
    10.  
    11. public class PostProcessWebGL
    12. {
    13.  
    14.     const string CUSTOM_TEMPLATE_PREFIX = "PROJECT:";
    15.  
    16.     static bool __UsesCustomTemplate { get { return PlayerSettings.WebGL.template.StartsWith(CUSTOM_TEMPLATE_PREFIX); } }
    17.  
    18.     static string __CustomTemplateName { get { return PlayerSettings.WebGL.template.Substring(CUSTOM_TEMPLATE_PREFIX.Length); } }
    19.  
    20.     static string __CustomTemplatePath { get { return Paths.Combine(Application.dataPath, "WebGLTemplates", __CustomTemplateName); } }
    21.  
    22.     [PostProcessBuild]
    23.     public static void ChangeWebGLTemplate(BuildTarget buildTarget, string pathToBuiltProject)
    24.     {
    25.         if (buildTarget != BuildTarget.WebGL) return;
    26.  
    27.         if (!__UsesCustomTemplate) return;
    28.  
    29.         Debug.Log("Add Custom WebGL Template: " + __CustomTemplateName);
    30.  
    31.         //Clear the TemplateData folder, built by Unity.
    32.         FileUtilExtended.CreateOrCleanDirectory(Paths.Combine(pathToBuiltProject, "TemplateData"));
    33.  
    34.         //Copy contents from WebGLTemplate. Ignore all .meta files
    35.         FileUtilExtended.CopyDirectoryFiltered(__CustomTemplatePath, pathToBuiltProject, true, @".*/\.+|\.meta$", true);
    36.  
    37.         //Replace contents of index.html
    38.         FixIndexHtml(pathToBuiltProject);
    39.     }
    40.  
    41.     //Replaces %...% defines in index.html
    42.     static void FixIndexHtml(string pathToBuiltProject)
    43.     {
    44.         //Fetch filenames to be referenced in index.html
    45.         string
    46.             webglBuildUrl,
    47.             webglLoaderUrl;
    48.  
    49.         if (File.Exists(Paths.Combine(pathToBuiltProject, "Build", "UnityLoader.js")))
    50.         {
    51.             webglLoaderUrl = "Build/UnityLoader.js";
    52.         }
    53.         else
    54.         {
    55.             webglLoaderUrl = "Build/UnityLoader.min.js";
    56.         }
    57.  
    58.         string buildName = pathToBuiltProject.Substring(pathToBuiltProject.LastIndexOf("/") + 1);
    59.         webglBuildUrl = string.Format("Build/{0}.json", buildName);
    60.  
    61.         //webglLoaderUrl = EditorUserBuildSettings.development? "Build/UnityLoader.js": "Build/UnityLoader.min.js";
    62.         Dictionary<string, string> replaceKeywordsMap = new Dictionary<string, string> {
    63.                     {
    64.                         "%UNITY_WIDTH%",
    65.                         PlayerSettings.defaultWebScreenWidth.ToString()
    66.                     },
    67.                     {
    68.                         "%UNITY_HEIGHT%",
    69.                         PlayerSettings.defaultWebScreenHeight.ToString()
    70.                     },
    71.                     {
    72.                         "%UNITY_WEB_NAME%",
    73.                         PlayerSettings.productName
    74.                     },
    75.                     {
    76.                         "%UNITY_WEBGL_LOADER_URL%",
    77.                         webglLoaderUrl
    78.                     },
    79.                     {
    80.                         "%UNITY_WEBGL_BUILD_URL%",
    81.                         webglBuildUrl
    82.                     }
    83.                 };
    84.  
    85.         string indexFilePath = Paths.Combine(pathToBuiltProject, "index.html");
    86.         Func<string, KeyValuePair<string, string>, string> replaceFunction = (current, replace) => current.Replace(replace.Key, replace.Value);
    87.         if (File.Exists(indexFilePath))
    88.         {
    89.             File.WriteAllText(indexFilePath, replaceKeywordsMap.Aggregate<KeyValuePair<string, string>, string>(File.ReadAllText(indexFilePath), replaceFunction));
    90.         }
    91.  
    92.     }
    93.  
    94.     private class FileUtilExtended
    95.     {
    96.  
    97.         internal static void CreateOrCleanDirectory(string dir)
    98.         {
    99.             if (Directory.Exists(dir))
    100.             {
    101.                 Directory.Delete(dir, true);
    102.             }
    103.             Directory.CreateDirectory(dir);
    104.         }
    105.  
    106.         //Fix forward slashes on other platforms than windows
    107.         internal static string FixForwardSlashes(string unityPath)
    108.         {
    109.             return ((Application.platform != RuntimePlatform.WindowsEditor) ? unityPath : unityPath.Replace("/", @"\"));
    110.         }
    111.  
    112.  
    113.  
    114.         //Copies the contents of one directory to another.
    115.         public static void CopyDirectoryFiltered(string source, string target, bool overwrite, string regExExcludeFilter, bool recursive)
    116.         {
    117.             RegexMatcher excluder = new RegexMatcher()
    118.             {
    119.                 exclude = null
    120.             };
    121.             try
    122.             {
    123.                 if (regExExcludeFilter != null)
    124.                 {
    125.                     excluder.exclude = new Regex(regExExcludeFilter);
    126.                 }
    127.             }
    128.             catch (ArgumentException)
    129.             {
    130.                 UnityEngine.Debug.Log("CopyDirectoryRecursive: Pattern '" + regExExcludeFilter + "' is not a correct Regular Expression. Not excluding any files.");
    131.                 return;
    132.             }
    133.             CopyDirectoryFiltered(source, target, overwrite, excluder.CheckInclude, recursive);
    134.         }
    135.         internal static void CopyDirectoryFiltered(string sourceDir, string targetDir, bool overwrite, Func<string, bool> filtercallback, bool recursive)
    136.         {
    137.             // Create directory if needed
    138.             if (!Directory.Exists(targetDir))
    139.             {
    140.                 Directory.CreateDirectory(targetDir);
    141.                 overwrite = false;
    142.             }
    143.  
    144.             // Iterate all files, files that match filter are copied.
    145.             foreach (string filepath in Directory.GetFiles(sourceDir))
    146.             {
    147.                 string localPath = filepath.Substring(sourceDir.Length);
    148.                 if (filtercallback(localPath))
    149.                 {
    150.                     string fileName = Path.GetFileName(filepath);
    151.                     string to = Path.Combine(targetDir, fileName);
    152.  
    153.                     File.Copy(FixForwardSlashes(filepath), FixForwardSlashes(to), overwrite);
    154.                 }
    155.             }
    156.  
    157.             // Go into sub directories
    158.             if (recursive)
    159.             {
    160.                 foreach (string subdirectorypath in Directory.GetDirectories(sourceDir))
    161.                 {
    162.                     string localPath = subdirectorypath.Substring(sourceDir.Length);
    163.                     if (filtercallback(localPath))
    164.                     {
    165.                         string directoryName = Path.GetFileName(subdirectorypath);
    166.                         CopyDirectoryFiltered(Path.Combine(sourceDir, directoryName), Path.Combine(targetDir, directoryName), overwrite, filtercallback, recursive);
    167.                     }
    168.                 }
    169.             }
    170.         }
    171.  
    172.         internal struct RegexMatcher
    173.         {
    174.             public Regex exclude;
    175.             public bool CheckInclude(string s)
    176.             {
    177.                 return exclude == null || !exclude.IsMatch(s);
    178.             }
    179.         }
    180.  
    181.     }
    182.  
    183.     private class Paths
    184.     {
    185.         //Combine multiple paths using Path.Combine
    186.         public static string Combine(params string[] components)
    187.         {
    188.             if (components.Length < 1)
    189.             {
    190.                 throw new ArgumentException("At least one component must be provided!");
    191.             }
    192.             string str = components[0];
    193.             for (int i = 1; i < components.Length; i++)
    194.             {
    195.                 str = Path.Combine(str, components[i]);
    196.             }
    197.             return str;
    198.         }
    199.     }
    200.  
    201. }
    202.  
    203. #endif
    204.  
     
  17. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    In Unity Cloud Build, it seems that PostProcessBuildAttribute cannot be used.

    It builds locally, but not on UCB.

    Is anyone else having this experience?
     
  18. NeoSu

    NeoSu

    Joined:
    Aug 11, 2013
    Posts:
    12
    hmm...it's 2020 and the webGL cloud build still cannot use a custom template. I guess I'll have to build my 20 webGL html on my own laptop?
     
    pedro_unity228 likes this.
  19. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    You can theoretically do a Unity build in Azure DevOps. I've had a lot more luck managing my own build server than using a hosted agent. The Android tools won't work without a manual install step. The Mac stuff, of course, doesn't work.

    Also, a $150 piece of hardware seems to completely run circles around Unity Cloud Build and is pretty fast when compared with Azure DevOps hosted agents.

    WebGL and Windows standalone seem to be the only builds people are reliably getting to work on Azure DevOps... so you may be in luck, if you choose that route.
     
  20. colinsenner

    colinsenner

    Joined:
    Oct 10, 2017
    Posts:
    1
    Since this thread was so helpful to me recently I wanted to contribute. I wrote a blog post on how I solved this issue with the above code since some of the function signatures seem to have changed and I wanted a solution that didn't rely on the [PostProcessBuild] attribute but rather on my Unity Cloud configuation settings.

    Big thanks to @PlayItSafe_Fries and @bourriquet in the thread. It's still insane we can't use Custom WebGL templates with Unity Cloud Build, when they're already well supported by Unity.

    TLDR for my blog post:

    The function signature for a "Post-Export Method" is
    Code (CSharp):
    1. public static void func(string)
    This should come as no big surprise, but when I was trying to solve this I ran into trying to use the solutions by other users' function signatures, which don't work if you're doing it via configuration options.

    @bourriquet 's code doesn't work anymore because when Unity Cloud builds the project it sets
    Code (CSharp):
    1. PlayerSettings.WebGL.template = "Application:Default"
    instead of what it does locally and his solution relys on:
    Code (CSharp):
    1. PlayerSettings.WebGL.template = "PROJECT:CustomTemplate"
    Here's the full info for how I got it all working with Unity 2019.2.x recently.

    https://www.colinsenner.com/blog/unity-cloud-build-with-custom-template/

    Hope this helps!
    Colin
     
  21. Samasab

    Samasab

    Joined:
    May 13, 2018
    Posts:
    13
    Wow. I will try the solution via your post @colinsenner. Thanks.

    @dannyd If I understand correctly, the main argument for Unity not to allow for setting custom templates, is because so you can 'play' the build in the Cloud Build portal with the right styling?
    I think anyone working on a serious WebGL project will create their own template without Unity branding/iframing, and I strongly suggest you reconsider removing the override to default.
     
    BlockValue and Martin_KageNova like this.
  22. Martin_KageNova

    Martin_KageNova

    Joined:
    Jul 30, 2019
    Posts:
    7
    We used to use the same approach as Colin, but we're upgrading to 2020.1 now, and the processing Unity does on the template files has become a lot fancier (conditional switches, a lot more template variables, etc). It's a huge pain to reproduce all that functionality in the post-process method. Not really sure what the best solution is now. Is there a way to invoke this processing logic from Unity in code somehow? Otherwise, we might just store a built version of the template with the project to copy over, but that's also prone to mistakes of course.
     
  23. PlayItSafe_Fries

    PlayItSafe_Fries

    Joined:
    Jan 17, 2018
    Posts:
    11
    In Unity 2020 or above the WebGL Templates are handled differently.
    Unity changed the keywords + the build files are now directly in index.html and no longer in a separate .json file (1 less file to download so for faster loading I assume)

    For Unity 2020 or above, this is the updated "FixIndexHtml" function:
    Code (CSharp):
    1.  // Replaces {{{ ... }}} defines in index.html
    2.     static void FixIndexHtml(string pathToBuiltProject)
    3.     {
    4.         string buildFolderName = "Build";
    5.         string buildDir = Paths.Combine(pathToBuiltProject, buildFolderName);
    6.  
    7.         // Build Keywords map
    8.         Dictionary<string, string> replaceKeywordsMap = new Dictionary<string, string>();
    9.  
    10.         replaceKeywordsMap.Add("{{{ LOADER_FILENAME }}}", FindFileNameWithExtension(buildDir, ".js"));
    11.         replaceKeywordsMap.Add("{{{ DATA_FILENAME }}}", FindFileNameWithExtension(buildDir, ".data.gz"));
    12.         replaceKeywordsMap.Add("{{{ CODE_FILENAME }}}", FindFileNameWithExtension(buildDir, ".wasm.gz"));
    13.         replaceKeywordsMap.Add("{{{ FRAMEWORK_FILENAME }}}", FindFileNameWithExtension(buildDir, ".js.gz"));
    14.         replaceKeywordsMap.Add("{{{ BACKGROUND_FILENAME }}}", FindFileNameWithExtension(buildDir, ".jpg"));
    15.  
    16.         //App info
    17.         replaceKeywordsMap.Add("{{{ COMPANY_NAME }}}", Application.companyName);
    18.         replaceKeywordsMap.Add("{{{ PRODUCT_NAME }}}", Application.productName);
    19.         replaceKeywordsMap.Add("{{{ PRODUCT_VERSION }}}", Application.version);
    20.  
    21.         string indexFilePath = Paths.Combine(pathToBuiltProject, "index.html");
    22.         Func<string, KeyValuePair<string, string>, string> replaceFunction = (current, replace) => string.IsNullOrEmpty(replace.Value)? current : current.Replace(replace.Key, replace.Value);
    23.         if (File.Exists(indexFilePath))
    24.         {
    25.             File.WriteAllText(indexFilePath, replaceKeywordsMap.Aggregate<KeyValuePair<string, string>, string>(File.ReadAllText(indexFilePath), replaceFunction));
    26.         }
    27.     }
    Note. This is only going to work if you use gzip compression.
    For Brotli or "Disabled", use the correct file extensions in the above method.
     
    xaldin-76 and IOU_RAY like this.
  24. Martin_KageNova

    Martin_KageNova

    Joined:
    Jul 30, 2019
    Posts:
    7
    That's an improvement, but it doesn't handle 2020 templates completely. You can now also use "preprocessor" directives in WebGL Templates like
    #if SOME_DEFINE_SYMBOL ... #endif
    .
     
  25. ElevenGame

    ElevenGame

    Joined:
    Jun 13, 2016
    Posts:
    148
    Hello everybody. Does anyone know where the FindFileNameWithExtension() function comes from? It is not known to my compiler and google didn't help either..
     
  26. ElevenGame

    ElevenGame

    Joined:
    Jun 13, 2016
    Posts:
    148
    In case anyone else is missing that function:
    Code (CSharp):
    1. static string FindFileNameWithExtension(string path, string ext)
    2. {
    3.     string[] filesWithExt = Directory.GetFiles(path, "*" + ext);
    4.     if( (filesWithExt != null) && (filesWithExt.Length > 0) )
    5.     {
    6.         return Path.GetFileName(filesWithExt[0]);
    7.     }
    8.     return "";
    9. }
     
    BlockValue likes this.
  27. PlayItSafe_Fries

    PlayItSafe_Fries

    Joined:
    Jan 17, 2018
    Posts:
    11
    Sorry,
    I didn't check the code for completeness and made the wrong assumption that function was in my previous post in this thread.
    I'm glad you managed to get it working and thanks for sharing your solution
    This is the function I forgot to add (although ElevenGame's solution seems more elegant)

    Code (CSharp):
    1.  static string FindFileNameWithExtension(string buildDirectory, string extension)
    2.     {
    3.         var dir = new DirectoryInfo(buildDirectory);
    4.  
    5.         foreach (var f in dir.GetFiles())
    6.         {
    7.             if (f.Name.EndsWith(extension))
    8.             {
    9.                 return f.Name;
    10.             }
    11.         }
    12.  
    13.         return null;
    14.     }
     
    BlockValue likes this.
  28. aFeesh

    aFeesh

    Joined:
    Feb 12, 2015
    Posts:
    35
    So does Unity plan on adding support for this or not? Seems odd Unity Cloud Build handles my WebGL build differently than building locally.

    Seems the best solution would be to opt out of having your build hosted on UCB, and then you can use custom templates? I really only want UCB to build my WebGL app not host.
     
  29. xaldin-76

    xaldin-76

    Joined:
    Oct 1, 2016
    Posts:
    25
    Thanks a lot for this, it works for my simple custom template webgl UCB unity v2020.3.2
    I had to change ".data.gz" to ".data.unityweb" - ".js.gz" to ".js.unityweb" - ".wasm.gz" to ".wasm.unityweb", I guess it doesn't have to do with compression type now?
     
  30. matheus_inmotionvr

    matheus_inmotionvr

    Joined:
    Oct 3, 2018
    Posts:
    63
    The fact that the Cloud Build generates a different build than my local editor is surprising to me. In the end, I always have to build locally to test my custom template, which goes against the reasons why I started using Cloud Build to begin with.

    I don't want Unity to host my webGL application. Hosting is quite easy nowadays, everyone can start a free AWS instance and upload the content there. It's a nice feature, but if it's present to the expense of consistency between builds, I would rather drop it completely.
     
  31. christokkies

    christokkies

    Joined:
    Apr 8, 2020
    Posts:
    13
    Hey @dannyd , any word on this? It would be really helpful to have a checkbox that allows us to use custom WebGL templates in Unity Cloud Build. The inability to use custom templates with Unity Cloud Build breaks our CI process. The workarounds described here are not really sustainable, and don't account for the preprocessing directives that are now executed with Templates. Simply want the ability to do something like PlayerSettings.WebGL.template = "MyCustomTemplate."

    Thank you!
     
    arumiat and Martin_KageNova like this.
  32. mkjuicemk

    mkjuicemk

    Joined:
    Oct 6, 2021
    Posts:
    2
    Hey guys is this still a working solution? With the new "FixIndexHtml" functioned mentioned above by game no longer builds. Runs perfectly locally without the function.
     
  33. christokkies

    christokkies

    Joined:
    Apr 8, 2020
    Posts:
    13
    For us it doesn't work either. The workaround correctly replaces the javascript macros with the appropriate file references, but it doesn't seem to work if you have preprocessor directives that need to be executed prior to building.
     
  34. rajivrao

    rajivrao

    Unity Technologies

    Joined:
    Feb 19, 2019
    Posts:
    111
    Hi folks, for this would recommend submitting a support ticket through the Dashboard. That would be the fastest way to get your ticket answered/fixed.
     
  35. LorenzoNuvoletta

    LorenzoNuvoletta

    Joined:
    Apr 28, 2014
    Posts:
    54
    What is the purpose of having WebGL target in Cloud Build that doesn't use the project settings?
     
    matheus_inmotionvr and PaulUsul like this.
  36. commodore

    commodore

    Joined:
    May 30, 2012
    Posts:
    40
    The workaround posted in this thread no longer seems to work.

    Unity please just make custom templates work in Cloud Build without the need for a messy workaround. I do not care about being able to play my WebGL builds through the dashboard. And even if I did, your system should not rely on the default template. Forcing the default template so that WebGL builds can be played via the dashboard doesn't seem like a good approach.
     
    matheus_inmotionvr likes this.
  37. jantjedetweede

    jantjedetweede

    Joined:
    Oct 21, 2021
    Posts:
    7
    How is this still an issue 7 years later??
     
  38. Barb-Unity

    Barb-Unity

    Unity Technologies

    Joined:
    Jul 2, 2019
    Posts:
    50
    Hi all!

    Unfortunately, custom WebGL templates are still not supported. However, there's a feature request to support them and our product team should start working on it in 2023.

    We apologize for the inconvenience. If you've found some workarounds that currently work, please share them with us and with the community by posting them in this thread.

    Cheers!
     
  39. JohnOknelorfDev

    JohnOknelorfDev

    Joined:
    Oct 2, 2017
    Posts:
    21
    Gosh, what a shame. Really. I mean it. I understand the initial intention, see the comment of @dannyd at the start of this thread: "We always build with the default template so that we can host the html properly and so the styling is consistent with the rest of our site, and the window sizing acts correctly", and maybe at that time in 2015s, hosting was a problem for people (actually, it wasn't, but let's pretend it was). Maybe at THAT time it was reasonable for some other internal reasons. But in any case, as @jantjedetweede said, I don't understand HOW is this still an issue 7 years later. SEVEN. YEARS. Why don't you want to give people a choice? I just can't understand why Unity team creates amazing and jaw-dropping things, but at the same time, completely and deliberately ignores the very basic requests.

    That is not even hard to implement. Just add a setting or a checkbox to configurations of WebGL build targets. Something like "Target WebGL Template" with two options:
    -- "Use Default Template". If this option is selected, then show a note that the project will be built with the default WebGL template and the hosting on Unity servers will be available;
    -- "Use Custom Template". If this option is selected, then show a note that the project will be built with the custom WebGL template used in the project settings, but the hosting on Unity servers will not be available. Only the downloading of the builds will be available.

    That's it. The logic is dead simple. If I know at least something about the development (and as a lead developer, I hope I do), the implementation should be dead simple as well. Why don't you give people and teams a proper choice?

    Our team have been using Unity Cloud Build for years. We have been paying money for that for years. But there was not a single time when we needed to host our builds on Unity servers. We just don't need it because we host builds on our own websites. But we need Unity Cloud Build. And we need to have the opportunity to use custom WebGL templates. And, actually repeating the words of @dannyd, our intention is to "host the html properly and so the styling is consistent with the rest of OUR websites" because we host on OUR websites, and develop for OUR websites.

    Why do we have to mess with some custom post-processing scripts (especially taking into consideration the current implementation and available functionality in WebGL templates like conditional directives and advanced JS macros)? If we think about it a bit more, it has no sense actually: Unity team allows using only the default templates "so that they can host the html properly, etc etc" BUT at the same time, Unity team leaves the opportunity to modify index.html in the post-processing scripts, so that people can substitute or inject basically any styles or html layout or scripts there which obviously comes into conflict with Unity team's intention "to keep the consistent styling" and other excuses. Ahhh. That's awful.

    Excuse me for being a bit harsh probably. But I have to admit it really makes me a bit mad already.
     
  40. arumiat

    arumiat

    Joined:
    Apr 26, 2014
    Posts:
    323
  41. ThomasKirae

    ThomasKirae

    Joined:
    Feb 13, 2023
    Posts:
    1
    upload_2023-5-2_10-18-44.png
    This is what is displayed in my Gaming Services Dashboard, as today. This could mean that the problem is solved (I haven't tried it yet)...
     
    matheus_inmotionvr likes this.
  42. matheus_inmotionvr

    matheus_inmotionvr

    Joined:
    Oct 3, 2018
    Posts:
    63
    It looks promising. Has anyone tested it so far?
     
  43. Matheuz

    Matheuz

    Joined:
    Aug 27, 2014
    Posts:
    36