Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,57 @@ export async function fetchLoaderConfig(
```

You can then use it to construct a `UnityContext` and pass this context to your `UnityRenderer` via the `context` prop.

## Module augmentation

Take the following example:

```typescript
// create some context
const ctx = new UnityContext({ ... });

// handles some "info" event with one parameter of type string
ctx.on('info', (message: string) => {
console.log(message);
});
```

The parameter `message` has to be explicitly defined as `string` each time a handler of for the event name `info` would be registered.
In order to make use of TypeScript to its fullest extent, you can augment an Interface of the library to get autocompletion and type-safety features here.

Put this either in a file importing `react-unity-renderer` or create a new `unity.d.ts` somewhere in your `src` or (if you have that) `typings` directory:

```typescript
// must be imported, else the module will be redefined,
// and this causes all sorts of errors.
import 'react-unity-renderer';

// module augmentation
declare module 'react-unity-renderer' {
// this is the interface providing autocompletion
interface EventSignatures {
// "info" is the event name
// the type on the right side is anything that would match TypeScript's
// Parameters<> helper type
info: [message: string];

// also possible:
info: [string];
'some-event': [number, debug: string];
// note that all parametrs names are just labels, so they are fully optional.
}
}
```

Now, any defined event will be auto-completed with its types for `UnityContext.on(...)`:

```typescript
// create some context
const ctx = new UnityContext({ ... });

// "info" will be suggested by your IDE
// "message" is now of type string
ctx.on('info', (message) => {
console.log(message);
});
```
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export {
UnityContext,
UnityLoaderConfig,
UnityInstanceConfig,
EventSignatures,
} from './lib/context';
export { UnityRenderer, UnityRendererProps } from './components/UnityRenderer';
32 changes: 26 additions & 6 deletions src/lib/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface UnityInstanceConfig {
frameworkUrl: string;
codeUrl: string;
frameworkUrl: string;
dataUrl: string;
memoryUrl?: string;
symbolsUrl?: string;
Expand All @@ -15,7 +15,22 @@ export interface UnityLoaderConfig extends UnityInstanceConfig {
loaderUrl: string;
}

type UnityEventCallback = (...params: any) => void;
/**
* An interface containing event names and their handler parameter signatures.
* This interface is supposed to be augmented via module augmentation by the
* user.
*/
export interface EventSignatures {}

/**
* Refers to a callback function with any parameters.
*/
type EventCallback = (...params: any) => void;

/**
* Defines a weak union type, which can fallback to another type.
*/
type WeakUnion<T, F> = T | (F & {});

/**
* Defines a Unity WebGL context.
Expand All @@ -29,7 +44,7 @@ export class UnityContext {

private instance?: UnityInstance;

private eventCallbacks: { [name: string]: UnityEventCallback } = {};
private eventCallbacks: { [name: string]: EventCallback } = {};

/**
* Creates a new `UnityContext` and registers the global event callback.
Expand Down Expand Up @@ -92,7 +107,7 @@ export class UnityContext {
}

/**
* Emits a remote procedure call towards the running Unity instance.
* Emits a message to the running Unity instance.
*
* @param {string} objectName The `GameObject` on which to call the method.
* @param {string} methodName The name of the method which should be invoked.
Expand All @@ -118,7 +133,12 @@ export class UnityContext {
* @param {UnityEventCallback} callback The callback which should be invoked
* upon the occurence of this event.
*/
public on<T extends UnityEventCallback>(name: string, callback: T): void {
public on<T extends WeakUnion<keyof EventSignatures, string>>(
name: WeakUnion<keyof EventSignatures, T>,
callback: (
...params: T extends keyof EventSignatures ? EventSignatures[T] : any
) => void
): void {
this.eventCallbacks[name] = callback;
}

Expand All @@ -141,7 +161,7 @@ export class UnityContext {
* @returns {UnityEventCallback} The callback which should
* handle the event.
*/
private bridgeCallback(name: string): UnityEventCallback {
private bridgeCallback(name: string): EventCallback {
if (this.eventCallbacks && this.eventCallbacks[name])
return this.eventCallbacks[name];

Expand Down
35 changes: 32 additions & 3 deletions typings/unity.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,40 @@ declare class UnityInstance {
}

declare interface Window {
UnityBridge: (name: string) => (...params: any) => void;
/**
* Global function returning a callback for the requested event.
* Will `console.warn()` and return a dummy callback in case the specified event
* name has no registered handler.
*
* @param {string} name name of the event
*/
UnityBridge(name: string): (...params: any) => void;

/**
* Mapper to the native JavaScript function from Unity's loader script,
* which loads and renders a WebGL build inside a `<canvas>` element.
*
* @param {HTMLCanvasElement} canvas The `<canvas>` object to which the game
* should be rendered.
* @param {UnityInstanceConfig} parameters The configuration containing all
* required information to load a WebGL build.
* @param {(progress: number) => void} [onProgress] Callback function
* for Unity loading progress changes. Ranges from `0` to `1.0`.
*/
createUnityInstance(
element: HTMLCanvasElement,
parameters: UnityInstanceConfig,
canvas: HTMLCanvasElement,
config: {
codeUrl: string;
frameworkUrl: string;
dataUrl: string;
memoryUrl?: string;
symbolsUrl?: string;
streamingAssetsUrl?: string;
companyName?: string;
productName?: string;
productVersion?: string;
modules?: { [key: string]: any };
},
onProgress?: (progress: number) => void
): Promise<UnityInstance>;
}