Compare commits

...

397 Commits

Author SHA1 Message Date
Davy Hélard
62ed7a4998 Put an icon on empty shape painter instances 2025-10-11 15:51:09 +02:00
Davy Hélard
dc5dab7086 Fix particle helper destruction 2025-10-11 15:48:43 +02:00
Davy Hélard
dad7546099 Display the 2D particle emitter renderer 2025-10-09 18:49:56 +02:00
Davy Hélard
0a55eb46d3 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-10-09 17:28:15 +02:00
Davy Hélard
2678d86783 Forbid selection when transform controls are used 2025-10-09 17:03:43 +02:00
Davy Hélard
b31618568a Fix default grid depth. 2025-10-09 16:16:10 +02:00
Davy Hélard
b3b4681c51 Fix grid not following the object 2025-10-09 15:33:42 +02:00
Davy Hélard
b1b7643efc Fix rotation controls 2025-10-09 14:58:07 +02:00
Davy Hélard
d43479cbe5 Avoid too big angles 2025-10-09 14:15:53 +02:00
Florian Rival
76d43e1695 Fix instances not visible when added by the AI 2025-10-05 22:43:16 +02:00
Davy Hélard
c3ee9d9891 Display the grid for duplicated and dragged objects 2025-10-03 17:45:26 +02:00
Davy Hélard
313f7857e2 Use the yellow point instead of Alt to drag the selection 2025-10-03 16:26:10 +02:00
Davy Hélard
3ac2aec229 Show the grid when dragging a new instance 2025-10-03 14:11:08 +02:00
Davy Hélard
92e8dfdf04 Keep the camera angle when centering on an object 2025-10-03 10:43:31 +02:00
Davy Hélard
80726da56d Add a getter for areEffectsHiddenInEditor. 2025-10-03 10:39:45 +02:00
Davy Hélard
3098fb034f Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-10-02 14:16:49 +02:00
Davy Hélard
9d0f172663 Format 2025-10-02 13:25:36 +02:00
Davy Hélard
e0d2f16dc1 Fix aspect ratio being lost when hot-reloading 3D models 2025-10-02 11:31:22 +02:00
Davy Hélard
0578c45b0f Additional rounding of object locations 2025-10-01 16:35:28 +02:00
Davy Hélard
a8866a7bd6 Use the origin point for translation and scaling 2025-10-01 11:49:16 +02:00
Davy Hélard
ec62e33530 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-09-30 18:10:57 +02:00
Davy Hélard
e1f12837f1 Fix hot-reloading not doing anything 2025-09-30 16:28:44 +02:00
Davy Hélard
732f3c14b9 Add a missing variant list in tests 2025-09-30 14:44:01 +02:00
Davy Hélard
7fdad45192 Add missing InstancesEditorSettings in tests 2025-09-30 14:31:46 +02:00
Davy Hélard
94590d3713 Fix to avoid to hot-reload the editor with game and the other way around 2025-09-30 13:04:13 +02:00
Davy Hélard
de310512a3 Fix captures not being done at preview 2025-09-30 13:04:13 +02:00
Florian Rival
a9edd2f21a Ensure Box2D wasm is properly loaded whatever the environment/protocol (#7862)
Tested on: 
- [x] preview (web-app, cloud project)
- [x] in-game editor (web-app, cloud project)
- [x] preview (electron cloud project, electron local project)
- [x] in-game editor (electron cloud project, electron local project)
- [x] cordova android
- [x] cordova ios
- [x] electron export (windows, macos)
2025-09-30 12:16:45 +02:00
Davy Hélard
0cc03bd4b5 Fix hot-reload losing model aspect ratio 2025-09-29 19:25:42 +02:00
Florian Rival
cdbcaf59d9 Fix framerate when dragging a new object 2025-09-29 17:10:36 +02:00
Florian Rival
4e9b09e426 Fix border color with AI pane 2025-09-29 17:08:03 +02:00
Davy Hélard
3d843a0170 Fix grid opacity 2025-09-29 14:10:24 +02:00
Davy Hélard
ee2f7fb8dc Fix the grid to snap on the object origin 2025-09-29 13:58:34 +02:00
Davy Hélard
a780601230 Fix custom object selection 2025-09-29 11:21:02 +02:00
Florian Rival
cc42923e16 Add support for touch controls in editor 2025-09-28 19:21:06 +02:00
Davy Hélard
0e8a223b24 Fix controls being put at (0; 0; 0) when switching of scene and a custom object is selected 2025-09-28 19:00:21 +02:00
Florian Rival
c2f17f9348 Improve rendering of the 2D plane in 3D so it's not cut when rotating the camera (#7857)
Allow the 2D plane in the 3D world to be rendered at the position "where
the 3D camera is looking at" (by projecting the camera frustrum on the Z
plane, with a "maximum drawing distance" to avoid a too large area). The
2D rendering texture size is not changed, so an area too big can create
blurry rendering near the camera - but this is expected.

In practice, 2D objects culling will limit the distance anyway of what's
visible.
2025-09-27 15:33:01 +02:00
Davy Hélard
0ba59d5d7f Remove log 2025-09-26 17:48:26 +02:00
Davy Hélard
6878a4cd75 Handle grid in variant tabs 2025-09-26 17:45:09 +02:00
Davy Hélard
33eed58c62 Save editor settings for variant tabs 2025-09-26 17:45:08 +02:00
Florian Rival
f516eff739 Fix toolbar not updating when enabling grid 2025-09-26 17:39:09 +02:00
Florian Rival
baefc272f6 Remove some TODOs and fix enabling/disabling grid 2025-09-26 17:25:51 +02:00
Florian Rival
2badc72dfb Remove useless fields 2025-09-26 16:44:10 +02:00
Davy Hélard
5e54f02061 Handle grid setting changes 2025-09-26 16:37:40 +02:00
Davy Hélard
827bb2d08d Fix grid axe when Alt is pressed 2025-09-25 17:30:28 +02:00
Davy Hélard
0fda0baa48 Remove log 2025-09-25 17:00:45 +02:00
Davy Hélard
e655cc0661 Fix undefined cell depth 2025-09-25 16:53:27 +02:00
Davy Hélard
225d1b37ab Remove log 2025-09-25 16:27:22 +02:00
Davy Hélard
5a0ab9dffa Snap objects on the grid (no grid setting update) 2025-09-25 16:08:23 +02:00
Davy Hélard
9300604d56 Round angles 2025-09-24 17:18:58 +02:00
Davy Hélard
02fcf1dbc2 Fix scaling control rotation 2025-09-24 17:04:25 +02:00
Florian Rival
a70b2e2c2c Fix low framerate when dragging a new instance 2025-09-24 12:55:59 +02:00
Davy Hélard
3e04b5a82f Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-09-24 10:36:54 +02:00
Florian Rival
cbcf0b7b70 Fix undo/redo stacking empty changes when selection is not even moved 2025-09-23 18:39:49 +02:00
Florian Rival
5d1fe83655 Fix undo/redo for default values of instances 2025-09-23 18:01:17 +02:00
Florian Rival
7cf70a6fd7 Reverse Q/E 2025-09-23 00:46:10 +02:00
Florian Rival
24c2dbc340 Add some tolerance radius when selecting an object 2025-09-22 23:26:31 +02:00
Florian Rival
290901849c Fix in-game edition framerate limitation 2025-09-22 17:47:51 +02:00
Florian Rival
59721cba7e Reduce scale speed 2025-09-22 13:08:30 +02:00
Florian Rival
53cd78bb45 Allow to scroll directly with mouse wheel click (even if shift not pressed) 2025-09-22 12:39:52 +02:00
Florian Rival
49f8ce9385 Remove default shortcut S to open scene properties to avoid conflict when using the new editor 2025-09-22 12:26:05 +02:00
Florian Rival
de2a9725f9 Adapt framerate of in-game editor according to usage/visibility 2025-09-21 22:29:14 +02:00
Florian Rival
b2ec3b5387 Merge branch 'master' into experimental-build/move-instance-in-game-2 2025-09-21 15:18:17 +02:00
Florian Rival
1873b5e592 Fix useless methods 2025-09-20 15:59:06 +02:00
Florian Rival
e47e35c090 Add shift + mouse wheel to pan 2025-09-20 15:45:23 +02:00
Florian Rival
ae572683f1 Add free camera speed boost with Shift 2025-09-20 12:14:44 +02:00
Florian Rival
f8e387230f Add ESC to clear selection, default to free camera, show a grab cursor when space pressed to pan (even if in orbit) 2025-09-20 12:09:52 +02:00
Florian Rival
9451e5969f Fix moving on the plane orthogonal to the camera with arrows/mouse wheel or Q/E 2025-09-19 16:51:36 +02:00
Florian Rival
ad7ddf09a3 Fix warning 2025-09-19 14:04:36 +02:00
Davy Hélard
8e70930d8d Fix default light not being exported 2025-09-18 17:15:42 +02:00
Davy Hélard
ab3bda24dc Fix a crash when undoing and a custom object is in the scene 2025-09-18 13:29:32 +02:00
Florian Rival
d33a7331b3 Fix custom object extraction on web-app 2025-09-18 12:19:12 +02:00
Davy Hélard
4d3793815f Fix a crash when adding a layer 2025-09-18 11:40:28 +02:00
Florian Rival
e1835d1144 Fix orbit camera restore 2025-09-17 19:16:08 +02:00
Florian Rival
72068460e1 Fix free camera in-game editor restore 2025-09-17 19:11:35 +02:00
Florian Rival
5da76ae655 Try to fix camera restore on in-game edition startup 2025-09-17 18:59:42 +02:00
Florian Rival
b5b25ad710 Fix editor shortcuts on macOS 2025-09-17 18:28:04 +02:00
Florian Rival
2279f069af Only show transform controls on non sealed/locked objects and adapt selection color to make it more obvious when a locked object is selected 2025-09-17 16:50:40 +02:00
Davy Hélard
f0a68db0d4 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-09-12 16:17:17 +02:00
Davy Hélard
15ed28ad8d Fix a crash at hot-reload after adding a scene 2025-09-12 16:17:10 +02:00
Davy Hélard
4291d5597a Update some todo 2025-09-10 16:26:37 +02:00
Davy Hélard
7af3fa5f2f Fix warning happening on messages which are not answers 2025-09-10 12:24:34 +02:00
Davy Hélard
5ce9591f68 Avoid a useless code generation when extracting a custom object 2025-09-09 18:45:21 +02:00
Davy Hélard
d5929010a7 Fix flow 2025-09-09 18:44:11 +02:00
Davy Hélard
30f2f5256b Fix custom object extraction behavior check 2025-09-09 14:21:20 +02:00
Davy Hélard
806d59fb88 Ask the selection AABB to all debuggers 2025-09-09 14:20:35 +02:00
Davy Hélard
e5f18ae2d8 Factorize the children configuration override check to ensure the hot-reload is consistent with the runtime 2025-09-09 11:55:24 +02:00
Davy Hélard
15a7fd1f85 Force the fov to 45° in the editor 2025-09-09 11:30:49 +02:00
Davy Hélard
3678a0dd45 Remove todo because this dialog should rarely happen and it now reload the editor instead of the game 2025-09-09 11:14:50 +02:00
Davy Hélard
197bd913b8 Explain why the new position is ignored when an object is dropped 2025-09-08 16:35:39 +02:00
Davy Hélard
baef911d61 Add type declaration 2025-09-08 13:43:49 +02:00
Davy Hélard
771e16e779 Refer to scene and external layout by name to launch previews 2025-09-08 12:11:08 +02:00
Davy Hélard
a2cf8e694b Factorize dragNewInstance 2025-09-08 11:55:18 +02:00
Davy Hélard
cb151ea30e Factorize _removeSelectionControls 2025-09-08 11:37:46 +02:00
Davy Hélard
7d1ebc8963 Fix screen adaptation 2025-09-05 17:55:07 +02:00
Davy Hélard
510a699c03 Remove useless todo 2025-09-05 17:05:09 +02:00
Davy Hélard
08892b68d4 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-09-04 19:43:52 +02:00
Davy Hélard
e079fa4108 Forbid to extract objects with Physics behaviors 2025-09-04 18:51:05 +02:00
Florian Rival
5a26e883b8 Make text to drop objects more visible on small screens 2025-09-04 18:36:14 +02:00
Florian Rival
6bf5b389b5 Make the embedded game frame works on small screen 2025-09-04 18:33:29 +02:00
Davy Hélard
2837a2906a Forbid to add Physics behaviors on child objects 2025-09-04 15:03:05 +02:00
Davy Hélard
8c63fae2f2 Fix flow 2025-09-04 14:37:19 +02:00
Davy Hélard
f837c22290 Display a message when trying to drag a 2D object 2025-09-04 12:21:48 +02:00
Davy Hélard
465bfa4deb Rename to notifyChangesToInGameEditor 2025-09-04 11:16:39 +02:00
Davy Hélard
a047ecdf9c Show a pop-up when the InGameEditor crashes 2025-09-03 18:31:31 +02:00
Davy Hélard
04c28de00b Hide the object icon when dragging a new object in the 3D editor 2025-09-03 11:52:23 +02:00
Davy Hélard
a7df6de044 Forbid to add 2D objects. 2025-09-02 19:46:57 +02:00
Davy Hélard
81c3199d00 Avoid hot-reloads when the extension doesn't have any custom object 2025-09-02 18:12:26 +02:00
Davy Hélard
dbe93a4bfd Hot-reload when changes are done in extensions 2025-09-02 17:48:34 +02:00
Davy Hélard
e0edbe8d57 Forbid interactions with text inputs from the editor 2025-09-02 11:41:07 +02:00
Davy Hélard
c25d51805f Forbid custom objects to use services from the editor 2025-09-02 11:21:03 +02:00
Davy Hélard
ab695370bb Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-09-01 18:35:05 +02:00
Davy Hélard
15ee216e33 Fix camera distance not being restored when changing tabs 2025-09-01 18:34:45 +02:00
Davy Hélard
3fac0522c9 Fix a crash when switching of scene 2025-09-01 18:05:10 +02:00
Florian Rival
8d502d7c5c Make it work on the web-app 2025-09-01 15:40:46 +02:00
Davy Hélard
12d18c45bc Rename variables 2025-09-01 13:56:40 +02:00
Davy Hélard
d255ab458b Fix tab switching when extracting a custom object 2025-09-01 12:26:42 +02:00
Florian Rival
fbfa0315de Merge branch 'master' into move-instance-in-game-2 2025-09-01 00:51:10 +02:00
Davy Hélard
9575705d29 Fix the inner area calculus when extracting a custom object 2025-08-29 19:49:27 +02:00
Davy Hélard
1223eaa348 Fix a crash in 2D projects 2025-08-29 12:14:52 +02:00
Davy Hélard
21ea077768 Better default zoom for variants 2025-08-29 11:14:18 +02:00
Davy Hélard
11895decd9 Fix switching to a variant was switching on the previous scene instead 2025-08-29 11:08:53 +02:00
Davy Hélard
c0e1e9fac6 Never generate scene event code for the editor 2025-08-28 19:54:15 +02:00
Davy Hélard
bd9c631e1b Fix inner area not displaying 2025-08-28 19:24:31 +02:00
Davy Hélard
4a6e8ef664 Fix missing included files to edit variants 2025-08-28 19:03:37 +02:00
Davy Hélard
e25345000d Rename to make it clear that the generation is for scenes events 2025-08-28 13:20:31 +02:00
Davy Hélard
111d37fc15 Make the difference between edited layers and camera layers easier to understand 2025-08-27 17:54:25 +02:00
Davy Hélard
4a83c9eb59 Fix inner area no longer displaying 2025-08-27 15:53:51 +02:00
Davy Hélard
177cb2c519 Move createSceneWithCustomObject in the editor 2025-08-27 15:08:32 +02:00
Davy Hélard
b5d69dee4c Clarify a comment 2025-08-27 14:50:43 +02:00
Davy Hélard
fbdaebe575 Avoid to switch the scene 2 times when changing of tab 2025-08-27 13:31:00 +02:00
Davy Hélard
2a2fd75ca3 Fix selection loss at hot-reload 2025-08-27 11:46:17 +02:00
Davy Hélard
af7563b4b7 Fix advanced properties not being hot-reloaded 2025-08-27 09:57:23 +02:00
Davy Hélard
1861c3be41 Handle missing zoom actions 2025-08-26 15:49:51 +02:00
Davy Hélard
895dc625eb Display the screen size rectangle 2025-08-26 11:48:05 +02:00
Davy Hélard
52aea76677 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-08-26 11:07:40 +02:00
Davy Hélard
4f87191176 Fix runtime crash report for in-game editor 2025-08-26 11:02:49 +02:00
Davy Hélard
43d4e2e8cc Force 2D+3D rendering mode. 2025-08-26 10:38:09 +02:00
Davy Hélard
cbfaa13978 Remove log 2025-08-25 17:39:11 +02:00
Davy Hélard
0aba5cf551 Delay the display of loader modals 2025-08-25 17:30:48 +02:00
Davy Hélard
a3ef07c163 Fix editor frame adaptation 2025-08-25 16:44:19 +02:00
Davy Hélard
a8ede5eee7 Fix generated events reload 2025-08-25 16:37:52 +02:00
Davy Hélard
ce965ca31c Fix libraries reload 2025-08-25 16:23:41 +02:00
Davy Hélard
039fbb8b1b Allow to launch back the editor preview after an error 2025-08-25 14:25:28 +02:00
Davy Hélard
80c1e67146 Refactor InGameEditor to no longer rely on SceneStack 2025-08-22 18:46:07 +02:00
Davy Hélard
2591cabdd0 Remove dead code 2025-08-22 16:28:48 +02:00
Davy Hélard
45620d6bf4 Avoid to return a string value from a static function. 2025-08-22 14:41:55 +02:00
Davy Hélard
89a59cdd35 Make a parameter const 2025-08-22 14:20:52 +02:00
Davy Hélard
17b819a423 Avoid to copy strings 2025-08-22 14:12:43 +02:00
Davy Hélard
afb4e3c1c6 Optimize DataProject serialization. 2025-08-22 11:59:56 +02:00
Davy Hélard
e5f8fe1bf8 Make the in-game editor resources from the project folder 2025-08-21 19:11:41 +02:00
Davy Hélard
30de5c6fa9 Use a different folder for editor previews 2025-08-21 15:55:21 +02:00
Davy Hélard
79a29cf7d5 Fix the "Include and libs export" time log 2025-08-21 15:33:00 +02:00
Davy Hélard
98a1323bf5 Allow to skip library files copy. 2025-08-21 15:08:19 +02:00
Davy Hélard
0fe6585897 Fix custom object hot-reloading 2025-08-18 19:14:16 +02:00
Davy Hélard
33dd605c57 Remove logs 2025-08-14 19:47:22 +02:00
Davy Hélard
bab0c227f8 Remove editedObjectDataList 2025-08-14 12:50:37 +02:00
Davy Hélard
5ad2be4a8a Remove log 2025-08-14 12:49:45 +02:00
Davy Hélard
39e18678f5 Keep the camera 2025-08-14 10:56:35 +02:00
Davy Hélard
f54b13a9f5 Avoid to hot-reload when replacing a resource with another one from the project. 2025-08-13 19:52:48 +02:00
Davy Hélard
703adc090a Avoid to call onChange when the resource name stay the same 2025-08-13 14:50:28 +02:00
Davy Hélard
81f0047dab Avoid to call triggerResourcesHaveChanged if no resource are added to the project 2025-08-12 19:14:49 +02:00
Davy Hélard
ef70add27b Format 2025-08-12 19:13:46 +02:00
Davy Hélard
d45932cc04 Allow to attach an external layout to a scene 2025-08-12 17:27:05 +02:00
Davy Hélard
081a97a5fc Remove log 2025-08-12 16:49:57 +02:00
Davy Hélard
2b3cedb441 Fast reload of project data for scene list changes 2025-08-12 16:49:37 +02:00
Davy Hélard
76426117ff Always rebuild the scene for editor hot-reload 2025-08-12 12:44:58 +02:00
Davy Hélard
e8720780eb Review changes: comment and object parameter 2025-08-11 19:30:12 +02:00
Davy Hélard
e5ed642121 Avoid to launch 2 previews when opening a project 2025-08-11 19:16:09 +02:00
Davy Hélard
a6602292b7 Remove log 2025-08-11 19:04:47 +02:00
Davy Hélard
859d8e08a0 Squash the hot-reloads for editing images when the editor dialog is closed 2025-08-11 18:31:05 +02:00
Davy Hélard
8128adfd7b Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-08-11 15:58:27 +02:00
Davy Hélard
f9317dd17f Refresh the resources without a hard-reload. 2025-08-11 15:54:50 +02:00
Davy Hélard
32a169014a Fix a crash in preview for 2D games. 2025-08-11 15:30:14 +02:00
Davy Hélard
37d3fd99eb Fix cached materials not being cleared when unloading the resource 2025-08-11 14:27:53 +02:00
Davy Hélard
e0973a8231 Clean up hot reload 2025-08-08 11:51:50 +02:00
Davy Hélard
876ce0d0a5 Add a todo for preview crash pop-up 2025-08-08 11:51:50 +02:00
Davy Hélard
9f614ce7e0 Refactor unloadResource 2025-08-08 11:51:50 +02:00
Davy Hélard
63de997e60 Fix making object as global. 2025-08-06 19:42:52 +02:00
Davy Hélard
d2aa49fd1c Rename a type 2025-08-06 16:17:12 +02:00
Davy Hélard
d355c16bdf Fix wrong preview level. 2025-08-06 16:14:04 +02:00
Davy Hélard
8c7f5d1ea8 Remove log 2025-08-06 14:59:20 +02:00
Davy Hélard
08635de08e Avoid 2 previews to run at the same time for the editor. 2025-08-06 14:56:54 +02:00
Davy Hélard
4dc8676848 Fix flow 2025-08-06 14:19:33 +02:00
Davy Hélard
164a230cbc Fix key codes for redo and cut. 2025-08-06 12:01:54 +02:00
Davy Hélard
f3dc551284 Fix delete and redo. 2025-08-06 11:53:28 +02:00
Davy Hélard
ec674e85d6 Fix the side panel from switching to instance when a hot-reload happens 2025-08-06 10:26:35 +02:00
Davy Hélard
32f47900cd Fix Scene3DTools not being exported for empty projects. 2025-08-06 10:13:11 +02:00
Davy Hélard
320774c8d8 Fix forcing perspective in 3D editor. 2025-08-05 21:55:49 +02:00
Davy Hélard
1594f44a72 Make newIDE keep track of 3D editor cameras 2025-08-05 16:04:44 +02:00
Davy Hélard
726d3a8816 Update the camera of 2d layers. 2025-08-01 18:40:09 +02:00
Davy Hélard
097f13db42 Always export Three.js for the in-game editor. 2025-08-01 18:34:31 +02:00
Davy Hélard
15585c2007 Fix event tab were focused instead of scene tab 2025-08-01 16:46:56 +02:00
Davy Hélard
ca0a2c3215 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-08-01 16:18:49 +02:00
Davy Hélard
d2820bdf2a Remove log. 2025-07-31 20:16:12 +02:00
Davy Hélard
947e5eb9a3 Fix object hot-reload when objects are added or removed. 2025-07-31 19:24:34 +02:00
Davy Hélard
5901e34f6d Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-31 17:56:31 +02:00
Davy Hélard
1c942d2f9d Fix 1st object of a type not appearing in the 3D editor. 2025-07-31 13:00:04 +02:00
Davy Hélard
b419bf8f35 Save the 2D editor camera position 2025-07-30 18:37:20 +02:00
Davy Hélard
7edfa1284a Fix object deletion 2025-07-30 16:27:10 +02:00
Davy Hélard
32e4006b2c Fix duplicate and paste 2025-07-30 15:56:28 +02:00
Davy Hélard
e773a1dbfc Fix object renaming. 2025-07-30 15:19:45 +02:00
Davy Hélard
3942214ba1 Make sure the 3D editor doesn't mess with the selection when disabled 2025-07-30 15:11:53 +02:00
Davy Hélard
461fc36401 Fix editor hot-reload no longer being triggered. 2025-07-30 11:12:24 +02:00
Davy Hélard
fa9198174e Fix the default object size. 2025-07-29 19:17:08 +02:00
Davy Hélard
96460d92d3 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-28 18:22:19 +02:00
Davy Hélard
e0d376c15b Handle background color changes. 2025-07-28 18:20:23 +02:00
Davy Hélard
46e0301dd0 Fix a crash when previewing a game. 2025-07-28 15:09:45 +02:00
Davy Hélard
14aa26c651 Fix hot-reload triggering to often. 2025-07-25 20:47:40 +02:00
Davy Hélard
ab59dfaa86 Remove useless code. 2025-07-25 19:39:15 +02:00
Davy Hélard
4841f46d95 Fix a mistake in previous commit. 2025-07-25 17:54:06 +02:00
Davy Hélard
faccc6a6f2 Handle copy and paste. 2025-07-25 17:41:04 +02:00
Florian Rival
f336e76a86 Fix size of 2D/3D toggle on small screens 2025-07-24 23:56:17 +02:00
Florian Rival
811604d15a Fix pointer events to use the 3D editor 2025-07-24 19:09:27 +02:00
Davy Hélard
dd005941e9 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-24 18:16:29 +02:00
Davy Hélard
311381332f Remove logs 2025-07-18 20:29:08 +02:00
Davy Hélard
50173c4127 Avoid to log EDIT in the history when the selection change 2025-07-18 20:13:52 +02:00
Davy Hélard
e030adbb72 Handle undo and redo. 2025-07-18 18:20:41 +02:00
Davy Hélard
3762ccf639 Factorize a log. 2025-07-18 14:55:50 +02:00
Davy Hélard
d2758400e0 Ensure no hot-reload is done in 2D mode.
Fix 3D editor not switching while in 2D mode.
2025-07-18 13:23:37 +02:00
Davy Hélard
1551ec440e Hide the diagnostic report for the in-game editor. 2025-07-17 15:05:15 +02:00
Davy Hélard
bd493e39e4 Update the 3D editor with changes done on instances with the 2D editor. 2025-07-17 14:01:23 +02:00
Davy Hélard
2fb663b63f Fix selection between 2D and 3D editors. 2025-07-17 12:03:06 +02:00
Davy Hélard
3878f35107 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-17 10:37:52 +02:00
Davy Hélard
e055c0edbf Remove some todo. 2025-07-11 19:43:14 +02:00
Davy Hélard
a02afc4c68 Fix resource reload. 2025-07-11 19:41:28 +02:00
Davy Hélard
30130df6fb Factorize 2025-07-11 17:45:56 +02:00
Davy Hélard
400cd2e22c Use a hard-reload to update asset content. 2025-07-11 17:29:07 +02:00
Davy Hélard
dc25a0e87f Remove a log 2025-07-11 14:29:15 +02:00
Davy Hélard
d21abd97a7 Allow to reload resources on hot-reload. 2025-07-11 14:14:15 +02:00
Davy Hélard
bd38eaf924 Handle all free camera keys for the switch. 2025-07-10 13:07:12 +02:00
Davy Hélard
360e98b3ee Fix to allow to reset to default size. 2025-07-10 12:45:16 +02:00
Davy Hélard
e03908da83 Fix update of instances without custom size. 2025-07-09 18:47:02 +02:00
Florian Rival
64e7afea7c Fix 2d/3d toggle way too large on large screens 2025-07-09 16:46:00 +02:00
Davy Hélard
13077f7ce8 Fix hot-reload when an effect type is changed. 2025-07-09 10:37:33 +02:00
Davy Hélard
ef68f4e7f6 Refactor camera handling. 2025-07-08 17:17:06 +02:00
Florian Rival
eaeef23bc6 Fix warning 2025-07-08 16:13:22 +02:00
Florian Rival
5a3b12a257 Ensure keys are not stuck when losing focus during in-game edition 2025-07-08 15:47:19 +02:00
Davy Hélard
794b74a90c Trigger an hot-reload when an effect is added. 2025-07-08 13:15:40 +02:00
Florian Rival
ac4a500fff Hard reload the in-game edition in case the connection to the editor is lost 2025-07-08 11:15:12 +02:00
Florian Rival
13a5939550 Add proper toggle between classic and 3d, real-time editor 2025-07-07 23:47:02 +02:00
Florian Rival
e560f0f08b Fix resizing/dragging of panels and set transparent background only for the 3D editor 2025-07-07 22:48:55 +02:00
Davy Hélard
3894720a93 Fix some original size implementation. 2025-07-07 17:19:02 +02:00
Davy Hélard
2a89b95510 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-07 16:16:27 +02:00
Florian Rival
e8e4bf07df Add checkbox to switch between editors
Also ensure custom object custom size is not reset.
TODO: getOriginalWidth and similar must be added on all objects

