Skip to content

Commit 3911828

Browse files
author
Philipp Molitor
committed
improve docs and readme, fix typings, add error callback
1 parent a26f8a8 commit 3911828

File tree

12 files changed

+3173
-91
lines changed

12 files changed

+3173
-91
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ module.exports = {
2626
tsconfigRootDir: __dirname,
2727
project: ['./tsconfig.json'],
2828
},
29-
ignorePatterns: ['build/**'],
29+
ignorePatterns: ['dist/**'],
3030
globals: {
3131
React: true,
3232
JSX: true,

.npmignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ src/
77
.husky/
88
.prettierrc
99
.eslintrc.js
10+
.eslintcache
11+
tsconfig.json
12+
13+
# jest
14+
__tests__/*
15+
*.test.ts
16+
*.test.tsx
1017

1118
# npm/yarn
1219
yarn*.log

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ TODO
2121
NPM
2222

2323
```
24-
npm install --save-dev react-unity-renderer
24+
npm install --save react-unity-renderer
2525
```
2626

2727
Yarn
@@ -32,9 +32,9 @@ yarn add react-unity-renderer
3232

3333
**Version compatability**
3434

35-
| NPM version | Unity version | `package.json` dependency |
36-
| ----------- | ----------------- | ------------------------------------- |
37-
| `1.2020` | `2020` and `2021` | `"react-unity-renderer": "1.2020.*",` |
35+
| Unity version | NPM version |
36+
| ----------------- | ----------- |
37+
| `2020` and `2021` | `2020.*` |
3838

3939
## Example usage
4040

@@ -76,8 +76,10 @@ export const UnityGameComponent: VFC = (): JSX.Element => {
7676
return (
7777
<UnityRenderer
7878
context={ctx}
79+
// optional state information callbacks
7980
onUnityProgressChange={(p) => setProgress(p)}
8081
onUnityReadyStateChange={(s) => setReady(s)}
82+
onUnityError={(e) => console.error(e)}
8183
// <UnityRenderer> has every prop (except ref) from HTMLCanvasElement.
8284
// This means you can use something like style!
8385
// Also it works perfectly with styled-components.
@@ -145,7 +147,9 @@ function fetchLoaderConfig(baseUrl: string): Promise<UnityLoaderConfig> {
145147
let result: AxiosResponse<UnityLoaderConfig>;
146148

147149
try {
150+
// this disables caching!
148151
const url = `${baseUrl}/build.json?t=${new Date().getTime()}`;
152+
149153
result = await axios.get<UnityLoaderConfig>(url);
150154
} catch (ex) {
151155
// network or request error

package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"name": "react-unity-renderer",
3-
"version": "1.2020.1",
3+
"version": "2020.0.0",
44
"description": "React Unity Renderer allows to interactively embed Unity WebGL builds into a React powered project.",
55
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
67
"author": "Philipp Molitor <philipp@molitor.cloud>",
78
"license": "MIT",
89
"repository": "git://github.com/PhilippMolitor/react-unity-renderer.git",
@@ -16,7 +17,7 @@
1617
],
1718
"scripts": {
1819
"watch": "tsc --build ./tsconfig.json --watch",
19-
"build": "tsc --build ./tsconfig.json",
20+
"build": "rimraf dist && tsc --build ./tsconfig.json",
2021
"test": "echo \"no tests defined yet\" && exit 1",
2122
"lint": "eslint --fix ./src/",
2223
"lint:staged": "lint-staged",
@@ -31,7 +32,8 @@
3132
]
3233
},
3334
"devDependencies": {
34-
"@types/react": "^17.*",
35+
"@types/react": "^16.8.0",
36+
"@types/react-dom": "^16.8.0",
3537
"@typescript-eslint/eslint-plugin": "^4.15.2",
3638
"eslint": "^7.20.0",
3739
"eslint-config-airbnb-typescript": "^12.0.0",
@@ -45,9 +47,11 @@
4547
"husky": "^5.1.1",
4648
"lint-staged": "^10.5.4",
4749
"prettier": "^2.2.1",
50+
"rimraf": "^3.0.2",
4851
"typescript": "^4.*"
4952
},
5053
"peerDependencies": {
51-
"react": ">= 17"
54+
"react": "^16.8.0, >= 17",
55+
"react-dom": "^16.8.0, >=17"
5256
}
5357
}

src/components/UnityRenderer.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type UnityRendererProps = Omit<
1313
context: UnityContext;
1414
onUnityProgressChange?: (progress: number) => void;
1515
onUnityReadyStateChange?: (ready: boolean) => void;
16+
onUnityError?: (error: Error) => void;
1617
};
1718

1819
/**
@@ -29,9 +30,10 @@ export const UnityRenderer: VFC<UnityRendererProps> = ({
2930
context,
3031
onUnityProgressChange,
3132
onUnityReadyStateChange,
33+
onUnityError,
3234
...canvasProps
3335
}: UnityRendererProps): JSX.Element | null => {
34-
const [service] = useState<UnityLoaderService>(new UnityLoaderService());
36+
const [loader, setLoader] = useState<UnityLoaderService>();
3537

3638
// We cannot actually render the `HTMLCanvasElement`, so we need the `ref`
3739
// for Unity and a `JSX.Element` for React rendering.
@@ -68,16 +70,15 @@ export const UnityRenderer: VFC<UnityRendererProps> = ({
6870
* @returns {Promise<void>} A Promise resolving on successful mount of the
6971
* Unity instance.
7072
*/
71-
async function mountUnityInstance(): Promise<void> {
72-
if (!renderer) return;
73-
73+
async function mount(): Promise<void> {
7474
// get the current loader configuration from the UnityContext
7575
const c = context.getConfig();
7676

7777
// attach Unity's native JavaScript loader
78-
await service.attachLoader(c.loaderUrl);
79-
const nativeUnityInstance = await window.createUnityInstance(
80-
renderer,
78+
await loader!.execute(c.loaderUrl);
79+
80+
const instance = await window.createUnityInstance(
81+
renderer!,
8182
{
8283
dataUrl: c.dataUrl,
8384
frameworkUrl: c.frameworkUrl,
@@ -91,20 +92,24 @@ export const UnityRenderer: VFC<UnityRendererProps> = ({
9192
);
9293

9394
// set the instance for further JavaScript <--> Unity communication
94-
context.setInstance(nativeUnityInstance);
95+
context.setInstance(instance);
9596
}
9697

9798
// on canvas change
9899
useEffect(() => {
99-
if (renderer)
100-
mountUnityInstance().catch((e) => {
101-
// eslint-disable-next-line no-console
102-
console.error('failed to mount unity instance: ', e);
100+
if (loader && renderer)
101+
mount().catch((e) => {
102+
if (onUnityError) onUnityError(e);
103+
if (onUnityProgressChange) onUnityProgressChange(0);
104+
if (onUnityReadyStateChange) onUnityReadyStateChange(false);
103105
});
104-
}, [renderer]);
106+
}, [loader, renderer]);
105107

106108
// on mount
107109
useEffect(() => {
110+
// create the loader service
111+
setLoader(new UnityLoaderService());
112+
108113
// create the renderer and let the ref callback set its handle
109114
setCanvas(
110115
createElement('canvas', {
@@ -117,7 +122,8 @@ export const UnityRenderer: VFC<UnityRendererProps> = ({
117122
return () => {
118123
context.shutdown(() => {
119124
// remove the loader script from the DOM
120-
service.detachLoader();
125+
if (loader) loader.unmount();
126+
121127
// reset progress / ready state
122128
if (onUnityProgressChange) onUnityProgressChange(0);
123129
if (onUnityReadyStateChange) onUnityReadyStateChange(false);

src/context.ts

Lines changed: 90 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,62 @@
11
import './global';
22
import { UnityLoaderConfig } from './interfaces/config';
33

4+
export type UnityEventCallback = (...params: any) => void;
5+
6+
/**
7+
* Defines a Unity WebGL context.
8+
*
9+
* The context is responsible for the initial startup parameters of the
10+
* `UnityRenderer`, as well as receiving events and emitting RPCs to Unity.
11+
*
12+
*/
413
export class UnityContext {
14+
private config: UnityLoaderConfig;
15+
516
private instance?: UnityInstance;
617

7-
private config: UnityLoaderConfig;
18+
private eventCallbacks: { [name: string]: UnityEventCallback } = {};
819

20+
/**
21+
* Creates a new `UnityContext` and registers the global event callback.
22+
*
23+
* @param {UnityLoaderConfig} config A loader configuration object.
24+
*/
925
constructor(config: UnityLoaderConfig) {
1026
this.config = config;
11-
if (!window.UnityBridge) window.UnityBridge = {};
27+
28+
// callback is needed so it can access the actual eventCallbacks object
29+
if (!window.UnityBridge)
30+
window.UnityBridge = (name: string) => this.bridgeCallback(name);
1231
}
1332

33+
/**
34+
* Retrieves the currently activte loader configuration.
35+
*
36+
* @returns {UnityLoaderConfig} The current loader configuration object.
37+
*/
1438
public getConfig(): UnityLoaderConfig {
1539
return this.config;
1640
}
1741

18-
public setInstance(unityInstance: UnityInstance): void {
19-
this.instance = unityInstance;
42+
/**
43+
* Sets the Unity instance this `UnityContext` is responsible for.
44+
*
45+
* @param {UnityInstance} instance The running Unity instance
46+
*/
47+
public setInstance(instance: UnityInstance): void {
48+
this.instance = instance;
2049
}
2150

51+
/**
52+
* Shuts down the running Unity instance, then unregisters the existing
53+
* event handlers.
54+
*
55+
* @param {() => void} onShutdownFinished Callback to execute when the
56+
* shutdown has been completed.
57+
*
58+
* @returns {void} void
59+
*/
2260
public shutdown(onShutdownFinished?: () => void): void {
2361
if (!this.instance) return;
2462

@@ -36,34 +74,69 @@ export class UnityContext {
3674
);
3775
}
3876

39-
public emitUnityEvent(
77+
/**
78+
* Emits a remote procedure call towards the running Unity instance.
79+
*
80+
* @param {string} objectName The `GameObject` on which to call the method.
81+
* @param {string} methodName The name of the method which should be invoked.
82+
* @param {(string | number)} value The value to pass to the method
83+
* as the first parameter.
84+
* @returns {void} void
85+
*/
86+
public emit(
4087
objectName: string,
4188
methodName: string,
42-
value?: string | number | boolean
89+
value?: string | number
4390
): void {
4491
if (!this.instance) return;
4592

4693
this.instance.SendMessage(objectName, methodName, value);
4794
}
4895

49-
public on<T extends (...params: any) => void = () => void>(
50-
name: string,
51-
callback: T
52-
): void {
53-
window.UnityBridge[name] = callback;
54-
}
55-
56-
public off(name: string) {
57-
if (name in window.UnityBridge) window.UnityBridge[name] = () => undefined;
96+
/**
97+
* Delegates an event handler to handle an event (from Unity) by using a
98+
* callback function.
99+
*
100+
* @param {string} name The (unique) name of the event.
101+
* @param {UnityEventCallback} callback The callback which should be invoked
102+
* upon the occurence of this event.
103+
*/
104+
public on<T extends UnityEventCallback>(name: string, callback: T): void {
105+
this.eventCallbacks[name] = callback;
58106
}
59107

108+
/**
109+
* Enables or disables fullscreen mode.
110+
*
111+
* @param {booolean} enabled Whether to enable or disable fullscreen.
112+
* @returns {void} void
113+
*/
60114
public setFullscreen(enabled: boolean): void {
61115
if (!this.instance) return;
62116
this.instance.SetFullscreen(enabled ? 1 : 0);
63117
}
64118

119+
/**
120+
* The internal handler for any incoming event.
121+
* Logs a warning for events with names that are not registered.
122+
*
123+
* @param {string} name The name of the event.
124+
* @returns {UnityEventCallback} The callback which should
125+
* handle the event.
126+
*/
127+
private bridgeCallback(name: string): UnityEventCallback {
128+
if (this.eventCallbacks && this.eventCallbacks[name])
129+
return this.eventCallbacks[name];
130+
131+
// eslint-disable-next-line no-console
132+
console.warn(`called event "${name}" which currently is not registered`);
133+
return () => undefined;
134+
}
135+
136+
/**
137+
* Unregisters all event handlers.
138+
*/
65139
private unregisterAllEventHandlers() {
66-
if (!window.UnityBridge || typeof window.UnityBridge !== 'object') return;
67-
Object.keys(window.UnityBridge).forEach((k) => this.off(k));
140+
this.eventCallbacks = {};
68141
}
69142
}

src/global.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// eslint-disable-next-line @typescript-eslint/no-unused-vars
22
interface Window {
3-
UnityBridge: {
4-
[event: string]: (...params: any) => void;
5-
};
3+
UnityBridge: (name: string) => (...params: any) => void;
64

75
createUnityInstance(
86
element: HTMLCanvasElement,

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/* eslint-disable import/no-cycle */
2-
export { UnityContext } from './context';
2+
export { UnityContext, UnityEventCallback } from './context';
33
export { UnityRenderer, UnityRendererProps } from './components/UnityRenderer';
44
export { UnityLoaderConfig } from './interfaces/config';

src/loader.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ export class UnityLoaderService {
33

44
private script?: HTMLScriptElement;
55

6-
public attachLoader(url: string): Promise<void> {
6+
/**
7+
* Creates a new `<script>` tag in the DOM which executes the Unity WebGL
8+
* loader.
9+
*
10+
* @param {string} url The url of the Unity WebGL loader script.
11+
* @returns {Promise<void>} A `Promise` that resolves on successful
12+
* script execution.
13+
*/
14+
public execute(url: string): Promise<void> {
715
// eslint-disable-next-line consistent-return
816
return new Promise<void>((resolve, reject): void => {
917
// already loaded
@@ -27,7 +35,10 @@ export class UnityLoaderService {
2735
});
2836
}
2937

30-
public detachLoader(): void {
38+
/**
39+
* Removes the loaders `<script>` tag from the DOM.
40+
*/
41+
public unmount(): void {
3142
this.script?.remove();
3243
}
3344
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@
1919
"jsx": "react-jsx"
2020
},
2121
"include": ["src", ".eslintrc.js", "typings/**/*.d.ts"],
22-
"exclude": ["node_modules", "dist"]
22+
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/*.test.tsx"]
2323
}

0 commit comments

Comments
 (0)