javascriptunity-game-enginemapboxunity-webgl

WebGL build is giving errors after I added my own location input


I am building a location based game in unity as a WebGL build. The file was building before I added my script changes but the location input was not working on site (Hosted on itch.io). I then added a javascript file and some changes to the location services (The location input script was imported from Mapbox api) using chatgpt. Now the file itself is not building.

javascript file:

var EXPORTED_FUNCTIONS = ['_requestWebGLLocation'];
mergeInto(LibraryManager.library, {
    requestWebGLLocation: function() {
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition(
                function(position) {
                    unityInstance.SendMessage('LocationProviderFactory', 
                    'OnLocationReceived',
                        position.coords.latitude + ',' +  
                     position.coords.longitude);
                },
                function(error) {
                    console.error('Error obtaining location: ', error);
                }
            );
        } else {
            console.error('Geolocation is not supported by this browser.');
        }
    }
})

Location Input code:

#if !UNITY_EDITOR
#define NOT_UNITY_EDITOR
#endif

namespace Mapbox.Unity.Location
{
    using UnityEngine;
    using Mapbox.Unity.Map;
    using System.Text.RegularExpressions;
    using System.Runtime.InteropServices;


    /// <summary>
    /// Singleton factory to allow easy access to various LocationProviders.
    /// This is meant to be attached to a game object.
    /// </summary>
    public class LocationProviderFactory : MonoBehaviour
    {
        [SerializeField]
        public AbstractMap mapManager;

        [SerializeField]
        [Tooltip("Provider using Unity's builtin 'Input.Location' service")]
        AbstractLocationProvider _deviceLocationProviderUnity;

