Skip to content

Commit 9ca1e99

Browse files
Unity Academy User Agreement, GUI (Button / Label) Functions and Bug Fixes (#203)
* Unity Academy User Agreement Update and Bug Fixes * Fixed a bug * Fixed a bug about checking User Agreement checkbox status * Unity Academy GUI (Button & Label) functions * Improved Unity Academy GUI functions * Unity Academy - Finished GUI functions * Update documentation
1 parent 2c64952 commit 9ca1e99

File tree

4 files changed

+150
-42
lines changed

4 files changed

+150
-42
lines changed

src/bundles/unity_academy/UnityAcademy.tsx

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ class UnityAcademyJsInteropContext {
162162
private unityInstanceState; // [set by interop]
163163
private guiData : any[]; // [get / clear by interop]
164164
public dimensionMode;
165+
private isShowingUnityAcademy : boolean; // [get by interop]
166+
private latestUserAgreementVersion : string;
165167

166168
constructor() {
167169
this.unityInstance = null;
@@ -180,6 +182,8 @@ class UnityAcademyJsInteropContext {
180182
};
181183
this.targetFrameRate = 30;
182184

185+
this.latestUserAgreementVersion = 'unknown';
186+
this.getLatestUserAgreementVersion();
183187

184188
// [ Why I don't put this into my module's own context? ]
185189
// Since Unity Academy application needs to access this from the WASM side, and the Unity Academy WASM side can not access the module context under the js-slang evaluation scope since Unity Academy app is running totally separated from js-slang in the WASM virtual machine.
@@ -191,6 +195,7 @@ class UnityAcademyJsInteropContext {
191195
document.body.appendChild(this.unityContainerElement);
192196
ReactDOM.render(<UnityComponent />, this.unityContainerElement);
193197
this.setShowUnityComponent(0);
198+
this.isShowingUnityAcademy = false;
194199

195200
this.gameObjectIdentifierWrapperClass = GameObjectIdentifier;
196201

@@ -243,6 +248,7 @@ class UnityAcademyJsInteropContext {
243248

244249
setShowUnityComponent(resolution: number) {
245250
const toShow = resolution > 0;
251+
this.isShowingUnityAcademy = toShow;
246252
const sendMessageFunctionName = 'SendMessage';
247253
if (toShow) {
248254
(this.unityContainerElement as any).style.visibility = 'visible';
@@ -304,6 +310,40 @@ class UnityAcademyJsInteropContext {
304310
return this.unityInstanceState === 'Ready';
305311
}
306312

313+
private getLatestUserAgreementVersion() : void {
314+
const jsonUrl = `${UNITY_ACADEMY_BACKEND_URL}user_agreement.json`;
315+
const xhr = new XMLHttpRequest();
316+
xhr.onreadystatechange = () => {
317+
if (xhr.readyState === 4 && xhr.status === 200) {
318+
this.latestUserAgreementVersion = JSON.parse(xhr.responseText).version;
319+
}
320+
};
321+
xhr.open('GET', jsonUrl, true);
322+
xhr.send();
323+
}
324+
325+
getUserAgreementStatus() : string {
326+
const agreedUserAgreementVersion = localStorage.getItem('unity_academy_agreed_user_agreement_version');
327+
if (agreedUserAgreementVersion === null || agreedUserAgreementVersion === 'unagreed' || agreedUserAgreementVersion === 'unknown') {
328+
return 'unagreed';
329+
}
330+
if (this.latestUserAgreementVersion === 'unknown') {
331+
return 'unagreed';
332+
}
333+
if (agreedUserAgreementVersion !== this.latestUserAgreementVersion) {
334+
return 'new_user_agreement';
335+
}
336+
return 'agreed';
337+
}
338+
339+
setUserAgreementStatus(agree : boolean) : void {
340+
if (agree) {
341+
localStorage.setItem('unity_academy_agreed_user_agreement_version', this.latestUserAgreementVersion);
342+
} else {
343+
localStorage.setItem('unity_academy_agreed_user_agreement_version', 'unagreed');
344+
}
345+
}
346+
307347
instantiateInternal(prefabName : string) : GameObjectIdentifier {
308348
let prefabExists = false;
309349
const len = this.prefabInfo.prefab_info.length;
@@ -443,6 +483,7 @@ class UnityAcademyJsInteropContext {
443483
const eulerY = Math.atan2(deltaVector.x, deltaVector.z);
444484
gameObject.transform.rotation.x = eulerX * 180 / Math.PI;
445485
gameObject.transform.rotation.y = eulerY * 180 / Math.PI;
486+
gameObject.transform.rotation.z = 0;
446487
}
447488

448489
gameObjectDistanceInternal(gameObjectIdentifier_A : GameObjectIdentifier, gameObjectIdentifier_B : GameObjectIdentifier) : number {
@@ -562,24 +603,26 @@ class UnityAcademyJsInteropContext {
562603
return this.getGameObjectIdentifierForPrimitiveGameObject('MainCamera');
563604
}
564605

565-
onGUI_Label(content : string, x : number, y : number) : void {
606+
onGUI_Label(content : string, x : number, y : number, fontSize : number) : void {
566607
content = content.replaceAll('|', ''); // operator '|' is reserved as gui data separator in Unity Academy
567608
const newLabel = {
568609
type: 'label',
569610
content,
570611
x,
571612
y,
613+
fontSize,
572614
};
573615
this.guiData.push(newLabel);
574616
}
575617

576-
onGUI_Button(text : string, x: number, y : number, onClick : Function) : void {
618+
onGUI_Button(text : string, x: number, y : number, fontSize : number, onClick : Function) : void {
577619
text = text.replaceAll('|', ''); // operator '|' is reserved as gui data separator in Unity Academy
578620
const newButton = {
579621
type: 'button',
580622
text,
581623
x,
582624
y,
625+
fontSize,
583626
onClick,
584627
};
585628
this.guiData.push(newButton);
@@ -608,19 +651,19 @@ class UnityAcademyJsInteropContext {
608651
}
609652

610653
export function initializeModule(dimensionMode : string) {
611-
let INSTANCE = getInstance();
612-
if (INSTANCE !== undefined) {
613-
if (!INSTANCE.isUnityInstanceReady()) {
654+
let instance = getInstance();
655+
if (instance !== undefined) {
656+
if (!instance.isUnityInstanceReady()) {
614657
throw new Error('Unity instance is not ready to accept a new Source program now. Please try again later.');
615658
}
616-
if (INSTANCE.unityInstance === null) {
617-
INSTANCE.reloadUnityAcademyInstanceAfterTermination();
659+
if (instance.unityInstance === null) {
660+
instance.reloadUnityAcademyInstanceAfterTermination();
618661
}
619-
INSTANCE.dimensionMode = dimensionMode;
620-
INSTANCE.reset();
662+
instance.dimensionMode = dimensionMode;
663+
instance.reset();
621664
return;
622665
}
623666

624-
INSTANCE = new UnityAcademyJsInteropContext();
625-
INSTANCE.dimensionMode = dimensionMode;
667+
instance = new UnityAcademyJsInteropContext();
668+
instance.dimensionMode = dimensionMode;
626669
}

src/bundles/unity_academy/functions.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,11 @@ export function set_update(gameObjectIdentifier : GameObjectIdentifier, updateFu
125125

126126

127127
/**
128-
* Creates a new GameObject from an existing Prefab
129-
* <br><b>3D mode only</b>
128+
* Creates a new GameObject from an existing Prefab<br>
129+
* <br>
130+
* <b>3D mode only</b><br>
131+
* <br>
132+
* Available Prefab Information: <a href = 'https://unity-academy.s3.ap-southeast-1.amazonaws.com/webgl_assetbundles/prefab_info.html' rel="noopener noreferrer" target="_blank">Click Here</a>
130133
*
131134
* @param prefab_name The prefab name
132135
* @return the identifier of the newly created GameObject
@@ -299,7 +302,9 @@ export function set_rotation_euler(gameObjectIdentifier : GameObjectIdentifier,
299302
}
300303

301304
/**
302-
* Returns the scale of a given GameObject
305+
* Returns the scale (size factor) of a given GameObject
306+
* <br>
307+
* By default the scale of a GameObject is (1, 1, 1)
303308
* @param gameObjectIdentifier The identifier for the GameObject that you want to get scale for.
304309
* @return the scale represented in an array with three elements: [x, y, z]
305310
*
@@ -313,7 +318,9 @@ export function get_scale(gameObjectIdentifier : GameObjectIdentifier) : Array<N
313318
}
314319

315320
/**
316-
* Set the scale of a given GameObject
321+
* Set the scale (size) of a given GameObject
322+
* <br>
323+
* By default the scale of a GameObject is (1, 1, 1). Changing the scale of a GameObject along one axis will lead to a stretch or squeeze of the GameObject along that axis.
317324
*
318325
* @param gameObjectIdentifier The identifier for the GameObject that you want to change scale for.
319326
* @param x The x component for the scale.
@@ -879,42 +886,69 @@ export function on_collision_exit(gameObjectIdentifier : GameObjectIdentifier, e
879886

880887

881888
/**
882-
* Draw a text (string) on the screen with given screen position.<br>
883-
* The origin of screen space is upper-left corner and the positive Y axis is downward.<br>
884-
* You should put this under the `Update` function (or a function that is called by the `Update` function) to keep the text in every frame.
889+
* Draw a text (string) on the screen with given <b>screen space position</b> in the current frame.<br>
890+
* The origin of screen space is upper-left corner and the positive Y direction is downward.<br>
891+
* 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.<br>
885892
*
886893
* @param content The string you want to display on screen.
887894
* @param x The x coordinate of the text (in screen position).
888895
* @param y The y coordinate of the text (in screen position).
896+
* @param fontSize The size of the text
889897
* @category Graphical User Interface
890898
*/
891-
export function gui_label(content : string, x : number, y : number) : void {
899+
export function gui_label(content : string, x : number, y : number, fontSize : number) : void {
892900
checkUnityAcademyExistence();
901+
checkParameterType(content, 'string');
893902
checkParameterType(x, 'number');
894903
checkParameterType(y, 'number');
904+
checkParameterType(fontSize, 'number');
895905
getInstance()
896-
.onGUI_Label(content, x, y);
906+
.onGUI_Label(content, x, y, fontSize);
897907
}
898908

899909

900910
/**
901-
* Make a button on the screen with given screen position. When user clicks the button, the `onClick` function will be called.<br>
902-
* The origin of screen space is upper-left corner and the positive Y axis is downward.<br>
903-
* You should put this under the `Update` function (or a function that is called by the `Update` function) to keep the button in every frame.
911+
* Make a button on the screen with given <b>screen space position</b> in the current frame. When user clicks the button, the `onClick` function will be called.<br>
912+
* The origin of screen space is upper-left corner and the positive Y direction is downward.<br>
913+
* The drawn button 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 button stays in every frame.
914+
* <br>
915+
* <br>
916+
* If this function is called by a lifecycle event function, then the `onClick` function in the fourth parameter could also be considered as a lifecycle event function.<br>
917+
* 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.<br>
918+
* For example, the code piece below
919+
* ```
920+
* import {init_unity_academy_3d, set_start, set_update, instantiate, gui_button, set_position }
921+
* from "unity_academy";
922+
* init_unity_academy_3d();
923+
*
924+
* const cube = instantiate("cube");
925+
*
926+
* const cube_update = (gameObject) => {
927+
* gui_button("Button", 1000, 300, ()=>
928+
* set_position(gameObject, 0, 10, 6) // calling set_position inside the onClick function
929+
* );
930+
* };
931+
932+
* set_update(cube, cube_update);
933+
* ```
934+
* is correct.<br>
904935
*
905936
* @param text The text you want to display on the button.
906937
* @param x The x coordinate of the button (in screen position).
907938
* @param y The y coordinate of the button (in screen position).
908939
* @param onClick The function that will be called when user clicks the button on screen.
940+
* @param fontSize The size of the text inside the button.
909941
* @category Graphical User Interface
910942
*/
911-
export function gui_button(text : string, x: number, y : number, onClick : Function) : void {
943+
export function gui_button(text : string, x: number, y : number, fontSize : number, onClick : Function) : void {
912944
checkUnityAcademyExistence();
945+
checkParameterType(text, 'string');
913946
checkParameterType(x, 'number');
914947
checkParameterType(y, 'number');
948+
checkParameterType(fontSize, 'number');
915949
checkParameterType(onClick, 'function');
916950
getInstance()
917-
.onGUI_Button(text, x, y, onClick);
951+
.onGUI_Button(text, x, y, fontSize, onClick);
918952
}
919953

920954
/**

src/bundles/unity_academy/index.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* <br>
66
* <b>Code Examples: </b><a href = 'https://unity-academy.s3.ap-southeast-1.amazonaws.com/code_examples.html' rel="noopener noreferrer" target="_blank">Click Here</a><br>
77
* <b>Prefab Information: </b><a href = 'https://unity-academy.s3.ap-southeast-1.amazonaws.com/webgl_assetbundles/prefab_info.html' rel="noopener noreferrer" target="_blank">Click Here</a><br>
8+
* <b>User Agreement: </b><a href = 'https://unity-academy.s3.ap-southeast-1.amazonaws.com/user_agreement.html' rel="noopener noreferrer" target="_blank">Click Here</a><br>
89
* <br>
9-
* <b><u>Note that you need to use this module with a 'Native' variant of Source language, otherwise you may get strange errors.</u></b>
10+
* <b><u>Note that you need to use this module with a '<i>Native</i>' variant of Source language, otherwise you may get strange errors.</u></b>
1011
* <br>
1112
* <br>
1213
* <b>Lifecycle Event Functions</b><br>
@@ -24,15 +25,19 @@
2425
* ● ===>`Update` is called on every GameObject once in every frame after `Start` have been called. <br>
2526
* ● 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.<br>
2627
* ● 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.<br><br>
27-
* <u><b>[IMPORTANT]</b> All functions in this module that is NOT under the "<b>Outside Lifecycle</b>" or "Maths" category need to call by Unity Academy lifecycle event functions (Start or Update) 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.</u><br>
28+
* <u><b>[IMPORTANT]</b> All functions in this module that is NOT under the "<b>Outside Lifecycle</b>" 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.</u><br>
2829
* For example:
2930
* ```
30-
* import {init_unity_academy_3d, instantiate, set_start, set_update, set_position} from 'unity_academy';
31+
* import {init_unity_academy_3d, instantiate, set_start, set_update, set_position, set_rotation_euler} from 'unity_academy';
3132
* init_unity_academy_3d(); // Correct, since this function is under the "Outside Lifecycle" category and it can be called outside lifecycle event functions.
3233
* const cube = instantiate("cube"); // Correct
3334
* set_position(cube, 1, 2, 3); // WRONG, since set_position should ONLY be called inside a lifecycle event function
3435
* function my_start(gameObject){ // A sample Start event function which will be binded to cube by my_start later.
3536
* set_position(gameObject, 1, 2, 3); // Correct, since the call to set_position is inside a lifecycle event function
37+
* something_else(gameObject);
38+
* }
39+
* function something_else(obj){
40+
* set_rotation_euler(obj, 0, 45, 45); // Correct, since the function "set_rotation_euler" is intermediately called by the lifecycle event function "my_start" through "something_else"
3641
* }
3742
* set_start(cube, my_start); // Correct
3843
* ```
@@ -44,16 +49,16 @@
4449
* <br>
4550
* <br>
4651
* <b>Key differences between 2D and 3D mode</b><br>
47-
* ● In 2D mode the main camera renders the scene in <b>orthographic</b> mode (Z position is used to determine sequence when sprites overlapping), whereas in 3D mode the camera renders the scene in <b>perspective</b> mode. Moreover, 3D mode and 2D mode have different kinds of default camera controller.<br>
48-
* ● 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.)<br>
49-
* ● In 2D mode you need to use <b>instantiate_sprite</b> to create new GameObjects, whereas in 3D mode you need to use <b>instantiate</b> to create new GameObjects.<br>
50-
* ● 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.<br>
51-
* ● In 2D mode playing frame animations for sprite GameObjects is currently unavailable, whereas in 3D mode you need to use <b>play_animator_state</b> to play 3D animations.<br>
52+
* ● <u>In 2D mode</u> the main camera renders the scene in <b>orthographic</b> mode (Z position is used to determine sequence when sprites overlapping), whereas <u>in 3D mode</u> the camera renders the scene in <b>perspective</b> mode. Moreover, 3D mode and 2D mode have different kinds of default camera controller.<br>
53+
* ● <u>In 2D mode</u>, 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 <u>in 3D mode</u> 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.)<br>
54+
* ● <u>In 2D mode</u> you need to use <b>instantiate_sprite</b> to create new GameObjects, whereas <u>in 3D mode</u> you need to use <b>instantiate</b> to create new GameObjects.<br>
55+
* ● <u>In 2D mode</u> 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 <u>in 3D mode</u> Unity Academy use regular 3D rigidbody and 3D colliders to simulate 3D physics.<br>
56+
* ● <u>In 2D mode</u> playing frame animations for sprite GameObjects is currently unavailable, whereas <u>in 3D mode</u> you need to use <b>play_animator_state</b> to play 3D animations.<br>
5257
* <br>
5358
* <br>
5459
* <b>Space and Coordinates</b><br>
55-
* ● 3D: Uses <b>left-hand coordinate system</b>: +X denotes rightward, +Y denotes upward, +Z denotes forward.<br>
56-
* ● 2D: +X denotes rightward, +Y denotes upward, Z value actually still exists and usually used for determining sequence of overlapping 2D GameObjects like sprites.
60+
* ● <u>3D:</u> Uses <b>left-hand coordinate system</b>: +X denotes rightward, +Y denotes upward, +Z denotes forward.<br>
61+
* ● <u>2D:</u> +X denotes rightward, +Y denotes upward, Z value actually still exists and usually used for determining sequence of overlapping 2D GameObjects like sprites.
5762
* <br>
5863
* <br>
5964
* <b>Unity Academy Camera Control (only available when the default camera controllers are being used)</b><br>
@@ -113,8 +118,8 @@ export {
113118
on_collision_enter,
114119
on_collision_stay,
115120
on_collision_exit,
116-
// gui_label,
117-
// gui_button,
121+
gui_label,
122+
gui_button,
118123
get_main_camera_following_target,
119124
request_for_main_camera_control,
120125
set_custom_prop,

0 commit comments

Comments
 (0)