diff --git a/Assets/AirConsole/scripts/Editor/Inspector.cs b/Assets/AirConsole/scripts/Editor/Inspector.cs index b89c22d9..8ce7e054 100644 --- a/Assets/AirConsole/scripts/Editor/Inspector.cs +++ b/Assets/AirConsole/scripts/Editor/Inspector.cs @@ -5,6 +5,7 @@ using UnityEngine; using UnityEditor; using Debug = UnityEngine.Debug; +using NDream.AirConsole; namespace NDream.AirConsole.Editor { [CustomEditor(typeof(AirConsole))] @@ -24,6 +25,7 @@ public class Inspector : UnityEditor.Editor { private const string INACTIVE_PLAYERS_SILENCED_INACTIVE = "var AIRCONSOLE_INACTIVE_PLAYERS_SILENCED = false;"; private const string ANDROID_NATIVE_GAME_SIZING_ACTIVE = "var AIRCONSOLE_ANDROID_NATIVE_GAMESIZING = true;"; private const string ANDROID_NATIVE_GAME_SIZING_INACTIVE = "var AIRCONSOLE_ANDROID_NATIVE_GAMESIZING = false;"; + private const string AIRCONSOLE_RUNTIME_SETTINGS_ASSET_FILE = AirconsoleRuntimeSettings.ResourceName + ".asset"; private static string SettingsPath => Application.dataPath + Settings.WEBTEMPLATE_PATH + "/airconsole-settings.js"; @@ -180,14 +182,24 @@ internal void UpdateAirConsoleConstructorSettings() { } private void ReadConstructorSettings() { - if (!File.Exists(SettingsPath)) { - return; + bool hasNativeGameSizingValue = false; + if (File.Exists(SettingsPath)) { + string persistedSettings = File.ReadAllText(SettingsPath); + translationValue = persistedSettings.Contains(TRANSLATION_ACTIVE); + inactivePlayersSilencedValue = !persistedSettings.Contains(INACTIVE_PLAYERS_SILENCED_INACTIVE); + nativeGameSizingSupportedValue = !persistedSettings.Contains(ANDROID_NATIVE_GAME_SIZING_INACTIVE); + hasNativeGameSizingValue = true; + } else { + AirconsoleRuntimeSettings asset = LoadAirconsoleRuntimeSettingsAsset(); + if (asset != null) { + nativeGameSizingSupportedValue = asset.NativeGameSizingSupported; + hasNativeGameSizingValue = true; + } } - string persistedSettings = File.ReadAllText(SettingsPath); - translationValue = persistedSettings.Contains(TRANSLATION_ACTIVE); - inactivePlayersSilencedValue = !persistedSettings.Contains(INACTIVE_PLAYERS_SILENCED_INACTIVE); - nativeGameSizingSupportedValue = !persistedSettings.Contains(ANDROID_NATIVE_GAME_SIZING_INACTIVE); + if (hasNativeGameSizingValue) { + PersistAirconsoleRuntimeSettings(nativeGameSizingSupportedValue); + } } private void WriteConstructorSettings() { @@ -197,6 +209,7 @@ private void WriteConstructorSettings() { + $"{(inactivePlayersSilencedValue ? INACTIVE_PLAYERS_SILENCED_ACTIVE : INACTIVE_PLAYERS_SILENCED_INACTIVE)}\n" + $"{(nativeGameSizingSupportedValue ? ANDROID_NATIVE_GAME_SIZING_ACTIVE : ANDROID_NATIVE_GAME_SIZING_INACTIVE)}\n" + GenerateGameInformation()); + PersistAirconsoleRuntimeSettings(nativeGameSizingSupportedValue); } catch (IOException e) { AirConsoleLogger.LogError(() => $"Failed to write settings file at {SettingsPath}: {e.Message}"); } @@ -225,6 +238,90 @@ private static void OpenUpgradeInstructions() { Application.OpenURL( "https://github.com/AirConsole/airconsole-unity-plugin/wiki/Upgrading-the-Unity-Plugin-to-a-newer-version"); } + + private static AirconsoleRuntimeSettings LoadAirconsoleRuntimeSettingsAsset() { + string assetPath = GetNativeGameSizingSettingsAssetPath(false); + return string.IsNullOrEmpty(assetPath) + ? null + : AssetDatabase.LoadAssetAtPath(assetPath); + } + + private static void PersistAirconsoleRuntimeSettings(bool value) { + string assetPath = GetNativeGameSizingSettingsAssetPath(true); + if (string.IsNullOrEmpty(assetPath)) { + return; + } + + AirconsoleRuntimeSettings asset = AssetDatabase.LoadAssetAtPath(assetPath); + if (asset == null) { + asset = CreateInstance(); + asset.SetNativeGameSizingSupported(value); + AssetDatabase.CreateAsset(asset, assetPath); + AssetDatabase.SaveAssets(); + return; + } + + if (asset.NativeGameSizingSupported == value) { + return; + } + + asset.SetNativeGameSizingSupported(value); + EditorUtility.SetDirty(asset); + AssetDatabase.SaveAssets(); + } + + private static string GetNativeGameSizingSettingsAssetPath(bool ensureFolderExists) { + string resourcesFolder = GetResourcesFolderRelativeToAirConsole(ensureFolderExists); + if (string.IsNullOrEmpty(resourcesFolder)) { + return null; + } + + return $"{resourcesFolder}/{AIRCONSOLE_RUNTIME_SETTINGS_ASSET_FILE}"; + } + + private static string GetResourcesFolderRelativeToAirConsole(bool ensureFolderExists) { + string airConsoleDirectory = GetAirConsoleScriptDirectory(); + if (string.IsNullOrEmpty(airConsoleDirectory)) { + return null; + } + + string resourcesFolder = $"{airConsoleDirectory}/Resources"; + if (AssetDatabase.IsValidFolder(resourcesFolder)) { + return resourcesFolder; + } + + if (!ensureFolderExists) { + return resourcesFolder; + } + + string parentFolder = Path.GetDirectoryName(resourcesFolder)?.Replace("\\", "/"); + string folderName = Path.GetFileName(resourcesFolder); + if (string.IsNullOrEmpty(parentFolder) || string.IsNullOrEmpty(folderName)) { + AirConsoleLogger.LogError(() => $"Failed to resolve Resources folder relative to {airConsoleDirectory}"); + return null; + } + + if (!AssetDatabase.IsValidFolder(parentFolder)) { + AirConsoleLogger.LogError(() => $"Resources parent folder not found: {parentFolder}"); + return null; + } + + AssetDatabase.CreateFolder(parentFolder, folderName); + return resourcesFolder; + } + + private static string GetAirConsoleScriptDirectory() { + string[] guids = AssetDatabase.FindAssets("AirConsole t:MonoScript"); + foreach (string guid in guids) { + string path = AssetDatabase.GUIDToAssetPath(guid); + if (path.EndsWith("/AirConsole.cs")) { + return Path.GetDirectoryName(path)?.Replace("\\", "/"); + } + } + + AirConsoleLogger.LogError(() => "Unable to locate AirConsole.cs via AssetDatabase."); + return null; + } } } -#endif \ No newline at end of file +#endif diff --git a/Assets/AirConsole/scripts/Runtime/AirConsole.cs b/Assets/AirConsole/scripts/Runtime/AirConsole.cs index b8a22afb..652ed02e 100644 --- a/Assets/AirConsole/scripts/Runtime/AirConsole.cs +++ b/Assets/AirConsole/scripts/Runtime/AirConsole.cs @@ -2119,6 +2119,8 @@ private void CreateAndroidWebview(string connectionUrl) { url += "&game-id=" + Application.identifier; url += "&game-version=" + androidGameVersion; url += "&unity-version=" + Application.unityVersion; + bool nativeSizingSupported = ResolveNativeGameSizingSupport(nativeGameSizingSupported); + url += nativeSizingSupported ? "&supportsNativeGameSizing=true" : "&supportsNativeGameSizing=false"; defaultScreenHeight = Screen.height; _webViewOriginalUrl = url; @@ -2144,6 +2146,12 @@ private void CreateAndroidWebview(string connectionUrl) { } } + private static bool ResolveNativeGameSizingSupport(bool fallback) { + AirconsoleRuntimeSettings settings = Resources.Load(AirconsoleRuntimeSettings.ResourceName); + return settings ? settings.NativeGameSizingSupported : fallback; + } + + private static int GetAndroidBundleVersionCode() { AndroidJavaObject ca = UnityAndroidObjectProvider.GetUnityActivity(); AndroidJavaObject packageManager = ca.Call("getPackageManager"); diff --git a/Assets/AirConsole/scripts/Runtime/AirconsoleRuntimeSettings.cs b/Assets/AirConsole/scripts/Runtime/AirconsoleRuntimeSettings.cs new file mode 100644 index 00000000..566e6edf --- /dev/null +++ b/Assets/AirConsole/scripts/Runtime/AirconsoleRuntimeSettings.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +namespace NDream.AirConsole { + /// + /// Provides runtime access to the native game sizing setting that is configured in the editor. + /// + public sealed class AirconsoleRuntimeSettings : ScriptableObject { + public const string ResourceName = "AirconsoleRuntimeSettings"; + + [SerializeField] + private bool nativeGameSizingSupported = true; + + public bool NativeGameSizingSupported => nativeGameSizingSupported; + +#if UNITY_EDITOR + public void SetNativeGameSizingSupported(bool value) { + nativeGameSizingSupported = value; + } +#endif + } +} diff --git a/Assets/AirConsole/scripts/Runtime/AirconsoleRuntimeSettings.cs.meta b/Assets/AirConsole/scripts/Runtime/AirconsoleRuntimeSettings.cs.meta new file mode 100644 index 00000000..d82a7e28 --- /dev/null +++ b/Assets/AirConsole/scripts/Runtime/AirconsoleRuntimeSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7a108afd50e493bb30165bdf49d7ea3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/AirConsole/scripts/Runtime/Resources.meta b/Assets/AirConsole/scripts/Runtime/Resources.meta new file mode 100644 index 00000000..66b03eb4 --- /dev/null +++ b/Assets/AirConsole/scripts/Runtime/Resources.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4b32d759701649cfb42aa5ef5726d310 +timeCreated: 1761728219 \ No newline at end of file diff --git a/Assets/AirConsole/scripts/Runtime/Resources/AirconsoleRuntimeSettings.asset b/Assets/AirConsole/scripts/Runtime/Resources/AirconsoleRuntimeSettings.asset new file mode 100644 index 00000000..1afe151f --- /dev/null +++ b/Assets/AirConsole/scripts/Runtime/Resources/AirconsoleRuntimeSettings.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a7a108afd50e493bb30165bdf49d7ea3, type: 3} + m_Name: AirconsoleRuntimeSettings + m_EditorClassIdentifier: + nativeGameSizingSupported: 1 diff --git a/Assets/AirConsole/scripts/Runtime/Resources/AirconsoleRuntimeSettings.asset.meta b/Assets/AirConsole/scripts/Runtime/Resources/AirconsoleRuntimeSettings.asset.meta new file mode 100644 index 00000000..655347eb --- /dev/null +++ b/Assets/AirConsole/scripts/Runtime/Resources/AirconsoleRuntimeSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52e9777dea32a417c8b847c3272075a6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WebGLTemplates/AirConsole-2020/airconsole-unity-plugin.js b/Assets/WebGLTemplates/AirConsole-2020/airconsole-unity-plugin.js index bd5ca587..a27095ac 100644 --- a/Assets/WebGLTemplates/AirConsole-2020/airconsole-unity-plugin.js +++ b/Assets/WebGLTemplates/AirConsole-2020/airconsole-unity-plugin.js @@ -9,10 +9,12 @@ function App(container, canvas, web_config, progress_config) { var me = this; - + me.is_native_app = typeof Unity != "undefined"; me.is_editor = !!me.getURLParameterByName("unity-editor-websocket-port"); - me.top_bar_height = window.outerHeight - window.innerHeight; + if (!window.AIRCONSOLE_ANDROID_NATIVE_GAMESIZING) { + me.top_bar_height = window.outerHeight - window.innerHeight; + } me.is_unity_ready = false; me.queue = false; me.game_container = container; @@ -90,18 +92,18 @@ App.prototype.updateProgressBar = function(progress_bar, progress) { App.prototype.startNativeApp = function() { var me = this; - - if (me.airconsole.supportsNativeGameSizing) { - me.game_container.style.display = 'none'; - } + + me.game_container.style.display = 'none'; me.is_unity_ready = true; window.onbeforeunload = function() { Unity.call(JSON.stringify({ action: "onGameEnd" })); }; - Unity.call(JSON.stringify({ - action: "onUnityWebviewResize", - top_bar_height: me.top_bar_height, - })); + if (!window.AIRCONSOLE_ANDROID_NATIVE_GAMESIZING) { + Unity.call(JSON.stringify({ + action: "onUnityWebviewResize", + top_bar_height: me.top_bar_height, + })); + } // forward WebView postMessage data from parent window window.addEventListener("message", function (event) { if (event.data["action"] == "androidunity") { @@ -136,8 +138,8 @@ App.prototype.setupEditorSocket = function() { document.body.innerHTML = "
Game stopped in Unity. Please close this tab.
"; }; document.body.innerHTML = "
" - + "
You can see the game scene in the Unity Editor.

Keep this window open in the background.
" - + "
"; + + "
You can see the game scene in the Unity Editor.

Keep this window open in the background.
" + + ""; }; App.prototype.validateNotLatestApi = function () { @@ -159,14 +161,14 @@ App.prototype.validateNotLatestApi = function () { App.prototype.initAirConsole = function() { this.validateNotLatestApi(); - + var me = this; - var translation = window.AIRCONSOLE_TRANSLATION; + var translation = window.AIRCONSOLE_TRANSLATION; var silence_inactive_players = window.AIRCONSOLE_INACTIVE_PLAYERS_SILENCED; var androidNativeGameSizing = window.AIRCONSOLE_ANDROID_NATIVE_GAMESIZING; me.airconsole = new AirConsole({ "synchronize_time": true, "translation": translation, "silence_inactive_players": silence_inactive_players, "supportsNativeGameSizing": androidNativeGameSizing }); - + const version = me.airconsole.version.split('.'); if(version.length < 3 || parseInt(version[0]) < 1 || parseInt(version[1]) < 10) { confirm('Unity AirConsole Plugin 2.6.0 requires at minimum the AirConsole API version 1.10.0. Please review the upgrade instructions'); @@ -193,8 +195,9 @@ App.prototype.initAirConsole = function() { "gameSafeArea": me.airconsole.gameSafeArea }); }; - + me.airconsole.onSetSafeArea = function (safeArea) { + console.log('onSetSafeArea', safeArea); me.postToUnity({ action: 'onSetSafeArea', safeArea: safeArea @@ -302,15 +305,15 @@ App.prototype.initAirConsole = function() { App.prototype.getURLParameterByName = function(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); + results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); }; App.prototype.setupErrorHandler = function() { window.onerror = function(message) { if (message.indexOf("UnknownError") != -1 || - message.indexOf("Program terminated with exit(0)") != -1 || - message.indexOf("DISABLE_EXCEPTION_CATCHING") != -1) { + message.indexOf("Program terminated with exit(0)") != -1 || + message.indexOf("DISABLE_EXCEPTION_CATCHING") != -1) { // message already correct, but preserving order. } else if (message.indexOf("Cannot enlarge memory arrays") != -1) { window.setTimeout(function() { @@ -318,8 +321,8 @@ App.prototype.setupErrorHandler = function() { }); return false; } else if (message.indexOf("Invalid array buffer length") != -1 || - message.indexOf("out of memory") != -1 || - message.indexOf("Array buffer allocation failed") != -1) { + message.indexOf("out of memory") != -1 || + message.indexOf("Array buffer allocation failed") != -1) { alert("Your browser ran out of memory. Try restarting your browser and close other applications running on your computer."); return true; } @@ -434,6 +437,7 @@ App.prototype.processUnityData = function (data) { }; App.prototype.resizeCanvas = function() { + console.log('resizeCanvas', window.innerWidth, window.innerHeight); var aspect_ratio = this.web_config.width / this.web_config.height; var w, h; @@ -453,6 +457,7 @@ App.prototype.resizeCanvas = function() { App.prototype.onGameReady = function(autoScale) { var me = this; + console.log('onGameReady', autoScale); me.is_unity_ready = true; me.postQueue(); diff --git a/Assets/WebGLTemplates/AirConsole-U6/airconsole-unity-plugin.js b/Assets/WebGLTemplates/AirConsole-U6/airconsole-unity-plugin.js index 6c6b978d..1c556c1f 100644 --- a/Assets/WebGLTemplates/AirConsole-U6/airconsole-unity-plugin.js +++ b/Assets/WebGLTemplates/AirConsole-U6/airconsole-unity-plugin.js @@ -12,7 +12,9 @@ function App(container, canvas, web_config, progress_config) { me.is_native_app = typeof Unity != "undefined"; me.is_editor = !!me.getURLParameterByName("unity-editor-websocket-port"); - me.top_bar_height = window.outerHeight - window.innerHeight; + if (!window.AIRCONSOLE_ANDROID_NATIVE_GAMESIZING) { + me.top_bar_height = window.outerHeight - window.innerHeight; + } me.is_unity_ready = false; me.queue = false; me.game_container = container; @@ -92,17 +94,17 @@ App.prototype.updateProgressBar = function(progress_bar, progress) { App.prototype.startNativeApp = function() { var me = this; - if (me.airconsole.supportsNativeGameSizing) { - me.game_container.style.display = 'none'; - } + me.game_container.style.display = 'none'; me.is_unity_ready = true; window.onbeforeunload = function() { Unity.call(JSON.stringify({ action: "onGameEnd" })); - }; - Unity.call(JSON.stringify({ - action: "onUnityWebviewResize", - top_bar_height: me.top_bar_height, - })); + } + if (!window.AIRCONSOLE_ANDROID_NATIVE_GAMESIZING) {; + Unity.call(JSON.stringify({ + action: "onUnityWebviewResize", + top_bar_height: me.top_bar_height, + })); + } // forward WebView postMessage data from parent window window.addEventListener("message", function (event) { if (event.data["action"] == "androidunity") { diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ed8b1d..063b34fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This includes security related updates like requiring fixed Unity versions and i ### Fixed - **Android:** Platform overlay resizes correctly on Android TV +- **Android:** Native game sizing is communicated to the AirConsole platform earlier for consistent initial layout (PRO-1747) - **Editor:** Project configuration checks no update index.html directly when validating API version usage. This prevents the index.html from becoming empty. ### Changed