        [SerializeField]
        [Tooltip("Custom native Android location provider. If this is not set above 
        provider is used")]
        DeviceLocationProviderAndroidNative _deviceLocationProviderAndroid;

        [SerializeField]
        AbstractLocationProvider _editorLocationProvider;

        [SerializeField]
        AbstractLocationProvider _transformLocationProvider;

        [SerializeField]
        bool _dontDestroyOnLoad;

        [DllImport("__Internal")]
        private static extern void requestWebGLLocation();



        /// <summary>
        /// The singleton instance of this factory.
        /// </summary>
        private static LocationProviderFactory _instance;
        public static LocationProviderFactory Instance
        {
            get
            {
                return _instance;
            }

            private set
            {
                _instance = value;
            }
        }

        ILocationProvider _defaultLocationProvider;

        /// <summary>
        /// The default location provider. 
        /// Outside of the editor, this will be a <see 
        cref="T:Mapbox.Unity.Location.DeviceLocationProvider"/>.
        /// In the Unity editor, this will be an <see 
        cref="T:Mapbox.Unity.Location.EditorLocationProvider"/>
        /// </summary>
        /// <example>
        /// Fetch location to set a transform's position:
        /// <code>
        /// void Update()
        /// {
        ///     var locationProvider = 
        LocationProviderFactory.Instance.DefaultLocationProvider;
        ///     transform.position = 
        Conversions.GeoToWorldPosition(locationProvider.Location,
        ///                                                         
        MapController.ReferenceTileRect.Center,
        ///                                                         
        MapController.WorldScaleFactor).ToVector3xz();
        /// }
        /// </code>
        /// </example>
        public ILocationProvider DefaultLocationProvider
        {
            get
            {
                return _defaultLocationProvider;
            }
            set
            {
                _defaultLocationProvider = value;
            }
        }

        /// <summary>
        /// Returns the serialized <see 
        cref="T:Mapbox.Unity.Location.TransformLocationProvider"/>.
        /// </summary>
        public ILocationProvider TransformLocationProvider
        {
            get
            {
                return _transformLocationProvider;
            }
        }

        /// <summary>
        /// Returns the serialized <see 
        cref="T:Mapbox.Unity.Location.EditorLocationProvider"/>.
        /// </summary>
        public ILocationProvider EditorLocationProvider
        {
            get
            {
                return _editorLocationProvider;
            }
        }

        /// <summary>
        /// Returns the serialized <see 
        cref="T:Mapbox.Unity.Location.DeviceLocationProvider"/>
        /// </summary>
        public ILocationProvider DeviceLocationProvider
        {
            get
            {
                return _deviceLocationProviderUnity;
            }
        }

        /// <summary>
        /// Create singleton instance and inject the DefaultLocationProvider upon 
        Initialization of this component. 
        /// </summary>
        protected virtual void Awake()
        {
            if (Instance != null)
            {
                DestroyImmediate(gameObject);
                return;
            }
            Instance = this;

            if (_dontDestroyOnLoad)
            {
                DontDestroyOnLoad(gameObject);
            }

            InjectEditorLocationProvider();
            InjectDeviceLocationProvider();
        }

        /// <summary>
        /// Injects the editor location provider.
        /// Depending on the platform, this method and calls to it will be stripped 
        during compile.
        /// </summary>
        [System.Diagnostics.Conditional("UNITY_EDITOR")]
        void InjectEditorLocationProvider()
        {
            Debug.LogFormat("LocationProviderFactory: Injected EDITOR Location Provider 
        - {0}", _editorLocationProvider.GetType());
            DefaultLocationProvider = _editorLocationProvider;
        }

        /// <summary>
        /// Injects the device location provider.
        /// Depending on the platform, this method and calls to it will be stripped 
        during compile.
        /// </summary>
        [System.Diagnostics.Conditional("NOT_UNITY_EDITOR")]
        void InjectDeviceLocationProvider()
        {
            if (Application.platform == RuntimePlatform.WebGLPlayer)
            {
                Debug.Log("Using WebGL Geolocation.");
                requestWebGLLocation();
            }
            else
            {
                int AndroidApiVersion = 0;
                var regex = new Regex(@"(?<=API-)-?\d+");
                Match match = regex.Match(SystemInfo.operatingSystem);
                if (match.Success) { int.TryParse(match.Groups[0].Value, out 
                AndroidApiVersion); }
                Debug.LogFormat("{0} => API version: {1}", SystemInfo.operatingSystem, 
                AndroidApiVersion);

                if (Application.platform == RuntimePlatform.Android && 
                AndroidApiVersion >= 24)
                {
                    DefaultLocationProvider = _deviceLocationProviderAndroid;
                }
                else
                {
                    DefaultLocationProvider = _deviceLocationProviderUnity;
                }
            }
        }

        public void OnLocationReceived(string locationData)
        {
            string[] coordinates = locationData.Split(',');
            if (coordinates.Length == 2)
            {
                float latitude = float.Parse(coordinates[0]);
                float longitude = float.Parse(coordinates[1]);
                Debug.Log($"WebGL Location Received: Latitude: {latitude}, Longitude: 
                {longitude}");
                // You can further process this location data as required
            }
        }

    }
}

Build errors:

  1. Library\Bee\artifacts\WebGL\build\debug_WebGL_wasm\build.js: undefined symbol: requestWebGLLocation (referenced by top-level compiled C/C++ code) UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

  2. Library\Bee\artifacts\WebGL\build\debug_WebGL_wasm\build.js: Aborting compilation due to previous errors UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

  3. Building Library\Bee\artifacts\WebGL\build\debug_WebGL_wasm\build.js failed with output: error: undefined symbol: requestWebGLLocation (referenced by top-level compiled C/C++ code) warning: Link with -s LLD_REPORT_UNDEFINED to get more information on undefined symbols warning: To disable errors for undefined symbols use -s ERROR_ON_UNDEFINED_SYMBOLS=0 warning: _requestWebGLLocation may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library Error: Aborting compilation due to previous errors emcc: error: '"C:/Program Files/Unity/Hub/Editor/2022.3.46f1/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/Emscripten/node/node.exe" "C:\Program Files\Unity\Hub\Editor\2022.3.46f1\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten\emscripten\src\compiler.js" C:\Users\Nifty\AppData\Local\Temp\tmpmbvsgucj.json' failed (returned 1) UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)


Solution

  • tl;dr: Change the file type from .js to .jslib


    The error itself is simple: The _requestWebGLLocation method/function is missing on the JavaScript side.

    You said that your file is a plain normal .js file. Unity will treat this one basically as any other text file asset and drop it as it isn't referenced anywhere.

    For the Unity WebGL build process to actually recognize it as a plug-in and include your JavaScript code into the webassembly it needs to rather be a proper .jslib plug-in file!

    See https://docs.unity3d.com/Manual/web-interacting-browser-js.html


    The

    var EXPORTED_FUNCTIONS = ['_requestWebGLLocation']; 
    

    shouldn't be required actually


    Also instead of using SendMessage you can rather pass in a callback and directly call it which I find way cleaner and less error prone and it is also more efficient as you directly pass on the method pointer rather than have the engine search for the according object and method by name

    See https://docs.unity3d.com/Manual/web-interacting-browser-example.html