Fix crash because of deleted initalVariables in instance data
2025-07-07 15:58:53 +02:00
Davy Hélard
4d3b98f78b Fix camera dragging and rectangle selection conflict. 2025-07-04 15:16:26 +02:00
Davy Hélard
42ee73b046 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-03 18:08:27 +02:00
Davy Hélard
0a5df21c1b Fix an merge issue. 2025-07-02 17:27:48 +02:00
Davy Hélard
b0ea0c262b Fix an exception in 2D projects. 2025-07-02 16:56:02 +02:00
Davy Hélard
ffa9c45692 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-07-02 16:19:14 +02:00
Davy Hélard
15f6f16d22 Fix missing hot-reload when extracting a layout 2025-07-02 16:18:44 +02:00
Davy Hélard
c3240b6767 Avoid to interrupt users with hot-reload too often 2025-07-01 19:33:56 +02:00
Davy Hélard
2f7fe905b8 Fix hot-reload when installing an extension and the focused tab is not a graphical editor. 2025-07-01 14:58:46 +02:00
Davy Hélard
0de1d42d73 Fix hot-reload when adding a new object. 2025-07-01 12:25:44 +02:00
Davy Hélard
c4c8931c73 Add a toggle to disable layer effects. 2025-06-30 19:38:59 +02:00
Davy Hélard
1c9eb654ce Allow to duplicate the selection with Ctrl + draggging. 2025-06-27 19:06:27 +02:00
Davy Hélard
ee5b55cfbb Fix selected instances being updated continuously. 2025-06-27 18:10:30 +02:00
Davy Hélard
7e121526ca Drag all the selection. 2025-06-27 17:26:56 +02:00
Davy Hélard
79dfec1feb Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-06-27 14:06:28 +02:00
Davy Hélard
d30a278b97 Handle instance lock. 2025-06-27 14:05:36 +02:00
Davy Hélard
5ae9e80987 Avoid to move the camera when saving. 2025-06-26 18:38:07 +02:00
Davy Hélard
245fb1b3ab Force orthographic camera type. 2025-06-26 18:32:10 +02:00
Davy Hélard
bedf8a48a2 Fix hard-reload 2025-06-26 17:58:44 +02:00
Davy Hélard
3c77c9e2d8 Avoid some crashes on 2D projects. 2025-06-26 16:46:03 +02:00
Davy Hélard
92ba2c3111 Fix a crash when creating a new EBO 2025-06-26 15:18:13 +02:00
Davy Hélard
3ea754bba2 Display the selection rectangle. 2025-06-25 18:15:38 +02:00
Davy Hélard
a3429fb687 Add some TODO 2025-06-25 17:03:41 +02:00
Davy Hélard
476a107c46 Fix instance disappearing when changing of tab if they were added after an asset installation 2025-06-25 12:48:40 +02:00
Davy Hélard
f98d28bee2 Round positions 2025-06-24 16:38:04 +02:00
Davy Hélard
df5ee8732a Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-06-24 16:31:45 +02:00
Davy Hélard
bd500f5a90 Fix changing of variant not being applied at hot-reload 2025-06-23 18:09:26 +02:00
Davy Hélard
536518f4bb Fix hot-reload of custom objects 2025-06-23 16:19:57 +02:00
Davy Hélard
7e11936fee Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-06-23 10:48:55 +02:00
Davy Hélard
b0da0508e1 Format 2025-06-21 13:10:35 +02:00
Davy Hélard
48cc5a27f6 Fix hot-reload when custom objects are in an external layout. 2025-06-21 13:02:08 +02:00
Davy Hélard
d470513f84 Make sure camera tools are exported when there are 3D objects 2025-06-21 13:00:49 +02:00
Davy Hélard
baaeb4317f Disable the nagging screen for in-game editor hot-reload. 2025-06-20 14:46:15 +02:00
Davy Hélard
40156c5e74 Trigger an full data hot-reload when assets are installed. 2025-06-20 14:10:28 +02:00
Davy Hélard
da0a47b487 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-06-20 11:10:46 +02:00
Davy Hélard
ee12b0b372 Hot-reload events when an EBO is installed or updated. 2025-06-19 19:08:32 +02:00
Davy Hélard
31f7ec3a17 Comply variants when an extension is updated. 2025-06-19 19:06:59 +02:00
Davy Hélard
3ec7617b14 Load resources for custom object tabs. 2025-06-19 14:50:58 +02:00
Davy Hélard
69e9eebd8e Trigger an full data hot-reload when a resource is changed in an object 2025-06-18 14:39:35 +02:00
Davy Hélard
268af00ce8 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-06-17 10:54:35 +02:00
Davy Hélard
ad748e4ed5 Allow to drag an object on top of another one. 2025-06-13 17:28:35 +02:00
Davy Hélard
f933736f1f Fix inner area not being cleared on tab change. 2025-06-13 14:07:32 +02:00
Davy Hélard
89861de544 Fix selection in custom object editor. 2025-06-13 14:00:58 +02:00
Davy Hélard
d503fff76b Display the inner area of custom objects. 2025-06-13 13:00:17 +02:00
Davy Hélard
e8dc9e3b20 Fix variant tabs. 2025-06-12 18:35:22 +02:00
Davy Hélard
e40cdfca95 Restore camera location when changing of tab. 2025-06-12 17:34:34 +02:00
Davy Hélard
e357fca9c0 Fix selection 2025-06-12 16:57:51 +02:00
Davy Hélard
6b4f022157 Handle locked layers. 2025-06-12 16:28:01 +02:00
Davy Hélard
29c276d061 Add new instance to the selected layer. 2025-06-12 13:51:35 +02:00
Davy Hélard
b28eec1071 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-06-12 11:24:45 +02:00
Davy Hélard
c3c774579d Fix free camera vertical rotation. 2025-05-23 16:11:32 +02:00
Davy Hélard
193977d6d2 Move zoom functions next to each other. 2025-05-23 15:36:08 +02:00
Davy Hélard
931bb10edb Automatically toggle in orbit mode when a zoom action is used. 2025-05-23 15:34:15 +02:00
Davy Hélard
408b9c151c Allow to toggle between free camera or orbit. 2025-05-22 20:17:09 +02:00
Davy Hélard
bd96a63087 Filter messages according to the editorId. 2025-05-21 13:39:00 +02:00
Davy Hélard
dd5eb3b96f Format and type 2025-05-21 11:07:31 +02:00
Davy Hélard
cd46428b81 Show a box at hovering. 2025-05-20 19:16:59 +02:00
Davy Hélard
29926f057e Handle rectangular selection (without displaying the rectangle yet) 2025-05-20 17:30:29 +02:00
Davy Hélard
5444896b76 Fix tabs unregistration. 2025-05-19 17:42:32 +02:00
Davy Hélard
4adb7295bd Update InstanceData to avoid old data to override new ones when changing of tab. 2025-05-19 14:44:06 +02:00
Davy Hélard
2edd127d68 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-05-16 10:47:40 +02:00
Davy Hélard
eb45ed8b9a Avoid to create objects where they can't be seen. 2025-05-14 21:03:20 +02:00
Davy Hélard
3a81c76741 Keep layer and object changes when switching of tabs. 2025-05-14 17:13:01 +02:00
Davy Hélard
14e2a6236a Keep the selection when switching of tabs. 2025-05-14 16:13:35 +02:00
Davy Hélard
1ea065dec6 Fix registration to previewDebuggerServer when a new tab is open. 2025-05-13 15:20:19 +02:00
Davy Hélard
db42bbef62 Better margin for zoom to fit. 2025-05-13 12:40:52 +02:00
Davy Hélard
bd0b610a90 Avoid the in-game editor to crash on 2D projects. 2025-05-13 12:34:26 +02:00
Davy Hélard
f46213ce6e Fix onEditorHidden when another project is open. 2025-05-13 12:17:12 +02:00
Davy Hélard
0909e1d5fe Fix zoom to objects 2025-05-13 12:17:12 +02:00
Davy Hélard
95c15b2edb Fix camera jumps when starting to drag. 2025-05-12 18:49:04 +02:00
Davy Hélard
e48115336d Fix flow 2025-05-12 18:32:45 +02:00
Davy Hélard
daa957fa7b Fix drop-down menu no showing up when several tabs are open. 2025-05-12 18:20:38 +02:00
Davy Hélard
3d5a997633 Merge remote-tracking branch 'official/master' into test6 2025-05-12 12:31:44 +02:00
Davy Hélard
ca15d0316d Handle duplication, deletion and cutting. 2025-05-12 12:25:19 +02:00
Davy Hélard
96a9c28e44 Handle copy and paste. 2025-05-12 12:25:19 +02:00
D8H
006f8b1900 Fix grid snapping of pasted instances (#7602) 2025-05-12 12:24:55 +02:00
Davy Hélard
d576b3ede8 Remove logs. 2025-05-07 17:03:44 +02:00
Davy Hélard
195664ee09 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-05-07 16:23:58 +02:00
Davy Hélard
bf85daf354 Fix the instance position sent to the instance property editor. 2025-05-07 16:22:35 +02:00
Davy Hélard
d3192734a1 Fix the default dimensions in the instance property editor. 2025-05-07 16:20:58 +02:00
Davy Hélard
16f5ecdd0b Remove logs. 2025-05-07 15:35:17 +02:00
Davy Hélard
b17fa8e9ae Use native resolution and show the drop-down menu. 2025-05-06 18:36:47 +02:00
Davy Hélard
88bcd797e9 Fix object hot-reload for external layouts and custom objects. 2025-05-06 14:45:49 +02:00
Davy Hélard
577d605b87 Plug fast hot-reload for layers. 2025-05-06 14:25:58 +02:00
Davy Hélard
a5e29a7f14 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-05-06 10:46:48 +02:00
Davy Hélard
7f27526c7c Handle select all instances 2025-05-05 10:43:20 +02:00
Davy Hélard
f3adb4e876 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-05-05 09:57:43 +02:00
Davy Hélard
c571a65a32 Handle the instance list selection. 2025-05-02 18:11:19 +02:00
Davy Hélard
55f38231a9 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-05-02 14:16:32 +02:00
Davy Hélard
6a2e37d147 Handle zoom commands. 2025-05-01 17:16:44 +02:00
Davy Hélard
f6ec9634ce Allow to unselect objects. 2025-04-30 11:29:54 +02:00
Davy Hélard
eedb030619 Format 2025-04-30 11:28:29 +02:00
Davy Hélard
c828c2cace Allow custom objects 3D edition. 2025-04-29 18:19:12 +02:00
Davy Hélard
b4188893a0 Display the content of custom object editors. 2025-04-29 14:17:07 +02:00
Davy Hélard
8ea26ec148 Enable interaction for external layouts. 2025-04-28 12:31:37 +02:00
Davy Hélard
bf73e430f8 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-04-28 11:17:26 +02:00
Davy Hélard
343f6a572d Allow to edit custom object instances in the editor 2025-04-25 19:09:20 +02:00
Davy Hélard
66134e95ce Handle object configuration changes. 2025-04-25 15:43:58 +02:00
Davy Hélard
1ffb03ccd5 Fix controls not moving with the instance edited from the panel 2025-04-25 11:55:37 +02:00
Davy Hélard
998bf76e72 Merge commit 'c5dd26c93b65ded5ec7972699e93c57dd320360b' into move-instance-in-game-2 2025-04-24 16:12:00 +02:00
Davy Hélard
47553bc020 Display rotation and size in fields. 2025-04-22 19:07:32 +02:00
Davy Hélard
c84b2410b9 Fix the rotation. 2025-04-22 15:11:38 +02:00
Davy Hélard
fbbac73e94 Fix the toggle. 2025-04-22 14:13:00 +02:00
Davy Hélard
c2f0f9b9a3 Handle rotation and scaling. 2025-04-18 19:10:36 +02:00
Davy Hélard
4d02ae092b Fix the dragging of a new object. 2025-04-18 16:15:44 +02:00
Davy Hélard
7db2849404 Fix the raycast when objects are found in several layers. 2025-04-18 13:34:42 +02:00
Davy Hélard
d5a3b55e98 Merge remote-tracking branch 'official/master' into move-instance-in-game-2 2025-04-15 17:18:23 +02:00
Florian Rival
79206fbfee WIP: Drag new instance 2025-02-06 15:26:29 +01:00
Florian Rival
2089e29664 WIP: display selection boxes in 3D editor 2025-01-29 17:49:06 +01:00
Florian Rival
38c5425d0c Fix selection of 3D models 2025-01-29 14:58:48 +01:00
Florian Rival
b0d45e80de Improve outline pass rendering 2025-01-26 16:28:04 +01:00
Florian Rival
2ac5beee0c Improve camera movement in 3D space 2025-01-25 14:23:30 +01:00
Florian Rival
c99c73fa96 WIP: handle multiselection movement and camera rotation 2025-01-25 13:02:01 +01:00
Florian Rival
f796228d06 Merge branch 'master' into move-instance-in-game-2 2025-01-25 11:17:11 +01:00
Florian Rival
ca3836b3c7 WIP: Handle deletion from in-game editor 2025-01-24 13:29:41 +01:00
Florian Rival
918f8ec989 WIP: handle selection in in-game editor 2025-01-22 22:50:20 +01:00
Florian Rival
f44d9682ee WIP: Allow more changes to instances 2025-01-20 16:05:50 +01:00
Florian Rival
ce6800164d WIP: replicate mouse/trackpad movement from the existing editor 2025-01-20 15:13:39 +01:00
Florian Rival
4afb2de178 WIP: add some camera movement 2025-01-19 22:17:28 +01:00
Florian Rival
9865fd3663 Simplify InGameEditor and add camera zooming (in 2D plane) with mouse wheel 2025-01-19 18:51:37 +01:00
Florian Rival
5501527974 Merge branch 'master' into move-instance-in-game-2 2025-01-19 16:14:14 +01:00
Florian Rival
3e4826bafc Fix memory leak with outlining in-game edited object 2025-01-15 09:50:09 +01:00
AlexandreSi
d914f9f5be Add comment 2025-01-07 15:51:40 +01:00
AlexandreSi
ab8c90dd41 Send layout name to runtime game when editing instance in editor 2025-01-07 15:32:52 +01:00
AlexandreSi
8882841a8d Fix position offset 2025-01-07 15:19:24 +01:00
AlexandreSi
3d357950f9 Send message to game when updating an instance position from the editor 2025-01-07 14:51:31 +01:00
AlexandreSi
ef5d2651c0 Update instance when receiving update from game and hot reload game 2025-01-03 17:15:32 +01:00
AlexandreSi
1dd4bb9b7a Add method to get instance persistent uuid 2025-01-03 16:39:06 +01:00
AlexandreSi
682e6f6b03 Send instances updated back to editor when modifying them with transform controls 2025-01-03 15:51:55 +01:00
AlexandreSi
b468c28ae8 Improve usability of the in-game instance control 2025-01-02 14:57:31 +01:00
AlexandreSi
9fc9452a08 Better handle gizmos 2025-01-02 12:29:53 +01:00
AlexandreSi
3b176b7152 Fix cleaning of listeners and helpers 2025-01-02 11:46:56 +01:00
AlexandreSi
f1d1a9b66b Remove unused import 2025-01-02 11:27:41 +01:00
AlexandreSi
c0d2f491a8 Fix code after rebase 2025-01-02 11:27:41 +01:00
AlexandreSi
aeecce2ea8 Avoid creating transform controls on each click 2025-01-02 11:05:05 +01:00
AlexandreSi
647ee149ff Fix hovered object outline 2025-01-02 11:05:05 +01:00
AlexandreSi
22313e148a Import changes to render without step 2025-01-02 11:05:05 +01:00
AlexandreSi
31f2d7ce2e Select object on click 2025-01-02 11:03:59 +01:00
AlexandreSi
4a0efec6c2 Add TransformControls to Three addons 2025-01-02 11:03:59 +01:00
AlexandreSi
5f555df5c1 Add outline pass in layer composer 2025-01-02 11:03:59 +01:00
AlexandreSi
53a611a1b9 Add outline pass to three addons 2025-01-02 11:03:59 +01:00
AlexandreSi
14511b23af WIP 2025-01-02 11:03:59 +01:00
Florian Rival
fa2371274d Add support for external layouts and reload when a change is made in the runtime 2025-01-02 00:10:45 +01:00
Florian Rival
0aea8dfa0f WIP: refactoring and prepare for handling external layout in-game edition 2024-12-31 17:07:21 +01:00
Florian Rival
81ca18098d Fix text object pre-rendering update 2024-12-31 16:04:11 +01:00
Florian Rival
b6e44a022f Add a note 2024-12-30 20:06:38 +01:00
Florian Rival
1a8eee2477 Remove game.resumed/paused in favor of status 2024-12-30 20:00:28 +01:00
Florian Rival
d0ef92da03 Fix flow and formatting 2024-12-30 19:25:43 +01:00
Florian Rival
9c98cb3b3b Relaunch an (in-game edition) preview when a change is made in GDJS Runtime 2024-12-30 18:56:09 +01:00
Florian Rival
3681542056 Robustify in game preview state management in the editor 2024-12-30 15:38:21 +01:00
Florian Rival
7c0bf135d7 WIP: Support for rendering while paused for in game edition 2024-12-30 12:32:05 +01:00
Florian Rival
9a31dd046c WIP: robustify scene change and initial scene for in game edition 2024-12-29 17:23:20 +01:00
Florian Rival
74401a1f9c WIP: Add scene change 2024-12-29 15:50:50 +01:00
Florian Rival
cedc6ea3e9 WIP 2024-12-29 15:50:50 +01:00
233 changed files with 14436 additions and 1827 deletions

View File

@@ -813,6 +813,13 @@ gd::String PlatformExtension::GetObjectFullType(const gd::String& extensionName,
return extensionName + separator + objectName;
}
gd::String PlatformExtension::GetVariantFullType(const gd::String& extensionName,
const gd::String& objectName,
const gd::String& variantName) {
const auto& separator = GetNamespaceSeparator();
return extensionName + separator + objectName + separator + variantName;
}
gd::String PlatformExtension::GetExtensionFromFullObjectType(
const gd::String& type) {
const auto separatorIndex =

View File

@@ -663,6 +663,10 @@ class GD_CORE_API PlatformExtension {
static gd::String GetObjectFullType(const gd::String& extensionName,
const gd::String& objectName);
static gd::String GetVariantFullType(const gd::String& extensionName,
const gd::String& objectName,
const gd::String& variantName);
static gd::String GetExtensionFromFullObjectType(const gd::String& type);
static gd::String GetObjectNameFromFullObjectType(const gd::String& type);

View File

@@ -0,0 +1,23 @@
#include "UsedObjectTypeFinder.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/IDE/ProjectBrowserHelper.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
namespace gd {
bool UsedObjectTypeFinder::ScanProject(gd::Project &project,
const gd::String &objectType) {
UsedObjectTypeFinder worker(project, objectType);
gd::ProjectBrowserHelper::ExposeProjectObjects(project, worker);
return worker.hasFoundObjectType;
};
void UsedObjectTypeFinder::DoVisitObject(gd::Object &object) {
if (!hasFoundObjectType && object.GetType() == objectType) {
hasFoundObjectType = true;
}
};
} // namespace gd

View File

@@ -0,0 +1,39 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <set>
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/SourceFileMetadata.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
#include "GDCore/String.h"
namespace gd {
class Project;
class Object;
} // namespace gd
namespace gd {
class GD_CORE_API UsedObjectTypeFinder : public ArbitraryObjectsWorker {
public:
static bool ScanProject(gd::Project &project, const gd::String &objectType);
private:
UsedObjectTypeFinder(gd::Project &project_, const gd::String &objectType_)
: project(project_), objectType(objectType_){};
gd::Project &project;
const gd::String &objectType;
bool hasFoundObjectType = false;
// Object Visitor
void DoVisitObject(gd::Object &object) override;
};
}; // namespace gd

View File

@@ -7,7 +7,6 @@
#include <map>
#include "GDCore/CommonTools.h"
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Project/ResourcesAbsolutePathChecker.h"
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Tools/Localization.h"
@@ -26,42 +25,37 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
if (updateOriginalProject) {
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, originalProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
originalProject, fs, destinationDirectory, preserveAbsoluteFilenames,
preserveDirectoryStructure);
} else {
gd::Project clonedProject = originalProject;
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, clonedProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
clonedProject, fs, destinationDirectory, preserveAbsoluteFilenames,
preserveDirectoryStructure);
}
return true;
}
bool ProjectResourcesCopier::CopyAllResourcesTo(
gd::Project& originalProject,
gd::Project& clonedProject,
bool ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
gd::Project& project,
AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
// Check if there are some resources with absolute filenames
gd::ResourcesAbsolutePathChecker absolutePathChecker(originalProject.GetResourcesManager(), fs);
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, absolutePathChecker);
auto projectDirectory = fs.DirNameFrom(originalProject.GetProjectFile());
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
std::cout << "Copying all resources from " << projectDirectory << " to "
<< destinationDirectory << "..." << std::endl;
// Get the resources to be copied
gd::ResourcesMergingHelper resourcesMergingHelper(
clonedProject.GetResourcesManager(), fs);
project.GetResourcesManager(), fs);
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
resourcesMergingHelper.PreserveDirectoriesStructure(
preserveDirectoryStructure);
resourcesMergingHelper.PreserveAbsoluteFilenames(preserveAbsoluteFilenames);
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
gd::ResourceExposer::ExposeWholeProjectResources(project,
resourcesMergingHelper);
// Copy resources

View File

@@ -50,12 +50,10 @@ class GD_CORE_API ProjectResourcesCopier {
bool preserveDirectoryStructure = true);
private:
static bool CopyAllResourcesTo(gd::Project& originalProject,
gd::Project& clonedProject,
gd::AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
static bool AdaptFilePathsAndCopyAllResourcesTo(
gd::Project &project, gd::AbstractFileSystem &fs,
gd::String destinationDirectory, bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
};
} // namespace gd

View File

@@ -1,17 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "ResourcesAbsolutePathChecker.h"
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/String.h"
namespace gd {
void ResourcesAbsolutePathChecker::ExposeFile(gd::String& resourceFilename) {
if (fs.IsAbsolute(resourceFilename)) hasAbsoluteFilenames = true;
}
} // namespace gd

View File

@@ -1,48 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
namespace gd {
/**
* \brief Helper used to check if a project has at least a resource with an
* absolute filename.
*
* \see ArbitraryResourceWorker
*
* \ingroup IDE
*/
class GD_CORE_API ResourcesAbsolutePathChecker
: public ArbitraryResourceWorker {
public:
ResourcesAbsolutePathChecker(gd::ResourcesManager &resourcesManager,
AbstractFileSystem &fileSystem)
: ArbitraryResourceWorker(resourcesManager), hasAbsoluteFilenames(false),
fs(fileSystem){};
virtual ~ResourcesAbsolutePathChecker(){};
/**
* Return true if there is at least a resource with an absolute filename.
*/
bool HasResourceWithAbsoluteFilenames() const {
return hasAbsoluteFilenames;
};
/**
* Check if there is a resource with an absolute path
*/
virtual void ExposeFile(gd::String& resource);
private:
bool hasAbsoluteFilenames;
AbstractFileSystem& fs;
};
} // namespace gd

View File

@@ -22,6 +22,14 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
resourceFullFilename = gd::AbstractFileSystem::NormalizeSeparator(
resourceFullFilename); // Protect against \ on Linux.
if (shouldUseOriginalAbsoluteFilenames) {
// There is no need to fill `newFilenames` and `oldFilenames` since the file
// location stays the same.
fs.MakeAbsolute(resourceFullFilename, baseDirectory);
resourceFilename = resourceFullFilename;
return;
}
// In the case of absolute filenames that we don't want to preserve, or
// in the case of copying files without preserving relative folders, the new
// names will be generated from the filename alone (with collision protection).

View File

@@ -3,8 +3,7 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef RESOURCESMERGINGHELPER_H
#define RESOURCESMERGINGHELPER_H
#pragma once
#include <map>
#include <memory>
@@ -58,6 +57,15 @@ public:
preserveAbsoluteFilenames = preserveAbsoluteFilenames_;
};
/**
* \brief Set if the absolute filenames of original files must be used for
* any resource.
*/
void SetShouldUseOriginalAbsoluteFilenames(
bool shouldUseOriginalAbsoluteFilenames_ = true) {
shouldUseOriginalAbsoluteFilenames = shouldUseOriginalAbsoluteFilenames_;
};
/**
* \brief Return a map containing the resources old absolute filename as key,
* and the resources new filenames as value. The new filenames are relative to
@@ -93,10 +101,13 @@ public:
///< absolute (C:\MyFile.png will not be
///< transformed into a relative filename
///< (MyFile.png).
/**
* Set to true if the absolute filenames of original files must be used for
* any resource.
*/
bool shouldUseOriginalAbsoluteFilenames = false;
gd::AbstractFileSystem&
fs; ///< The gd::AbstractFileSystem used to manipulate files.
};
} // namespace gd
#endif // RESOURCESMERGINGHELPER_H

View File

