@pizzapopcorn/unijs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # UniJS - WebGL Interop (Library)
2
+
3
+ ## Prerequisites
4
+ - Your Unity WebGL build must have the [Unity plugin package](https://github.com/PizzaPopcorn/unijs) installed and implemented (instructions provided in the README of that repo).
5
+ - If you want to customize the library you need to have [Node.js](https://nodejs.org) installed.
6
+
7
+ ## Installation
8
+ IMPORTANT: You don't have to clone/fork this repository if you just want to use the library without modifying it. Use CDN in that case.
9
+
10
+ For getting the latest version in your project just paste this line in the html page that will load the Unity instance:
11
+ ```javascript
12
+ <script src="https://cdn.jsdelivr.net/npm/@pizzapopcorn/unijs/dist/unity.min.js"></script>
13
+ ```
14
+ Now if you want to customize the library you can clone/fork this repository and follow the build steps.
15
+
16
+ ## Build
17
+ Building is pretty straightforward, just do the modifications you need and then run the following command:
18
+ ```bash
19
+ npm run build
20
+ ```
21
+ That will create `unity.js` and `unity.min.js` files that you can manually import to your web page project.
22
+
23
+ ## Additional Notes
24
+ Notice that this library is designed to be only compatible with browser since that's where Unity runs. That being said, there is no need to import the npm package in any scenario, it just exists for the sake of the CDN download.
package/dist/unity.js ADDED
@@ -0,0 +1,347 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Unity = factory());
5
+ })(this, (function () { 'use strict';
6
+
7
+ class GameObject {
8
+
9
+ static keyGameObjects = {};
10
+ static #lifeCycleCallbacks = {
11
+ awake: {},
12
+ start: {},
13
+ enable: {},
14
+ disable: {},
15
+ destroy: {}
16
+ }
17
+
18
+ static GetKeyGameObject(key){
19
+ if(!GameObject.keyGameObjects.hasOwnProperty(key)) {
20
+ console.error(`GameObject with key '${key}' not found`);
21
+ return null;
22
+ }
23
+ return GameObject.keyGameObjects[key];
24
+ }
25
+
26
+ static onAwake(key, callback){
27
+ if(!GameObject.#lifeCycleCallbacks.awake.hasOwnProperty(key)) {
28
+ GameObject.#lifeCycleCallbacks.awake[key] = new Set();
29
+ }
30
+ GameObject.#lifeCycleCallbacks.awake[key].add(callback);
31
+ }
32
+
33
+ static onStart(key, callback) {
34
+ if(!GameObject.#lifeCycleCallbacks.start.hasOwnProperty(key)) {
35
+ GameObject.#lifeCycleCallbacks.start[key] = new Set();
36
+ }
37
+ GameObject.#lifeCycleCallbacks.start[key].add(callback);
38
+ }
39
+
40
+ static onEnable(key, callback) {
41
+ if(!GameObject.#lifeCycleCallbacks.enable.hasOwnProperty(key)) {
42
+ GameObject.#lifeCycleCallbacks.enable[key] = new Set();
43
+ }
44
+ GameObject.#lifeCycleCallbacks.enable[key].add(callback);
45
+ }
46
+
47
+ static onDisable(key, callback) {
48
+ if(!GameObject.#lifeCycleCallbacks.disable.hasOwnProperty(key)) {
49
+ GameObject.#lifeCycleCallbacks.disable[key] = new Set();
50
+ }
51
+ GameObject.#lifeCycleCallbacks.disable[key].add(callback);
52
+ }
53
+
54
+ static onDestroy(key, callback) {
55
+ if(!GameObject.#lifeCycleCallbacks.destroy.hasOwnProperty(key)) {
56
+ GameObject.#lifeCycleCallbacks.destroy[key] = new Set();
57
+ }
58
+ GameObject.#lifeCycleCallbacks.destroy[key].add(callback);
59
+ }
60
+
61
+ static _register(key, data) {
62
+ GameObject.keyGameObjects[key] = new GameObject(key, data);
63
+ }
64
+
65
+ static _receiveLifeCycleEvent(key, event) {
66
+ const gameObject = GameObject.keyGameObjects[key];
67
+ if(!gameObject) return;
68
+
69
+ const callbacks = GameObject.#lifeCycleCallbacks[event][key];
70
+ if(callbacks) {
71
+ for(const callback of callbacks) {
72
+ callback(gameObject);
73
+ }
74
+ }
75
+ if(event === "destroy") {
76
+ gameObject.transform = null;
77
+ delete GameObject.keyGameObjects[key];
78
+ }
79
+ }
80
+
81
+ constructor(key, data) {
82
+ this.key = key;
83
+ this.name = data.name;
84
+ this.transform = data.transform;
85
+ this.hierarchyPath = data.hasOwnProperty("hierarchyPath") ? data.hierarchyPath : "";
86
+ }
87
+
88
+ SetActive(active) {
89
+ this?.#invokeGameObjectEvent("gameObject.setActive", active);
90
+ }
91
+
92
+ InvokeMethod(methodName, paramType = "", paramValue = "") {
93
+ const { Unity } = require('./Unity');
94
+ paramType = Unity.types[paramType] || paramType;
95
+ this?.#invokeGameObjectEvent("gameObject.invokeMethod", { methodName: methodName, parameterType: paramType, parameterValue: paramValue });
96
+ }
97
+
98
+ GetChild(query) {
99
+ const eventName = typeof query === "string" ? "gameObject.findChild" : "gameObject.getChild";
100
+ const childData = this?.#invokeGameObjectEvent(eventName, query);
101
+ if(childData !== null) {
102
+ const currentPath = this.hierarchyPath === "" ? this.key : this.hierarchyPath;
103
+ childData.hierarchyPath = currentPath + "/" + childData.name;
104
+ return new GameObject(this.key, childData);
105
+ }
106
+ return null;
107
+ }
108
+
109
+ Translate(x, y, z) {
110
+ this?.#invokeGameObjectEvent("transform.translate", { x: x, y: y, z: z });
111
+ }
112
+
113
+ Rotate(x, y, z) {
114
+ this?.#invokeGameObjectEvent("transform.rotate", { x: x, y: y, z: z });
115
+ }
116
+
117
+ SetLocalScale(x, y, z) {
118
+ this?.#invokeGameObjectEvent("transform.setLocalScale", { x: x, y: y, z: z });
119
+ }
120
+
121
+ SetLocalPosition(x, y, z) {
122
+ this?.#invokeGameObjectEvent("transform.setLocalPosition", { x: x, y: y, z: z });
123
+ }
124
+
125
+ SetText(text) {
126
+ this?.#invokeGameObjectEvent("text.setText", text);
127
+ }
128
+
129
+ Destroy() {
130
+ this?.#invokeGameObjectEvent("gameObject.destroy", "");
131
+ }
132
+
133
+ #invokeGameObjectEvent(eventName, payload) {
134
+ if(!this.transform) return null;
135
+
136
+ let payloadJson = payload;
137
+ if(typeof payload === "object") payloadJson = JSON.stringify(payload);
138
+ else if(typeof payload !== "string") payloadJson = payload.toString();
139
+ const eventPayload = { eventName: eventName, hierarchyPath: this.hierarchyPath, payloadJson: payloadJson, listenDisabled: true };
140
+
141
+ const { Unity } = require('./Unity');
142
+ const response = Unity.InvokeEvent(`GOEvent:${this.key}`, JSON.stringify(eventPayload));
143
+
144
+ console.log(`Invoked Event: GOEvent:${this.key}`, eventPayload);
145
+
146
+ if(response === null || !response.hasOwnProperty("ok")){
147
+ console.error(`Invalid JSON response from GameObject event callback: ${response}`);
148
+ return null;
149
+ }
150
+
151
+ if(response.ok) {
152
+ let responseObj = response.responseJson;
153
+ try {
154
+ responseObj = JSON.parse(response.responseJson);
155
+ } catch {}
156
+ return responseObj;
157
+ }
158
+ console.error(`Error invoking GameObject event: ${eventName}`, response.error);
159
+ return null;
160
+ }
161
+ }
162
+
163
+ class Unity {
164
+ /** @type {typeof GameObject} */
165
+ static GameObject = GameObject;
166
+ static internalLogs = false;
167
+ static types = {
168
+ int: "System.Int32",
169
+ float: "System.Single",
170
+ double: "System.Double",
171
+ bool: "System.Boolean",
172
+ string: "System.String",
173
+ char: "System.Char",
174
+ byte: "System.Byte",
175
+ long: "System.Int64",
176
+ short: "System.Int16",
177
+ decimal: "System.Decimal",
178
+ object: "System.Object",
179
+ customClass: (className, namespace = "", assembly = "Assembly-CSharp") => {
180
+ const qualifiedName = namespace === "" ? className : `${namespace}.${className}`;
181
+ return `${qualifiedName}, ${assembly}.dll`;
182
+ },
183
+ };
184
+
185
+ static #clientEventCallback;
186
+ static #instanceReady = false;
187
+ static #onInstanceReadyListeners = new Set();
188
+ static #onEventListeners = {};
189
+
190
+ static LoadInstance(url, elementId) {
191
+ Unity.#instanceReady = false;
192
+ const r = new XMLHttpRequest();
193
+ r.open("GET", url + "/index.html", true);
194
+ r.onreadystatechange = function () {
195
+ if (r.readyState !== 4 || r.status !== 200) return;
196
+ document.querySelector(`#${elementId}`).innerHTML = r.responseText;
197
+
198
+ const link = document.createElement('link');
199
+
200
+ link.rel = 'stylesheet';
201
+ link.type = 'text/css';
202
+ link.href = url + "/TemplateData/style.css";
203
+ document.head.appendChild(link);
204
+
205
+ const indexScript = document.createElement("script");
206
+ indexScript.src = url + "/index.js";
207
+ document.body.appendChild(indexScript);
208
+ };
209
+ r.send();
210
+ }
211
+
212
+ static GetBuildVersion() {
213
+ return Unity.InvokeEvent("InstanceEvent:GetBuildVersion");
214
+ }
215
+
216
+ static InvokeEvent(eventName, payload = undefined) {
217
+ const responseJson = Unity.#invokeEventInternal(eventName, payload);
218
+ try {
219
+ const response = JSON.parse(responseJson);
220
+ if(response.hasOwnProperty("promiseId")){
221
+ console.warn(`Event '${eventName}' returned a promise. Consider using InvokeEventAsync instead.`);
222
+ Unity.onEvent(`PromiseResolvedEvent:${response.promiseId}`, payload => {
223
+ delete Unity.#onEventListeners[`PromiseResolvedEvent:${response.promiseId}`];
224
+ });
225
+ }
226
+ return response;
227
+ }
228
+ catch {
229
+ return responseJson;
230
+ }
231
+ }
232
+
233
+ static async InvokeEventAsync(eventName, payload = undefined) {
234
+ return new Promise(resolve => {
235
+ const responseJson = Unity.#invokeEventInternal(eventName, payload);
236
+ try {
237
+ const response = JSON.parse(responseJson);
238
+ if(response.hasOwnProperty("promiseId")){
239
+ Unity.onEvent(`PromiseResolvedEvent:${response.promiseId}`, payload => {
240
+ resolve(payload);
241
+ delete Unity.#onEventListeners[`PromiseResolvedEvent:${response.promiseId}`];
242
+ });
243
+ }
244
+ else {
245
+ resolve(response);
246
+ }
247
+ } catch {
248
+ resolve(responseJson);
249
+ }
250
+ })
251
+ }
252
+
253
+ static async WaitForEndOfFrameAsync() {
254
+ return new Promise((resolve) => {
255
+ const eventId = crypto.randomUUID().toString();
256
+ const eventName = `EndOfFrameEvent:${eventId}`;
257
+
258
+ Unity.onEvent(eventName, () => {
259
+ resolve();
260
+ delete Unity.#onEventListeners[eventName];
261
+ });
262
+
263
+ Unity.InvokeEvent("InstanceEvent:WaitForEndOfFrame", eventId);
264
+ });
265
+ }
266
+
267
+ static async LoadBundleAsync(bundleUrl) {
268
+ await Unity.InvokeEventAsync("InstanceEvent:LoadBundle", bundleUrl);
269
+ }
270
+
271
+ static async InstantiatePrefabFromBundleAsync(bundleUrl, prefabName, parentKey = "") {
272
+ await Unity.InvokeEventAsync("InstanceEvent:InstantiatePrefabFromBundle", {
273
+ bundleUrl: bundleUrl,
274
+ prefabName: prefabName,
275
+ parentKey: parentKey
276
+ });
277
+ }
278
+
279
+ // Listeners -----------------------------
280
+
281
+ static onInstanceReady(callback) {
282
+ if(!Unity.#instanceReady) {
283
+ Unity.#onInstanceReadyListeners.add(callback);
284
+ }
285
+ else {
286
+ callback();
287
+ }
288
+ }
289
+
290
+ static onEvent(eventName, callback) {
291
+ if(!Unity.#onEventListeners.hasOwnProperty(eventName)) {
292
+ Unity.#onEventListeners[eventName] = new Set();
293
+ }
294
+ Unity.#onEventListeners[eventName].add(callback);
295
+ }
296
+
297
+ static offEvent(eventName, callback) {
298
+ if(!Unity.#onEventListeners.hasOwnProperty(eventName)) return;
299
+ Unity.#onEventListeners[eventName].delete(callback);
300
+ }
301
+
302
+ static #invokeEventInternal(eventName, payload) {
303
+ if(payload === undefined || payload === null) payload = "";
304
+ let payloadJson = payload;
305
+ if(typeof payload === "object") payloadJson = JSON.stringify(payload);
306
+ else if(typeof payload !== "string") payloadJson = payload.toString();
307
+ const responseJson = Unity.#clientEventCallback(eventName, payloadJson);
308
+ Unity.#onEventListeners[eventName]?.forEach(callback => callback(payloadJson));
309
+ return responseJson;
310
+ }
311
+
312
+ // JSLib usage -----------------------------
313
+
314
+ /**Only for unity js library use*/
315
+ static _instanceReady() {
316
+ Unity.#instanceReady = true;
317
+ Unity.#onInstanceReadyListeners.forEach(callback => callback());
318
+ }
319
+
320
+ /**Only for unity js library use*/
321
+ static _registerClientListener(callback) {
322
+ Unity.#clientEventCallback = callback;
323
+ }
324
+
325
+ /**Only for unity js library use*/
326
+ static _receiveEvent(eventName, payloadJson) {
327
+ let payload = payloadJson;
328
+ try {
329
+ payload = JSON.parse(payloadJson);
330
+ } catch {}
331
+
332
+ Unity.InvokeEvent(eventName, payload);
333
+ }
334
+
335
+ /**Only for unity js library use*/
336
+ static _logFromUnity(verbosity, message) {
337
+ if(verbosity === "internal" && !Unity.internalLogs) return;
338
+ if(verbosity === "error") console.error(`[Unity] ${message}`);
339
+ else if(verbosity === "warning") console.warn(`[Unity] ${message}`);
340
+ else console.log(`[Unity] ${message}`);
341
+ }
342
+
343
+ }
344
+
345
+ return Unity;
346
+
347
+ }));
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Unity=t()}(this,function(){"use strict";class e{static keyGameObjects={};static#e={awake:{},start:{},enable:{},disable:{},destroy:{}};static GetKeyGameObject(t){return e.keyGameObjects.hasOwnProperty(t)?e.keyGameObjects[t]:(console.error(`GameObject with key '${t}' not found`),null)}static onAwake(t,n){e.#e.awake.hasOwnProperty(t)||(e.#e.awake[t]=new Set),e.#e.awake[t].add(n)}static onStart(t,n){e.#e.start.hasOwnProperty(t)||(e.#e.start[t]=new Set),e.#e.start[t].add(n)}static onEnable(t,n){e.#e.enable.hasOwnProperty(t)||(e.#e.enable[t]=new Set),e.#e.enable[t].add(n)}static onDisable(t,n){e.#e.disable.hasOwnProperty(t)||(e.#e.disable[t]=new Set),e.#e.disable[t].add(n)}static onDestroy(t,n){e.#e.destroy.hasOwnProperty(t)||(e.#e.destroy[t]=new Set),e.#e.destroy[t].add(n)}static _register(t,n){e.keyGameObjects[t]=new e(t,n)}static _receiveLifeCycleEvent(t,n){const s=e.keyGameObjects[t];if(!s)return;const a=e.#e[n][t];if(a)for(const e of a)e(s);"destroy"===n&&(s.transform=null,delete e.keyGameObjects[t])}constructor(e,t){this.key=e,this.name=t.name,this.transform=t.transform,this.hierarchyPath=t.hasOwnProperty("hierarchyPath")?t.hierarchyPath:""}SetActive(e){this?.#t("gameObject.setActive",e)}InvokeMethod(e,t="",n=""){const{Unity:s}=require("./Unity");t=s.types[t]||t,this?.#t("gameObject.invokeMethod",{methodName:e,parameterType:t,parameterValue:n})}GetChild(t){const n="string"==typeof t?"gameObject.findChild":"gameObject.getChild",s=this?.#t(n,t);if(null!==s){const t=""===this.hierarchyPath?this.key:this.hierarchyPath;return s.hierarchyPath=t+"/"+s.name,new e(this.key,s)}return null}Translate(e,t,n){this?.#t("transform.translate",{x:e,y:t,z:n})}Rotate(e,t,n){this?.#t("transform.rotate",{x:e,y:t,z:n})}SetLocalScale(e,t,n){this?.#t("transform.setLocalScale",{x:e,y:t,z:n})}SetLocalPosition(e,t,n){this?.#t("transform.setLocalPosition",{x:e,y:t,z:n})}SetText(e){this?.#t("text.setText",e)}Destroy(){this?.#t("gameObject.destroy","")}#t(e,t){if(!this.transform)return null;let n=t;"object"==typeof t?n=JSON.stringify(t):"string"!=typeof t&&(n=t.toString());const s={eventName:e,hierarchyPath:this.hierarchyPath,payloadJson:n,listenDisabled:!0},{Unity:a}=require("./Unity"),r=a.InvokeEvent(`GOEvent:${this.key}`,JSON.stringify(s));if(console.log(`Invoked Event: GOEvent:${this.key}`,s),null===r||!r.hasOwnProperty("ok"))return console.error(`Invalid JSON response from GameObject event callback: ${r}`),null;if(r.ok){let e=r.responseJson;try{e=JSON.parse(r.responseJson)}catch{}return e}return console.error(`Error invoking GameObject event: ${e}`,r.error),null}}class t{static GameObject=e;static internalLogs=!1;static types={int:"System.Int32",float:"System.Single",double:"System.Double",bool:"System.Boolean",string:"System.String",char:"System.Char",byte:"System.Byte",long:"System.Int64",short:"System.Int16",decimal:"System.Decimal",object:"System.Object",customClass:(e,t="",n="Assembly-CSharp")=>`${""===t?e:`${t}.${e}`}, ${n}.dll`};static#n;static#s=!1;static#a=new Set;static#r={};static LoadInstance(e,n){t.#s=!1;const s=new XMLHttpRequest;s.open("GET",e+"/index.html",!0),s.onreadystatechange=function(){if(4!==s.readyState||200!==s.status)return;document.querySelector(`#${n}`).innerHTML=s.responseText;const t=document.createElement("link");t.rel="stylesheet",t.type="text/css",t.href=e+"/TemplateData/style.css",document.head.appendChild(t);const a=document.createElement("script");a.src=e+"/index.js",document.body.appendChild(a)},s.send()}static GetBuildVersion(){return t.InvokeEvent("InstanceEvent:GetBuildVersion")}static InvokeEvent(e,n=void 0){const s=t.#o(e,n);try{const n=JSON.parse(s);return n.hasOwnProperty("promiseId")&&(console.warn(`Event '${e}' returned a promise. Consider using InvokeEventAsync instead.`),t.onEvent(`PromiseResolvedEvent:${n.promiseId}`,e=>{delete t.#r[`PromiseResolvedEvent:${n.promiseId}`]})),n}catch{return s}}static async InvokeEventAsync(e,n=void 0){return new Promise(s=>{const a=t.#o(e,n);try{const e=JSON.parse(a);e.hasOwnProperty("promiseId")?t.onEvent(`PromiseResolvedEvent:${e.promiseId}`,n=>{s(n),delete t.#r[`PromiseResolvedEvent:${e.promiseId}`]}):s(e)}catch{s(a)}})}static async WaitForEndOfFrameAsync(){return new Promise(e=>{const n=crypto.randomUUID().toString(),s=`EndOfFrameEvent:${n}`;t.onEvent(s,()=>{e(),delete t.#r[s]}),t.InvokeEvent("InstanceEvent:WaitForEndOfFrame",n)})}static async LoadBundleAsync(e){await t.InvokeEventAsync("InstanceEvent:LoadBundle",e)}static async InstantiatePrefabFromBundleAsync(e,n,s=""){await t.InvokeEventAsync("InstanceEvent:InstantiatePrefabFromBundle",{bundleUrl:e,prefabName:n,parentKey:s})}static onInstanceReady(e){t.#s?e():t.#a.add(e)}static onEvent(e,n){t.#r.hasOwnProperty(e)||(t.#r[e]=new Set),t.#r[e].add(n)}static offEvent(e,n){t.#r.hasOwnProperty(e)&&t.#r[e].delete(n)}static#o(e,n){null==n&&(n="");let s=n;"object"==typeof n?s=JSON.stringify(n):"string"!=typeof n&&(s=n.toString());const a=t.#n(e,s);return t.#r[e]?.forEach(e=>e(s)),a}static _instanceReady(){t.#s=!0,t.#a.forEach(e=>e())}static _registerClientListener(e){t.#n=e}static _receiveEvent(e,n){let s=n;try{s=JSON.parse(n)}catch{}t.InvokeEvent(e,s)}static _logFromUnity(e,n){("internal"!==e||t.internalLogs)&&("error"===e?console.error(`[Unity] ${n}`):"warning"===e?console.warn(`[Unity] ${n}`):console.log(`[Unity] ${n}`))}}return t});
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@pizzapopcorn/unijs",
3
+ "version": "0.0.1",
4
+ "description": "A seamless interop layer to control Unity WebGL builds from the browser's JavaScript context.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "unijs",
8
+ "unityjs",
9
+ "unity.js",
10
+ "unity",
11
+ "uni",
12
+ "js",
13
+ "javascript",
14
+ "interop",
15
+ "web",
16
+ "webgl"
17
+ ],
18
+ "homepage": "https://github.com/PizzaPopcorn/unijs-lib#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/PizzaPopcorn/unijs-lib/issues"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/PizzaPopcorn/unijs-lib.git"
25
+ },
26
+ "author": "Andres Urrutia <urrutiac.andres@gmail.com> (https://portfolio.sharpdevmx.com)",
27
+ "type": "module",
28
+ "devDependencies": {
29
+ "@rollup/plugin-terser": "^0.4.4",
30
+ "rollup": "^4.59.0"
31
+ },
32
+ "scripts": {
33
+ "build": "rollup -c",
34
+ "test": "echo \"Error: no test specified\" && exit 1"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ]
39
+ }