From 518c0abfabe0c9e8f1e6d5e444fbf98b6066b824 Mon Sep 17 00:00:00 2001 From: larrywang0701 <113608053+larrywang0701@users.noreply.github.com> Date: Mon, 9 Oct 2023 06:22:52 +0800 Subject: [PATCH] Unity Academy upgrades (#251) --- src/bundles/unity_academy/UnityAcademy.tsx | 165 +++-- .../unity_academy/UnityAcademyMaths.ts | 6 +- src/bundles/unity_academy/functions.ts | 612 ++++++++++++------ src/bundles/unity_academy/index.ts | 60 +- src/tabs/UnityAcademy/index.tsx | 27 +- 5 files changed, 607 insertions(+), 263 deletions(-) diff --git a/src/bundles/unity_academy/UnityAcademy.tsx b/src/bundles/unity_academy/UnityAcademy.tsx index 97a633857..6b321a369 100644 --- a/src/bundles/unity_academy/UnityAcademy.tsx +++ b/src/bundles/unity_academy/UnityAcademy.tsx @@ -26,6 +26,7 @@ type StudentGameObject = { onCollisionExitMethod : Function | null; transform : Transform; rigidbody : RigidbodyData | null; + audioSource : AudioSourceData | null; customProperties : any; isDestroyed : boolean; // [set by interop] }; @@ -43,12 +44,32 @@ type RigidbodyData = { angularDrag : number; }; +type AudioSourceData = { + audioClipIdentifier : AudioClipIdentifier; + playSpeed : number; + playProgress : number; + volume : number; + isLooping : boolean; + isPlaying : boolean; +}; + + declare const createUnityInstance : Function; // This function comes from {BUILD_NAME}.loader.js in Unity Academy Application (For Example: ua-frontend-prod.loader.js) export function getInstance() : UnityAcademyJsInteropContext { return (window as any).unityAcademyContext as UnityAcademyJsInteropContext; } +type AudioClipInternalName = string; + +export class AudioClipIdentifier { // A wrapper class to store identifier string and prevent users from using arbitrary string for idenfitier + audioClipInternalName : AudioClipInternalName; + constructor(audioClipInternalName : string) { + this.audioClipInternalName = audioClipInternalName; + } +} + + export class GameObjectIdentifier { // A wrapper class to store identifier string and prevent users from using arbitrary string for idenfitier gameObjectIdentifier : string; constructor(gameObjectIdentifier : string) { @@ -143,7 +164,7 @@ const UNITY_CONFIG = { streamingAssetsUrl: `${UNITY_ACADEMY_BACKEND_URL}webgl_assetbundles`, companyName: 'Wang Zihan @ NUS SoC 2026', productName: 'Unity Academy (Source Academy Embedding Version)', - productVersion: 'prod-2023.4', + productVersion: 'See \'About\' in the embedded frontend.', }; @@ -164,6 +185,8 @@ class UnityAcademyJsInteropContext { public dimensionMode; private isShowingUnityAcademy : boolean; // [get by interop] private latestUserAgreementVersion : string; + private audioClipStorage : AudioClipInternalName[]; + private audioClipIdentifierSerialCounter = 0; constructor() { this.unityInstance = null; @@ -176,6 +199,7 @@ class UnityAcademyJsInteropContext { this.loadPrefabInfo(); this.studentActionQueue = []; this.studentGameObjectStorage = {}; + this.audioClipStorage = []; this.guiData = []; this.input = { keyboardInputInfo: {}, @@ -299,6 +323,7 @@ class UnityAcademyJsInteropContext { private resetModuleData() { this.studentActionQueue = []; this.studentGameObjectStorage = {}; + this.audioClipStorage = []; this.gameObjectIdentifierSerialCounter = 0; this.input.keyboardInputInfo = {}; this.guiData = []; @@ -364,6 +389,8 @@ class UnityAcademyJsInteropContext { } instantiate2DSpriteUrlInternal(sourceImageUrl : string) : GameObjectIdentifier { + // Use percent-encoding "%7C" to replace all '|' characters as '|' is used as the data separator in student action strings in Unity Academy Embedded Frontend. + sourceImageUrl = sourceImageUrl.replaceAll('|', '%7C'); const gameObjectIdentifier = `2DSprite_${this.gameObjectIdentifierSerialCounter}`; this.gameObjectIdentifierSerialCounter++; this.makeGameObjectDataStorage(gameObjectIdentifier); @@ -379,6 +406,22 @@ class UnityAcademyJsInteropContext { return new GameObjectIdentifier(gameObjectIdentifier); } + instantiateAudioSourceInternal(audioClipIdentifier : AudioClipIdentifier) { + const gameObjectIdentifier = `AudioSource_${this.gameObjectIdentifierSerialCounter}`; + this.gameObjectIdentifierSerialCounter++; + this.makeGameObjectDataStorage(gameObjectIdentifier); + this.studentGameObjectStorage[gameObjectIdentifier].audioSource = { + audioClipIdentifier, + playSpeed: 1, + playProgress: 0, + volume: 1, + isLooping: false, + isPlaying: false, + }; + this.dispatchStudentAction(`instantiateAudioSourceGameObject|${gameObjectIdentifier}|${audioClipIdentifier.audioClipInternalName}`); + return new GameObjectIdentifier(gameObjectIdentifier); + } + destroyGameObjectInternal(gameObjectIdentifier : GameObjectIdentifier) : void { this.dispatchStudentAction(`destroyGameObject|${gameObjectIdentifier.gameObjectIdentifier}`); } @@ -396,6 +439,7 @@ class UnityAcademyJsInteropContext { scale: new Vector3(1, 1, 1), }, rigidbody: null, + audioSource: null, customProperties: {}, isDestroyed: false, }; @@ -428,31 +472,31 @@ class UnityAcademyJsInteropContext { return new this[propName](name); } - getGameObjectTransformProp(propName : string, gameObjectIdentifier : GameObjectIdentifier) : Array { + getGameObjectTransformProp(propName : string, gameObjectIdentifier : GameObjectIdentifier) : Vector3 { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - return [gameObject.transform[propName].x, gameObject.transform[propName].y, gameObject.transform[propName].z]; + return new Vector3(gameObject.transform[propName].x, gameObject.transform[propName].y, gameObject.transform[propName].z); } - setGameObjectTransformProp(propName : string, gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { + setGameObjectTransformProp(propName : string, gameObjectIdentifier : GameObjectIdentifier, newValue : Vector3) : void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - gameObject.transform[propName].x = x; - gameObject.transform[propName].y = y; - gameObject.transform[propName].z = z; + gameObject.transform[propName].x = newValue.x; + gameObject.transform[propName].y = newValue.y; + gameObject.transform[propName].z = newValue.z; } getDeltaTime() : number { return this.deltaTime; } - translateWorldInternal(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { + translateWorldInternal(gameObjectIdentifier : GameObjectIdentifier, deltaPosition : Vector3) : void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - gameObject.transform.position.x += x; - gameObject.transform.position.y += y; - gameObject.transform.position.z += z; + gameObject.transform.position.x += deltaPosition.x; + gameObject.transform.position.y += deltaPosition.y; + gameObject.transform.position.z += deltaPosition.z; } - translateLocalInternal(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { + translateLocalInternal(gameObjectIdentifier : GameObjectIdentifier, deltaPosition : Vector3) : void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rotation = gameObject.transform.rotation; @@ -467,18 +511,18 @@ class UnityAcademyJsInteropContext { [cos(rx) * sin(rz) + sin(rx) * sin(ry) * cos(rz), cos(rx) * cos(rz) - sin(rx) * sin(ry) * sin(rz), -sin(rx) * cos(ry)], [sin(rx) * sin(rz) - cos(rx) * sin(ry) * cos(rz), cos(rx) * sin(ry) * sin(rz) + sin(rx) * cos(rz), cos(rx) * cos(ry)]]; const finalWorldTranslateVector = [ - rotationMatrix[0][0] * x + rotationMatrix[0][1] * y + rotationMatrix[0][2] * z, - rotationMatrix[1][0] * x + rotationMatrix[1][1] * y + rotationMatrix[1][2] * z, - rotationMatrix[2][0] * x + rotationMatrix[2][1] * y + rotationMatrix[2][2] * z, + rotationMatrix[0][0] * deltaPosition.x + rotationMatrix[0][1] * deltaPosition.y + rotationMatrix[0][2] * deltaPosition.z, + rotationMatrix[1][0] * deltaPosition.x + rotationMatrix[1][1] * deltaPosition.y + rotationMatrix[1][2] * deltaPosition.z, + rotationMatrix[2][0] * deltaPosition.x + rotationMatrix[2][1] * deltaPosition.y + rotationMatrix[2][2] * deltaPosition.z, ]; gameObject.transform.position.x += finalWorldTranslateVector[0]; gameObject.transform.position.y += finalWorldTranslateVector[1]; gameObject.transform.position.z += finalWorldTranslateVector[2]; } - lookAtPositionInternal(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { + lookAtPositionInternal(gameObjectIdentifier : GameObjectIdentifier, position : Vector3) : void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - const deltaVector = normalizeVector(new Vector3(x - gameObject.transform.position.x, y - gameObject.transform.position.y, z - gameObject.transform.position.z)); + const deltaVector = normalizeVector(new Vector3(position.x - gameObject.transform.position.x, position.y - gameObject.transform.position.y, position.z - gameObject.transform.position.z)); const eulerX = Math.asin(-deltaVector.y); const eulerY = Math.atan2(deltaVector.x, deltaVector.z); gameObject.transform.rotation.x = eulerX * 180 / Math.PI; @@ -492,19 +536,22 @@ class UnityAcademyJsInteropContext { return pointDistance(gameObjectA.transform.position, gameObjectB.transform.position); } - rotateWorldInternal(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { + rotateWorldInternal(gameObjectIdentifier : GameObjectIdentifier, angles : Vector3) : void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); - gameObject.transform.rotation.x += x; - gameObject.transform.rotation.y += y; - gameObject.transform.rotation.z += z; + gameObject.transform.rotation.x += angles.x; + gameObject.transform.rotation.y += angles.y; + gameObject.transform.rotation.z += angles.z; } - copyTransformPropertiesInternal(propName : string, from : GameObjectIdentifier, to : GameObjectIdentifier, delta_x : number, delta_y : number, delta_z : number) : void { + copyTransformPropertiesInternal(propName : string, from : GameObjectIdentifier, to : GameObjectIdentifier, deltaValues : Vector3) : void { const fromGameObject = this.getStudentGameObject(from); const toGameObject = this.getStudentGameObject(to); - if (Math.abs(delta_x) !== Infinity) toGameObject.transform[propName].x = fromGameObject.transform[propName].x + delta_x; - if (Math.abs(delta_y) !== Infinity) toGameObject.transform[propName].y = fromGameObject.transform[propName].y + delta_y; - if (Math.abs(delta_z) !== Infinity) toGameObject.transform[propName].z = fromGameObject.transform[propName].z + delta_z; + const deltaX = deltaValues.x; + const deltaY = deltaValues.y; + const deltaZ = deltaValues.z; + if (Math.abs(deltaX) !== 999999) toGameObject.transform[propName].x = fromGameObject.transform[propName].x + deltaX; + if (Math.abs(deltaY) !== 999999) toGameObject.transform[propName].y = fromGameObject.transform[propName].y + deltaY; + if (Math.abs(deltaZ) !== 999999) toGameObject.transform[propName].z = fromGameObject.transform[propName].z + deltaZ; } getKeyState(keyCode : string) : number { @@ -538,18 +585,18 @@ class UnityAcademyJsInteropContext { return gameObject.rigidbody; } - getRigidbodyVelocityVector3Prop(propName : string, gameObjectIdentifier : GameObjectIdentifier) : Array { + getRigidbodyVelocityVector3Prop(propName : string, gameObjectIdentifier : GameObjectIdentifier) : Vector3 { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); - return [rigidbody[propName].x, rigidbody[propName].y, rigidbody[propName].z]; + return new Vector3(rigidbody[propName].x, rigidbody[propName].y, rigidbody[propName].z); } - setRigidbodyVelocityVector3Prop(propName : string, gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { + setRigidbodyVelocityVector3Prop(propName : string, gameObjectIdentifier : GameObjectIdentifier, newValue : Vector3) : void { const gameObject = this.getStudentGameObject(gameObjectIdentifier); const rigidbody = this.getRigidbody(gameObject); - rigidbody[propName].x = x; - rigidbody[propName].y = y; - rigidbody[propName].z = z; + rigidbody[propName].x = newValue.x; + rigidbody[propName].y = newValue.y; + rigidbody[propName].z = newValue.z; } getRigidbodyNumericalProp(propName : string, gameObjectIdentifier : GameObjectIdentifier) : number { @@ -570,8 +617,8 @@ class UnityAcademyJsInteropContext { rigidbody.useGravity = useGravity; } - addImpulseForceInternal(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { - this.dispatchStudentAction(`addImpulseForce|${gameObjectIdentifier.gameObjectIdentifier}|${x.toString()}|${y.toString()}|${z.toString()}`); + addImpulseForceInternal(gameObjectIdentifier : GameObjectIdentifier, force : Vector3) : void { + this.dispatchStudentAction(`addImpulseForce|${gameObjectIdentifier.gameObjectIdentifier}|${force.x.toString()}|${force.y.toString()}|${force.z.toString()}`); } removeColliderComponentsInternal(gameObjectIdentifier : GameObjectIdentifier) : void { @@ -603,31 +650,69 @@ class UnityAcademyJsInteropContext { return this.getGameObjectIdentifierForPrimitiveGameObject('MainCamera'); } - onGUI_Label(content : string, x : number, y : number, fontSize : number) : void { - content = content.replaceAll('|', ''); // operator '|' is reserved as gui data separator in Unity Academy + onGUI_Label(content : string, x : number, y : number) : void { + // Temporarily use "<%7C>" to replace all '|' characters as '|' is used as the data separator in GUI data in Unity Academy Embedded Frontend. + // In Unity Academy Embedded Frontend, "<%7C>" will be replaced back to '|' when displaying the text in GUI + content = content.replaceAll('|', '<%7C>'); const newLabel = { type: 'label', content, x, y, - fontSize, }; this.guiData.push(newLabel); } - onGUI_Button(text : string, x: number, y : number, fontSize : number, onClick : Function) : void { - text = text.replaceAll('|', ''); // operator '|' is reserved as gui data separator in Unity Academy + onGUI_Button(text : string, x: number, y : number, width : number, height : number, onClick : Function) : void { + // Temporarily use "<%7C>" to replace all '|' characters as '|' is used as the data separator in GUI data in Unity Academy Embedded Frontend. + // In Unity Academy Embedded Frontend, "<%7C>" will be replaced back to '|' when displaying the text in GUI + text = text.replaceAll('|', '<%7C>'); const newButton = { type: 'button', text, x, y, - fontSize, + width, + height, onClick, }; this.guiData.push(newButton); } + loadAudioClipInternal(audioClipUrl : string, audioType : string) : AudioClipIdentifier { + const audioClipInternalName = `AudioClip_${this.audioClipIdentifierSerialCounter}`; + this.audioClipIdentifierSerialCounter++; + this.audioClipStorage[this.audioClipStorage.length] = audioClipInternalName; + this.dispatchStudentAction(`loadAudioClip|${audioClipUrl}|${audioType}|${audioClipInternalName}`); + return new AudioClipIdentifier(audioClipInternalName); + } + + private getAudioSourceData(gameObjectIdentifier : GameObjectIdentifier) : AudioSourceData { + const gameObject = this.getStudentGameObject(gameObjectIdentifier); + const retVal = gameObject.audioSource; + if (retVal === null) { + throw new Error('The given GameObject is not a valid audio source.'); + } + return retVal; + } + + setAudioSourceProp(propName : string, audioSrc : GameObjectIdentifier, value : any) : void { + const audioSourceData = this.getAudioSourceData(audioSrc); + audioSourceData[propName] = value; + } + + getAudioSourceProp(propName : string, audioSrc : GameObjectIdentifier) : any { + const audioSourceData = this.getAudioSourceData(audioSrc); + return audioSourceData[propName]; + } + + studentLogger(contentStr : string, severity : string) { + // Temporarily use "<%7C>" to replace all '|' characters as '|' is used as the data separator in student action strings in Unity Academy Embedded Frontend. + // In Unity Academy Embedded Frontend, "<%7C>" will be replaced back to '|' when displaying the log message. + contentStr = contentStr.replaceAll('|', '<%7C>'); + this.dispatchStudentAction(`studentLogger|${severity}|${contentStr}`); + } + setTargetFrameRate(newTargetFrameRate : number) : void { newTargetFrameRate = Math.floor(newTargetFrameRate); if (newTargetFrameRate < 15) return; @@ -654,7 +739,7 @@ export function initializeModule(dimensionMode : string) { let instance = getInstance(); if (instance !== undefined) { if (!instance.isUnityInstanceReady()) { - throw new Error('Unity instance is not ready to accept a new Source program now. Please try again later.'); + throw new Error('Unity Academy Embedded Frontend is not ready to accept a new Source program now, please try again later. If you just successfully ran your code before but haven\'t open Unity Academy Embedded Frontend before running your code again, please try open the frontend first. If this error persists or you can not open Unity Academy Embedded Frontend, please try to refresh your browser\'s page.'); } if (instance.unityInstance === null) { instance.reloadUnityAcademyInstanceAfterTermination(); diff --git a/src/bundles/unity_academy/UnityAcademyMaths.ts b/src/bundles/unity_academy/UnityAcademyMaths.ts index ba57f74a9..052deb5ac 100644 --- a/src/bundles/unity_academy/UnityAcademyMaths.ts +++ b/src/bundles/unity_academy/UnityAcademyMaths.ts @@ -36,10 +36,14 @@ export function scaleVector(vector : Vector3, factor : number) : Vector3 { return new Vector3(vector.x * factor, vector.y * factor, vector.z * factor); } -export function addVector(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function addVectors(vectorA : Vector3, vectorB : Vector3) : Vector3 { return new Vector3(vectorA.x + vectorB.x, vectorA.y + vectorB.y, vectorA.z + vectorB.z); } +export function vectorDifference(vectorA : Vector3, vectorB : Vector3) : Vector3 { + return new Vector3(vectorA.x - vectorB.x, vectorA.y - vectorB.y, vectorA.z - vectorB.z); +} + export function dotProduct(vectorA : Vector3, vectorB : Vector3) : number { return vectorA.x * vectorB.x + vectorA.y * vectorB.y + vectorA.z * vectorB.z; } diff --git a/src/bundles/unity_academy/functions.ts b/src/bundles/unity_academy/functions.ts index 332ae830a..306aa15da 100644 --- a/src/bundles/unity_academy/functions.ts +++ b/src/bundles/unity_academy/functions.ts @@ -5,15 +5,15 @@ */ -import { initializeModule, getInstance, type GameObjectIdentifier } from './UnityAcademy'; +import { initializeModule, getInstance, type GameObjectIdentifier, type AudioClipIdentifier } from './UnityAcademy'; import { - type Vector3, checkVector3Parameter, makeVector3D, scaleVector, addVector, dotProduct, crossProduct, - normalizeVector, vectorMagnitude, zeroVector, pointDistance, + type Vector3, checkVector3Parameter, makeVector3D, scaleVector, addVectors, vectorDifference, dotProduct, + crossProduct, normalizeVector, vectorMagnitude, zeroVector, pointDistance, } from './UnityAcademyMaths'; /** - * Load and initialize Unity Academy WebGL player and set it to 2D mode. All other functions (except Maths functions) in this module requires calling this function or init_unity_academy_3d first. + * Load and initialize Unity Academy WebGL player and set it to 2D mode. All other functions (except Maths functions) in this module requires calling this function or `init_unity_academy_3d` first. * * I recommand you just call this function at the beginning of your Source Unity program under the 'import' statements. * @@ -25,7 +25,7 @@ export function init_unity_academy_2d() : void { } /** - * Load and initialize Unity Academy WebGL player and set it to 3D mode. All other functions (except Maths functions) in this module requires calling this function or init_unity_academy_2d first. + * Load and initialize Unity Academy WebGL player and set it to 3D mode. All other functions (except Maths functions) in this module requires calling this function or `init_unity_academy_2d` first. * * I recommand you just call this function at the beginning of your Source Unity program under the 'import' statements. * @@ -131,6 +131,8 @@ export function set_update(gameObjectIdentifier : GameObjectIdentifier, updateFu * * **3D mode only** * + * A prefab is something that is pre-built and can be created and used as a whole. + * * Available Prefab Information: Click Here * * @param prefab_name The prefab name @@ -184,28 +186,25 @@ export function instantiate_sprite(sourceImageUrl : string) { */ export function instantiate_empty() : GameObjectIdentifier { checkUnityAcademyExistence(); - checkIs3DMode(); return getInstance() .instantiateEmptyGameObjectInternal(); } /** - * Returns the value of Time.deltaTime in Unity ( roughly saying it's about `1 / instant frame rate` ) + * Returns the value of Time.deltaTime in Unity ( roughly saying it's about `1 / instant_frame_rate_per_second` ) * * This should be useful when implementing timers or constant speed control in Update function. * * For example: - * * ``` * function update(gameObject){ * const move_speed = 3; * translate_world(gameObject, 0, 0, move_speed * delta_time()); * } * ``` - * By assigning the above code to a GameObject with `set_update`, that GameObject will move in a constant speed of 3 units along world +Z axis, ignoring the affect of unstable instant frame rate. + * By assigning the above code to a GameObject with `set_update`, that GameObject will move in a constant speed for about 3 units per second along world +Z axis. * * For more information, see https://docs.unity3d.com/ScriptReference/Time-deltaTime.html - * * @return the delta time value in decimal * * @category Common @@ -217,7 +216,7 @@ export function delta_time() { } /** - * Remove a GameObject + * Removes a GameObject * * Note that this won't remove the GameObject immediately, the actual removal will happen at the end of the current main cycle loop. * @@ -233,26 +232,14 @@ export function destroy(gameObjectIdentifier : GameObjectIdentifier) : void { .destroyGameObjectInternal(gameObjectIdentifier); } - -/** - * Set the target frame rate of Unity Academy. The frame rate should be an integer between 1 and 30. The default value is 30. - * - * @category Basics - */ -/* export function set_target_fps(frameRate : number) { - checkUnityEngineStatus(); - return getInstance() - .setTargetFrameRate(frameRate); -}*/ - /** * Returns the world position of a given GameObject * @param gameObjectIdentifier The identifier for the GameObject that you want to get position for. - * @return the position represented in an array with three elements: [x, y, z] + * @return The position represented in a Vector3. * * @category Transform */ -export function get_position(gameObjectIdentifier : GameObjectIdentifier) : Array { +export function get_position(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); return getInstance() @@ -262,30 +249,26 @@ export function get_position(gameObjectIdentifier : GameObjectIdentifier) : Arra /** * Set the world position of a given GameObject * @param gameObjectIdentifier The identifier for the GameObject that you want to change position for. - * @param x The x component for the position. - * @param y The y component for the position. - * @param z The z component for the position. + * @param position The new position for the GameObject. * * @category Transform */ -export function set_position(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { +export function set_position(gameObjectIdentifier : GameObjectIdentifier, position : Vector3) : void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - checkParameterType(x, 'number'); - checkParameterType(y, 'number'); - checkParameterType(z, 'number'); + checkVector3Parameter(position); return getInstance() - .setGameObjectTransformProp('position', gameObjectIdentifier, x, y, z); + .setGameObjectTransformProp('position', gameObjectIdentifier, position); } /** * Returns the world Euler angle rotation of a given GameObject * @param gameObjectIdentifier The identifier for the GameObject that you want to get rotation for. - * @return the Euler angle rotation represented in an array with three elements: [x, y, z] + * @return The Euler angle rotation represented in a Vector3. * * @category Transform */ -export function get_rotation_euler(gameObjectIdentifier : GameObjectIdentifier) : Array { +export function get_rotation_euler(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); return getInstance() @@ -295,33 +278,28 @@ export function get_rotation_euler(gameObjectIdentifier : GameObjectIdentifier) /** * Set the world rotation of a given GameObject with given Euler angle rotation. * @param gameObjectIdentifier The identifier for the GameObject that you want to change rotation for. - * @param x The x component (Euler angle) for the rotation. - * @param y The y component (Euler angle) for the rotation. - * @param z The z component (Euler angle) for the rotation. + * @param rotation The new rotation (in Euler angles) for the GameObject. * * @category Transform */ -export function set_rotation_euler(gameObjectIdentifier : GameObjectIdentifier, x : number, y : number, z : number) : void { +export function set_rotation_euler(gameObjectIdentifier : GameObjectIdentifier, rotation : Vector3) : void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - checkParameterType(x, 'number'); - checkParameterType(y, 'number'); - checkParameterType(z, 'number'); + checkVector3Parameter(rotation); return getInstance() - .setGameObjectTransformProp('rotation', gameObjectIdentifier, x, y, z); + .setGameObjectTransformProp('rotation', gameObjectIdentifier, rotation); } /** * Returns the scale (size factor) of a given GameObject * * By default the scale of a GameObject is (1, 1, 1) - * * @param gameObjectIdentifier The identifier for the GameObject that you want to get scale for. - * @return the scale represented in an array with three elements: [x, y, z] + * @return The scale represented in a Vector3. * * @category Transform */ -export function get_scale(gameObjectIdentifier : GameObjectIdentifier) : Array { +export function get_scale(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); return getInstance() @@ -340,34 +318,28 @@ export function get_scale(gameObjectIdentifier : GameObjectIdentifier) : Array= 'a' && char <= 'z') || (char >= '0' && char <= '9'))) { - throw new Error(`Key code must be either a letter between A-Z or a-z or 0-9 or one among 'LeftMouseBtn', 'RightMouseBtn', 'MiddleMouseBtn', 'LeftShift' or 'RightShift'! Given: ${keyCode}`); + throw new Error(`Key code must be either a letter between A-Z or a-z or 0-9 or one among 'LeftMouseBtn', 'RightMouseBtn', 'MiddleMouseBtn', 'Space', 'LeftShift' or 'RightShift'! Given: ${keyCode}`); } return keyCode; } @@ -549,7 +491,6 @@ function checkKeyCodeValidityAndToLowerCase(keyCode : string) : string { * When user presses a key on the keyboard or mouse button, this function will return true only at the frame when the key is just pressed down and return false afterwards. * * For more information, see https://docs.unity3d.com/ScriptReference/Input.GetKeyDown.html - * * @return A boolean value equivalent to Input.GetKeyDown(keyCode) in Unity. * * @param keyCode The key to detact input for. @@ -566,7 +507,6 @@ export function get_key_down(keyCode : string) : boolean { * When user presses a key on the keyboard or mouse button, this function will return true in every frame that the key is still being pressed and false otherwise. * * For more information, see https://docs.unity3d.com/ScriptReference/Input.GetKey.html - * * @return A boolean value equivalent to Input.GetKey(keyCode) in Unity. * * @param keyCode The key to detact input for. @@ -585,7 +525,6 @@ export function get_key(keyCode : string) : boolean { * When user releases a pressed key on the keyboard or mouse button, this function will return true only at the frame when the key is just released up and return false otherwise. * * For more information, see https://docs.unity3d.com/ScriptReference/Input.GetKeyUp.html - * * @return A boolean value equivalent to Input.GetKeyUp(keyCode) in Unity. * * @param keyCode The key to detact input for. @@ -605,8 +544,6 @@ export function get_key_up(keyCode : string) : boolean { * * **3D mode only** * - * [For Prefab Authors] Please follow these conventions if you are making humanoid prefabs (for example: any human-like characters): Name the standing animation state as "Idle" and name the walking animation state as "Walk" in Unity Animator. - * * @param gameObjectIdentifier The identifier for the GameObject that you want to play the animation on. * @param animatorStateName The name for the animator state to play. * @category Common @@ -621,13 +558,12 @@ export function play_animator_state(gameObjectIdentifier : GameObjectIdentifier, } /** - * Apply rigidbody (2D or 3D based on the current dimension mode) to the given game object to use Unity's physics engine + * Apply rigidbody (2D or 3D based on the current dimension mode) to the given GameObject to use Unity's physics engine. * - * All other functions under the Physics - Rigidbody category require calling this function first on the applied game objects. + * All other functions under the Physics - Rigidbody category require calling this function first on the applied GameObjects. * * For more information, see - * - * - https://docs.unity3d.com/ScriptReference/Rigidbody.html (For 3D Mode) or + * - https://docs.unity3d.com/ScriptReference/Rigidbody.html (For 3D Mode) * - https://docs.unity3d.com/ScriptReference/Rigidbody2D.html (For 2D Mode) * * @param gameObjectIdentifier The identifier for the GameObject that you want to apply rigidbody on. @@ -641,7 +577,7 @@ export function apply_rigidbody(gameObjectIdentifier : GameObjectIdentifier) : v } /** - * Returns the mass of the rigidbody attached on the game object. + * Returns the mass of the rigidbody attached on the GameObject. * * Usage of all physics functions under the Physics - Rigidbody category requires calling `apply_rigidbody` first on the applied game objects. * @@ -674,15 +610,15 @@ export function set_mass(gameObjectIdentifier : GameObjectIdentifier, mass : num } /** - * Returns the velocity of the rigidbody attached on the game object. + * Returns the velocity of the rigidbody attached on the GameObject. * * Usage of all physics functions under the Physics - Rigidbody category requires calling `apply_rigidbody` first on the applied game objects. * * @param gameObjectIdentifier The identifier for the GameObject that you want to get velocity for. - * @return the velocity at this moment represented in an array with three elements: [x, y, z] + * @return the velocity at this moment represented in a Vector3. * @category Physics - Rigidbody */ -export function get_velocity(gameObjectIdentifier : GameObjectIdentifier) : Array { +export function get_velocity(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); return getInstance() @@ -690,24 +626,20 @@ export function get_velocity(gameObjectIdentifier : GameObjectIdentifier) : Arra } /** - * Set the (linear) velocity of the rigidbody attached on the game object. + * Set the (linear) velocity of the rigidbody attached on the GameObject. * * Usage of all physics functions under the Physics - Rigidbody category requires calling `apply_rigidbody` first on the applied game objects. * * @param gameObjectIdentifier The identifier for the GameObject that you want to change velocity for. - * @param x The x component of the velocity vector. - * @param y The y component of the velocity vector. - * @param z The z component of the velocity vector. + * @param velocity The new velocity for the rigidbody attached on the GameObject. * @category Physics - Rigidbody */ -export function set_velocity(gameObjectIdentifier : GameObjectIdentifier, x: number, y: number, z: number) : void { +export function set_velocity(gameObjectIdentifier : GameObjectIdentifier, velocity : Vector3) : void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - checkParameterType(x, 'number'); - checkParameterType(y, 'number'); - checkParameterType(z, 'number'); + checkVector3Parameter(velocity); getInstance() - .setRigidbodyVelocityVector3Prop('velocity', gameObjectIdentifier, x, y, z); + .setRigidbodyVelocityVector3Prop('velocity', gameObjectIdentifier, velocity); } /** @@ -715,13 +647,13 @@ export function set_velocity(gameObjectIdentifier : GameObjectIdentifier, x: num * * Usage of all physics functions under the Physics - Rigidbody category requires calling `apply_rigidbody` first on the applied game objects. * - * **2D Mode Special: **In 2D mode there is no angular velocity on X nor Y axis, so in the first two elements for the returned array will always be zero. + * **2D Mode Special: **In 2D mode there is no angular velocity on X nor Y axis, so in the X and Y values in the returned Vector3 will always be zero. * * @param gameObjectIdentifier The identifier for the GameObject that you want to get angular velocity for. - * @return the angular velocity at this moment represented in an array with three elements: [x, y, z] + * @return the angular velocity at this moment represented in a Vector3. * @category Physics - Rigidbody */ -export function get_angular_velocity(gameObjectIdentifier : GameObjectIdentifier) : Array { +export function get_angular_velocity(gameObjectIdentifier : GameObjectIdentifier) : Vector3 { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); return getInstance() @@ -733,28 +665,24 @@ export function get_angular_velocity(gameObjectIdentifier : GameObjectIdentifier * * Usage of all physics functions under the Physics - Rigidbody category requires calling `apply_rigidbody` first on the applied game objects. * - * **2D Mode Special: **In 2D mode there is no angular velocity on X nor Y axis, so the value of the first two parameters for this function is ignored. + * **2D Mode Special: **In 2D mode there is no angular velocity on X nor Y axis, so the X and Y values in the Vector3 is ignored. * * @param gameObjectIdentifier The identifier for the GameObject that you want to change angular velocity for. - * @param x The x component of the angular velocity vector. - * @param y The y component of the angular velocity vector. - * @param z The z component of the angular velocity vector. + * @param angularVelocity The new angular velocity for the rigidbody attached on the GameObject. * @category Physics - Rigidbody */ -export function set_angular_velocity(gameObjectIdentifier : GameObjectIdentifier, x: number, y: number, z: number) : void { +export function set_angular_velocity(gameObjectIdentifier : GameObjectIdentifier, angularVelocity : Vector3) : void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - checkParameterType(x, 'number'); - checkParameterType(y, 'number'); - checkParameterType(z, 'number'); + checkVector3Parameter(angularVelocity); getInstance() - .setRigidbodyVelocityVector3Prop('angularVelocity', gameObjectIdentifier, x, y, z); + .setRigidbodyVelocityVector3Prop('angularVelocity', gameObjectIdentifier, angularVelocity); } /** * Set the drag (similar to air resistance) the rigidbody attached on the game object. * - * By default the drag is zero + * By default the drag is zero. * * Usage of all physics functions under the Physics - Rigidbody category requires calling `apply_rigidbody` first on the applied game objects. * @@ -813,19 +741,15 @@ export function set_use_gravity(gameObjectIdentifier : GameObjectIdentifier, use * Usage of all physics functions under the Physics category requires calling `apply_rigidbody` first on the applied game objects. * * @param gameObjectIdentifier The identifier for the GameObject that you want to add the force. - * @param x The x component of the force vector. - * @param y The y component of the force vector. - * @param z The z component of the force vector. + * @param The force vector. * @category Physics - Rigidbody */ -export function add_impulse_force(gameObjectIdentifier : GameObjectIdentifier, x: number, y: number, z: number) : void { +export function add_impulse_force(gameObjectIdentifier : GameObjectIdentifier, force : Vector3) : void { checkUnityAcademyExistence(); checkGameObjectIdentifierParameter(gameObjectIdentifier); - checkParameterType(x, 'number'); - checkParameterType(y, 'number'); - checkParameterType(z, 'number'); + checkVector3Parameter(force); getInstance() - .addImpulseForceInternal(gameObjectIdentifier, x, y, z); + .addImpulseForceInternal(gameObjectIdentifier, force); } /** @@ -852,11 +776,12 @@ export function remove_collider_components(gameObjectIdentifier : GameObjectIden * * For example: `const myFunction = (self, other) => {...};` * - * Note that for collision detaction to happen, for the two colliding GameObjects, at least one GameObject should have a Rigidbody / Rigidbody2D component (called `apply_rigidbody` on the GameObject). + * - Note that for collision detaction to happen, for the two colliding GameObjects: + * - if **in 3D mode**, both GameObjects must applied Rigidbody by `apply_rigidbody` + * - if **in 2D mode**, at least one GameObject must applied Rigidbody by `apply_rigidbody` * * For more information, see - * - * - https://docs.unity3d.com/ScriptReference/Collider.OnCollisionEnter.html (For 3D Mode) or + * - https://docs.unity3d.com/ScriptReference/Collider.OnCollisionEnter.html (For 3D Mode) * - https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionEnter2D.html (For 2D Mode) * * @param gameObjectIdentifier The identifier for the GameObject that you want to bind the lifecycle event function on. @@ -879,11 +804,12 @@ export function on_collision_enter(gameObjectIdentifier : GameObjectIdentifier, * * For example: `const myFunction = (self, other) => {...};` * - * Note that for collision detaction to happen, for the two colliding GameObjects, at least one GameObject should have a Rigidbody / Rigidbody2D component (called `apply_rigidbody` on the GameObject). + * - Note that for collision detaction to happen, for the two colliding GameObjects: + * - if **in 3D mode**, both GameObjects must applied Rigidbody by `apply_rigidbody` + * - if **in 2D mode**, at least one GameObject must applied Rigidbody by `apply_rigidbody` * * For more information, see - * - * - https://docs.unity3d.com/ScriptReference/Collider.OnCollisionStay.html (For 3D Mode) or + * - https://docs.unity3d.com/ScriptReference/Collider.OnCollisionStay.html (For 3D Mode) * - https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionStay2D.html (For 2D Mode) * * @param gameObjectIdentifier The identifier for the GameObject that you want to bind the lifecycle event function on. @@ -906,11 +832,12 @@ export function on_collision_stay(gameObjectIdentifier : GameObjectIdentifier, e * * For example: `const myFunction = (self, other) => {...};` * - * Note that for collision detaction to happen, for the two colliding GameObjects, at least one GameObject should have a Rigidbody / Rigidbody2D component (called `apply_rigidbody` on the GameObject). + * - Note that for collision detaction to happen, for the two colliding GameObjects: + * - if **in 3D mode**, both GameObjects must applied Rigidbody by `apply_rigidbody` + * - if **in 2D mode**, at least one GameObject must applied Rigidbody by `apply_rigidbody` * * For more information, see - * - * - https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionExit.html (For 3D Mode) or + * - https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionExit.html (For 3D Mode) * - https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnCollisionExit2D.html (For 2D Mode) * * @param gameObjectIdentifier The identifier for the GameObject that you want to bind the lifecycle event function on. @@ -934,21 +861,20 @@ export function on_collision_exit(gameObjectIdentifier : GameObjectIdentifier, e * * The drawn text will only last for one frame. You should put this under the `Update` function (or a function that is called by the `Update` function) to keep the text stays in every frame. * + * You can use rich text for the parameter `text`. For example: `gui_label("Hello World", 100, 100);` * - * @param content The string you want to display on screen. + * @param text The string you want to display on screen. * @param x The x coordinate of the text (in screen position). * @param y The y coordinate of the text (in screen position). - * @param fontSize The size of the text * @category Graphical User Interface */ -export function gui_label(content : string, x : number, y : number, fontSize : number) : void { +export function gui_label(text : string, x : number, y : number) : void { checkUnityAcademyExistence(); - checkParameterType(content, 'string'); + checkParameterType(text, 'string'); checkParameterType(x, 'number'); checkParameterType(y, 'number'); - checkParameterType(fontSize, 'number'); getInstance() - .onGUI_Label(content, x, y, fontSize); + .onGUI_Label(text, x, y); } @@ -964,7 +890,6 @@ export function gui_label(content : string, x : number, y : number, fontSize : n * This means that you can use other functions from this module inside the `onClick` function, even though the functions are not under the `Outside Lifecycle` category. * * For example, the code piece below - * * ``` * import {init_unity_academy_3d, set_start, set_update, instantiate, gui_button, set_position } * from "unity_academy"; @@ -973,40 +898,41 @@ export function gui_label(content : string, x : number, y : number, fontSize : n * const cube = instantiate("cube"); * * const cube_update = (gameObject) => { - * gui_button("Button", 1000, 300, ()=> + * gui_button("Button", 1000, 300, 200, 50, ()=> * set_position(gameObject, 0, 10, 6) // calling set_position inside the onClick function * ); * }; * set_update(cube, cube_update); * ``` - * * is correct. * + * You can use rich text for the parameter `text`. For example: `gui_button("Hello World", 100, 100, 200, 50, my_onclick_function);` + * * @param text The text you want to display on the button. * @param x The x coordinate of the button (in screen position). * @param y The y coordinate of the button (in screen position). + * @param width The width for the button. + * @param height The height for the button. * @param onClick The function that will be called when user clicks the button on screen. - * @param fontSize The size of the text inside the button. * @category Graphical User Interface */ -export function gui_button(text : string, x: number, y : number, fontSize : number, onClick : Function) : void { +export function gui_button(text : string, x: number, y : number, width : number, height : number, onClick : Function) : void { checkUnityAcademyExistence(); checkParameterType(text, 'string'); checkParameterType(x, 'number'); checkParameterType(y, 'number'); - checkParameterType(fontSize, 'number'); + checkParameterType(width, 'number'); + checkParameterType(height, 'number'); checkParameterType(onClick, 'function'); getInstance() - .onGUI_Button(text, x, y, fontSize, onClick); + .onGUI_Button(text, x, y, width, height, onClick); } /** * Get the main camera following target GameObject (an invisible GameObject) to use it to control the position of the main camera with the default camera controller. - * - * **In 3D mode**, the default camera controller behaves as third-person camera controller, and the center to follow is the following target GameObject. Also, Unity Academy will automatically set the rotation of this "following target" to the same rotation as the current main camera's rotation to let you get the main camera's rotation. - * - * **In 2D mode**, the default camera controller will follow the target GameObject to move, along with a position delta value that you can adjust with the arrow keys on your keyboard. + * - **In 3D mode**, the default camera controller behaves as third-person camera controller, and the center to follow is the following target GameObject. Also, Unity Academy will automatically set the rotation of this "following target" to the same rotation as the current main camera's rotation to let you get the main camera's rotation. + * - **In 2D mode**, the default camera controller will follow the target GameObject to move, along with a position delta value that you can adjust with the arrow keys on your keyboard. * * The main camera following target GameObject is a primitive GameObject. This means that you are not allowed to destroy it and/or instantiate it during runtime. Multiple calls to this function will return GameObject identifiers that refer to the same primitive GameObject. * @@ -1030,7 +956,6 @@ export function get_main_camera_following_target() : GameObjectIdentifier { * * This function is for totally customizing the position and rotation of the main camera. If you'd like to simplify the camera controlling with the help of the default camera controllers in Unity Academy, please consider use `get_main_camera_following_target` function. * - * * @return The GameObject identifier that can directly be used to control the main camera's position and rotation * @category Camera * @category Outside Lifecycle @@ -1154,10 +1079,24 @@ export function scale_vector(vector : Vector3, factor : number) : Vector3 { * * @category Maths */ -export function add_vector(vectorA : Vector3, vectorB : Vector3) : Vector3 { +export function add_vectors(vectorA : Vector3, vectorB : Vector3) : Vector3 { checkVector3Parameter(vectorA); checkVector3Parameter(vectorB); - return addVector(vectorA, vectorB); + return addVectors(vectorA, vectorB); +} + +/** + * Calcuate the vector difference between two vectors (vectorA - vectorB). + * @param vectorA The minuend vector. + * @param vectorB The subtrahend vector. + * @return The result for vectorA - vectorB + * + * @category Maths + */ +export function vector_difference(vectorA : Vector3, vectorB : Vector3) : Vector3 { + checkVector3Parameter(vectorA); + checkVector3Parameter(vectorB); + return vectorDifference(vectorA, vectorB); } /** @@ -1237,3 +1176,290 @@ export function point_distance(pointA : Vector3, pointB : Vector3) : number { checkVector3Parameter(pointB); return pointDistance(pointA, pointB); } + + + +/** + * + * Documentation TODO + * + * @category Sound / Audio + * @category Outside Lifecycle + */ +export function load_audio_clip_mp3(audioUrl: string) : AudioClipIdentifier { + checkUnityAcademyExistence(); + checkParameterType(audioUrl, 'string'); + return getInstance() + .loadAudioClipInternal(audioUrl, 'mp3'); +} + +/** + * + * Documentation TODO + * + * @category Sound / Audio + * @category Outside Lifecycle + */ +export function load_audio_clip_ogg(audioUrl: string) : AudioClipIdentifier { + checkUnityAcademyExistence(); + checkParameterType(audioUrl, 'string'); + return getInstance() + .loadAudioClipInternal(audioUrl, 'ogg'); +} + +/** + * + * Documentation TODO + * + * @category Sound / Audio + * @category Outside Lifecycle + */ +export function load_audio_clip_wav(audioUrl: string) : AudioClipIdentifier { + checkUnityAcademyExistence(); + checkParameterType(audioUrl, 'string'); + return getInstance() + .loadAudioClipInternal(audioUrl, 'wav'); +} + +/** + * + * Create an audio source GameObject + * + * The audio source GameObject can be used to play an audio clip with audio functions. But it's basically a regular GameObject with extra data for audio playing. + * So you can still use the audio source as a regular GameObject, like setting its position with `set_position`, using `set_start` and `set_update` to set its `Start` and `Update` funtions, etc. + * + * @param audioClip the audio clip that you want to use for this audio source + * @return the identifier of the newly created GameObject + * + * @category Sound / Audio + * @category Outside Lifecycle + */ +export function instantiate_audio_source(audioClip : AudioClipIdentifier) : GameObjectIdentifier { + // todo: check audio clip identifier type + checkUnityAcademyExistence(); + return getInstance() + .instantiateAudioSourceInternal(audioClip); +} + +/** + * + * Start / resume playing the audio of an audio source + * + * @param audioSrc the GameObject identifier for the audio source + * + * @category Sound / Audio + */ +export function play_audio(audioSrc : GameObjectIdentifier) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + getInstance() + .setAudioSourceProp('isPlaying', audioSrc, true); +} + +/** + * + * Pause the audio of an audio source + * + * @param audioSrc the GameObject identifier for the audio source + * + * @category Sound / Audio + */ +export function pause_audio(audioSrc : GameObjectIdentifier) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + getInstance() + .setAudioSourceProp('isPlaying', audioSrc, false); +} + +/** + * + * Set the play speed of an audio source. + * + * @param audioSrc the GameObject identifier for the audio source + * @param speed the play speed + * + * @category Sound / Audio + */ +export function set_audio_play_speed(audioSrc : GameObjectIdentifier, speed : number) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + checkParameterType(speed, 'number'); + getInstance() + .setAudioSourceProp('playSpeed', audioSrc, speed); +} + +/** + * + * Get the current play progress of an audio source + * + * @param audioSrc the GameObject identifier for the audio source + * @returns the current play progress (seconds after the beginning of the audio clip) + * + * @category Sound / Audio + */ +export function get_audio_play_progress(audioSrc : GameObjectIdentifier) : number { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + return getInstance() + .getAudioSourceProp('playProgress', audioSrc); +} + +/** + * + * Set the play progress of an audio source + * + * @param audioSrc the GameObject identifier for the audio source + * @param progress the play progress (seconds after the beginning of the audio clip) + * + * @category Sound / Audio + */ +export function set_audio_play_progress(audioSrc : GameObjectIdentifier, progress : number) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + checkParameterType(progress, 'number'); + getInstance() + .setAudioSourceProp('playProgress', audioSrc, progress); +} + +/** + * + * @category Sound / Audio + */ +export function change_audio_clip(audioSrc : GameObjectIdentifier, newAudioClip : AudioClipIdentifier) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + // todo: check audio clip identifier type + getInstance() + .setAudioSourceProp('audioClipIdentifier', audioSrc, newAudioClip); +} + +/** + * + * @category Sound / Audio + */ +export function set_audio_looping(audioSrc : GameObjectIdentifier, looping : boolean) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + checkParameterType(looping, 'boolean'); + getInstance() + .setAudioSourceProp('isLooping', audioSrc, looping); +} + +/** + * + * @category Sound / Audio + */ +export function set_audio_volume(audioSrc : GameObjectIdentifier, volume : number) : void { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + checkParameterType(volume, 'number'); + getInstance() + .setAudioSourceProp('volume', audioSrc, volume); +} + +/** + * + * @category Sound / Audio + */ +export function is_audio_playing(audioSrc : GameObjectIdentifier) : boolean { + checkUnityAcademyExistence(); + checkGameObjectIdentifierParameter(audioSrc); + return getInstance() + .getAudioSourceProp('isPlaying', audioSrc); +} + +/** + * + * Log to Unity Academy Embedded Frontend's console. + * + * You can use rich text for the parameter `content`. + * + * @param content The content of the log message. + * + * @category Common + * @category Outside Lifecycle + */ +export function debug_log(content : any) : void { + checkUnityAcademyExistence(); + const contentStr = content.toString(); + getInstance() + .studentLogger(contentStr, 'log'); +} + +/** + * + * Log to Unity Academy Embedded Frontend's console, with yellow font color as highlighting. + * + * You can use rich text for the parameter `content`. + * + * @param content The content of the log message. + * + * @category Common + * @category Outside Lifecycle + */ +export function debug_logwarning(content : any) : void { + checkUnityAcademyExistence(); + const contentStr = content.toString(); + getInstance() + .studentLogger(contentStr, 'warning'); +} + +/** + * + * Log to Unity Academy Embedded Frontend's console, with red font color as highlighting. + * + * Note that this function does not "really" throw any error. It just logs a message with red font color and the student code will continue running normally after calling this function to log the error. + * + * You can use rich text for the parameter `content`. + * + * @param content The content of the log message. + * + * @category Common + * @category Outside Lifecycle + */ +export function debug_logerror(content : any) : void { + checkUnityAcademyExistence(); + const contentStr = content.toString(); + getInstance() + .studentLogger(contentStr, 'error'); +} + +/** + * + * Set the audio listener's position in the space. + * + * The audio listener's position will only be used when playing audio clips with 3D sound effects. (refer to `play_audio_clip_3d_sound`) + * + * By default, the audio listener's position will be the same as the main camera's position. After you calling this function, the audio listener's will no longer follow the main camera's position and will be set to your specified position. + * + * @category Sound / Audio + */ +export function set_audio_listener_position(positionX: number, positionY: number, positionZ: number) { + // todo: check audio clip identifier type + checkUnityAcademyExistence(); + checkParameterType(positionX, 'number'); + checkParameterType(positionY, 'number'); + checkParameterType(positionZ, 'number'); + // TODO +} + +/** + * + * Plays an audio clip, using 3D sound effects. + * + * The audio listener in the space will receive the sound and outputs the sound to user's sound devices. So the position of the audio clip and position of the audio listener will both effect the volume and direction of the actual output sound. + * + * Refer to `set_audio_listener_position` to customize the position of listening the sound in the space. + * + * @category Sound / Audio + */ +export function play_audio_clip_3d_sound(audioClip : AudioClipIdentifier, volume: number, loop: boolean, positionX: number, positionY: number, positionZ: number) { + // todo: check audio clip identifier type + checkUnityAcademyExistence(); + checkParameterType(volume, 'number'); + checkParameterType(loop, 'boolean'); + checkParameterType(positionX, 'number'); + checkParameterType(positionY, 'number'); + checkParameterType(positionZ, 'number'); + // TODO +} diff --git a/src/bundles/unity_academy/index.ts b/src/bundles/unity_academy/index.ts index 7a5768a2c..8cedf5906 100644 --- a/src/bundles/unity_academy/index.ts +++ b/src/bundles/unity_academy/index.ts @@ -9,30 +9,25 @@ * * **Note that you need to use this module with a 'Native' variant of Source language, otherwise you may get strange errors.** * - * **Lifecycle Event Functions** * + * **Lifecycle Event Functions** * - Unity Academy has its own internal loop on students' GameObject lifecycle. * - Lifecycle event functions are functions that are not supposed to be called by Source Academy's default evaluator, instead they are called by Unity Academy at certain time in the GameObject's lifecycle. * - Currently there are five types of Unity Academy lifecycle event function: `Start`, `Update` and three collision detaction functions. * - Both `Start` and `Update` functions should be a student-side function object with only one parameter, which automatically refers to the GameObject that is binded with the function when Unity Academy calls the function. So different GameObject instances can share the same lifecycle event function together. - * * For example: - * * ``` * function my_start(gameObject){...}; * const my_update = (gameObject) => {...}; * ``` - * - * - The functions `set_start` and `set_update` in this module can be used to set one GameObject's `Start` and `Update` functions + * - The functions `set_start` and `set_update` in this module can be used to set one GameObject's `Start` and `Update` functions. * - `Start` is called only for one time after the GameObject is created (instantiated) and before its first `Update` call. * - `Update` is called on every GameObject once in every frame after `Start` have been called. * - For the three collision detaction lifecycle event functions, please refer to `on_collision_enter`, `on_collision_stay` and `on_collision_exit` functions under the `Physics - Collision` category. * - You can not bind multiple lifecycle functions of the same type to the same GameObject. For example, you can't bind two `Update` functions to the same GameObject. In this case, previously binded `Update` functions will be overwritten by the latest binded `Update` function. * * **[IMPORTANT]** All functions in this module that is NOT under the "**Outside Lifecycle**" or "Maths" category need to call by Unity Academy lifecycle event functions (directly or intermediately) to work correctly. Failure to follow this rule may lead to noneffective or incorrect behaviors of the functions and may crash the Unity Academy instance. - * * For example: - * * ``` * import {init_unity_academy_3d, instantiate, set_start, set_update, set_position, set_rotation_euler} from 'unity_academy'; * init_unity_academy_3d(); // Correct, since this function is under the "Outside Lifecycle" category and it can be called outside lifecycle event functions. @@ -48,25 +43,34 @@ * set_start(cube, my_start); // Correct * ``` * - * When any runtime errors happen in lifecycle event functions, they will be displayed in Unity Academy's information page and the lifecycle event function that caused the errors will automatically unbind from the GameObject. + * When any runtime errors happen in lifecycle event functions, they will be displayed in Unity Academy's console and the lifecycle event function that caused the errors will automatically unbind from the GameObject. * - * **Input Function Key Codes** Accepts A-Z, a-z and "LeftMouseBtn" / "RightMouseBtn" / "MiddleMouseBtn" / "LeftShift" / "RightShift" * - * **Key differences between 2D and 3D mode** + * **Input Function Key Codes** + * + * - Accepts A-Z, a-z, 0-9 and "LeftMouseBtn" / "RightMouseBtn" / "MiddleMouseBtn" / "Space" / "LeftShift" / "RightShift" * + * + * **Key differences between 2D and 3D mode** * - In 2D mode the main camera renders the scene in **orthographic** mode (Z position is used to determine sequence when sprites overlapping), whereas in 3D mode the camera renders the scene in **perspective** mode. Moreover, 3D mode and 2D mode have different kinds of default camera controller. * - In 2D mode, due to the loss of one dimension, for some values and axis in 3D coordinate system, they sometimes behaves differently with 3D mode. For example, some coordinate values is ignored in 2D mode. Whereas in 3D mode you can use the fully-supported 3D coordinate system. (Actually, in general, Unity Academy just simply uses 3D space and an orthographic camera to simulate 2D space.) * - In 2D mode you need to use **instantiate_sprite** to create new GameObjects, whereas in 3D mode you need to use **instantiate** to create new GameObjects. * - In 2D mode Unity Academy will use Rigidbody2D and 2D colliders like BoxCollider2D for physics engine (certain values for 3D physics engine in 2D physics engine is ignored and will always be zero), whereas in 3D mode Unity Academy use regular 3D rigidbody and 3D colliders to simulate 3D physics. * - In 2D mode playing frame animations for sprite GameObjects is currently unavailable, whereas in 3D mode you need to use **play_animator_state** to play 3D animations. * - * **Space and Coordinates** * + * **Space and Coordinates** * - 3D: Uses **left-hand coordinate system**: +X denotes rightward, +Y denotes upward, +Z denotes forward. * - 2D: +X denotes rightward, +Y denotes upward, Z value actually still exists and usually used for determining sequence of overlapping 2D GameObjects like sprites. + * - About **Vector3** + * - `Vector3` is an object that can represent either a 3D vector or simply a combination of three coordinate values (X, Y and Z) depends on where it is used. It can be created through `vector3(x, y, z);`. + * - For example: + * - In function `set_velocity`, the `Vector3` parameter represents the 3D vector for the velocity of the rigidbody. + * - In function `set_position`, the `Vector3` parameter represents a point defined by (X, Y, Z) in the space. + * - In function `set_scale`, the `Vector3` parameter represents the scale of the GameObject along each of the three axes (X, Y, Z). * - * **Unity Academy Camera Control (only available when the default camera controllers are being used)** * + * **Unity Academy Camera Control (only available when the default camera controllers are being used)** * - In 2D mode: * - 'W'/'A'/'S'/'D' : Moves the main camera around * - '=' (equals key) : Resets the main camera to its initial position @@ -74,6 +78,14 @@ * - '=' (equals key) : Resets the main camera to its initial position and rotation * - Left Mouse Button : Hold to rotate the main camera in a faster speed * - Mouse Scrollwheel : Zoom in / out + * + * **Rich Text** + * - You can use Unity's rich text feature in the `text` parameter for functions `gui_label` and `gui_button`, + * and the `content` paramater for functions `debug_log`, `debug_logwarning` and `debug_logerror` to customize + * font color, font size and display your text in boldface and italics. + * - See https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/StyledText.html for more information about how to use this feature. + * - In Unity Academy, only tags `b`, `i`, `size` and `color` are supported. + * * @module unity_academy * @author Wang Zihan */ @@ -134,11 +146,33 @@ export { get_y, get_z, scale_vector, - add_vector, + add_vectors, + vector_difference, dot, cross, normalize, magnitude, zero_vector, point_distance, + /* I will uncomment these audio functions when I totally finish audio support. + load_audio_clip_mp3, + load_audio_clip_ogg, + load_audio_clip_wav, + instantiate_audio_source, + play_audio, + pause_audio, + set_audio_play_speed, + set_audio_play_progress, + change_audio_clip, + set_audio_looping, + set_audio_volume, + get_audio_play_progress, + is_audio_playing, + // todos + set_audio_listener_position, + play_audio_clip_3d_sound, + */ + debug_log, + debug_logwarning, + debug_logerror, } from './functions'; diff --git a/src/tabs/UnityAcademy/index.tsx b/src/tabs/UnityAcademy/index.tsx index 35aa8ed3c..848221322 100644 --- a/src/tabs/UnityAcademy/index.tsx +++ b/src/tabs/UnityAcademy/index.tsx @@ -24,9 +24,9 @@ class Unity3DTab extends React.Component { const currentTargetFrameRate = getInstance() .getTargetFrameRate(); if (currentTargetFrameRate > 30 && currentTargetFrameRate <= 60) { - highFPSWarning =
[Warning] You are using a target fps higher than default value (30). Higher FPS will lead to more cost in your device's resources such as GPU, increace device temperature and battery usage and may even lead to browser not responding, crash the browser or even crash your operation system if your device really can not endure the high resource cost.
; + highFPSWarning =
[Warning] You are using a target FPS higher than default value (30). Higher FPS will lead to more cost in your device's resources such as GPU, increace device temperature and battery usage and may even lead to browser not responding, crash the browser or even crash your operation system if your device really can not endure the high resource cost.
; } else if (currentTargetFrameRate > 60 && currentTargetFrameRate <= 120) { - highFPSWarning =
[!!WARNING!!] You are using a target fps that is extremely high. This FPS may lead to large cost in your device's resources such as GPU, significantly increace device temperature and battery usage and have a large chance of making browser not responding, crash the browser or even crash your operation system if your device's performance is not enough.

***ARE YOU REALLY CONFIDENT ABOUT THE PERFORMANCE OF YOUR OWN DEVICE?***
; + highFPSWarning =
[!!WARNING!!] You are using a target FPS that is extremely high. This FPS may lead to large cost in your device's resources such as GPU, significantly increace device temperature and battery usage and have a large chance of making browser not responding, crash the browser or even crash your operation system if your device's performance is not enough.

***ARE YOU REALLY CONFIDENT ABOUT THE PERFORMANCE OF YOUR OWN DEVICE?***
; } else { highFPSWarning =
; } @@ -41,35 +41,29 @@ class Unity3DTab extends React.Component {

Note that you need to use a 'Native' variant of Source language in order to use this module. If any strange error happens when using this module, please check whether you are using the 'Native' variant of Source language or not.


-

100% resolution will display Unity Academy in a larger area with more detailed graphics but requires higher GPU performance than 50% resolution.