Compare commits

...

484 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
Clément Pasteau
11d3c52197 Automatically scroll down the AI Panel when generating (#7888) 2025-10-08 15:17:11 +02:00
github-actions[bot]
2ee1050083 [Auto PR] Update translations (#7886) 2025-10-08 13:31:58 +02:00
Clément Pasteau
ca00afb918 Allow coming back from bundle purchase flow & improve display (#7887)
Do not show in changelog
2025-10-08 11:10:05 +02:00
Florian Rival
02953a1436 Fix warning 2025-10-08 09:34:04 +02:00
Florian Rival
e14215c679 Make GDJS runtime of the deployed web-app to use the exact commit hash
This avoids issues when deploying again the same version number

Don't show in changelog
2025-10-08 00:51:34 +02:00
github-actions[bot]
c60ea0701a [Auto PR] Update translations (#7885) 2025-10-07 12:43:15 +02:00
Digvijay Rawat
de6ae1cc8f Save the selected layer in each scene so it is persisted across sessions (#7879) 2025-10-07 12:31:47 +02:00
Clément Pasteau
ad24acd72f Allow buying a bundle without an account (#7873)
Do not show in changelog
2025-10-07 12:30:13 +02:00
Florian Rival
3c0bb83032 Add "Pick Nearest" and "Rotate toward object" action (#7883) 2025-10-07 11:59:07 +02:00
Florian Rival
d5c96d74ed Add a grace distance to Destroy Outside behavior to avoid deleting objects if they are never seen but near the camera (#7864) 2025-10-07 11:58:42 +02:00
github-actions[bot]
43aada8ae7 [Auto PR] Update translations (#7881) 2025-10-07 11:55:22 +02:00
Florian Rival
17a7c2815f Send GDevelop version to AI endpoints for backward compatibility
Don't show in changelog
2025-10-06 18:46:31 +02:00
Sebastien Dionne
9441774a22 Fix typos and linguistic errors in documentation / hacktoberfest (#7882)
Signed-off-by: Sebastien Dionne <survivant00@gmail.com>
2025-10-06 11:44:05 +02:00
Florian Rival
d279276cc0 Fix instance sometimes not visible after being added by the AI 2025-10-05 23:52:35 +02:00
Florian Rival
c7f8a7a2eb Fix formatting 2025-10-05 23:44:53 +02:00
D8H
0fb92e000b Fix the link to the asynchronous function help page (#7880) 2025-10-05 23:27:18 +02:00
Florian Rival
76d43e1695 Fix instances not visible when added by the AI 2025-10-05 22:43:16 +02:00
Florian Rival
d70e3f71a6 Improve description of debugger tools extension [ci skip]
Don't show in changelog
2025-10-04 01:58:13 +02:00
Florian Rival
fe50ea3c01 Improve fog density description 2025-10-03 20:01:52 +02:00
danvervlad
77f821250a Add internal flag allowing to disable entirely hitboxes of Spine objects to avoid unnecessary recomputations (#7834)
Only show in developer changelog
2025-10-03 18:30:37 +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
github-actions[bot]
ff028ffa62 [Auto PR] Update translations (#7860)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-10-03 15:29:54 +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
D8H
64cc788d43 Save grid settings for variant tabs (#7869) 2025-10-02 14:15:41 +02:00
D8H
a85668c8d9 Fix aspect ratio being lost when hot-reloading 3D models (#7868) 2025-10-02 14:15:26 +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
Clément Pasteau
add042ec16 Create a standalone page that can be used for bundles (#7861) 2025-09-30 14:20:53 +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
Clément Pasteau
e174136fb4 New Save & Load built-in actions (#7811)
* New actions are available to save & load the game state, making Saving & Loading as easy as adding 1 action to your game!
* While it will work in most cases, it has a few limitations and hasn't been thoroughly tested on all types of objects/behaviors and games, so it is for the moment tagged as **Experimental** while we gather feedback and improve it
* Check out the wiki for more info: https://wiki.gdevelop.io/gdevelop5/all-features/save-state
2025-09-29 15:38:37 +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
github-actions[bot]
ff8697ed71 [Auto PR] Update translations (#7852) 2025-09-26 10:23:40 +02:00
ViktorVovk
542a841791 Add error handling for Howler sound methods (#7853)
* Specifically for "Maximum call stack size exceeded" errors.
2025-09-26 10:11:58 +02:00
github-actions[bot]
b9640f0049 [Auto PR] Update translations (#7851)
Do not show in changelog
2025-09-25 17:51:05 +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
Florian Rival
a6cc3dc85b Update storage action to make clearer the usage of manual load/unload 2025-09-25 16:28:36 +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
Clément Pasteau
6083c71e0e Bump newIDE version (#7850) 2025-09-25 10:48:53 +02:00
github-actions[bot]
e844ea819b Update extension translations [skip ci] (#7849)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-25 09:38:37 +02:00
github-actions[bot]
27f0300bc7 Update translations [skip ci] (#7845)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-25 09:35:00 +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
Clément Pasteau
35cea2eaba Update example slug info for analytics (#7847)
Do not show in changelog
2025-09-24 14:42:11 +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
D8H
7c4617da99 Add a skybox effect (#7843) 2025-09-23 21:14:44 +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
Clément Pasteau
d6d7c5c1fb Fix templates not accessible when bought via an unlisted bundle (#7844) 2025-09-23 11:18:15 +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
github-actions[bot]
b7f7a39aa7 Update translations [skip ci] (#7840)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-22 17:05:31 +02:00
Clément Pasteau
8c04771a87 Allow opening a bundle with a link via category (#7842)
Do not show in changelog
2025-09-22 16:36:16 +02:00
Clément Pasteau
febf15b279 Slightly improve Course page overload (#7841)
Do not show in changelog
2025-09-22 13:58:55 +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
D8H
a05e4b7ecc Fix groups wrongly underlined in red while having the right behaviors in the Events Sheet (#7839) 2025-09-22 11:25:37 +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
Florian Rival
3568a999f9 Update README [skip ci] [ci skip] 2025-09-18 16:48:48 +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
207097bf03 Bump newIDE version 2025-09-18 10:01:44 +02:00
github-actions[bot]
0e3d2b9570 Update translations [skip ci] (#7831)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-09-18 10:01:32 +02:00
D8H
87fac429e8 Underline groups having missing behaviors in the Events Sheet (#7832) 2025-09-18 10:00: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
D8H
3b1097931b Fix child object creation when called from another function (#7836) 2025-09-17 18:38:06 +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
D8H
ec7e408cd1 Fix button labels no longer updating in the editor (#7835) 2025-09-17 15:37:39 +02:00
Florian Rival
f8a99b9cfa Add prices and currency to store analytics (#7833)
Don't show in changelog
2025-09-16 15:44:35 +02:00
D8H
4c7231e6ae Fix a crash at runtime when behaviors are missing in functions (#7830) 2025-09-15 16:40:21 +02:00
D8H
883d32515c Rename "community" extensions as "experimental" extensions (#7828) 2025-09-15 14:04:31 +02:00
Florian Rival
f0f3c257fa Bump newIDE version 2025-09-14 16:11:02 +02:00
github-actions[bot]
42fce7d9ce Update translations [skip ci] (#7810)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-09-14 16:10:35 +02:00
Florian Rival
facac37fff Fix opening of the scene after creating project with AI
Don't show in changelog
2025-09-14 15:44:04 +02:00
Florian Rival
1272b601c6 Add many AI improvements (#7819)
* Allow the AI to start a game from a template
* Improved instances manipulation
* Better variables support
* Allow AI to swap an object by another one from the asset store
* Allow AI to change multiple properties at the same time
* Fix AI not properly removing behaviors associated to a behavior just removed
2025-09-13 18:01:35 +02:00
D8H
58e35cfaf5 Allow extensions to define labels for properties with string selectors (#7825) 2025-09-12 18:30:10 +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
D8H
2befc9781b Fix the list of assets suggested for sliders and toggle switches (#7826) 2025-09-12 15:42:10 +02:00
Florian Rival
8fb2872c36 Disable spell check on comments
Don't show in changelog
2025-09-12 10:25:41 +02:00
D8H
98033515c8 Fix a 1-frame delay when applying anchors to child-objects (#7820) 2025-09-11 15:19:45 +02:00
D8H
25c02cea2e Add a link to an help page for asynchronous functions (#7823) 2025-09-11 14:53:15 +02:00
D8H
6d5be78fec Forbid to import an extension which has the same name as a built-in one (#7822) 2025-09-11 14:52:49 +02:00
D8H
f6e60085db Fix missing extension dependencies in exported assets (#7821) 2025-09-11 14:52:32 +02:00
Florian Rival
a61648af70 Fix landscape/portrait orientation not working on iOS 2025-09-11 10:50:06 +02:00
Davy Hélard
4291d5597a Update some todo 2025-09-10 16:26:37 +02:00
D8H
2b496c6fd3 Fix custom object extraction behavior check (#7818)
- Don't show in changelog
2025-09-10 12:30:48 +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
Florian Rival
1e984f0965 Fix the chosen preset for AI not always valid when switching between chat and agent 2025-09-08 18:18:53 +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
Florian Rival
37bed36315 Make comment events editable exactly as they look (#7812) 2025-09-05 14:46:42 +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
D8H
5394cc5201 Forbid to add Physics behaviors on objects inside custom objects (#7809) 2025-09-04 19:37:57 +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
Florian Rival
5e3dfb0e9c Add condition to check if a key was just pressed (#7808) 2025-09-04 17:32:25 +02:00
Clément Pasteau
f6a6c981f8 Add a small intro explaining how to follow the courses (#7807) 2025-09-04 16:10:37 +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
Clément Pasteau
415c1bfd2f Fix bundles showing up in new object search (#7806) 2025-09-03 14:11:16 +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
github-actions[bot]
e4a911db25 Update translations [skip ci] (#7804)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-03 10:42:14 +02:00
Clément Pasteau
5fcd67d77b Additional bundles are added to the Learn & Shop sections (#7805)
* A premium bundle, including multiple courses, asset packs, templates and a gold subscription
* A curated platformer-specific bundle, including everything needed to learn & create a platformer game
2025-09-03 10:30:24 +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
86db08ac3f Fix npm start on Windows
Don't show in changelog
2025-09-01 15:49:21 +02:00
Florian Rival
8d502d7c5c Make it work on the web-app 2025-09-01 15:40:46 +02:00
github-actions[bot]
8d735fc726 Update translations [skip ci] (#7800)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-01 14:32:50 +02:00
Clément Pasteau
1e1f4bb2a3 Merge Bundle page into 1 component + small design fixes (#7803)
Do not show in changelog
2025-09-01 14:17:33 +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
Florian Rival
d8000aca10 Simplify editor README [skip ci] [ci skip] 2025-09-01 00:00:14 +02:00
Florian Rival
a2660ff0dc Allow to easily work on the game engine using the web-app development version (#7802) 2025-08-31 23:54:44 +02:00
Florian Rival
000d5785cf Improve AI agent performance by removing old object properties
Don't show in changelog
2025-08-30 17:29:49 +02:00
Florian Rival
9fe04712a9 Improve AI ability to update existing instances 2025-08-30 16:59:09 +02:00
Florian Rival
846afd9e0a Improve documentation with reference of all available effects. 2025-08-30 14:16:00 +02:00
Davy Hélard
9575705d29 Fix the inner area calculus when extracting a custom object 2025-08-29 19:49:27 +02:00
Florian Rival
6125ff0f90 Allow AI to change layers/effects/scene and some game properties (#7799) 2025-08-29 19:15:50 +02:00
github-actions[bot]
a5428a8843 Update translations [skip ci] (#7798)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-08-29 15:58:38 +02:00
Ansel Games
19be45cda6 Fix grammar in text font size change warning (#7797) 2025-08-29 14:26:04 +02:00
Clément Pasteau
889c97cb27 Update Bundle Page with more details about content and limited offer (#7795) 2025-08-29 14:24:49 +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
Florian Rival
1d83da41a9 Improve physics extensions descriptions to tell about the scale and typical force values 2025-08-27 16:42:41 +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
github-actions[bot]
a65f2174eb Update translations [skip ci] (#7781)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-08-27 10:05:59 +02:00
Davy Hélard
af7563b4b7 Fix advanced properties not being hot-reloaded 2025-08-27 09:57:23 +02:00
D8H
9db493e87e Refactor tab opening (#7794)
- Don't show in changelogs
2025-08-26 17:29:42 +02:00
Florian Rival
49a3a18b51 Make clear in-app tutorials are free
Don't show in changelog
2025-08-26 15:53:55 +02:00
Davy Hélard
1861c3be41 Handle missing zoom actions 2025-08-26 15:49:51 +02:00
Florian Rival
0489e7036b Rework stories for TeamSection 2025-08-26 12:07:57 +02:00
Davy Hélard
895dc625eb Display the screen size rectangle 2025-08-26 11:48:05 +02:00
Florian Rival
794d5a781c Add support for setting a full name for a student account (#7788) 2025-08-26 11:09:19 +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
D8H
c21dfbcc1f Optimize used resources search during export (#7790) 2025-08-25 20:12:17 +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
633 changed files with 51310 additions and 9453 deletions

View File

@@ -42,15 +42,19 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
const vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t relationalOperatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
std::size_t relationalOperatorIndex =
instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "relationalOperator") {
if (instrInfos.parameters.GetParameter(i).GetType() ==
"relationalOperator") {
relationalOperatorIndex = i;
}
}
// Ensure that there is at least one parameter after the relational operator
if (relationalOperatorIndex + 1 >= instrInfos.parameters.GetParametersCount()) {
if (relationalOperatorIndex + 1 >=
instrInfos.parameters.GetParametersCount()) {
ReportError();
return "";
}
@@ -87,20 +91,23 @@ gd::String EventsCodeGenerator::GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) {
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " + rhs;
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " +
rhs;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=" &&
operatorString != "startsWith" && operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(
const gd::String& operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" &&
operatorString != "!=" && operatorString != "startsWith" &&
operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
/**
@@ -124,7 +131,8 @@ gd::String EventsCodeGenerator::GenerateOperatorCall(
const gd::String& getterStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -195,7 +203,8 @@ gd::String EventsCodeGenerator::GenerateCompoundOperatorCall(
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -248,7 +257,8 @@ gd::String EventsCodeGenerator::GenerateMutatorCall(
const gd::String& callStartString,
std::size_t startFromArgument) {
std::size_t operatorIndex = instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument; i < instrInfos.parameters.GetParametersCount();
for (std::size_t i = startFromArgument;
i < instrInfos.parameters.GetParametersCount();
++i) {
if (instrInfos.parameters.GetParameter(i).GetType() == "operator") {
operatorIndex = i;
@@ -323,83 +333,97 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
// Insert code only parameters and be sure there is no lack of parameter.
while (condition.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
while (condition.GetParameters().size() <
instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = condition.GetParameters();
parameters.push_back(gd::Expression(""));
condition.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
++pNb) {
if (ParameterMetadata::IsObject(
instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter =
condition.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
const auto& expectedObjectType =
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
const auto& actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
objectInParameter)) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
objectInParameter, "");
gd::ProjectDiagnostic::ErrorType::UnknownObject,
"",
objectInParameter,
"");
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Unknown object - skipped. */";
} else if (!expectedObjectType.empty() &&
actualObjectType != expectedObjectType) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
actualObjectType, expectedObjectType, objectInParameter);
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
"",
actualObjectType,
expectedObjectType,
objectInParameter);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Mismatched object type - skipped. */";
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
if (instrInfos.IsObjectInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
if (!objectName.empty() && instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Set up the context
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
gd::String objectType =
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
// Prepare arguments and generate the condition whole code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateObjectCondition(realObjects[i],
objInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
// Prepare arguments and generate the condition whole code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateObjectCondition(realObjects[i],
objInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
context.SetNoCurrentObject();
context.SetNoCurrentObject();
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = condition.GetParameter(0).GetPlainString();
const gd::String &behaviorName =
const gd::String& objectName = condition.GetParameter(0).GetPlainString();
const gd::String& behaviorName =
condition.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
const BehaviorMetadata &autoInfo =
const BehaviorMetadata& autoInfo =
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
for (std::size_t i = 0; i < realObjects.size(); ++i) {
@@ -411,15 +435,14 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
// Prepare arguments and generate the whole condition code
vector<gd::String> arguments = GenerateParametersCodes(
condition.GetParameters(), instrInfos.parameters, context);
conditionCode += GenerateBehaviorCondition(
realObjects[i],
behaviorName,
autoInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
conditionCode += GenerateBehaviorCondition(realObjects[i],
behaviorName,
autoInfo,
arguments,
instrInfos,
returnBoolean,
condition.IsInverted(),
context);
context.SetNoCurrentObject();
}
@@ -488,31 +511,50 @@ gd::String EventsCodeGenerator::GenerateConditionsListCode(
return outputCode;
}
void EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos) {
gd::ParameterMetadataTools::IterateOverParameters(
instruction.GetParameters(), instrInfos.parameters,
[this](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue,
const gd::String &lastObjectName) {
bool EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction& instruction,
const gd::InstructionMetadata& instrInfos) {
bool isAnyBehaviorMissing = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
instrInfos.parameters,
[this, &isAnyBehaviorMissing, &instrInfos](
const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName,
size_t lastObjectIndex) {
if (ParameterMetadata::IsBehavior(parameterMetadata.GetType())) {
const gd::String &behaviorName = parameterValue.GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& behaviorName = parameterValue.GetPlainString();
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehaviorInObjectOrGroup(
lastObjectName, behaviorName);
const gd::String &expectedBehaviorType =
const gd::String& expectedBehaviorType =
parameterMetadata.GetExtraInfo();
if (!expectedBehaviorType.empty() &&
actualBehaviorType != expectedBehaviorType) {
const auto& objectParameterMetadata =
instrInfos.GetParameter(lastObjectIndex);
// Event functions crash if some objects in a group are missing
// the required behaviors, since they lose reference to the original
// objects. Missing behaviors are considered "fatal" only for
// ObjectList parameters, in order to minimize side effects on
// built-in functions.
if (objectParameterMetadata.GetType() == "objectList") {
isAnyBehaviorMissing = true;
}
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MissingBehavior, "",
actualBehaviorType, expectedBehaviorType, lastObjectName);
gd::ProjectDiagnostic::ErrorType::MissingBehavior,
"",
actualBehaviorType,
expectedBehaviorType,
lastObjectName);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
}
}
});
return isAnyBehaviorMissing;
}
/**
@@ -521,7 +563,8 @@ void EventsCodeGenerator::CheckBehaviorParameters(
gd::String EventsCodeGenerator::GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
gd::String actionCode;
const gd::InstructionMetadata& instrInfos =
@@ -546,39 +589,51 @@ gd::String EventsCodeGenerator::GenerateActionCode(
: instrInfos.codeExtraInformation.functionCallName;
// Be sure there is no lack of parameter.
while (action.GetParameters().size() < instrInfos.parameters.GetParametersCount()) {
while (action.GetParameters().size() <
instrInfos.parameters.GetParametersCount()) {
vector<gd::Expression> parameters = action.GetParameters();
parameters.push_back(gd::Expression(""));
action.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount();
++pNb) {
if (ParameterMetadata::IsObject(
instrInfos.parameters.GetParameter(pNb).GetType())) {
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
const auto &expectedObjectType =
const auto& expectedObjectType =
instrInfos.parameters.GetParameter(pNb).GetExtraInfo();
const auto &actualObjectType =
const auto& actualObjectType =
GetObjectsContainersList().GetTypeOfObject(objectInParameter);
if (!GetObjectsContainersList().HasObjectOrGroupNamed(
objectInParameter)) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::UnknownObject, "",
objectInParameter, "");
gd::ProjectDiagnostic::ErrorType::UnknownObject,
"",
objectInParameter,
"");
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Unknown object - skipped. */";
} else if (!expectedObjectType.empty() &&
actualObjectType != expectedObjectType) {
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType, "",
actualObjectType, expectedObjectType, objectInParameter);
gd::ProjectDiagnostic::ErrorType::MismatchedObjectType,
"",
actualObjectType,
expectedObjectType,
objectInParameter);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
return "/* Mismatched object type - skipped. */";
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
// Call free function first if available
if (instrInfos.IsObjectInstruction()) {
@@ -586,43 +641,46 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (instrInfos.parameters.GetParametersCount() > 0) {
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
gd::String objectType =
GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
AddIncludeFiles(objInfo.includeFiles);
context.SetCurrentObject(realObjects[i]);
context.ObjectsListNeeded(realObjects[i]);
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode += GenerateObjectAction(realObjects[i],
objInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName,
optionalAsyncCallbackId);
context.SetNoCurrentObject();
context.SetNoCurrentObject();
}
}
} else if (instrInfos.IsBehaviorInstruction()) {
if (instrInfos.parameters.GetParametersCount() >= 2) {
const gd::String &objectName = action.GetParameter(0).GetPlainString();
const gd::String &behaviorName = action.GetParameter(1).GetPlainString();
const gd::String &actualBehaviorType =
const gd::String& objectName = action.GetParameter(0).GetPlainString();
const gd::String& behaviorName = action.GetParameter(1).GetPlainString();
const gd::String& actualBehaviorType =
GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
std::vector<gd::String> realObjects =
GetObjectsContainersList().ExpandObjectName(
objectName, context.GetCurrentObject());
const BehaviorMetadata &autoInfo =
const BehaviorMetadata& autoInfo =
MetadataProvider::GetBehaviorMetadata(platform, actualBehaviorType);
AddIncludeFiles(autoInfo.includeFiles);
@@ -634,15 +692,15 @@ gd::String EventsCodeGenerator::GenerateActionCode(
// Prepare arguments and generate the whole action code
vector<gd::String> arguments = GenerateParametersCodes(
action.GetParameters(), instrInfos.parameters, context);
actionCode +=
GenerateBehaviorAction(realObjects[i],
behaviorName,
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
actionCode += GenerateBehaviorAction(realObjects[i],
behaviorName,
autoInfo,
functionCallName,
arguments,
instrInfos,
context,
optionalAsyncCallbackName,
optionalAsyncCallbackId);
context.SetNoCurrentObject();
}
@@ -654,7 +712,8 @@ gd::String EventsCodeGenerator::GenerateActionCode(
arguments,
instrInfos,
context,
optionalAsyncCallbackName);
optionalAsyncCallbackName,
optionalAsyncCallbackId);
}
return actionCode;
@@ -667,8 +726,8 @@ gd::String EventsCodeGenerator::GenerateLocalVariablesStackAccessor() {
}
gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
const gd::Expression &variableExpression,
EventsCodeGenerationContext &context) {
const gd::Expression& variableExpression,
EventsCodeGenerationContext& context) {
const auto variableName = gd::ExpressionVariableNameFinder::GetVariableName(
*variableExpression.GetRootNode());
@@ -679,8 +738,12 @@ gd::String EventsCodeGenerator::GenerateAnyOrSceneVariableGetter(
: "scenevar";
return gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, variableParameterType,
variableExpression.GetPlainString(), "", "AllowUndeclaredVariable");
*this,
context,
variableParameterType,
variableExpression.GetPlainString(),
"",
"AllowUndeclaredVariable");
}
const EventsCodeGenerator::CallbackDescriptor
@@ -727,6 +790,11 @@ EventsCodeGenerator::GenerateCallback(
AddCustomCodeOutsideMain(callbackCode);
const gd::String idToCallbackMapUpdate = GetCodeNamespaceAccessor() +
"idToCallbackMap.set(" + callbackID +
", " + callbackFunctionName + ");\n";
AddCustomCodeOutsideMain(idToCallbackMapUpdate);
std::set<gd::String> requiredObjects;
// Build the list of all objects required by the callback. Any object that has
// already been declared could have gone through previous object picking, so
@@ -769,7 +837,7 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
} else {
outputCode += actionCode;
}
outputCode += "}";
outputCode += "}\n";
}
return outputCode;
@@ -786,13 +854,28 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
if (ParameterMetadata::IsExpression("number", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "number", parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
"number",
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsExpression("string", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, "string", parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
"string",
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsExpression("variable", metadata.GetType())) {
argOutput = gd::ExpressionCodeGenerator::GenerateExpressionCode(
*this, context, metadata.GetType(), parameter, lastObjectName, metadata.GetExtraInfo());
*this,
context,
metadata.GetType(),
parameter,
lastObjectName,
metadata.GetExtraInfo());
} else if (ParameterMetadata::IsObject(metadata.GetType())) {
// It would be possible to run a gd::ExpressionCodeGenerator if later
// objects can have nested objects, or function returning objects.
@@ -827,7 +910,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
metadata.GetType() == "atlasResource" ||
metadata.GetType() == "spineResource" ||
// Deprecated, old parameter names:
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
metadata.GetType() == "password" ||
metadata.GetType() == "musicfile" ||
metadata.GetType() == "soundfile") {
argOutput = "\"" + ConvertToString(parameter.GetPlainString()) + "\"";
} else if (metadata.GetType() == "mouse") {
@@ -977,7 +1061,8 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
for (std::size_t eId = 0; eId < events.size(); ++eId) {
auto& event = events[eId];
if (event.HasVariables()) {
GetProjectScopedContainers().GetVariablesContainersList().Push(event.GetVariables());
GetProjectScopedContainers().GetVariablesContainersList().Push(
event.GetVariables());
}
// Each event has its own context : Objects picked in an event are totally
@@ -1102,7 +1187,7 @@ gd::String EventsCodeGenerator::GenerateFreeCondition(
instrInfos.codeExtraInformation.functionCallName);
} else {
predicate = instrInfos.codeExtraInformation.functionCallName + "(" +
GenerateArgumentsList(arguments, 0) + ")";
GenerateArgumentsList(arguments, 0) + ")";
}
// Add logical not if needed
@@ -1146,7 +1231,7 @@ gd::String EventsCodeGenerator::GenerateObjectCondition(
instrInfos, arguments, objectFunctionCallNamePart, 1);
} else {
predicate = objectFunctionCallNamePart + "(" +
GenerateArgumentsList(arguments, 1) + ")";
GenerateArgumentsList(arguments, 1) + ")";
}
if (conditionInverted) predicate = GenerateNegatedPredicate(predicate);
@@ -1178,18 +1263,20 @@ gd::String EventsCodeGenerator::GenerateBehaviorCondition(
}
gd::String EventsCodeGenerator::GenerateFreeAction(
const gd::String& functionCallName,
const gd::String& functionCallName,
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Generate call
gd::String call;
if (instrInfos.codeExtraInformation.type == "number" ||
instrInfos.codeExtraInformation.type == "string" ||
// Boolean actions declared with addExpressionAndConditionAndAction uses
// MutatorAndOrAccessor even though they don't declare an operator parameter.
// Boolean operators are only used with SetMutators or SetCustomCodeGenerator.
// MutatorAndOrAccessor even though they don't declare an operator
// parameter. Boolean operators are only used with SetMutators or
// SetCustomCodeGenerator.
(instrInfos.codeExtraInformation.type == "boolean" &&
instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::AccessType::Mutators)) {
@@ -1202,23 +1289,19 @@ gd::String EventsCodeGenerator::GenerateFreeAction(
instrInfos.codeExtraInformation.optionalAssociatedInstruction);
else if (instrInfos.codeExtraInformation.accessType ==
gd::InstructionMetadata::ExtraInformation::Mutators)
call =
GenerateMutatorCall(instrInfos,
arguments,
functionCallName);
call = GenerateMutatorCall(instrInfos, arguments, functionCallName);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
functionCallName);
call =
GenerateCompoundOperatorCall(instrInfos, arguments, functionCallName);
} else {
call = functionCallName + "(" +
GenerateArgumentsList(arguments) + ")";
call = functionCallName + "(" + GenerateArgumentsList(arguments) + ")";
}
if (!optionalAsyncCallbackName.empty())
if (!optionalAsyncCallbackName.empty() && !optionalAsyncCallbackId.empty()) {
call = "runtimeScene.getAsyncTasksManager().addTask(" + call + ", " +
optionalAsyncCallbackName + ")";
optionalAsyncCallbackName + ", " + optionalAsyncCallbackId +
", asyncObjectsList)";
}
return call + ";\n";
}
@@ -1230,7 +1313,8 @@ gd::String EventsCodeGenerator::GenerateObjectAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Create call
gd::String call;
if ((instrInfos.codeExtraInformation.type == "number" ||
@@ -1271,7 +1355,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName) {
const gd::String& optionalAsyncCallbackName,
const gd::String& optionalAsyncCallbackId) {
// Create call
gd::String call;
if ((instrInfos.codeExtraInformation.type == "number" ||
@@ -1286,17 +1371,13 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
2);
else
call = GenerateCompoundOperatorCall(
instrInfos,
arguments,
functionCallName,
2);
instrInfos, arguments, functionCallName, 2);
return "For each picked object \"" + objectName + "\", call " + call +
" for behavior \"" + behaviorName + "\".\n";
} else {
gd::String argumentsStr = GenerateArgumentsList(arguments, 2);
call = functionCallName + "(" +
argumentsStr + ")";
call = functionCallName + "(" + argumentsStr + ")";
return "For each picked object \"" + objectName + "\", call " + call + "(" +
argumentsStr + ")" + " for behavior \"" + behaviorName + "\"" +
@@ -1351,42 +1432,47 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
return argumentsStr;
}
gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
gd::String EventsCodeGenerator::GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property) {
return "getProperty" + property.GetName() + "()";
}
gd::String EventsCodeGenerator::GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode) {
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode) {
return "setProperty" + property.GetName() + "(" + operandCode + ")";
}
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
gd::String EventsCodeGenerator::GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata &parameter) {
return "getParameter" + parameter.GetName() + "()";
}
const gd::ParameterMetadata& parameter) {
return "getParameter" + parameter.GetName() + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_)
: platform(platform_),
projectScopedContainers(gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project_, layout)),
projectScopedContainers(
gd::ProjectScopedContainers::
MakeNewProjectScopedContainersForProjectAndLayout(project_,
layout)),
hasProjectAndLayout(true),
project(&project_),
scene(&layout),
@@ -1395,7 +1481,7 @@ EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
maxCustomConditionsDepth(0),
maxConditionsListsSize(0),
eventsListNextUniqueId(0),
diagnosticReport(nullptr){};
diagnosticReport(nullptr) {};
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
@@ -1410,6 +1496,6 @@ EventsCodeGenerator::EventsCodeGenerator(
maxCustomConditionsDepth(0),
maxConditionsListsSize(0),
eventsListNextUniqueId(0),
diagnosticReport(nullptr){};
diagnosticReport(nullptr) {};
} // namespace gd

View File

@@ -9,9 +9,9 @@
#include <utility>
#include <vector>
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
@@ -62,7 +62,7 @@ class GD_CORE_API EventsCodeGenerator {
EventsCodeGenerator(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
virtual ~EventsCodeGenerator() {};
/**
* \brief Preprocess an events list (replacing for example links with the
@@ -160,7 +160,8 @@ class GD_CORE_API EventsCodeGenerator {
gd::String GenerateActionCode(
gd::Instruction& action,
EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
struct CallbackDescriptor {
CallbackDescriptor(const gd::String functionName_,
@@ -168,7 +169,7 @@ class GD_CORE_API EventsCodeGenerator {
const std::set<gd::String> requiredObjects_)
: functionName(functionName_),
argumentsList(argumentsList_),
requiredObjects(requiredObjects_){};
requiredObjects(requiredObjects_) {};
/**
* The name by which the function can be invoked.
*/
@@ -338,9 +339,9 @@ class GD_CORE_API EventsCodeGenerator {
}
/**
* @brief Give access to the project scoped containers as code generation might
* push and pop variable containers (for local variables).
* This could be passed as a parameter recursively in code generation, but this requires
* @brief Give access to the project scoped containers as code generation
* might push and pop variable containers (for local variables). This could be
* passed as a parameter recursively in code generation, but this requires
* heavy refactoring. Instead, we use this single instance.
*/
gd::ProjectScopedContainers& GetProjectScopedContainers() {
@@ -387,9 +388,7 @@ class GD_CORE_API EventsCodeGenerator {
diagnosticReport = diagnosticReport_;
}
gd::DiagnosticReport* GetDiagnosticReport() {
return diagnosticReport;
}
gd::DiagnosticReport* GetDiagnosticReport() { return diagnosticReport; }
/**
* \brief Generate the full name for accessing to a boolean variable used for
@@ -513,16 +512,16 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Generate an any variable getter that fallbacks on scene variable for
* compatibility reason.
*/
gd::String
GenerateAnyOrSceneVariableGetter(const gd::Expression &variableExpression,
EventsCodeGenerationContext &context);
gd::String GenerateAnyOrSceneVariableGetter(
const gd::Expression& variableExpression,
EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertySetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property,
const gd::String &operandCode);
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& operandCode);
protected:
protected:
virtual const gd::String GenerateRelationalOperatorCodes(
const gd::String& operatorString);
@@ -643,16 +642,16 @@ protected:
gd::EventsCodeGenerationContext& context);
virtual gd::String GeneratePropertyGetterWithoutCasting(
const gd::PropertiesContainer &propertiesContainer,
const gd::NamedPropertyDescriptor &property);
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property);
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String
GenerateParameterGetterWithoutCasting(const gd::ParameterMetadata &parameter);
virtual gd::String GenerateParameterGetterWithoutCasting(
const gd::ParameterMetadata& parameter);
/**
* \brief Generate the code to reference an object which is
@@ -769,7 +768,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
virtual gd::String GenerateObjectAction(
const gd::String& objectName,
@@ -778,7 +778,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
virtual gd::String GenerateBehaviorAction(
const gd::String& objectName,
@@ -788,7 +789,8 @@ protected:
const std::vector<gd::String>& arguments,
const gd::InstructionMetadata& instrInfos,
gd::EventsCodeGenerationContext& context,
const gd::String& optionalAsyncCallbackName = "");
const gd::String& optionalAsyncCallbackName = "",
const gd::String& optionalAsyncCallbackId = "");
gd::String GenerateRelationalOperatorCall(
const gd::InstructionMetadata& instrInfos,
@@ -837,9 +839,8 @@ protected:
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName);
void CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos);
bool CheckBehaviorParameters(const gd::Instruction& instruction,
const gd::InstructionMetadata& instrInfos);
const gd::Platform& platform; ///< The platform being used.
@@ -876,4 +877,3 @@ protected:
};
} // namespace gd

View File

@@ -293,6 +293,25 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddCodeOnlyParameter("currentScene", "")
.MarkAsAdvanced();
obj.AddAction(
"RotateTowardObject",
_("Rotate toward another object"),
_("Rotate an object towards another object, with the specified speed. "
"Note that if multiple instances of the target object are picked, "
"only the first one will be used. Use a For Each event or actions "
"like \"Pick nearest object\", \"Pick a random object\" to refine "
"the choice of the target object."),
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_ deg/second"),
_("Angle"),
"res/actions/rotate24_black.png",
"res/actions/rotate_black.png")
.AddParameter("object", _("Object"))
.AddParameter("objectPtr", _("Target object"))
.AddParameter("expression", _("Angular speed (in degrees per second)"))
.SetParameterLongDescription(_("Enter 0 for an immediate rotation."))
.AddCodeOnlyParameter("currentScene", "")
.MarkAsAdvanced();
obj.AddAction(
"AddForceXY",
_("Add a force"),
@@ -1617,7 +1636,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
extension
.AddAction("AjoutObjConcern",
_("Pick all instances"),
_("Pick all object instances"),
_("Pick all instances of the specified object(s). When you "
"pick all instances, "
"the next conditions and actions of this event work on all "
@@ -1631,20 +1650,34 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced();
extension
.AddAction(
"AjoutHasard",
_("Pick a random object"),
_("Pick one object from all the specified objects. When an object "
"is picked, the next conditions and actions of this event work "
"only on that object."),
_("Pick a random _PARAM1_"),
_("Objects"),
"res/actions/ajouthasard24.png",
"res/actions/ajouthasard.png")
.AddAction("AjoutHasard",
_("Pick a random object"),
_("Pick one instance from all the specified objects. When an "
"instance is picked, the next conditions and actions of "
"this event work only on that object instance."),
_("Pick a random _PARAM1_"),
_("Objects"),
"res/actions/ajouthasard24.png",
"res/actions/ajouthasard.png")
.AddCodeOnlyParameter("objectsContext", "")
.AddParameter("objectList", _("Object"))
.MarkAsSimple();
extension
.AddAction(
"PickNearest",
_("Pick nearest object"),
_("Pick the instance of this object that is nearest to the specified "
"position."),
_("Pick the _PARAM0_ that is nearest to _PARAM1_;_PARAM2_"),
_("Objects"),
"res/conditions/distance24.png",
"res/conditions/distance.png")
.AddParameter("objectList", _("Object"))
.AddParameter("expression", _("X position"))
.AddParameter("expression", _("Y position"))
.MarkAsSimple();
extension
.AddAction(
"MoveObjects",
@@ -1694,11 +1727,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
extension
.AddCondition(
"AjoutObjConcern",
_("Pick all objects"),
_("Pick all the specified objects. When you pick all objects, "
_("Pick all object instances"),
_("Pick all instances of the specified object(s). When you "
"pick all instances, "
"the next conditions and actions of this event work on all "
"of them."),
_("Pick all _PARAM1_ objects"),
_("Pick all instances of _PARAM1_"),
_("Objects"),
"res/conditions/add24.png",
"res/conditions/add.png")
@@ -1707,16 +1741,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.MarkAsAdvanced();
extension
.AddCondition(
"AjoutHasard",
_("Pick a random object"),
_("Pick one object from all the specified objects. When an object "
"is picked, the next conditions and actions of this event work "
"only on that object."),
_("Pick a random _PARAM1_"),
_("Objects"),
"res/conditions/ajouthasard24.png",
"res/conditions/ajouthasard.png")
.AddCondition("AjoutHasard",
_("Pick a random object"),
_("Pick one instance from all the specified objects. When "
"an instance is picked, the next conditions and actions "
"of this event work only on that object instance."),
_("Pick a random _PARAM1_"),
_("Objects"),
"res/conditions/ajouthasard24.png",
"res/conditions/ajouthasard.png")
.AddCodeOnlyParameter("objectsContext", "")
.AddParameter("objectList", _("Object"))
.MarkAsSimple();
@@ -1725,9 +1758,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
.AddCondition(
"PickNearest",
_("Pick nearest object"),
_("Pick the object of this type that is nearest to the specified "
"position. If the condition is inverted, the object farthest from "
"the specified position is picked instead."),
_("Pick the instance of this object that is nearest to the specified "
"position. If the condition is inverted, the instance farthest "
"from the specified position is picked instead."),
_("Pick the _PARAM0_ that is nearest to _PARAM1_;_PARAM2_"),
_("Objects"),
"res/conditions/distance24.png",

View File

@@ -42,13 +42,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFileExtension(
extension
.AddAction(
"LoadFile",
_("Load a storage in memory"),
_("This action loads the specified storage in memory, so you can "
"write and read it.\nYou can open and write without using this "
"action, but it will be slower.\nIf you use this action, do not "
"forget to unload the storage from memory."),
_("Load storage _PARAM0_ in memory"),
"",
_("Manually preload a storage in memory"),
_("Forces the specified storage to be loaded and kept in "
"memory, allowing faster reads/writes. "
"However, it requires manual management: if you use this "
"action, you *must* also unload the storage manually when "
"it's no longer needed to ensure data is persisted.\n\n"
"Unless you have a specific performance need, avoid using this "
"action. The system already handles loading/unloading "
"automatically."),
_("Load data storage _PARAM0_ in memory"),
_("Advanced"),
"res/actions/fichier24.png",
"res/actions/fichier.png")
.AddParameter("string", _("Storage name"))
@@ -56,11 +60,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFileExtension(
extension
.AddAction("UnloadFile",
_("Close a storage"),
_("This action closes the structured data previously loaded "
_("Manually unload and persist a storage"),
_("Close the specified storage previously loaded "
"in memory, saving all changes made."),
_("Close structured data _PARAM0_"),
"",
_("Unload and persist data storage _PARAM0_"),
_("Advanced"),
"res/actions/fichier24.png",
"res/actions/fichier.png")
.AddParameter("string", _("Storage name"))

View File

@@ -52,10 +52,25 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
.SetHidden();
extension
.AddCondition("KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed"),
_("_PARAM1_ key is pressed"),
.AddCondition(
"KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed. This stays true as long as "
"the key is held down. To check if a key was pressed during "
"the frame, use \"Key just pressed\" instead."),
_("_PARAM1_ key is pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("keyboardKey", _("Key to check"))
.MarkAsSimple();
extension
.AddCondition("KeyFromTextJustPressed",
_("Key just pressed"),
_("Check if a key was just pressed."),
_("_PARAM1_ key was just pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
@@ -66,7 +81,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyFromTextReleased",
_("Key released"),
_("Check if a key was just released"),
_("Check if a key was just released."),
_("_PARAM1_ key is released"),
"",
"res/conditions/keyboard24.png",

View File

@@ -298,6 +298,19 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
return *this;
}
/**
* Check if the behavior can be used on objects from event-based objects.
*/
bool IsRelevantForChildObjects() const { return isRelevantForChildObjects; }
/**
* Set that behavior can't be used on objects from event-based objects.
*/
BehaviorMetadata &MarkAsIrrelevantForChildObjects() {
isRelevantForChildObjects = false;
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
@@ -393,6 +406,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
mutable std::vector<gd::String> requiredBehaviors;
bool isPrivate = false;
bool isHidden = false;
bool isRelevantForChildObjects = true;
gd::String openFullEditorLabel;
QuickCustomization::Visibility quickCustomizationVisibility = QuickCustomization::Visibility::Default;

View File

@@ -277,6 +277,10 @@ class GD_CORE_API MetadataProvider {
return &metadata == &badObjectInfo;
}
static bool IsBadEffectMetadata(const gd::EffectMetadata& metadata) {
return &metadata == &badEffectMetadata;
}
virtual ~MetadataProvider();
private:

View File

@@ -194,7 +194,8 @@ void ParameterMetadataTools::IterateOverParameters(
[&fn](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& lastObjectName,
size_t lastObjectIndex) {
fn(parameterMetadata, parameterValue, lastObjectName);
});
}
@@ -205,8 +206,10 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn) {
const gd::String& lastObjectName,
size_t lastObjectIndex)> fn) {
gd::String lastObjectName = "";
size_t lastObjectIndex = 0;
for (std::size_t pNb = 0; pNb < parametersMetadata.GetParametersCount();
++pNb) {
const gd::ParameterMetadata &parameterMetadata =
@@ -218,15 +221,17 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
? Expression(parameterMetadata.GetDefaultValue())
: parameterValue;
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName);
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName, lastObjectIndex);
// Memorize the last object name. By convention, parameters that require
// an object (mainly, "objectvar" and "behavior") should be placed after
// the object in the list of parameters (if possible, just after).
// Search "lastObjectName" in the codebase for other place where this
// convention is enforced.
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType()))
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType())) {
lastObjectName = parameterValueOrDefault.GetPlainString();
lastObjectIndex = pNb;
}
}
}

View File

@@ -64,7 +64,8 @@ class GD_CORE_API ParameterMetadataTools {
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn);
const gd::String& lastObjectName,
size_t lastObjectIndex)> fn);
/**
* Iterate over the parameters of a FunctionCallNode.

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

@@ -29,7 +29,7 @@ bool BehaviorParametersFiller::DoVisitInstruction(gd::Instruction &instruction,
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (parameterMetadata.GetValueTypeMetadata().IsBehavior() &&
parameterValue.GetPlainString().length() == 0) {

View File

@@ -108,12 +108,10 @@ bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String& type = parameterMetadata.GetType();
if (gd::ParameterMetadata::IsBehavior(type)) {

View File

@@ -183,12 +183,10 @@ bool EventsParameterReplacer::DoVisitInstruction(gd::Instruction& instruction,
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (!gd::EventsParameterReplacer::CanContainParameter(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -217,12 +217,10 @@ bool EventsPropertyReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (!gd::EventsPropertyReplacer::CanContainProperty(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -334,7 +334,7 @@ private:
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (!gd::EventsObjectReplacer::CanContainObject(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -42,18 +42,16 @@ bool EventsVariableInstructionTypeSwitcher::DoVisitInstruction(gd::Instruction&
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) ||
!gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction(
instruction.GetType())) {
return;
return;
}
const auto variableName =
gd::ExpressionVariableNameFinder::GetVariableName(
@@ -72,10 +70,11 @@ bool EventsVariableInstructionTypeSwitcher::DoVisitInstruction(gd::Instruction&
.GetObjectOrGroupVariablesContainer(lastObjectName);
}
} else if (type == "variableOrProperty") {
variablesContainer =
&GetProjectScopedContainers()
.GetVariablesContainersList()
.GetVariablesContainerFromVariableOrPropertyName(variableName);
variablesContainer =
&GetProjectScopedContainers()
.GetVariablesContainersList()
.GetVariablesContainerFromVariableOrPropertyName(
variableName);
} else {
if (GetProjectScopedContainers().GetVariablesContainersList().Has(
variableName)) {

View File

@@ -448,12 +448,10 @@ bool EventsVariableReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&

View File

@@ -150,7 +150,7 @@ bool ProjectElementRenamer::DoVisitInstruction(gd::Instruction &instruction,
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName) {
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (parameterMetadata.GetType() == "layer") {
if (parameterValue.GetPlainString().length() < 2) {
// This is either the base layer or an invalid layer name.

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

@@ -76,6 +76,7 @@ void ObjectAssetSerializer::SerializeTo(
double width = 0;
double height = 0;
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
if (project.HasEventsBasedObject(object.GetType())) {
SerializerElement &variantsElement =
objectAssetElement.AddChild("variants");
@@ -87,7 +88,6 @@ void ObjectAssetSerializer::SerializeTo(
height = variant->GetAreaMaxY() - variant->GetAreaMinY();
}
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
project, object, variantsElement, alreadyUsedVariantIdentifiers);
}
@@ -114,14 +114,24 @@ void ObjectAssetSerializer::SerializeTo(
resourceElement.SetAttribute("name", resource.GetName());
}
std::unordered_set<gd::String> usedExtensionNames;
usedExtensionNames.insert(extensionName);
for (auto &usedVariantIdentifier : alreadyUsedVariantIdentifiers) {
usedExtensionNames.insert(PlatformExtension::GetExtensionFromFullObjectType(
usedVariantIdentifier));
}
SerializerElement &requiredExtensionsElement =
objectAssetElement.AddChild("requiredExtensions");
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", extensionName);
requiredExtensionElement.SetAttribute("extensionVersion", "1.0.0");
for (auto &usedExtensionName : usedExtensionNames) {
if (project.HasEventsFunctionsExtensionNamed(usedExtensionName)) {
auto &extension = project.GetEventsFunctionsExtension(usedExtensionName);
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", usedExtensionName);
requiredExtensionElement.SetAttribute("extensionVersion",
extension.GetVersion());
}
}
// TODO This can be removed when the asset script no longer require it.

View File

@@ -227,12 +227,11 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[this, &instruction](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterExpression,
size_t parameterIndex,
const gd::String& lastObjectName) {
instruction.GetParameters(), metadata.GetParameters(),
[this, &instruction](
const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterExpression, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const String& parameterValue = parameterExpression.GetPlainString();
if (parameterMetadata.GetType() == "fontResource") {
gd::String updatedParameterValue = parameterValue;

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

@@ -248,12 +248,13 @@ gd::String PropertyFunctionGenerator::GetStringifiedExtraInfo(
gd::String arrayString;
arrayString += "[";
bool isFirst = true;
for (const gd::String &choice : property.GetExtraInfo()) {
for (const auto &choice : property.GetChoices()) {
if (!isFirst) {
arrayString += ",";
}
isFirst = false;
arrayString += "\"" + choice + "\"";
// TODO Handle labels (and search "choice label")
arrayString += "\"" + choice.GetValue() + "\"";
}
arrayString += "]";
return arrayString;

View File

@@ -75,6 +75,17 @@ void ResourceExposer::ExposeProjectResources(
// Expose global objects configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
objectWorker.Launch(project.GetObjects());
// Exposed extension event resources
// Note that using resources in extensions is very unlikely and probably not
// worth the effort of something smart.
auto eventWorker = gd::GetResourceWorkerOnEvents(project, worker);
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
project, eventsFunctionsExtension, eventWorker);
}
}
void ResourceExposer::ExposeLayoutResources(
@@ -103,16 +114,34 @@ void ResourceExposer::ExposeLayoutResources(
auto eventWorker = gd::GetResourceWorkerOnEvents(project, worker);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndDependencies(
project, layout, eventWorker);
}
// Exposed extension event resources
// Note that using resources in extensions is very unlikely and probably not
// worth the effort of something smart.
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
project, eventsFunctionsExtension, 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(

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

@@ -37,6 +37,7 @@ void EventsBasedObjectVariant::SerializeTo(SerializerElement &element) const {
layers.SerializeLayersTo(element.AddChild("layers"));
initialInstances.SerializeTo(element.AddChild("instances"));
editorSettings.SerializeTo(element.AddChild("editionSettings"));
}
void EventsBasedObjectVariant::UnserializeFrom(
@@ -66,6 +67,7 @@ void EventsBasedObjectVariant::UnserializeFrom(
layers.Reset();
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
editorSettings.UnserializeFrom(element.GetChild("editionSettings"));
}
} // namespace gd

View File

@@ -5,6 +5,7 @@
*/
#pragma once
#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/LayersContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
@@ -199,6 +200,19 @@ public:
const gd::String &GetAssetStoreOriginalName() const {
return assetStoreOriginalName;
};
/**
*
* \brief Get the user settings for the IDE.
*/
const gd::EditorSettings& GetAssociatedEditorSettings() const {
return editorSettings;
}
/**
* \brief Get the user settings for the IDE.
*/
gd::EditorSettings& GetAssociatedEditorSettings() { return editorSettings; }
void SerializeTo(SerializerElement &element) const;
@@ -224,6 +238,7 @@ private:
* store.
*/
gd::String assetStoreOriginalName;
gd::EditorSettings editorSettings;
};
} // namespace gd

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

@@ -34,6 +34,20 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
}
}
if (!choices.empty()
// Compatibility with GD <= 5.5.239
|| !extraInformation.empty()
// end of compatibility code
) {
SerializerElement &choicesElement = element.AddChild("choices");
choicesElement.ConsiderAsArrayOf("choice");
for (const auto &choice : choices) {
auto &choiceElement = choicesElement.AddChild("Choice");
choiceElement.SetStringAttribute("value", choice.GetValue());
choiceElement.SetStringAttribute("label", choice.GetLabel());
}
}
if (hidden) {
element.AddChild("hidden").SetBoolValue(hidden);
}
@@ -56,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 =
@@ -80,6 +96,26 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
extraInformationElement.GetChild(i).GetStringValue());
}
if (element.HasChild("choices")) {
choices.clear();
const SerializerElement &choicesElement = element.GetChild("choices");
choicesElement.ConsiderAsArrayOf("choice");
for (std::size_t i = 0; i < choicesElement.GetChildrenCount(); ++i) {
auto &choiceElement = choicesElement.GetChild(i);
AddChoice(choiceElement.GetStringAttribute("value"),
choiceElement.GetStringAttribute("label"));
}
}
// Compatibility with GD <= 5.5.239
else if (type == "Choice") {
choices.clear();
for (auto &choiceValue : extraInformation) {
AddChoice(choiceValue, choiceValue);
}
extraInformation.clear();
}
// end of compatibility code
hidden = element.HasChild("hidden")
? element.GetChild("hidden").GetBoolValue()
: false;

View File

@@ -116,6 +116,11 @@ class GD_CORE_API PropertyDescriptor {
return *this;
}
PropertyDescriptor& ClearChoices() {
choices.clear();
return *this;
}
PropertyDescriptor& AddChoice(const gd::String& value,
const gd::String& label) {
choices.push_back(PropertyDescriptorChoice(value, label));

View File

@@ -24,7 +24,7 @@ The rest of this page is an introduction to the main concepts of GDevelop archit
Extensions do have the same distinction between the "**IDE**" part and the "**Runtime**" part. For example, most extensions have:
- A file called [`JsExtension.js`(https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/JsExtension.js)], which contains the _declaration_ of the extension for the **IDE**
- A file called [`JsExtension.js`](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/JsExtension.js), which contains the _declaration_ of the extension for the **IDE**
- One or more files implementing the feature for the game, in other words for **Runtime**. This can be a [Runtime Object](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/dummyruntimeobject.ts) or a [Runtime Behavior](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/dummyruntimebehavior.ts), [functions called by actions or conditions](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/examplejsextensiontools.ts) or by the game engine.
### "Runtime" and "IDE" difference using an example: the `gd::Variable` class

View File

@@ -764,129 +764,6 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in event-based functions") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto &function = extension.GetEventsFunctions().InsertNewEventsFunction(
"MyFreeFunction", 0);
gd::StandardEvent standardEvent;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomethingWithResources");
instruction.SetParametersCount(3);
instruction.SetParameter(0, "res3");
instruction.SetParameter(1, "res1");
instruction.SetParameter(2, "res4");
standardEvent.GetActions().Insert(instruction);
function.GetEvents().InsertEvent(standardEvent);
auto& layout = project.InsertNewLayout("MyScene", 0);
// MyEventExtension::MyFreeFunction doesn't need to be actually used in
// events because the implementation is naive.
gd::ResourceExposer::ExposeLayoutResources(project, layout, worker);
REQUIRE(worker.bitmapFonts.size() == 1);
REQUIRE(worker.bitmapFonts[0] == "res3");
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
REQUIRE(worker.audios.size() == 1);
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in event-based behavior functions") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& behavior = extension.GetEventsBasedBehaviors().InsertNew("MyBehavior", 0);
auto& function = behavior.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0);
gd::StandardEvent standardEvent;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomethingWithResources");
instruction.SetParametersCount(3);
instruction.SetParameter(0, "res3");
instruction.SetParameter(1, "res1");
instruction.SetParameter(2, "res4");
standardEvent.GetActions().Insert(instruction);
function.GetEvents().InsertEvent(standardEvent);
auto& layout = project.InsertNewLayout("MyScene", 0);
// MyEventExtension::MyBehavior::MyFunction doesn't need to be actually used in
// events because the implementation is naive.
gd::ResourceExposer::ExposeLayoutResources(project, layout, worker);
REQUIRE(worker.bitmapFonts.size() == 1);
REQUIRE(worker.bitmapFonts[0] == "res3");
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
REQUIRE(worker.audios.size() == 1);
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in event-based object functions") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto& extension = project.InsertNewEventsFunctionsExtension("MyEventExtension", 0);
auto& object = extension.GetEventsBasedObjects().InsertNew("MyObject", 0);
auto& function = object.GetEventsFunctions().InsertNewEventsFunction("MyFunction", 0);
gd::StandardEvent standardEvent;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomethingWithResources");
instruction.SetParametersCount(3);
instruction.SetParameter(0, "res3");
instruction.SetParameter(1, "res1");
instruction.SetParameter(2, "res4");
standardEvent.GetActions().Insert(instruction);
function.GetEvents().InsertEvent(standardEvent);
auto& layout = project.InsertNewLayout("MyScene", 0);
// MyEventExtension::MyObject::MyFunction doesn't need to be actually used in
// events because the implementation is naive.
gd::ResourceExposer::ExposeLayoutResources(project, layout, worker);
REQUIRE(worker.bitmapFonts.size() == 1);
REQUIRE(worker.bitmapFonts[0] == "res3");
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
REQUIRE(worker.audios.size() == 1);
REQUIRE(worker.audios[0] == "res4");
}
SECTION("Can find resource usages in layer effects") {
gd::Project project;
gd::Platform platform;

View File

@@ -139,8 +139,8 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
.SetLabel("Dot shape")
.SetDescription("The shape is used for collision.")
.SetGroup("Movement");
property.GetExtraInfo().push_back("Dot shape");
property.GetExtraInfo().push_back("Bounding disk");
property.AddChoice("DotShape", "Dot shape");
property.AddChoice("BoundingDisk", "Bounding disk");
gd::PropertyFunctionGenerator::GenerateBehaviorGetterAndSetter(
project, extension, behavior, property, false);
@@ -157,7 +157,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
gd::EventsFunction::ExpressionAndCondition);
REQUIRE(getter.GetExpressionType().GetName() == "stringWithSelector");
REQUIRE(getter.GetExpressionType().GetExtraInfo() ==
"[\"Dot shape\",\"Bounding disk\"]");
"[\"DotShape\",\"BoundingDisk\"]");
}
SECTION("Can generate functions for a boolean property in a behavior") {
@@ -386,8 +386,8 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
.SetLabel("Dot shape")
.SetDescription("The shape is used for collision.")
.SetGroup("Movement");
property.GetExtraInfo().push_back("Dot shape");
property.GetExtraInfo().push_back("Bounding disk");
property.AddChoice("DotShape", "Dot shape");
property.AddChoice("BoundingDisk", "Bounding disk");
gd::PropertyFunctionGenerator::GenerateObjectGetterAndSetter(
project, extension, object, property);
@@ -404,7 +404,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
gd::EventsFunction::ExpressionAndCondition);
REQUIRE(getter.GetExpressionType().GetName() == "stringWithSelector");
REQUIRE(getter.GetExpressionType().GetExtraInfo() ==
"[\"Dot shape\",\"Bounding disk\"]");
"[\"DotShape\",\"BoundingDisk\"]");
}
SECTION("Can generate functions for a boolean property in an object") {

View File

@@ -97,6 +97,14 @@ namespace gdjs {
oldObjectData: Object3DData,
newObjectData: Object3DData
): boolean {
this.updateOriginalDimensionsFromObjectData(oldObjectData, newObjectData);
return true;
}
updateOriginalDimensionsFromObjectData(
oldObjectData: Object3DData,
newObjectData: Object3DData
): void {
// There is no need to check if they changed because events can't modify them.
this._setOriginalWidth(
getValidDimensionValue(newObjectData.content.width)
@@ -107,12 +115,13 @@ namespace gdjs {
this._setOriginalDepth(
getValidDimensionValue(newObjectData.content.depth)
);
return true;
}
getNetworkSyncData(): Object3DNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Object3DNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
z: this.getZ(),
d: this.getDepth(),
rx: this.getRotationX(),
@@ -123,8 +132,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: Object3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
@@ -147,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 {
@@ -322,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;
}
@@ -368,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

@@ -452,9 +452,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Cube3DObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Cube3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
fo: this._facesOrientation,
bfu: this._backFaceUpThroughWhichAxisRotation,
@@ -466,9 +468,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: Cube3DObjectNetworkSyncData
networkSyncData: Cube3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;

View File

@@ -1,12 +1,16 @@
namespace gdjs {
type CustomObject3DNetworkSyncDataType = CustomObjectNetworkSyncDataType & {
type CustomObject3DNetworkSyncDataType = {
z: float;
d: float;
rx: float;
ry: float;
ifz: boolean;
ccz: float;
};
type CustomObject3DNetworkSyncData = CustomObjectNetworkSyncData &
CustomObject3DNetworkSyncDataType;
/**
* Base class for 3D custom objects.
*/
@@ -74,32 +78,30 @@ 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(): CustomObject3DNetworkSyncDataType {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): CustomObject3DNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
z: this.getZ(),
d: this.getDepth(),
rx: this.getRotationX(),
ry: this.getRotationY(),
ifz: this.isFlippedZ(),
ccz: this._customCenterZ,
};
}
updateFromNetworkSyncData(
networkSyncData: CustomObject3DNetworkSyncDataType
networkSyncData: CustomObject3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
@@ -107,6 +109,8 @@ namespace gdjs {
if (networkSyncData.ry !== undefined)
this.setRotationY(networkSyncData.ry);
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
if (networkSyncData.ccz !== undefined)
this._customCenterZ = networkSyncData.ccz;
}
/**
@@ -315,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

@@ -1901,6 +1901,11 @@ module.exports = {
.getOrCreate('density')
.setValue('0.0012')
.setLabel(_('Density'))
.setDescription(
_(
'Density of the fog. Usual values are between 0.0005 (far away) and 0.005 (very thick fog).'
)
)
.setType('number');
}
{
@@ -1908,7 +1913,9 @@ module.exports = {
.addEffect('AmbientLight')
.setFullName(_('Ambient light'))
.setDescription(
_('A light that illuminates all objects from every direction.')
_(
'A light that illuminates all objects from every direction. Often used along with a Directional light (though a Hemisphere light can be used instead of an Ambient light).'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
@@ -1929,7 +1936,11 @@ module.exports = {
const effect = extension
.addEffect('DirectionalLight')
.setFullName(_('Directional light'))
.setDescription(_('A very far light source like the sun.'))
.setDescription(
_(
"A very far light source like the sun. This is the light to use for casting shadows for 3D objects (other lights won't emit shadows). Often used along with a Hemisphere light."
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/DirectionalLight.js');
@@ -2013,7 +2024,7 @@ module.exports = {
.setFullName(_('Hemisphere light'))
.setDescription(
_(
'A light that illuminates objects from every direction with a gradient.'
'A light that illuminates objects from every direction with a gradient. Often used along with a Directional light.'
)
)
.markAsNotWorkingForObjects()
@@ -2057,6 +2068,48 @@ module.exports = {
.setType('number')
.setGroup(_('Orientation'));
}
{
const effect = extension
.addEffect('Skybox')
.setFullName(_('Skybox'))
.setDescription(
_('Display a background on a cube surrounding the scene.')
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/Skybox.js');
const properties = effect.getProperties();
properties
.getOrCreate('rightFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Right face (X+)'));
properties
.getOrCreate('leftFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Left face (X-)'));
properties
.getOrCreate('bottomFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Bottom face (Y+)'));
properties
.getOrCreate('topFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Top face (Y-)'));
properties
.getOrCreate('frontFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Front face (Z+)'));
properties
.getOrCreate('backFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Back face (Z-)'));
}
{
const effect = extension
.addEffect('HueAndSaturation')

View File

@@ -8,6 +8,7 @@ namespace gdjs {
anis: Model3DAnimation[];
ai: integer;
ass: float;
aet: float;
ap: boolean;
cfd: float;
};
@@ -152,6 +153,14 @@ namespace gdjs {
}
}
override updateOriginalDimensionsFromObjectData(
oldObjectData: Object3DData,
newObjectData: Object3DData
): void {
// Original dimensions must not be reset by `super.updateFromObjectData`.
// `_updateModel` has a different logic to evaluate them using `keepAspectRatio`.
}
updateFromObjectData(
oldObjectData: Model3DObjectData,
newObjectData: Model3DObjectData
@@ -181,8 +190,14 @@ namespace gdjs {
oldObjectData.content.keepAspectRatio !==
newObjectData.content.keepAspectRatio ||
oldObjectData.content.materialType !==
newObjectData.content.materialType
newObjectData.content.materialType ||
oldObjectData.content.centerLocation !==
newObjectData.content.centerLocation
) {
// The center is applied to the model by `_updateModel`.
this._centerPoint = getPointForLocation(
newObjectData.content.centerLocation
);
this._updateModel(newObjectData);
}
if (
@@ -192,14 +207,7 @@ namespace gdjs {
this._originPoint = getPointForLocation(
newObjectData.content.originLocation
);
}
if (
oldObjectData.content.centerLocation !==
newObjectData.content.centerLocation
) {
this._centerPoint = getPointForLocation(
newObjectData.content.centerLocation
);
this._renderer.updatePosition();
}
if (
oldObjectData.content.isCastingShadow !==
@@ -216,24 +224,28 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Model3DObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Model3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
op: this._originPoint,
cp: this._centerPoint,
anis: this._animations,
ai: this._currentAnimationIndex,
ass: this._animationSpeedScale,
aet: this.getAnimationElapsedTime(),
ap: this._animationPaused,
cfd: this._crossfadeDuration,
};
}
updateFromNetworkSyncData(
networkSyncData: Model3DObjectNetworkSyncData
networkSyncData: Model3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;
@@ -247,11 +259,14 @@ namespace gdjs {
if (networkSyncData.anis !== undefined) {
this._animations = networkSyncData.anis;
}
if (networkSyncData.ass !== undefined) {
this.setAnimationSpeedScale(networkSyncData.ass);
}
if (networkSyncData.ai !== undefined) {
this.setAnimationIndex(networkSyncData.ai);
}
if (networkSyncData.ass !== undefined) {
this.setAnimationSpeedScale(networkSyncData.ass);
if (networkSyncData.aet !== undefined) {
this.setAnimationElapsedTime(networkSyncData.aet);
}
if (networkSyncData.ap !== undefined) {
if (networkSyncData.ap !== this.isAnimationPaused()) {
@@ -273,14 +288,17 @@ namespace gdjs {
const rotationX = objectData.content.rotationX || 0;
const rotationY = objectData.content.rotationY || 0;
const rotationZ = objectData.content.rotationZ || 0;
const width = objectData.content.width || 100;
const height = objectData.content.height || 100;
const depth = objectData.content.depth || 100;
const keepAspectRatio = objectData.content.keepAspectRatio;
this._renderer._updateModel(
rotationX,
rotationY,
rotationZ,
this._getOriginalWidth(),
this._getOriginalHeight(),
this._getOriginalDepth(),
width,
height,
depth,
keepAspectRatio
);
}

View File

@@ -233,6 +233,10 @@ namespace gdjs {
this._object._setOriginalWidth(scaleRatio * modelWidth);
this._object._setOriginalHeight(scaleRatio * modelHeight);
this._object._setOriginalDepth(scaleRatio * modelDepth);
} else {
this._object._setOriginalWidth(originalWidth);
this._object._setOriginalHeight(originalHeight);
this._object._setOriginalDepth(originalDepth);
}
}
@@ -286,6 +290,7 @@ namespace gdjs {
this.get3DRendererObject().remove(this._threeObject);
this.get3DRendererObject().add(threeObject);
this._threeObject = threeObject;
this.updatePosition();
this._updateShadow();
// Start the current animation on the new 3D object.

102
Extensions/3D/Skybox.ts Normal file
View File

@@ -0,0 +1,102 @@
namespace gdjs {
interface SkyboxFilterNetworkSyncData {}
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Skybox',
new (class implements gdjs.PixiFiltersTools.FilterCreator {
makeFilter(
target: EffectsTarget,
effectData: EffectData
): gdjs.PixiFiltersTools.Filter {
if (typeof THREE === 'undefined') {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
_cubeTexture: THREE.CubeTexture;
_oldBackground:
| THREE.CubeTexture
| THREE.Texture
| THREE.Color
| null = null;
_isEnabled: boolean = false;
constructor() {
this._cubeTexture = target
.getRuntimeScene()
.getGame()
.getImageManager()
.getThreeCubeTexture(
effectData.stringParameters.rightFaceResourceName,
effectData.stringParameters.leftFaceResourceName,
effectData.stringParameters.topFaceResourceName,
effectData.stringParameters.bottomFaceResourceName,
effectData.stringParameters.frontFaceResourceName,
effectData.stringParameters.backFaceResourceName
);
}
isEnabled(target: EffectsTarget): boolean {
return this._isEnabled;
}
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
if (this._isEnabled === enabled) {
return true;
}
if (enabled) {
return this.applyEffect(target);
} else {
return this.removeEffect(target);
}
}
applyEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
// TODO Add a background stack in LayerPixiRenderer to allow
// filters to stack them.
this._oldBackground = scene.background;
scene.background = this._cubeTexture;
if (!scene.environment) {
scene.environment = this._cubeTexture;
}
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
scene.background = this._oldBackground;
scene.environment = null;
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {}
getDoubleParameter(parameterName: string): number {
return 0;
}
updateStringParameter(parameterName: string, value: string): void {}
updateColorParameter(parameterName: string, value: number): void {}
getColorParameter(parameterName: string): number {
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
getNetworkSyncData(): SkyboxFilterNetworkSyncData {
return {};
}
updateFromNetworkSyncData(
syncData: SkyboxFilterNetworkSyncData
): void {}
})();
}
})()
);
}

View File

@@ -473,7 +473,14 @@ namespace gdjs {
this._parentOldMaxY = instanceContainer.getUnrotatedViewportMaxY();
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Custom objects can be resized during the events step.
// The anchor constraints must be applied on child-objects after the parent events.
const isChildObject = instanceContainer !== instanceContainer.getScene();
if (isChildObject) {
this.doStepPreEvents(instanceContainer);
}
}
private _convertCoords(
instanceContainer: gdjs.RuntimeInstanceContainer,

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

@@ -145,9 +145,11 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): BBTextObjectNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): BBTextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
text: this._text,
o: this._opacity,
c: this._color,
@@ -162,9 +164,10 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: BBTextObjectNetworkSyncData
networkSyncData: BBTextObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (this._text !== undefined) {
this.setBBText(networkSyncData.text);
}

View File

@@ -155,9 +155,11 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): BitmapTextObjectNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): BitmapTextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
text: this._text,
opa: this._opacity,
tint: this._tint,
@@ -172,9 +174,10 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: BitmapTextObjectNetworkSyncData
networkSyncData: BitmapTextObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
if (this._text !== undefined) {
this.setText(networkSyncData.text);
}
@@ -218,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

@@ -21,7 +21,9 @@ module.exports = {
.setExtensionInformation(
'DebuggerTools',
_('Debugger Tools'),
_('Allow to interact with the editor debugger from the game.'),
_(
'Allow to interact with the editor debugger from the game (notably: enable 2D debug draw, log a message in the debugger console).'
),
'Arthur Pacaud (arthuro555), Aurélien Vivet (Bouh)',
'MIT'
)

View File

@@ -12,7 +12,8 @@ This project is released under the MIT License.
#include "GDCore/Tools/Localization.h"
void DestroyOutsideBehavior::InitializeContent(gd::SerializerElement& content) {
content.SetAttribute("extraBorder", 300);
content.SetAttribute("extraBorder", 200);
content.SetAttribute("unseenGraceDistance", 10000);
}
#if defined(GD_IDE_ONLY)
@@ -27,7 +28,15 @@ DestroyOutsideBehavior::GetProperties(
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetLabel(_("Deletion margin"))
.SetDescription(_("Margin before deleting the object, in pixels"));
.SetDescription(_("Margin before deleting the object, in pixels."));
properties["unseenGraceDistance"]
.SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("unseenGraceDistance", 0)))
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetLabel(_("Unseen object grace distance"))
.SetDescription(_("If the object hasn't been visible yet, don't delete it until it travels this far beyond the screen (in pixels). Useful to avoid objects being deleted before they are visible when they spawn."));
return properties;
}
@@ -38,6 +47,8 @@ bool DestroyOutsideBehavior::UpdateProperty(
const gd::String& value) {
if (name == "extraBorder")
behaviorContent.SetAttribute("extraBorder", value.To<double>());
else if (name == "unseenGraceDistance")
behaviorContent.SetAttribute("unseenGraceDistance", value.To<double>());
else
return false;

View File

@@ -6,6 +6,7 @@ This project is released under the MIT License.
*/
#include "DestroyOutsideBehavior.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Tools/Localization.h"
@@ -19,11 +20,10 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
"outside of the bounds of the 2D camera. Useful for 2D bullets or "
"other short-lived objects. Don't use it for 3D objects in a "
"FPS/TPS game or any game with a camera not being a top view "
"(for 3D objects, prefer comparing "
"the position, for example Z position to see if an object goes "
"outside of the bound of the map). Be careful when using this "
"behavior because if the object appears outside of the screen, it "
"will be immediately removed."),
"(for 3D objects, prefer comparing the position, for example Z "
"position to see if an object goes outside of the bound of the "
"map). If the object appears outside of the screen, it's not "
"removed unless it goes beyond the unseen object grace distance."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Game mechanic")
@@ -44,34 +44,39 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
std::shared_ptr<gd::BehaviorsSharedData>())
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
aut.AddCondition("ExtraBorder",
_("Additional border (extra distance before deletion)"),
_("Compare the extra distance (in pixels) the object must "
"travel beyond the screen before it gets deleted."),
_("the additional border"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png",
"CppPlatform/Extensions/destroyoutsideicon16.png")
aut.AddExpressionAndConditionAndAction(
"number",
"ExtraBorder",
_("Additional border (extra distance before deletion)"),
_("the extra distance (in pixels) the object must "
"travel beyond the screen before it gets deleted"),
_("the additional border"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "DestroyOutside")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced()
.SetFunctionName("GetExtraBorder");
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
aut.AddAction("ExtraBorder",
_("Additional border (extra distance before deletion)"),
_("Change the extra distance (in pixels) the object must "
"travel beyond the screen before it gets deleted."),
_("the additional border"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png",
"CppPlatform/Extensions/destroyoutsideicon16.png")
// Deprecated:
aut.AddDuplicatedAction("ExtraBorder", "DestroyOutside::SetExtraBorder")
.SetHidden();
aut.AddDuplicatedCondition("ExtraBorder", "DestroyOutside::ExtraBorder")
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"number",
"UnseenGraceDistance",
_("Unseen object grace distance"),
_("the grace distance (in pixels) before deleting the object if it "
"has "
"never been visible on the screen. Useful to avoid objects being "
"deleted before they are visible when they spawn"),
_("the unseen grace distance"),
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "DestroyOutside")
.UseStandardOperatorParameters("number",
gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced()
.SetFunctionName("SetExtraBorder")
.SetGetter("GetExtraBorder");
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
}

View File

@@ -5,11 +5,11 @@ Copyright (c) 2014-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include <iostream>
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Tools/Localization.h"
#include <iostream>
void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension);
/**
@@ -29,19 +29,36 @@ class DestroyOutsideBehaviorJsExtension : public gd::PlatformExtension {
"Extensions/DestroyOutsideBehavior/"
"destroyoutsideruntimebehavior.js");
GetAllExpressionsForBehavior(
"DestroyOutsideBehavior::DestroyOutside")["ExtraBorder"]
.SetFunctionName("getExtraBorder");
GetAllConditionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::ExtraBorder"]
.SetFunctionName("getExtraBorder");
GetAllActionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::SetExtraBorder"]
.SetFunctionName("setExtraBorder")
.SetGetter("getExtraBorder");
// Deprecated:
GetAllConditionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::ExtraBorder"]
.SetFunctionName("getExtraBorder")
.SetIncludeFile(
"Extensions/DestroyOutsideBehavior/"
"destroyoutsideruntimebehavior.js");
.SetFunctionName("getExtraBorder");
GetAllActionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::ExtraBorder"]
.SetFunctionName("setExtraBorder")
.SetGetter("getExtraBorder")
.SetIncludeFile(
"Extensions/DestroyOutsideBehavior/"
"destroyoutsideruntimebehavior.js");
.SetGetter("getExtraBorder");
GetAllExpressionsForBehavior(
"DestroyOutsideBehavior::DestroyOutside")["UnseenGraceDistance"]
.SetFunctionName("getUnseenGraceDistance");
GetAllConditionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::UnseenGraceDistance"]
.SetFunctionName("getUnseenGraceDistance");
GetAllActionsForBehavior("DestroyOutsideBehavior::DestroyOutside")
["DestroyOutsideBehavior::DestroyOutside::SetUnseenGraceDistance"]
.SetFunctionName("setUnseenGraceDistance")
.SetGetter("getUnseenGraceDistance");
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
};

View File

@@ -8,7 +8,9 @@ namespace gdjs {
* The DestroyOutsideRuntimeBehavior represents a behavior that destroys the object when it leaves the screen.
*/
export class DestroyOutsideRuntimeBehavior extends gdjs.RuntimeBehavior {
_extraBorder: any;
_extraBorder: float;
_unseenGraceDistance: float;
_hasBeenOnScreen: boolean;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -17,12 +19,20 @@ namespace gdjs {
) {
super(instanceContainer, behaviorData, owner);
this._extraBorder = behaviorData.extraBorder || 0;
this._unseenGraceDistance = behaviorData.unseenGraceDistance || 0;
this._hasBeenOnScreen = false;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
this._extraBorder = newBehaviorData.extraBorder;
}
if (
oldBehaviorData.unseenGraceDistance !==
newBehaviorData.unseenGraceDistance
) {
this._unseenGraceDistance = newBehaviorData.unseenGraceDistance;
}
return true;
}
@@ -35,23 +45,47 @@ namespace gdjs {
const ocy = this.owner.getDrawableY() + this.owner.getCenterY();
const layer = instanceContainer.getLayer(this.owner.getLayer());
const boundingCircleRadius = Math.sqrt(ow * ow + oh * oh) / 2.0;
const cameraLeft = layer.getCameraX() - layer.getCameraWidth() / 2;
const cameraRight = layer.getCameraX() + layer.getCameraWidth() / 2;
const cameraTop = layer.getCameraY() - layer.getCameraHeight() / 2;
const cameraBottom = layer.getCameraY() + layer.getCameraHeight() / 2;
if (
ocx + boundingCircleRadius + this._extraBorder <
layer.getCameraX() - layer.getCameraWidth() / 2 ||
ocx - boundingCircleRadius - this._extraBorder >
layer.getCameraX() + layer.getCameraWidth() / 2 ||
ocy + boundingCircleRadius + this._extraBorder <
layer.getCameraY() - layer.getCameraHeight() / 2 ||
ocy - boundingCircleRadius - this._extraBorder >
layer.getCameraY() + layer.getCameraHeight() / 2
ocx + boundingCircleRadius + this._extraBorder < cameraLeft ||
ocx - boundingCircleRadius - this._extraBorder > cameraRight ||
ocy + boundingCircleRadius + this._extraBorder < cameraTop ||
ocy - boundingCircleRadius - this._extraBorder > cameraBottom
) {
//We are outside the camera area.
this.owner.deleteFromScene();
if (this._hasBeenOnScreen) {
// Object is outside the camera area and object was previously seen inside it:
// delete it now.
this.owner.deleteFromScene();
} else if (
ocx + boundingCircleRadius + this._unseenGraceDistance < cameraLeft ||
ocx - boundingCircleRadius - this._unseenGraceDistance >
cameraRight ||
ocy + boundingCircleRadius + this._unseenGraceDistance < cameraTop ||
ocy - boundingCircleRadius - this._unseenGraceDistance > cameraBottom
) {
// Object is outside the camera area and also outside the grace distance:
// force deletion.
this.owner.deleteFromScene();
} else {
// Object is outside the camera area but inside the grace distance
// and was never seen inside the camera area: don't delete it yet.
}
} else {
this._hasBeenOnScreen = true;
}
}
/**
* Set an additional border to the camera viewport as a buffer before the object gets destroyed.
* Set the additional border outside the camera area.
*
* If the object goes beyond the camera area and this border, it will be deleted (unless it was
* never seen inside the camera area and this border before, in which case it will be deleted
* according to the grace distance).
* @param val Border in pixels.
*/
setExtraBorder(val: number): void {
@@ -59,12 +93,36 @@ namespace gdjs {
}
/**
* Get the additional border of the camera viewport buffer which triggers the destruction of an object.
* Get the additional border outside the camera area.
* @return The additional border around the camera viewport in pixels
*/
getExtraBorder(): number {
return this._extraBorder;
}
/**
* Change the grace distance before an object is deleted if it's outside the camera area
* and was never seen inside the camera area. Typically useful to avoid objects being deleted
* before they are visible when they spawn.
*/
setUnseenGraceDistance(val: number): void {
this._unseenGraceDistance = val;
}
/**
* Get the grace distance before an object is deleted if it's outside the camera area
* and was never seen inside the camera area.
*/
getUnseenGraceDistance(): number {
return this._unseenGraceDistance;
}
/**
* Check if this object has been visible on screen (precisely: inside the camera area *including* the extra border).
*/
hasBeenOnScreen(): boolean {
return this._hasBeenOnScreen;
}
}
gdjs.registerBehavior(
'DestroyOutsideBehavior::DestroyOutside',

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

@@ -87,16 +87,21 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): LightNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): LightNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
rad: this.getRadius(),
col: this.getColor(),
};
}
updateFromNetworkSyncData(networkSyncData: LightNetworkSyncData): void {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: LightNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.rad !== undefined) {
this.setRadius(networkSyncData.rad);

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

@@ -729,7 +729,9 @@ namespace gdjs {
behavior.playerNumber = ownerPlayerNumber;
}
instance.updateFromNetworkSyncData(messageData);
instance.updateFromNetworkSyncData(messageData, {
clearInputs: false,
});
setLastClockReceivedForInstanceOnScene({
sceneNetworkId,
@@ -1737,7 +1739,7 @@ namespace gdjs {
return;
}
runtimeScene.updateFromNetworkSyncData(messageData);
runtimeScene.updateFromNetworkSyncData(messageData, {});
} else {
// If the game is not ready to receive game update messages, we need to save the data for later use.
// This can happen when joining a game that is already running.
@@ -1890,7 +1892,7 @@ namespace gdjs {
const messageData = message.getData();
const messageSender = message.getSender();
if (gdjs.multiplayer.isReadyToSendOrReceiveGameUpdateMessages()) {
runtimeScene.getGame().updateFromNetworkSyncData(messageData);
runtimeScene.getGame().updateFromNetworkSyncData(messageData, {});
} else {
// If the game is not ready to receive game update messages, we need to save the data for later use.
// This can happen when joining a game that is already running.
@@ -1918,7 +1920,7 @@ namespace gdjs {
// Reapply the game saved updates.
lastReceivedGameSyncDataUpdates.getUpdates().forEach((messageData) => {
debugLogger.info(`Reapplying saved update of game.`);
runtimeScene.getGame().updateFromNetworkSyncData(messageData);
runtimeScene.getGame().updateFromNetworkSyncData(messageData, {});
});
// Game updates are always applied properly, so we can clear them.
lastReceivedGameSyncDataUpdates.clear();
@@ -1937,7 +1939,7 @@ namespace gdjs {
debugLogger.info(`Reapplying saved update of scene ${sceneNetworkId}.`);
runtimeScene.updateFromNetworkSyncData(messageData);
runtimeScene.updateFromNetworkSyncData(messageData, {});
// We only remove the message if it was successfully applied, so it can be reapplied later,
// in case we were not on the right scene.
lastReceivedSceneSyncDataUpdates.remove(messageData);

View File

@@ -278,7 +278,7 @@ namespace gdjs {
const instanceNetworkId = this._getOrCreateInstanceNetworkId();
const objectName = this.owner.getName();
const objectNetworkSyncData = this.owner.getNetworkSyncData();
const objectNetworkSyncData = this.owner.getNetworkSyncData({});
// this._logToConsoleWithThrottle(
// `Synchronizing object ${this.owner.getName()} (instance ${
@@ -448,7 +448,7 @@ namespace gdjs {
objectOwner: this.playerNumber,
objectName,
instanceNetworkId,
objectNetworkSyncData: this.owner.getNetworkSyncData(),
objectNetworkSyncData: this.owner.getNetworkSyncData({}),
sceneNetworkId,
});
this._sendDataToPeersWithIncreasedClock(
@@ -598,7 +598,7 @@ namespace gdjs {
debugLogger.info(
'Sending update message to move the object immediately.'
);
const objectNetworkSyncData = this.owner.getNetworkSyncData();
const objectNetworkSyncData = this.owner.getNetworkSyncData({});
const {
messageName: updateMessageName,
messageData: updateMessageData,

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);
}
@@ -119,18 +123,21 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): PanelSpriteNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): PanelSpriteNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
op: this.getOpacity(),
color: this.getColor(),
};
}
updateFromNetworkSyncData(
networkSyncData: PanelSpriteNetworkSyncData
networkSyncData: PanelSpriteNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
// Texture is not synchronized, see if this is asked or not.
@@ -163,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
);
}
/**
@@ -244,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;
@@ -370,9 +374,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): ParticleEmitterObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): ParticleEmitterObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
prms: this.particleRotationMinSpeed,
prmx: this.particleRotationMaxSpeed,
mpc: this.maxParticlesCount,
@@ -399,9 +405,10 @@ namespace gdjs {
}
updateFromNetworkSyncData(
syncData: ParticleEmitterObjectNetworkSyncData
syncData: ParticleEmitterObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(syncData);
super.updateFromNetworkSyncData(syncData, options);
if (syncData.x !== undefined) {
this.setX(syncData.x);
}
@@ -799,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

@@ -6,7 +6,7 @@ namespace gdjs {
const logger = new gdjs.Logger('Pathfinding behavior');
interface PathfindingNetworkSyncDataType {
// Syncing the path should be enough to have a good prediction.
// Syncing the path and its position on it should be enough to have a good prediction.
path: FloatPoint[];
pf: boolean;
sp: number;
@@ -15,6 +15,7 @@ namespace gdjs {
tss: number;
re: boolean;
ma: number;
dos: number;
}
export interface PathfindingNetworkSyncData extends BehaviorNetworkSyncData {
@@ -133,9 +134,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): PathfindingNetworkSyncData {
getNetworkSyncData(
options: GetNetworkSyncDataOptions
): PathfindingNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
path: this._path,
pf: this._pathFound,
@@ -145,14 +148,16 @@ namespace gdjs {
tss: this._totalSegmentDistance,
re: this._reachedEnd,
ma: this._movementAngle,
dos: this._distanceOnSegment,
},
};
}
updateFromNetworkSyncData(
networkSyncData: PathfindingNetworkSyncData
networkSyncData: PathfindingNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.path !== undefined) {
this._path = behaviorSpecificProps.path;
@@ -181,6 +186,9 @@ namespace gdjs {
if (behaviorSpecificProps.ma !== undefined) {
this._movementAngle = behaviorSpecificProps.ma;
}
if (behaviorSpecificProps.dos !== undefined) {
this._distanceOnSegment = behaviorSpecificProps.dos;
}
}
setCellWidth(width: float): void {

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

@@ -21,7 +21,11 @@ module.exports = {
.setExtensionInformation(
'Physics2',
_('2D Physics Engine'),
"The 2D physics engine simulates realistic object physics, with gravity, forces, collisions, joints, etc. It's perfect for 2D games that need to have realistic behaving objects and a gameplay centered around it.",
"The 2D physics engine simulates realistic object physics, with gravity, forces, collisions, joints, etc. It's perfect for 2D games that need to have realistic behaving objects and a gameplay centered around it.\n" +
'\n' +
'Objects like floors or wall objects should usually be set to "Static" as type. Objects that should be moveable are usually "Dynamic" (default). "Kinematic" objects (typically, players or controlled characters) are only moved by their "linear velocity" and "angular velocity" - they can interact with other objects but only these other objects will move.\n' +
'\n' +
'Forces (and impulses) are expressed in all conditions/expressions/actions of the 2D physics engine in Newtons (N). Typical values for a force are 10-200 N. One meter is 100 pixels by default in the game (check the world scale). Mass is expressed in kilograms (kg).',
'Florian Rival, Franco Maciel',
'MIT'
)
@@ -527,6 +531,7 @@ module.exports = {
physics2Behavior,
sharedData
)
.markAsIrrelevantForChildObjects()
.setIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.addIncludeFile('Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.js')
.addRequiredFile('Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.wasm')

View File

@@ -499,7 +499,9 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): Physics2NetworkSyncData {
getNetworkSyncData(
options: GetNetworkSyncDataOptions
): Physics2NetworkSyncData {
const bodyProps = this._body
? {
tpx: this._body.GetTransform().get_p().get_x(),
@@ -520,7 +522,7 @@ namespace gdjs {
aw: undefined,
};
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
...bodyProps,
layers: this.layers,
@@ -529,45 +531,13 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(networkSyncData: Physics2NetworkSyncData) {
super.updateFromNetworkSyncData(networkSyncData);
updateFromNetworkSyncData(
networkSyncData: Physics2NetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (
behaviorSpecificProps.tpx !== undefined &&
behaviorSpecificProps.tpy !== undefined &&
behaviorSpecificProps.tqa !== undefined
) {
if (this._body) {
this._body.SetTransform(
this.b2Vec2(behaviorSpecificProps.tpx, behaviorSpecificProps.tpy),
behaviorSpecificProps.tqa
);
}
}
if (
behaviorSpecificProps.lvx !== undefined &&
behaviorSpecificProps.lvy !== undefined
) {
if (this._body) {
this._body.SetLinearVelocity(
this.b2Vec2(behaviorSpecificProps.lvx, behaviorSpecificProps.lvy)
);
}
}
if (behaviorSpecificProps.av !== undefined) {
if (this._body) {
this._body.SetAngularVelocity(behaviorSpecificProps.av);
}
}
if (behaviorSpecificProps.aw !== undefined) {
if (this._body) {
this._body.SetAwake(behaviorSpecificProps.aw);
}
}
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
@@ -576,6 +546,38 @@ namespace gdjs {
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
}
this.updateBodyFromObject();
if (!this._body) return;
if (
behaviorSpecificProps.tpx !== undefined &&
behaviorSpecificProps.tpy !== undefined &&
behaviorSpecificProps.tqa !== undefined
) {
this._body.SetTransform(
this.b2Vec2(behaviorSpecificProps.tpx, behaviorSpecificProps.tpy),
behaviorSpecificProps.tqa
);
}
if (
behaviorSpecificProps.lvx !== undefined &&
behaviorSpecificProps.lvy !== undefined
) {
this._body.SetLinearVelocity(
this.b2Vec2(behaviorSpecificProps.lvx, behaviorSpecificProps.lvy)
);
}
if (behaviorSpecificProps.av !== undefined) {
this._body.SetAngularVelocity(behaviorSpecificProps.av);
}
if (behaviorSpecificProps.aw !== undefined) {
this._body.SetAwake(behaviorSpecificProps.aw);
}
}
onDeActivate() {

View File

@@ -21,7 +21,11 @@ module.exports = {
.setExtensionInformation(
'Physics3D',
_('3D physics engine'),
"The 3D physics engine simulates realistic object physics, with gravity, forces, collisions, joints, etc. It's perfect for almost all 3D games.",
"The 3D physics engine simulates realistic object physics, with gravity, forces, collisions, joints, etc. It's perfect for almost all 3D games.\n" +
'\n' +
'Objects like floors or wall objects should usually be set to "Static" as type. Objects that should be moveable are usually "Dynamic" (default). "Kinematic" objects (typically, players or controlled characters) are only moved by their "linear velocity" and "angular velocity" - they can interact with other objects but only these other objects will move.\n' +
'\n' +
'Forces (and impulses) are expressed in all conditions/expressions/actions of the 3D physics engine in Newtons (N). Typical values for a force are 10-200 N. One meter is 100 pixels by default in the game (check the world scale). Mass is expressed in kilograms (kg).',
'Florian Rival',
'MIT'
)
@@ -675,6 +679,7 @@ module.exports = {
behavior,
sharedData
)
.markAsIrrelevantForChildObjects()
.addIncludeFile(
'Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js'
)
@@ -2043,7 +2048,12 @@ module.exports = {
'PhysicsCharacter3D',
_('3D physics character'),
'PhysicsCharacter3D',
_('Jump and run on platforms.'),
_(
'Allow an object to jump and run on platforms that have the 3D physics behavior' +
'(and which are generally set to "Static" as type, unless the platform is animated/moved in events).\n' +
'\n' +
'This behavior is usually used with one or more "mapper" behavior to let the player move it.'
),
'',
'JsPlatform/Extensions/physics_character3d.svg',
'PhysicsCharacter3D',
@@ -2612,7 +2622,7 @@ module.exports = {
'JumpSustainTime',
_('Jump sustain time'),
_(
'the jump sustain time of an object. This is the time during which keeping the jump button held allow the initial jump speed to be maintained.'
'the jump sustain time of an object. This is the time during which keeping the jump button held allow the initial jump speed to be maintained'
),
_('the jump sustain time'),
_('Character configuration'),
@@ -3300,7 +3310,11 @@ module.exports = {
'PhysicsCar3D',
_('3D physics car'),
'PhysicsCar3D',
_('Simulate a realistic car using the 3D physics engine.'),
_(
"Simulate a realistic car using the 3D physics engine. This is mostly useful for the car controlled by the player (it's usually too complex for other cars in a game).\n" +
'\n' +
'This behavior is usually used with one or more "mapper" behavior to let the player move it.'
),
'',
'JsPlatform/Extensions/physics_car3d.svg',
'PhysicsCar3D',

View File

@@ -495,7 +495,9 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): Physics3DNetworkSyncData {
override getNetworkSyncData(
options: GetNetworkSyncDataOptions
): Physics3DNetworkSyncData {
let bodyProps;
if (this._body) {
const position = this._body.GetPosition();
@@ -537,7 +539,7 @@ namespace gdjs {
};
}
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
...bodyProps,
layers: this.layers,
@@ -547,27 +549,40 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: Physics3DNetworkSyncData
networkSyncData: Physics3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
}
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
}
this._needToRecreateShape = true;
this._needToRecreateBody = true;
this.updateBodyFromObject();
if (!this._body) return;
if (
behaviorSpecificProps.px !== undefined &&
behaviorSpecificProps.py !== undefined &&
behaviorSpecificProps.pz !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetPosition(
this._body.GetID(),
this.getRVec3(
behaviorSpecificProps.px,
behaviorSpecificProps.py,
behaviorSpecificProps.pz
),
Jolt.EActivation_DontActivate
);
}
this._sharedData.bodyInterface.SetPosition(
this._body.GetID(),
this.getRVec3(
behaviorSpecificProps.px,
behaviorSpecificProps.py,
behaviorSpecificProps.pz
),
Jolt.EActivation_DontActivate
);
}
if (
behaviorSpecificProps.rx !== undefined &&
@@ -575,56 +590,44 @@ namespace gdjs {
behaviorSpecificProps.rz !== undefined &&
behaviorSpecificProps.rw !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetRotation(
this._body.GetID(),
this.getQuat(
behaviorSpecificProps.rx,
behaviorSpecificProps.ry,
behaviorSpecificProps.rz,
behaviorSpecificProps.rw
),
Jolt.EActivation_DontActivate
);
}
this._sharedData.bodyInterface.SetRotation(
this._body.GetID(),
this.getQuat(
behaviorSpecificProps.rx,
behaviorSpecificProps.ry,
behaviorSpecificProps.rz,
behaviorSpecificProps.rw
),
Jolt.EActivation_DontActivate
);
}
if (
behaviorSpecificProps.lvx !== undefined &&
behaviorSpecificProps.lvy !== undefined &&
behaviorSpecificProps.lvz !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetLinearVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.lvx,
behaviorSpecificProps.lvy,
behaviorSpecificProps.lvz
)
);
}
this._sharedData.bodyInterface.SetLinearVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.lvx,
behaviorSpecificProps.lvy,
behaviorSpecificProps.lvz
)
);
}
if (
behaviorSpecificProps.avx !== undefined &&
behaviorSpecificProps.avy !== undefined &&
behaviorSpecificProps.avz !== undefined
) {
if (this._body) {
this._sharedData.bodyInterface.SetAngularVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.avx,
behaviorSpecificProps.avy,
behaviorSpecificProps.avz
)
);
}
}
if (behaviorSpecificProps.layers !== undefined) {
this.layers = behaviorSpecificProps.layers;
}
if (behaviorSpecificProps.masks !== undefined) {
this.masks = behaviorSpecificProps.masks;
this._sharedData.bodyInterface.SetAngularVelocity(
this._body.GetID(),
this.getVec3(
behaviorSpecificProps.avx,
behaviorSpecificProps.avy,
behaviorSpecificProps.avz
)
);
}
}
@@ -921,31 +924,58 @@ namespace gdjs {
this.updateBodyFromObject();
}
recreateBody() {
if (!this._body) {
this._createBody();
return;
}
recreateBody(previousBodyData?: {
linearVelocityX: float;
linearVelocityY: float;
linearVelocityZ: float;
angularVelocityX: float;
angularVelocityY: float;
angularVelocityZ: float;
}) {
const bodyInterface = this._sharedData.bodyInterface;
const linearVelocity = this._body.GetLinearVelocity();
const linearVelocityX = linearVelocity.GetX();
const linearVelocityY = linearVelocity.GetY();
const linearVelocityZ = linearVelocity.GetZ();
const angularVelocity = this._body.GetAngularVelocity();
const angularVelocityX = angularVelocity.GetX();
const angularVelocityY = angularVelocity.GetY();
const angularVelocityZ = angularVelocity.GetZ();
const linearVelocityX = previousBodyData
? previousBodyData.linearVelocityX
: this._body
? this._body.GetLinearVelocity().GetX()
: 0;
const linearVelocityY = previousBodyData
? previousBodyData.linearVelocityY
: this._body
? this._body.GetLinearVelocity().GetY()
: 0;
const linearVelocityZ = previousBodyData
? previousBodyData.linearVelocityZ
: this._body
? this._body.GetLinearVelocity().GetZ()
: 0;
const angularVelocityX = previousBodyData
? previousBodyData.angularVelocityX
: this._body
? this._body.GetAngularVelocity().GetX()
: 0;
const angularVelocityY = previousBodyData
? previousBodyData.angularVelocityY
: this._body
? this._body.GetAngularVelocity().GetY()
: 0;
const angularVelocityZ = previousBodyData
? previousBodyData.angularVelocityZ
: this._body
? this._body.GetAngularVelocity().GetZ()
: 0;
this.bodyUpdater.destroyBody();
this._contactsEndedThisFrame.length = 0;
this._contactsStartedThisFrame.length = 0;
this._currentContacts.length = 0;
if (this._body) {
this.bodyUpdater.destroyBody();
this._contactsEndedThisFrame.length = 0;
this._contactsStartedThisFrame.length = 0;
this._currentContacts.length = 0;
}
this._createBody();
if (!this._body) {
return;
}
const bodyID = this._body.GetID();
bodyInterface.SetLinearVelocity(
bodyID,

View File

@@ -12,6 +12,7 @@ namespace gdjs {
etm: float;
esm: float;
ei: float;
es: float;
}
export interface PhysicsCar3DNetworkSyncData extends BehaviorNetworkSyncData {
@@ -96,7 +97,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
private _dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -168,12 +169,37 @@ namespace gdjs {
this._isHookedToPhysicsStep = true;
}
// Destroy the body before switching the bodyUpdater,
// to ensure the body of the previous bodyUpdater is not left alive.
// (would be a memory leak and would create a phantom body in the physics world)
// But transfer the linear and angular velocity to the new body,
// so the body doesn't stop when it is recreated.
let previousBodyData = {
linearVelocityX: 0,
linearVelocityY: 0,
linearVelocityZ: 0,
angularVelocityX: 0,
angularVelocityY: 0,
angularVelocityZ: 0,
};
if (behavior._body) {
const linearVelocity = behavior._body.GetLinearVelocity();
previousBodyData.linearVelocityX = linearVelocity.GetX();
previousBodyData.linearVelocityY = linearVelocity.GetY();
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
const angularVelocity = behavior._body.GetAngularVelocity();
previousBodyData.angularVelocityX = angularVelocity.GetX();
previousBodyData.angularVelocityY = angularVelocity.GetY();
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
behavior.bodyUpdater.destroyBody();
}
behavior.bodyUpdater =
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
this,
behavior.bodyUpdater
);
behavior.recreateBody();
behavior.recreateBody(previousBodyData);
return this._physics3D;
}
@@ -273,13 +299,15 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): PhysicsCar3DNetworkSyncData {
override getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): PhysicsCar3DNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
props: {
lek: this._wasLeftKeyPressed,
rik: this._wasRightKeyPressed,
@@ -291,14 +319,16 @@ namespace gdjs {
etm: this._engineTorqueMax,
esm: this._engineSpeedMax,
ei: this._engineInertia,
es: this.getEngineSpeed(),
},
};
}
override updateFromNetworkSyncData(
networkSyncData: PhysicsCar3DNetworkSyncData
networkSyncData: PhysicsCar3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
this._hasPressedForwardKey = behaviorSpecificProps.upk;
@@ -311,9 +341,15 @@ namespace gdjs {
this._engineTorqueMax = behaviorSpecificProps.etm;
this._engineSpeedMax = behaviorSpecificProps.esm;
this._engineInertia = behaviorSpecificProps.ei;
if (this._vehicleController) {
this._vehicleController
.GetEngine()
.SetCurrentRPM(behaviorSpecificProps.es);
}
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// When the object is synchronized from the network, the inputs must not be cleared,
// except if asked specifically.
this._clearInputsBetweenFrames = !!options.clearInputs;
}
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
@@ -490,7 +526,7 @@ namespace gdjs {
this._previousAcceleratorStickForce = this._acceleratorStickForce;
this._previousSteeringStickForce = this._steeringStickForce;
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
this._hasPressedForwardKey = false;
this._hasPressedBackwardKey = false;
this._hasPressedRightKey = false;

View File

@@ -2,11 +2,24 @@
namespace gdjs {
interface PhysicsCharacter3DNetworkSyncDataType {
sma: float;
shm: float;
grav: float;
mfs: float;
facc: float;
fdec: float;
fsm: float;
sacc: float;
sdec: float;
ssm: float;
jumpspeed: float;
jumpsustime: float;
sbpa: boolean;
fwa: float;
fws: float;
sws: float;
fs: float;
js: float;
cfs: float;
cjs: float;
cj: boolean;
lek: boolean;
rik: boolean;
@@ -102,7 +115,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
private _dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
/**
* A very small value compare to 1 pixel, yet very huge compare to rounding errors.
@@ -207,10 +220,35 @@ namespace gdjs {
this._isHookedToPhysicsStep = true;
}
// Destroy the body before switching the bodyUpdater,
// to ensure the body of the previous bodyUpdater is not left alive.
// (would be a memory leak and would create a phantom body in the physics world)
// But transfer the linear and angular velocity to the new body,
// so the body doesn't stop when it is recreated.
let previousBodyData = {
linearVelocityX: 0,
linearVelocityY: 0,
linearVelocityZ: 0,
angularVelocityX: 0,
angularVelocityY: 0,
angularVelocityZ: 0,
};
if (behavior._body) {
const linearVelocity = behavior._body.GetLinearVelocity();
previousBodyData.linearVelocityX = linearVelocity.GetX();
previousBodyData.linearVelocityY = linearVelocity.GetY();
previousBodyData.linearVelocityZ = linearVelocity.GetZ();
const angularVelocity = behavior._body.GetAngularVelocity();
previousBodyData.angularVelocityX = angularVelocity.GetX();
previousBodyData.angularVelocityY = angularVelocity.GetY();
previousBodyData.angularVelocityZ = angularVelocity.GetZ();
behavior.bodyUpdater.destroyBody();
}
behavior.bodyUpdater =
new gdjs.PhysicsCharacter3DRuntimeBehavior.CharacterBodyUpdater(this);
behavior.collisionChecker = this.collisionChecker;
behavior.recreateBody();
behavior.recreateBody(previousBodyData);
// Always begin in the direction of the object.
this._forwardAngle = this.owner.getAngle();
@@ -277,19 +315,34 @@ namespace gdjs {
return true;
}
override getNetworkSyncData(): PhysicsCharacter3DNetworkSyncData {
override getNetworkSyncData(
options: GetNetworkSyncDataOptions
): PhysicsCharacter3DNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(options),
props: {
sma: this._slopeMaxAngle,
shm: this._stairHeightMax,
grav: this._gravity,
mfs: this._maxFallingSpeed,
facc: this._forwardAcceleration,
fdec: this._forwardDeceleration,
fsm: this._forwardSpeedMax,
sacc: this._sidewaysAcceleration,
sdec: this._sidewaysDeceleration,
ssm: this._sidewaysSpeedMax,
jumpspeed: this._jumpSpeed,
jumpsustime: this._jumpSustainTime,
fwa: this._forwardAngle,
sbpa: this._shouldBindObjectAndForwardAngle,
fws: this._currentForwardSpeed,
sws: this._currentSidewaysSpeed,
fs: this._currentFallSpeed,
js: this._currentJumpSpeed,
cfs: this._currentFallSpeed,
cjs: this._currentJumpSpeed,
cj: this._canJump,
lek: this._wasLeftKeyPressed,
rik: this._wasRightKeyPressed,
@@ -306,16 +359,30 @@ namespace gdjs {
}
override updateFromNetworkSyncData(
networkSyncData: PhysicsCharacter3DNetworkSyncData
networkSyncData: PhysicsCharacter3DNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
this._slopeMaxAngle = behaviorSpecificProps.sma;
this._stairHeightMax = behaviorSpecificProps.shm;
this._gravity = behaviorSpecificProps.grav;
this._maxFallingSpeed = behaviorSpecificProps.mfs;
this._forwardAcceleration = behaviorSpecificProps.facc;
this._forwardDeceleration = behaviorSpecificProps.fdec;
this._forwardSpeedMax = behaviorSpecificProps.fsm;
this._sidewaysAcceleration = behaviorSpecificProps.sacc;
this._sidewaysDeceleration = behaviorSpecificProps.sdec;
this._sidewaysSpeedMax = behaviorSpecificProps.ssm;
this._jumpSpeed = behaviorSpecificProps.jumpspeed;
this._jumpSustainTime = behaviorSpecificProps.jumpsustime;
this._forwardAngle = behaviorSpecificProps.fwa;
this._shouldBindObjectAndForwardAngle = behaviorSpecificProps.sbpa;
this._currentForwardSpeed = behaviorSpecificProps.fws;
this._currentSidewaysSpeed = behaviorSpecificProps.sws;
this._currentFallSpeed = behaviorSpecificProps.fs;
this._currentJumpSpeed = behaviorSpecificProps.js;
this._currentFallSpeed = behaviorSpecificProps.cfs;
this._currentJumpSpeed = behaviorSpecificProps.cjs;
this._canJump = behaviorSpecificProps.cj;
this._hasPressedForwardKey = behaviorSpecificProps.upk;
this._hasPressedBackwardKey = behaviorSpecificProps.dok;
@@ -328,8 +395,8 @@ namespace gdjs {
this._timeSinceCurrentJumpStart = behaviorSpecificProps.tscjs;
this._jumpKeyHeldSinceJumpStart = behaviorSpecificProps.jkhsjs;
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// Clear user inputs between frames only if requested.
this._clearInputsBetweenFrames = !!options.clearInputs;
}
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
@@ -650,7 +717,7 @@ namespace gdjs {
this._wasJumpKeyPressed = this._hasPressedJumpKey;
this._wasStickUsed = this._hasUsedStick;
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
this._hasPressedForwardKey = false;
this._hasPressedBackwardKey = false;
this._hasPressedRightKey = false;

View File

@@ -37,7 +37,8 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
"res/physics-deprecated32.png",
"PhysicsBehavior",
std::make_shared<PhysicsBehavior>(),
std::make_shared<ScenePhysicsDatas>());
std::make_shared<ScenePhysicsDatas>())
.MarkAsIrrelevantForChildObjects();
aut.AddAction("SetStatic",
("Make the object static"),

View File

@@ -147,7 +147,7 @@ namespace gdjs {
// This is useful when the object is synchronized by an external source
// like in a multiplayer game, and we want to be able to predict the
// movement of the object, even if the inputs are not updated every frame.
_dontClearInputsBetweenFrames: boolean = false;
private _clearInputsBetweenFrames: boolean = true;
// This is useful when the object is synchronized over the network,
// object is controlled by the network and we want to ensure the current player
// cannot control it.
@@ -227,14 +227,16 @@ namespace gdjs {
this._state = this._falling;
}
getNetworkSyncData(): PlatformerObjectNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): PlatformerObjectNetworkSyncData {
// This method is called, so we are synchronizing this object.
// Let's clear the inputs between frames as we control it.
this._dontClearInputsBetweenFrames = false;
this._clearInputsBetweenFrames = true;
this._ignoreDefaultControlsAsSyncedByNetwork = false;
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
props: {
cs: this._currentSpeed,
@@ -263,11 +265,52 @@ namespace gdjs {
}
updateFromNetworkSyncData(
networkSyncData: PlatformerObjectNetworkSyncData
networkSyncData: PlatformerObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
) {
super.updateFromNetworkSyncData(networkSyncData);
super.updateFromNetworkSyncData(networkSyncData, options);
const behaviorSpecificProps = networkSyncData.props;
switch (behaviorSpecificProps.sn) {
case 'Falling':
if (behaviorSpecificProps.sn !== this._state.toString()) {
this._setFalling();
}
this._falling.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
break;
case 'OnFloor':
// Let it handle automatically as we don't know which platform to land on.
// @ts-ignore - we assume it's OnFloorStateNetworkSyncData
this._onFloor.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
break;
case 'Jumping':
if (behaviorSpecificProps.sn !== this._state.toString()) {
this._setJumping();
}
// @ts-ignore - we assume it's JumpingStateNetworkSyncData
this._jumping.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
break;
case 'GrabbingPlatform':
// Let it handle automatically as we don't know which platform to grab.
this._grabbingPlatform.updateFromNetworkSyncData(
// @ts-ignore - we assume it's GrabbingPlatformStateNetworkSyncData
behaviorSpecificProps.ssd
);
break;
case 'OnLadder':
if (behaviorSpecificProps.sn !== this._state.toString()) {
this._setOnLadder();
}
this._onLadder.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
break;
default:
console.error(
'Unknown state name: ' + behaviorSpecificProps.sn + '.'
);
break;
}
if (behaviorSpecificProps.cs !== this._currentSpeed) {
this._currentSpeed = behaviorSpecificProps.cs;
}
@@ -317,39 +360,10 @@ namespace gdjs {
this._jumpKeyHeldSinceJumpStart = behaviorSpecificProps.jkhsjs;
}
if (behaviorSpecificProps.sn !== this._state.toString()) {
switch (behaviorSpecificProps.sn) {
case 'Falling':
this._setFalling();
break;
case 'OnFloor':
// Let it handle automatically as we don't know which platform to land on.
break;
case 'Jumping':
this._setJumping();
break;
case 'GrabbingPlatform':
// Let it handle automatically as we don't know which platform to grab.
break;
case 'OnLadder':
this._setOnLadder();
break;
default:
console.error(
'Unknown state name: ' + behaviorSpecificProps.sn + '.'
);
break;
}
}
if (behaviorSpecificProps.sn === this._state.toString()) {
this._state.updateFromNetworkSyncData(behaviorSpecificProps.ssd);
}
// When the object is synchronized from the network, the inputs must not be cleared.
this._dontClearInputsBetweenFrames = true;
// Clear user inputs between frames only if requested.
this._clearInputsBetweenFrames = !!options.clearInputs;
// And we are not using the default controls.
this._ignoreDefaultControlsAsSyncedByNetwork = true;
this._ignoreDefaultControlsAsSyncedByNetwork = !options.keepControl;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
@@ -545,8 +559,9 @@ namespace gdjs {
this._wasJumpKeyPressed = this._jumpKey;
this._wasReleasePlatformKeyPressed = this._releasePlatformKey;
this._wasReleaseLadderKeyPressed = this._releaseLadderKey;
//4) Do not forget to reset pressed keys
if (!this._dontClearInputsBetweenFrames) {
if (this._clearInputsBetweenFrames) {
// Reset the keys only if the inputs are not supposed to survive between frames.
// (Most of the time, except if this object is synchronized by an external source)
this._leftKey = false;

View File

@@ -157,6 +157,57 @@ namespace gdjs {
return true;
}
trackChangesAndUpdateManagerIfNeeded() {
if (!this.activated() && this._registeredInManager) {
this._manager.removePlatform(this);
this._registeredInManager = false;
} else {
if (this.activated() && !this._registeredInManager) {
this._manager.addPlatform(this);
this._registeredInManager = true;
}
}
if (
this._oldX !== this.owner.getX() ||
this._oldY !== this.owner.getY() ||
this._oldWidth !== this.owner.getWidth() ||
this._oldHeight !== this.owner.getHeight() ||
this._oldAngle !== this.owner.getAngle()
) {
if (this._registeredInManager) {
this._manager.removePlatform(this);
this._manager.addPlatform(this);
}
this._oldX = this.owner.getX();
this._oldY = this.owner.getY();
this._oldWidth = this.owner.getWidth();
this._oldHeight = this.owner.getHeight();
this._oldAngle = this.owner.getAngle();
}
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): BehaviorNetworkSyncData {
return super.getNetworkSyncData(syncOptions);
}
updateFromNetworkSyncData(
networkSyncData: BehaviorNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
this.trackChangesAndUpdateManagerIfNeeded();
}
onCreated(): void {
// Register it right away if activated,
// so it can be used by platformer objects in that same frame.
this.trackChangesAndUpdateManagerIfNeeded();
}
onDestroy() {
if (this._manager && this._registeredInManager) {
this._manager.removePlatform(this);
@@ -175,34 +226,7 @@ namespace gdjs {
}*/
//Make sure the platform is or is not in the platforms manager.
if (!this.activated() && this._registeredInManager) {
this._manager.removePlatform(this);
this._registeredInManager = false;
} else {
if (this.activated() && !this._registeredInManager) {
this._manager.addPlatform(this);
this._registeredInManager = true;
}
}
//Track changes in size or position
if (
this._oldX !== this.owner.getX() ||
this._oldY !== this.owner.getY() ||
this._oldWidth !== this.owner.getWidth() ||
this._oldHeight !== this.owner.getHeight() ||
this._oldAngle !== this.owner.getAngle()
) {
if (this._registeredInManager) {
this._manager.removePlatform(this);
this._manager.addPlatform(this);
}
this._oldX = this.owner.getX();
this._oldY = this.owner.getY();
this._oldWidth = this.owner.getWidth();
this._oldHeight = this.owner.getHeight();
this._oldAngle = this.owner.getAngle();
}
this.trackChangesAndUpdateManagerIfNeeded();
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}

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

@@ -37,6 +37,24 @@ namespace gdjs {
export type ShapePainterObjectData = ObjectData & ShapePainterObjectDataType;
type ShapePainterNetworkSyncDataType = {
cbf: boolean; // clearBetweenFrames
aa: Antialiasing; // antialiasing
ac: boolean; // absoluteCoordinates
fc: integer; // fillColor
oc: integer; // outlineColor
os: float; // outlineSize
fo: float; // fillOpacity
oo: float; // outlineOpacity
scaleX: number;
scaleY: number;
ifx: boolean; // isFlippedX
ify: boolean; // isFlippedY
};
export type ShapePainterNetworkSyncData = ObjectNetworkSyncData &
ShapePainterNetworkSyncDataType;
/**
* The ShapePainterRuntimeObject allows to draw graphics shapes on screen.
*/
@@ -191,17 +209,77 @@ namespace gdjs {
return true;
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): ShapePainterNetworkSyncData {
return {
...super.getNetworkSyncData(syncOptions),
cbf: this._clearBetweenFrames,
aa: this._antialiasing,
ac: this._useAbsoluteCoordinates,
fc: this._fillColor,
oc: this._outlineColor,
os: this._outlineSize,
fo: this._fillOpacity,
oo: this._outlineOpacity,
scaleX: this.getScaleX(),
scaleY: this.getScaleY(),
ifx: this._flippedX,
ify: this._flippedY,
};
}
updateFromNetworkSyncData(
networkSyncData: ShapePainterNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.cbf !== undefined) {
this._clearBetweenFrames = networkSyncData.cbf;
}
if (networkSyncData.aa !== undefined) {
this.setAntialiasing(networkSyncData.aa);
}
if (networkSyncData.ac !== undefined) {
this.setCoordinatesRelative(!networkSyncData.ac);
}
if (networkSyncData.fc !== undefined) {
this._fillColor = networkSyncData.fc;
}
if (networkSyncData.oc !== undefined) {
this._outlineColor = networkSyncData.oc;
}
if (networkSyncData.os !== undefined) {
this.setOutlineSize(networkSyncData.os);
}
if (networkSyncData.fo !== undefined) {
this.setFillOpacity(networkSyncData.fo);
}
if (networkSyncData.oo !== undefined) {
this.setOutlineOpacity(networkSyncData.oo);
}
if (networkSyncData.scaleX !== undefined) {
this.setScaleX(networkSyncData.scaleX);
}
if (networkSyncData.scaleY !== undefined) {
this.setScaleY(networkSyncData.scaleY);
}
if (networkSyncData.ifx !== undefined) {
this.flipX(networkSyncData.ifx);
}
if (networkSyncData.ify !== undefined) {
this.flipY(networkSyncData.ify);
}
}
/**
* Initialize the extra parameters that could be set for an instance.
* @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

@@ -0,0 +1,190 @@
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
* Changes in this file are watched and automatically imported if the editor
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
*
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
* ⚠️ If you make a change and the extension is not loaded, open the developer console
* and search for any errors.
*
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
'SaveState',
_('Save State (experimental)'),
_('Allows to save and load the full state of a game.'),
'Neyl Mahfouf',
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/save-state')
.setCategory('Game mechanic')
.addInstructionOrExpressionGroupMetadata(_('Save State (experimental)'))
.setIcon('res/actions/saveDown.svg');
extension
.addAction(
'SaveGameSnapshotToVariable',
_('Save game to a variable'),
_('Takes a snapshot of the game and save it to a variable.'),
_('Save the game in variable _PARAM1_'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('variable', _('Variable to store the save to'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.saveVariableGameSnapshot');
extension
.addAction(
'SaveGameSnapshotToStorage',
_('Save game to device storage'),
_('Takes a snapshot of the game and save it to device storage.'),
_('Save the game to device storage under key _PARAM1_'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('string', _('Storage key to save to'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.saveStorageGameSnapshot');
extension
.addAction(
'LoadGameSnapshotFromVariable',
_('Load game from variable'),
_('Load game from a variable save snapshot.'),
_('Load the game from variable _PARAM0_'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.addParameter('variable', _('Variable to load the game from'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.loadGameFromVariableSnapshot');
extension
.addAction(
'LoadGameSnapshotFromStorage',
_('Load game from device storage'),
_('Load game from device storage save snapshot.'),
_('Load the game from device storage under key _PARAM0_.'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.addParameter('string', _('Storage key to load the game from'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.loadGameFromStorageSnapshot');
extension
.addExpressionAndCondition(
'number',
'TimeSinceLastSave',
_('Time since last save'),
_(
'Time since the last save, in seconds. Returns -1 if no save happened, and a positive number otherwise.'
),
_('Time since the last save'),
'',
'res/actions/saveDown.svg'
)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.getSecondsSinceLastSave')
.setGetter('gdjs.saveState.getSecondsSinceLastSave');
extension
.addExpressionAndCondition(
'number',
'TimeSinceLastLoad',
_('Time since last load'),
_(
'Time since the last load, in seconds. Returns -1 if no load happened, and a positive number otherwise.'
),
_('Time since the last load'),
'',
'res/actions/saveDown.svg'
)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.getSecondsSinceLastLoad')
.setGetter('gdjs.saveState.getSecondsSinceLastLoad');
extension
.addCondition(
'SaveJustSucceeded',
_('Save just succeeded'),
_('the save just succeeded'),
_('the save just succeeded'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasSaveJustSucceeded');
extension
.addCondition(
'SaveJustFailed',
_('Save just failed'),
_('the save just failed'),
_('the save just failed'),
_('Save'),
'res/actions/saveDown.svg',
'res/actions/saveDown.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasSaveJustFailed');
extension
.addCondition(
'LoadJustSucceeded',
_('Load just succeeded'),
_('the load just succeeded'),
_('the load just succeeded'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasLoadJustSucceeded');
extension
.addCondition(
'LoadJustFailed',
_('Load just failed'),
_('the load just failed'),
_('the load just failed'),
_('Load'),
'res/actions/saveUp.svg',
'res/actions/saveUp.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/SaveState/savestatetools.js')
.setFunctionName('gdjs.saveState.hasLoadJustFailed');
return extension;
},
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -0,0 +1,360 @@
namespace gdjs {
const logger = new gdjs.Logger('Save state');
export type LoadRequestOptions = {
loadStorageName?: string;
loadVariable?: gdjs.Variable;
};
export namespace saveState {
export const getIndexedDbDatabaseName = () => {
const gameId = gdjs.projectData.properties.projectUuid;
return `gdevelop-game-${gameId}`;
};
export const getIndexedDbObjectStore = () => {
return `game-saves`;
};
export const getIndexedDbStorageKey = (key: string) => {
return `save-${key}`;
};
const getNetworkSyncOptions: GetNetworkSyncDataOptions = {
syncObjectIdentifiers: true,
syncAllVariables: true,
syncAllBehaviors: true,
syncSceneTimers: true,
syncOnceTriggers: true,
syncSounds: true,
syncTweens: true,
syncLayers: true,
syncAsyncTasks: true,
syncSceneVisualProps: true,
syncFullTileMaps: true,
};
const updateFromNetworkSyncDataOptions: UpdateFromNetworkSyncDataOptions = {
clearSceneStack: true,
preventInitialInstancesCreation: true,
preventSoundsStoppingOnStartup: true,
clearInputs: true,
keepControl: true,
ignoreVariableOwnership: true,
};
let lastSaveTime: number | null = null;
let lastLoadTime: number | null = null;
let saveJustSucceeded: boolean = false;
let saveJustFailed: boolean = false;
let loadJustSucceeded: boolean = false;
let loadJustFailed: boolean = false;
let loadRequestOptions: LoadRequestOptions | null = null;
export const getSecondsSinceLastSave = (): number => {
if (!lastSaveTime) return -1;
return Math.floor((Date.now() - lastSaveTime) / 1000);
};
export const getSecondsSinceLastLoad = (): number => {
if (!lastLoadTime) return -1;
return Math.floor((Date.now() - lastLoadTime) / 1000);
};
export const hasSaveJustSucceeded = () => {
return saveJustSucceeded;
};
export const hasLoadJustSucceeded = () => {
return loadJustSucceeded;
};
export const hasSaveJustFailed = () => {
return saveJustFailed;
};
export const hasLoadJustFailed = () => {
return loadJustFailed;
};
export const markSaveJustSucceeded = () => {
saveJustSucceeded = true;
lastSaveTime = Date.now();
};
export const markLoadJustSucceeded = () => {
loadJustSucceeded = true;
lastLoadTime = Date.now();
};
export const markSaveJustFailed = () => {
saveJustFailed = true;
};
export const markLoadJustFailed = () => {
loadJustFailed = true;
};
// Ensure that the condition "save/load just succeeded/failed" are valid only for one frame.
gdjs.registerRuntimeScenePostEventsCallback(() => {
saveJustSucceeded = false;
saveJustFailed = false;
loadJustSucceeded = false;
loadJustFailed = false;
});
gdjs.registerRuntimeScenePostEventsCallback(
(runtimeScene: gdjs.RuntimeScene) => {
loadGameSnapshotAtTheEndOfFrameIfAny(runtimeScene);
}
);
const getGameSaveState = (runtimeScene: RuntimeScene) => {
const gameSaveState: GameSaveState = {
gameNetworkSyncData: {},
layoutNetworkSyncDatas: [],
};
const gameData = runtimeScene
.getGame()
.getNetworkSyncData(getNetworkSyncOptions);
const scenes = runtimeScene.getGame().getSceneStack().getAllScenes();
gameSaveState.gameNetworkSyncData = gameData || {};
scenes.forEach((scene, index) => {
gameSaveState.layoutNetworkSyncDatas[index] = {
sceneData: {} as LayoutNetworkSyncData,
objectDatas: {},
};
// First collect all object sync data, as they may generate unique
// identifiers like their networkId.
const sceneRuntimeObjects = scene.getAdhocListOfAllInstances();
for (const key in sceneRuntimeObjects) {
if (sceneRuntimeObjects.hasOwnProperty(key)) {
const object = sceneRuntimeObjects[key];
const objectSyncData = object.getNetworkSyncData(
getNetworkSyncOptions
);
gameSaveState.layoutNetworkSyncDatas[index].objectDatas[object.id] =
objectSyncData;
}
}
// Collect all scene data in the end.
const sceneDatas = (scene.getNetworkSyncData(getNetworkSyncOptions) ||
[]) as LayoutNetworkSyncData;
gameSaveState.layoutNetworkSyncDatas[index].sceneData = sceneDatas;
});
return gameSaveState;
};
export const saveVariableGameSnapshot = async function (
currentScene: RuntimeScene,
variable: gdjs.Variable
) {
try {
const gameSaveState = getGameSaveState(currentScene);
variable.fromJSObject(gameSaveState);
markSaveJustSucceeded();
} catch (error) {
logger.error('Error saving to variable:', error);
markSaveJustFailed();
}
};
export const saveStorageGameSnapshot = async function (
currentScene: RuntimeScene,
storageKey: string
) {
try {
const gameSaveState = getGameSaveState(currentScene);
await gdjs.indexedDb.saveToIndexedDB(
getIndexedDbDatabaseName(),
getIndexedDbObjectStore(),
getIndexedDbStorageKey(storageKey),
gameSaveState
);
markSaveJustSucceeded();
} catch (error) {
logger.error('Error saving to IndexedDB:', error);
markSaveJustFailed();
}
};
export const loadGameFromVariableSnapshot = async function (
variable: gdjs.Variable
) {
// The information is saved, so that the load can be done
// at the end of the frame,
// and avoid possible conflicts with running events.
loadRequestOptions = {
loadVariable: variable,
};
};
export const loadGameFromStorageSnapshot = async function (
storageName: string
) {
// The information is saved, so that the load can be done
// at the end of the frame,
// and avoid possible conflicts with running events.
loadRequestOptions = {
loadStorageName: storageName,
};
};
const loadGameSnapshotAtTheEndOfFrameIfAny = function (
runtimeScene: RuntimeScene
) {
if (!loadRequestOptions) return;
const optionsToApply = loadRequestOptions;
// Reset it so we don't load it twice.
loadRequestOptions = null;
if (optionsToApply.loadVariable) {
const sceneVariables = runtimeScene.getVariables();
const variablePathInScene =
sceneVariables.getVariablePathInContainerByLoopingThroughAllVariables(
optionsToApply.loadVariable
);
const gameVariables = runtimeScene.getGame().getVariables();
const variablePathIngame =
gameVariables.getVariablePathInContainerByLoopingThroughAllVariables(
optionsToApply.loadVariable
);
const saveState =
optionsToApply.loadVariable.toJSObject() as GameSaveState;
try {
loadGameFromSave(runtimeScene, saveState, {
variableToRehydrate: optionsToApply.loadVariable,
variablePathInScene: variablePathInScene,
variablePathInGame: variablePathIngame,
});
markLoadJustSucceeded();
} catch (error) {
logger.error('Error loading from variable:', error);
markLoadJustFailed();
}
} else if (optionsToApply.loadStorageName) {
gdjs.indexedDb
.loadFromIndexedDB(
getIndexedDbDatabaseName(),
getIndexedDbObjectStore(),
getIndexedDbStorageKey(optionsToApply.loadStorageName)
)
.then((jsonData) => {
const saveState = jsonData as GameSaveState;
loadGameFromSave(runtimeScene, saveState);
markLoadJustSucceeded();
})
.catch((error) => {
logger.error('Error loading from IndexedDB:', error);
markLoadJustFailed();
});
}
};
const loadGameFromSave = (
runtimeScene: RuntimeScene,
saveState: GameSaveState,
saveOptions?: {
variableToRehydrate: gdjs.Variable;
variablePathInScene: string[] | null;
variablePathInGame: string[] | null;
}
): void => {
// Save the content of the save, as it will be erased after the load.
const variableToRehydrateNetworkSyncData = saveOptions
? saveOptions.variableToRehydrate.getNetworkSyncData(
getNetworkSyncOptions
)
: null;
// First update the game, which will update the variables,
// and set the scene stack to update when ready.
const runtimeGame = runtimeScene.getGame();
runtimeGame.updateFromNetworkSyncData(
saveState.gameNetworkSyncData,
updateFromNetworkSyncDataOptions
);
// Apply the scene stack updates, as we are at the end of a frame,
// we can safely do it.
const sceneStack = runtimeGame.getSceneStack();
sceneStack.applyUpdateFromNetworkSyncDataIfAny(
updateFromNetworkSyncDataOptions
);
// Then get all scenes, which we assume will be the expected ones
// after the load has been done, so we can update them,
// and create their objects.
const runtimeScenes = sceneStack.getAllScenes();
runtimeScenes.forEach((scene, index) => {
const layoutSyncData = saveState.layoutNetworkSyncDatas[index];
if (!layoutSyncData) return;
// Create objects first, so they are available for the scene update,
// especially so that they have a networkId defined.
const objectDatas = layoutSyncData.objectDatas;
for (const id in objectDatas) {
const objectNetworkSyncData = objectDatas[id];
const objectName = objectNetworkSyncData.n;
if (!objectName) {
logger.warn('Tried to recreate an object without a name.');
continue;
}
const object = scene.createObject(objectName);
if (object) {
object.updateFromNetworkSyncData(
objectNetworkSyncData,
updateFromNetworkSyncDataOptions
);
}
}
// Update the scene last.
scene.updateFromNetworkSyncData(
layoutSyncData.sceneData,
updateFromNetworkSyncDataOptions
);
});
// Finally, if the save was done in a variable,
// rehydrate the variable where the save was done,
// as it has been erased by the load.
if (saveOptions && variableToRehydrateNetworkSyncData) {
const currentScene = sceneStack.getCurrentScene();
if (!currentScene) return;
const sceneVariables = currentScene.getVariables();
const gameVariables = currentScene.getGame().getVariables();
const { variablePathInScene, variablePathInGame } = saveOptions;
if (variablePathInScene && variablePathInScene.length > 0) {
const variableName =
variablePathInScene[variablePathInScene.length - 1];
const variableInScene =
sceneVariables.getVariableFromPath(variablePathInScene);
if (variableInScene) {
const variableNetworkSyncData: VariableNetworkSyncData = {
name: variableName,
...variableToRehydrateNetworkSyncData,
};
variableInScene.updateFromNetworkSyncData(
variableNetworkSyncData,
updateFromNetworkSyncDataOptions
);
}
}
if (variablePathInGame && variablePathInGame.length > 0) {
const variableName =
variablePathInGame[variablePathInGame.length - 1];
const variableInGame =
gameVariables.getVariableFromPath(variablePathInGame);
if (variableInGame) {
const variableNetworkSyncData: VariableNetworkSyncData = {
name: variableName,
...variableToRehydrateNetworkSyncData,
};
variableInGame.updateFromNetworkSyncData(
variableNetworkSyncData,
updateFromNetworkSyncDataOptions
);
}
}
}
};
}
}

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

@@ -53,6 +53,8 @@ namespace gdjs {
readonly spineResourceName: string;
static isHitBoxesUpdateDisabled = false;
/**
* @param instanceContainer The container the object belongs to.
* @param objectData The object data used to initialize the object
@@ -74,6 +76,10 @@ namespace gdjs {
this.setAnimationIndex(0);
this._renderer.updateAnimation(0);
if (SpineRuntimeObject.isHitBoxesUpdateDisabled) {
this.hitBoxes.length = 0;
}
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
}
@@ -111,9 +117,11 @@ namespace gdjs {
return true;
}
getNetworkSyncData(): SpineNetworkSyncData {
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): SpineNetworkSyncData {
return {
...super.getNetworkSyncData(),
...super.getNetworkSyncData(syncOptions),
opa: this._opacity,
scaX: this.getScaleX(),
scaY: this.getScaleY(),
@@ -127,8 +135,11 @@ namespace gdjs {
};
}
updateFromNetworkSyncData(syncData: SpineNetworkSyncData): void {
super.updateFromNetworkSyncData(syncData);
updateFromNetworkSyncData(
syncData: SpineNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(syncData, options);
if (syncData.opa !== undefined && syncData.opa !== this._opacity) {
this.setOpacity(syncData.opa);
@@ -183,6 +194,14 @@ namespace gdjs {
}
}
updateHitBoxes(): void {
if (SpineRuntimeObject.isHitBoxesUpdateDisabled) {
return;
}
super.updateHitBoxes();
}
extraInitializationFromInitialInstance(
initialInstanceData: InstanceData
): void {
@@ -199,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 {

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