@@ -6,6 +6,7 @@
#include "SceneResourcesFinder.h"
#include "GDCore/IDE/ResourceExposer.h"
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -27,6 +28,14 @@ std::set<gd::String> SceneResourcesFinder::FindSceneResources(gd::Project &proje
return resourceWorker.resourceNames;
}
std::set<gd::String> SceneResourcesFinder::FindEventsBasedObjectVariantResources(gd::Project &project,
gd::EventsBasedObjectVariant &variant) {
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
gd::ResourceExposer::ExposeEventsBasedObjectVariantResources(project, variant, resourceWorker);
return resourceWorker.resourceNames;
}
void SceneResourcesFinder::AddUsedResource(gd::String &resourceName) {
if (resourceName.empty()) {
return;

View File

@@ -15,6 +15,7 @@ namespace gd {
class Project;
class Layout;
class SerializerElement;
class EventsBasedObjectVariant;
} // namespace gd
namespace gd {
@@ -27,7 +28,7 @@ namespace gd {
class SceneResourcesFinder : private gd::ArbitraryResourceWorker {
public:
/**
* @brief Find resource usages in a given scenes.
* @brief Find resource usages in a given scene.
*
* It doesn't include resources used globally.
*/
@@ -41,6 +42,13 @@ public:
*/
static std::set<gd::String> FindProjectResources(gd::Project &project);
/**
* @brief Find resource usages in a given events-based object variant.
*/
static std::set<gd::String>
FindEventsBasedObjectVariantResources(gd::Project &project,
gd::EventsBasedObjectVariant &variant);
virtual ~SceneResourcesFinder(){};
private:

View File

@@ -332,6 +332,12 @@ void ProjectBrowserHelper::ExposeLayoutObjects(gd::Layout &layout,
worker.Launch(layout.GetObjects());
}
void ProjectBrowserHelper::ExposeEventsBasedObjectVariantObjects(
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
gd::ArbitraryObjectsWorker &worker) {
worker.Launch(eventsBasedObjectVariant.GetObjects());
}
void ProjectBrowserHelper::ExposeProjectFunctions(
gd::Project &project, gd::ArbitraryEventsFunctionsWorker &worker) {

View File

@@ -13,6 +13,7 @@ class EventsFunctionsExtension;
class EventsFunction;
class EventsBasedBehavior;
class EventsBasedObject;
class EventsBasedObjectVariant;
class ArbitraryEventsWorker;
class ArbitraryEventsWorkerWithContext;
class ArbitraryEventsFunctionsWorker;
@@ -207,6 +208,17 @@ public:
static void ExposeLayoutObjects(gd::Layout &layout,
gd::ArbitraryObjectsWorker &worker);
/**
* \brief Call the specified worker on all ObjectContainers of the
* events-based object variant.
*
* This should be the preferred way to traverse all the objects of an
* events-based object variant.
*/
static void ExposeEventsBasedObjectVariantObjects(
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
gd::ArbitraryObjectsWorker &worker);
/**
* \brief Call the specified worker on all FunctionsContainers of the project
* (global, layouts...)

View File

@@ -116,6 +116,34 @@ void ResourceExposer::ExposeLayoutResources(
project, layout, eventWorker);
}
void ResourceExposer::ExposeEventsBasedObjectVariantResources(
gd::Project &project,
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
gd::ArbitraryResourceWorker &worker) {
// Expose object configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
gd::ProjectBrowserHelper::ExposeEventsBasedObjectVariantObjects(
eventsBasedObjectVariant, objectWorker);
// Expose layer effect resources
auto &layers = eventsBasedObjectVariant.GetLayers();
for (std::size_t layerIndex = 0; layerIndex < layers.GetLayersCount();
layerIndex++) {
auto &layer = layers.GetLayer(layerIndex);
auto &effects = layer.GetEffects();
for (size_t effectIndex = 0; effectIndex < effects.GetEffectsCount();
effectIndex++) {
auto &effect = effects.GetEffect(effectIndex);
gd::ResourceExposer::ExposeEffectResources(project.GetCurrentPlatform(),
effect, worker);
}
}
// We don't check the events because it would cost too much to do it for every
// variant. Resource usage in events-based object events and their
// dependencies should be rare.
}
void ResourceExposer::ExposeEffectResources(
gd::Platform &platform,
gd::Effect &effect,

View File

@@ -9,10 +9,11 @@ namespace gd {
class Platform;
class Project;
class ArbitraryResourceWorker;
class EventsBasedObjectVariant;
class EventsFunctionsExtension;
class Effect;
class Layout;
} // namespace gd
} // namespace gd
namespace gd {
@@ -20,7 +21,7 @@ namespace gd {
* \brief
*/
class GD_CORE_API ResourceExposer {
public:
public:
/**
* \brief Called ( e.g. during compilation ) so as to inventory internal
* resources, sometimes update their filename or any other work or resources.
@@ -50,6 +51,14 @@ class GD_CORE_API ResourceExposer {
gd::Layout &layout,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose the resources used in a given events-based object variant.
*/
static void ExposeEventsBasedObjectVariantResources(
gd::Project &project,
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose the resources used in a given effect.
*/

View File

@@ -60,6 +60,18 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
} else {
SetHasCustomDepth(false);
}
if (element.HasChild("defaultWidth") ||
element.HasAttribute("defaultWidth")) {
defaultWidth = element.GetDoubleAttribute("defaultWidth");
}
if (element.HasChild("defaultHeight") ||
element.HasAttribute("defaultHeight")) {
defaultHeight = element.GetDoubleAttribute("defaultHeight");
}
if (element.HasChild("defaultDepth") ||
element.HasAttribute("defaultDepth")) {
defaultDepth = element.GetDoubleAttribute("defaultDepth");
}
SetZOrder(element.GetIntAttribute("zOrder", 0, "plan"));
SetOpacity(element.GetIntAttribute("opacity", 255));
SetLayer(element.GetStringAttribute("layer"));
@@ -74,45 +86,51 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
if (persistentUuid.empty()) ResetPersistentUuid();
numberProperties.clear();
const SerializerElement& numberPropertiesElement =
element.GetChild("numberProperties", 0, "floatInfos");
numberPropertiesElement.ConsiderAsArrayOf("property", "Info");
for (std::size_t j = 0; j < numberPropertiesElement.GetChildrenCount(); ++j) {
gd::String name =
numberPropertiesElement.GetChild(j).GetStringAttribute("name");
double value =
numberPropertiesElement.GetChild(j).GetDoubleAttribute("value");
if (element.HasChild("numberProperties", "floatInfos")) {
const SerializerElement& numberPropertiesElement =
element.GetChild("numberProperties", 0, "floatInfos");
numberPropertiesElement.ConsiderAsArrayOf("property", "Info");
for (std::size_t j = 0; j < numberPropertiesElement.GetChildrenCount(); ++j) {
gd::String name =
numberPropertiesElement.GetChild(j).GetStringAttribute("name");
double value =
numberPropertiesElement.GetChild(j).GetDoubleAttribute("value");
// Compatibility with GD <= 5.1.164
if (name == "z") {
SetZ(value);
} else if (name == "rotationX") {
SetRotationX(value);
} else if (name == "rotationY") {
SetRotationY(value);
} else if (name == "depth") {
SetHasCustomDepth(true);
SetCustomDepth(value);
}
// end of compatibility code
else {
numberProperties[name] = value;
// Compatibility with GD <= 5.1.164
if (name == "z") {
SetZ(value);
} else if (name == "rotationX") {
SetRotationX(value);
} else if (name == "rotationY") {
SetRotationY(value);
} else if (name == "depth") {
SetHasCustomDepth(true);
SetCustomDepth(value);
}
// end of compatibility code
else {
numberProperties[name] = value;
}
}
}
stringProperties.clear();
const SerializerElement& stringPropElement =
element.GetChild("stringProperties", 0, "stringInfos");
stringPropElement.ConsiderAsArrayOf("property", "Info");
for (std::size_t j = 0; j < stringPropElement.GetChildrenCount(); ++j) {
gd::String name = stringPropElement.GetChild(j).GetStringAttribute("name");
gd::String value =
stringPropElement.GetChild(j).GetStringAttribute("value");
stringProperties[name] = value;
if (element.HasChild("stringProperties", "stringInfos")) {
const SerializerElement& stringPropElement =
element.GetChild("stringProperties", 0, "stringInfos");
stringPropElement.ConsiderAsArrayOf("property", "Info");
for (std::size_t j = 0; j < stringPropElement.GetChildrenCount(); ++j) {
gd::String name = stringPropElement.GetChild(j).GetStringAttribute("name");
gd::String value =
stringPropElement.GetChild(j).GetStringAttribute("value");
stringProperties[name] = value;
}
}
GetVariables().UnserializeFrom(
element.GetChild("initialVariables", 0, "InitialVariables"));
if (element.HasChild("initialVariables", "InitialVariables")) {
GetVariables().UnserializeFrom(
element.GetChild("initialVariables", 0, "InitialVariables"));
}
}
void InitialInstance::SerializeTo(SerializerElement& element) const {
@@ -133,6 +151,8 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
element.SetAttribute("width", GetCustomWidth());
element.SetAttribute("height", GetCustomHeight());
if (HasCustomDepth()) element.SetAttribute("depth", GetCustomDepth());
// defaultWidth, defaultHeight and defaultDepth are not serialized
// because they are evaluated by InGameEditor.
if (IsLocked()) element.SetAttribute("locked", IsLocked());
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());

View File

@@ -219,6 +219,13 @@ class GD_CORE_API InitialInstance {
double GetCustomDepth() const { return depth; }
void SetCustomDepth(double depth_) { depth = depth_; }
double GetDefaultWidth() const { return defaultWidth; }
double GetDefaultHeight() const { return defaultHeight; }
double GetDefaultDepth() const { return defaultDepth; }
void SetDefaultWidth(double width_) { defaultWidth = width_; }
void SetDefaultHeight(double height_) { defaultHeight = height_; }
void SetDefaultDepth(double depth_) { defaultDepth = depth_; }
/**
* \brief Return true if the instance is locked and cannot be moved in the
* IDE.
@@ -366,7 +373,11 @@ class GD_CORE_API InitialInstance {
*/
InitialInstance& ResetPersistentUuid();
const gd::String& GetPersistentUuid() const { return persistentUuid; }
/**
* \brief Reset the persistent UUID used to recognize
* the same initial instance between serialization.
*/
const gd::String& GetPersistentUuid() const { return persistentUuid; }
///@}
private:
@@ -395,6 +406,9 @@ class GD_CORE_API InitialInstance {
double width; ///< Instance custom width
double height; ///< Instance custom height
double depth; ///< Instance custom depth
double defaultWidth = 0; ///< Instance default width as reported by InGameEditor
double defaultHeight = 0; ///< Instance default height as reported by InGameEditor
double defaultDepth = 0; ///< Instance default depth as reported by InGameEditor
gd::VariablesContainer initialVariables; ///< Instance specific variables
bool locked; ///< True if the instance is locked
bool sealed; ///< True if the instance is sealed

View File

@@ -23,6 +23,7 @@ Layer::Layer()
camera3DNearPlaneDistance(3),
camera3DFarPlaneDistance(10000),
camera3DFieldOfView(45),
camera2DPlaneMaxDrawingDistance(5000),
ambientLightColorR(200),
ambientLightColorG(200),
ambientLightColorB(200) {}
@@ -56,6 +57,8 @@ void Layer::SerializeTo(SerializerElement& element) const {
element.SetAttribute("camera3DFarPlaneDistance",
GetCamera3DFarPlaneDistance());
element.SetAttribute("camera3DFieldOfView", GetCamera3DFieldOfView());
element.SetAttribute("camera2DPlaneMaxDrawingDistance",
GetCamera2DPlaneMaxDrawingDistance());
SerializerElement& camerasElement = element.AddChild("cameras");
camerasElement.ConsiderAsArrayOf("camera");
@@ -99,6 +102,8 @@ void Layer::UnserializeFrom(const SerializerElement& element) {
"camera3DFarPlaneDistance", 10000, "threeDFarPlaneDistance"));
SetCamera3DFieldOfView(element.GetDoubleAttribute(
"camera3DFieldOfView", 45, "threeDFieldOfView"));
SetCamera2DPlaneMaxDrawingDistance(element.GetDoubleAttribute(
"camera2DPlaneMaxDrawingDistance", 5000));
cameras.clear();
SerializerElement& camerasElement = element.GetChild("cameras");

View File

@@ -182,6 +182,8 @@ class GD_CORE_API Layer {
}
double GetCamera3DFieldOfView() const { return camera3DFieldOfView; }
void SetCamera3DFieldOfView(double angle) { camera3DFieldOfView = angle; }
double GetCamera2DPlaneMaxDrawingDistance() const { return camera2DPlaneMaxDrawingDistance; }
void SetCamera2DPlaneMaxDrawingDistance(double distance) { camera2DPlaneMaxDrawingDistance = distance; }
///@}
/** \name Cameras
@@ -292,6 +294,7 @@ class GD_CORE_API Layer {
double camera3DNearPlaneDistance; ///< 3D camera frustum near plan distance
double camera3DFarPlaneDistance; ///< 3D camera frustum far plan distance
double camera3DFieldOfView; ///< 3D camera field of view (fov) in degrees
double camera2DPlaneMaxDrawingDistance; ///< Max drawing distance of the 2D plane when in the 3D world
unsigned int ambientLightColorR; ///< Ambient light color Red component
unsigned int ambientLightColorG; ///< Ambient light color Green component
unsigned int ambientLightColorB; ///< Ambient light color Blue component

View File

@@ -730,6 +730,8 @@ void Project::UnserializeFrom(const SerializerElement& element) {
SetPackageName(propElement.GetStringAttribute("packageName"));
SetTemplateSlug(propElement.GetStringAttribute("templateSlug"));
SetOrientation(propElement.GetStringAttribute("orientation", "default"));
SetEffectsHiddenInEditor(
propElement.GetBoolAttribute("areEffectsHiddenInEditor", false));
SetFolderProject(propElement.GetBoolAttribute("folderProject"));
SetLastCompilationDirectory(propElement
.GetChild("latestCompilationDirectory",
@@ -1109,6 +1111,10 @@ void Project::SerializeTo(SerializerElement& element) const {
propElement.SetAttribute("packageName", packageName);
propElement.SetAttribute("templateSlug", templateSlug);
propElement.SetAttribute("orientation", orientation);
if (areEffectsHiddenInEditor) {
propElement.SetBoolAttribute("areEffectsHiddenInEditor",
areEffectsHiddenInEditor);
}
platformSpecificAssets.SerializeTo(
propElement.AddChild("platformSpecificAssets"));
loadingScreen.SerializeTo(propElement.AddChild("loadingScreen"));
@@ -1150,6 +1156,8 @@ void Project::SerializeTo(SerializerElement& element) const {
// end of compatibility code
extensionProperties.SerializeTo(propElement.AddChild("extensionProperties"));
playableDevicesElement.AddChild("").SetStringValue("mobile");
SerializerElement& platformsElement = propElement.AddChild("platforms");
platformsElement.ConsiderAsArrayOf("platform");
@@ -1319,6 +1327,8 @@ void Project::Init(const gd::Project& game) {
sceneResourcesPreloading = game.sceneResourcesPreloading;
sceneResourcesUnloading = game.sceneResourcesUnloading;
areEffectsHiddenInEditor = game.areEffectsHiddenInEditor;
}
} // namespace gd

View File

@@ -506,6 +506,20 @@ class GD_CORE_API Project {
*/
void SetCurrentPlatform(const gd::String& platformName);
/**
* Check if the effects are shown.
*/
bool AreEffectsHiddenInEditor() const { return areEffectsHiddenInEditor; }
/**
* Define the project as playable on a mobile.
* \param enable True When false effects are not shown and a default light is
* used for 3D layers.
*/
void SetEffectsHiddenInEditor(bool enable = true) {
areEffectsHiddenInEditor = enable;
}
///@}
/** \name Factory method
@@ -1165,6 +1179,9 @@ class GD_CORE_API Project {
mutable unsigned int gdBuildVersion =
0; ///< The GD build version used the last
///< time the project was saved.
bool areEffectsHiddenInEditor =
false; ///< When false effects are not shown and a default light is used
///< for 3D layers.
};
} // namespace gd

View File

@@ -70,7 +70,9 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
currentValue = element.GetChild("value").GetStringValue();
type = element.GetChild("type").GetStringValue();
if (type == "Number") {
gd::String unitName = element.GetChild("unit").GetStringValue();
gd::String unitName = element.HasChild("unit")
? element.GetChild("unit").GetStringValue()
: "";
measurementUnit =
gd::MeasurementUnit::HasDefaultMeasurementUnitNamed(unitName)
? measurementUnit =

View File

@@ -159,15 +159,9 @@ namespace gdjs {
if (initialInstanceData.depth !== undefined) {
this.setDepth(initialInstanceData.depth);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
if (initialInstanceData.flippedZ) {
this.flipZ(initialInstanceData.flippedZ);
}
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
this.flipZ(!!initialInstanceData.flippedZ);
}
setX(x: float): void {
@@ -334,6 +328,18 @@ namespace gdjs {
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
}
override getOriginalWidth(): float {
return this._originalWidth;
}
override getOriginalHeight(): float {
return this._originalHeight;
}
getOriginalDepth(): float {
return this._originalDepth;
}
getWidth(): float {
return this._width;
}
@@ -380,31 +386,6 @@ namespace gdjs {
this.getRenderer().updateSize();
}
/**
* Return the width of the object for a scale of 1.
*
* It can't be 0.
*/
_getOriginalWidth(): float {
return this._originalWidth;
}
/**
* Return the height of the object for a scale of 1.
*
* It can't be 0.
*/
_getOriginalHeight(): float {
return this._originalHeight;
}
/**
* Return the object size on the Z axis (called "depth") when the scale equals 1.
*/
_getOriginalDepth(): float {
return this._originalDepth;
}
/**
* Set the width of the object for a scale of 1.
*/

View File

@@ -11,6 +11,8 @@ namespace gdjs {
this._object = runtimeObject;
this._threeObject3D = threeObject3D;
this._threeObject3D.rotation.order = 'ZYX';
//@ts-ignore
this._threeObject3D.gdjsRuntimeObject = runtimeObject;
instanceContainer
.getLayer('')

View File

@@ -115,6 +115,12 @@ namespace gdjs {
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMaxZ(): number;
/**
* Return the depth of the object before any custom size is applied.
* @return The depth of the object
*/
getOriginalDepth(): float;
}
export interface Object3DDataContent {
@@ -131,7 +137,11 @@ namespace gdjs {
export namespace Base3DHandler {
export const is3D = (
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
): object is gdjs.RuntimeObject &
gdjs.Base3DHandler &
gdjs.Resizable &
gdjs.Scalable &
gdjs.Flippable => {
//@ts-ignore We are checking if the methods are present.
return object.getZ && object.setZ;
};
@@ -243,6 +253,10 @@ namespace gdjs {
getUnrotatedAABBMaxZ(): number {
return this.object.getUnrotatedAABBMaxZ();
}
getOriginalDepth(): float {
return this.object.getOriginalDepth();
}
}
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);

View File

@@ -78,15 +78,9 @@ namespace gdjs {
if (initialInstanceData.depth !== undefined) {
this.setDepth(initialInstanceData.depth);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
if (initialInstanceData.flippedZ) {
this.flipZ(initialInstanceData.flippedZ);
}
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
this.flipZ(!!initialInstanceData.flippedZ);
}
getNetworkSyncData(
@@ -325,6 +319,10 @@ namespace gdjs {
return this._maxZ - this._minZ;
}
getOriginalDepth(): float {
return this._instanceContainer._getInitialInnerAreaDepth();
}
override _updateUntransformedHitBoxes(): void {
super._updateUntransformedHitBoxes();

View File

@@ -23,6 +23,8 @@ namespace gdjs {
this._threeGroup = new THREE.Group();
this._threeGroup.rotation.order = 'ZYX';
//@ts-ignore
this._threeGroup.gdjsRuntimeObject = object;
const layer = parent.getLayer('');
if (layer) {

View File

@@ -40,6 +40,19 @@ describe('gdjs.AnchorRuntimeBehavior', () => {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -221,9 +221,11 @@ namespace gdjs {
this.setWrappingWidth(initialInstanceData.width);
this.setWrapping(true);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
override onDestroyed(): void {

View File

@@ -29,6 +29,19 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -388,6 +388,9 @@ namespace gdjs {
return new gdjs.PromiseTask(
(async () => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
const scoreSavingState = (_scoreSavingStateByLeaderboard[
leaderboardId
] =
@@ -423,6 +426,9 @@ namespace gdjs {
) =>
new gdjs.PromiseTask(
(async () => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!playerId || !playerToken) {
@@ -747,6 +753,9 @@ namespace gdjs {
leaderboardId: string,
displayLoader: boolean
) {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
// First ensure we're not trying to display multiple times the same leaderboard (in which case
// we "de-duplicate" the request to display it).
if (leaderboardId === _requestedLeaderboardId) {

View File

@@ -35,6 +35,19 @@ describe('gdjs.LinksManager', function () {
stopSoundsOnStartup: false,
title: '',
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -1841,6 +1841,9 @@ namespace gdjs {
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (isQuickJoiningTooFast()) {
return;
}
@@ -1860,6 +1863,9 @@ namespace gdjs {
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (isQuickJoiningTooFast()) {
return;
}
@@ -1893,6 +1899,9 @@ namespace gdjs {
export const openLobbiesWindow = async (
runtimeScene: gdjs.RuntimeScene
) => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (
isLobbiesWindowOpen(runtimeScene) ||
gdjs.playerAuthentication.isAuthenticationWindowOpen()

View File

@@ -53,6 +53,8 @@ namespace gdjs {
_renderer: gdjs.PanelSpriteRuntimeObjectRenderer;
_objectData: PanelSpriteObjectData;
/**
* @param instanceContainer The container the object belongs to.
* @param panelSpriteObjectData The initial properties of the object
@@ -62,6 +64,7 @@ namespace gdjs {
panelSpriteObjectData: PanelSpriteObjectData
) {
super(instanceContainer, panelSpriteObjectData);
this._objectData = panelSpriteObjectData;
this._rBorder = panelSpriteObjectData.rightMargin;
this._lBorder = panelSpriteObjectData.leftMargin;
this._tBorder = panelSpriteObjectData.topMargin;
@@ -84,6 +87,7 @@ namespace gdjs {
oldObjectData: PanelSpriteObjectData,
newObjectData: PanelSpriteObjectData
): boolean {
this._objectData = newObjectData;
if (oldObjectData.width !== newObjectData.width) {
this.setWidth(newObjectData.width);
}
@@ -166,9 +170,11 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
/**
@@ -247,6 +253,14 @@ namespace gdjs {
this.setHeight(newHeight);
}
override getOriginalWidth(): float {
return this._objectData.width;
}
override getOriginalHeight(): float {
return this._objectData.height;
}
setOpacity(opacity: float): void {
if (opacity < 0) {
opacity = 0;

View File

@@ -18,12 +18,15 @@ namespace gdjs {
renderer: PIXI.Container;
emitter: PIXI.particles.Emitter;
started: boolean = false;
helperGraphics: PIXI.Graphics | null = null;
runtimeObject: gdjs.ParticleEmitterObject;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
runtimeObject: gdjs.RuntimeObject,
runtimeObject: gdjs.ParticleEmitterObject,
objectData: any
) {
this.runtimeObject = runtimeObject;
const pixiRenderer = instanceContainer
.getGame()
.getRenderer()
@@ -223,6 +226,44 @@ namespace gdjs {
if (!this.started && wasEmitting) {
this.started = true;
}
if (this.helperGraphics) {
this.helperGraphics.clear();
this.helperGraphics.position.x = this.runtimeObject.getX();
this.helperGraphics.position.y = this.runtimeObject.getY();
const emitterAngle = gdjs.toRad(this.runtimeObject.getAngle());
const sprayConeAngle = gdjs.toRad(
this.runtimeObject.getConeSprayAngle()
);
const line1Angle = emitterAngle - sprayConeAngle / 2;
const line2Angle = emitterAngle + sprayConeAngle / 2;
const length = 64;
this.helperGraphics.beginFill(0, 0);
this.helperGraphics.lineStyle(
3,
this.runtimeObject.getParticleColorEnd(),
1
);
this.helperGraphics.moveTo(0, 0);
this.helperGraphics.lineTo(
Math.cos(line1Angle) * length,
Math.sin(line1Angle) * length
);
this.helperGraphics.moveTo(0, 0);
this.helperGraphics.lineTo(
Math.cos(line2Angle) * length,
Math.sin(line2Angle) * length
);
this.helperGraphics.endFill();
this.helperGraphics.lineStyle(0, 0x000000, 1);
this.helperGraphics.beginFill(
this.runtimeObject.getParticleColorStart()
);
this.helperGraphics.drawCircle(0, 0, 8);
this.helperGraphics.endFill();
}
}
setPosition(x: number, y: number): void {
@@ -443,6 +484,17 @@ namespace gdjs {
}
private static readonly frequencyMinimumValue = 0.0001;
setHelperVisible(visible: boolean) {
if (visible && !this.helperGraphics) {
this.helperGraphics = new PIXI.Graphics();
this.renderer.addChild(this.helperGraphics);
} else if (!visible && this.helperGraphics) {
this.helperGraphics.removeFromParent();
this.helperGraphics.destroy();
this.helperGraphics = null;
}
}
}
// @ts-ignore - Register the class to let the engine use it.

View File

@@ -174,6 +174,10 @@ namespace gdjs {
this,
particleObjectData
);
if (instanceContainer.getGame().isInGameEdition()) {
// TODO Disable the particles rendering
this._renderer.setHelperVisible(true);
}
this.angleA = particleObjectData.emitterAngleA;
this.angleB = particleObjectData.emitterAngleB;
this.forceMin = particleObjectData.emitterForceMin;
@@ -802,6 +806,14 @@ namespace gdjs {
}
}
getParticleColorStart(): number {
return this.color1;
}
getParticleColorEnd(): number {
return this.color2;
}
getParticleRed1(): number {
return gdjs.hexNumberToRGBArray(this.color1)[0];
}

View File

@@ -36,6 +36,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -39,6 +39,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -41,6 +41,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -623,10 +623,46 @@ Zv();a.b2Manifold.e_faceA=$v();a.b2Manifold.e_faceB=aw();a.b2_staticBody=bw();a.
})();
gdjs.registerAsynchronouslyLoadingLibraryPromise(initializeBox2D({locateFile: function(path, prefix) {
return location.protocol === 'file:' ?
// This is needed to run on Electron.
prefix + "Extensions/Physics2Behavior/" + path :
prefix + path;
// Path should always be "Box2D_v2.3.1_min.wasm.wasm" (and if it's not, we should probably hardcode it).
if (path !== 'Box2D_v2.3.1_min.wasm.wasm') {
console.warn("'path' argument sent to locateFile in Box2D_v2.3.1_min.wasm.js is not the expected string 'Box2D_v2.3.1_min.wasm.wasm'. Loading may fail.")
}
// Prefix is typically:
// Games ("exported", standalone game):
// - Web game: "https://games.gdevelop-app.com/[...]/Extensions/Physics2Behavior/"
// - Cordova Android: "https://localhost/Extensions/Physics2Behavior/".
// - Cordova iOS: "ionic://localhost/Extensions/Physics2Behavior/".
// - Electron macOS: "/private/var/[...]/Contents/Resources/app.asar/app/" (notice the missing folder).
// - Electron Windows: "C:\Users\[...]\AppData\Local\[...]\resources\app.asar\app/" (notice the missing folder).
// Preview (in the editor):
// - Web app preview (dev editor): "http://localhost:5002/Runtime/Extensions/Physics2Behavior/"
// - Web app preview (production editor): "https://resources.gdevelop-app.com/[...]/Runtime/Extensions/Physics2Behavior/"
// - Electron app preview (dev editor): "/var/[...]/preview/" (notice the missing folder).
// - Electron app preview (production editor): "/var/[...]/preview/" (notice the missing folder).
// In-game editor:
// - Web app (dev editor): "http://localhost:5002/Runtime/Extensions/Physics2Behavior/"
// - Web app (production editor): "https://resources.gdevelop-app.com/[...]/Runtime/Extensions/Physics2Behavior/"
// - Electron app (dev editor): "file:///var/[...]/in-game-editor-preview/Extensions/Physics2Behavior/"
// - Electron app (production editor): "file:///var/[...]/in-game-editor-preview/Extensions/Physics2Behavior/"
// If the prefix is a full URL, it's a full URL to the folder containing this JS file.
// Sill consider the case where the folder could have been missing.
let url;
if (prefix.startsWith('http:') || prefix.startsWith('https:')) {
url = prefix.endsWith('Extensions/Physics2Behavior/') ?
prefix + path :
prefix + 'Extensions/Physics2Behavior/' + path;
} else {
// Electron or Cordova iOS will fall in this case.
// We can't use this simple solution for http/https because
// on the web-app, the runtime is not necessarily hosted
// on the same domain as where the game generated files are served (so "prefix" is needed).
url = "Extensions/Physics2Behavior/" + path;
}
console.info(`Box2D wasm file is being loaded from path "${path}" with prefix "${prefix}". Resolved URL: "${url}".`);
return url;
}}).then(box2d => {
window.Box2D = box2d;
}));

View File

@@ -647,6 +647,9 @@ namespace gdjs {
export const displayAuthenticationBanner = function (
runtimeScene: gdjs.RuntimeScene
) {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (_authenticationBanner) {
// Banner already displayed, ensure it's visible.
_authenticationBanner.style.opacity = '1';
@@ -1042,6 +1045,10 @@ namespace gdjs {
): gdjs.PromiseTask<{ status: 'logged' | 'errored' | 'dismissed' }> =>
new gdjs.PromiseTask(
new Promise((resolve) => {
if (runtimeScene.getGame().isInGameEdition()) {
resolve({ status: 'dismissed' });
}
// Create the authentication container for the player to wait.
const domElementContainer = runtimeScene
.getGame()

View File

@@ -20,6 +20,8 @@ namespace gdjs {
_antialiasingFilter: null | PIXI.Filter = null;
_placeholder: PIXI.Sprite | null = null;
private static readonly _positionForTransformation: PIXI.IPointData = {
x: 0,
y: 0,
@@ -405,6 +407,25 @@ namespace gdjs {
updatePreRender(): void {
this.updatePositionIfNeeded();
const game = this._object.getRuntimeScene().getGame();
if (
game.isInGameEdition() &&
this._graphics.geometry.graphicsData.length === 0
) {
if (!this._placeholder) {
console.log(game.getGameData().resources.resources);
const texture = game
.getImageManager()
.getPIXITexture('InGameEditor-ShapePainterIcon');
this._placeholder = new PIXI.Sprite(texture);
}
this._graphics.addChild(this._placeholder);
} else if (this._placeholder) {
this._placeholder.removeFromParent();
this._placeholder.destroy();
this._placeholder = null;
}
}
updatePositionX(): void {

View File

@@ -278,12 +278,8 @@ namespace gdjs {
* @param initialInstanceData The extra parameters
*/
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
}
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {

View File

@@ -50,6 +50,19 @@ describe('gdjs.ShapePainterRuntimeObject (using a PixiJS RuntimeGame with assets
instances: [],
variables: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -203,13 +203,17 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
const loadedSpineAtlas = this._loadedSpineAtlases.getFromName(
resourceData.name
);
if (loadedSpineAtlas) {
loadedSpineAtlas.dispose();
this._loadedSpineAtlases.delete(resourceData);
}
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
const loadingSpineAtlas = this._loadingSpineAtlases.getFromName(
resourceData.name
);
if (loadingSpineAtlas) {
loadingSpineAtlas.then((atl) => atl.dispose());
this._loadingSpineAtlases.delete(resourceData);

View File

@@ -218,15 +218,13 @@ namespace gdjs {
this.setSize(initialInstanceData.width, initialInstanceData.height);
this.invalidateHitboxes();
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
}
getDrawableX(): number {

View File

@@ -61,6 +61,19 @@ describe('gdjs.TextInputRuntimeObject (using a PixiJS RuntimeGame with DOM eleme
instances: [],
variables: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -141,7 +141,9 @@ namespace gdjs {
);
this._borderOpacity = objectData.content.borderOpacity;
this._borderWidth = objectData.content.borderWidth;
this._disabled = objectData.content.disabled;
this._disabled = instanceContainer.getGame().isInGameEdition()
? true
: objectData.content.disabled;
this._readOnly = objectData.content.readOnly;
this._spellCheck =
objectData.content.spellCheck !== undefined
@@ -334,9 +336,11 @@ namespace gdjs {
this.setHeight(initialInstanceData.height);
this._renderer.updatePadding();
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
onScenePaused(runtimeScene: gdjs.RuntimeScene): void {
@@ -566,6 +570,9 @@ namespace gdjs {
}
setDisabled(value: boolean) {
if (this.getInstanceContainer().getGame().isInGameEdition()) {
return;
}
this._disabled = value;
this._renderer.updateDisabled();
}

View File

@@ -340,7 +340,9 @@ namespace gdjs {
return this._renderer.getRendererObject();
}
override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
override updatePreRender(
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
this._renderer.ensureUpToDate();
}
@@ -358,9 +360,11 @@ namespace gdjs {
} else {
this.setWrapping(false);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
/**

View File

@@ -236,9 +236,11 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
// 4. Update position (calculations based on renderer's dimensions).
this._renderer.updatePosition();
@@ -449,6 +451,14 @@ namespace gdjs {
return this._renderer.getHeight();
}
override getOriginalWidth(): float {
return this.getTileMapWidth();
}
override getOriginalHeight(): float {
return this.getTileMapHeight();
}
getScaleX(): float {
return this._renderer.getScaleX();
}

View File

@@ -57,6 +57,19 @@ describe('gdjs.TileMapCollisionMaskRuntimeObject', function () {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -197,9 +197,11 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
private _updateTileMap(): void {
@@ -343,6 +345,14 @@ namespace gdjs {
this.setHeight(newHeight);
}
override getOriginalWidth(): float {
return this.getTileMapWidth();
}
override getOriginalHeight(): float {
return this.getTileMapHeight();
}
/**
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
*

View File

@@ -42,6 +42,8 @@ namespace gdjs {
_renderer: gdjs.TiledSpriteRuntimeObjectRenderer;
_objectData: TiledSpriteObjectData;
/**
* @param instanceContainer The container the object belongs to.
* @param tiledSpriteObjectData The initial properties of the object
@@ -51,6 +53,7 @@ namespace gdjs {
tiledSpriteObjectData: TiledSpriteObjectData
) {
super(instanceContainer, tiledSpriteObjectData);
this._objectData = tiledSpriteObjectData;
this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer(
this,
instanceContainer,
@@ -66,6 +69,7 @@ namespace gdjs {
}
updateFromObjectData(oldObjectData, newObjectData): boolean {
this._objectData = newObjectData;
if (oldObjectData.texture !== newObjectData.texture) {
this.setTexture(newObjectData.texture, this.getRuntimeScene());
}
@@ -129,9 +133,11 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
/**
@@ -223,6 +229,14 @@ namespace gdjs {
this.setHeight(height);
}
override getOriginalWidth(): float {
return this._objectData.width;
}
override getOriginalHeight(): float {
return this._objectData.height;
}
/**
* Set the offset on the X-axis when displaying the image of the Tiled Sprite object.
* @param xOffset The new offset on the X-axis.

View File

@@ -34,6 +34,19 @@ describe('gdjs.TopDownMovementRuntimeBehavior', function () {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
},
usedExtensionsWithVariablesData: [],
});

View File

@@ -151,9 +151,11 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
}
onDestroyed(): void {

View File

@@ -18,6 +18,9 @@
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
#include "GDCore/IDE/ProjectStripper.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
@@ -47,7 +50,7 @@ Exporter::~Exporter() {}
bool Exporter::ExportProjectForPixiPreview(
const PreviewExportOptions &options) {
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
return helper.ExportProjectForPixiPreview(options);
return helper.ExportProjectForPixiPreview(options, includesFiles);
}
bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
@@ -80,7 +83,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
// Prepare the export directory
fs.MkDir(exportDir);
std::vector<gd::String> includesFiles;
includesFiles.clear();
std::vector<gd::String> resourcesFiles;
// Export the resources (before generating events as some resources
@@ -98,6 +101,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
helper.AddLibsInclude(
/*pixiRenderers=*/true,
usedExtensionsResult.Has3DObjects(),
/*isInGameEditor=*/false,
/*includeWebsocketDebuggerClient=*/false,
/*includeWindowMessageDebuggerClient=*/false,
/*includeMinimalDebuggerClient=*/false,
@@ -120,7 +124,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
helper.ExportEffectIncludes(exportedProject, includesFiles);
// Export events
if (!helper.ExportEventsCode(exportedProject,
if (!helper.ExportScenesEventsCode(exportedProject,
codeOutputDir,
includesFiles,
wholeProjectDiagnosticReport,
@@ -130,29 +134,11 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
return false;
}
auto projectUsedResources =
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
for (std::size_t layoutIndex = 0;
layoutIndex < exportedProject.GetLayoutsCount();
layoutIndex++) {
auto &layout = exportedProject.GetLayout(layoutIndex);
scenesUsedResources[layout.GetName()] =
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
}
// Strip the project (*after* generating events as the events may use
// stripped things like objects groups...)...
gd::ProjectStripper::StripProjectForExport(exportedProject);
//...and export it
gd::SerializerElement noRuntimeGameOptions;
helper.ExportProjectData(fs,
exportedProject,
codeOutputDir + "/data.js",
helper.ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
noRuntimeGameOptions,
projectUsedResources,
scenesUsedResources);
/*isInGameEdition=*/false);
includesFiles.push_back(codeOutputDir + "/data.js");
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
@@ -215,4 +201,17 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
return true;
}
void Exporter::SerializeProjectData(const gd::Project &project,
const PreviewExportOptions &options,
gd::SerializerElement &projectDataElement) {
ExporterHelper::SerializeProjectData(fs, project, options, projectDataElement);
}
void Exporter::SerializeRuntimeGameOptions(
const PreviewExportOptions &options,
gd::SerializerElement &runtimeGameOptionsElement) {
ExporterHelper::SerializeRuntimeGameOptions(
fs, gdjsRoot, options, includesFiles, runtimeGameOptionsElement);
}
} // namespace gdjs

View File

@@ -16,6 +16,7 @@ class Project;
class Layout;
class ExternalLayout;
class AbstractFileSystem;
class SerializerElement;
} // namespace gd
namespace gdjs {
struct PreviewExportOptions;
@@ -64,7 +65,33 @@ class Exporter {
codeOutputDir = codeOutputDir_;
}
private:
/**
* \brief Serialize a project without its events to JSON
*
* \param project The project to be exported
* \param options The content of the extra configuration
* \param projectDataElement The element where the project data is serialized
*/
void SerializeProjectData(const gd::Project &project,
const PreviewExportOptions &options,
gd::SerializerElement &projectDataElement);
/**
* \brief Serialize the content of the extra configuration to store
* in gdjs.runtimeGameOptions to JSON
*
* \warning `ExportProjectForPixiPreview` must be called first to serialize
* the list of scripts files.
*
* \param options The content of the extra configuration
* \param runtimeGameOptionsElement The element where the game options are
* serialized
*/
void
SerializeRuntimeGameOptions(const PreviewExportOptions &options,
gd::SerializerElement &runtimeGameOptionsElement);
private:
gd::AbstractFileSystem&
fs; ///< The abstract file system to be used for exportation.
gd::String lastError; ///< The last error that occurred.
@@ -72,6 +99,8 @@ class Exporter {
gdjsRoot; ///< The root directory of GDJS, used to copy runtime files.
gd::String codeOutputDir; ///< The directory where JS code is outputted. Will
///< be then copied to the final output directory.
std::vector<gd::String>
includesFiles; ///< The list of scripts files - useful for hot-reloading
};
} // namespace gdjs

View File

@@ -28,10 +28,13 @@
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
#include "GDCore/IDE/ExportedDependencyResolver.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
#include "GDCore/IDE/ProjectStripper.h"
#include "GDCore/IDE/ResourceExposer.h"
#include "GDCore/IDE/SceneNameMangler.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/EventsBasedObjectVariant.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
@@ -58,7 +61,6 @@ double GetTimeSpent(double previousTime) { return GetTimeNow() - previousTime; }
double LogTimeSpent(const gd::String &name, double previousTime) {
gd::LogStatus(name + " took " + gd::String::From(GetTimeSpent(previousTime)) +
"ms");
std::cout << std::endl;
return GetTimeNow();
}
} // namespace
@@ -104,128 +106,298 @@ ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
: fs(fileSystem), gdjsRoot(gdjsRoot_), codeOutputDir(codeOutputDir_) {};
bool ExporterHelper::ExportProjectForPixiPreview(
const PreviewExportOptions &options) {
const PreviewExportOptions &options,
std::vector<gd::String> &includesFiles) {
if (options.isInGameEdition && !options.shouldReloadProjectData &&
!options.shouldReloadLibraries && !options.shouldGenerateScenesEventsCode) {
gd::LogStatus("Skip project export entirely");
return "";
}
double previousTime = GetTimeNow();
fs.MkDir(options.exportPath);
fs.ClearDir(options.exportPath);
std::vector<gd::String> includesFiles;
if (options.shouldClearExportFolder) {
fs.ClearDir(options.exportPath);
}
includesFiles.clear();
std::vector<gd::String> resourcesFiles;
// TODO Try to remove side effects to avoid the copy
// that destroys the AST in cache.
gd::Project exportedProject = options.project;
const gd::Project &immutableProject = exportedProject;
const gd::Project &immutableProject = options.project;
previousTime = LogTimeSpent("Project cloning", previousTime);
if (options.fullLoadingScreen) {
// Use project properties fallback to set empty properties
if (exportedProject.GetAuthorIds().empty() &&
!options.fallbackAuthorId.empty()) {
exportedProject.GetAuthorIds().push_back(options.fallbackAuthorId);
}
if (exportedProject.GetAuthorUsernames().empty() &&
!options.fallbackAuthorUsername.empty()) {
exportedProject.GetAuthorUsernames().push_back(
options.fallbackAuthorUsername);
if (options.isInGameEdition) {
if (options.shouldReloadProjectData || options.shouldGenerateScenesEventsCode) {
auto projectDirectory = fs.DirNameFrom(exportedProject.GetProjectFile());
gd::ResourcesMergingHelper resourcesMergingHelper(
exportedProject.GetResourcesManager(), fs);
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
resourcesMergingHelper.SetShouldUseOriginalAbsoluteFilenames();
gd::ResourceExposer::ExposeWholeProjectResources(exportedProject,
resourcesMergingHelper);
previousTime = LogTimeSpent("Resource path resolving", previousTime);
}
gd::LogStatus("Resource export is skipped");
} else {
// Most of the time, we skip the logo and minimum duration so that
// the preview start as soon as possible.
exportedProject.GetLoadingScreen()
.ShowGDevelopLogoDuringLoadingScreen(false)
.SetMinDuration(0);
exportedProject.GetWatermark().ShowGDevelopWatermark(false);
// Export resources (*before* generating events as some resources filenames
// may be updated)
ExportResources(fs, exportedProject, options.exportPath);
previousTime = LogTimeSpent("Resource export", previousTime);
}
// Export resources (*before* generating events as some resources filenames
// may be updated)
ExportResources(fs, exportedProject, options.exportPath);
previousTime = LogTimeSpent("Resource export", previousTime);
// Compatibility with GD <= 5.0-beta56
// Stay compatible with text objects declaring their font as just a filename
// without a font resource - by manually adding these resources.
AddDeprecatedFontFilesToFontResources(
fs, exportedProject.GetResourcesManager(), options.exportPath);
// end of compatibility code
auto usedExtensionsResult =
gd::UsedExtensionsFinder::ScanProject(exportedProject);
// Export engine libraries
AddLibsInclude(/*pixiRenderers=*/true,
usedExtensionsResult.Has3DObjects(),
/*includeWebsocketDebuggerClient=*/
!options.websocketDebuggerServerAddress.empty(),
/*includeWindowMessageDebuggerClient=*/
options.useWindowMessageDebuggerClient,
/*includeMinimalDebuggerClient=*/
options.useMinimalDebuggerClient,
/*includeCaptureManager=*/
!options.captureOptions.IsEmpty(),
/*includeInAppTutorialMessage*/
!options.inAppTutorialMessageInPreview.empty(),
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
// Export files for free function, object and behaviors
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
InsertUnique(includesFiles, includeFile);
}
for (const auto &requiredFile : usedExtensionsResult.GetUsedRequiredFiles()) {
InsertUnique(resourcesFiles, requiredFile);
if (options.shouldReloadProjectData || options.shouldGenerateScenesEventsCode) {
// Compatibility with GD <= 5.0-beta56
// Stay compatible with text objects declaring their font as just a filename
// without a font resource - by manually adding these resources.
AddDeprecatedFontFilesToFontResources(
fs, exportedProject.GetResourcesManager(), options.exportPath);
// end of compatibility code
}
// Export effects (after engine libraries as they auto-register themselves to
// the engine)
ExportEffectIncludes(exportedProject, includesFiles);
std::vector<gd::SourceFileMetadata> noUsedSourceFiles;
std::vector<gd::SourceFileMetadata> &usedSourceFiles = noUsedSourceFiles;
if (options.shouldReloadLibraries) {
auto usedExtensionsResult =
gd::UsedExtensionsFinder::ScanProject(exportedProject);
usedSourceFiles = usedExtensionsResult.GetUsedSourceFiles();
previousTime = LogTimeSpent("Include files export", previousTime);
// Export engine libraries
AddLibsInclude(/*pixiRenderers=*/true,
/*pixiInThreeRenderers=*/
usedExtensionsResult.Has3DObjects(),
/*isInGameEdition=*/
options.isInGameEdition,
/*includeWebsocketDebuggerClient=*/
!options.websocketDebuggerServerAddress.empty(),
/*includeWindowMessageDebuggerClient=*/
options.useWindowMessageDebuggerClient,
/*includeMinimalDebuggerClient=*/
options.useMinimalDebuggerClient,
/*includeCaptureManager=*/
!options.captureOptions.IsEmpty(),
/*includeInAppTutorialMessage*/
!options.inAppTutorialMessageInPreview.empty(),
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
if (!options.projectDataOnlyExport) {
// Export files for free function, object and behaviors
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
InsertUnique(includesFiles, includeFile);
}
for (const auto &requiredFile : usedExtensionsResult.GetUsedRequiredFiles()) {
InsertUnique(resourcesFiles, requiredFile);
}
if (options.isInGameEdition) {
// TODO Scan the objects and events of event-based objects
// (it could be an alternative method ScanProjectAndEventsBasedObjects in
// UsedExtensionsFinder).
// This is already done by UsedExtensionsFinder, but maybe it shouldn't.
// Export all event-based objects because they can be edited even if they
// are not used yet.
for (std::size_t e = 0;
e < exportedProject.GetEventsFunctionsExtensionsCount(); e++) {
auto &eventsFunctionsExtension =
exportedProject.GetEventsFunctionsExtension(e);
for (auto &&eventsBasedObjectUniquePtr :
eventsFunctionsExtension.GetEventsBasedObjects()
.GetInternalVector()) {
auto eventsBasedObject = eventsBasedObjectUniquePtr.get();
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
exportedProject.GetCurrentPlatform(),
gd::PlatformExtension::GetObjectFullType(
eventsFunctionsExtension.GetName(),
eventsBasedObject->GetName()));
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
InsertUnique(includesFiles, includeFile);
}
for (auto &behaviorType :
metadata.GetMetadata().GetDefaultBehaviors()) {
auto behaviorMetadata =
gd::MetadataProvider::GetExtensionAndBehaviorMetadata(
exportedProject.GetCurrentPlatform(), behaviorType);
for (auto &&includeFile :
behaviorMetadata.GetMetadata().includeFiles) {
InsertUnique(includesFiles, includeFile);
}
}
}
}
}
// Export effects (after engine libraries as they auto-register themselves to
// the engine)
ExportEffectIncludes(exportedProject, includesFiles);
previousTime = LogTimeSpent("Include files export", previousTime);
}
else {
gd::LogStatus("Include files export is skipped");
}
if (options.shouldGenerateScenesEventsCode) {
gd::WholeProjectDiagnosticReport &wholeProjectDiagnosticReport =
options.project.GetWholeProjectDiagnosticReport();
wholeProjectDiagnosticReport.Clear();
// Generate events code
if (!ExportEventsCode(immutableProject,
if (!ExportScenesEventsCode(immutableProject,
codeOutputDir,
includesFiles,
wholeProjectDiagnosticReport,
true)) {
return false;
}
previousTime = LogTimeSpent("Events code export", previousTime);
}
auto projectUsedResources =
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
for (std::size_t layoutIndex = 0;
layoutIndex < exportedProject.GetLayoutsCount();
layoutIndex++) {
auto &layout = exportedProject.GetLayout(layoutIndex);
scenesUsedResources[layout.GetName()] =
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
else {
gd::LogStatus("Events code export is skipped");
}
// Strip the project (*after* generating events as the events may use stripped
// things (objects groups...))
gd::ProjectStripper::StripProjectForExport(exportedProject);
exportedProject.SetFirstLayout(options.layoutName);
if (options.shouldReloadProjectData) {
if (options.fullLoadingScreen) {
// Use project properties fallback to set empty properties
if (exportedProject.GetAuthorIds().empty() &&
!options.fallbackAuthorId.empty()) {
exportedProject.GetAuthorIds().push_back(options.fallbackAuthorId);
}
if (exportedProject.GetAuthorUsernames().empty() &&
!options.fallbackAuthorUsername.empty()) {
exportedProject.GetAuthorUsernames().push_back(
options.fallbackAuthorUsername);
}
} else {
// Most of the time, we skip the logo and minimum duration so that
// the preview start as soon as possible.
exportedProject.GetLoadingScreen()
.ShowGDevelopLogoDuringLoadingScreen(false)
.SetMinDuration(0);
exportedProject.GetWatermark().ShowGDevelopWatermark(false);
}
previousTime = LogTimeSpent("Data stripping", previousTime);
gd::SerializerElement runtimeGameOptions;
ExporterHelper::SerializeRuntimeGameOptions(fs, gdjsRoot, options,
includesFiles, runtimeGameOptions);
ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
runtimeGameOptions, options.isInGameEdition);
includesFiles.push_back(codeOutputDir + "/data.js");
previousTime = LogTimeSpent("Project data export", previousTime);
}
else {
gd::LogStatus("Project data export is skipped");
}
if (options.shouldReloadLibraries) {
if (options.isInGameEdition) {
InsertUnique(resourcesFiles, "InGameEditor/Resources/primitivedrawingicon.png");
}
// Copy all the dependencies and their source maps
ExportIncludesAndLibs(includesFiles, options.exportPath, true);
ExportIncludesAndLibs(resourcesFiles, options.exportPath, true);
// TODO Build a full includesFiles list without actually doing export or
// generation.
if (options.shouldGenerateScenesEventsCode) {
// Create the index file
if (!ExportIndexFile(exportedProject, gdjsRoot + "/Runtime/index.html",
options.exportPath, includesFiles, usedSourceFiles,
options.nonRuntimeScriptsCacheBurst,
"gdjs.runtimeGameOptions")) {
return false;
}
}
previousTime = LogTimeSpent("Include and libs export", previousTime);
} else {
gd::LogStatus("Include and libs export is skipped");
}
return true;
}
gd::String ExporterHelper::ExportProjectData(
gd::AbstractFileSystem &fs, gd::Project &project, gd::String filename,
const gd::SerializerElement &runtimeGameOptions, bool isInGameEdition) {
fs.MkDir(fs.DirNameFrom(filename));
gd::SerializerElement projectDataElement;
ExporterHelper::StriptAndSerializeProjectData(
project, projectDataElement, isInGameEdition);
// Save the project to JSON
gd::String output =
"gdjs.projectData = " + gd::Serializer::ToJSON(projectDataElement) +
";\ngdjs.runtimeGameOptions = " + gd::Serializer::ToJSON(runtimeGameOptions) +
";\n";
if (!fs.WriteToFile(filename, output))
return "Unable to write " + filename;
return "";
}
void ExporterHelper::SerializeRuntimeGameOptions(
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
const PreviewExportOptions &options, std::vector<gd::String> &includesFiles,
gd::SerializerElement &runtimeGameOptions) {
// Create the setup options passed to the gdjs.RuntimeGame
gd::SerializerElement runtimeGameOptions;
runtimeGameOptions.AddChild("isPreview").SetBoolValue(true);
if (!options.externalLayoutName.empty()) {
runtimeGameOptions.AddChild("injectExternalLayout")
.SetValue(options.externalLayoutName);
auto &initialRuntimeGameStatus =
runtimeGameOptions.AddChild("initialRuntimeGameStatus");
initialRuntimeGameStatus.AddChild("sceneName")
.SetStringValue(options.layoutName);
if (options.isInGameEdition) {
initialRuntimeGameStatus.AddChild("isInGameEdition").SetBoolValue(true);
initialRuntimeGameStatus.AddChild("editorId").SetValue(options.editorId);
if (!options.editorCamera3DCameraMode.empty()) {
auto &editorCamera3D =
initialRuntimeGameStatus.AddChild("editorCamera3D");
editorCamera3D.AddChild("cameraMode").SetStringValue(
options.editorCamera3DCameraMode);
editorCamera3D.AddChild("positionX")
.SetDoubleValue(options.editorCamera3DPositionX);
editorCamera3D.AddChild("positionY")
.SetDoubleValue(options.editorCamera3DPositionY);
editorCamera3D.AddChild("positionZ")
.SetDoubleValue(options.editorCamera3DPositionZ);
editorCamera3D.AddChild("rotationAngle")
.SetDoubleValue(options.editorCamera3DRotationAngle);
editorCamera3D.AddChild("elevationAngle")
.SetDoubleValue(options.editorCamera3DElevationAngle);
editorCamera3D.AddChild("distance")
.SetDoubleValue(options.editorCamera3DDistance);
}
}
runtimeGameOptions.AddChild("projectDataOnlyExport")
.SetBoolValue(options.projectDataOnlyExport);
if (!options.externalLayoutName.empty()) {
initialRuntimeGameStatus.AddChild("injectedExternalLayoutName")
.SetValue(options.externalLayoutName);
if (options.isInGameEdition) {
initialRuntimeGameStatus.AddChild("skipCreatingInstancesFromScene")
.SetBoolValue(true);
}
}
if (!options.eventsBasedObjectType.empty()) {
initialRuntimeGameStatus.AddChild("eventsBasedObjectType")
.SetValue(options.eventsBasedObjectType);
initialRuntimeGameStatus.AddChild("eventsBasedObjectVariantName")
.SetValue(options.eventsBasedObjectVariantName);
}
runtimeGameOptions.AddChild("shouldReloadLibraries")
.SetBoolValue(options.shouldReloadLibraries);
runtimeGameOptions.AddChild("shouldGenerateScenesEventsCode")
.SetBoolValue(options.shouldGenerateScenesEventsCode);
runtimeGameOptions.AddChild("nativeMobileApp")
.SetBoolValue(options.nativeMobileApp);
runtimeGameOptions.AddChild("websocketDebuggerServerAddress")
@@ -297,71 +469,106 @@ bool ExporterHelper::ExportProjectForPixiPreview(
for (const auto &includeFile : includesFiles) {
auto hashIt = options.includeFileHashes.find(includeFile);
gd::String scriptSrc = GetExportedIncludeFilename(includeFile);
gd::String scriptSrc = GetExportedIncludeFilename(fs, gdjsRoot, includeFile);
scriptFilesElement.AddChild("scriptFile")
.SetStringAttribute("path", scriptSrc)
.SetIntAttribute(
"hash",
hashIt != options.includeFileHashes.end() ? hashIt->second : 0);
}
// Export the project
ExportProjectData(fs,
exportedProject,
codeOutputDir + "/data.js",
runtimeGameOptions,
projectUsedResources,
scenesUsedResources);
includesFiles.push_back(codeOutputDir + "/data.js");
previousTime = LogTimeSpent("Project data export", previousTime);
// Copy all the dependencies and their source maps
ExportIncludesAndLibs(includesFiles, options.exportPath, true);
ExportIncludesAndLibs(resourcesFiles, options.exportPath, true);
// Create the index file
if (!ExportIndexFile(exportedProject,
gdjsRoot + "/Runtime/index.html",
options.exportPath,
includesFiles,
usedExtensionsResult.GetUsedSourceFiles(),
options.nonRuntimeScriptsCacheBurst,
"gdjs.runtimeGameOptions"))
return false;
previousTime = LogTimeSpent("Include and libs export", previousTime);
return true;
}
gd::String ExporterHelper::ExportProjectData(
gd::AbstractFileSystem &fs,
gd::Project &project,
gd::String filename,
const gd::SerializerElement &runtimeGameOptions,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
fs.MkDir(fs.DirNameFrom(filename));
void ExporterHelper::AddInGameEditorResources(
gd::Project &project, std::set<gd::String> &projectUsedResources) {
project.GetResourcesManager().AddResource(
"InGameEditor-ShapePainterIcon",
"InGameEditor/Resources/primitivedrawingicon.png", "image");
projectUsedResources.insert("InGameEditor-ShapePainterIcon");
}
void ExporterHelper::SerializeProjectData(gd::AbstractFileSystem &fs,
const gd::Project &project,
const PreviewExportOptions &options,
gd::SerializerElement &rootElement) {
gd::Project clonedProject = project;
// Replace all resource file paths with the one used in exported projects.
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
gd::ResourcesMergingHelper resourcesMergingHelper(
clonedProject.GetResourcesManager(), fs);
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
if (options.isInGameEdition) {
resourcesMergingHelper.SetShouldUseOriginalAbsoluteFilenames();
} else {
resourcesMergingHelper.PreserveDirectoriesStructure(false);
resourcesMergingHelper.PreserveAbsoluteFilenames(false);
}
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
resourcesMergingHelper);
ExporterHelper::StriptAndSerializeProjectData(
clonedProject, rootElement, options.isInGameEdition);
}
void ExporterHelper::StriptAndSerializeProjectData(
gd::Project &project, gd::SerializerElement &rootElement,
bool isInGameEdition) {
auto projectUsedResources =
gd::SceneResourcesFinder::FindProjectResources(project);
if (isInGameEdition) {
ExporterHelper::AddInGameEditorResources(project, projectUsedResources);
}
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
for (std::size_t layoutIndex = 0;
layoutIndex < project.GetLayoutsCount(); layoutIndex++) {
auto &layout = project.GetLayout(layoutIndex);
scenesUsedResources[layout.GetName()] =
gd::SceneResourcesFinder::FindSceneResources(project, layout);
}
std::unordered_map<gd::String, std::set<gd::String>>
eventsBasedObjectVariantsUsedResources;
for (std::size_t extensionIndex = 0;
extensionIndex < project.GetEventsFunctionsExtensionsCount();
extensionIndex++) {
auto &eventsFunctionsExtension =
project.GetEventsFunctionsExtension(extensionIndex);
for (auto &&eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
auto eventsBasedObjectType = gd::PlatformExtension::GetObjectFullType(
eventsFunctionsExtension.GetName(), eventsBasedObject->GetName());
eventsBasedObjectVariantsUsedResources[eventsBasedObjectType] =
gd::SceneResourcesFinder::FindEventsBasedObjectVariantResources(
project, eventsBasedObject->GetDefaultVariant());
for (auto &&eventsBasedObjectVariant :
eventsBasedObject->GetVariants().GetInternalVector()) {
auto variantType = gd::PlatformExtension::GetVariantFullType(
eventsFunctionsExtension.GetName(), eventsBasedObject->GetName(),
eventsBasedObjectVariant->GetName());
eventsBasedObjectVariantsUsedResources[variantType] =
gd::SceneResourcesFinder::FindEventsBasedObjectVariantResources(
project, *eventsBasedObjectVariant);
}
}
}
// Strip the project (*after* generating events as the events may use stripped
// things (objects groups...))
gd::ProjectStripper::StripProjectForExport(project);
// Save the project to JSON
gd::SerializerElement rootElement;
project.SerializeTo(rootElement);
SerializeUsedResources(
rootElement, projectUsedResources, scenesUsedResources);
gd::String output =
"gdjs.projectData = " + gd::Serializer::ToJSON(rootElement) + ";\n" +
"gdjs.runtimeGameOptions = " +
gd::Serializer::ToJSON(runtimeGameOptions) + ";\n";
if (!fs.WriteToFile(filename, output)) return "Unable to write " + filename;
return "";
SerializeUsedResources(rootElement, projectUsedResources, scenesUsedResources,
eventsBasedObjectVariantsUsedResources);
}
void ExporterHelper::SerializeUsedResources(
gd::SerializerElement &rootElement,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources,
std::unordered_map<gd::String, std::set<gd::String>>
&eventsBasedObjectVariantsUsedResources) {
auto serializeUsedResources =
[](gd::SerializerElement &element,
std::set<gd::String> &usedResources) -> void {
@@ -385,6 +592,41 @@ void ExporterHelper::SerializeUsedResources(
auto &layoutUsedResources = scenesUsedResources[layoutName];
serializeUsedResources(layoutElement, layoutUsedResources);
}
auto &extensionsElement = rootElement.GetChild("eventsFunctionsExtensions");
for (std::size_t extensionIndex = 0;
extensionIndex < extensionsElement.GetChildrenCount();
extensionIndex++) {
auto &extensionElement = extensionsElement.GetChild(extensionIndex);
const auto extensionName = extensionElement.GetStringAttribute("name");
auto &objectsElement = extensionElement.GetChild("eventsBasedObjects");
for (std::size_t objectIndex = 0;
objectIndex < objectsElement.GetChildrenCount(); objectIndex++) {
auto &objectElement = objectsElement.GetChild(objectIndex);
const auto objectName = objectElement.GetStringAttribute("name");
auto eventsBasedObjectType =
gd::PlatformExtension::GetObjectFullType(extensionName, objectName);
auto &objectUsedResources =
eventsBasedObjectVariantsUsedResources[eventsBasedObjectType];
serializeUsedResources(objectElement, objectUsedResources);
auto &variantsElement = objectElement.GetChild("variants");
for (std::size_t variantIndex = 0;
variantIndex < variantsElement.GetChildrenCount(); variantIndex++) {
auto &variantElement = variantsElement.GetChild(variantIndex);
const auto variantName = variantElement.GetStringAttribute("name");
auto variantType = gd::PlatformExtension::GetVariantFullType(
extensionName, objectName, variantName);
auto &variantUsedResources =
eventsBasedObjectVariantsUsedResources[variantType];
serializeUsedResources(variantElement, variantUsedResources);
}
}
}
}
bool ExporterHelper::ExportIndexFile(
@@ -775,7 +1017,7 @@ bool ExporterHelper::CompleteIndexFile(
gd::String codeFilesIncludes;
for (auto &include : includesFiles) {
gd::String scriptSrc =
GetExportedIncludeFilename(include, nonRuntimeScriptsCacheBurst);
GetExportedIncludeFilename(fs, gdjsRoot, include, nonRuntimeScriptsCacheBurst);
// Sanity check if the file exists - if not skip it to avoid
// including it in the list of scripts.
@@ -801,6 +1043,7 @@ bool ExporterHelper::CompleteIndexFile(
void ExporterHelper::AddLibsInclude(bool pixiRenderers,
bool pixiInThreeRenderers,
bool isInGameEdition,
bool includeWebsocketDebuggerClient,
bool includeWindowMessageDebuggerClient,
bool includeMinimalDebuggerClient,
@@ -878,6 +1121,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "debugger-client/hot-reloader.js");
InsertUnique(includesFiles, "debugger-client/abstract-debugger-client.js");
InsertUnique(includesFiles, "debugger-client/InGameDebugger.js");
InsertUnique(includesFiles, "InGameEditor/InGameEditor.js");
}
if (includeWebsocketDebuggerClient) {
InsertUnique(includesFiles, "debugger-client/websocket-debugger-client.js");
@@ -890,14 +1134,16 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "debugger-client/minimal-debugger-client.js");
}
if (pixiInThreeRenderers) {
if (pixiInThreeRenderers || isInGameEdition) {
InsertUnique(includesFiles, "pixi-renderers/three.js");
InsertUnique(includesFiles, "pixi-renderers/ThreeAddons.js");
InsertUnique(includesFiles, "pixi-renderers/draco/gltf/draco_decoder.wasm");
InsertUnique(includesFiles,
"pixi-renderers/draco/gltf/draco_wasm_wrapper.js");
// Extensions in JS may use it.
InsertUnique(includesFiles, "Extensions/3D/Scene3DTools.js");
}
if (pixiRenderers) {
if (pixiRenderers || isInGameEdition) {
InsertUnique(includesFiles, "pixi-renderers/pixi.js");
InsertUnique(includesFiles, "pixi-renderers/pixi-filters-tools.js");
InsertUnique(includesFiles, "pixi-renderers/runtimegame-pixi-renderer.js");
@@ -921,7 +1167,12 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
includesFiles,
"fontfaceobserver-font-manager/fontfaceobserver-font-manager.js");
}
if (pixiInThreeRenderers) {
if (isInGameEdition) {
// `InGameEditor` uses the `is3D` function.
InsertUnique(includesFiles, "Extensions/3D/Base3DBehavior.js");
InsertUnique(includesFiles, "Extensions/3D/HemisphereLight.js");
}
if (pixiInThreeRenderers || isInGameEdition) {
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3D.js");
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3DRenderer.js");
InsertUnique(includesFiles, "Extensions/3D/CustomRuntimeObject3D.js");
@@ -959,7 +1210,7 @@ bool ExporterHelper::ExportEffectIncludes(
return true;
}
bool ExporterHelper::ExportEventsCode(
bool ExporterHelper::ExportScenesEventsCode(
const gd::Project &project,
gd::String outputDir,
std::vector<gd::String> &includesFiles,
@@ -995,6 +1246,7 @@ bool ExporterHelper::ExportEventsCode(
}
gd::String ExporterHelper::GetExportedIncludeFilename(
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst) {
auto addSearchParameterToUrl = [](const gd::String &url,
const gd::String &urlEncodedParameterName,

View File

@@ -42,9 +42,9 @@ struct PreviewExportOptions {
useWindowMessageDebuggerClient(false),
useMinimalDebuggerClient(false),
nativeMobileApp(false),
projectDataOnlyExport(false),
fullLoadingScreen(false),
isDevelopmentEnvironment(false),
isInGameEdition(false),
nonRuntimeScriptsCacheBurst(0),
inAppTutorialMessageInPreview(""),
inAppTutorialMessagePositionInPreview(""),
@@ -145,6 +145,26 @@ struct PreviewExportOptions {
return *this;
}
/**
* \brief Set the (optional) events-based object to be instantiated in the scene
* at the beginning of the previewed game.
*/
PreviewExportOptions &SetEventsBasedObjectType(
const gd::String &eventsBasedObjectType_) {
eventsBasedObjectType = eventsBasedObjectType_;
return *this;
}
/**
* \brief Set the (optional) events-based object variant to be instantiated in the scene
* at the beginning of the previewed game.
*/
PreviewExportOptions &SetEventsBasedObjectVariantName(
const gd::String &eventsBasedObjectVariantName_) {
eventsBasedObjectVariantName = eventsBasedObjectVariantName_;
return *this;
}
/**
* \brief Set the hash associated to an include file. Useful for the preview
* hot-reload, to know if a file changed.
@@ -156,11 +176,34 @@ struct PreviewExportOptions {
}
/**
* \brief Set if the export should only export the project data, not
* exporting events code.
* \brief Set if the exported folder should be cleared befor the export.
*/
PreviewExportOptions &SetProjectDataOnlyExport(bool enable) {
projectDataOnlyExport = enable;
PreviewExportOptions &SetShouldClearExportFolder(bool enable) {
shouldClearExportFolder = enable;
return *this;
}
/**
* \brief Set if the `ProjectData` must be reloaded.
*/
PreviewExportOptions &SetShouldReloadProjectData(bool enable) {
shouldReloadProjectData = enable;
return *this;
}
/**
* \brief Set if GDJS libraries must be reloaded.
*/
PreviewExportOptions &SetShouldReloadLibraries(bool enable) {
shouldReloadLibraries = enable;
return *this;
}
/**
* \brief Set if the export should export events code.
*/
PreviewExportOptions &SetShouldGenerateScenesEventsCode(bool enable) {
shouldGenerateScenesEventsCode = enable;
return *this;
}
@@ -182,6 +225,40 @@ struct PreviewExportOptions {
return *this;
}
/**
* \brief Set if the export is made for being edited in the editor.
*/
PreviewExportOptions &SetIsInGameEdition(bool enable) {
isInGameEdition = enable;
return *this;
}
/**
* \brief Set the in-game editor identifier.
*/
PreviewExportOptions &SetEditorId(const gd::String &editorId_) {
editorId = editorId_;
return *this;
}
/**
* \brief Set the camera state to use in the in-game editor.
*/
PreviewExportOptions &
SetEditorCameraState3D(const gd::String &cameraMode, double positionX,
double positionY, double positionZ,
double rotationAngle, double elevationAngle,
double distance) {
editorCamera3DCameraMode = cameraMode;
editorCamera3DPositionX = positionX;
editorCamera3DPositionY = positionY;
editorCamera3DPositionZ = positionZ;
editorCamera3DRotationAngle = rotationAngle;
editorCamera3DElevationAngle = elevationAngle;
editorCamera3DDistance = distance;
return *this;
}
/**
* \brief If set to a non zero value, the exported script URLs will have an
* extra search parameter added (with the given value) to ensure browser cache
@@ -294,6 +371,8 @@ struct PreviewExportOptions {
bool useMinimalDebuggerClient;
gd::String layoutName;
gd::String externalLayoutName;
gd::String eventsBasedObjectType;
gd::String eventsBasedObjectVariantName;
gd::String fallbackAuthorUsername;
gd::String fallbackAuthorId;
gd::String playerId;
@@ -303,9 +382,21 @@ struct PreviewExportOptions {
gd::String inAppTutorialMessagePositionInPreview;
bool nativeMobileApp;
std::map<gd::String, int> includeFileHashes;
bool projectDataOnlyExport;
bool shouldClearExportFolder = true;
bool shouldReloadProjectData = true;
bool shouldReloadLibraries = true;
bool shouldGenerateScenesEventsCode = true;
bool fullLoadingScreen;
bool isDevelopmentEnvironment;
bool isInGameEdition;
gd::String editorId;
gd::String editorCamera3DCameraMode;
double editorCamera3DPositionX = 0;
double editorCamera3DPositionY = 0;
double editorCamera3DPositionZ = 0;
double editorCamera3DRotationAngle = 0;
double editorCamera3DElevationAngle = 0;
double editorCamera3DDistance = 0;
unsigned int nonRuntimeScriptsCacheBurst;
gd::String electronRemoteRequirePath;
gd::String gdevelopResourceToken;
@@ -379,23 +470,51 @@ class ExporterHelper {
const gd::String &GetLastError() const { return lastError; };
/**
* \brief Export a project to JSON
* \brief Export a project without its events and options to 2 JS variables
*
* \param fs The abstract file system to use to write the file
* \param project The project to be exported.
* \param filename The filename where export the project
* \param runtimeGameOptions The content of the extra configuration to store
* in gdjs.runtimeGameOptions \return Empty string if everything is ok,
* in gdjs.runtimeGameOptions
*
* \return Empty string if everything is ok,
* description of the error otherwise.
*/
static gd::String ExportProjectData(
gd::AbstractFileSystem &fs,
gd::Project &project,
gd::String filename,
const gd::SerializerElement &runtimeGameOptions,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>>
&layersUsedResources);
gd::AbstractFileSystem &fs, gd::Project &project, gd::String filename,
const gd::SerializerElement &runtimeGameOptions, bool isInGameEdition);
/**
* \brief Serialize a project without its events to JSON
*
* \param fs The abstract file system to use to write the file
* \param project The project to be exported.
* \param options The content of the extra configuration
* \param projectDataElement The element where the project data is serialized
*/
static void SerializeProjectData(gd::AbstractFileSystem &fs,
const gd::Project &project,
const PreviewExportOptions &options,
gd::SerializerElement &projectDataElement);
/**
* \brief Serialize the content of the extra configuration to store
* in gdjs.runtimeGameOptions to JSON
*
* \param fs The abstract file system to use to write the file
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
* \param options The content of the extra configuration
* \param includesFiles The list of scripts files - useful for hot-reloading
* \param runtimeGameOptionsElement The element where the game options are
* serialized
*/
static void
SerializeRuntimeGameOptions(gd::AbstractFileSystem &fs,
const gd::String &gdjsRoot,
const PreviewExportOptions &options,
std::vector<gd::String> &includesFiles,
gd::SerializerElement &runtimeGameOptionsElement);
/**
* \brief Copy all the resources of the project to to the export directory,
@@ -416,6 +535,7 @@ class ExporterHelper {
*/
void AddLibsInclude(bool pixiRenderers,
bool pixiInThreeRenderers,
bool isInGameEdition,
bool includeWebsocketDebuggerClient,
bool includeWindowMessageDebuggerClient,
bool includeMinimalDebuggerClient,
@@ -453,7 +573,7 @@ class ExporterHelper {
* includesFiles A reference to a vector that will be filled with JS files to
* be exported along with the project. ( including "codeX.js" files ).
*/
bool ExportEventsCode(
bool ExportScenesEventsCode(
const gd::Project &project,
gd::String outputDir,
std::vector<gd::String> &includesFiles,
@@ -578,14 +698,20 @@ class ExporterHelper {
* a browser pointing to the preview.
*
* \param options The options to generate the preview.
* \param includesFiles The list of scripts files - useful for hot-reloading
*/
bool ExportProjectForPixiPreview(const PreviewExportOptions &options);
bool ExportProjectForPixiPreview(const PreviewExportOptions &options,
std::vector<gd::String> &includesFiles);
/**
* \brief Given an include file, returns the name of the file to reference
* in the exported game.
*
* \param fs The abstract file system to use
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
*/
gd::String GetExportedIncludeFilename(
static gd::String GetExportedIncludeFilename(
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst = 0);
/**
@@ -612,11 +738,36 @@ class ExporterHelper {
///< be then copied to the final output directory.
private:
static void SerializeUsedResources(
gd::SerializerElement &rootElement,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>>
&layersUsedResources);
static void
SerializeUsedResources(gd::SerializerElement &rootElement,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>>
&layersUsedResources,
std::unordered_map<gd::String, std::set<gd::String>>
&eventsBasedObjectVariantsUsedResources);
/**
* \brief Stript a project and serialize it to JSON
*
* \param project The project to be exported.
*/
static void StriptAndSerializeProjectData(gd::Project &project,
gd::SerializerElement &rootElement,
bool isInGameEdition);
/**
* \brief Add additional resources that are used by the in-game editor to the
* project.
*
* \param project The project to be exported where resource declarations are
* added.
*
* \param projectUsedResources The list of resource to be loaded
* globally by the runtime.
*/
static void
AddInGameEditorResources(gd::Project &project,
std::set<gd::String> &projectUsedResources);
};
} // namespace gdjs

View File

@@ -13,7 +13,7 @@ namespace gdjs {
export type CustomObjectConfiguration = ObjectConfiguration & {
animatable?: SpriteAnimationData[];
variant: string;
childrenContent: { [objectName: string]: ObjectConfiguration & any };
childrenContent?: { [objectName: string]: ObjectConfiguration & any };
isInnerAreaFollowingParentSize: boolean;
};
@@ -118,37 +118,19 @@ namespace gdjs {
);
return;
}
if (!eventsBasedObjectData.defaultVariant) {
eventsBasedObjectData.defaultVariant = {
...eventsBasedObjectData,
name: '',
};
}
// Legacy events-based objects don't have any instance in their default
// variant since there wasn't a graphical editor at the time.
// In this case, the editor doesn't allow to choose a variant, but a
// variant may have stayed after a user rolled back the extension.
// This variant must be ignored to match what the editor shows.
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
eventsBasedObjectData.defaultVariant.instances.length == 0;
let usedVariantData: EventsBasedObjectVariantData =
eventsBasedObjectData.defaultVariant;
if (
customObjectData.variant &&
!isForcedToOverrideEventsBasedObjectChildrenConfiguration
) {
for (
let variantIndex = 0;
variantIndex < eventsBasedObjectData.variants.length;
variantIndex++
) {
const variantData = eventsBasedObjectData.variants[variantIndex];
if (variantData.name === customObjectData.variant) {
usedVariantData = variantData;
break;
}
}
const usedVariantData: EventsBasedObjectVariantData | null =
this.getRuntimeScene()
.getGame()
.getEventsBasedObjectVariantData(
customObjectData.type,
customObjectData.variant
);
if (!usedVariantData) {
// This can't actually happen.
logger.error(
`Unknown variant "${customObjectData.variant}" for object "${customObjectData.type}".`
);
return;
}
this._isInnerAreaFollowingParentSize =
@@ -178,8 +160,7 @@ namespace gdjs {
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
super.reinitialize(objectData);
this._reinitializeRenderer();
this._initializeFromObjectData(objectData);
this._reinitializeContentFromObjectData(objectData);
// When changing the variant, the instance is like a new instance.
// We call again `onCreated` at the end, like done by the constructor
@@ -187,6 +168,14 @@ namespace gdjs {
this.onCreated();
}
private _reinitializeContentFromObjectData(
objectData: ObjectData & CustomObjectConfiguration
) {
this._reinitializeRenderer();
this._instanceContainer._unloadContent();
this._initializeFromObjectData(objectData);
}
override updateFromObjectData(
oldObjectData: ObjectData & CustomObjectConfiguration,
newObjectData: ObjectData & CustomObjectConfiguration
@@ -214,8 +203,7 @@ namespace gdjs {
this._instanceContainer._initialInnerArea.max[1] !==
this._innerArea.max[1]);
this._reinitializeRenderer();
this._initializeFromObjectData(newObjectData);
this._reinitializeContentFromObjectData(newObjectData);
// The generated code calls the onCreated super implementation at the end.
this.onCreated();
@@ -311,15 +299,13 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
}
override onDeletedFromScene(): void {
@@ -658,6 +644,20 @@ namespace gdjs {
return this._unrotatedAABB.max[1];
}
getOriginalWidth(): float {
return (
this._instanceContainer.getInitialUnrotatedViewportMaxX() -
this._instanceContainer.getInitialUnrotatedViewportMinX()
);
}
getOriginalHeight(): float {
return (
this._instanceContainer.getInitialUnrotatedViewportMaxY() -
this._instanceContainer.getInitialUnrotatedViewportMinY()
);
}
/**
* @return the internal width of the object according to its children.
*/

View File

@@ -16,6 +16,7 @@ namespace gdjs {
_parent: gdjs.RuntimeInstanceContainer;
/** The object that is built with the instances of this container. */
_customObject: gdjs.CustomRuntimeObject;
// TODO Remove this attribute
_isLoaded: boolean = false;
/**
* The default size defined by users in the custom object initial instances editor.
@@ -46,15 +47,28 @@ namespace gdjs {
this._debuggerRenderer = new gdjs.DebuggerRenderer(this);
}
// TODO `_layers` and `_orderedLayers` should not be used directly.
addLayer(layerData: LayerData) {
if (this._layers.containsKey(layerData.name)) {
return;
}
// This code is duplicated with `RuntimeScene.addLayer` because it avoids
// to expose a method to build a layer.
const layer = new gdjs.RuntimeCustomObjectLayer(layerData, this);
this._layers.put(layerData.name, layer);
this._orderedLayers.push(layer);
}
_unloadContent() {
this.onDeletedFromScene(this._parent);
// At this point, layer renderers are already removed by
// `CustomRuntimeObject._reinitializeRenderer`.
// It's not great to do this here, but it allows to keep it private.
this._layers.clear();
this._orderedLayers.length = 0;
}
createObject(objectName: string): gdjs.RuntimeObject | null {
const result = super.createObject(objectName);
this._customObject.onChildrenLocationChanged();
@@ -63,21 +77,14 @@ namespace gdjs {
/**
* Load the container from the given initial configuration.
* @param customObjectData An object containing the container data.
* @param customObjectData An object containing the parent object data.
* @param eventsBasedObjectVariantData An object containing the container data.
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
*/
loadFrom(
customObjectData: ObjectData & CustomObjectConfiguration,
eventsBasedObjectVariantData: EventsBasedObjectVariantData
) {
if (this._isLoaded) {
this.onDeletedFromScene(this._parent);
}
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
!eventsBasedObjectVariantData.name &&
eventsBasedObjectVariantData.instances.length == 0;
this._setOriginalInnerArea(eventsBasedObjectVariantData);
// Registering objects
@@ -87,19 +94,21 @@ namespace gdjs {
++i
) {
const childObjectData = eventsBasedObjectVariantData.objects[i];
// The children configuration override only applies to the default variant.
if (
customObjectData.childrenContent &&
(!eventsBasedObjectVariantData.name ||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
customObjectData,
eventsBasedObjectVariantData
)
) {
this.registerObject({
...childObjectData,
// The custom object overrides its events-based object configuration.
// The custom object overrides its variant configuration with
// a legacy children configuration.
...customObjectData.childrenContent[childObjectData.name],
});
} else {
// The custom object follows its events-based object configuration.
// The custom object follows its variant configuration.
this.registerObject(childObjectData);
}
}
@@ -154,6 +163,28 @@ namespace gdjs {
this._isLoaded = true;
}
/**
* Check if the custom object has a children configuration overriding that
* should be used instead of the variant's objects configurations.
* @param customObjectData An object containing the parent object data.
* @param eventsBasedObjectVariantData An object containing the container data.
* @returns
*/
static hasChildrenConfigurationOverriding(
customObjectData: CustomObjectConfiguration,
eventsBasedObjectVariantData: EventsBasedObjectVariantData
): boolean {
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
!eventsBasedObjectVariantData.name &&
eventsBasedObjectVariantData.instances.length == 0;
// The children configuration override only applies to the default variant.
return customObjectData.childrenContent
? !eventsBasedObjectVariantData.name ||
isForcedToOverrideEventsBasedObjectChildrenConfiguration
: false;
}
/**
* Initialize `_initialInnerArea` if it doesn't exist.
* `_initialInnerArea` is shared by every instance to save memory.
@@ -161,7 +192,10 @@ namespace gdjs {
private _setOriginalInnerArea(
eventsBasedObjectData: EventsBasedObjectVariantData
) {
if (eventsBasedObjectData.instances.length > 0) {
if (
eventsBasedObjectData.instances.length > 0 ||
this.getGame().isInGameEdition()
) {
if (!eventsBasedObjectData._initialInnerArea) {
eventsBasedObjectData._initialInnerArea = {
min: [
@@ -341,6 +375,12 @@ namespace gdjs {
return this._initialInnerArea ? this._initialInnerArea.max[1] : 0;
}
_getInitialInnerAreaDepth(): float {
return this._initialInnerArea
? this._initialInnerArea.max[2] - this._initialInnerArea.min[2]
: 0;
}
getViewportWidth(): float {
return this._customObject.getUnscaledWidth();
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -164,14 +164,17 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedThreeModel = this._loadedThreeModels.get(resourceData);
const loadedThreeModel = this._loadedThreeModels.getFromName(
resourceData.name
);
if (loadedThreeModel) {
loadedThreeModel.scene.clear();
this._loadedThreeModels.delete(resourceData);
}
const downloadedArrayBuffer =
this._downloadedArrayBuffers.get(resourceData);
const downloadedArrayBuffer = this._downloadedArrayBuffers.getFromName(
resourceData.name
);
if (downloadedArrayBuffer) {
this._downloadedArrayBuffers.delete(resourceData);
}

View File

@@ -282,6 +282,27 @@ namespace gdjs {
}
}
async loadResources(
resourceNames: Array<string>,
onProgress: (loadingCount: integer, totalCount: integer) => void
): Promise<void> {
let loadedCount = 0;
await processAndRetryIfNeededWithPromisePool(
resourceNames,
maxForegroundConcurrency,
maxAttempt,
async (resourceName) => {
const resource = this._resources.get(resourceName);
if (resource) {
await this._loadResource(resource);
await this._processResource(resource);
}
loadedCount++;
onProgress(loadedCount, this._resources.size);
}
);
}
/**
* Load the resources that are needed to launch the first scene.
*/
@@ -551,6 +572,23 @@ namespace gdjs {
// TODO: mark the scene as unloaded so it's not automatically loaded again eagerly.
}
/**
* To be called when hot-reloading resources.
*/
unloadAllResources(): void {
debugLogger.log(`Unloading of all resources was requested.`);
for (const resource of this._resources.values()) {
const resourceManager = this._resourceManagersMap.get(resource.kind);
if (resourceManager) {
resourceManager.unloadResource(resource);
}
}
for (const sceneLoadingState of this._sceneLoadingStates.values()) {
sceneLoadingState.status = 'not-loaded';
}
debugLogger.log(`Unloading of all resources finished.`);
}
/**
* Put a given scene at the end of the queue.
*
@@ -652,6 +690,9 @@ namespace gdjs {
* the resource (this can be for example a token needed to access the resource).
*/
getFullUrl(url: string) {
if (this._runtimeGame.isInGameEdition()) {
url = addSearchParameterToUrl(url, 'cache', '' + Date.now());
}
const { gdevelopResourceToken } = this._runtimeGame._options;
if (!gdevelopResourceToken) return url;

View File

@@ -575,6 +575,14 @@ namespace gdjs {
this._cacheOrClearRemovedInstances();
}
_updateObjectsForInGameEditor() {
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const obj = allInstancesList[i];
obj.update(this);
}
}
/**
* Call each behavior stepPostEvents method.
*/
@@ -612,7 +620,7 @@ namespace gdjs {
getObjects(name: string): gdjs.RuntimeObject[] | undefined {
if (!this._instances.containsKey(name)) {
logger.info(
'RuntimeScene.getObjects: No instances called "' +
'RuntimeInstanceContainer.getObjects: No instances called "' +
name +
'"! Adding it.'
);

View File

@@ -55,13 +55,14 @@ namespace gdjs {
_timeScale: float = 1;
_defaultZOrder: integer = 0;
_hidden: boolean;
_initialEffectsData: Array<EffectData>;
_initialLayerData: LayerData;
// TODO EBO Don't store scene layer related data in layers used by custom objects.
// (both these 3D settings and the lighting layer properties below).
_initialCamera3DFieldOfView: float;
_initialCamera3DFarPlaneDistance: float;
_initialCamera3DNearPlaneDistance: float;
_initialCamera2DPlaneMaxDrawingDistance: float;
_runtimeScene: gdjs.RuntimeInstanceContainer;
_effectsManager: gdjs.EffectsManager;
@@ -94,7 +95,9 @@ namespace gdjs {
layerData.camera3DNearPlaneDistance || 0.1;
this._initialCamera3DFarPlaneDistance =
layerData.camera3DFarPlaneDistance || 2000;
this._initialEffectsData = layerData.effects || [];
this._initialCamera2DPlaneMaxDrawingDistance =
layerData.camera2DPlaneMaxDrawingDistance || 5000;
this._initialLayerData = layerData;
this._runtimeScene = instanceContainer;
this._effectsManager = instanceContainer.getGame().getEffectsManager();
this._isLightingLayer = layerData.isLightingLayer;
@@ -491,6 +494,9 @@ namespace gdjs {
getInitialCamera3DFarPlaneDistance(): float {
return this._initialCamera3DFarPlaneDistance;
}
getInitialCamera2DPlaneMaxDrawingDistance(): float {
return this._initialCamera2DPlaneMaxDrawingDistance;
}
/**
* Return the initial effects data for the layer. Only to
@@ -498,7 +504,7 @@ namespace gdjs {
* @deprecated
*/
getInitialEffectsData(): EffectData[] {
return this._initialEffectsData;
return this._initialLayerData.effects || [];
}
/**

View File

@@ -107,8 +107,12 @@ namespace gdjs {
exception: Error,
runtimeGame: gdjs.RuntimeGame
) => {
const sceneNames = runtimeGame.getSceneStack().getAllSceneNames();
const currentScene = runtimeGame.getSceneStack().getCurrentScene();
const currentScene = runtimeGame.isInGameEdition()
? runtimeGame.getInGameEditor()?.getCurrentScene()
: runtimeGame.getSceneStack().getCurrentScene();
const sceneNames = runtimeGame.isInGameEdition()
? [currentScene?.getName()]
: runtimeGame.getSceneStack().getAllSceneNames();
return {
type: 'javascript-uncaught-exception',
exception,
@@ -116,6 +120,7 @@ namespace gdjs {
playerId: runtimeGame.getPlayerId(),
sessionId: runtimeGame.getSessionId(),
isPreview: runtimeGame.isPreview(),
isInGameEdition: runtimeGame.isInGameEdition(),
gdevelop: {
previewContext: runtimeGame.getAdditionalOptions().previewContext,
isNativeMobileApp: runtimeGame.getAdditionalOptions().nativeMobileApp,
@@ -233,42 +238,250 @@ namespace gdjs {
protected handleCommand(data: any) {
const that = this;
const runtimeGame = this._runtimegame;
const inGameEditor = runtimeGame.getInGameEditor();
if (!data || !data.command) {
// Not a command that's meant to be handled by the debugger, return silently to
// avoid polluting the console.
return;
}
if (data.command === 'play') {
runtimeGame.pause(false);
} else if (data.command === 'pause') {
runtimeGame.pause(true);
that.sendRuntimeGameDump();
} else if (data.command === 'refresh') {
that.sendRuntimeGameDump();
} else if (data.command === 'set') {
that.set(data.path, data.newValue);
} else if (data.command === 'call') {
that.call(data.path, data.args);
} else if (data.command === 'profiler.start') {
runtimeGame.startCurrentSceneProfiler(function (stoppedProfiler) {
that.sendProfilerOutput(
stoppedProfiler.getFramesAverageMeasures(),
stoppedProfiler.getStats()
try {
if (data.command === 'play') {
runtimeGame.pause(false);
} else if (data.command === 'pause') {
runtimeGame.pause(true);
that.sendRuntimeGameDump();
} else if (data.command === 'refresh') {
that.sendRuntimeGameDump();
} else if (data.command === 'getStatus') {
that.sendRuntimeGameStatus();
} else if (data.command === 'set') {
that.set(data.path, data.newValue);
} else if (data.command === 'call') {
that.call(data.path, data.args);
} else if (data.command === 'profiler.start') {
runtimeGame.startCurrentSceneProfiler(function (stoppedProfiler) {
that.sendProfilerOutput(
stoppedProfiler.getFramesAverageMeasures(),
stoppedProfiler.getStats()
);
that.sendProfilerStopped();
});
that.sendProfilerStarted();
} else if (data.command === 'profiler.stop') {
runtimeGame.stopCurrentSceneProfiler();
} else if (data.command === 'hotReload') {
const runtimeGameOptions: RuntimeGameOptions =
data.payload.runtimeGameOptions;
if (
(runtimeGameOptions.initialRuntimeGameStatus?.isInGameEdition ||
false) === runtimeGame.isInGameEdition()
) {
this._hasLoggedUncaughtException = false;
that._hotReloader
.hotReload({
projectData: data.payload.projectData,
runtimeGameOptions,
shouldReloadResources:
data.payload.shouldReloadResources || false,
})
.then((logs) => {
that.sendHotReloaderLogs(logs);
});
}
} else if (data.command === 'hotReloadObjects') {
if (inGameEditor) {
const editedInstanceContainer =
inGameEditor.getEditedInstanceContainer();
if (editedInstanceContainer) {
that._hotReloader.hotReloadRuntimeSceneObjects(
data.payload.updatedObjects,
editedInstanceContainer
);
}
}
} else if (data.command === 'hotReloadLayers') {
if (inGameEditor) {
const editedInstanceContainer =
inGameEditor.getEditedInstanceContainer();
if (editedInstanceContainer) {
inGameEditor.onLayersDataChange(
data.payload.layers,
data.payload.areEffectsHidden
);
runtimeGame.getGameData().areEffectsHiddenInEditor =
data.payload.areEffectsHidden;
that._hotReloader.hotReloadRuntimeSceneLayers(
data.payload.layers,
editedInstanceContainer
);
}
}
} else if (data.command === 'setBackgroundColor') {
if (inGameEditor) {
const editedInstanceContainer =
inGameEditor.getEditedInstanceContainer();
if (editedInstanceContainer) {
const backgroundColor = data.payload.backgroundColor;
if (
backgroundColor &&
editedInstanceContainer instanceof gdjs.RuntimeScene
) {
const sceneData = runtimeGame.getSceneData(
editedInstanceContainer.getScene().getName()
);
if (sceneData) {
editedInstanceContainer._backgroundColor =
gdjs.rgbToHexNumber(
backgroundColor[0],
backgroundColor[1],
backgroundColor[2]
);
sceneData.r = backgroundColor[0];
sceneData.v = backgroundColor[1];
sceneData.b = backgroundColor[2];
}
}
}
}
} else if (data.command === 'hotReloadAllInstances') {
if (inGameEditor) {
const editedInstanceContainer =
inGameEditor.getEditedInstanceContainer();
if (editedInstanceContainer) {
that._hotReloader.hotReloadRuntimeInstances(
inGameEditor.getEditedInstanceDataList(),
data.payload.instances,
editedInstanceContainer
);
}
}
} else if (data.command === 'switchForInGameEdition') {
if (!this._runtimegame.isInGameEdition()) return;
const sceneName = data.sceneName || null;
const eventsBasedObjectType = data.eventsBasedObjectType || null;
if (!sceneName && !eventsBasedObjectType) {
logger.warn(
'No scene name specified, switchForInGameEdition aborted'
);
return;
}
if (inGameEditor) {
const wasPaused = this._runtimegame.isPaused();
this._runtimegame.pause(true);
inGameEditor.switchToSceneOrVariant(
data.editorId || null,
sceneName,
data.externalLayoutName || null,
eventsBasedObjectType,
data.eventsBasedObjectVariantName || null,
data.editorCamera3D || null
);
this._runtimegame.pause(wasPaused);
}
} else if (data.command === 'setVisibleStatus') {
if (inGameEditor) {
inGameEditor.setVisibleStatus(data.visible);
}
} else if (data.command === 'updateInstances') {
if (inGameEditor) {
inGameEditor.reloadInstances(data.payload.instances);
}
} else if (data.command === 'addInstances') {
if (inGameEditor) {
inGameEditor.addInstances(data.payload.instances);
inGameEditor.setSelectedObjects(
data.payload.instances.map((instance) => instance.persistentUuid)
);
if (data.payload.moveUnderCursor) {
inGameEditor.moveSelectionUnderCursor();
}
}
} else if (data.command === 'deleteSelection') {
if (inGameEditor) {
inGameEditor.deleteSelection();
}
} else if (data.command === 'dragNewInstance') {
const gameCoords = runtimeGame
.getRenderer()
.convertPageToGameCoords(data.x, data.y);
runtimeGame
.getInputManager()
.onMouseMove(gameCoords[0], gameCoords[1]);
if (inGameEditor)
inGameEditor.dragNewInstance({
name: data.name,
dropped: data.dropped,
isAltPressed: data.isAltPressed,
});
} else if (data.command === 'cancelDragNewInstance') {
if (inGameEditor) inGameEditor.cancelDragNewInstance();
} else if (data.command === 'setInstancesEditorSettings') {
if (inGameEditor)
inGameEditor.updateInstancesEditorSettings(
data.payload.instancesEditorSettings
);
} else if (data.command === 'zoomToInitialPosition') {
if (inGameEditor) {
inGameEditor.zoomToInitialPosition(data.payload.visibleScreenArea);
}
} else if (data.command === 'zoomToFitContent') {
if (inGameEditor) {
inGameEditor.zoomToFitContent(data.payload.visibleScreenArea);
}
} else if (data.command === 'setSelectedLayer') {
if (inGameEditor) {
inGameEditor.setSelectedLayerName(data.payload.layerName);
}
} else if (data.command === 'zoomToFitSelection') {
if (inGameEditor) {
inGameEditor.zoomToFitSelection(data.payload.visibleScreenArea);
}
} else if (data.command === 'zoomBy') {
if (inGameEditor) {
inGameEditor.zoomBy(data.payload.zoomFactor);
}
} else if (data.command === 'setZoom') {
if (inGameEditor) {
inGameEditor.setZoom(data.payload.zoom);
}
} else if (data.command === 'setSelectedInstances') {
if (inGameEditor) {
inGameEditor.setSelectedObjects(data.payload.instanceUuids);
}
} else if (data.command === 'centerViewOnLastSelectedInstance') {
if (inGameEditor) {
// TODO: use data.payload.visibleScreenArea
inGameEditor.centerViewOnLastSelectedInstance();
}
} else if (data.command === 'updateInnerArea') {
if (inGameEditor) {
inGameEditor.updateInnerArea(
data.payload.areaMinX,
data.payload.areaMinY,
data.payload.areaMinZ,
data.payload.areaMaxX,
data.payload.areaMaxY,
data.payload.areaMaxZ
);
}
} else if (data.command === 'getSelectionAABB') {
if (inGameEditor) {
this.sendSelectionAABB(data.messageId);
}
} else if (data.command === 'hardReload') {
// This usually means that the preview was modified so much that an entire reload
// is needed, or that the runtime itself could have been modified.
this.launchHardReload();
} else {
logger.info(
'Unknown command "' + data.command + '" received by the debugger.'
);
that.sendProfilerStopped();
});
that.sendProfilerStarted();
} else if (data.command === 'profiler.stop') {
runtimeGame.stopCurrentSceneProfiler();
} else if (data.command === 'hotReload') {
that._hotReloader.hotReload().then((logs) => {
that.sendHotReloaderLogs(logs);
});
} else {
logger.info(
'Unknown command "' + data.command + '" received by the debugger.'
);
}
} catch (error) {
this.onUncaughtException(error);
}
}
@@ -330,9 +543,12 @@ namespace gdjs {
}
onUncaughtException(exception: Error): void {
logger.error('Uncaught exception: ' + exception);
logger.error('Uncaught exception: ', exception, exception.stack);
this._inGameDebugger.setUncaughtException(exception);
const runtimeGame = this._runtimegame;
if (!runtimeGame.isInGameEdition()) {
this._inGameDebugger.setUncaughtException(exception);
}
if (!this._hasLoggedUncaughtException) {
// Only log an uncaught exception once, to avoid spamming the debugger server
@@ -435,6 +651,20 @@ namespace gdjs {
return true;
}
sendRuntimeGameStatus(): void {
const currentScene = this._runtimegame.getSceneStack().getCurrentScene();
this._sendMessage(
circularSafeStringify({
command: 'status',
payload: {
isPaused: this._runtimegame.isPaused(),
isInGameEdition: this._runtimegame.isInGameEdition(),
sceneName: currentScene ? currentScene.getName() : null,
},
})
);
}
/**
* Dump all the relevant data from the {@link RuntimeGame} instance and send it to the server.
*/
@@ -515,7 +745,10 @@ namespace gdjs {
this._sendMessage(
circularSafeStringify({
command: 'hotReloader.logs',
payload: logs,
payload: {
isInGameEdition: this._runtimegame.isInGameEdition(),
logs,
},
})
);
}
@@ -544,26 +777,152 @@ namespace gdjs {
);
}
/**
* Callback called when the game is paused.
*/
sendGamePaused(): void {
sendInstanceChanges(changes: {
isSendingBackSelectionForDefaultSize: boolean;
updatedInstances: Array<InstanceData>;
addedInstances: Array<InstanceData>;
selectedInstances: Array<InstancePersistentUuidData>;
removedInstances: Array<InstancePersistentUuidData>;
}): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'game.paused',
payload: null,
command: 'updateInstances',
editorId: inGameEditor.getEditorId(),
payload: changes,
})
);
}
/**
* Callback called when the game is resumed.
*/
sendGameResumed(): void {
sendOpenContextMenu(cursorX: float, cursorY: float): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'game.resumed',
payload: null,
command: 'openContextMenu',
editorId: inGameEditor.getEditorId(),
payload: { cursorX, cursorY },
})
);
}
sendCameraState(cameraState: EditorCameraState): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'setCameraState',
editorId: inGameEditor.getEditorId(),
payload: cameraState,
})
);
}
sendUndo(): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'undo',
editorId: inGameEditor.getEditorId(),
payload: {},
})
);
}
sendRedo(): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'redo',
editorId: inGameEditor.getEditorId(),
payload: {},
})
);
}
sendCopy(): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'copy',
editorId: inGameEditor.getEditorId(),
payload: {},
})
);
}
sendPaste(): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'paste',
editorId: inGameEditor.getEditorId(),
payload: {},
})
);
}
sendCut(): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
this._sendMessage(
circularSafeStringify({
command: 'cut',
editorId: inGameEditor.getEditorId(),
payload: {},
})
);
}
sendSelectionAABB(messageId: number): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
const selectionAABB = inGameEditor.getSelectionAABB();
this._sendMessage(
circularSafeStringify({
command: 'selectionAABB',
editorId: inGameEditor.getEditorId(),
messageId,
payload: selectionAABB
? {
minX: selectionAABB.min[0],
minY: selectionAABB.min[1],
minZ: selectionAABB.min[2],
maxX: selectionAABB.max[0],
maxY: selectionAABB.max[1],
maxZ: selectionAABB.max[2],
}
: {
minX: 0,
minY: 0,
minZ: 0,
maxX: 0,
maxY: 0,
maxZ: 0,
},
})
);
}
@@ -587,5 +946,43 @@ namespace gdjs {
})
);
}
launchHardReload(): void {
try {
const reloadUrl = new URL(location.href);
// Construct the initial status to be restored.
const initialRuntimeGameStatus =
this._runtimegame.getAdditionalOptions().initialRuntimeGameStatus;
// We use empty strings to avoid `null` to become `"null"`.
const runtimeGameStatus: RuntimeGameStatus = {
editorId: initialRuntimeGameStatus?.editorId || '',
isPaused: this._runtimegame.isPaused(),
isInGameEdition: this._runtimegame.isInGameEdition(),
sceneName: initialRuntimeGameStatus?.sceneName || '',
injectedExternalLayoutName:
initialRuntimeGameStatus?.injectedExternalLayoutName || '',
skipCreatingInstancesFromScene:
initialRuntimeGameStatus?.skipCreatingInstancesFromScene || false,
eventsBasedObjectType:
initialRuntimeGameStatus?.eventsBasedObjectType || '',
eventsBasedObjectVariantName:
initialRuntimeGameStatus?.eventsBasedObjectVariantName || '',
editorCamera3D: this._runtimegame.getInGameEditor()?.getCameraState(),
};
reloadUrl.searchParams.set(
'runtimeGameStatus',
JSON.stringify(runtimeGameStatus)
);
location.replace(reloadUrl);
} catch (error) {
logger.error(
'Could not reload the game with the new initial status',
error
);
location.reload();
}
}
}
}

View File

@@ -144,18 +144,30 @@ namespace gdjs {
});
}
hotReload(): Promise<HotReloaderLog[]> {
async hotReload({
shouldReloadResources,
projectData: newProjectData,
runtimeGameOptions: newRuntimeGameOptions,
}: {
shouldReloadResources: boolean;
projectData: ProjectData;
runtimeGameOptions: RuntimeGameOptions;
}): Promise<HotReloaderLog[]> {
logger.info('Hot reload started');
const wasPaused = this._runtimeGame.isPaused();
this._runtimeGame.pause(true);
this._logs = [];
// Save old data of the project, to be used to compute
// the difference between the old and new project data:
const oldProjectData: ProjectData = gdjs.projectData;
gdjs.projectData = newProjectData;
const oldScriptFiles = gdjs.runtimeGameOptions
.scriptFiles as RuntimeGameOptionsScriptFile[];
const oldRuntimeGameOptions = gdjs.runtimeGameOptions;
gdjs.runtimeGameOptions = newRuntimeGameOptions;
const oldScriptFiles =
oldRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
oldScriptFiles.forEach((scriptFile) => {
this._alreadyLoadedScriptFiles[scriptFile.path] = true;
@@ -167,76 +179,102 @@ namespace gdjs {
gdjs.behaviorsTypes.items[behaviorTypeName];
}
// Reload projectData and runtimeGameOptions stored by convention in data.js:
return this._reloadScript('data.js').then(() => {
const newProjectData: ProjectData = gdjs.projectData;
if (gdjs.inAppTutorialMessage) {
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
this._runtimeGame,
newRuntimeGameOptions.inAppTutorialMessageInPreview,
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
);
}
const newRuntimeGameOptions: RuntimeGameOptions =
gdjs.runtimeGameOptions;
const newScriptFiles =
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
const shouldGenerateScenesEventsCode =
!!newRuntimeGameOptions.shouldGenerateScenesEventsCode;
const shouldReloadLibraries =
!!newRuntimeGameOptions.shouldReloadLibraries;
if (gdjs.inAppTutorialMessage) {
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
this._runtimeGame,
newRuntimeGameOptions.inAppTutorialMessageInPreview,
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
// Reload the changed scripts, which will have the side effects of re-running
// the new scripts, potentially replacing the code of the free functions from
// extensions (which is fine) and registering updated behaviors (which will
// need to be re-instantiated in runtime objects).
try {
if (shouldReloadLibraries) {
await this.reloadScriptFiles(
newProjectData,
oldScriptFiles,
newScriptFiles,
shouldGenerateScenesEventsCode
);
}
const newScriptFiles =
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
const projectDataOnlyExport =
!!newRuntimeGameOptions.projectDataOnlyExport;
// Reload the changed scripts, which will have the side effects of re-running
// the new scripts, potentially replacing the code of the free functions from
// extensions (which is fine) and registering updated behaviors (which will
// need to be re-instantiated in runtime objects).
return this.reloadScriptFiles(
newProjectData,
oldScriptFiles,
newScriptFiles,
projectDataOnlyExport
)
.then(() => {
const changedRuntimeBehaviors =
this._computeChangedRuntimeBehaviors(
oldBehaviorConstructors,
gdjs.behaviorsTypes.items
);
return this._hotReloadRuntimeGame(
oldProjectData,
newProjectData,
changedRuntimeBehaviors,
this._runtimeGame
const newRuntimeGameStatus =
newRuntimeGameOptions.initialRuntimeGameStatus;
if (
newRuntimeGameStatus &&
newRuntimeGameStatus.editorId &&
newRuntimeGameStatus.isInGameEdition
) {
if (shouldReloadResources) {
// Unloading all resources will force them to be loaded again,
// which is sufficient for ensuring they are up-to-date as
// resources will be loaded with a 'cache bursting' parameter.
this._runtimeGame._resourcesLoader.unloadAllResources();
}
// The editor don't need to hot-reload the current scene because the
// editor always stays in the initial state.
this._runtimeGame.setProjectData(newProjectData);
await this._runtimeGame.loadFirstAssetsAndStartBackgroundLoading(
newRuntimeGameStatus.sceneName || newProjectData.firstLayout,
() => {}
);
const inGameEditor = this._runtimeGame.getInGameEditor();
if (inGameEditor) {
await inGameEditor.switchToSceneOrVariant(
newRuntimeGameStatus.editorId || null,
newRuntimeGameStatus.sceneName,
newRuntimeGameStatus.injectedExternalLayoutName,
newRuntimeGameStatus.eventsBasedObjectType,
newRuntimeGameStatus.eventsBasedObjectVariantName,
newRuntimeGameStatus.editorCamera3D || null
);
})
.catch((error) => {
const errorTarget = error.target;
if (errorTarget instanceof HTMLScriptElement) {
this._logs.push({
kind: 'fatal',
message: 'Unable to reload script: ' + errorTarget.src,
});
} else {
this._logs.push({
kind: 'fatal',
message:
'Unexpected error happened while hot-reloading: ' +
error.message +
'\n' +
error.stack,
});
}
})
.then(() => {
logger.info(
'Hot reload finished with logs:',
this._logs.map((log) => '\n' + log.kind + ': ' + log.message)
);
this._runtimeGame.pause(false);
return this._logs;
}
} else {
const changedRuntimeBehaviors = this._computeChangedRuntimeBehaviors(
oldBehaviorConstructors,
gdjs.behaviorsTypes.items
);
await this._hotReloadRuntimeGame(
oldProjectData,
newProjectData,
changedRuntimeBehaviors,
this._runtimeGame
);
}
} catch (error) {
const errorTarget = error.target;
if (errorTarget instanceof HTMLScriptElement) {
this._logs.push({
kind: 'fatal',
message: 'Unable to reload script: ' + errorTarget.src,
});
});
} else {
this._logs.push({
kind: 'fatal',
message:
'Unexpected error happened while hot-reloading: ' +
error.message +
'\n' +
error.stack,
});
}
}
logger.info(
'Hot reload finished with logs:',
this._logs.map((log) => '\n' + log.kind + ': ' + log.message)
);
this._runtimeGame.pause(wasPaused);
return this._logs;
}
_computeChangedRuntimeBehaviors(
@@ -281,12 +319,12 @@ namespace gdjs {
newProjectData: ProjectData,
oldScriptFiles: RuntimeGameOptionsScriptFile[],
newScriptFiles: RuntimeGameOptionsScriptFile[],
projectDataOnlyExport: boolean
shouldGenerateScenesEventsCode: boolean
): Promise<void[]> {
const reloadPromises: Array<Promise<void>> = [];
// Reload events, only if they were exported.
if (!projectDataOnlyExport) {
if (shouldGenerateScenesEventsCode) {
newProjectData.layouts.forEach((_layoutData, index) => {
reloadPromises.push(this._reloadScript('code' + index + '.js'));
});
@@ -326,7 +364,7 @@ namespace gdjs {
)[0];
// A file may be removed because of a partial preview.
if (!newScriptFile && !projectDataOnlyExport) {
if (!newScriptFile && !shouldGenerateScenesEventsCode) {
this._logs.push({
kind: 'warning',
message: 'Script file ' + oldScriptFile.path + ' was removed.',
@@ -694,6 +732,16 @@ namespace gdjs {
runtimeScene.setEventsGeneratedCodeFunction(newLayoutData);
}
/**
* Add the children object data into every custom object data.
*
* At the runtime, this is done at the object instantiation.
* For hot-reloading, it's done before hands to optimize.
*
* @param projectData The project data
* @param objectDatas The object datas to modify
* @returns
*/
static resolveCustomObjectConfigurations(
projectData: ProjectData,
objectDatas: ObjectData[]
@@ -717,27 +765,43 @@ namespace gdjs {
if (!eventsBasedObjectData) {
return objectData;
}
const customObjectConfiguration = objectData as ObjectData &
CustomObjectConfiguration;
const eventsBasedObjectVariantData =
gdjs.RuntimeGame._getEventsBasedObjectVariantData(
eventsBasedObjectData,
customObjectConfiguration.variant
);
// Apply the legacy children configuration overriding if any.
const mergedChildObjectDataList =
customObjectConfiguration.childrenContent
? eventsBasedObjectData.objects.map((objectData) => ({
...objectData,
...customObjectConfiguration.childrenContent[objectData.name],
}))
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
customObjectConfiguration,
eventsBasedObjectVariantData
)
? eventsBasedObjectData.objects.map((objectData) =>
customObjectConfiguration.childrenContent
? {
...objectData,
...customObjectConfiguration.childrenContent[
objectData.name
],
}
: objectData
)
: eventsBasedObjectData.objects;
const mergedObjectConfiguration = {
...eventsBasedObjectData,
...objectData,
// ObjectData doesn't have an `objects` attribute.
// ObjectData doesn't have an `objects` nor `instances` attribute.
// This is a small optimization to avoid to create an
// InstanceContainerData for each instance to hot-reload their inner
// scene (see `_hotReloadRuntimeInstanceContainer` call from
// `_hotReloadRuntimeSceneInstances`).
...eventsBasedObjectData,
...eventsBasedObjectVariantData,
objects: mergedChildObjectDataList,
// It must be the last one to ensure the object name won't be overridden.
...objectData,
};
return mergedObjectConfiguration;
});
@@ -751,6 +815,12 @@ namespace gdjs {
changedRuntimeBehaviors: ChangedRuntimeBehavior[],
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
): void {
if (!oldLayoutData.objects || !newLayoutData.objects) {
// It can happen when `hotReloadRuntimeInstances` is executed.
// `hotReloadRuntimeInstances` doesn't resolve the custom objects
// because it can only modify the 1st level of instances.
return;
}
const oldObjectDataList = HotReloader.resolveCustomObjectConfigurations(
oldProjectData,
oldLayoutData.objects
@@ -921,16 +991,62 @@ namespace gdjs {
return;
}
hotReloadRuntimeSceneObjects(
updatedObjects: Array<ObjectData>,
// runtimeInstanceContainer gives an access as a map.
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
): void {
const oldObjects: Array<ObjectData | null> = updatedObjects.map(
(objectData) =>
runtimeInstanceContainer._objects.get(objectData.name) || null
);
const projectData: ProjectData = this._runtimeGame._data;
const newObjectDataList = HotReloader.resolveCustomObjectConfigurations(
projectData,
updatedObjects
);
this._hotReloadRuntimeSceneObjects(
oldObjects,
newObjectDataList,
runtimeInstanceContainer
);
// Update the GameData
for (let index = 0; index < updatedObjects.length; index++) {
const oldObjectData = oldObjects[index];
// When the object is new, the hot-reload call `registerObject`
// so `_objects` is already updated.
if (oldObjectData) {
// In gdjs.CustomRuntimeObjectInstanceContainer.loadFrom, object can
// be registered with a different instance from the ProjectData. This
// is only done for children of a custom object with a children overriding.
// In the case of the editor, the fake custom object used for editing
// variants has no children overriding (see
// gdjs.RuntimeGame._createSceneWithCustomObject).
// Thus, the oldObjectData is always the one from the ProjectData.
HotReloader.assignOrDelete(oldObjectData, updatedObjects[index]);
} else {
console.warn(
`Can't update object data for "${updatedObjects[index].name}" because it doesn't exist.`
);
}
}
}
_hotReloadRuntimeSceneObjects(
oldObjects: ObjectData[],
oldObjects: Array<ObjectData | null>,
newObjects: ObjectData[],
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
): void {
oldObjects.forEach((oldObjectData) => {
if (!oldObjectData) {
return;
}
const name = oldObjectData.name;
const newObjectData = newObjects.filter(
const newObjectData = newObjects.find(
(objectData) => objectData.name === name
)[0];
);
// Note: if an object is renamed in the editor, it will be considered as removed,
// and the new object name as a new object to register.
@@ -952,9 +1068,9 @@ namespace gdjs {
});
newObjects.forEach((newObjectData) => {
const name = newObjectData.name;
const oldObjectData = oldObjects.filter(
(layerData) => layerData.name === name
)[0];
const oldObjectData = oldObjects.find(
(layerData) => layerData && layerData.name === name
);
if (
(!oldObjectData || oldObjectData.type !== newObjectData.type) &&
!runtimeInstanceContainer.isObjectRegistered(name)
@@ -1192,6 +1308,31 @@ namespace gdjs {
);
}
hotReloadRuntimeSceneLayers(
newLayers: LayerData[],
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
): void {
const layerNames = [];
runtimeInstanceContainer.getAllLayerNames(layerNames);
const oldLayers = layerNames.map((layerName) =>
runtimeInstanceContainer.hasLayer(layerName)
? runtimeInstanceContainer.getLayer(layerName)._initialLayerData
: null
);
this._hotReloadRuntimeSceneLayers(
oldLayers.filter(Boolean) as LayerData[],
newLayers,
runtimeInstanceContainer
);
// Update the GameData
for (let index = 0; index < newLayers.length; index++) {
const oldLayer = oldLayers[index];
if (oldLayer) {
HotReloader.assignOrDelete(oldLayer, newLayers[index]);
}
}
}
_hotReloadRuntimeSceneLayers(
oldLayers: LayerData[],
newLayers: LayerData[],
@@ -1273,6 +1414,8 @@ namespace gdjs {
newLayer.effects,
runtimeLayer
);
runtimeLayer._initialLayerData = newLayer;
}
_hotReloadRuntimeLayerEffects(
@@ -1357,6 +1500,28 @@ namespace gdjs {
}
}
hotReloadRuntimeInstances(
oldInstances: InstanceData[],
newInstances: InstanceData[],
runtimeInstanceContainer: RuntimeInstanceContainer
): void {
const projectData: ProjectData = gdjs.projectData;
const objects: Array<ObjectData> = [];
runtimeInstanceContainer._objects.values(objects);
projectData.layouts;
this._hotReloadRuntimeSceneInstances(
projectData,
projectData,
[],
objects,
objects,
oldInstances,
newInstances,
runtimeInstanceContainer
);
gdjs.copyArray(newInstances, oldInstances);
}
_hotReloadRuntimeSceneInstances(
oldProjectData: ProjectData,
newProjectData: ProjectData,
@@ -1423,6 +1588,9 @@ namespace gdjs {
);
} else {
// Reload objects that were created at runtime.
// This is a subset of what is done by `_hotReloadRuntimeInstance`.
// Since the instance doesn't exist in the editor, it's properties
// can't be updated, only the object changes are applied.
// Update variables
this._hotReloadVariablesContainer(
@@ -1431,6 +1599,7 @@ namespace gdjs {
runtimeObject.getVariables()
);
// Update the content of custom object
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
const childrenInstanceContainer =
runtimeObject.getChildrenContainer();
@@ -1443,15 +1612,18 @@ namespace gdjs {
CustomObjectConfiguration &
InstanceContainerData;
// Reload the content of custom objects that were created at runtime.
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
// Reload the content of custom objects that were created at runtime.
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
}
}
}
}
@@ -1513,22 +1685,16 @@ namespace gdjs {
somethingChanged = true;
}
if (gdjs.Base3DHandler && gdjs.Base3DHandler.is3D(runtimeObject)) {
if (oldInstance.z !== newInstance.z && newInstance.z !== undefined) {
runtimeObject.setZ(newInstance.z);
if (oldInstance.z !== newInstance.z) {
runtimeObject.setZ(newInstance.z || 0);
somethingChanged = true;
}
if (
oldInstance.rotationX !== newInstance.rotationX &&
newInstance.rotationX !== undefined
) {
runtimeObject.setRotationX(newInstance.rotationX);
if (oldInstance.rotationX !== newInstance.rotationX) {
runtimeObject.setRotationX(newInstance.rotationX || 0);
somethingChanged = true;
}
if (
oldInstance.rotationY !== newInstance.rotationY &&
newInstance.rotationY !== undefined
) {
runtimeObject.setRotationY(newInstance.rotationY);
if (oldInstance.rotationY !== newInstance.rotationY) {
runtimeObject.setRotationY(newInstance.rotationY || 0);
somethingChanged = true;
}
}
@@ -1583,8 +1749,6 @@ namespace gdjs {
}
}
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
const childrenInstanceContainer = runtimeObject.getChildrenContainer();
// The `objects` attribute is already resolved by `resolveCustomObjectConfigurations()`.
const oldCustomObjectData = oldObjectData as ObjectData &
CustomObjectConfiguration &
@@ -1593,14 +1757,19 @@ namespace gdjs {
CustomObjectConfiguration &
InstanceContainerData;
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
const childrenInstanceContainer =
runtimeObject.getChildrenContainer();
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
}
}
// Update variables
@@ -1727,5 +1896,23 @@ namespace gdjs {
// true if both NaN, false otherwise
return a !== a && b !== b;
}
static assignOrDelete(
target: any,
source: any,
ignoreKeys: string[] = []
): void {
Object.assign(target, source);
for (const key in target) {
if (ignoreKeys.includes(key)) {
continue;
}
if (Object.prototype.hasOwnProperty.call(target, key)) {
if (source[key] === undefined) {
delete target[key];
}
}
}
}
}
}

View File

@@ -56,6 +56,19 @@ namespace gdjs {
};
this._ws.onclose = function close() {
logger.info('Debugger connection closed');
if (that._runtimegame.isInGameEdition()) {
// Sometimes, for example if the editor is launched for a long time and the device goes to sleep,
// the WebSocket connection between the editor and the game is closed. When we are in in-game edition,
// we can't afford to lose the connection because it means the editor is unusable.
// In this case, we hard reload the game to re-establish a new connection.
setTimeout(() => {
logger.info(
'Debugger connection closed while in in-game edition - this is suspicious so hard reloading to re-establish a new connection.'
);
that.launchHardReload();
}, 1000);
}
};
this._ws.onerror = function errored(error) {
logger.warn('Debugger client error:', error);

View File

@@ -11,7 +11,13 @@ namespace gdjs {
constructor(runtimeGame: RuntimeGame) {
super(runtimeGame);
// Opener is either the `opener` for popups, or the `parent` if the game
// is running as an iframe (notably: in-game edition).
this._opener = window.opener || null;
if (!this._opener && window.parent !== window) {
this._opener = window.parent;
}
if (!this._opener) {
logger.info("`window.opener` not existing, the debugger won't work.");
return;

View File

@@ -207,7 +207,7 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const resource = this._loadedFontFamily.get(resourceData);
const resource = this._loadedFontFamily.getFromName(resourceData.name);
if (resource) {
this._loadedFontFamily.delete(resourceData);
}

View File

@@ -1157,12 +1157,12 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const musicRes = this._loadedMusics.get(resourceData);
const musicRes = this._loadedMusics.getFromName(resourceData.name);
if (musicRes) {
this.unloadAudio(resourceData.name, true);
}
const soundRes = this._loadedSounds.get(resourceData);
const soundRes = this._loadedSounds.getFromName(resourceData.name);
if (soundRes) {
this.unloadAudio(resourceData.name, false);
}

View File

@@ -33,24 +33,28 @@ namespace gdjs {
/**
* The cursor X position (moved by mouse and touch events).
*/
_cursorX: float = 0;
private _cursorX: float = 0;
/**
* The cursor Y position (moved by mouse and touch events).
*/
_cursorY: float = 0;
private _cursorY: float = 0;
/**
* The mouse X position (only moved by mouse events).
*/
_mouseX: float = 0;
private _mouseX: float = 0;
/**
* The mouse Y position (only moved by mouse events).
*/
_mouseY: float = 0;
_isMouseInsideCanvas: boolean = true;
_mouseWheelDelta: float = 0;
private _mouseY: float = 0;
private _isMouseInsideCanvas: boolean = true;
private _wheelDeltaX: float = 0;
private _wheelDeltaY: float = 0;
private _wheelDeltaZ: float = 0;
// TODO Remove _touches when there is no longer SpritePanelButton 1.2.0
// extension in the wild.
_touches = {
// @ts-ignore
private _touches = {
firstKey: (): string | number | null => {
for (const key in this._mouseOrTouches.items) {
// Exclude mouse key.
@@ -61,22 +65,23 @@ namespace gdjs {
return null;
},
};
_mouseOrTouches: Hashtable<Touch>;
private _mouseOrTouches: Hashtable<Touch>;
//Identifiers of the touches that started during/before the frame.
_startedTouches: Array<integer> = [];
private _startedTouches: Array<integer> = [];
//Identifiers of the touches that ended during/before the frame.
_endedTouches: Array<integer> = [];
_touchSimulateMouse: boolean = true;
private _endedTouches: Array<integer> = [];
private _touchSimulateMouse: boolean = true;
/**
* @deprecated
*/
_lastStartedTouchIndex = 0;
private _lastStartedTouchIndex = 0;
/**
* @deprecated
*/
_lastEndedTouchIndex = 0;
private _lastEndedTouchIndex = 0;
constructor() {
this._pressedKeys = new Hashtable();
@@ -96,7 +101,7 @@ namespace gdjs {
* @param keyCode The raw key code
* @param location The location
*/
_getLocationAwareKeyCode(
static getLocationAwareKeyCode(
keyCode: number,
location: number | null | undefined
): integer {
@@ -121,7 +126,7 @@ namespace gdjs {
* @param location The location of the event.
*/
onKeyPressed(keyCode: number, location?: number): void {
const locationAwareKeyCode = this._getLocationAwareKeyCode(
const locationAwareKeyCode = InputManager.getLocationAwareKeyCode(
keyCode,
location
);
@@ -138,7 +143,7 @@ namespace gdjs {
* @param location The location of the event.
*/
onKeyReleased(keyCode: number, location?: number): void {
const locationAwareKeyCode = this._getLocationAwareKeyCode(
const locationAwareKeyCode = InputManager.getLocationAwareKeyCode(
keyCode,
location
);
@@ -335,6 +340,19 @@ namespace gdjs {
}
}
/**
* Return true if any mouse button is pressed.
* @return true if any mouse button is pressed.
*/
anyMouseButtonPressed(): boolean {
for (const buttonCode in this._pressedMouseButtons) {
if (this._pressedMouseButtons[buttonCode]) {
return true;
}
}
return false;
}
_setMouseButtonPressed(buttonCode: number): void {
this._pressedMouseButtons[buttonCode] = true;
this._releasedMouseButtons[buttonCode] = false;
@@ -380,17 +398,37 @@ namespace gdjs {
/**
* Should be called whenever the mouse wheel is used
* @param wheelDelta The mouse wheel delta
* @param wheelDeltaY The mouse wheel delta
*/
onMouseWheel(wheelDelta: number): void {
this._mouseWheelDelta = wheelDelta;
onMouseWheel(
wheelDeltaY: number,
wheelDeltaX: number,
wheelDeltaZ: number
): void {
this._wheelDeltaY = wheelDeltaY;
if (wheelDeltaX !== undefined) this._wheelDeltaX = wheelDeltaX;
if (wheelDeltaZ !== undefined) this._wheelDeltaZ = wheelDeltaZ;
}
/**
* Return the mouse wheel delta
* Return the mouse wheel delta on Y axis.
*/
getMouseWheelDelta(): float {
return this._mouseWheelDelta;
return this._wheelDeltaY;
}
/**
* Return the mouse wheel delta on X axis.
*/
getMouseWheelDeltaX(): float {
return this._wheelDeltaX;
}
/**
* Return the mouse wheel delta on Z axis.
*/
getMouseWheelDeltaZ(): float {
return this._wheelDeltaZ;
}
/**
@@ -578,7 +616,9 @@ namespace gdjs {
this._releasedKeys.clear();
this._justPressedKeys.clear();
this._releasedMouseButtons.length = 0;
this._mouseWheelDelta = 0;
this._wheelDeltaX = 0;
this._wheelDeltaY = 0;
this._wheelDeltaZ = 0;
this._lastStartedTouchIndex = 0;
this._lastEndedTouchIndex = 0;
}

View File

@@ -210,12 +210,12 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedJson = this._loadedJsons.get(resourceData);
const loadedJson = this._loadedJsons.getFromName(resourceData.name);
if (loadedJson) {
this._loadedJsons.delete(resourceData);
}
const callback = this._callbacks.get(resourceData);
const callback = this._callbacks.getFromName(resourceData.name);
if (callback) {
this._callbacks.delete(resourceData);
}

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,178 @@
namespace gdjs {
const logger = new gdjs.Logger('LayerPixiRenderer');
const FRUSTUM_EDGES: Array<[number, number]> = [
// near plane edges
[0, 1],
[1, 2],
[2, 3],
[3, 0],
// far plane edges
[4, 5],
[5, 6],
[6, 7],
[7, 4],
// near↔far connections
[0, 4],
[1, 5],
[2, 6],
[3, 7],
];
/** Normalized Device Coordinates corners for near (-1) and far (+1) planes (Three.js NDC: z=-1 near, z=+1 far). */
const NDC_CORNERS: Array<Array<float>> = [
// near
[-1, -1, -1],
[+1, -1, -1],
[+1, +1, -1],
[-1, +1, -1],
// far
[-1, -1, +1],
[+1, -1, +1],
[+1, +1, +1],
[-1, +1, +1],
];
/** Sort convex polygon vertices around centroid to get consistent winding. */
const sortConvexPolygon = (points: THREE.Vector3[]): THREE.Vector3[] => {
if (points.length <= 2) return points;
const cx = points.reduce((s, p) => s + p.x, 0) / points.length;
const cy = points.reduce((s, p) => s + p.y, 0) / points.length;
return points
.map((p) => ({ p, a: Math.atan2(p.y - cy, p.x - cx) }))
.sort((u, v) => u.a - v.a)
.map((u) => u.p);
};
/**
* Intersect a frustum edge segment [a,b] with plane Z=0.
* Returns point or null if no intersection on the segment.
*/
const intersectSegmentWithZ0 = (
a: THREE.Vector3,
b: THREE.Vector3,
eps = 1e-9
): THREE.Vector3 | null => {
const az = a.z,
bz = b.z;
const dz = bz - az;
// If both z on same side and not on plane, no crossing.
if (Math.abs(dz) < eps) {
// Segment is (almost) parallel to plane.
if (Math.abs(az) < eps && Math.abs(bz) < eps) {
// Entire segment lies on plane: return endpoints (handled by caller via dedup).
// Here we return null and let caller add endpoints if needed.
return null;
}
return null;
}
// Solve a.z + t*(b.z - a.z) = 0 ⇒ t = -a.z / (b.z - a.z)
const t = -az / dz;
if (t < -eps || t > 1 + eps) {
// Intersection beyond the segment bounds.
return null;
}
const p = new THREE.Vector3(
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
0
);
return p;
};
/** Remove near-duplicate points. */
const dedupPoints = (
points: THREE.Vector3[],
eps = 1e-6
): THREE.Vector3[] => {
const out: THREE.Vector3[] = [];
for (const p of points) {
const exists = out.some(
(q) => Math.abs(p.x - q.x) < eps && Math.abs(p.y - q.y) < eps
);
if (!exists) out.push(p);
}
return out;
};
/**
* Compute the convex polygon of the camera frustum clipped by plane Z=0.
* Returns ordered vertices (world coords, z=0). Empty array if no intersection.
*/
const clipFrustumAgainstZ0 = (camera: THREE.Camera): THREE.Vector3[] => {
camera.updateMatrixWorld(true);
// Get the 8 corners of the camera frustum in world coordinates.
const corners = NDC_CORNERS.map((ndc) =>
new THREE.Vector3(ndc[0], ndc[1], ndc[2]).unproject(camera)
);
if (corners.length !== 8) return [];
const hits: THREE.Vector3[] = [];
// 1) Add vertices that already lie on the plane (z≈0).
for (const v of corners) {
if (Math.abs(v.z) < 1e-9) {
hits.push(new THREE.Vector3(v.x, v.y, 0));
}
}
// 2) Intersect each frustum edge with plane Z=0.
for (const [i, j] of FRUSTUM_EDGES) {
const a = corners[i],
b = corners[j];
const p = intersectSegmentWithZ0(a, b);
if (p) hits.push(p);
}
// Deduplicate and order.
const unique = dedupPoints(hits);
if (unique.length < 3) return [];
return sortConvexPolygon(unique);
};
/**
* Intersect the ray going through a normalized device coordinate (nx, ny)
* with the plane Z=0. Returns the hit point in THREE world coords (z=0)
* or null if the ray doesn't intersect the plane in front of the camera.
*/
const projectNDCToZ0 = (
camera: THREE.Camera,
nx: number,
ny: number
): THREE.Vector3 | null => {
if (!camera) return null;
camera.updateMatrixWorld(true);
const origin = new THREE.Vector3();
const dir = new THREE.Vector3();
const p = new THREE.Vector3(nx, ny, 0.5);
if (camera instanceof THREE.OrthographicCamera) {
// For ortho, unproject a point on the camera plane, and use forward dir.
p.z = 0; // on the camera plane
p.unproject(camera); // gives a point on the camera plane in world coords
origin.copy(p);
camera.getWorldDirection(dir);
} else {
// Perspective: unproject a point on the frustum plane, build a ray.
p.unproject(camera);
origin.copy(camera.position);
dir.copy(p).sub(origin).normalize();
}
const dz = dir.z;
if (Math.abs(dz) < 1e-8) return null; // parallel
const t = -origin.z / dz;
if (t <= 0) return null; // behind the camera => not visible
return origin.addScaledVector(dir, t).setZ(0);
};
/**
* The renderer for a gdjs.Layer using Pixi.js.
*/
@@ -47,6 +219,7 @@ namespace gdjs {
private _threePlaneGeometry: THREE.PlaneGeometry | null = null;
private _threePlaneMaterial: THREE.ShaderMaterial | null = null;
private _threePlaneMesh: THREE.Mesh | null = null;
private _threePlaneMeshDebugOutline: THREE.LineSegments | null = null;
/**
* Pixi doesn't sort children with zIndex == 0.
@@ -99,6 +272,9 @@ namespace gdjs {
// The layer is now fully initialized. Adapt the 3D camera position
// (which we could not do before in `_setup3DRendering`).
this._update3DCameraAspectAndPosition();
// Uncomment to show the outline of the 2D rendering plane.
// this.show2DRenderingPlaneDebugOutline(true);
}
onGameResolutionResized() {
@@ -134,6 +310,10 @@ namespace gdjs {
return this._threeScene;
}
getThreeGroup(): THREE.Group | null {
return this._threeGroup;
}
getThreeCamera():
| THREE.PerspectiveCamera
| THREE.OrthographicCamera
@@ -285,6 +465,10 @@ namespace gdjs {
'Tried to setup PixiJS plane for 2D rendering in 3D for a layer that is already set up.'
);
this.set2DPlaneMaxDrawingDistance(
this._layer.getInitialCamera2DPlaneMaxDrawingDistance()
);
// If we have both 2D and 3D objects to be rendered, create a render texture that PixiJS will use
// to render, and that will be projected on a plane by Three.js
this._createPixiRenderTexture(pixiRenderer);
@@ -388,30 +572,298 @@ namespace gdjs {
}
/**
* Update the position of the PIXI container. To be called after each change
* made to position, zoom or rotation of the camera.
* Enable or disable the drawing of an outline of the 2D rendering plane.
* Useful to visually see where the 2D rendering is done in the 3D world.
*/
updatePosition(): void {
const angle = -gdjs.toRad(this._layer.getCameraRotation());
const zoomFactor = this._layer.getCameraZoom();
this._pixiContainer.rotation = angle;
this._pixiContainer.scale.x = zoomFactor;
this._pixiContainer.scale.y = zoomFactor;
const cosValue = Math.cos(angle);
const sinValue = Math.sin(angle);
const centerX =
this._layer.getCameraX() * zoomFactor * cosValue -
this._layer.getCameraY() * zoomFactor * sinValue;
const centerY =
this._layer.getCameraX() * zoomFactor * sinValue +
this._layer.getCameraY() * zoomFactor * cosValue;
this._pixiContainer.position.x = this._layer.getWidth() / 2 - centerX;
this._pixiContainer.position.y = this._layer.getHeight() / 2 - centerY;
show2DRenderingPlaneDebugOutline(enable: boolean) {
if (!this._threePlaneMesh) return;
if (enable && !this._threePlaneMeshDebugOutline) {
// Add rectangle outline around the plane.
const edges = new THREE.EdgesGeometry(this._threePlaneGeometry);
const lineMaterial = new THREE.LineBasicMaterial({
color: 0xff0000,
});
this._threePlaneMeshDebugOutline = new THREE.LineSegments(
edges,
lineMaterial
);
// Attach the outline to the plane so it follows position/scale/rotation.
this._threePlaneMesh.add(this._threePlaneMeshDebugOutline);
}
if (!enable && this._threePlaneMeshDebugOutline) {
this._threePlaneMesh.remove(this._threePlaneMeshDebugOutline);
this._threePlaneMeshDebugOutline = null;
}
}
/** Maximum size of the 2D plane, in pixels. */
private _2DPlaneMaxDrawingDistance: number = 5000;
/** Tilt degrees below which the 2D plane is not clamped. */
private _2DPlaneClampFreeTiltDeg: number = 0.1;
/** Tilt degrees below which the 2D plane is fully clamped. */
private _2DPlaneClampHardTiltDeg: number = 6;
private _2DPlaneClampRampPower: number = 1.5; // 1 = linear, >1 = smoother
/**
* Set the maximum "drawing distance", in pixels, of the 2D when in the 3D world.
* This corresponds to the "height" of the 2D plane.
* Used when the 3D camera is tilted on the X or Y axis (instead of looking down the Z axis,
* as it's done by default for 2D games).
* This is useful to avoid the 2D plane being too big when the camera is tilted.
*/
set2DPlaneMaxDrawingDistance(h: number) {
this._2DPlaneMaxDrawingDistance = Math.max(0, h);
}
/**
* Set the tilt degrees below which the 2D plane is not clamped.
*/
set2DPlaneClampFreeTiltDegrees(d: number) {
this._2DPlaneClampFreeTiltDeg = Math.max(0, d);
}
/**
* Set the tilt degrees below which the 2D plane is clamped (see `set2DPlaneMaxDrawingDistance`).
*/
set2DPlaneClampHardTiltDegrees(d: number) {
this._2DPlaneClampHardTiltDeg = Math.max(0, d);
}
/**
* Set the ramp power of the 2D plane clamping (see `set2DPlaneMaxDrawingDistance`). Used
* for smoother transition between clamped and unclamped.
*/
set2DPlaneClampRampPower(p: number) {
this._2DPlaneClampRampPower = Math.max(0.1, p);
}
/**
* Get the size of the 2D plane, in the world coordinates.
*/
private _get2DPlaneSize(): [number, number] {
if (!this._threeCamera) return [0, 0];
// Compute the intersection of the frustrum of the camera on the Z=0 plane.
// In theory, that's where the entire 2D rendering should be displayed.
const poly = clipFrustumAgainstZ0(this._threeCamera);
if (poly.length === 0) {
// No intersection at all: Z=0 not in view.
return [0, 0];
}
// Compute the axis-aligned bounds on Z=0 (world units) of the polygon,
// so we can compute the size of the plane doing the 2D rendering.
let minX = Infinity,
maxX = -Infinity,
minY = Infinity,
maxY = -Infinity;
for (const p of poly) {
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.y < minY) minY = p.y;
if (p.y > maxY) maxY = p.y;
}
let boxW = Math.max(1e-8, maxX - minX);
let boxH = Math.max(1e-8, maxY - minY);
// Keep 2D layer aspect ratio (so texture isn't stretched).
const targetAspect = this._layer.getWidth() / this._layer.getHeight();
const boxAspect = boxW / boxH;
if (boxAspect < targetAspect) {
boxW = targetAspect * boxH;
} else {
boxH = boxW / targetAspect;
}
// Decide if we should cap based on camera tilt (X/Y) ---
const forward = new THREE.Vector3();
this._threeCamera.getWorldDirection(forward);
// |forward.z| ≈ 1 -> no tilt (look mostly perpendicular to Z=0).
// |forward.z| ≈ 0 -> grazing the horizon (strong tilt).
const freeCos = Math.cos(
THREE.MathUtils.degToRad(this._2DPlaneClampFreeTiltDeg)
);
const hardCos = Math.cos(
THREE.MathUtils.degToRad(this._2DPlaneClampHardTiltDeg)
);
const tiltCos = Math.abs(forward.z);
// Map tiltCos ∈ [hardCos, freeCos] to w ∈ [1, 0]
let w = 0;
if (tiltCos <= hardCos)
w = 1; // fully clamped
else if (tiltCos >= freeCos)
w = 0; // no clamp
else w = (freeCos - tiltCos) / (freeCos - hardCos);
// Ease it
w = Math.pow(w, this._2DPlaneClampRampPower);
// Interpolate Infinity→base via 1/w (bounded):
const BIG = 1e12; // “practically infinite”
const denom = Math.max(w, 1e-6);
const effectiveMaxH = Math.min(
BIG,
this._2DPlaneMaxDrawingDistance / denom
);
// Apply the max height.
if (effectiveMaxH < BIG) {
const clampedH = Math.max(1e-8, Math.min(boxH, effectiveMaxH));
if (clampedH !== boxH) {
boxH = clampedH;
boxW = targetAspect * boxH; // keep aspect
}
}
return [boxW, boxH];
}
private _get2DPlanePosition(boxH: number): [number, number] {
if (!this._threeCamera) return [0, 0];
// Choose the plane position (anchor to bottom of screen, heading-invariant) ---
const bottomLeft = projectNDCToZ0(this._threeCamera, -1, -1);
const bottomRight = projectNDCToZ0(this._threeCamera, +1, -1);
let cx: number, cy: number;
if (bottomLeft && bottomRight) {
// Midpoint of the bottom-of-screen segment on Z=0:
const mx = 0.5 * (bottomLeft.x + bottomRight.x);
const my = 0.5 * (bottomLeft.y + bottomRight.y);
// Tangent along the bottom line (unit):
let dx = bottomRight.x - bottomLeft.x;
let dy = bottomRight.y - bottomLeft.y;
const len = Math.hypot(dx, dy) || 1;
dx /= len;
dy /= len;
// Inward normal n = +90° rotation of d in XY plane:
// d = (dx, dy) -> n = (-dy, dx)
let nx = -dy;
let ny = dx;
// Ensure n points "into the screen":
const midIn = projectNDCToZ0(this._threeCamera, 0, -0.5);
if (midIn) {
const vx = midIn.x - mx;
const vy = midIn.y - my;
if (vx * nx + vy * ny < 0) {
nx = -nx;
ny = -ny;
}
}
// Place the plane so its bottom edge lies on the bottom-of-screen line:
cx = mx + nx * (boxH * 0.5);
cy = my + ny * (boxH * 0.5);
} else {
// Fallback to the camera center projected on Z=0 if bottom line not visible:
const centerRay = projectNDCToZ0(this._threeCamera, 0, 0);
if (centerRay) {
cx = centerRay.x;
cy = centerRay.y;
} else {
// Fallback to the camera position if the center ray is not visible:
cx = this._threeCamera.position.x;
cy = this._threeCamera.position.y;
}
}
return [cx, cy];
}
updatePosition(): void {
// Update the 3D camera position and rotation.
if (this._threeCamera) {
const angle = -gdjs.toRad(this._layer.getCameraRotation());
this._threeCamera.position.x = this._layer.getCameraX();
this._threeCamera.position.y = -this._layer.getCameraY(); // scene is mirrored on Y
this._threeCamera.rotation.z = angle;
if (this._threeCamera instanceof THREE.OrthographicCamera) {
this._threeCamera.zoom = this._layer.getCameraZoom();
this._threeCamera.updateProjectionMatrix();
this._threeCamera.position.z = this._layer.getCameraZ(null);
} else {
this._threeCamera.position.z = this._layer.getCameraZ(
this._threeCamera.fov
);
}
}
let effectivePixiZoom = 1;
const angle = -gdjs.toRad(this._layer.getCameraRotation());
const angleCosValue = Math.cos(angle);
const angleSinValue = Math.sin(angle);
// Update the 2D plane in the 3D world position, size and rotation,
// and update the 2D Pixi container position, size and rotation.
if (this._threeCamera && this._threePlaneMesh) {
const [boxW, boxH] = this._get2DPlaneSize();
if (boxW === 0 || boxH === 0) {
// No size means the 2D plane is not visible.
this._threePlaneMesh.visible = false;
} else {
this._threePlaneMesh.visible = true;
const [cx, cy] = this._get2DPlanePosition(boxH);
// Update the 2D plane size, position and rotation (so 2D remains upright).
// Plane size (geometry is 1×1).
this._threePlaneMesh.scale.set(boxW, boxH, 1);
this._threePlaneMesh.position.set(cx, -cy, 0);
this._threePlaneMesh.rotation.set(0, 0, -angle);
// Update the 2D Pixi container size and rotation to match the "zoom" (which comes from the 2D plane size)
// rotation and position.
effectivePixiZoom = this._layer.getWidth() / boxW; // == height/boxH
this._pixiContainer.scale.set(effectivePixiZoom, effectivePixiZoom);
this._pixiContainer.rotation = angle;
const followX = cx;
const followY = -cy;
const centerX2d =
followX * effectivePixiZoom * angleCosValue -
followY * effectivePixiZoom * angleSinValue;
const centerY2d =
followX * effectivePixiZoom * angleSinValue +
followY * effectivePixiZoom * angleCosValue;
this._pixiContainer.position.x =
this._layer.getWidth() / 2 - centerX2d;
this._pixiContainer.position.y =
this._layer.getHeight() / 2 - centerY2d;
}
}
// 2D only (no 3D rendering and so no 2D plane in the 3D world):
// Update the 2D Pixi container position, size and rotation.
if (!this._threeCamera || !this._threePlaneMesh) {
effectivePixiZoom = this._layer.getCameraZoom();
this._pixiContainer.rotation = angle;
this._pixiContainer.scale.x = effectivePixiZoom;
this._pixiContainer.scale.y = effectivePixiZoom;
const centerX =
this._layer.getCameraX() * effectivePixiZoom * angleCosValue -
this._layer.getCameraY() * effectivePixiZoom * angleSinValue;
const centerY =
this._layer.getCameraX() * effectivePixiZoom * angleSinValue +
this._layer.getCameraY() * effectivePixiZoom * angleCosValue;
this._pixiContainer.position.x = this._layer.getWidth() / 2 - centerX;
this._pixiContainer.position.y = this._layer.getHeight() / 2 - centerY;
}
// Pixel rounding for the Pixi rendering (be it for 2D only
// or for the 2D rendering shown in the 2D plane in the 3D world).
if (
this._layer.getRuntimeScene().getGame().getPixelsRounding() &&
(cosValue === 0 || sinValue === 0) &&
Number.isInteger(zoomFactor)
(angleCosValue === 0 || angleSinValue === 0) &&
Number.isInteger(effectivePixiZoom)
) {
// Camera rounding is important for pixel perfect games.
// Otherwise, the camera position fractional part is added to
@@ -467,39 +919,12 @@ namespace gdjs {
);
}
}
if (this._threeCamera) {
// TODO (3D) - improvement: handle camera rounding like down for PixiJS?
this._threeCamera.position.x = this._layer.getCameraX();
this._threeCamera.position.y = -this._layer.getCameraY(); // Inverted because the scene is mirrored on Y axis.
this._threeCamera.rotation.z = angle;
if (this._threeCamera instanceof THREE.OrthographicCamera) {
this._threeCamera.zoom = this._layer.getCameraZoom();
this._threeCamera.updateProjectionMatrix();
this._threeCamera.position.z = this._layer.getCameraZ(null);
} else {
this._threeCamera.position.z = this._layer.getCameraZ(
this._threeCamera.fov
);
}
if (this._threePlaneMesh) {
// Adapt the plane size so that it covers the whole screen.
this._threePlaneMesh.scale.x = this._layer.getWidth() / zoomFactor;
this._threePlaneMesh.scale.y = this._layer.getHeight() / zoomFactor;
// Adapt the plane position so that it's always displayed on the whole screen.
this._threePlaneMesh.position.x = this._threeCamera.position.x;
this._threePlaneMesh.position.y = -this._threeCamera.position.y; // Inverted because the scene is mirrored on Y axis.
this._threePlaneMesh.rotation.z = -angle;
}
}
}
updateResolution() {
if (this._threeEffectComposer) {
const game = this._layer.getRuntimeScene().getGame();
this._threeEffectComposer.setPixelRatio(window.devicePixelRatio);
this._threeEffectComposer.setSize(
game.getGameResolutionWidth(),
game.getGameResolutionHeight()

View File

@@ -309,7 +309,7 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedFont = this._loadedFontsData.get(resourceData);
const loadedFont = this._loadedFontsData.getFromName(resourceData.name);
if (loadedFont) {
this._loadedFontsData.delete(resourceData);
}

View File

@@ -103,6 +103,10 @@ namespace gdjs {
if (!existingTexture) {
return this._invalidTexture;
}
if (existingTexture.destroyed) {
logger.error('Texture for ' + resourceName + ' is not valid anymore.');
return this._invalidTexture;
}
if (!existingTexture.valid) {
logger.error(
'Texture for ' +

View File

@@ -101,6 +101,7 @@ namespace gdjs {
this._threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
this._threeRenderer.useLegacyLights = true;
this._threeRenderer.autoClear = false;
this._threeRenderer.pixelRatio = window.devicePixelRatio;
this._threeRenderer.setSize(
this._game.getGameResolutionWidth(),
this._game.getGameResolutionHeight()
@@ -761,7 +762,7 @@ namespace gdjs {
};
// @ts-ignore
canvas.onwheel = function (event) {
manager.onMouseWheel(-event.deltaY);
manager.onMouseWheel(-event.deltaY, event.deltaX, event.deltaZ);
};
// Touches:
@@ -784,6 +785,7 @@ namespace gdjs {
touch.pageY
);
manager.onTouchMove(touch.identifier, pos[0], pos[1]);
manager.onTouchMove(touch.identifier, pos[0], pos[1]);
// This works because touch events are sent
// when they continue outside of the canvas.
if (manager.isSimulatingMouseWithTouch()) {

View File

@@ -115,9 +115,10 @@ namespace gdjs {
const runtimeLayerRenderingType = runtimeLayer.getRenderingType();
const layerHas3DObjectsToRender = runtimeLayerRenderer.has3DObjects();
if (
runtimeLayerRenderingType ===
!this._runtimeScene.getGame().isInGameEdition() &&
(runtimeLayerRenderingType ===
gdjs.RuntimeLayerRenderingType.TWO_D ||
!layerHas3DObjectsToRender
!layerHas3DObjectsToRender)
) {
// Render a layer with 2D rendering (PixiJS) only if layer is configured as is
// or if there is no 3D object to render.

View File

@@ -41,18 +41,71 @@ namespace gdjs {
return supportedCompressionMethods;
};
/**
* The desired status of the game, used for previews or in-game edition.
* Either stored in the options generated by the preview or in the URL
* in case of a hard reload.
*/
export type RuntimeGameStatus = {
isPaused: boolean;
isInGameEdition: boolean;
sceneName: string | null;
injectedExternalLayoutName: string | null;
skipCreatingInstancesFromScene: boolean;
eventsBasedObjectType: string | null;
eventsBasedObjectVariantName: string | null;
editorId: string | null;
editorCamera3D?: EditorCameraState;
};
/**
* Read the desired status of the game from the URL. Only useful for previews
* when hard reloaded.
*/
const readRuntimeGameStatusFromUrl = (): RuntimeGameStatus | null => {
try {
const url = new URL(location.href);
const runtimeGameStatus = url.searchParams.get('runtimeGameStatus');
if (!runtimeGameStatus) return null;
const parsedRuntimeGameStatus = JSON.parse(runtimeGameStatus);
return {
isPaused: !!parsedRuntimeGameStatus.isPaused,
isInGameEdition: !!parsedRuntimeGameStatus.isInGameEdition,
sceneName: '' + parsedRuntimeGameStatus.sceneName,
injectedExternalLayoutName:
'' + parsedRuntimeGameStatus.injectedExternalLayoutName,
skipCreatingInstancesFromScene:
!!parsedRuntimeGameStatus.skipCreatingInstancesFromScene,
eventsBasedObjectType: parsedRuntimeGameStatus.eventsBasedObjectType,
eventsBasedObjectVariantName:
parsedRuntimeGameStatus.eventsBasedObjectVariantName,
editorId: parsedRuntimeGameStatus.editorId,
editorCamera3D: parsedRuntimeGameStatus.editorCamera3D,
};
} catch (e) {
return null;
}
};
/** Options given to the game at startup. */
export type RuntimeGameOptions = {
/** if true, force fullscreen. */
forceFullscreen?: boolean;
/** if true, game is run as a preview launched from an editor. */
isPreview?: boolean;
/** The name of the external layout to create in the scene at position 0;0. */
injectExternalLayout?: string;
/** if set, the status of the game to be restored. */
initialRuntimeGameStatus?: RuntimeGameStatus;
/** Script files, used for hot-reloading. */
scriptFiles?: Array<RuntimeGameOptionsScriptFile>;
/** if true, export is a partial preview without events. */
projectDataOnlyExport?: boolean;
/** if true, export is a partial preview without reloading libraries. */
shouldReloadLibraries?: boolean;
/** if true, export is a partial preview without generating events. */
shouldGenerateScenesEventsCode?: boolean;
/** if true, preview is launched from GDevelop native mobile app. */
nativeMobileApp?: boolean;
/** The address of the debugger server, to reach out using WebSocket. */
@@ -139,7 +192,13 @@ namespace gdjs {
_gameResolutionHeight: integer;
_originalWidth: float;
_originalHeight: float;
_resizeMode: 'adaptWidth' | 'adaptHeight' | string;
_resizeMode:
| ''
| 'scaleOuter'
| 'adaptWidth'
| 'adaptHeight'
| 'native'
| string;
_adaptGameResolutionAtRuntime: boolean;
_scaleMode: 'linear' | 'nearest';
_pixelsRounding: boolean;
@@ -171,12 +230,8 @@ namespace gdjs {
_hasJustResumed: boolean = false;
//Inputs :
_inputManager: InputManager;
private _inputManager: InputManager;
/**
* Allow to specify an external layout to insert in the first scene.
*/
_injectExternalLayout: any;
_options: RuntimeGameOptions;
/**
@@ -194,6 +249,7 @@ namespace gdjs {
_sessionMetricsInitialized: boolean = false;
_disableMetrics: boolean = false;
_isPreview: boolean;
_isInGameEdition: boolean;
/**
* The capture manager, used to manage captures (screenshots, videos, etc...).
@@ -203,12 +259,27 @@ namespace gdjs {
/** True if the RuntimeGame has been disposed and should not be used anymore. */
_wasDisposed: boolean = false;
_inGameEditor: InGameEditor | null;
/**
* @param data The object (usually stored in data.json) containing the full project data
* @param options The game options
*/
constructor(data: ProjectData, options?: RuntimeGameOptions) {
this._options = options || {};
this._isPreview = this._options.isPreview || false;
if (this._isPreview) {
// Check if we need to restore the state from the URL, which is used
// when a preview is hard reloaded (search for `hardReload`).
const runtimeGameStatusFromUrl = readRuntimeGameStatusFromUrl();
if (runtimeGameStatusFromUrl) {
this._options.initialRuntimeGameStatus = runtimeGameStatusFromUrl;
}
}
this._isInGameEdition =
this._options.initialRuntimeGameStatus?.isInGameEdition || false;
this._variables = new gdjs.VariablesContainer(data.variables);
this._variablesByExtensionName = new Map<
string,
@@ -237,7 +308,12 @@ namespace gdjs {
getGlobalResourceNames(data),
data.layouts
);
this._inGameEditor = this._isInGameEdition
? new gdjs.InGameEditor(this, data)
: null;
this._debuggerClient = gdjs.DebuggerClient
? new gdjs.DebuggerClient(this)
: null;
this._effectsManager = new gdjs.EffectsManager();
this._maxFPS = this._data.properties.maxFPS;
this._minFPS = this._data.properties.minFPS;
@@ -265,17 +341,12 @@ namespace gdjs {
);
this._sceneStack = new gdjs.SceneStack(this);
this._inputManager = new gdjs.InputManager();
this._injectExternalLayout = this._options.injectExternalLayout || '';
this._debuggerClient = gdjs.DebuggerClient
? new gdjs.DebuggerClient(this)
: null;
this._captureManager = gdjs.CaptureManager
? new gdjs.CaptureManager(
this._renderer,
this._options.captureOptions || {}
)
: null;
this._isPreview = this._options.isPreview || false;
this._sessionId = null;
this._playerId = null;
@@ -311,6 +382,9 @@ namespace gdjs {
* @param projectData The object (usually stored in data.json) containing the full project data
*/
setProjectData(projectData: ProjectData): void {
if (this._inGameEditor) {
this._inGameEditor.onProjectDataChange(projectData);
}
this._data = projectData;
this._updateSceneAndExtensionsData();
this._resourcesLoader.setResources(
@@ -485,6 +559,55 @@ namespace gdjs {
return eventsBasedObjectData;
}
getEventsBasedObjectVariantData(
type: string,
variantName: string
): EventsBasedObjectVariantData | null {
const eventsBasedObjectData = this.getEventsBasedObjectData(type);
if (!eventsBasedObjectData) {
return null;
}
return gdjs.RuntimeGame._getEventsBasedObjectVariantData(
eventsBasedObjectData,
variantName
);
}
static _getEventsBasedObjectVariantData(
eventsBasedObjectData: EventsBasedObjectData,
variantName: string
): EventsBasedObjectVariantData {
if (!eventsBasedObjectData.defaultVariant) {
eventsBasedObjectData.defaultVariant = {
...eventsBasedObjectData,
name: '',
};
}
// Legacy events-based objects don't have any instance in their default
// variant since there wasn't a graphical editor at the time.
// In this case, the editor doesn't allow to choose a variant, but a
// variant may have stayed after a user rolled back the extension.
// This variant must be ignored to match what the editor shows.
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
eventsBasedObjectData.defaultVariant.instances.length == 0;
if (isForcedToOverrideEventsBasedObjectChildrenConfiguration) {
return eventsBasedObjectData.defaultVariant;
}
let usedVariantData: EventsBasedObjectVariantData =
eventsBasedObjectData.defaultVariant;
for (
let variantIndex = 0;
variantIndex < eventsBasedObjectData.variants.length;
variantIndex++
) {
const variantData = eventsBasedObjectData.variants[variantIndex];
if (variantData.name === variantName) {
usedVariantData = variantData;
}
}
return usedVariantData;
}
/**
* Get the data associated to a scene.
*
@@ -523,6 +646,22 @@ namespace gdjs {
return false;
}
/**
* Get the data associated to a scene.
*
* @param name The name of the scene.
* @return The data associated to the scene or null if not found.
*/
getSceneData(sceneName: string): LayoutData | null {
for (let i = 0, len = this._data.layouts.length; i < len; ++i) {
const sceneData = this._data.layouts[i];
if (sceneData.name == sceneName) {
return sceneData;
}
}
return null;
}
/**
* Get the data associated to an external layout.
*
@@ -594,7 +733,7 @@ namespace gdjs {
this._gameResolutionWidth = width;
this._gameResolutionHeight = height;
if (this._adaptGameResolutionAtRuntime) {
if (this._adaptGameResolutionAtRuntime || this._isInGameEdition) {
if (
gdjs.RuntimeGameRenderer &&
gdjs.RuntimeGameRenderer.getWindowInnerWidth &&
@@ -606,7 +745,10 @@ namespace gdjs {
gdjs.RuntimeGameRenderer.getWindowInnerHeight();
// Enlarge either the width or the eight to fill the inner window space.
if (this._resizeMode === 'adaptWidth') {
if (this._isInGameEdition) {
this._gameResolutionWidth = windowInnerWidth;
this._gameResolutionHeight = windowInnerHeight;
} else if (this._resizeMode === 'adaptWidth') {
this._gameResolutionWidth =
(this._gameResolutionHeight * windowInnerWidth) /
windowInnerHeight;
@@ -735,9 +877,9 @@ namespace gdjs {
if (this._paused === enable) return;
this._paused = enable;
if (this._inGameEditor) this._inGameEditor.activate(enable);
if (this._debuggerClient) {
if (this._paused) this._debuggerClient.sendGamePaused();
else this._debuggerClient.sendGameResumed();
this._debuggerClient.sendRuntimeGameStatus();
}
}
@@ -914,11 +1056,16 @@ namespace gdjs {
await loadAssets(onProgress);
await loadingScreen.unload();
this.pause(false);
if (!this._isInGameEdition) {
this.pause(false);
}
}
private _getFirstSceneName(): string {
const firstSceneName = this._data.firstLayout;
const firstSceneName =
this._options.initialRuntimeGameStatus?.sceneName ||
this._data.firstLayout;
return this.hasScene(firstSceneName)
? firstSceneName
: // There is always at least a scene
@@ -938,10 +1085,41 @@ namespace gdjs {
this._forceGameResolutionUpdate();
// Load the first scene
this._sceneStack.push(
this._getFirstSceneName(),
this._injectExternalLayout
);
const sceneName = this._getFirstSceneName();
const externalLayoutName =
this._options.initialRuntimeGameStatus?.injectedExternalLayoutName ||
null;
if (this._inGameEditor) {
const eventsBasedObjectType =
this._options.initialRuntimeGameStatus?.eventsBasedObjectType ||
null;
const eventsBasedObjectVariantName =
this._options.initialRuntimeGameStatus
?.eventsBasedObjectVariantName || null;
const editorId =
this._options.initialRuntimeGameStatus?.editorId || null;
const editorCamera3D =
this._options.initialRuntimeGameStatus?.editorCamera3D || null;
this._inGameEditor.switchToSceneOrVariant(
editorId,
sceneName,
externalLayoutName,
eventsBasedObjectType,
eventsBasedObjectVariantName,
editorCamera3D
);
} else {
if (sceneName) {
this.getSceneStack().replace({
sceneName,
externalLayoutName:
externalLayoutName === null ? undefined : externalLayoutName,
skipCreatingInstancesFromScene: !!externalLayoutName,
clear: true,
});
}
}
this._watermark.displayAtStartup();
//Uncomment to profile the first x frames of the game.
@@ -967,15 +1145,33 @@ namespace gdjs {
}
// The standard game loop
let lastFrameSceneName: string | null = null;
let accumulatedElapsedTime = 0;
this._hasJustResumed = false;
this._renderer.startGameLoop((lastCallElapsedTime) => {
try {
if (this._paused) {
return true;
// Watch the scene name to automatically update debugger when a scene is changed.
if (this._debuggerClient) {
const currentScene = (
this._inGameEditor || this.getSceneStack()
).getCurrentScene();
if (
currentScene &&
currentScene.getName() !== lastFrameSceneName
) {
lastFrameSceneName = currentScene.getName();
this._debuggerClient.sendRuntimeGameStatus();
}
}
// Skip the frame if we rendering frames too fast
// If the game is edited, update the target framerate according to interactions.
// Do it now (before frame skip), so that if a user interaction happens
// we don't wait for a frame to pass at the current, probably very slow framerate.
if (this._paused && this._inGameEditor) {
this._inGameEditor.updateTargetFramerate(lastCallElapsedTime);
}
// Skip the frame if we rendering frames too fast.
accumulatedElapsedTime += lastCallElapsedTime;
if (
this._maxFPS > 0 &&
@@ -992,17 +1188,36 @@ namespace gdjs {
// Manage resize events.
if (this._notifyScenesForGameResolutionResize) {
this._sceneStack.onGameResolutionResized();
if (this._inGameEditor) {
this._inGameEditor.onGameResolutionResized();
} else {
this._sceneStack.onGameResolutionResized();
}
this._notifyScenesForGameResolutionResize = false;
}
// Render and step the scene.
if (this._sceneStack.step(elapsedTime)) {
this.getInputManager().onFrameEnded();
// Render and possibly step the game.
if (this._paused) {
if (this._inGameEditor) {
// The game is paused for edition: the in-game editor runs and render
// the scene.
this._inGameEditor.updateAndRender();
} else {
// The game is paused (for debugging): the rendering of the scene is done,
// but the game logic is not executed (no full "step").
this._sceneStack.renderWithoutStep();
}
} else {
// The game is not paused (and so, not edited): both the rendering
// and game logic (a full "step") is executed.
if (!this._sceneStack.step(elapsedTime)) {
return false; // Return if game asked to be stopped.
}
this._hasJustResumed = false;
return true;
}
return false;
this.getInputManager().onFrameEnded();
return true;
} catch (e) {
if (this._debuggerClient)
this._debuggerClient.onUncaughtException(e);
@@ -1323,6 +1538,37 @@ namespace gdjs {
return this._isPreview;
}
/**
* Check if the game loop is paused, for debugging/edition purposes.
* @returns true if the current game is paused
*/
isPaused(): boolean {
return this._paused;
}
/**
* Check if the game should display in-game edition tools or not.
* @returns true if the current game is being edited.
*/
isInGameEdition(): boolean {
return this._isInGameEdition;
}
/**
* Return in-game editor.
*/
getInGameEditor(): InGameEditor | null {
return this._inGameEditor;
}
/**
* Set the maximum FPS of the game.
* @param maximumFps The maximum FPS.
*/
setMaximumFps(maximumFps: integer) {
this._maxFPS = maximumFps;
}
/**
* Check if the game should call GDevelop development APIs or not.
*

View File

@@ -1437,6 +1437,22 @@ namespace gdjs {
return this.hidden;
}
/**
* Return the width of the object before any custom size is applied.
* @return The width of the object
*/
getOriginalWidth(): float {
return this.getWidth();
}
/**
* Return the width of the object before any custom size is applied.
* @return The width of the object
*/
getOriginalHeight(): float {
return this.getHeight();
}
/**
* Set the width of the object, if applicable.
* @param width The new width in pixels.

View File

@@ -128,7 +128,8 @@ namespace gdjs {
/**
* Load the runtime scene from the given scene.
* @param sceneAndExtensionsData An object containing the scene data.
* @param sceneAndExtensionsData The data of the scene and extension variables to be loaded.
* @param options Options to change what is loaded.
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
*/
loadFromScene(
@@ -195,8 +196,8 @@ namespace gdjs {
this.registerObject(sceneData.objects[i]);
}
// Create initial instances of objects
if (!options || !options.skipCreatingInstances)
//Create initial instances of objects
if (!options || !options.skipCreatingInstances) {
this.createObjectsFrom(
sceneData.instances,
0,
@@ -205,6 +206,7 @@ namespace gdjs {
/*trackByPersistentUuid=*/
true
);
}
// Set up the default z order (for objects created from events)
this._setLayerDefaultZOrders();
@@ -377,7 +379,7 @@ namespace gdjs {
}
/**
* Step and render the scene.
* Step (execute the game logic) and render the scene.
* @param elapsedTime In milliseconds
* @return true if the game loop should continue, false if a scene change/push/pop
* or a game stop was requested.
@@ -437,6 +439,21 @@ namespace gdjs {
if (this._profiler) {
this._profiler.end('callbacks and extensions (post-events)');
}
this.render();
this._isJustResumed = false;
if (this._profiler) {
this._profiler.end('render');
}
if (this._profiler) {
this._profiler.endFrame();
}
return !!this.getRequestedChange();
}
/**
* Render the scene (but do not execute the game logic).
*/
render() {
if (this._profiler) {
this._profiler.begin('objects (pre-render, effects update)');
}
@@ -466,21 +483,6 @@ namespace gdjs {
);
}
this._isJustResumed = false;
this.render();
if (this._profiler) {
this._profiler.end('render');
}
if (this._profiler) {
this._profiler.endFrame();
}
return !!this.getRequestedChange();
}
/**
* Render the PIXI container associated to the runtimeScene.
*/
render() {
this._renderer.render();
}

View File

@@ -93,10 +93,10 @@ namespace gdjs {
renderWithoutStep(): boolean {
this._throwIfDisposed();
if (this._stack.length === 0) {
const currentScene = this.getCurrentScene();
if (!currentScene) {
return false;
}
const currentScene = this._stack[this._stack.length - 1];
currentScene.render();
return true;
}

View File

@@ -190,15 +190,13 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
}
/**
@@ -853,6 +851,14 @@ namespace gdjs {
this.setHeight(newHeight);
}
override getOriginalWidth(): float {
return this._renderer.getUnscaledWidth() * this._preScale;
}
override getOriginalHeight(): float {
return this._renderer.getUnscaledHeight() * this._preScale;
}
/**
* Change the scale on X and Y axis of the object.
*

View File

@@ -2,7 +2,10 @@ import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils';
import { TransformControls } from 'three/examples/jsm/controls/TransformControls';
import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
import { Pass } from 'three/examples/jsm/postprocessing/Pass';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
@@ -21,7 +24,10 @@ declare global {
GLTF,
DRACOLoader,
SkeletonUtils,
TransformControls,
SelectionBox,
EffectComposer,
OutlinePass,
Pass,
RenderPass,
ShaderPass,

View File

@@ -18,6 +18,7 @@ declare interface ProjectData {
layouts: LayoutData[];
externalLayouts: ExternalLayoutData[];
eventsFunctionsExtensions: EventsFunctionsExtensionData[];
areEffectsHiddenInEditor?: boolean;
}
declare interface EventsFunctionsVariablesData {
@@ -288,6 +289,21 @@ declare interface LayoutData extends InstanceContainerData {
usedResources: ResourceReference[];
resourcesPreloading?: 'at-startup' | 'never' | 'inherit';
resourcesUnloading?: 'at-scene-exit' | 'never' | 'inherit';
uiSettings: InstancesEditorSettings;
}
declare interface InstancesEditorSettings {
grid: boolean;
gridType: 'rectangular' | 'isometric';
gridWidth: float;
gridHeight: float;
gridDepth?: float;
gridOffsetX: float;
gridOffsetY: float;
gridOffsetZ?: float;
gridColor: int;
gridAlpha: float;
snap: boolean;
}
declare interface LayoutNetworkSyncData {
@@ -383,6 +399,8 @@ declare interface EventsBasedObjectVariantData extends InstanceContainerData {
instances: InstanceData[];
objects: ObjectData[];
layers: LayerData[];
usedResources: ResourceReference[];
editionSettings: InstancesEditorSettings;
}
declare interface BehaviorSharedData {
@@ -394,13 +412,17 @@ declare interface ExternalLayoutData {
name: string;
associatedLayout: string;
instances: InstanceData[];
editionSettings: InstancesEditorSettings;
}
declare interface InstanceData {
declare interface InstancePersistentUuidData {
persistentUuid: string;
}
declare interface InstanceData extends InstancePersistentUuidData {
layer: string;
locked: boolean;
locked?: boolean;
sealed?: boolean;
name: string;
x: number;
@@ -451,8 +473,10 @@ declare interface LayerData {
camera3DFieldOfView?: float;
camera3DFarPlaneDistance?: float;
camera3DNearPlaneDistance?: float;
camera2DPlaneMaxDrawingDistance?: float;
isLightingLayer: boolean;
followBaseLayerCamera: boolean;
isLocked?: boolean;
}
declare interface CameraData {
@@ -497,7 +521,7 @@ declare interface ProjectPropertiesData {
pixelsRounding: boolean;
antialiasingMode: 'none' | 'MSAA';
antialisingEnabledOnMobile: boolean;
sizeOnStartupMode: string;
sizeOnStartupMode: '' | 'scaleOuter' | 'adaptWidth' | 'adaptHeight';
version: string;
name: string;
author: string;

View File

@@ -19,6 +19,7 @@ const allowedExtensions = [
'.map',
'.wasm',
'.txt',
'.png',
];
// These extensions will be built with esbuild (the other will be copied).
@@ -42,6 +43,7 @@ const untransformedPaths = [
'GDJS/Runtime/FacebookInstantGames',
'GDJS/Runtime/libs/CocoonJS',
'GDJS/Runtime/libs/rbush.js',
'GDJS/Runtime/InGameEditor/Resources/primitivedrawingicon.png',
// Extensions pre-built files:
'Extensions/Leaderboards/sha256.js',

View File

@@ -77,6 +77,19 @@ gdjs.getPixiRuntimeGameWithAssets = () => {
title: '',
variables: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
}
},
],
externalLayouts: [],
@@ -210,6 +223,7 @@ gdjs.getPixiRuntimeGameWithAssets = () => {
areaMaxY: 64,
areaMaxZ: 0,
_initialInnerArea: null,
variants: [],
},
],
sceneVariables: [],

View File

@@ -22,6 +22,19 @@ describe('gdjs.ResourceLoader', () => {
title: '',
variables: [],
usedResources,
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
}
};
};

View File

@@ -107,6 +107,19 @@ describe('gdjs.EffectsManager', () => {
objects: [],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
}
}, usedExtensionsWithVariablesData: []});
const runtimeLayer = runtimeScene.getLayer('');

View File

@@ -185,6 +185,19 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
? instances.map((instance) => ({ ...defaultInstance, ...instance }))
: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
}
};
};
@@ -217,6 +230,20 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
_initialInnerArea: null,
isInnerAreaFollowingParentSize: false,
variants: [],
usedResources: [],
editionSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
}
};
};

View File

@@ -47,6 +47,19 @@ describe('gdjs.RuntimeScene integration tests', function () {
],
instances: [],
usedResources: [],
uiSettings: {
grid: false,
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
}
}, usedExtensionsWithVariablesData: []});
const object = runtimeScene.createObject('Object1');

View File

@@ -21,6 +21,20 @@ describe('gdjs.SceneStack', () => {
title: '',
variables: [],
usedResources,
uiSettings: {
grid: false,
/** @type {"rectangular" | "isometric"} */
gridType: 'rectangular',
gridWidth: 10,
gridHeight: 10,
gridDepth: 10,
gridOffsetX: 0,
gridOffsetY: 0,
gridOffsetZ: 0,
gridColor: 0,
gridAlpha: 1,
snap: false,
},
};
};

View File

@@ -602,6 +602,8 @@ interface Project {
boolean IsFolderProject();
void SetUseDeprecatedZeroAsDefaultZOrder(boolean enable);
boolean GetUseDeprecatedZeroAsDefaultZOrder();
boolean AreEffectsHiddenInEditor();
void SetEffectsHiddenInEditor(boolean enable);
void SetLastCompilationDirectory([Const] DOMString path);
[Const, Ref] DOMString GetLastCompilationDirectory();
@@ -1119,6 +1121,8 @@ interface Layer {
void SetCamera3DFarPlaneDistance(double distance);
double GetCamera3DFieldOfView();
void SetCamera3DFieldOfView(double angle);
double GetCamera2DPlaneMaxDrawingDistance();
void SetCamera2DPlaneMaxDrawingDistance(double distance);
void SetAmbientLightColor(unsigned long r, unsigned long g, unsigned long b);
unsigned long GetAmbientLightColorRed();
@@ -1433,6 +1437,13 @@ interface InitialInstance {
void SetCustomDepth(double depth);
double GetCustomDepth();
double GetDefaultWidth();
double GetDefaultHeight();
double GetDefaultDepth();
void SetDefaultWidth(double width);
void SetDefaultHeight(double height);
void SetDefaultDepth(double depth);
[Ref] InitialInstance ResetPersistentUuid();
[Const, Ref] DOMString GetPersistentUuid();
@@ -2876,6 +2887,10 @@ interface UsedExtensionsFinder {
[Value] UsedExtensionsResult STATIC_ScanProject([Ref] Project project);
};
interface UsedObjectTypeFinder {
boolean STATIC_ScanProject([Ref] Project project, [Const] DOMString objectType);
};
interface ExampleExtensionUsagesFinder {
[Value] SetString STATIC_GetUsedExtensions([Ref] Project project);
};
@@ -4005,11 +4020,26 @@ interface PreviewExportOptions {
[Ref] PreviewExportOptions SetFallbackAuthor([Const] DOMString id, [Const] DOMString username);
[Ref] PreviewExportOptions SetAuthenticatedPlayer([Const] DOMString playerId, [Const] DOMString playerUsername, [Const] DOMString playerToken);
[Ref] PreviewExportOptions SetExternalLayoutName([Const] DOMString externalLayoutName);
[Ref] PreviewExportOptions SetEventsBasedObjectType([Const] DOMString eventsBasedObjectType);
[Ref] PreviewExportOptions SetEventsBasedObjectVariantName([Const] DOMString eventsBasedObjectVariantName);
[Ref] PreviewExportOptions SetIncludeFileHash([Const] DOMString includeFile, long hash);
[Ref] PreviewExportOptions SetProjectDataOnlyExport(boolean enable);
[Ref] PreviewExportOptions SetShouldClearExportFolder(boolean enable);
[Ref] PreviewExportOptions SetShouldReloadProjectData(boolean enable);
[Ref] PreviewExportOptions SetShouldReloadLibraries(boolean enable);
[Ref] PreviewExportOptions SetShouldGenerateScenesEventsCode(boolean enable);
[Ref] PreviewExportOptions SetNativeMobileApp(boolean enable);
[Ref] PreviewExportOptions SetFullLoadingScreen(boolean enable);
[Ref] PreviewExportOptions SetIsDevelopmentEnvironment(boolean enable);
[Ref] PreviewExportOptions SetIsInGameEdition(boolean enable);
[Ref] PreviewExportOptions SetEditorId([Const] DOMString editorId);
[Ref] PreviewExportOptions SetEditorCameraState3D(
[Const] DOMString cameraMode,
double positionX,
double positionY,
double positionZ,
double rotationAngle,
double elevationAngle,
double distance);
[Ref] PreviewExportOptions SetNonRuntimeScriptsCacheBurst(unsigned long value);
[Ref] PreviewExportOptions SetElectronRemoteRequirePath([Const] DOMString electronRemoteRequirePath);
[Ref] PreviewExportOptions SetGDevelopResourceToken([Const] DOMString gdevelopResourceToken);
@@ -4036,6 +4066,13 @@ interface Exporter {
boolean ExportProjectForPixiPreview([Const, Ref] PreviewExportOptions options);
boolean ExportWholePixiProject([Const, Ref] ExportOptions options);
void SerializeProjectData(
[Const, Ref] Project project,
[Const, Ref] PreviewExportOptions options,
[Ref] SerializerElement projectDataElement);
void SerializeRuntimeGameOptions(
[Const, Ref] PreviewExportOptions options,
[Ref] SerializerElement runtimeGameOptionsElement);
[Const, Ref] DOMString GetLastError();
};

View File

@@ -44,6 +44,7 @@
#include <GDCore/IDE/Events/InstructionsTypeRenamer.h>
#include <GDCore/IDE/Events/TextFormatting.h>
#include <GDCore/IDE/Events/UsedExtensionsFinder.h>
#include <GDCore/IDE/Events/UsedObjectTypeFinder.h>
#include <GDCore/IDE/Events/ExampleExtensionUsagesFinder.h>
#include <GDCore/IDE/EventsFunctionTools.h>
#include <GDCore/IDE/ObjectVariableHelper.h>
@@ -826,6 +827,7 @@ typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice
#define STATIC_ScanProject ScanProject
#define STATIC_GetUsedExtensions GetUsedExtensions
#define STATIC_SerializeProjectData SerializeProjectData
#define STATIC_ApplyTranslation ApplyTranslation
#define STATIC_GetUndefined GetUndefined

View File

@@ -423,6 +423,14 @@ type CustomObjectConfiguration_EdgeAnchor = 0 | 1 | 2 | 3 | 4`
'types/gdexporter.js'
);
// Rename classes from GDJS:
shell.sed(
'-i',
'declare class gdExporterHelper {',
'declare class gdjsExporterHelper {',
'types/gdexporter.js'
);
// Improve typing of resources kind.
shell.sed(
'-i',

View File

@@ -565,6 +565,8 @@ export class Project extends EmscriptenObject {
isFolderProject(): boolean;
setUseDeprecatedZeroAsDefaultZOrder(enable: boolean): void;
getUseDeprecatedZeroAsDefaultZOrder(): boolean;
areEffectsHiddenInEditor(): boolean;
setEffectsHiddenInEditor(enable: boolean): void;
setLastCompilationDirectory(path: string): void;
getLastCompilationDirectory(): string;
getExtensionProperties(): ExtensionProperties;
@@ -923,6 +925,8 @@ export class Layer extends EmscriptenObject {
setCamera3DFarPlaneDistance(distance: number): void;
getCamera3DFieldOfView(): number;
setCamera3DFieldOfView(angle: number): void;
getCamera2DPlaneMaxDrawingDistance(): number;
setCamera2DPlaneMaxDrawingDistance(distance: number): void;
setAmbientLightColor(r: number, g: number, b: number): void;
getAmbientLightColorRed(): number;
getAmbientLightColorGreen(): number;
@@ -1193,6 +1197,12 @@ export class InitialInstance extends EmscriptenObject {
getCustomHeight(): number;
setCustomDepth(depth: number): void;
getCustomDepth(): number;
getDefaultWidth(): number;
getDefaultHeight(): number;
getDefaultDepth(): number;
setDefaultWidth(width: number): void;
setDefaultHeight(height: number): void;
setDefaultDepth(depth: number): void;
resetPersistentUuid(): InitialInstance;
getPersistentUuid(): string;
updateCustomProperty(name: string, value: string, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer): void;
@@ -2054,6 +2064,10 @@ export class UsedExtensionsFinder extends EmscriptenObject {
static scanProject(project: Project): UsedExtensionsResult;
}
export class UsedObjectTypeFinder extends EmscriptenObject {
static scanProject(project: Project, objectType: string): boolean;
}
export class ExampleExtensionUsagesFinder extends EmscriptenObject {
static getUsedExtensions(project: Project): SetString;
}
@@ -2968,11 +2982,19 @@ export class PreviewExportOptions extends EmscriptenObject {
setFallbackAuthor(id: string, username: string): PreviewExportOptions;
setAuthenticatedPlayer(playerId: string, playerUsername: string, playerToken: string): PreviewExportOptions;
setExternalLayoutName(externalLayoutName: string): PreviewExportOptions;
setEventsBasedObjectType(eventsBasedObjectType: string): PreviewExportOptions;
setEventsBasedObjectVariantName(eventsBasedObjectVariantName: string): PreviewExportOptions;
setIncludeFileHash(includeFile: string, hash: number): PreviewExportOptions;
setProjectDataOnlyExport(enable: boolean): PreviewExportOptions;
setShouldClearExportFolder(enable: boolean): PreviewExportOptions;
setShouldReloadProjectData(enable: boolean): PreviewExportOptions;
setShouldReloadLibraries(enable: boolean): PreviewExportOptions;
setShouldGenerateScenesEventsCode(enable: boolean): PreviewExportOptions;
setNativeMobileApp(enable: boolean): PreviewExportOptions;
setFullLoadingScreen(enable: boolean): PreviewExportOptions;
setIsDevelopmentEnvironment(enable: boolean): PreviewExportOptions;
setIsInGameEdition(enable: boolean): PreviewExportOptions;
setEditorId(editorId: string): PreviewExportOptions;
setEditorCameraState3D(cameraMode: string, positionX: number, positionY: number, positionZ: number, rotationAngle: number, elevationAngle: number, distance: number): PreviewExportOptions;
setNonRuntimeScriptsCacheBurst(value: number): PreviewExportOptions;
setElectronRemoteRequirePath(electronRemoteRequirePath: string): PreviewExportOptions;
setGDevelopResourceToken(gdevelopResourceToken: string): PreviewExportOptions;
@@ -2996,6 +3018,8 @@ export class Exporter extends EmscriptenObject {
setCodeOutputDirectory(path: string): void;
exportProjectForPixiPreview(options: PreviewExportOptions): boolean;
exportWholePixiProject(options: ExportOptions): boolean;
serializeProjectData(project: Project, options: PreviewExportOptions, projectDataElement: SerializerElement): void;
serializeRuntimeGameOptions(options: PreviewExportOptions, runtimeGameOptionsElement: SerializerElement): void;
getLastError(): string;
}

Some files were not shown because too many files have changed in this diff Show More