unity-webgl
Version:
Unity-WebGL provides an easy solution for embedding Unity WebGL builds in your web projects, with two-way communication between your webApp and Unity application with advanced API's.
243 lines (218 loc) • 6.87 kB
text/typescript
import { UnityWebglEvent } from './event'
import { unityLoader } from './loader'
import { isBrowser, isObject, omit, queryCanvas, log } from './utils'
import { UnityArguments } from './types/unity-arguments'
import { UnityInstance } from './types/unity-instance'
import { UnityConfig } from './types/unity-config'
type CanvasElementOrString = HTMLCanvasElement | string
function createUnityArgs(ctx: UnityWebgl, config: UnityConfig): UnityArguments {
const unityArgs: UnityArguments = omit(config, ['loaderUrl'])
unityArgs.print = function (msg: string) {
ctx.emit('debug', msg)
}
unityArgs.printError = function (msg: string) {
ctx.emit('error', msg)
}
return unityArgs
}
class UnityWebgl extends UnityWebglEvent {
private _config: UnityConfig
private _unity: UnityInstance | null = null
private _loader: (() => void) | null = null
private _canvas: HTMLCanvasElement | null = null
constructor(canvas: CanvasElementOrString, config: UnityConfig)
constructor(config: UnityConfig)
constructor(canvas: CanvasElementOrString | UnityConfig, config?: UnityConfig) {
super()
if (!(typeof canvas === 'string' || canvas instanceof HTMLCanvasElement || isObject(canvas))) {
throw new TypeError('Parameter canvas is not valid')
}
// config
if (isObject(canvas)) {
config = canvas as UnityConfig
}
if (
!config ||
!config.loaderUrl ||
!config.dataUrl ||
!config.frameworkUrl ||
!config.codeUrl
) {
throw new TypeError('UnityConfig is not valid')
}
this._config = config
// canvas
if (typeof canvas === 'string' || canvas instanceof HTMLCanvasElement) {
this.render(canvas)
}
}
/**
* @deprecated Use `render()` instead.
*/
create(canvas: CanvasElementOrString): Promise<void> {
return this.render(canvas)
}
/**
* Renders the UnityInstance into the target html canvas element.
* @param canvas The target html canvas element.
*/
render(canvas: CanvasElementOrString): Promise<void> {
if (!isBrowser) return Promise.resolve()
if (this._unity && this._canvas && this._loader) {
log.warn('UnityInstance already created')
return Promise.resolve()
}
return new Promise((resolve, reject) => {
try {
const $canvas = queryCanvas(canvas)
if (!$canvas) {
throw new Error('CanvasElement is not found')
}
this._canvas = $canvas
const ctx = this
// Create UnityInstance Arguments
const unityArgs = createUnityArgs(this, this._config)
this.emit('beforeMount', this)
this._loader = unityLoader(this._config.loaderUrl, {
resolve() {
window
.createUnityInstance($canvas, unityArgs, (val: number) => ctx.emit('progress', val))
.then((ins: UnityInstance) => {
ctx._unity = ins
ctx.emit('mounted', ctx, ins)
resolve()
})
.catch((err) => {
throw err
})
},
reject(err) {
throw err
},
})
} catch (err) {
this._unity = null
this.emit('error', err)
reject(err)
}
})
}
/**
* Sends a message to the UnityInstance to invoke a public method.
* @param {string} objectName Unity scene name.
* @param {string} methodName public method name.
* @param {any} value an optional method parameter.
* @returns
*/
sendMessage(objectName: string, methodName: string, value?: any) {
if (!this._unity) {
log.warn('Unable to Send Message while Unity is not Instantiated.')
return this
}
if (value === undefined || value === null) {
this._unity.SendMessage(objectName, methodName)
} else {
const _value = typeof value === 'object' ? JSON.stringify(value) : value
this._unity.SendMessage(objectName, methodName, _value)
}
return this
}
/**
* @deprecated Use `sendMessage()` instead.
*/
send(objectName: string, methodName: string, value?: any) {
return this.sendMessage(objectName, methodName, value)
}
/**
* Asynchronously ask for the pointer to be locked on current canvas.
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/requestPointerLock
*/
requestPointerLock(): void {
if (!this._unity || !this._unity.Module.canvas) {
log.warn('Unable to requestPointerLock while Unity is not Instantiated.')
return
}
this._unity.Module.canvas.requestPointerLock()
}
/**
* Takes a screenshot of the canvas and returns a base64 encoded string.
* @param {string} dataType Defines the type of screenshot, e.g "image/jpeg"
* @param {number} quality Defines the quality of the screenshot, e.g 0.92
* @returns A base 64 encoded string of the screenshot.
*/
takeScreenshot(dataType?: string, quality?: any): string | undefined {
if (!this._unity || !this._unity.Module.canvas) {
log.warn('Unable to take Screenshot while Unity is not Instantiated.')
return
}
return this._unity.Module.canvas.toDataURL(dataType, quality)
}
/**
* Enables or disabled the Fullscreen mode of the Unity Instance.
* @param {boolean} enabled
*/
setFullscreen(enabled: boolean) {
if (!this._unity) {
log.warn('Unable to set Fullscreen while Unity is not Instantiated.')
return
}
this._unity.SetFullscreen(enabled ? 1 : 0)
}
/**
* Quits the Unity instance and clears it from memory so that Unmount from the DOM.
*/
unload(): Promise<void> {
if (!this._unity) {
log.warn('Unable to Quit Unity while Unity is not Instantiated.')
return Promise.reject()
}
this.emit('beforeUnmount', this)
// Unmount unity.loader.js from DOM
if (typeof this._loader === 'function') {
this._loader()
this._loader = null
}
// Unmount unityInstance from memory
return this._unity
.Quit()
.then(() => {
this._unity = null
this._canvas = null
this.clear()
this.emit('unmounted')
})
.catch((err) => {
log.error('Unable to Unload Unity')
this.emit('error', err)
throw err
})
}
/**
* 保障Unity组件可以安全卸载. 在unity实例从内存中销毁之前保障Dom存在.
*
* Warning! This is a workaround for the fact that the Unity WebGL instances
* which are build with Unity 2021.2 and newer cannot be unmounted before the
* Unity Instance is unloaded.
*/
unsafe_unload(): Promise<void> {
try {
if (!this._unity || !this._unity.Module.canvas) {
log.warn('No UnityInstance found.')
return Promise.reject()
}
// Re-attaches the canvas to the body element of the document. This way it
// wont be removed from the DOM when the component is unmounted. Then the
// canvas will be hidden while it is being unloaded.
const canvas = this._unity.Module.canvas as HTMLCanvasElement
document.body.appendChild(canvas)
canvas.style.display = 'none'
return this.unload().then(() => {
canvas.remove()
})
} catch (e) {
return Promise.reject(e)
}
}
}
export default UnityWebgl
export type { UnityArguments, UnityConfig, UnityInstance }