diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9139e86a..1a08d576 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,8 @@ on: push: tags: - '*' + branches: + - master jobs: variables: @@ -28,7 +30,12 @@ jobs: - name: Set tag id: set_tag - run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT + else + echo "VERSION=${{ steps.set_unity_version.outputs.VERSION }}-webgl2-master" >> $GITHUB_OUTPUT + fi - name: Set target name id: set_build_name @@ -62,8 +69,8 @@ jobs: buildProject: name: Create Unity WebGL Build 🏗 - # only build with additional parameters, the tag alone should only create a release draft - if: ${{ needs.variables.outputs.TAG != needs.variables.outputs.UNITY_VERSION }} + # Build if it's a master push or if the tag is not just the unity version + if: ${{ github.ref == 'refs/heads/master' || needs.variables.outputs.TAG != needs.variables.outputs.UNITY_VERSION }} needs: [ variables ] runs-on: ubuntu-latest strategy: @@ -112,7 +119,7 @@ jobs: createRelease: name: Create Github release 🐙 # only run for the pure tag without build parameters - if: ${{ needs.variables.outputs.TAG == needs.variables.outputs.UNITY_VERSION }} + if: ${{ github.ref_type == 'tag' && needs.variables.outputs.TAG == needs.variables.outputs.UNITY_VERSION }} needs: [ variables ] runs-on: ubuntu-latest steps: diff --git a/Assets/Plugins/WebGL/WebBridge/CommonCommands.cs b/Assets/Plugins/WebGL/WebBridge/CommonCommands.cs index 0c64ed34..d4d48e2d 100644 --- a/Assets/Plugins/WebGL/WebBridge/CommonCommands.cs +++ b/Assets/Plugins/WebGL/WebBridge/CommonCommands.cs @@ -9,6 +9,7 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections; using System.Collections.Generic; using Supyrb.Attributes; using UnityEngine; @@ -274,5 +275,84 @@ public void LogShaderCompilation(int enabled) GraphicsSettings.logWhenShaderIsCompiled = enabled == 1; Debug.Log($"GraphicsSettings.logWhenShaderIsCompiled: {GraphicsSettings.logWhenShaderIsCompiled}"); } + + /// + /// Copy text to clipboard using the browser's clipboard API + /// + /// Text to copy to clipboard + [WebCommand(Description = "Copy text to clipboard")] + public void CopyToClipboard(string text) + { + WebToolPlugins.CopyToClipboard(text); + } + + /// + /// Check if the browser has an internet connection + /// + [WebCommand(Description = "Check if browser is online")] + public void CheckOnlineStatus() + { + bool isOnline = WebToolPlugins.IsOnline(); + Debug.Log($"Online Status: {(isOnline ? "Connected" : "Disconnected")}"); + } + + /// + /// Captures the current screen and saves it as a PNG file. + /// + [WebCommand(Description = "Save current screen as PNG")] + public void SaveScreenshot() + { + SaveScreenshotSuperSize(1); + } + + /// + /// Captures the current screen and saves it as a PNG file. + /// + /// 1 for normal size, 2 for double size, 4 for quadruple size + [WebCommand(Description = "Save current screen as PNG with variable super size")] + public void SaveScreenshotSuperSize(int superSize) + { + StartCoroutine(CaptureScreenshot(superSize)); + } + + private IEnumerator CaptureScreenshot(int superSize) + { + // Wait for the end of frame to ensure everything is rendered + yield return new WaitForEndOfFrame(); + + string filename = "screenshot.png"; + try + { + // Capture the screen + Texture2D screenshot = ScreenCapture.CaptureScreenshotAsTexture(superSize); + + try + { + // Convert to PNG + byte[] pngData = screenshot.EncodeToPNG(); + + // Download through browser + WebToolPlugins.DownloadBinaryFile(filename, pngData, "image/png"); + + Debug.Log($"Screenshot saved as {filename} ({screenshot.width}x{screenshot.height}) with size {pngData.Length} bytes"); + } + finally + { + // Clean up the texture + if (Application.isPlaying) + { + Destroy(screenshot); + } + else + { + DestroyImmediate(screenshot); + } + } + } + catch (System.Exception e) + { + Debug.LogError($"Failed to save screenshot: {e.Message}"); + } + } } } \ No newline at end of file diff --git a/Assets/Plugins/WebGL/WebBridge/WebBridge.cs b/Assets/Plugins/WebGL/WebBridge/WebBridge.cs index 0bca0b6a..4ca27a17 100644 --- a/Assets/Plugins/WebGL/WebBridge/WebBridge.cs +++ b/Assets/Plugins/WebGL/WebBridge/WebBridge.cs @@ -109,6 +109,7 @@ public void Help() { sb.AppendLine($"---{webCommand.GetType().Name}---"); MethodInfo[] methods = webCommand.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance); + Array.Sort(methods, (a, b) => string.Compare(a.Name, b.Name)); for (int i = 0; i < methods.Length; i++) { @@ -123,11 +124,11 @@ public void Help() var parameter = parameters[j]; if (parameter.ParameterType == typeof(string)) { - sb.Append($", \"{parameter.ParameterType} {parameter.Name}\""); + sb.Append($", \"{GetFriendlyTypeName(parameter.ParameterType)} {parameter.Name}\""); } else { - sb.Append($", {parameter.ParameterType} {parameter.Name}"); + sb.Append($", {GetFriendlyTypeName(parameter.ParameterType)} {parameter.Name}"); } } @@ -140,5 +141,16 @@ public void Help() sb.AppendLine($"\nRun a command with 'runUnityCommand(\"COMMAND_NAME\",PARAMETER);'"); Debug.Log(sb.ToString()); } + + private string GetFriendlyTypeName(Type type) + { + if (type == typeof(int)) return "int"; + if (type == typeof(long)) return "long"; + if (type == typeof(float)) return "float"; + if (type == typeof(double)) return "double"; + if (type == typeof(bool)) return "bool"; + if (type == typeof(string)) return "string"; + return type.Name; + } } } \ No newline at end of file diff --git a/Assets/Plugins/WebGL/WebTools/WebToolPlugins.cs b/Assets/Plugins/WebGL/WebTools/WebToolPlugins.cs index 8b3c0cd4..b79af50b 100644 --- a/Assets/Plugins/WebGL/WebTools/WebToolPlugins.cs +++ b/Assets/Plugins/WebGL/WebTools/WebToolPlugins.cs @@ -35,9 +35,14 @@ public static class WebToolPlugins [DllImport("__Internal")] private static extern uint _GetTotalMemorySize(); [DllImport("__Internal")] - private static extern uint _GetStaticMemorySize(); + private static extern bool _CopyToClipboard(string text); [DllImport("__Internal")] - private static extern uint _GetDynamicMemorySize(); + private static extern int _IsOnline(); + [DllImport("__Internal")] + private static extern void _DownloadFile(string filename, string content); + [DllImport("__Internal")] + private static extern void _DownloadBlob(string filename, byte[] byteArray, int byteLength, string mimeType); + #endif private static bool _infoPanelVisible = false; @@ -154,23 +159,6 @@ public static bool IsMobileDevice() userAgent.Contains("Android"); } - /// - /// Get the total memory size used by the application in MB - /// - /// Size in MB - public static float GetTotalMemorySize() - { -#if UNITY_WEBGL && !UNITY_EDITOR - var bytes = _GetTotalMemorySize(); - return GetMegaBytes(bytes); -#elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS - Debug.Log($"{nameof(WebToolPlugins)}.{nameof(GetTotalMemorySize)} called"); - return -1f; -#else - return -1f; -#endif - } - /// /// Log all current memory data in MB /// @@ -178,25 +166,24 @@ public static void LogMemory() { #if UNITY_WEBGL && !UNITY_EDITOR var managed = GetManagedMemorySize(); - var native = GetNativeMemorySize(); var total = GetTotalMemorySize(); - Debug.Log($"Memory stats:\nManaged: {managed:0.00}MB\nNative: {native:0.00}MB\nTotal: {total:0.00}MB"); + Debug.Log($"Memory stats:\nManaged: {managed:0.00}MB\nTotal: {total:0.00}MB"); #elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS Debug.Log($"{nameof(WebToolPlugins)}.{nameof(LogMemory)} called"); #endif } /// - /// Get the static memory size used by the application in MB + /// Get the total memory size used by the application in MB /// /// Size in MB - public static float GetStaticMemorySize() + public static float GetTotalMemorySize() { #if UNITY_WEBGL && !UNITY_EDITOR - var bytes = _GetStaticMemorySize(); + var bytes = _GetTotalMemorySize(); return GetMegaBytes(bytes); #elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS - Debug.Log($"{nameof(WebToolPlugins)}.{nameof(GetStaticMemorySize)} called"); + Debug.Log($"{nameof(WebToolPlugins)}.{nameof(GetTotalMemorySize)} called"); return -1f; #else return -1f; @@ -204,56 +191,93 @@ public static float GetStaticMemorySize() } /// - /// Get the dynamic memory size used by the application in MB + /// Get the managed memory size used by the application in MB /// /// Size in MB - public static float GetDynamicMemorySize() + public static float GetManagedMemorySize() { -#if UNITY_WEBGL && !UNITY_EDITOR - var bytes = _GetStaticMemorySize(); + var bytes = (uint)GC.GetTotalMemory(false); return GetMegaBytes(bytes); -#elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS - Debug.Log($"{nameof(WebToolPlugins)}.{nameof(GetDynamicMemorySize)} called"); - return -1f; -#else - return -1f; -#endif } /// - /// Get the native memory size used by the application in MB (Static + Dynamic memory) + /// Converts bytes (B) to mega bytes (MB) /// - /// Size in MB - public static float GetNativeMemorySize() + /// bytes to convert + /// bytes / (1024 * 1024) + private static float GetMegaBytes(uint bytes) { -#if UNITY_WEBGL && !UNITY_EDITOR - return GetDynamicMemorySize() + GetStaticMemorySize(); -#elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS - Debug.Log($"{nameof(WebToolPlugins)}.{nameof(GetNativeMemorySize)} called"); - return -1f; -#else - return -1f; -#endif + return (float)bytes / (1024 * 1024); } /// - /// Get the managed memory size used by the application in MB + /// Copies the specified text to the system clipboard using the browser's clipboard API. + /// Only works in WebGL builds and requires clipboard-write permission in modern browsers. /// - /// Size in MB - public static float GetManagedMemorySize() + /// The text to copy to the clipboard + /// True if the copy operation was successful, false otherwise + public static void CopyToClipboard(string text) { - var bytes = (uint)GC.GetTotalMemory(false); - return GetMegaBytes(bytes); + #if UNITY_WEBGL && !UNITY_EDITOR + _CopyToClipboard(text); + #elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS + Debug.Log($"{nameof(WebToolPlugins)}.{nameof(CopyToClipboard)} called with: {text}"); + #endif } /// - /// Converts bytes (B) to mega bytes (MB) + /// Checks if the browser currently has an internet connection using the navigator.onLine property. /// - /// bytes to convert - /// bytes / (1024 * 1024) - private static float GetMegaBytes(uint bytes) + /// True if the browser is online, false if it's offline + public static bool IsOnline() { - return (float)bytes / (1024 * 1024); + #if UNITY_WEBGL && !UNITY_EDITOR + return _IsOnline() == 1; + #elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS + Debug.Log($"{nameof(WebToolPlugins)}.{nameof(IsOnline)} called"); + return true; + #else + return true; + #endif + } + + /// + /// Downloads a text file through the browser with the specified filename and content. + /// Creates a temporary anchor element to trigger the download. + /// + /// The name of the file to be downloaded + /// The text content to be saved in the file + public static void DownloadTextFile(string filename, string content) + { + #if UNITY_WEBGL && !UNITY_EDITOR + _DownloadFile(filename, content); + #elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS + Debug.Log($"{nameof(WebToolPlugins)}.{nameof(DownloadTextFile)} called with filename: {filename}"); + #endif + } + + /// + /// Downloads a binary file through the browser with the specified filename and data. + /// Creates a Blob with the specified MIME type and triggers the download. + /// + /// The name of the file to be downloaded + /// The binary data to be saved in the file + /// The MIME type of the file (defaults to "application/octet-stream") + /// + /// + /// // Example: Save a Texture2D as PNG + /// Texture2D texture; + /// byte[] pngData = texture.EncodeToPNG(); + /// WebToolPlugins.DownloadBinaryFile("texture.png", pngData, "image/png"); + /// + /// + public static void DownloadBinaryFile(string filename, byte[] data, string mimeType = "application/octet-stream") + { + #if UNITY_WEBGL && !UNITY_EDITOR + _DownloadBlob(filename, data, data.Length, mimeType); + #elif UNITY_EDITOR && WEBTOOLS_LOG_CALLS + Debug.Log($"{nameof(WebToolPlugins)}.{nameof(DownloadBinaryFile)} called with filename: {filename}"); + #endif } } } \ No newline at end of file diff --git a/Assets/Plugins/WebGL/WebTools/WebToolPlugins.jslib b/Assets/Plugins/WebGL/WebTools/WebToolPlugins.jslib index 893777d5..637e9216 100644 --- a/Assets/Plugins/WebGL/WebTools/WebToolPlugins.jslib +++ b/Assets/Plugins/WebGL/WebTools/WebToolPlugins.jslib @@ -46,7 +46,7 @@ var WebGlPlugins = } var currentTimeRounded = currentTime.toFixed(2); - console.log('Time tracker event ' +eventNameText +': ' + currentTimeRounded + 'ms'); + console.log('Time tracker event ' + eventNameText + ': ' + currentTimeRounded + 'ms'); }, _AddFpsTrackingEvent: function(fps) { @@ -99,43 +99,53 @@ var WebGlPlugins = return -1; }, - _GetTotalStackSize: function() - { - if(typeof Module !== 'undefined' && typeof Module.STACK_SIZE !== 'undefined') { - return Module.STACK_SIZE; - } - if(typeof TOTAL_STACK !== 'undefined') { // Legacy support - return TOTAL_STACK; - } - - console.warn("Problem with retrieving stack size"); - return -1; + _CopyToClipboard: function(text) { + var str = UTF8ToString(text); + navigator.clipboard.writeText(str) + .then(function() { + }) + .catch(function(err) { + console.error('Failed to copy text: ', err); + }); }, - _GetStaticMemorySize: function() - { - if(typeof Module !== 'undefined' && typeof Module.staticAlloc !== 'undefined') { - return Module.staticAlloc; - } - if(typeof STATICTOP !== 'undefined' && typeof STATIC_BASE !== 'undefined') { // Legacy support - return STATICTOP - STATIC_BASE; - } + _IsOnline: function() { + return navigator.onLine ? 1 : 0; + }, - console.warn("Problem with retrieving static memory size"); - return -1; + _DownloadFile: function(filename, content) { + var filenameStr = UTF8ToString(filename); + var contentStr = UTF8ToString(content); + + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(contentStr)); + element.setAttribute('download', filenameStr); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); }, - _GetDynamicMemorySize: function() - { - if(typeof Module !== 'undefined' && typeof Module.dynamicAlloc !== 'undefined') { - return Module.dynamicAlloc; - } - if(typeof HEAP32 !== 'undefined' && typeof DYNAMICTOP_PTR !== 'undefined' && typeof DYNAMIC_BASE !== 'undefined') { // Legacy support - return HEAP32[DYNAMICTOP_PTR >> 2] - DYNAMIC_BASE; + _DownloadBlob: function(filename, byteArray, byteLength, mimeType) { + var filenameStr = UTF8ToString(filename); + var mimeTypeStr = UTF8ToString(mimeType); + + var data = new Uint8Array(byteLength); + for (var i = 0; i < byteLength; i++) { + data[i] = HEAPU8[byteArray + i]; } - console.warn("Problem with retrieving dynamic memory size"); - return -1; + var blob = new Blob([data], { type: mimeTypeStr }); + var url = URL.createObjectURL(blob); + + var element = document.createElement('a'); + element.setAttribute('href', url); + element.setAttribute('download', filenameStr); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + URL.revokeObjectURL(url); } }; diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 62a17737..6970908a 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -140,7 +140,7 @@ PlayerSettings: loadStoreDebugModeEnabled: 0 visionOSBundleVersion: 1.0 tvOSBundleVersion: 1.0 - bundleVersion: 1.3.0 + bundleVersion: 1.4.0 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0