Ok, I’m not really sure this is even a sensible thing for Unity to try implementing BUT it would be neat if, for parity with other platforms, OnPause was triggered.
Currently I don’t think it is which means, for my game at least which involves gamestate progressing based on time elapsing, players ‘lose’ progress because the onpause handlers that are there to work out how much time passed while paused … don’t get called.
On laptops closing the screen is functionally equivalent to backgrounding (pausing) a mobile app. Would be neat if we could use the same logic for both.
JavaScript code and native application have different access to the system events. Could you describe expected behavior in more details, providing specific use case examples?
For example, the following events can be potentially tracked:
user switched to/from another browser tab or minimized/restored the browser window
computer woke up from sleep/hibernation (not 100% reliable)
Can your problem be described in such terms?
Hi Alex, thanks for the reply. It’s the sleep/hibernation detection that’s missing currently.
In states where the tab gets ‘backgrounded’ like by switching tabs everything will keep running but with a less frequent updates - using Time.unscaledDeltaTime instead of Time.deltaTime works around that.
The issue is where the browser gets hibernated - there’s no way to keep the game state updating when this happens and no (Unity) notification that this state occurs so no way for a dev to do something about it.
As far as I know, there is no way to track the moment when computer is going to sleep/hibernate from JavaScript (i.e. if you want to notify other players about this event). You can still try do this externally, for example, the build can send keep-alives to the server, so when keep-alives are no longer received, the server can assume that the game has been closed or paused. Unfortunately, there does not seem to be a reliable way to differentiate between pause and close (until computer wakes up), you might still try to do some tricks from the unload handler when the tab is closed, but it will not work reliably in situations when the browser itself is closed or crashes.
Nevertheless, you are able to track the moment when computer wakes up from sleep/hibernation. All you have to do is to periodically execute some function and check the difference between its execution times. When the difference is bigger than expected, you can assume that execution has been suspended.
A simple approach would therefore be:
function testAlive() {
var time = Date.now();
if (testAlive.time && time - testAlive.time > 5000 && Module["calledRun"])
SendMessage("GameObject", "ResumeEvent", time - testAlive.time);
testAlive.time = time;
setTimeout(testAlive, 1000);
}
testAlive();
Note that you should make sure that Module[“calledRun”] has been set before you use SendMessage, otherwise you might end up with a runtime error.
The disadvantage of the simple approach is that while running on the main thread, this code would block every time when a popup dialog appears (like alert or prompt) or some computative synchronous task is in progress (like synchronous XMLHttpRequest or asm.js module compilation). To avoid those issues, you may run it in a separate thread (using a worker). Add the following /Assets/Plugins/testAlive.jspre plugin to your project (modify the setTimeout period and delay threshold according to your needs):
function testAlive() {
var time = Date.now();
if (testAlive.time && time - testAlive.time > 5000)
postMessage(time - testAlive.time);
testAlive.time = time;
setTimeout(testAlive, 1000);
}
var workerUrl = URL.createObjectURL(new Blob([testAlive.toString(), "testAlive();"], { type: "text/javascript" }));
var worker = new Worker(workerUrl);
worker.onmessage = function (e) { if (Module["calledRun"]) SendMessage("GameObject", "ResumeEvent", e.data); };
URL.revokeObjectURL(workerUrl);
And the C# handler code for the message:
using UnityEngine;
using System.Collections;
public class GameObjectScript : MonoBehaviour {
void ResumeEvent(int milliseconds) {
Debug.Log("The game has been paused for " + milliseconds + " ms");
}
}
Now the ResumeEvent should be called each time after execution has been suspended for longer than 5 seconds due to sleep, hibernation or other reason.
Epic reply! Thank you for taking the time to work this all out, clearly not a simple solution here.
For my specific use-case I can’t trust the local machine time (players will use it to cheat, a lot). I also believe this approach would need to have a really long (1min+) timer to stop false-positives from when the browser switches tabs and decides to run everything slower (?).
I do happen to have a time-server setup already in the game that I can see ways to use (could compare a known server time + unscaled time passed vs. next timer server timestamps) … fiddly though!
Thanks again for the reply … maybe these events should be a part of the way browsers work already? Who do I suggest that to?
Actually it is the opposite. In fact, the code does not run any slower when you switch the tab or minimize the window. It is just the frame rendering callback which is being executed less often due to the requestAnimationFrame period being adjusted (normally from monitor refresh rate to 1 fps). You can even modify this behavior by manually setting the frame rate. This makes the main thread less busy, therefore the chances of a false-positive hibernation check (when you run it in the main thread) will actually decrease, and not increase.
Note that this does not apply to the solution running in a worker (suggested above) in the first place, because it will be running in a separate thread. You can easily check on that if you execute the following JavaScript function while your game is active:
function blockMainThread(milliseconds) {
for (var unblockTime = Date.now() + milliseconds; Date.now() < unblockTime;);
}
Your game will get completely frozen for the specified period of time and even the browser window might become unresponsive (which depends on the browser), but the hibernation check will continue to work without any issues at all, because it runs in a separate thread.
Note that you do not have to use the system time, you may as well perform requests to your server (i.e. for time synchronization and checks) directly from the JavaScript worker.
There is also a more reliable way to detect this event, which you can just add to a .jspre plugin:
document.addEventListener("visibilitychange", function() {
console.log("The game is " + document.visibilityState);
});
You may also use the boolean document.hidden value instead of the document.visibilityState string.
Thanks again Alex, given me a few things to think about. Interesting to learn that you can manually set the frame rate to keep the game updating more frequently while in backgrounded states!
Attach the following script to an object named MyObject:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
public class MyScript : MonoBehaviour {
[DllImport("__Internal")]
private static extern void registerVisibilityChangeEvent();
void Start() {
registerVisibilityChangeEvent();
}
void OnVisibilityChange(string visibilityState) {
System.Console.WriteLine("[" + System.DateTime.Now + "] the game switched to " + (visibilityState == "visible" ? "foreground" : "background"));
}
}
add the following /Assets/Plugins/visibilityChangeEvent.jslib plugin:
mergeInto(LibraryManager.library, {
registerVisibilityChangeEvent: function () {
document.addEventListener("visibilitychange", function () {
SendMessage("MyObject", "OnVisibilityChange", document.visibilityState);
});
if (document.visibilityState != "visible")
SendMessage("MyObject", "OnVisibilityChange", document.visibilityState);
},
});
A small note:
One might assume that the game always starts in foreground, however, that is not always the case, because user can switch the browser tab while the game is still loading. In this case document.visibilityState can still be occasionally set to “hidden” at the time when your script starts, so you might want to explicitly notify your application about it at the time of event registration (see the plugin code above).
To Update this, OnApplicationFocus detects Tab/Switch and focus lost/gained on Unity 2021.3LTS. For some reason is not triggering this when you minimize the whole browser. To detect this I had to use Alexsurovov script. In firefox takes a second and a half to trigger but works…
Still, the callbacks of states not working properly. Because of that, every developer need to implement the already invented thing; State Callbacks. I literally dont want to implement any of those and want to use the Unity itself i mean isnt what a game engine there for? I think instead of supporting every browser, you should make the browser support your system instead, right? But after wasting hours for that topic, this is not the case.
After some researches i see almost every web browser really lacks of state callbacks. Because of that, most of the developers are shouting out but there is no luck at all because of the bad implementation of their browser or the base of their browser “Chromium”. Their attempts of speeding up the browser and attempting to support every platform with one base is killing the callbacks one by one and the obvious example would be “OnUnload” event to be not reliable which is ridicilious hahaha