Compare commits

...

184 Commits

Author SHA1 Message Date
Florian Rival
ae0f2687a5 Update typing for TileMapHelper 2024-05-12 20:33:36 +02:00
Florian Rival
56bcb79dcd Refactor PixiResourcesLoader to remove static methods and type it properly in extensions 2024-05-12 20:06:29 +02:00
Florian Rival
4ca859c8b9 Allow player authentication in a leaderboard (#6552)
* Enable leaderboards to launch authentication and claim an anonymous score that was just sent
* Also automatically attach a score sent to the logged in player (unless deactivated)
* Make various fixes for authentication on iOS
2024-05-10 10:38:33 +02:00
AlexandreS
701b5a3250 Fix platform icons generation for cloud projects from desktop app (#6551) 2024-05-06 10:38:43 +02:00
Arthur Pacaud (arthuro555)
1e87b74d48 Disable caching in preview server (#6553)
* Useful in case the preview is proxied through a CDN

Only show in developer changelog
2024-05-05 16:48:00 +02:00
AlexandreS
b7a4bab53c Few fixes for the compact instance properties editor (#6547)
- Improve panel spacing
- Remove Restore icon on dimension fields if the instance uses the default size.
- When locking/unlocking multiple instances, apply same value to every instances
2024-05-02 11:48:42 +02:00
Clément Pasteau
2223b6516b Improve Marketing plan logic (#6546)
Do not show in changelog
2024-04-29 17:14:29 +02:00
AlexandreS
7d89208e7b Fix cloud project autosave crashing on Firefox versions <=125 (#6544) 2024-04-29 11:56:24 +02:00
AlexandreS
70508292e4 Bump newIDE version (#6541) 2024-04-25 11:38:53 +02:00
github-actions[bot]
c899db1dba Update translations [skip ci] (#6540) 2024-04-25 11:38:34 +02:00
D8H
2fb27f430e Fix "is on platform" condition that was no longer do anything (#6538) 2024-04-25 11:12:33 +02:00
AlexandreS
6cd2400848 Fix animation placeholder vertical centering (#6539) 2024-04-25 09:57:40 +02:00
github-actions[bot]
fadccba757 Update translations [skip ci] (#6533)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-04-24 15:14:35 +02:00
Florian Rival
29a43514ce Add a banner to encourage to fill the github username in the profile (#6536) 2024-04-24 13:35:51 +02:00
Florian Rival
e64ab94f76 Remove dead code (#6535)
Don't show in changelog
2024-04-23 18:22:57 +02:00
Florian Rival
ed969d2909 Add a field to enter the GitHub username in the user profile (#6534) 2024-04-23 17:48:15 +02:00
AlexandreS
77bf67fdf0 Bump newIDE version (#6532) 2024-04-23 10:38:42 +02:00
github-actions[bot]
580c7a370c Update translations [skip ci] (#6520) 2024-04-23 10:24:37 +02:00
Florian Rival
266f0f2b6e Disable rating of feedbacks that have no text 2024-04-22 10:40:34 +02:00
Florian Rival
58af6aacb4 Add tutorials in the Learn page for education subscribers (#6526) 2024-04-21 21:58:08 +02:00
D8H
1c4ee1c928 Fix some crashes when conditions with objectList are used without any behavior (#6529) 2024-04-21 11:16:31 +02:00
AlexandreS
0275476bec Fix cloud project autosave crash happening in particular situations (#6527) 2024-04-19 17:03:30 +02:00
AlexandreS
3f0194acf0 Increase lower toolbars size and padding on mobile (#6525) 2024-04-19 15:47:32 +02:00
AlexandreS
017d8b28c2 Fix copy (#6524)
Don't show in changelog
2024-04-19 11:21:21 +02:00
AlexandreS
4b518ba0fc Fix undefined resources when opening cloud project autosave after a time of inactivity (#6523) 2024-04-18 11:08:16 +02:00
AlexandreS
e4938b25b5 Fix dimensions set to 0 0 0 on instance when switching layer (#6522) 2024-04-18 10:35:38 +02:00
AlexandreS
c56ad2d277 Bump newIDE version (#6521) 2024-04-17 10:10:02 +02:00
github-actions[bot]
f069f9d942 Update translations [skip ci] (#6515)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-04-17 09:35:34 +02:00
D8H
9f7c60a69e Fix the particles emitter not to delete itself when the emission is paused (#6505)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-04-17 09:34:06 +02:00
AlexandreS
774bf0db61 Update limits for pro subscribers in stories (#6519)
Don't show in changelog
2024-04-16 16:49:14 +02:00
AlexandreS
ccd9c5c0ff Fix: Remove useless horizontal scroll in tree view (#6518) 2024-04-16 11:40:04 +02:00
AlexandreS
5b130a34e3 Fix: Holding space now prevents from resize/rotating an instance (#6516)
---------

Co-authored-by: Leonardo Alves <leonardo.alves@tecnico.ulisboa.pt>
2024-04-15 17:34:18 +02:00
AlexandreS
3e6d147660 Instance properties panel rework
The instance properties panel of the scene editor has been reworked into a more compact and user-friendly panel.
2024-04-15 15:29:27 +02:00
github-actions[bot]
433d85db94 Update translations [skip ci] (#6499)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2024-04-15 11:15:04 +02:00
D8H
b75f707b30 Use new icons for variable parameters in events (#6512) 2024-04-09 16:58:25 +02:00
Florian Rival
67799eb3eb Improve user profiles by showing games, asset packs and game templates made by the user (#6511)
* The app now shows the same profiles as those you can see on gd.games
2024-04-09 13:31:31 +02:00
AlexandreS
1173210386 Fix cloud projects autosave feature (#6510) 2024-04-08 19:12:19 +02:00
D8H
007cc48291 Fix missing object name field in some object editors (#6506) 2024-04-05 17:31:47 +02:00
D8H
12813c9ad9 Fix Sprite collision mask updating when animation frames don't share the same one (#6504) 2024-04-05 14:56:44 +02:00
Clément Pasteau
e503e34059 Fix asset pack & game template licenses being correctly purchased (#6502)
* An issue was always selecting the first license when the purchase dialog opened
2024-04-04 10:40:45 +02:00
Clément Pasteau
4bf576a889 Bump to 5.3.198 (#6500) 2024-04-03 17:57:57 +02:00
D8H
eff1c1bcb6 Fix a regression on "animation by index" action when the index is not an integer (#6498) 2024-04-03 15:00:40 +02:00
Clément Pasteau
abce34f2b1 Bump to 5.3.197 (#6497)
Do not show in changelog
2024-04-03 10:08:23 +02:00
github-actions[bot]
07276d5e16 Update translations [skip ci] (#6496)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-04-03 10:07:26 +02:00
Arthur Pacaud (arthuro555)
1fdd8cc792 Add an action to allow disabling P2P direct connections (#6475) 2024-04-02 22:38:41 +02:00
AlexandreS
79e40605d5 Fix collapse/expand arrow on event sheet for light themes (#6495) 2024-04-02 22:36:00 +02:00
D8H
fcc91e3fea Fix issues with Sprite animation frame updates (#6493) 2024-04-02 21:14:48 +02:00
github-actions[bot]
5c66623631 Update translations [skip ci] (#6487)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-03-28 12:07:38 +01:00
AlexandreS
5637642e1b Compute max discount instead of using hardcoded value (#6488)
Don't show in changelog
2024-03-28 11:05:13 +01:00
Florian Rival
7e8b44af2e Fix expressions involving a variable (or property) of type number/string wrongly rejected when an operator like + was used after them (#6467)
* For example, "Your score is " + NumberVariable + " points" was rejected, because the second operator type was not properly inferred.
* If something does not work in your game anymore, double check if your expressions in the events sheets are not underlined in red. Sometimes, adding `ToString(` or `ToNumber(` around it might be required.
2024-03-28 09:42:41 +01:00
Clément Pasteau
0dd4650aae Bump to 5.3.196 (#6486)
Do not show in changelog
2024-03-28 09:35:22 +01:00
github-actions[bot]
c7cac31830 Update translations [skip ci] (#6457)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-03-28 08:57:13 +01:00
Clément Pasteau
56cb8581c4 Fix plugin-consent version for admob (#6485) 2024-03-27 19:09:42 +01:00
Florian Rival
1993040b70 Add community leaderboard to highlight members doing great feedbacks (#6484) 2024-03-27 18:47:14 +01:00
AlexandreS
883991081a Define app theme related CSS variables at HTML body level (#6483)
Only show in developer changelog
2024-03-27 17:48:19 +01:00
D8H
7d8afef1ad Fix collision mask of rotated Spine objects (#6482)
- Fixes rotation of Spine objects in the editor.
2024-03-27 16:35:55 +01:00
Florian Rival
8178595546 Use a CSS module for SimpleTextField, to avoid name clashes (#6481)
Only show in developer changelog
2024-03-27 12:06:53 +01:00
AlexandreS
a478068c64 Add possibility to subscribe to GDevelop on a yearly basis (#6462) 2024-03-27 11:26:44 +01:00
Tristan Rhodes
368da1b610 New action for Physics behavior: Set the linear velocity towards an angle (#5670) 2024-03-27 08:27:00 +01:00
D8H
4ee43202e9 Fix sprite with resource tests (#6479)
- Don't show in changelog
2024-03-26 15:34:31 +01:00
Florian Rival
602fdf4bfd Fix warning
Don't show in changelog
2024-03-26 11:40:16 +01:00
Florian Rival
6110acafcc Add buttons to rate the quality of feedbacks (#6478)
* When you receive a feedback on a game, go to the Game Dashboard (or check the email notification for the feedback) to rank the comment as great, good or not useful. If a comment is abusive, spammy or harmful, you can also report it as such.
* Users making the best comments will be showcased in a leaderboard on the community page and on gd.games
* As a game creator, you're also benefit from this: games with the most rated feedbacks will be displayed in a leaderboard and on gd.games. We encourage you to take the time to rate the feedbacks you're receiving so your game becomes more visible to the community!
2024-03-26 11:28:45 +01:00
D8H
a3696ca9d1 Allow custom objects to use animations (#6426) 2024-03-25 10:47:28 +01:00
Clément Pasteau
1bb473b0b0 Display questions when canceling a subscription (#6471) 2024-03-22 14:07:59 +01:00
Florian Rival
4376b4f36e Improve first screen layouts (#6468) 2024-03-22 10:33:42 +01:00
Florian Rival
6ecbae9c35 Display the coordinates of the center point of a Sprite even when set automatically
Fix #6472
2024-03-22 10:33:05 +01:00
D8H
93e9fc6aed Fix missing expressions for text object (#6470) 2024-03-21 10:30:25 +01:00
Clément Pasteau
7c0a7a4152 Game templates in changelog (#6458)
Do not show in changelog
2024-03-14 16:42:38 +01:00
Clément Pasteau
d4cdb3ff83 Bump to 5.3.195 (#6455) 2024-03-14 16:20:15 +01:00
AlexandreS
a7cac91e45 Fix warning about end of subscription (#6456)
Do not show in changelog
2024-03-14 15:49:09 +01:00
D8H
6a6d72cd9a Fix games from crashing when a scene contains a wrapped text object with a big font size (#6453) 2024-03-14 15:36:34 +01:00
github-actions[bot]
11d74b3ea5 Update translations [skip ci] (#6442)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-03-14 15:31:27 +01:00
Clément Pasteau
5b65cf84eb Fix survey mobile (#6454)
Do not show in changelog
2024-03-14 15:00:57 +01:00
Clément Pasteau
6b7af0474f Improve Guided lessons (#6446)
* Lessons have been slightly reworked overall
* Most languages are now supported
* A new option allows to fill a value automatically with a button, speeding up the typing part
2024-03-14 14:40:12 +01:00
D8H
99f7e55044 Fix the object effect color tween action (#6452) 2024-03-14 14:13:28 +01:00
AlexandreS
6b522b1c31 Add support for game sessions achievements notifications (#6449) 2024-03-14 12:18:42 +01:00
D8H
0f35e48690 Allow to change text of custom objects like any text object (#6450) 2024-03-14 11:50:55 +01:00
D8H
53d19dd628 Fix custom object capability changes to be applied right away (#6447) 2024-03-14 10:49:33 +01:00
Florian Rival
155dc1bec8 Display redesigned subscription plans (#6440)
Don't show in changelog
2024-03-14 10:45:29 +01:00
D8H
be26e39eae Fix 3D custom objects CenterZ expression and condition (#6448) 2024-03-13 14:07:24 +01:00
TRP
90fa5ea8e8 Add action to draw a fillet rectangle with Shape painter (rounded rectangle with inverted corners) (#6433) 2024-03-12 16:11:02 +01:00
Florian Rival
4845251f78 Fix broken editor panels drag'n'drop (#6444) 2024-03-11 11:34:27 +01:00
Arthur Pacaud (arthuro555)
31ef3fec58 Add TypeScript type checking to JsExtension.js files (#6321) 2024-03-09 17:22:12 +01:00
Clément Pasteau
292b23ea17 Fix usage displayer (#6441)
* Fix wrong logic for usage displayer

* Bump version too
2024-03-08 18:11:11 +01:00
github-actions[bot]
9c5a5db7a8 Update translations [skip ci] (#6439)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-03-08 16:40:48 +01:00
Clément Pasteau
d73ae4c56e Bump to 5.3.193 (#6436) 2024-03-08 14:30:29 +01:00
D8H
339929e021 Sort 3D effects to show light and fog effects first (#6437) 2024-03-08 14:17:56 +01:00
github-actions[bot]
cea1cf20f1 Update translations [skip ci] (#6438)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-03-08 14:16:04 +01:00
github-actions[bot]
4ec2705f75 Update translations [skip ci] (#6435)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-03-08 12:36:52 +01:00
AlexandreS
408f6f8134 Display notifications to the user (#6432) 2024-03-08 12:15:21 +01:00
github-actions[bot]
c64bac0010 Update translations [skip ci] (#6424)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-03-08 09:54:15 +01:00
Clément Pasteau
f8d5c89ebf Allow using credits to start builds when quota is exhausted (#6430) 2024-03-08 09:26:18 +01:00
Florian Rival
ca3bb40e96 Store an achievement when finishing a tutorial (#6431) 2024-03-07 16:18:50 +01:00
Florian Rival
713d437b70 Fix naming of license file for Electron
Don't show in changelog
2024-03-07 10:15:04 +01:00
Florian Rival
09381c3836 Fix warning
Don't show in changelog
2024-03-06 17:47:08 +01:00
Florian Rival
ea4c2e0827 Fix useless query param
Don't show in changelog
2024-03-06 17:38:31 +01:00
Florian Rival
a5d1149c21 Rework the Get Started page to directly show recommendations for all users (#6428) 2024-03-06 17:35:30 +01:00
Florian Rival
119b0fadce Avoid rendering all asset packs and loading 100+ images in the Shop/Asset Store tab (#6425) 2024-03-06 17:11:32 +01:00
D8H
7111859768 Remove useless import (#6427)
Don't show in changelog
2024-03-05 15:44:07 +01:00
Vladyslav Pohorielov
08dfb4d36b Include PixiJS Spine pre-built files as part of the extension, like other extensions (#6340)
Only show in developer changelog
2024-03-05 15:28:23 +01:00
Florian Rival
cacd482af9 Fix type check for GDJS and documentation generation 2024-03-05 14:34:34 +01:00
D8H
3568a58019 Allow custom objects to be used as 3D objects (#6369)
- Custom objects like the 3D particle emitter can be used as any other 3D object
  - with the same set of actions and conditions thanks to the 3D capability
  - in the scene editor, their Z position and 3D rotations can be changed
- Fix child creation in `doStepPostEvent` function
2024-03-05 13:51:41 +01:00
Florian Rival
a32d5fcd7e Add file to ease detection of GDevelop by tool like SteamDB or Itch (#6423) 2024-03-05 13:12:16 +01:00
github-actions[bot]
1a999fd2dd Update translations [skip ci] (#6413)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-03-04 18:13:44 +01:00
Florian Rival
76648764bb Refactor EditorMosaic, add some placeholder for potential screen adaptation (#6421)
Don't show in changelog
2024-03-04 16:47:36 +01:00
Vladyslav Pohorielov
c25803c122 Fix Spine resources not loaded properly in games in the web-app version of GDevelop (#6416) 2024-03-04 16:03:48 +01:00
D8H
ba8d7f4e38 Fix a crash that sometimes happens when deleting links of an object (#6411) 2024-03-01 17:18:01 +01:00
AlexandreS
e1c22f6994 Display QRCode at the end of the gd games export flow (#6410) 2024-03-01 16:36:29 +01:00
Clément Pasteau
7b70f9172f Bump to 5.3.192 (#6408) 2024-03-01 12:58:55 +01:00
github-actions[bot]
a871e0f2ec Update translations [skip ci] (#6406)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-03-01 12:51:58 +01:00
Clément Pasteau
f6499e1163 Fix export flow with gdgames and show warning when game is not owned (#6407) 2024-03-01 12:34:23 +01:00
github-actions[bot]
d03e58636d Update translations [skip ci] (#6404)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-03-01 10:17:35 +01:00
AlexandreS
a7be928f2f Fix icons generating utility not working on macOS (#6405) 2024-02-29 17:07:35 +01:00
AlexandreS
7c83610d28 Fix missing s in Reponsive (#6403)
Don't show in changelog
2024-02-29 10:03:44 +01:00
github-actions[bot]
1a1e92b072 Update translations [skip ci] (#6398)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-02-29 09:52:55 +01:00
Florian Rival
5118f13e0b Add support for usages with 30 days period (#6401) 2024-02-28 18:20:41 +01:00
Clément Pasteau
f3ee18cdc7 Fix share tab and other design improvements on mobile + info alert when build is running (#6400)
Do not show in changelog
2024-02-28 15:12:33 +01:00
AlexandreS
c5584f746e Upgrade automatic pull request action (#6399)
Don't show in changelog
2024-02-28 14:34:57 +01:00
AlexandreS
706a6de94c Bump newIDE version (#6389) 2024-02-28 13:54:16 +01:00
github-actions[bot]
8c750b54dd Update translations [skip ci] (#6397)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2024-02-28 13:54:01 +01:00
AlexandreS
649d744664 Remove tree view nav with left and right arrow keys in project manager and events function list (#6396) 2024-02-28 13:53:30 +01:00
Florian Rival
12a6fec18e Ensure special characters like parenthesis are removed from Cordova project name to allow proper iOS app store upload (#6395)
Don't show in changelog
2024-02-28 13:35:13 +01:00
github-actions[bot]
7fc4aa47f6 Update translations [skip ci] (#6394)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-28 11:44:06 +01:00
github-actions[bot]
483f78fa75 Update translations [skip ci] (#6393)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-28 11:25:10 +01:00
Clément Pasteau
35b80818dc Improve export flow's overall design (#6392) 2024-02-28 11:04:42 +01:00
github-actions[bot]
b3f19726dc Update translations [skip ci] (#6391)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-02-28 10:50:47 +01:00
D8H
dd332e3cce Fix boolean property instruction group (#6386) 2024-02-28 10:24:03 +01:00
D8H
dc95a7511f Use a tree view for the project manager (#6346)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-02-27 16:51:59 +01:00
github-actions[bot]
77130c7d2e Update translations [skip ci] (#6382)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-02-27 16:25:46 +01:00
AlexandreS
8d1c3e8290 Improve display performance on Project and Team members lists (#6388) 2024-02-27 15:24:10 +01:00
AlexandreS
63570fd3c8 Paginate list of games (#6387) 2024-02-27 12:39:06 +01:00
AlexandreS
6945409280 Fix: Update BBText hitboxes when text changes (#6385)
- Also: Deprecate current font family setting action that uses a string as input and create a new action that uses a selector instead.
2024-02-27 08:57:17 +01:00
AlexandreS
e345b2726f Fix URL opening action on iOS (#6384) 2024-02-26 12:04:52 +01:00
github-actions[bot]
3a2a662f62 Update translations [skip ci] (#6380)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-23 19:35:40 +01:00
Clément Pasteau
73d850f933 Fix missing dependencies (#6381) 2024-02-23 19:35:24 +01:00
github-actions[bot]
1f2293e7d6 Update translations [skip ci] (#6371)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-02-23 18:54:24 +01:00
Clément Pasteau
b038984097 Display promotions & announcements only on a few pages & make it scrollable (#6379)
Do not show in changelog
2024-02-23 18:53:56 +01:00
AlexandreS
a45a5bfe7e Remove shuffling of data from backend services (#6378) 2024-02-23 17:33:25 +01:00
Florian Rival
8cbefa0cde Add support for iOS Cloud builds and automated publishing to App Store Connect (#6373)
* This allows to build directly from GDevelop, without any additional development tool or access to a Mac, a game for iOS.
* Builds can be done for development (testing on iPhone/iPad) or for the App Store directly.
* An Apple Developer account is required, so that you can generate the required certificate files (and authentication keys for uploading to App Store Connect).
* Using these new "iOS cloud builds" requires a Gold or a Pro subscription.
2024-02-23 16:42:09 +01:00
AlexandreS
1d607d474a Fix: Display audio previews in premium asset pack previews (#6377) 2024-02-23 16:07:17 +01:00
Florian Rival
3cd6e36a73 Fix scrollbars not properly styled in latest Chrome (#6376) 2024-02-23 12:16:51 +01:00
Florian Rival
cfbec2df4a Fix naming 2024-02-23 09:30:26 +01:00
Clément Pasteau
2823fde86a Few game template improvements (#6375)
Do not show in changelog
2024-02-22 14:36:33 +01:00
D8H
f29ae50f2e Fix 3d model resource changes not being detected by the other fields of the object editor (#6374) 2024-02-22 11:40:36 +01:00
Clément Pasteau
ba40c67941 Improve visibility on small tablets and mobiles in landscape (#6372)
* Prevent the screen to be detected as mobile and some layout to shift unnecessarily
2024-02-22 10:23:20 +01:00
D8H
c8ca3c2931 Fix a crash when the camera Z is changed and there is no 3D object in the project (#6370) 2024-02-21 10:36:33 +01:00
github-actions[bot]
0bb0969ab8 Update translations [skip ci] (#6365)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-20 12:07:31 +01:00
Clément Pasteau
3b0a7c442f Hide slideshow arrows when not hovering (#6367)
* And handle keyboard navigation

Do not show in changelog
2024-02-20 12:00:56 +01:00
AlexandreS
df3c95c466 Bump newIDE version (#6366) 2024-02-19 16:26:05 +01:00
AlexandreS
482d52ff5a Open example store from all templates list of build section (#6364)
Don't show in changelog
2024-02-19 16:13:19 +01:00
github-actions[bot]
5a78c763ef Update translations [skip ci] (#6358)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-02-19 14:15:40 +01:00
AlexandreS
3393af4b3b Add button to see more templates on the build section (#6359) 2024-02-19 13:57:41 +01:00
D8H
c2b77c9df7 Fix platformer character position when its height changes (#6360) 2024-02-19 12:57:26 +01:00
Clément Pasteau
514e4692ab Display promotion items on the homepage (#6353) 2024-02-19 12:45:49 +01:00
AlexandreS
210e59f201 Fix: Auto scroll to instruction item when editing an instruction (#6361) 2024-02-19 12:22:38 +01:00
D8H
e2058052f2 Fix a crash that sometimes happens when several sounds are played at the same time (#6357) 2024-02-19 11:04:01 +01:00
github-actions[bot]
90dca41e20 Update translations [skip ci] (#6351)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-02-19 10:29:05 +01:00
AlexandreS
15b463dad1 Add possibility to claim a monthly asset pack free of charge (#6350)
For gold and startup subscribers only
2024-02-16 17:54:45 +01:00
github-actions[bot]
a97a1f8a86 Update translations [skip ci] (#6348)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-16 14:15:40 +01:00
D8H
901f84daaa Add bloom and color effects for 3D layers (#6228)
- Add 3D Effects for color adjustment and bloom.
- A lower quality anti-aliasing is used when post-processing is enable (SMAA instead of MSAA).
- Upgrade to Three.js 0.160.0.
2024-02-16 14:12:46 +01:00
Clément Pasteau
6af50ae5c0 Show all products on app stores, purchasable with GDevelop credits (#6349) 2024-02-16 11:19:10 +01:00
AlexandreS
f7bd4bee6e Fix conflict between opening and previewing a private game template from the store section (#6347) 2024-02-15 14:39:11 +01:00
github-actions[bot]
4a4b015242 Update translations [skip ci] (#6345)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-15 14:35:19 +01:00
Clément Pasteau
75ee0b68e3 Allow buying asset packs and game templates with credits (#6343) 2024-02-14 17:03:38 +01:00
github-actions[bot]
778104447c Update translations [skip ci] (#6336)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-02-13 18:52:45 +01:00
D8H
a4ddc0a6ef Fix parameters drop-down list for properties of type choices (#6342) 2024-02-13 14:32:30 +01:00
D8H
3e713029e2 Fix visibility of hidden extension functions starting with an object parameter from within the extension (#6341) 2024-02-13 11:27:57 +01:00
D8H
7eb5402ee2 Keep original object and resource names when exporting assets (#6329) 2024-02-12 19:16:53 +01:00
AlexandreS
25adb026b8 Fix typo (#6337)
Don't show in changelog
2024-02-09 12:14:48 +01:00
Clément Pasteau
54bd2960ac Fix doublons in example tags search (#6335) 2024-02-09 12:14:34 +01:00
AlexandreS
1fde65b5e3 Fix template opening (#6334)
Don't show in changelog
2024-02-09 12:06:28 +01:00
github-actions[bot]
b4295b4077 Update translations [skip ci] (#6332)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-02-09 11:42:05 +01:00
AlexandreS
c7a929374d Fix homepage navigation (#6333)
Don't show in changelog
2024-02-09 10:09:02 +01:00
AlexandreS
7571e64396 Bump newIDE version (#6331) 2024-02-09 10:08:38 +01:00
github-actions[bot]
198267d7cb Update translations [skip ci] (#6325)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-02-09 09:36:53 +01:00
Aurélien Vivet
0982424d0d Set a preference for the importation of resources for projects stored on the computer (#6318) 2024-02-09 09:33:58 +01:00
D8H
29747690c2 Remove unused method declaration (#6330)
* don't show in changelog
2024-02-08 19:14:41 +01:00
AlexandreS
ad74532752 Remove unused import (#6328)
Don't show in changelog
2024-02-08 15:56:15 +01:00
AlexandreS
80e5376f74 Make opening screen more stable and reliable (#6327) 2024-02-08 15:40:50 +01:00
D8H
cc24eab2aa Allow to set the text outline and shadow from the object editor (#6246)
* Avoid the shadow to be cut out.
* Fix the shadow angle action.
2024-02-08 13:11:18 +01:00
D8H
423c8165ad Collapse advanced behavior properties (#6326) 2024-02-08 13:08:52 +01:00
D8H
0af00818dc Allow to select instances with a selection rectangle starting from within another instance holding Shift (#6304) 2024-02-07 16:24:37 +01:00
D8H
9f5c783d73 Keep 3D model origin as part of the object when used as the object origin (#6303)
- In case you choose "Model origin" for the "Origin point" property and the model origin is outside of the model, the model size may be smaller than in previous releases. To solve the issue you can:
  - Choose one of the other options for the "Origin point" property
  - Reset the "Scaling factor" to its previous value to get back the same size as before
  - Modify the model to set the origin nearer to the geometry
- Also fix the scaling of flat 3D models
2024-02-07 15:26:07 +01:00
github-actions[bot]
ad65971c01 Update translations [skip ci] (#6320)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-07 14:43:44 +01:00
969 changed files with 49675 additions and 19195 deletions

View File

@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
cache: "npm"
cache-dependency-path: "newIDE/app/package-lock.json"
@@ -58,7 +58,7 @@ jobs:
working-directory: newIDE/app
- name: Create a Pull Request with the changes
uses: peter-evans/create-pull-request@v5
uses: peter-evans/create-pull-request@v6
with:
commit-message: Update translations [skip ci]
branch: chore/update-translations

View File

@@ -56,6 +56,7 @@ blocks:
- name: GDJS typing and documentation generation
commands:
- checkout
- cache restore newIDE-app-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum newIDE/app/package-lock.json)
- cache restore GDJS-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/package-lock.json)
- cache restore GDJS-tests-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/tests/package-lock.json)
- cd GDJS

1
.vscode/tasks.json vendored
View File

@@ -8,6 +8,7 @@
"group": "build",
"label": "Start development server",
"detail": "Starts the GDevelop development server.",
"options": { "env": { "NODE_OPTIONS": "--max-old-space-size=8192" } },
"problemMatcher": [
{
"owner": "cra",

View File

@@ -6,8 +6,6 @@
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
#include <vector>
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
#include "GDCore/String.h"
namespace gd {

View File

@@ -4,13 +4,11 @@
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_ANIMATION_H
#define GDCORE_ANIMATION_H
#pragma once
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Direction;
}
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
namespace gd {
@@ -93,4 +91,3 @@ class GD_CORE_API Animation {
};
} // namespace gd
#endif // GDCORE_ANIMATION_H

View File

@@ -3,12 +3,12 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_DIRECTION_H
#define GDCORE_DIRECTION_H
#pragma once
#include <vector>
#include "GDCore/String.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
namespace gd {
class Sprite;
class SerializerElement;
}
@@ -142,4 +142,3 @@ class GD_CORE_API Direction {
};
} // namespace gd
#endif // GDCORE_DIRECTION_H

View File

@@ -0,0 +1,157 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h"
#include <algorithm>
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/InitialInstance.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
Animation SpriteAnimationList::badAnimation;
SpriteAnimationList::SpriteAnimationList()
: adaptCollisionMaskAutomatically(true) {}
SpriteAnimationList::~SpriteAnimationList(){};
void SpriteAnimationList::UnserializeFrom(const gd::SerializerElement& element) {
adaptCollisionMaskAutomatically =
element.GetBoolAttribute("adaptCollisionMaskAutomatically", false);
RemoveAllAnimations();
const gd::SerializerElement& animationsElement =
element.GetChild("animations", 0, "Animations");
animationsElement.ConsiderAsArrayOf("animation", "Animation");
for (std::size_t i = 0; i < animationsElement.GetChildrenCount(); ++i) {
const gd::SerializerElement& animationElement =
animationsElement.GetChild(i);
Animation newAnimation;
newAnimation.useMultipleDirections = animationElement.GetBoolAttribute(
"useMultipleDirections", false, "typeNormal");
newAnimation.SetName(animationElement.GetStringAttribute("name", ""));
// Compatibility with GD <= 3.3
if (animationElement.HasChild("Direction")) {
for (std::size_t j = 0;
j < animationElement.GetChildrenCount("Direction");
++j) {
Direction direction;
direction.UnserializeFrom(animationElement.GetChild("Direction", j));
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
newAnimation.SetDirection(direction,
newAnimation.GetDirectionsCount() - 1);
}
}
// End of compatibility code
else {
const gd::SerializerElement& directionsElement =
animationElement.GetChild("directions");
directionsElement.ConsiderAsArrayOf("direction");
for (std::size_t j = 0; j < directionsElement.GetChildrenCount(); ++j) {
Direction direction;
direction.UnserializeFrom(directionsElement.GetChild(j));
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
newAnimation.SetDirection(direction,
newAnimation.GetDirectionsCount() - 1);
}
}
AddAnimation(newAnimation);
}
}
void SpriteAnimationList::SerializeTo(gd::SerializerElement& element) const {
element.SetAttribute("adaptCollisionMaskAutomatically",
adaptCollisionMaskAutomatically);
// Animations
gd::SerializerElement& animationsElement = element.AddChild("animations");
animationsElement.ConsiderAsArrayOf("animation");
for (std::size_t k = 0; k < GetAnimationsCount(); k++) {
gd::SerializerElement& animationElement =
animationsElement.AddChild("animation");
animationElement.SetAttribute("useMultipleDirections",
GetAnimation(k).useMultipleDirections);
animationElement.SetAttribute("name", GetAnimation(k).GetName());
gd::SerializerElement& directionsElement =
animationElement.AddChild("directions");
directionsElement.ConsiderAsArrayOf("direction");
for (std::size_t l = 0; l < GetAnimation(k).GetDirectionsCount(); l++) {
GetAnimation(k).GetDirection(l).SerializeTo(
directionsElement.AddChild("direction"));
}
}
}
void SpriteAnimationList::ExposeResources(gd::ArbitraryResourceWorker& worker) {
for (std::size_t j = 0; j < GetAnimationsCount(); j++) {
for (std::size_t k = 0; k < GetAnimation(j).GetDirectionsCount(); k++) {
for (std::size_t l = 0;
l < GetAnimation(j).GetDirection(k).GetSpritesCount();
l++) {
worker.ExposeImage(
GetAnimation(j).GetDirection(k).GetSprite(l).GetImageName());
}
}
}
}
const Animation& SpriteAnimationList::GetAnimation(std::size_t nb) const {
if (nb >= animations.size()) return badAnimation;
return animations[nb];
}
Animation& SpriteAnimationList::GetAnimation(std::size_t nb) {
if (nb >= animations.size()) return badAnimation;
return animations[nb];
}
void SpriteAnimationList::AddAnimation(const Animation& animation) {
animations.push_back(animation);
}
bool SpriteAnimationList::RemoveAnimation(std::size_t nb) {
if (nb >= GetAnimationsCount()) return false;
animations.erase(animations.begin() + nb);
return true;
}
void SpriteAnimationList::SwapAnimations(std::size_t firstIndex,
std::size_t secondIndex) {
if (firstIndex < animations.size() && secondIndex < animations.size() &&
firstIndex != secondIndex)
std::swap(animations[firstIndex], animations[secondIndex]);
}
void SpriteAnimationList::MoveAnimation(std::size_t oldIndex, std::size_t newIndex) {
if (oldIndex >= animations.size() || newIndex >= animations.size()) return;
auto animation = animations[oldIndex];
animations.erase(animations.begin() + oldIndex);
animations.insert(animations.begin() + newIndex, animation);
}
} // namespace gd

View File

@@ -0,0 +1,119 @@
/*
* 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/Extensions/Builtin/SpriteExtension/Animation.h"
namespace gd {
class InitialInstance;
class SerializerElement;
class PropertyDescriptor;
class ArbitraryResourceWorker;
} // namespace gd
namespace gd {
/**
* \brief A list of animations, containing directions with images and collision mask.
*
* It's used in the configuration of object that implements image-based animations.
*
* \see Animation
* \see Direction
* \see Sprite
* \ingroup SpriteObjectExtension
*/
class GD_CORE_API SpriteAnimationList {
public:
SpriteAnimationList();
virtual ~SpriteAnimationList();
void ExposeResources(gd::ArbitraryResourceWorker& worker);
/**
* \brief Return the animation at the specified index.
* If the index is out of bound, a "bad animation" object is returned.
*/
const Animation& GetAnimation(std::size_t nb) const;
/**
* \brief Return the animation at the specified index.
* If the index is out of bound, a "bad animation" object is returned.
*/
Animation& GetAnimation(std::size_t nb);
/**
* \brief Return the number of animations this object has.
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
/**
* \brief Add an animation at the end of the existing ones.
*/
void AddAnimation(const Animation& animation);
/**
* \brief Remove an animation.
*/
bool RemoveAnimation(std::size_t nb);
/**
* \brief Remove all animations.
*/
void RemoveAllAnimations() { animations.clear(); }
/**
* \brief Return true if the object hasn't any animation.
*/
bool HasNoAnimations() const { return animations.empty(); }
/**
* \brief Swap the position of two animations
*/
void SwapAnimations(std::size_t firstIndex, std::size_t secondIndex);
/**
* \brief Change the position of the specified animation
*/
void MoveAnimation(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Return a read-only reference to the vector containing all the
* animation of the object.
*/
const std::vector<Animation>& GetAllAnimations() const { return animations; }
/**
* @brief Check if the collision mask adapts automatically to the animation.
*/
bool AdaptCollisionMaskAutomatically() const {
return adaptCollisionMaskAutomatically;
}
/**
* @brief Set if the collision mask adapts automatically to the animation.
*/
void SetAdaptCollisionMaskAutomatically(bool enable) {
adaptCollisionMaskAutomatically = enable;
}
void UnserializeFrom(const gd::SerializerElement& element);
void SerializeTo(gd::SerializerElement& element) const;
private:
mutable std::vector<Animation> animations;
static Animation badAnimation; //< Bad animation when an out of bound
// animation is requested.
bool adaptCollisionMaskAutomatically; ///< If set to true, the collision
///< mask will be automatically
///< adapted to the animation of the
///< object.
};
} // namespace gd

View File

@@ -23,88 +23,20 @@
namespace gd {
Animation SpriteObject::badAnimation;
SpriteObject::SpriteObject()
: updateIfNotVisible(false), adaptCollisionMaskAutomatically(true) {}
: updateIfNotVisible(false) {}
SpriteObject::~SpriteObject(){};
void SpriteObject::DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) {
updateIfNotVisible = element.GetBoolAttribute("updateIfNotVisible", true);
adaptCollisionMaskAutomatically =
element.GetBoolAttribute("adaptCollisionMaskAutomatically", false);
RemoveAllAnimations();
const gd::SerializerElement& animationsElement =
element.GetChild("animations", 0, "Animations");
animationsElement.ConsiderAsArrayOf("animation", "Animation");
for (std::size_t i = 0; i < animationsElement.GetChildrenCount(); ++i) {
const gd::SerializerElement& animationElement =
animationsElement.GetChild(i);
Animation newAnimation;
newAnimation.useMultipleDirections = animationElement.GetBoolAttribute(
"useMultipleDirections", false, "typeNormal");
newAnimation.SetName(animationElement.GetStringAttribute("name", ""));
// Compatibility with GD <= 3.3
if (animationElement.HasChild("Direction")) {
for (std::size_t j = 0;
j < animationElement.GetChildrenCount("Direction");
++j) {
Direction direction;
direction.UnserializeFrom(animationElement.GetChild("Direction", j));
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
newAnimation.SetDirection(direction,
newAnimation.GetDirectionsCount() - 1);
}
}
// End of compatibility code
else {
const gd::SerializerElement& directionsElement =
animationElement.GetChild("directions");
directionsElement.ConsiderAsArrayOf("direction");
for (std::size_t j = 0; j < directionsElement.GetChildrenCount(); ++j) {
Direction direction;
direction.UnserializeFrom(directionsElement.GetChild(j));
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
newAnimation.SetDirection(direction,
newAnimation.GetDirectionsCount() - 1);
}
}
AddAnimation(newAnimation);
}
animations.UnserializeFrom(element);
}
void SpriteObject::DoSerializeTo(gd::SerializerElement& element) const {
element.SetAttribute("updateIfNotVisible", updateIfNotVisible);
element.SetAttribute("adaptCollisionMaskAutomatically",
adaptCollisionMaskAutomatically);
// Animations
gd::SerializerElement& animationsElement = element.AddChild("animations");
animationsElement.ConsiderAsArrayOf("animation");
for (std::size_t k = 0; k < GetAnimationsCount(); k++) {
gd::SerializerElement& animationElement =
animationsElement.AddChild("animation");
animationElement.SetAttribute("useMultipleDirections",
GetAnimation(k).useMultipleDirections);
animationElement.SetAttribute("name", GetAnimation(k).GetName());
gd::SerializerElement& directionsElement =
animationElement.AddChild("directions");
directionsElement.ConsiderAsArrayOf("direction");
for (std::size_t l = 0; l < GetAnimation(k).GetDirectionsCount(); l++) {
GetAnimation(k).GetDirection(l).SerializeTo(
directionsElement.AddChild("direction"));
}
}
animations.SerializeTo(element);
}
std::map<gd::String, gd::PropertyDescriptor> SpriteObject::GetProperties()
@@ -127,16 +59,7 @@ bool SpriteObject::UpdateProperty(const gd::String& name,
}
void SpriteObject::ExposeResources(gd::ArbitraryResourceWorker& worker) {
for (std::size_t j = 0; j < GetAnimationsCount(); j++) {
for (std::size_t k = 0; k < GetAnimation(j).GetDirectionsCount(); k++) {
for (std::size_t l = 0;
l < GetAnimation(j).GetDirection(k).GetSpritesCount();
l++) {
worker.ExposeImage(
GetAnimation(j).GetDirection(k).GetSprite(l).GetImageName());
}
}
}
animations.ExposeResources(worker);
}
std::map<gd::String, gd::PropertyDescriptor>
@@ -168,42 +91,12 @@ bool SpriteObject::UpdateInitialInstanceProperty(
return true;
}
const Animation& SpriteObject::GetAnimation(std::size_t nb) const {
if (nb >= animations.size()) return badAnimation;
return animations[nb];
const SpriteAnimationList& SpriteObject::GetAnimations() const {
return animations;
}
Animation& SpriteObject::GetAnimation(std::size_t nb) {
if (nb >= animations.size()) return badAnimation;
return animations[nb];
}
void SpriteObject::AddAnimation(const Animation& animation) {
animations.push_back(animation);
}
bool SpriteObject::RemoveAnimation(std::size_t nb) {
if (nb >= GetAnimationsCount()) return false;
animations.erase(animations.begin() + nb);
return true;
}
void SpriteObject::SwapAnimations(std::size_t firstIndex,
std::size_t secondIndex) {
if (firstIndex < animations.size() && secondIndex < animations.size() &&
firstIndex != secondIndex)
std::swap(animations[firstIndex], animations[secondIndex]);
}
void SpriteObject::MoveAnimation(std::size_t oldIndex, std::size_t newIndex) {
if (oldIndex >= animations.size() || newIndex >= animations.size()) return;
auto animation = animations[oldIndex];
animations.erase(animations.begin() + oldIndex);
animations.insert(animations.begin() + newIndex, animation);
SpriteAnimationList& SpriteObject::GetAnimations() {
return animations;
}
} // namespace gd

View File

@@ -4,18 +4,15 @@
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_SPRITEOBJECT_H
#define GDCORE_SPRITEOBJECT_H
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
#pragma once
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h"
#include "GDCore/Project/Object.h"
namespace gd {
class InitialInstance;
class Object;
class Layout;
class Sprite;
class Animation;
class SerializerElement;
class PropertyDescriptor;
} // namespace gd
@@ -59,76 +56,15 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
gd::Project& project,
gd::Layout& scene) override;
/** \name Animations
* Methods related to animations management
*/
///@{
/**
* \brief Return the animation at the specified index.
* If the index is out of bound, a "bad animation" object is returned.
* \brief Return the animation configuration.
*/
const Animation& GetAnimation(std::size_t nb) const;
const SpriteAnimationList& GetAnimations() const;
/**
* \brief Return the animation at the specified index.
* If the index is out of bound, a "bad animation" object is returned.
* @brief Return the animation configuration.
*/
Animation& GetAnimation(std::size_t nb);
/**
* \brief Return the number of animations this object has.
*/
std::size_t GetAnimationsCount() const { return animations.size(); };
/**
* \brief Add an animation at the end of the existing ones.
*/
void AddAnimation(const Animation& animation);
/**
* \brief Remove an animation.
*/
bool RemoveAnimation(std::size_t nb);
/**
* \brief Remove all animations.
*/
void RemoveAllAnimations() { animations.clear(); }
/**
* \brief Return true if the object hasn't any animation.
*/
bool HasNoAnimations() const { return animations.empty(); }
/**
* \brief Swap the position of two animations
*/
void SwapAnimations(std::size_t firstIndex, std::size_t secondIndex);
/**
* \brief Change the position of the specified animation
*/
void MoveAnimation(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Return a read-only reference to the vector containing all the
* animation of the object.
*/
const std::vector<Animation>& GetAllAnimations() const { return animations; }
/**
* @brief Check if the collision mask adapts automatically to the animation.
*/
bool AdaptCollisionMaskAutomatically() const {
return adaptCollisionMaskAutomatically;
}
/**
* @brief Set if the collision mask adapts automatically to the animation.
*/
void SetAdaptCollisionMaskAutomatically(bool enable) {
adaptCollisionMaskAutomatically = enable;
}
SpriteAnimationList& GetAnimations();
/**
* \brief Set if the object animation should be played even if the object is
@@ -143,25 +79,17 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
* is hidden or far from the camera (false by default).
*/
bool GetUpdateIfNotVisible() const { return updateIfNotVisible; }
///@}
private:
void DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) override;
void DoSerializeTo(gd::SerializerElement& element) const override;
mutable std::vector<Animation> animations;
SpriteAnimationList animations;
bool updateIfNotVisible; ///< If set to true, ask the game engine to play
///< object animation even if hidden or far from
///< the screen.
static Animation badAnimation; //< Bad animation when an out of bound
// animation is requested.
bool adaptCollisionMaskAutomatically; ///< If set to true, the collision
///< mask will be automatically
///< adapted to the animation of the
///< object.
};
} // namespace gd
#endif // GDCORE_SPRITEOBJECT_H

View File

@@ -114,6 +114,8 @@ public:
/**
* \brief Erase any existing include file and add the specified include.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
virtual AbstractFunctionMetadata &
SetIncludeFile(const gd::String &includeFile) = 0;

View File

@@ -13,12 +13,15 @@
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Tools/Log.h"
namespace gd {
const std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::badProperties;
BehaviorMetadata::BehaviorMetadata(
const gd::String& extensionNamespace_,
const gd::String& nameWithNamespace,
@@ -47,8 +50,14 @@ BehaviorMetadata::BehaviorMetadata(
"BehaviorMetadata is valid for: " + nameWithNamespace);
}
if (instance) instance->SetTypeName(nameWithNamespace);
if (sharedDatasInstance) sharedDatasInstance->SetTypeName(nameWithNamespace);
if (instance) {
instance->SetTypeName(nameWithNamespace);
instance->InitializeContent();
}
if (sharedDatasInstance) {
sharedDatasInstance->SetTypeName(nameWithNamespace);
sharedDatasInstance->InitializeContent();
}
}
gd::InstructionMetadata& BehaviorMetadata::AddCondition(
@@ -405,10 +414,30 @@ gd::Behavior& BehaviorMetadata::Get() const {
return *instance;
}
std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetProperties() const {
if (!instance) {
return badProperties;
}
// TODO Properties should be declared on BehaviorMetadata directly.
// - Add 2 `properties` members (one for shared properties)
// - Add methods to declare new properties
return instance->GetProperties();
}
gd::BehaviorsSharedData* BehaviorMetadata::GetSharedDataInstance() const {
return sharedDatasInstance.get();
}
std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetSharedProperties() const {
if (!sharedDatasInstance) {
return badProperties;
}
// TODO Properties should be declared on BehaviorMetadata directly.
// - Add 2 `properties` members (one for shared properties)
// - Add methods to declare new properties
return sharedDatasInstance->GetProperties();
}
const std::vector<gd::String>& BehaviorMetadata::GetRequiredBehaviorTypes() const {
requiredBehaviors.clear();
for (auto& property : Get().GetProperties()) {

View File

@@ -18,6 +18,7 @@ class BehaviorsSharedData;
class MultipleInstructionMetadata;
class InstructionMetadata;
class ExpressionMetadata;
class PropertyDescriptor;
} // namespace gd
namespace gd {
@@ -204,6 +205,8 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
* \brief Erase any existing include file and add the specified include.
* \note The requirement may vary depending on the platform: Most of the time,
* the include file contains the declaration of the behavior.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
BehaviorMetadata& SetIncludeFile(const gd::String& includeFile) override;
@@ -302,6 +305,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
*/
gd::Behavior& Get() const;
/**
* \brief Called when the IDE wants to know about the custom properties of the
* behavior.
*
* \return a std::map with properties names as key.
* \see gd::PropertyDescriptor
*/
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const;
/**
* \brief Return the associated gd::BehaviorsSharedData, handling behavior
* shared data, if any (nullptr if none).
@@ -311,6 +323,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
*/
gd::BehaviorsSharedData* GetSharedDataInstance() const;
/**
* \brief Called when the IDE wants to know about the custom shared properties
* of the behavior.
*
* \return a std::map with properties names as key.
* \see gd::PropertyDescriptor
*/
std::map<gd::String, gd::PropertyDescriptor> GetSharedProperties() const;
/**
* \brief Return a reference to a map containing the names of the actions
* (as keys) and the metadata associated with (as values).
@@ -357,6 +378,8 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
std::shared_ptr<gd::Behavior> instance;
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance;
static const std::map<gd::String, gd::PropertyDescriptor> badProperties;
};
} // namespace gd

View File

@@ -65,6 +65,8 @@ class GD_CORE_API EffectMetadata {
/**
* \brief Clear any existing include file and add the specified include file.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
EffectMetadata& SetIncludeFile(const gd::String& includeFile);

View File

@@ -288,6 +288,8 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
/**
* \brief Erase any existing include file and add the specified include.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
ExpressionMetadata& SetIncludeFile(
const gd::String& includeFile) override {

View File

@@ -494,6 +494,8 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
/**
* \brief Erase any existing include file and add the specified include.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
InstructionMetadata &SetIncludeFile(const gd::String &includeFile) override {
codeExtraInformation.includeFiles.clear();

View File

@@ -142,6 +142,8 @@ public:
* \brief Erase any existing include file and add the specified include.
* \note The requirement may vary depending on the platform: Most of the time,
* the include file contains the declaration of the behavior.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
virtual InstructionOrExpressionContainerMetadata &
SetIncludeFile(const gd::String &includeFile) = 0;

View File

@@ -150,6 +150,10 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata
return *this;
}
/**
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
MultipleInstructionMetadata &SetIncludeFile(const gd::String &includeFile) override {
if (expression)
expression->SetIncludeFile(includeFile);

View File

@@ -264,6 +264,8 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
* \brief Erase any existing include file and add the specified include.
* \note The requirement may vary depending on the platform: Most of the time,
* the include file contains the declaration of the object.
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
* error prone.
*/
ObjectMetadata& SetIncludeFile(const gd::String& includeFile) override;

View File

@@ -285,22 +285,6 @@ class GD_CORE_API PlatformExtension {
std::shared_ptr<gd::Behavior> instance,
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance);
/**
* \brief Declare a new events based behavior as being part of the extension.
*
* \param name The name of the behavior
* \param fullname The user friendly name of the behavior
* \param description The user friendly description of the behavior
* \param group The behavior category label
* \param icon The icon of the behavior.
*/
gd::BehaviorMetadata& AddEventsBasedBehavior(
const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& group_,
const gd::String& icon_);
/**
* \brief Declare a new effect as being part of the extension.
* \param name The internal name of the effect (also called effect type).

View File

@@ -86,8 +86,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
void OnVisitOperatorNode(OperatorNode& node) override {
ReportAnyError(node);
// The "required" type ("parentType") will be used when visiting the first operand.
// Note that it may be refined thanks to this first operand (see later).
node.leftHandSide->Visit(*this);
const Type leftType = childType;
const Type leftType = childType; // Store the type of the first operand.
if (leftType == Type::Number) {
if (node.op == ' ') {
@@ -120,15 +122,19 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
node.rightHandSide->location);
}
parentType = leftType;
// The "required" type ("parentType") of the second operator is decided by:
// - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
// - the first operand.
parentType = ShouldTypeBeRefined(parentType) ? leftType : parentType;
node.rightHandSide->Visit(*this);
const Type rightType = childType;
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// The type of the overall operator ("childType") is decided by:
// - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
// - the first operand. Unless it can (`number|string`) or should (`unknown`) be refined, then:
// - the right operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
childType = (leftType == Type::Unknown || leftType == Type::NumberOrString) ? leftType : rightType;
childType = ShouldTypeBeRefined(parentType) ? (ShouldTypeBeRefined(leftType) ? leftType : rightType) : parentType;
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
ReportAnyError(node);
@@ -395,6 +401,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
}
static bool ShouldTypeBeRefined(Type type) {
return (type == Type::Unknown || type == Type::NumberOrString);
}
static Type StringToType(const gd::String &type);
static const gd::String &TypeToString(Type type);
static const gd::String unknownTypeString;

View File

@@ -39,7 +39,7 @@ ObjectAssetSerializer::GetObjectExtensionName(const gd::Object &object) {
void ObjectAssetSerializer::SerializeTo(
gd::Project &project, const gd::Object &object,
const gd::String &objectFullName, SerializerElement &element,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap) {
std::vector<gd::String> &usedResourceNames) {
auto cleanObject = object.Clone();
cleanObject->GetVariables().Clear();
cleanObject->GetEffects().Clear();
@@ -49,11 +49,6 @@ void ObjectAssetSerializer::SerializeTo(
gd::String extensionName = GetObjectExtensionName(*cleanObject);
std::map<gd::String, gd::String> resourcesNameReverseMap;
gd::ObjectAssetSerializer::RenameObjectResourceFiles(
project, *cleanObject, "", objectFullName, resourcesFileNameMap,
resourcesNameReverseMap);
element.SetAttribute("id", "");
element.SetAttribute("name", "");
element.SetAttribute("license", "");
@@ -86,23 +81,16 @@ void ObjectAssetSerializer::SerializeTo(
auto &resourcesManager = project.GetResourcesManager();
gd::ResourcesInUseHelper resourcesInUse(resourcesManager);
cleanObject->GetConfiguration().ExposeResources(resourcesInUse);
for (auto &&newResourceName : resourcesInUse.GetAllResources()) {
if (newResourceName.length() == 0) {
for (auto &&resourceName : resourcesInUse.GetAllResources()) {
if (resourceName.length() == 0) {
continue;
}
auto &resource = resourcesManager.GetResource(
resourcesNameReverseMap.find(newResourceName) !=
resourcesNameReverseMap.end()
? resourcesNameReverseMap[newResourceName]
: newResourceName);
usedResourceNames.push_back(resourceName);
auto &resource = resourcesManager.GetResource(resourceName);
SerializerElement &resourceElement = resourcesElement.AddChild("resource");
resource.SerializeTo(resourceElement);
// Override name and file because the project and the asset don't use the
// same one.
resourceElement.SetAttribute("kind", resource.GetKind());
resourceElement.SetAttribute("name", newResourceName);
auto &oldFilePath = resource.GetFile();
resourceElement.SetAttribute("file", newResourceName);
resourceElement.SetAttribute("name", resource.GetName());
}
SerializerElement &requiredExtensionsElement =
@@ -120,127 +108,4 @@ void ObjectAssetSerializer::SerializeTo(
objectAssetElement.AddChild("customization");
customizationElement.ConsiderAsArrayOf("empty");
}
void ObjectAssetSerializer::RenameObjectResourceFiles(
gd::Project &project, gd::Object &object,
const gd::String &destinationDirectory, const gd::String &objectFullName,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap,
std::map<gd::String, gd::String> &resourcesNameReverseMap) {
std::map<gd::String, gd::String> cleanedResourcesFileNameMap;
gd::AssetResourcePathCleaner assetResourcePathCleaner(
project.GetResourcesManager(), cleanedResourcesFileNameMap,
resourcesNameReverseMap);
object.GetConfiguration().ExposeResources(assetResourcePathCleaner);
// Use asset store script naming conventions for sprite resource files.
if (object.GetConfiguration().GetType() == "Sprite") {
gd::SpriteObject &spriteConfiguration =
dynamic_cast<gd::SpriteObject &>(object.GetConfiguration());
/// Resource files may be duplicated because the files names allow the
/// asset store script to rebuild the animations.
std::map<gd::String, std::vector<gd::String>> normalizedFileNames;
for (std::size_t animationIndex = 0;
animationIndex < spriteConfiguration.GetAnimationsCount();
animationIndex++) {
auto &animation = spriteConfiguration.GetAnimation(animationIndex);
auto &direction = animation.GetDirection(0);
const gd::String &animationName =
animation.GetName().empty()
? gd::String::From(animationIndex)
: animation.GetName().FindAndReplace("_", " ", true);
// Search frames that share the same resource.
std::map<gd::String, std::vector<int>> frameIndexes;
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
frameIndex++) {
auto &frame = direction.GetSprite(frameIndex);
if (frameIndexes.find(frame.GetImageName()) == frameIndexes.end()) {
std::vector<int> emptyVector;
frameIndexes[frame.GetImageName()] = emptyVector;
}
auto &indexes = frameIndexes[frame.GetImageName()];
indexes.push_back(frameIndex);
}
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
frameIndex++) {
auto &frame = direction.GetSprite(frameIndex);
auto oldName = frame.GetImageName();
if (normalizedFileNames.find(oldName) == normalizedFileNames.end()) {
std::vector<gd::String> value;
normalizedFileNames[oldName] = value;
}
gd::String newName = objectFullName;
if (spriteConfiguration.GetAnimationsCount() > 1) {
newName += "_" + animationName;
}
if (direction.GetSpritesCount() > 1) {
newName += "_";
auto &indexes = frameIndexes[frame.GetImageName()];
for (size_t i = 0; i < indexes.size(); i++) {
newName += gd::String::From(indexes.at(i) + 1);
if (i < indexes.size() - 1) {
newName += ";";
}
}
}
gd::String extension = oldName.substr(oldName.find_last_of("."));
newName += extension;
frame.SetImageName(newName);
auto &newNames = normalizedFileNames[oldName];
if (find(newNames.begin(), newNames.end(), newName) == newNames.end()) {
newNames.push_back(newName);
}
}
}
for (std::map<gd::String, gd::String>::const_iterator it =
cleanedResourcesFileNameMap.begin();
it != cleanedResourcesFileNameMap.end(); ++it) {
if (!it->first.empty()) {
const gd::String &originFile = it->first;
const gd::String &destinationFile = it->second;
std::vector<gd::String> value;
for (auto &&normalizedFileName : normalizedFileNames[destinationFile]) {
value.push_back(normalizedFileName);
}
resourcesFileNameMap[originFile] = value;
}
}
auto clonedResourcesNameReverseMap = resourcesNameReverseMap;
resourcesNameReverseMap.clear();
for (std::map<gd::String, gd::String>::const_iterator it =
clonedResourcesNameReverseMap.begin();
it != clonedResourcesNameReverseMap.end(); ++it) {
if (!it->first.empty()) {
const gd::String& newResourceName = it->first;
const gd::String& oldResourceName = it->second;
for (auto&& normalizedFileName : normalizedFileNames[newResourceName]) {
resourcesNameReverseMap[normalizedFileName] =
oldResourceName;
}
}
}
}
else {
for (std::map<gd::String, gd::String>::const_iterator it =
cleanedResourcesFileNameMap.begin();
it != cleanedResourcesFileNameMap.end(); ++it) {
if (!it->first.empty()) {
const gd::String &originFile = it->first;
const gd::String &destinationFile = it->second;
std::vector<gd::String> value;
value.push_back(destinationFile);
resourcesFileNameMap[originFile] = value;
}
}
}
}
} // namespace gd

View File

@@ -39,27 +39,18 @@ public:
* \param object The object to serialize as an asset.
* \param objectFullName The object name with spaces instead of PascalCase.
* \param element The element where the asset is serialize.
* \param resourcesFileNameMap The map from project resource file paths to
* asset resource file paths. Resource files of Sprite objects may be
* duplicated because the files names allow the asset store script to rebuild
* the animations.
* \param usedResourceNames Return the names of the resources used by the asset.
*/
static void
SerializeTo(gd::Project &project, const gd::Object &object,
const gd::String &objectFullName, SerializerElement &element,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap);
std::vector<gd::String> &usedResourceNames);
~ObjectAssetSerializer(){};
private:
ObjectAssetSerializer(){};
static void RenameObjectResourceFiles(
gd::Project &project, gd::Object &object,
const gd::String &destinationDirectory, const gd::String &objectFullName,
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap,
std::map<gd::String, gd::String> &resourcesNameReverseMap);
static gd::String GetObjectExtensionName(const gd::Object &object);
};

View File

@@ -4,8 +4,6 @@
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/Behavior.h"
#include <iostream>
#include "GDCore/Project/PropertyDescriptor.h"
namespace gd {

View File

@@ -13,12 +13,14 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Log.h"
#include "GDCore/Project/CustomConfigurationHelper.h"
#include "GDCore/Project/InitialInstance.h"
using namespace gd;
void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& objectConfiguration) {
project = objectConfiguration.project;
objectContent = objectConfiguration.objectContent;
animations = objectConfiguration.animations;
// There is no default copy for a map of unique_ptr like childObjectConfigurations.
childObjectConfigurations.clear();
@@ -88,23 +90,38 @@ bool CustomObjectConfiguration::UpdateProperty(const gd::String& propertyName,
std::map<gd::String, gd::PropertyDescriptor>
CustomObjectConfiguration::GetInitialInstanceProperties(
const gd::InitialInstance& instance,
gd::Project& project,
gd::Layout& scene) {
return std::map<gd::String, gd::PropertyDescriptor>();
const gd::InitialInstance &initialInstance, gd::Project &project,
gd::Layout &scene) {
std::map<gd::String, gd::PropertyDescriptor> properties;
if (!animations.HasNoAnimations()) {
properties["animation"] =
gd::PropertyDescriptor(
gd::String::From(initialInstance.GetRawDoubleProperty("animation")))
.SetLabel(_("Animation"))
.SetType("number");
}
return properties;
}
bool CustomObjectConfiguration::UpdateInitialInstanceProperty(
gd::InitialInstance& instance,
const gd::String& name,
const gd::String& value,
gd::Project& project,
gd::Layout& scene) {
return false;
gd::InitialInstance &initialInstance, const gd::String &name,
const gd::String &value, gd::Project &project, gd::Layout &scene) {
if (name == "animation") {
initialInstance.SetRawDoubleProperty(
"animation", std::max(0, value.empty() ? 0 : value.To<int>()));
}
return true;
}
void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const {
element.AddChild("content") = objectContent;
if (!animations.HasNoAnimations()) {
auto &animatableElement = element.AddChild("animatable");
animations.SerializeTo(animatableElement);
}
auto &childrenContentElement = element.AddChild("childrenContent");
for (auto &pair : childObjectConfigurations) {
auto &childName = pair.first;
@@ -116,6 +133,12 @@ void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const
void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
const SerializerElement& element) {
objectContent = element.GetChild("content");
if (element.HasChild("animatable")) {
auto &animatableElement = element.GetChild("animatable");
animations.UnserializeFrom(animatableElement);
}
auto &childrenContentElement = element.GetChild("childrenContent");
for (auto &pair : childrenContentElement.GetAllChildren()) {
auto &childName = pair.first;
@@ -126,6 +149,8 @@ void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
}
void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& worker) {
animations.ExposeResources(worker);
std::map<gd::String, gd::PropertyDescriptor> properties = GetProperties();
for (auto& property : properties) {
@@ -178,3 +203,11 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
configuration.ExposeResources(worker);
}
}
const SpriteAnimationList& CustomObjectConfiguration::GetAnimations() const {
return animations;
}
SpriteAnimationList& CustomObjectConfiguration::GetAnimations() {
return animations;
}

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 GDCORE_CUSTOMOBJECTCONFIGURATION_H
#define GDCORE_CUSTOMOBJECTCONFIGURATION_H
#pragma once
#include "GDCore/Project/ObjectConfiguration.h"
@@ -16,7 +15,7 @@
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h"
using namespace gd;
@@ -72,7 +71,17 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
gd::ObjectConfiguration &GetChildObjectConfiguration(const gd::String& objectName);
protected:
/**
* \brief Return the animation configuration for Animatable custom objects.
*/
const SpriteAnimationList& GetAnimations() const;
/**
* @brief Return the animation configuration for Animatable custom objects.
*/
SpriteAnimationList& GetAnimations();
protected:
void DoSerializeTo(SerializerElement& element) const override;
void DoUnserializeFrom(Project& project, const SerializerElement& element) override;
@@ -84,6 +93,8 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
static gd::ObjectConfiguration badObjectConfiguration;
SpriteAnimationList animations;
/**
* Initialize configuration using another configuration. Used by copy-ctor
* and assign-op.
@@ -95,6 +106,5 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
*/
void Init(const gd::CustomObjectConfiguration& object);
};
} // namespace gd
#endif // GDCORE_CUSTOMOBJECTCONFIGURATION_H
} // namespace gd

View File

@@ -13,7 +13,10 @@ EventsBasedObject::EventsBasedObject()
: AbstractEventsBasedEntity(
"MyObject",
gd::EventsFunctionsContainer::FunctionOwner::Object),
ObjectsContainer() {
ObjectsContainer(),
isRenderedIn3D(false),
isAnimatable(false),
isTextContainer(false) {
}
EventsBasedObject::~EventsBasedObject() {}
@@ -30,6 +33,12 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
if (isRenderedIn3D) {
element.SetBoolAttribute("is3D", true);
}
if (isAnimatable) {
element.SetBoolAttribute("isAnimatable", true);
}
if (isTextContainer) {
element.SetBoolAttribute("isTextContainer", true);
}
AbstractEventsBasedEntity::SerializeTo(element);
SerializeObjectsTo(element.AddChild("objects"));
@@ -40,6 +49,8 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
defaultName = element.GetStringAttribute("defaultName");
isRenderedIn3D = element.GetBoolAttribute("is3D", false);
isAnimatable = element.GetBoolAttribute("isAnimatable", false);
isTextContainer = element.GetBoolAttribute("isTextContainer", false);
AbstractEventsBasedEntity::UnserializeFrom(project, element);
UnserializeObjectsFrom(project, element.GetChild("objects"));

View File

@@ -85,6 +85,32 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
*/
bool IsRenderedIn3D() const { return isRenderedIn3D; }
/**
* \brief Declare an Animatable capability.
*/
EventsBasedObject& MarkAsAnimatable(bool isAnimatable_) {
isAnimatable = isAnimatable_;
return *this;
}
/**
* \brief Return true if the object needs an Animatable capability.
*/
bool IsAnimatable() const { return isAnimatable; }
/**
* \brief Declare a TextContainer capability.
*/
EventsBasedObject& MarkAsTextContainer(bool isTextContainer_) {
isTextContainer = isTextContainer_;
return *this;
}
/**
* \brief Return true if the object needs a TextContainer capability.
*/
bool IsTextContainer() const { return isTextContainer; }
void SerializeTo(SerializerElement& element) const override;
void UnserializeFrom(gd::Project& project,
@@ -93,6 +119,8 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
private:
gd::String defaultName;
bool isRenderedIn3D;
bool isAnimatable;
bool isTextContainer;
};
} // namespace gd

View File

@@ -15,7 +15,7 @@
namespace gd {
gd::String* InitialInstance::badStringProperyValue = NULL;
gd::String* InitialInstance::badStringPropertyValue = NULL;
InitialInstance::InitialInstance()
: objectName(""),
@@ -34,6 +34,7 @@ InitialInstance::InitialInstance()
depth(0),
locked(false),
sealed(false),
keepRatio(true),
persistentUuid(UUID::MakeUuid4()) {}
void InitialInstance::UnserializeFrom(const SerializerElement& element) {
@@ -58,6 +59,7 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
SetLayer(element.GetStringAttribute("layer"));
SetLocked(element.GetBoolAttribute("locked", false));
SetSealed(element.GetBoolAttribute("sealed", false));
SetShouldKeepRatio(element.GetBoolAttribute("keepRatio", false));
persistentUuid = element.GetStringAttribute("persistentUuid");
if (persistentUuid.empty()) ResetPersistentUuid();
@@ -120,6 +122,7 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
if (HasCustomDepth()) element.SetAttribute("depth", GetCustomDepth());
if (IsLocked()) element.SetAttribute("locked", IsLocked());
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());
if (persistentUuid.empty()) persistentUuid = UUID::MakeUuid4();
element.SetStringAttribute("persistentUuid", persistentUuid);
@@ -188,10 +191,10 @@ double InitialInstance::GetRawDoubleProperty(const gd::String& name) const {
const gd::String& InitialInstance::GetRawStringProperty(
const gd::String& name) const {
if (!badStringProperyValue) badStringProperyValue = new gd::String("");
if (!badStringPropertyValue) badStringPropertyValue = new gd::String("");
const auto& it = stringProperties.find(name);
return it != stringProperties.end() ? it->second : *badStringProperyValue;
return it != stringProperties.end() ? it->second : *badStringPropertyValue;
}
void InitialInstance::SetRawDoubleProperty(const gd::String& name,

View File

@@ -206,6 +206,17 @@ class GD_CORE_API InitialInstance {
*/
void SetSealed(bool enable = true) { sealed = enable; }
/**
* \brief Return true if the dimensions (width, height and depth) should keep
* the same ratio.
*/
bool ShouldKeepRatio() const { return keepRatio; };
/**
* \brief Define if instance's dimensions should keep the same ratio.
*/
void SetShouldKeepRatio(bool enable = true) { keepRatio = enable; }
///@}
/** \name Variable management
@@ -340,11 +351,13 @@ class GD_CORE_API InitialInstance {
gd::VariablesContainer initialVariables; ///< Instance specific variables
bool locked; ///< True if the instance is locked
bool sealed; ///< True if the instance is sealed
bool keepRatio; ///< True if the instance's dimensions
/// should keep the same ratio.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for hot reloading.
/// useful for hot reloading.
static gd::String*
badStringProperyValue; ///< Empty string returned by GetRawStringProperty
badStringPropertyValue; ///< Empty string returned by GetRawStringProperty
};
} // namespace gd

View File

@@ -120,9 +120,6 @@ class GD_CORE_API Object {
*/
const gd::String& GetType() const { return configuration->GetType(); }
/** \brief Shortcut to check if the object is a 3D object.
*/
bool Is3DObject() const { return configuration->Is3DObject(); }
///@}
/** \name Behaviors management

View File

@@ -5,11 +5,8 @@
*/
#include "GDCore/Project/ObjectConfiguration.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/CustomBehavior.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -20,7 +17,7 @@ namespace gd {
ObjectConfiguration::~ObjectConfiguration() {}
ObjectConfiguration::ObjectConfiguration(): is3DObject(false) {}
ObjectConfiguration::ObjectConfiguration() {}
std::map<gd::String, gd::PropertyDescriptor> ObjectConfiguration::GetProperties() const {
std::map<gd::String, gd::PropertyDescriptor> nothing;

View File

@@ -63,20 +63,12 @@ class GD_CORE_API ObjectConfiguration {
*/
void SetType(const gd::String& type_) {
type = type_;
// For now, as a shortcut, consider only the objects from the built-in 3D extension
// to be 3D object.
is3DObject = type.find("Scene3D::") == 0;
}
/** \brief Return the type of the object.
*/
const gd::String& GetType() const { return type; }
/** \brief Shortcut to check if the object is a 3D object.
*/
bool Is3DObject() const { return is3DObject; }
/** \name Object properties
* Reading and updating object configuration properties
*/
@@ -180,7 +172,6 @@ class GD_CORE_API ObjectConfiguration {
protected:
gd::String type; ///< Which type of object is represented by this
///< configuration.
bool is3DObject;
/**
* \brief Derived object configuration can redefine this method to load

View File

@@ -102,21 +102,19 @@ std::unique_ptr<gd::Object> Project::CreateObject(
behavior->SetDefaultBehavior(true);
};
if (Project::HasEventsBasedObject(objectType)) {
addDefaultBehavior("EffectCapability::EffectBehavior");
addDefaultBehavior("ResizableCapability::ResizableBehavior");
addDefaultBehavior("ScalableCapability::ScalableBehavior");
addDefaultBehavior("FlippableCapability::FlippableBehavior");
} else {
auto& objectMetadata =
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
if (MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
gd::LogWarning("Object: " + name + " has an unknown type: " + objectType);
}
for (auto& behaviorType : objectMetadata.GetDefaultBehaviors()) {
auto &objectMetadata =
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
if (!MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
for (auto &behaviorType : objectMetadata.GetDefaultBehaviors()) {
addDefaultBehavior(behaviorType);
}
}
// During project deserialization, event-based object metadata are not yet
// generated. Default behaviors will be added by
// MetadataDeclarationHelper::UpdateCustomObjectDefaultBehaviors
else if (!project.HasEventsBasedObject(objectType)) {
gd::LogWarning("Object: " + name + " has an unknown type: " + objectType);
}
return std::move(object);
}

View File

@@ -29,10 +29,15 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
for (const gd::String& information : extraInformation) {
extraInformationElement.AddChild("").SetStringValue(information);
}
element.AddChild("hidden").SetBoolValue(hidden);
if (hidden) {
element.AddChild("hidden").SetBoolValue(hidden);
}
if (deprecated) {
element.AddChild("deprecated").SetBoolValue(deprecated);
}
if (advanced) {
element.AddChild("advanced").SetBoolValue(advanced);
}
}
void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
@@ -64,6 +69,9 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
deprecated = element.HasChild("deprecated")
? element.GetChild("deprecated").GetBoolValue()
: false;
advanced = element.HasChild("advanced")
? element.GetChild("advanced").GetBoolValue()
: false;
}
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {

View File

@@ -31,14 +31,14 @@ class GD_CORE_API PropertyDescriptor {
*/
PropertyDescriptor(gd::String propertyValue)
: currentValue(propertyValue), type("string"), label(""), hidden(false),
deprecated(false),
deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
/**
* \brief Empty constructor creating an empty property to be displayed.
*/
PropertyDescriptor()
: hidden(false), deprecated(false),
: hidden(false), deprecated(false), advanced(false),
measurementUnit(gd::MeasurementUnit::GetUndefined()){};
/**
@@ -159,6 +159,19 @@ class GD_CORE_API PropertyDescriptor {
*/
bool IsDeprecated() const { return deprecated; }
/**
* \brief Set if the property is marked as advanced.
*/
PropertyDescriptor& SetAdvanced(bool enable = true) {
advanced = enable;
return *this;
}
/**
* \brief Check if the property is marked as advanced.
*/
bool IsAdvanced() const { return advanced; }
/** \name Serialization
*/
///@{
@@ -197,6 +210,7 @@ class GD_CORE_API PropertyDescriptor {
///< box.
bool hidden;
bool deprecated;
bool advanced;
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
};

View File

@@ -91,7 +91,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
sprite.SetImageName("res1");
anim.SetDirectionsCount(1);
anim.GetDirection(0).AddSprite(sprite);
spriteConfiguration.AddAnimation(anim);
spriteConfiguration.GetAnimations().AddAnimation(anim);
gd::Object obj("myObject", "", spriteConfiguration.Clone());
project.InsertObject(obj, 0);
@@ -138,7 +138,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
sprite.SetImageName("res1");
anim.SetDirectionsCount(1);
anim.GetDirection(0).AddSprite(sprite);
spriteConfiguration.AddAnimation(anim);
spriteConfiguration.GetAnimations().AddAnimation(anim);
gd::Object obj("myObject", "", spriteConfiguration.Clone());
layout.InsertObject(obj, 0);
@@ -437,7 +437,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
sprite.SetImageName("res1");
anim.SetDirectionsCount(1);
anim.GetDirection(0).AddSprite(sprite);
spriteConfiguration.AddAnimation(anim);
spriteConfiguration.GetAnimations().AddAnimation(anim);
gd::Object obj("myObject", "", spriteConfiguration.Clone());
layout.InsertObject(obj, 0);

View File

@@ -30,6 +30,21 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
layout1.GetVariables().InsertNew("MySceneBooleanVariable").SetBool(true);
layout1.GetVariables().InsertNew("MySceneStructureVariable").GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneStructureVariable2").GetChild("MyChild");
layout1.GetVariables().InsertNew("MySceneEmptyArrayVariable").CastTo(gd::Variable::Type::Array);
{
auto& variable = layout1.GetVariables().InsertNew("MySceneNumberArrayVariable");
variable.CastTo(gd::Variable::Type::Array);
variable.PushNew().SetValue(1);
variable.PushNew().SetValue(2);
variable.PushNew().SetValue(3);
}
{
auto& variable = layout1.GetVariables().InsertNew("MySceneStringArrayVariable");
variable.CastTo(gd::Variable::Type::Array);
variable.PushNew().SetString("1");
variable.PushNew().SetString("2");
variable.PushNew().SetString("3");
}
auto &mySpriteObject = layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
mySpriteObject.GetVariables().InsertNew("MyNumberVariable").SetValue(123);
@@ -1295,6 +1310,221 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
REQUIRE(output == "getVariableForObject(MySpriteObject, MyOtherSpriteObject).getChild(\"Child\").getChild(\"Grandchild\")");
}
}
SECTION("Type conversions (valid operators with variables having different types than the expression)") {
SECTION("Expression/parent type is 'string'") {
{
auto node =
parser.ParseExpression("\"You have \" + MySceneVariable + \" points\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"You have \" + getLayoutVariable(MySceneVariable).getAsString() + \" points\"");
}
{
auto node =
parser.ParseExpression("MySceneVariable + MySceneStringVariable");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsString() + getLayoutVariable(MySceneStringVariable).getAsString()");
}
}
SECTION("Expression/parent type is 'string' (with an unknown variable)") {
{
auto node =
parser.ParseExpression("\"You have \" + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \" points\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"You have \" + getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsString() + \" points\"");
}
}
SECTION("Expression/parent type is 'string' (2 number variables)") {
{
auto node =
parser.ParseExpression("MySceneVariable + MySceneVariable2 + \"world\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsString() + getLayoutVariable(MySceneVariable2).getAsString() + \"world\"");
}
{
auto node =
parser.ParseExpression("MySceneVariable + MySceneVariable2 + MySceneStringVariable");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsString() + getLayoutVariable(MySceneVariable2).getAsString() + getLayoutVariable(MySceneStringVariable).getAsString()");
}
}
SECTION("Expression/parent type is 'string' (array variable)") {
{
auto node =
parser.ParseExpression("\"hello\" + MySceneNumberArrayVariable[2] + \"world\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneNumberArrayVariable).getChild(2).getAsString() + \"world\"");
}
{
auto node =
parser.ParseExpression("\"hello\" + MySceneEmptyArrayVariable[2] + \"world\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneEmptyArrayVariable).getChild(2).getAsString() + \"world\"");
}
}
SECTION("Expression/parent type is 'number'") {
{
auto node =
parser.ParseExpression("123 + MySceneVariable + 456");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneVariable).getAsNumber() + 456");
}
{
auto node =
parser.ParseExpression("MySceneStringVariable + MySceneVariable");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneVariable).getAsNumber()");
}
}
SECTION("Expression/parent type is 'string' (with an unknown variable)") {
{
auto node =
parser.ParseExpression("123 + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 456");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsNumber() + 456");
}
}
SECTION("Expression/parent type is 'number' (2 string variables)") {
{
auto node =
parser.ParseExpression("MySceneStringVariable + MySceneStringVariable + 456");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneStringVariable).getAsNumber() + 456");
}
{
auto node =
parser.ParseExpression("MySceneStringVariable + MySceneStringVariable + MySceneVariable");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneVariable).getAsNumber()");
}
}
SECTION("Expression/parent type is 'number' (array variable)") {
{
auto node =
parser.ParseExpression("123 + MySceneNumberArrayVariable[2] + 456");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneNumberArrayVariable).getChild(2).getAsNumber() + 456");
}
{
auto node =
parser.ParseExpression("123 + MySceneEmptyArrayVariable[2] + 456");
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneEmptyArrayVariable).getChild(2).getAsNumber() + 456");
}
}
SECTION("Multiple type conversions in sub expressions or same expression") {
{
auto node =
parser.ParseExpression("\"hello\" + MySceneNumberArrayVariable[2 + MySceneStringVariable] + \"world\" + MySceneVariable + \"world 2\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneNumberArrayVariable).getChild(2 + getLayoutVariable(MySceneStringVariable).getAsNumber()).getAsString() + \"world\" + getLayoutVariable(MySceneVariable).getAsString() + \"world 2\"");
}
{
auto node =
parser.ParseExpression("\"hello\" + MySceneNumberArrayVariable[\"foo\" + MySceneVariable + \"bar\"] + \"world\" + MySceneVariable + \"world 2\"");
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
"",
codeGenerator,
context);
REQUIRE(node);
node->Visit(expressionCodeGenerator);
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneNumberArrayVariable).getChild(\"foo\" + getLayoutVariable(MySceneVariable).getAsString() + \"bar\").getAsString() + \"world\" + getLayoutVariable(MySceneVariable).getAsString() + \"world 2\"");
}
}
}
SECTION("Mixed test (1)") {
{
auto node = parser.ParseExpression("-+-MyExtension::MouseX(,)");

View File

@@ -2954,6 +2954,238 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Valid operators with variables having different types than the expression") {
SECTION("Expression/parent type is 'string'") {
// A trivial test (everything is a string).
{
auto node = parser.ParseExpression("\"You have \" + MySceneStringVariable + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A string concatenated with a number variable (will have to be casted to a string in code generation)
{
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A string concatenated with a number variable (will have to be casted to a string in code generation)
// and then with a string again.
{
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A string concatenated with an unknown variable (will have to be casted to a string in code generation)
// and then with a string again.
{
auto node = parser.ParseExpression("\"You have \" + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
}
SECTION("Expression/parent type is 'number'") {
// A trivial test (everything is a string).
{
auto node = parser.ParseExpression("123 + MySceneNumberVariable + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A number concatenated with a string variable (will have to be casted to a number in code generation)
{
auto node = parser.ParseExpression("123 + MySceneStringVariable");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A number concatenated with a string variable (will have to be casted to a number in code generation)
// and then with a number again.
{
auto node = parser.ParseExpression("123 + MySceneStringVariable + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A number concatenated with an unknown variable (will have to be casted to a number in code generation)
// and then with a number again.
{
auto node = parser.ParseExpression("123 + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
}
SECTION("Expression/parent type is 'number|string'") {
SECTION("Expression/parent inferred type is 'string'") {
// A trivial test (everything is a string).
{
auto node = parser.ParseExpression("\"You have \" + MySceneStringVariable + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A string concatenated with a number variable (will have to be casted to a string in code generation)
{
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A string concatenated with a number variable (will have to be casted to a string in code generation)
// and then with a string again.
{
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A string concatenated with an unknown variable (will have to be casted to a string in code generation)
// and then with a string again.
{
auto node = parser.ParseExpression("\"You have \" + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
}
SECTION("Expression/parent inferred type is 'number'") {
// A trivial test (everything is a string).
{
auto node = parser.ParseExpression("123 + MySceneNumberVariable + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A number concatenated with a string variable (will have to be casted to a number in code generation)
{
auto node = parser.ParseExpression("123 + MySceneStringVariable");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A number concatenated with a string variable (will have to be casted to a number in code generation)
// and then with a number again.
{
auto node = parser.ParseExpression("123 + MySceneStringVariable + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
// A number concatenated with an unknown variable (will have to be casted to a number in code generation)
// and then with a number again.
{
auto node = parser.ParseExpression("123 + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 0);
}
}
}
}
SECTION("Invalid operators with variables having different types than the expression") {
// Try to do a sum between numbers in a string expression
{
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + 2 + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a number, but a text was expected (in quotes).");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 38);
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 39);
}
// Try to do a sum between numbers in a number|string expression (that is inferred as a string with the first operand)
{
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + 2 + \" points\"");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a number, but a text was expected (in quotes).");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 38);
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 39);
}
// Try to do a string concatenation in a number expression
{
auto node = parser.ParseExpression("123 + MySceneStringVariable + \"hello\" + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a text, but a number was expected.");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 30);
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 37);
}
// Try to do a string concatenation in a number|string expression (that is inferred as a number with the first operand)
{
auto node = parser.ParseExpression("123 + MySceneStringVariable + \"hello\" + 456");
REQUIRE(node != nullptr);
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a text, but a number was expected.");
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 30);
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 37);
}
}
SECTION("Valid function call with object variable") {
{
// Note that in this test we need to use an expression with "objectvar",

View File

@@ -71,19 +71,17 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
frame.SetImageName("assets/Idle.png");
direction.AddSprite(frame);
spriteConfiguration->AddAnimation(animation);
spriteConfiguration->GetAnimations().AddAnimation(animation);
}
SerializerElement assetElement;
std::map<gd::String, std::vector<gd::String>> resourcesFileNameMap;
std::vector<gd::String> usedResourceNames;
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
assetElement, resourcesFileNameMap);
assetElement, usedResourceNames);
// This mapping is used to copy resource files.
REQUIRE(resourcesFileNameMap.find("assets/Idle.png") !=
resourcesFileNameMap.end());
REQUIRE(resourcesFileNameMap["assets/Idle.png"].size() == 1);
REQUIRE(resourcesFileNameMap["assets/Idle.png"][0] == "Idle.png");
// This list is used to copy resource files.
REQUIRE(usedResourceNames.size() == 1);
REQUIRE(usedResourceNames[0] == "assets/Idle.png");
// Check that the project is left untouched.
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
@@ -113,8 +111,8 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
REQUIRE(resourcesElement.GetChildrenCount() == 1);
{
auto &resourceElement = resourcesElement.GetChild(0);
REQUIRE(resourceElement.GetStringAttribute("name") == "Idle.png");
REQUIRE(resourceElement.GetStringAttribute("file") == "Idle.png");
REQUIRE(resourceElement.GetStringAttribute("name") == "assets/Idle.png");
REQUIRE(resourceElement.GetStringAttribute("file") == "assets/Idle.png");
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
}
@@ -144,6 +142,6 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
spritesElement.ConsiderAsArrayOf("sprite");
REQUIRE(spritesElement.GetChildrenCount() == 1);
auto &spriteElement = spritesElement.GetChild(0);
REQUIRE(spriteElement.GetStringAttribute("image") == "Idle.png");
REQUIRE(spriteElement.GetStringAttribute("image") == "assets/Idle.png");
}
}

View File

@@ -35,7 +35,7 @@ void SetupSpriteConfiguration(gd::ObjectConfiguration &configuration) {
REQUIRE(spriteConfiguration != nullptr);
gd::Animation animation;
animation.SetName("Idle");
spriteConfiguration->AddAnimation(animation);
spriteConfiguration->GetAnimations().AddAnimation(animation);
};
gd::Object &SetupProjectWithSprite(gd::Project &project,
@@ -83,9 +83,9 @@ void CheckSpriteConfigurationInProjectElement(
void CheckSpriteConfiguration(gd::ObjectConfiguration &configuration) {
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(&configuration);
REQUIRE(spriteConfiguration);
REQUIRE(spriteConfiguration->GetAnimationsCount() == 1);
REQUIRE(spriteConfiguration->GetAnimations().GetAnimationsCount() == 1);
auto &animation = spriteConfiguration->GetAnimation(0);
auto &animation = spriteConfiguration->GetAnimations().GetAnimation(0);
REQUIRE(animation.GetName() == "Idle");
};

View File

@@ -1 +0,0 @@
Spine/pixi-spine

View File

@@ -154,7 +154,23 @@ namespace gdjs {
* @return The Z position of the rendered object.
*/
getDrawableZ(): float {
return this.getZ();
return this._z;
}
/**
* Return the bottom Z of the object.
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMinZ(): number {
return this.getDrawableZ();
}
/**
* Return the top Z of the object.
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMaxZ(): number {
return this.getDrawableZ() + this.getDepth();
}
/**

View File

@@ -24,8 +24,8 @@ namespace gdjs {
updatePosition() {
this._threeObject3D.position.set(
this._object.x + this._object.getWidth() / 2,
this._object.y + this._object.getHeight() / 2,
this._object.getX() + this._object.getWidth() / 2,
this._object.getY() + this._object.getHeight() / 2,
this._object.getZ() + this._object.getDepth() / 2
);
}

View File

@@ -103,6 +103,27 @@ namespace gdjs {
flipZ(enable: boolean): void;
isFlippedZ(): boolean;
/**
* Return the bottom Z of the object.
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMinZ(): number;
/**
* Return the top Z of the object.
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMaxZ(): number;
}
export namespace Base3DHandler {
export const is3D = (
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
//@ts-ignore We are checking if the methods are present.
return object.getZ && object.setZ;
};
}
/**
@@ -202,6 +223,14 @@ namespace gdjs {
isFlippedZ(): boolean {
return this.object.isFlippedZ();
}
getUnrotatedAABBMinZ(): number {
return this.object.getUnrotatedAABBMinZ();
}
getUnrotatedAABBMaxZ(): number {
return this.object.getUnrotatedAABBMaxZ();
}
}
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);

View File

@@ -0,0 +1,89 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Bloom',
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 {
shaderPass: THREE_ADDONS.UnrealBloomPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.UnrealBloomPass(
new THREE.Vector2(256, 256),
1,
0,
0
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'strength') {
this.shaderPass.strength = value;
}
if (parameterName === 'radius') {
this.shaderPass.radius = value;
}
if (parameterName === 'threshold') {
this.shaderPass.threshold = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'strength') {
return this.shaderPass.strength;
}
if (parameterName === 'radius') {
return this.shaderPass.radius;
}
if (parameterName === 'threshold') {
return this.shaderPass.threshold;
}
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 {}
})();
}
})()
);
}

View File

@@ -0,0 +1,80 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::BrightnessAndContrast',
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 {
shaderPass: THREE_ADDONS.ShaderPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.ShaderPass(
THREE_ADDONS.BrightnessContrastShader
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'brightness') {
this.shaderPass.uniforms[parameterName].value = value;
}
if (parameterName === 'contrast') {
this.shaderPass.uniforms[parameterName].value = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'brightness') {
return this.shaderPass.uniforms[parameterName].value;
}
if (parameterName === 'contrast') {
return this.shaderPass.uniforms[parameterName].value;
}
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 {}
})();
}
})()
);
}

View File

@@ -0,0 +1,373 @@
namespace gdjs {
export interface Object3DDataContent {
width: float;
height: float;
depth: float;
}
/** Base parameters for {@link gdjs.RuntimeObject3D} */
export interface Object3DData extends ObjectData {
/** The base parameters of the RuntimeObject3D */
content: Object3DDataContent;
}
/**
* Base class for 3D custom objects.
*/
export class CustomRuntimeObject3D
extends gdjs.CustomRuntimeObject
implements gdjs.Base3DHandler {
/**
* Position on the Z axis.
*/
private _z: float = 0;
private _minZ: float = 0;
private _maxZ: float = 0;
private _scaleZ: float = 1;
private _flippedZ: boolean = false;
/**
* Euler angle with the `ZYX` order.
*
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
*/
private _rotationX: float = 0;
/**
* Euler angle with the `ZYX` order.
*
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
*/
private _rotationY: float = 0;
private _customCenterZ: float = 0;
private static _temporaryVector = new THREE.Vector3();
constructor(
parent: gdjs.RuntimeInstanceContainer,
objectData: Object3DData & CustomObjectConfiguration
) {
super(parent, objectData);
this._renderer.reinitialize(this, parent);
}
protected _createRender() {
const parent = this._runtimeScene;
return new gdjs.CustomRuntimeObject3DRenderer(
this,
this._instanceContainer,
parent
);
}
protected _reinitializeRenderer(): void {
this.getRenderer().reinitialize(this, this.getParent());
}
getRenderer(): gdjs.CustomRuntimeObject3DRenderer {
return super.getRenderer() as gdjs.CustomRuntimeObject3DRenderer;
}
get3DRendererObject() {
// It can't be null because Three.js is always loaded
// when a custom 3D object is used.
return this.getRenderer().get3DRendererObject()!;
}
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
super.extraInitializationFromInitialInstance(initialInstanceData);
if (initialInstanceData.depth !== undefined)
this.setDepth(initialInstanceData.depth);
}
/**
* Set the object position on the Z axis.
*/
setZ(z: float): void {
if (z === this._z) return;
this._z = z;
this.getRenderer().updatePosition();
}
/**
* Get the object position on the Z axis.
*/
getZ(): float {
return this._z;
}
/**
* Get the Z position of the rendered object.
*
* For most objects, this will returns the same value as getZ(). But if the
* object has an origin that is not the same as the point (0,0,0) of the
* object displayed, getDrawableZ will differ.
*
* @return The Z position of the rendered object.
*/
getDrawableZ(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return this._z + this._minZ;
}
/**
* Return the Z position of the object center, **relative to the object Z
* position** (`getDrawableX`).
*
* Use `getCenterZInScene` to get the position of the center in the scene.
*
* @return the Z position of the object center, relative to
* `getDrawableZ()`.
*/
getCenterZ(): float {
return this.getDepth() / 2;
}
getCenterZInScene(): float {
return this.getDrawableZ() + this.getCenterZ();
}
setCenterZInScene(z: float): void {
this.setZ(z + this._z - (this.getDrawableZ() + this.getCenterZ()));
}
/**
* Return the bottom Z of the object.
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMinZ(): number {
return this.getDrawableZ();
}
/**
* Return the top Z of the object.
* Rotations around X and Y are not taken into account.
*/
getUnrotatedAABBMaxZ(): number {
return this.getDrawableZ() + this.getDepth();
}
/**
* Set the object rotation on the X axis.
*
* This is an Euler angle. Objects use the `ZYX` order.
*/
setRotationX(angle: float): void {
this._rotationX = angle;
this.getRenderer().updateRotation();
}
/**
* Set the object rotation on the Y axis.
*
* This is an Euler angle. Objects use the `ZYX` order.
*/
setRotationY(angle: float): void {
this._rotationY = angle;
this.getRenderer().updateRotation();
}
/**
* Get the object rotation on the X axis.
*
* This is an Euler angle. Objects use the `ZYX` order.
*/
getRotationX(): float {
return this._rotationX;
}
/**
* Get the object rotation on the Y axis.
*
* This is an Euler angle. Objects use the `ZYX` order.
*/
getRotationY(): float {
return this._rotationY;
}
/**
* Turn the object around the scene x axis at its center.
* @param deltaAngle the rotation angle
*/
turnAroundX(deltaAngle: float): void {
const axisX = gdjs.CustomRuntimeObject3D._temporaryVector;
axisX.set(1, 0, 0);
const mesh = this.get3DRendererObject();
mesh.rotateOnWorldAxis(axisX, gdjs.toRad(deltaAngle));
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
}
/**
* Turn the object around the scene y axis at its center.
* @param deltaAngle the rotation angle
*/
turnAroundY(deltaAngle: float): void {
const axisY = gdjs.CustomRuntimeObject3D._temporaryVector;
axisY.set(0, 1, 0);
const mesh = this.get3DRendererObject();
mesh.rotateOnWorldAxis(axisY, gdjs.toRad(deltaAngle));
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
}
/**
* Turn the object around the scene z axis at its center.
* @param deltaAngle the rotation angle
*/
turnAroundZ(deltaAngle: float): void {
const axisZ = gdjs.CustomRuntimeObject3D._temporaryVector;
axisZ.set(0, 0, 1);
const mesh = this.get3DRendererObject();
mesh.rotateOnWorldAxis(axisZ, gdjs.toRad(deltaAngle));
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
}
/**
* @return the internal width of the object according to its children.
*/
getUnscaledDepth(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return this._maxZ - this._minZ;
}
_updateUntransformedHitBoxes(): void {
super._updateUntransformedHitBoxes();
let minZ = Number.MAX_VALUE;
let maxZ = -Number.MAX_VALUE;
for (const childInstance of this._instanceContainer.getAdhocListOfAllInstances()) {
if (!childInstance.isIncludedInParentCollisionMask()) {
continue;
}
if (!gdjs.Base3DHandler.is3D(childInstance)) {
continue;
}
minZ = Math.min(minZ, childInstance.getUnrotatedAABBMinZ());
maxZ = Math.max(maxZ, childInstance.getUnrotatedAABBMaxZ());
}
if (minZ === Number.MAX_VALUE) {
// The unscaled size can't be 0 because setWidth and setHeight wouldn't
// have any effect.
minZ = 0;
maxZ = 1;
}
this._minZ = minZ;
this._maxZ = maxZ;
}
/**
* @returns the center Z from the local origin (0;0).
*/
getUnscaledCenterZ(): float {
if (this.hasCustomRotationCenter()) {
return this._customCenterZ;
}
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return (this._minZ + this._maxZ) / 2;
}
/**
* The center of rotation is defined relatively to the origin (the object
* position).
* This avoids the center to move when children push the bounds.
*
* When no custom center is defined, it will move
* to stay at the center of the children bounds.
*
* @param x coordinate of the custom center
* @param y coordinate of the custom center
*/
setRotationCenter3D(x: float, y: float, z: float) {
this._customCenterZ = z;
this.setRotationCenter(x, y);
}
/**
* Get the object size on the Z axis (called "depth").
*/
getDepth(): float {
return this.getUnscaledDepth() * this.getScaleZ();
}
/**
* Set the object size on the Z axis (called "depth").
*/
setDepth(depth: float): void {
const unscaledDepth = this.getUnscaledDepth();
if (unscaledDepth !== 0) {
this.setScaleZ(depth / unscaledDepth);
}
}
/**
* Change the scale on X, Y and Z axis of the object.
*
* @param newScale The new scale (must be greater than 0).
*/
setScale(newScale: number): void {
super.setScale(newScale);
this.setScaleZ(newScale);
}
/**
* Change the scale on Z axis of the object (changing its height).
*
* @param newScale The new scale (must be greater than 0).
*/
setScaleZ(newScale: number): void {
if (newScale < 0) {
newScale = 0;
}
if (newScale === Math.abs(this._scaleZ)) {
return;
}
this._scaleZ = newScale * (this._flippedZ ? -1 : 1);
this.getRenderer().updateSize();
}
/**
* Get the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
*
* @return the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
*/
getScale(): number {
const scaleX = this.getScaleX();
const scaleY = this.getScaleY();
const scaleZ = this.getScaleZ();
return scaleX === scaleY && scaleX === scaleZ
? scaleX
: Math.pow(scaleX * scaleY * scaleZ, 1 / 3);
}
/**
* Get the scale of the object on Z axis.
*
* @return the scale of the object on Z axis
*/
getScaleZ(): float {
return Math.abs(this._scaleZ);
}
flipZ(enable: boolean) {
if (enable === this._flippedZ) {
return;
}
this._flippedZ = enable;
this.getRenderer().updateSize();
}
isFlippedZ(): boolean {
return this._flippedZ;
}
}
}

View File

@@ -0,0 +1,179 @@
namespace gdjs {
export interface PixiImageManager {
_threeAnimationFrameTextureManager: ThreeAnimationFrameTextureManager;
}
/**
* The renderer for a {@link gdjs.CustomRuntimeObject3D} using Three.js.
*/
export class CustomRuntimeObject3DRenderer
implements gdjs.RuntimeInstanceContainerRenderer {
_object: gdjs.CustomRuntimeObject3D;
_instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer;
_isContainerDirty: boolean = true;
_threeGroup: THREE.Group;
constructor(
object: gdjs.CustomRuntimeObject3D,
instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer,
parent: gdjs.RuntimeInstanceContainer
) {
this._object = object;
this._instanceContainer = instanceContainer;
this._threeGroup = new THREE.Group();
this._threeGroup.rotation.order = 'ZYX';
const layer = parent.getLayer('');
if (layer) {
layer.getRenderer().add3DRendererObject(this._threeGroup);
}
}
get3DRendererObject(): THREE.Object3D {
return this._threeGroup;
}
getRendererObject() {
return null;
}
reinitialize(
object: gdjs.CustomRuntimeObject3D,
parent: gdjs.RuntimeInstanceContainer
) {
this._object = object;
this._isContainerDirty = true;
const layer = parent.getLayer('');
if (layer) {
layer.getRenderer().add3DRendererObject(this._threeGroup);
}
}
_updateThreeGroup() {
const threeObject3D = this.get3DRendererObject();
const scaleX = this._object.getScaleX();
const scaleY = this._object.getScaleY();
const scaleZ = this._object.getScaleZ();
const pivotX = this._object.getUnscaledCenterX() * scaleX;
const pivotY = this._object.getUnscaledCenterY() * scaleY;
const pivotZ = this._object.getUnscaledCenterZ() * scaleZ;
threeObject3D.rotation.set(
gdjs.toRad(this._object.getRotationX()),
gdjs.toRad(this._object.getRotationY()),
gdjs.toRad(this._object.angle)
);
threeObject3D.position.set(
this._object.isFlippedX() ? pivotX : -pivotX,
this._object.isFlippedY() ? pivotY : -pivotY,
this._object.isFlippedZ() ? pivotZ : -pivotZ
);
threeObject3D.position.applyEuler(threeObject3D.rotation);
threeObject3D.position.x += this._object.getX() + pivotX;
threeObject3D.position.y += this._object.getY() + pivotY;
threeObject3D.position.z += this._object.getZ() + pivotZ;
threeObject3D.scale.set(
this._object.isFlippedX() ? -scaleX : scaleX,
this._object.isFlippedY() ? -scaleY : scaleY,
this._object.isFlippedZ() ? -scaleZ : scaleZ
);
threeObject3D.visible = !this._object.hidden;
this._isContainerDirty = false;
}
/**
* Call this to make sure the object is ready to be rendered.
*/
ensureUpToDate() {
if (this._isContainerDirty) {
this._updateThreeGroup();
}
}
update(): void {
this._isContainerDirty = true;
}
updateX(): void {
this._isContainerDirty = true;
}
updateY(): void {
this._isContainerDirty = true;
}
updateAngle(): void {
this._isContainerDirty = true;
}
updatePosition() {
this._isContainerDirty = true;
}
updateRotation() {
this._isContainerDirty = true;
}
updateSize() {
this._isContainerDirty = true;
}
updateVisibility(): void {
this._threeGroup.visible = !this._object.hidden;
}
updateOpacity(): void {
// Opacity is not handled by 3D custom objects.
}
setLayerIndex(layer: gdjs.RuntimeLayer, index: float): void {
// Layers are not handled for 3D custom objects.
}
static getAnimationFrameTextureManager(
imageManager: gdjs.PixiImageManager
): ThreeAnimationFrameTextureManager {
if (!imageManager._threeAnimationFrameTextureManager) {
imageManager._threeAnimationFrameTextureManager = new ThreeAnimationFrameTextureManager(
imageManager
);
}
return imageManager._threeAnimationFrameTextureManager;
}
}
class ThreeAnimationFrameTextureManager
implements gdjs.AnimationFrameTextureManager<THREE.Material> {
private _imageManager: gdjs.PixiImageManager;
constructor(imageManager: gdjs.PixiImageManager) {
this._imageManager = imageManager;
}
getAnimationFrameTexture(imageName: string) {
return this._imageManager.getThreeMaterial(imageName, {
useTransparentTexture: true,
forceBasicMaterial: true,
});
}
getAnimationFrameWidth(material: THREE.Material) {
const map = (material as
| THREE.MeshBasicMaterial
| THREE.MeshStandardMaterial).map;
return map ? map.image.width : 0;
}
getAnimationFrameHeight(material: THREE.Material) {
const map = (material as
| THREE.MeshBasicMaterial
| THREE.MeshStandardMaterial).map;
return map ? map.image.height : 0;
}
}
}

View File

@@ -0,0 +1,74 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Exposure',
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 {
shaderPass: THREE_ADDONS.ShaderPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.ShaderPass(
THREE_ADDONS.ExposureShader
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'exposure') {
this.shaderPass.uniforms[parameterName].value = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'exposure') {
return this.shaderPass.uniforms[parameterName].value;
}
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 {}
})();
}
})()
);
}

View File

@@ -0,0 +1,80 @@
namespace gdjs {
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::HueAndSaturation',
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 {
shaderPass: THREE_ADDONS.ShaderPass;
_isEnabled: boolean;
constructor() {
this.shaderPass = new THREE_ADDONS.ShaderPass(
THREE_ADDONS.HueSaturationShader
);
this._isEnabled = false;
}
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 {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().addPostProcessingPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().removePostProcessingPass(this.shaderPass);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'hue') {
this.shaderPass.uniforms[parameterName].value = value / 180;
}
if (parameterName === 'saturation') {
this.shaderPass.uniforms[parameterName].value = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'hue') {
return this.shaderPass.uniforms[parameterName].value * 180;
}
if (parameterName === 'saturation') {
return this.shaderPass.uniforms[parameterName].value;
}
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 {}
})();
}
})()
);
}

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -811,7 +803,6 @@ module.exports = {
}
const Cube3DObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
Cube3DObject.updateProperty = function (
objectContent,
propertyName,
@@ -860,7 +851,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
Cube3DObject.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -1100,7 +1090,6 @@ module.exports = {
})
);
// $FlowExpectedError
Cube3DObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -1112,7 +1101,6 @@ module.exports = {
return false;
};
// $FlowExpectedError
Cube3DObject.getInitialInstanceProperties = function (
content,
instance,
@@ -1665,7 +1653,7 @@ module.exports = {
'Change the camera rotation to look at an object. The camera top always face the screen.'
),
_('Change the camera rotation of _PARAM2_ to look at _PARAM1_'),
_("Layers and cameras"),
_('Layers and cameras'),
'res/conditions/3d_box.svg',
'res/conditions/3d_box.svg'
)
@@ -1939,6 +1927,86 @@ module.exports = {
.setType('number')
.setGroup(_('Orientation'));
}
{
const effect = extension
.addEffect('HueAndSaturation')
.setFullName(_('Hue and saturation'))
.setDescription(_('Adjust hue and saturation.'))
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/HueAndSaturationEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('hue')
.setValue('0')
.setLabel(_('Hue in degrees (between -180 and 180)'))
.setType('number');
properties
.getOrCreate('saturation')
.setValue('0')
.setLabel(_('Saturation (between -1 and 1)'))
.setType('number');
}
{
const effect = extension
.addEffect('Exposure')
.setFullName(_('Exposure'))
.setDescription(_('Adjust exposure.'))
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/ExposureEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('exposure')
.setValue('1')
.setLabel(_('Exposure (positive value)'))
.setType('number');
}
{
const effect = extension
.addEffect('Bloom')
.setFullName(_('Bloom'))
.setDescription(_('Apply a bloom effect.'))
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/BloomEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('strength')
.setValue('1')
.setLabel(_('Strength (between 0 and 3)'))
.setType('number');
properties
.getOrCreate('radius')
.setValue('0')
.setLabel(_('Radius (between 0 and 1)'))
.setType('number');
properties
.getOrCreate('threshold')
.setValue('0')
.setLabel(_('Threshold (between 0 and 1)'))
.setType('number');
}
{
const effect = extension
.addEffect('BrightnessAndContrast')
.setFullName(_('Brightness and contrast.'))
.setDescription(_('Adjust brightness and contrast.'))
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/BrightnessAndContrastEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('brightness')
.setValue('0')
.setLabel(_('Brightness (between -1 and 1)'))
.setType('number');
properties
.getOrCreate('contrast')
.setValue('0')
.setLabel(_('Contrast (between -1 and 1)'))
.setType('number');
}
// Don't forget to update the alert condition in Model3DEditor.js when
// adding a new light.
@@ -1954,10 +2022,7 @@ module.exports = {
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
/**
@@ -1965,17 +2030,13 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {},
registerEditorConfigurations: function (objectsEditorService) {},
/**
* Register renderers for instance of objects on the scene editor.
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const Rendered3DInstance = objectsRenderingService.Rendered3DInstance;
const PIXI = objectsRenderingService.PIXI;
@@ -1983,39 +2044,25 @@ module.exports = {
const THREE_ADDONS = objectsRenderingService.THREE_ADDONS;
const materialIndexToFaceIndex = {
// $FlowFixMe
0: 3,
// $FlowFixMe
1: 2,
// $FlowFixMe
2: 5,
// $FlowFixMe
3: 4,
// $FlowFixMe
4: 0,
// $FlowFixMe
5: 1,
};
const noRepeatTextureVertexIndexToUvMapping = {
// $FlowFixMe
0: [0, 0],
// $FlowFixMe
1: [1, 0],
// $FlowFixMe
2: [0, 1],
// $FlowFixMe
3: [1, 1],
};
const noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ = {
// $FlowFixMe
0: [0, 1],
// $FlowFixMe
1: [0, 0],
// $FlowFixMe
2: [1, 1],
// $FlowFixMe
3: [1, 0],
};
@@ -2061,6 +2108,11 @@ module.exports = {
};
class RenderedCube3DObject2DInstance extends RenderedInstance {
/** @type {number} */
_centerX = 0;
/** @type {number} */
_centerY = 0;
constructor(
project,
layout,
@@ -2077,10 +2129,9 @@ module.exports = {
pixiContainer,
pixiResourcesLoader
);
/**
* Name of the resource that is rendered.
* If no face is visible, this will be null.
*/
// Name of the resource that is rendered.
// If no face is visible, this will be null.
this._renderedResourceName = undefined;
const properties = associatedObjectConfiguration.getProperties();
this._defaultWidth = parseFloat(properties.get('width').getValue());
@@ -2110,12 +2161,9 @@ module.exports = {
}
static getThumbnail(project, resourcesLoader, objectConfiguration) {
const instance = this._instance;
const textureResourceName =
RenderedCube3DObject2DInstance._getResourceNameToDisplay(
objectConfiguration
);
const textureResourceName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
objectConfiguration
);
if (textureResourceName) {
return resourcesLoader.getResourceFullUrl(
project,
@@ -2127,20 +2175,18 @@ module.exports = {
}
updateTextureIfNeeded() {
const textureName =
RenderedCube3DObject2DInstance._getResourceNameToDisplay(
this._associatedObjectConfiguration
);
const textureName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
this._associatedObjectConfiguration
);
if (textureName === this._renderedResourceName) return;
this.updateTexture();
}
updateTexture() {
const textureName =
RenderedCube3DObject2DInstance._getResourceNameToDisplay(
this._associatedObjectConfiguration
);
const textureName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
this._associatedObjectConfiguration
);
if (!textureName) {
this._renderFallbackObject = true;
@@ -2398,10 +2444,9 @@ module.exports = {
continue;
}
const shouldRepeatTexture =
this._shouldRepeatTextureOnFace[
materialIndexToFaceIndex[materialIndex]
];
const shouldRepeatTexture = this._shouldRepeatTextureOnFace[
materialIndexToFaceIndex[materialIndex]
];
const shouldOrientateFacesTowardsY = this._facesOrientation === 'Y';
@@ -2436,13 +2481,16 @@ module.exports = {
}
} else {
if (shouldOrientateFacesTowardsY) {
[x, y] =
noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
[x, y] = noRepeatTextureVertexIndexToUvMapping[
vertexIndex % 4
];
} else {
[x, y] =
noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
vertexIndex % 4
];
[
x,
y,
] = noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
vertexIndex % 4
];
}
}
break;
@@ -2472,13 +2520,16 @@ module.exports = {
}
} else {
if (shouldOrientateFacesTowardsY) {
[x, y] =
noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
[x, y] = noRepeatTextureVertexIndexToUvMapping[
vertexIndex % 4
];
} else {
[x, y] =
noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
vertexIndex % 4
];
[
x,
y,
] = noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
vertexIndex % 4
];
x = -x;
y = -y;
}
@@ -2624,6 +2675,8 @@ module.exports = {
RenderedCube3DObject3DInstance
);
const epsilon = 1 / (1 << 16);
class Model3DRendered2DInstance extends RenderedInstance {
_modelOriginPoint = [0, 0, 0];
@@ -2746,13 +2799,24 @@ module.exports = {
);
threeObject.updateMatrixWorld(true);
const boundingBox = new THREE.Box3().setFromObject(threeObject);
const shouldKeepModelOrigin = !this._originPoint;
if (shouldKeepModelOrigin) {
// Keep the origin as part of the model.
// For instance, a model can be 1 face of a cube and we want to keep the
// inside as part of the object even if it's just void.
// It also avoids to have the origin outside of the object box.
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
}
const modelWidth = boundingBox.max.x - boundingBox.min.x;
const modelHeight = boundingBox.max.y - boundingBox.min.y;
const modelDepth = boundingBox.max.z - boundingBox.min.z;
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
this._modelOriginPoint[0] =
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
this._modelOriginPoint[1] =
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
this._modelOriginPoint[2] =
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
// The model is flipped on Y axis.
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
@@ -2761,19 +2825,10 @@ module.exports = {
const centerPoint = this._centerPoint;
if (centerPoint) {
threeObject.position.set(
-(
boundingBox.min.x +
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
),
-(boundingBox.min.x + modelWidth * centerPoint[0]),
// The model is flipped on Y axis.
-(
boundingBox.min.y +
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
),
-(
boundingBox.min.z +
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
)
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
-(boundingBox.min.z + modelDepth * centerPoint[2])
);
}
@@ -2786,9 +2841,9 @@ module.exports = {
);
// Stretch the model in a 1x1x1 cube.
const scaleX = 1 / modelWidth;
const scaleY = 1 / modelHeight;
const scaleZ = 1 / modelDepth;
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
const scaleMatrix = new THREE.Matrix4();
// Flip on Y because the Y axis is on the opposite side of direct basis.
@@ -2799,10 +2854,22 @@ module.exports = {
if (keepAspectRatio) {
// Reduce the object dimensions to keep aspect ratio.
const widthRatio = originalWidth / modelWidth;
const heightRatio = originalHeight / modelHeight;
const depthRatio = originalDepth / modelDepth;
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
const widthRatio =
modelWidth < epsilon
? Number.POSITIVE_INFINITY
: originalWidth / modelWidth;
const heightRatio =
modelHeight < epsilon
? Number.POSITIVE_INFINITY
: originalHeight / modelHeight;
const depthRatio =
modelDepth < epsilon
? Number.POSITIVE_INFINITY
: originalDepth / modelDepth;
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
if (!Number.isFinite(scaleRatio)) {
scaleRatio = 1;
}
this._defaultWidth = scaleRatio * modelWidth;
this._defaultHeight = scaleRatio * modelHeight;
@@ -2954,6 +3021,11 @@ module.exports = {
return this.getHeight() * originPoint[1];
}
getOriginZ() {
const originPoint = this.getOriginPoint();
return this.getDepth() * originPoint[2];
}
getCenterX() {
const centerPoint = this.getCenterPoint();
return this.getWidth() * centerPoint[0];
@@ -2964,6 +3036,11 @@ module.exports = {
return this.getHeight() * centerPoint[1];
}
getCenterZ() {
const centerPoint = this.getCenterPoint();
return this.getDepth() * centerPoint[2];
}
getOriginPoint() {
return this._originPoint || this._modelOriginPoint;
}
@@ -2990,12 +3067,24 @@ module.exports = {
threeObject.updateMatrixWorld(true);
const boundingBox = new THREE.Box3().setFromObject(threeObject);
const shouldKeepModelOrigin = !this._originPoint;
if (shouldKeepModelOrigin) {
// Keep the origin as part of the model.
// For instance, a model can be 1 face of a cube and we want to keep the
// inside as part of the object even if it's just void.
// It also avoids to have the origin outside of the object box.
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
}
const modelWidth = boundingBox.max.x - boundingBox.min.x;
const modelHeight = boundingBox.max.y - boundingBox.min.y;
const modelDepth = boundingBox.max.z - boundingBox.min.z;
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
this._modelOriginPoint[0] =
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
this._modelOriginPoint[1] =
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
this._modelOriginPoint[2] =
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
// The model is flipped on Y axis.
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
@@ -3004,19 +3093,10 @@ module.exports = {
const centerPoint = this._centerPoint;
if (centerPoint) {
threeObject.position.set(
-(
boundingBox.min.x +
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
),
-(boundingBox.min.x + modelWidth * centerPoint[0]),
// The model is flipped on Y axis.
-(
boundingBox.min.y +
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
),
-(
boundingBox.min.z +
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
)
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
-(boundingBox.min.z + modelDepth * centerPoint[2])
);
}
@@ -3029,9 +3109,9 @@ module.exports = {
);
// Stretch the model in a 1x1x1 cube.
const scaleX = 1 / modelWidth;
const scaleY = 1 / modelHeight;
const scaleZ = 1 / modelDepth;
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
const scaleMatrix = new THREE.Matrix4();
// Flip on Y because the Y axis is on the opposite side of direct basis.
@@ -3042,14 +3122,63 @@ module.exports = {
if (keepAspectRatio) {
// Reduce the object dimensions to keep aspect ratio.
const widthRatio = originalWidth / modelWidth;
const heightRatio = originalHeight / modelHeight;
const depthRatio = originalDepth / modelDepth;
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
const widthRatio =
modelWidth < epsilon
? Number.POSITIVE_INFINITY
: originalWidth / modelWidth;
const heightRatio =
modelHeight < epsilon
? Number.POSITIVE_INFINITY
: originalHeight / modelHeight;
const depthRatio =
modelDepth < epsilon
? Number.POSITIVE_INFINITY
: originalDepth / modelDepth;
const minScaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
if (!Number.isFinite(minScaleRatio)) {
this._defaultWidth = modelWidth;
this._defaultHeight = modelHeight;
this._defaultDepth = modelDepth;
} else {
if (widthRatio === minScaleRatio) {
this._defaultWidth = originalWidth;
this._defaultHeight = Rendered3DInstance.applyRatio({
oldReferenceValue: modelWidth,
newReferenceValue: originalWidth,
valueToApplyTo: modelHeight,
});
this._defaultDepth = Rendered3DInstance.applyRatio({
oldReferenceValue: modelWidth,
newReferenceValue: originalWidth,
valueToApplyTo: modelDepth,
});
} else if (heightRatio === minScaleRatio) {
this._defaultWidth = Rendered3DInstance.applyRatio({
oldReferenceValue: modelHeight,
newReferenceValue: originalHeight,
valueToApplyTo: modelWidth,
});
this._defaultWidth = scaleRatio * modelWidth;
this._defaultHeight = scaleRatio * modelHeight;
this._defaultDepth = scaleRatio * modelDepth;
this._defaultHeight = originalHeight;
this._defaultDepth = Rendered3DInstance.applyRatio({
oldReferenceValue: modelHeight,
newReferenceValue: originalHeight,
valueToApplyTo: modelDepth,
});
} else {
this._defaultWidth = Rendered3DInstance.applyRatio({
oldReferenceValue: modelDepth,
newReferenceValue: originalDepth,
valueToApplyTo: modelWidth,
});
this._defaultHeight = Rendered3DInstance.applyRatio({
oldReferenceValue: modelDepth,
newReferenceValue: originalDepth,
valueToApplyTo: modelHeight,
});
this._defaultDepth = originalDepth;
}
}
}
}

View File

@@ -1,6 +1,8 @@
namespace gdjs {
type FloatPoint3D = [float, float, float];
const epsilon = 1 / (1 << 16);
const removeMetalness = (material: THREE.Material): void => {
//@ts-ignore
if (material.metalness) {
@@ -9,7 +11,7 @@ namespace gdjs {
}
};
const removeMetalnessFromMesh = (node: THREE.Object3D<THREE.Event>) => {
const removeMetalnessFromMesh = (node: THREE.Object3D) => {
const mesh = node as THREE.Mesh;
if (!mesh.material) {
return;
@@ -23,9 +25,8 @@ namespace gdjs {
}
};
const traverseToRemoveMetalnessFromMeshes = (
node: THREE.Object3D<THREE.Event>
) => node.traverse(removeMetalnessFromMesh);
const traverseToRemoveMetalnessFromMeshes = (node: THREE.Object3D) =>
node.traverse(removeMetalnessFromMesh);
const convertToBasicMaterial = (
material: THREE.Material
@@ -44,7 +45,7 @@ namespace gdjs {
return basicMaterial;
};
const setBasicMaterialTo = (node: THREE.Object3D<THREE.Event>): void => {
const setBasicMaterialTo = (node: THREE.Object3D): void => {
const mesh = node as THREE.Mesh;
if (!mesh.material) {
return;
@@ -59,9 +60,8 @@ namespace gdjs {
}
};
const traverseToSetBasicMaterialFromMeshes = (
node: THREE.Object3D<THREE.Event>
) => node.traverse(setBasicMaterialTo);
const traverseToSetBasicMaterialFromMeshes = (node: THREE.Object3D) =>
node.traverse(setBasicMaterialTo);
class Model3DRuntimeObject3DRenderer extends gdjs.RuntimeObject3DRenderer {
private _model3DRuntimeObject: gdjs.Model3DRuntimeObject;
@@ -158,12 +158,24 @@ namespace gdjs {
threeObject.updateMatrixWorld(true);
const boundingBox = new THREE.Box3().setFromObject(threeObject);
const shouldKeepModelOrigin = !this._model3DRuntimeObject._originPoint;
if (shouldKeepModelOrigin) {
// Keep the origin as part of the model.
// For instance, a model can be 1 face of a cube and we want to keep the
// inside as part of the object even if it's just void.
// It also avoids to have the origin outside of the object box.
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
}
const modelWidth = boundingBox.max.x - boundingBox.min.x;
const modelHeight = boundingBox.max.y - boundingBox.min.y;
const modelDepth = boundingBox.max.z - boundingBox.min.z;
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
this._modelOriginPoint[0] =
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
this._modelOriginPoint[1] =
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
this._modelOriginPoint[2] =
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
// The model is flipped on Y axis.
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
@@ -172,19 +184,10 @@ namespace gdjs {
const centerPoint = this._model3DRuntimeObject._centerPoint;
if (centerPoint) {
threeObject.position.set(
-(
boundingBox.min.x +
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
),
-(boundingBox.min.x + modelWidth * centerPoint[0]),
// The model is flipped on Y axis.
-(
boundingBox.min.y +
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
),
-(
boundingBox.min.z +
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
)
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
-(boundingBox.min.z + modelDepth * centerPoint[2])
);
}
@@ -197,9 +200,9 @@ namespace gdjs {
);
// Stretch the model in a 1x1x1 cube.
const scaleX = 1 / modelWidth;
const scaleY = 1 / modelHeight;
const scaleZ = 1 / modelDepth;
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
const scaleMatrix = new THREE.Matrix4();
// Flip on Y because the Y axis is on the opposite side of direct basis.
@@ -210,10 +213,22 @@ namespace gdjs {
if (keepAspectRatio) {
// Reduce the object dimensions to keep aspect ratio.
const widthRatio = originalWidth / modelWidth;
const heightRatio = originalHeight / modelHeight;
const depthRatio = originalDepth / modelDepth;
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
const widthRatio =
modelWidth < epsilon
? Number.POSITIVE_INFINITY
: originalWidth / modelWidth;
const heightRatio =
modelHeight < epsilon
? Number.POSITIVE_INFINITY
: originalHeight / modelHeight;
const depthRatio =
modelDepth < epsilon
? Number.POSITIVE_INFINITY
: originalDepth / modelDepth;
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
if (!Number.isFinite(scaleRatio)) {
scaleRatio = 1;
}
this._object._setOriginalWidth(scaleRatio * modelWidth);
this._object._setOriginalHeight(scaleRatio * modelHeight);

View File

@@ -11,12 +11,11 @@ namespace gdjs {
const layer = runtimeScene.getLayer(layerName);
const layerRenderer = layer.getRenderer();
const threeCamera = layerRenderer.getThreeCamera();
const fov =
threeCamera instanceof THREE.OrthographicCamera
const fov = threeCamera
? threeCamera instanceof THREE.OrthographicCamera
? null
: threeCamera
? threeCamera.fov
: assumedFovIn2D;
: threeCamera.fov
: assumedFovIn2D;
return layer.getCameraZ(fov, cameraIndex);
};
@@ -29,12 +28,11 @@ namespace gdjs {
const layer = runtimeScene.getLayer(layerName);
const layerRenderer = layer.getRenderer();
const threeCamera = layerRenderer.getThreeCamera();
const fov =
threeCamera instanceof THREE.OrthographicCamera
const fov = threeCamera
? threeCamera instanceof THREE.OrthographicCamera
? null
: threeCamera
? threeCamera.fov
: assumedFovIn2D;
: threeCamera.fov
: assumedFovIn2D;
layer.setCameraZ(z, fov, cameraIndex);
};
@@ -223,10 +221,11 @@ namespace gdjs {
const layerRenderer = layer.getRenderer();
const threeCamera = layerRenderer.getThreeCamera();
if (!threeCamera) return assumedFovIn2D;
return threeCamera instanceof THREE.OrthographicCamera
? 0
: threeCamera.fov;
return threeCamera
? threeCamera instanceof THREE.OrthographicCamera
? 0
: threeCamera.fov
: assumedFovIn2D;
};
export const setFov = (

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -41,6 +33,14 @@ module.exports = {
.addInstructionOrExpressionGroupMetadata(_('AdMob'))
.setIcon('JsPlatform/Extensions/admobicon24.png');
extension
.addDependency()
.setName('Consent Cordova plugin')
.setDependencyType('cordova')
.setExportName('cordova-plugin-consent')
.setVersion('2.4.0')
.onlyIfOtherDependencyIsExported('AdMob Cordova plugin');
extension
.registerProperty('AdMobAppIdAndroid')
.setLabel(_('AdMob Android App ID'))
@@ -71,13 +71,6 @@ module.exports = {
)
.onlyIfSomeExtraSettingsNonEmpty();
extension
.addDependency()
.setName('Consent Cordova plugin')
.setDependencyType('cordova')
.setExportName('cordova-plugin-consent')
.onlyIfOtherDependencyIsExported('AdMob Cordova plugin');
extension
.addAction(
'SetTestMode',
@@ -789,10 +782,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,11 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -710,10 +709,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,20 +13,11 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
const stringifyOptions = (options) => '["' + options.join('","') + '"]';
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -42,7 +34,6 @@ module.exports = {
.setIcon('JsPlatform/Extensions/bbcode32.png');
var objectBBText = new gd.ObjectJsImplementation();
// $FlowExpectedError
objectBBText.updateProperty = function (
objectContent,
propertyName,
@@ -59,7 +50,6 @@ module.exports = {
return false;
};
// $FlowExpectedError
objectBBText.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -126,7 +116,8 @@ module.exports = {
};
objectBBText.setRawJSONContent(
JSON.stringify({
text: '[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
text:
'[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
opacity: 255,
fontSize: 20,
visible: true,
@@ -137,7 +128,6 @@ module.exports = {
})
);
// $FlowExpectedError
objectBBText.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -148,7 +138,6 @@ module.exports = {
) {
return false;
};
// $FlowExpectedError
objectBBText.getInitialInstanceProperties = function (
content,
instance,
@@ -223,10 +212,9 @@ module.exports = {
parameterType === 'string' ||
parameterType === 'stringWithSelector'
) {
const parameterOptions =
gd.ParameterOptions.makeNewOptions().setDescription(
property.paramLabel
);
const parameterOptions = gd.ParameterOptions.makeNewOptions().setDescription(
property.paramLabel
);
if (property.options) {
parameterOptions.setTypeExtraInfo(
stringifyOptions(property.options)
@@ -276,10 +264,9 @@ module.exports = {
parameterType === 'number' ||
parameterType === 'stringWithSelector'
) {
const parameterOptions =
gd.ParameterOptions.makeNewOptions().setDescription(
property.paramLabel
);
const parameterOptions = gd.ParameterOptions.makeNewOptions().setDescription(
property.paramLabel
);
if (property.options) {
parameterOptions.setTypeExtraInfo(
stringifyOptions(property.options)
@@ -436,6 +423,21 @@ module.exports = {
addSettersAndGettersToObject(object, setterAndGetterProperties, 'BBText');
object
.addAction(
`SetFontFamily2`,
_('Font family'),
_('Set font family'),
_('Set the font of _PARAM0_ to _PARAM1_'),
'',
'res/actions/font24.png',
'res/actions/font24.png'
)
.addParameter('object', 'BBText', 'BBText', false)
.addParameter('fontResource', _('Font family'), '', false)
.getCodeExtraInformation()
.setFunctionName(`setFontFamily`);
const actions = object.getAllActions();
const conditions = object.getAllConditions();
const expressions = object.getAllExpressions();
@@ -443,6 +445,9 @@ module.exports = {
actions.get('BBText::SetOpacity').setHidden();
conditions.get('BBText::IsOpacity').setHidden();
expressions.get('GetOpacity').setHidden();
// Action deprecated because it's using the `string` type instead of the more
// user-friendly `fontResource` type.
actions.get('BBText::SetFontFamily').setHidden();
return extension;
},
@@ -457,10 +462,7 @@ module.exports = {
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
/**
@@ -468,9 +470,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {
registerEditorConfigurations: function (objectsEditorService) {
objectsEditorService.registerEditorConfiguration(
'BBText::BBText',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
@@ -483,11 +483,8 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;
const MultiStyleText = objectsRenderingService.requireModule(
__dirname,
'pixi-multistyle-text/dist/pixi-multistyle-text.umd'
@@ -496,150 +493,145 @@ module.exports = {
/**
* Renderer for instances of BBText inside the IDE.
*
* @extends RenderedBBTextInstance
* @extends RenderedInstance
* @class RenderedBBTextInstance
* @constructor
*/
function RenderedBBTextInstance(
project,
layout,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
) {
RenderedInstance.call(
this,
class RenderedBBTextInstance extends RenderedInstance {
constructor(
project,
layout,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
) {
super(
project,
layout,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
const bbTextStyles = {
default: {
// Use a default font family the time for the resource font to be loaded.
fontFamily: 'Arial',
fontSize: '24px',
fill: '#cccccc',
tagStyle: 'bbcode',
wordWrap: true,
wordWrapWidth: 250, // This value is the default wrapping width of the runtime object.
align: 'left',
},
};
const bbTextStyles = {
default: {
// Use a default font family the time for the resource font to be loaded.
fontFamily: 'Arial',
fontSize: '24px',
fill: '#cccccc',
tagStyle: 'bbcode',
wordWrap: true,
wordWrapWidth: 250, // This value is the default wrapping width of the runtime object.
align: 'left',
},
};
this._pixiObject = new MultiStyleText('', bbTextStyles);
this._pixiObject = new MultiStyleText('', bbTextStyles);
this._pixiObject.anchor.x = 0.5;
this._pixiObject.anchor.y = 0.5;
this._pixiContainer.addChild(this._pixiObject);
this.update();
}
RenderedBBTextInstance.prototype = Object.create(
RenderedInstance.prototype
);
/**
* Return the path to the thumbnail of the specified object.
*/
RenderedBBTextInstance.getThumbnail = function (
project,
resourcesLoader,
objectConfiguration
) {
return 'JsPlatform/Extensions/bbcode24.png';
};
/**
* This is called to update the PIXI object on the scene editor
*/
RenderedBBTextInstance.prototype.update = function () {
const properties = this._associatedObjectConfiguration.getProperties();
const rawText = properties.get('text').getValue();
if (rawText !== this._pixiObject.text) {
this._pixiObject.text = rawText;
this._pixiObject.anchor.x = 0.5;
this._pixiObject.anchor.y = 0.5;
this._pixiContainer.addChild(this._pixiObject);
this.update();
}
const opacity = properties.get('opacity').getValue();
this._pixiObject.alpha = opacity / 255;
const color = properties.get('color').getValue();
this._pixiObject.textStyles.default.fill =
objectsRenderingService.rgbOrHexToHexNumber(color);
const fontSize = properties.get('fontSize').getValue();
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
const fontResourceName = properties.get('fontFamily').getValue();
if (this._fontResourceName !== fontResourceName) {
this._fontResourceName = fontResourceName;
this._pixiResourcesLoader
.loadFontFamily(this._project, fontResourceName)
.then((fontFamily) => {
// Once the font is loaded, we can use the given fontFamily.
this._pixiObject.textStyles.default.fontFamily = fontFamily;
this._pixiObject.dirty = true;
})
.catch((err) => {
// Ignore errors
console.warn(
'Unable to load font family for RenderedBBTextInstance',
err
);
});
/**
* Return the path to the thumbnail of the specified object.
*/
static getThumbnail(project, resourcesLoader, objectConfiguration) {
return 'JsPlatform/Extensions/bbcode24.png';
}
const wordWrap = properties.get('wordWrap').getValue() === 'true';
if (wordWrap !== this._pixiObject._style.wordWrap) {
this._pixiObject._style.wordWrap = wordWrap;
this._pixiObject.dirty = true;
}
/**
* This is called to update the PIXI object on the scene editor
*/
update() {
const properties = this._associatedObjectConfiguration.getProperties();
const align = properties.get('align').getValue();
if (align !== this._pixiObject._style.align) {
this._pixiObject._style.align = align;
this._pixiObject.dirty = true;
}
const rawText = properties.get('text').getValue();
if (rawText !== this._pixiObject.text) {
this._pixiObject.text = rawText;
}
this._pixiObject.position.x =
this._instance.getX() + this._pixiObject.width / 2;
this._pixiObject.position.y =
this._instance.getY() + this._pixiObject.height / 2;
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
const opacity = +properties.get('opacity').getValue();
this._pixiObject.alpha = opacity / 255;
if (this._instance.hasCustomSize() && this._pixiObject) {
const customWidth = this.getCustomWidth();
if (
this._pixiObject &&
this._pixiObject._style.wordWrapWidth !== customWidth
) {
this._pixiObject._style.wordWrapWidth = customWidth;
const color = properties.get('color').getValue();
this._pixiObject.textStyles.default.fill = objectsRenderingService.rgbOrHexToHexNumber(
color
);
const fontSize = properties.get('fontSize').getValue();
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
const fontResourceName = properties.get('fontFamily').getValue();
if (this._fontResourceName !== fontResourceName) {
this._fontResourceName = fontResourceName;
this._pixiResourcesLoader
.loadFontFamily(this._project, fontResourceName)
.then((fontFamily) => {
// Once the font is loaded, we can use the given fontFamily.
this._pixiObject.textStyles.default.fontFamily = fontFamily;
this._pixiObject.dirty = true;
})
.catch((err) => {
// Ignore errors
console.warn(
'Unable to load font family for RenderedBBTextInstance',
err
);
});
}
const wordWrap = properties.get('wordWrap').getValue() === 'true';
if (wordWrap !== this._pixiObject._style.wordWrap) {
this._pixiObject._style.wordWrap = wordWrap;
this._pixiObject.dirty = true;
}
const align = properties.get('align').getValue();
if (align !== this._pixiObject._style.align) {
this._pixiObject._style.align = align;
this._pixiObject.dirty = true;
}
this._pixiObject.position.x =
this._instance.getX() + this._pixiObject.width / 2;
this._pixiObject.position.y =
this._instance.getY() + this._pixiObject.height / 2;
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
if (this._instance.hasCustomSize() && this._pixiObject) {
const customWidth = this.getCustomWidth();
if (
this._pixiObject &&
this._pixiObject._style.wordWrapWidth !== customWidth
) {
this._pixiObject._style.wordWrapWidth = customWidth;
this._pixiObject.dirty = true;
}
}
}
};
/**
* Return the width of the instance, when it's not resized.
*/
RenderedBBTextInstance.prototype.getDefaultWidth = function () {
return this._pixiObject.width;
};
/**
* Return the width of the instance, when it's not resized.
*/
getDefaultWidth() {
return this._pixiObject.width;
}
/**
* Return the height of the instance, when it's not resized.
*/
RenderedBBTextInstance.prototype.getDefaultHeight = function () {
return this._pixiObject.height;
};
/**
* Return the height of the instance, when it's not resized.
*/
getDefaultHeight() {
return this._pixiObject.height;
}
}
objectsRenderingService.registerInstanceRenderer(
'BBText::BBText',

View File

@@ -141,6 +141,7 @@ namespace gdjs {
setBBText(text): void {
this._text = text;
this._renderer.updateText();
this.invalidateHitboxes();
}
/**

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -42,7 +34,6 @@ module.exports = {
.setIcon('JsPlatform/Extensions/bitmapfont32.png');
const bitmapTextObject = new gd.ObjectJsImplementation();
// $FlowExpectedError
bitmapTextObject.updateProperty = function (
objectContent,
propertyName,
@@ -59,7 +50,6 @@ module.exports = {
return false;
};
// $FlowExpectedError
bitmapTextObject.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -127,7 +117,8 @@ module.exports = {
};
bitmapTextObject.setRawJSONContent(
JSON.stringify({
text: 'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
text:
'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
opacity: 255,
scale: 1,
fontSize: 20,
@@ -139,7 +130,6 @@ module.exports = {
})
);
// $FlowExpectedError
bitmapTextObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -150,7 +140,6 @@ module.exports = {
) {
return false;
};
// $FlowExpectedError
bitmapTextObject.getInitialInstanceProperties = function (
content,
instance,
@@ -176,7 +165,7 @@ module.exports = {
'Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.js'
)
.setCategoryFullName(_('Text'))
.addDefaultBehavior("TextContainerCapability::TextContainerBehavior")
.addDefaultBehavior('TextContainerCapability::TextContainerBehavior')
.addDefaultBehavior('EffectCapability::EffectBehavior')
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior');
@@ -327,33 +316,33 @@ module.exports = {
.getCodeExtraInformation()
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
object
.addAction(
'SetBitmapFontAndTextureAtlasResourceName2',
_('Bitmap files resources'),
_('Change the Bitmap Font and/or the atlas image used by the object.'),
_(
'Set the bitmap font of _PARAM0_ to _PARAM1_ and the atlas to _PARAM2_'
),
'',
'res/actions/font24.png',
'res/actions/font.png'
)
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
.addParameter(
'bitmapFontResource',
_('Bitmap font resource name'),
'',
false
)
.addParameter(
'imageResource',
_('Texture atlas resource name'),
'',
false
)
.getCodeExtraInformation()
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
object
.addAction(
'SetBitmapFontAndTextureAtlasResourceName2',
_('Bitmap files resources'),
_('Change the Bitmap Font and/or the atlas image used by the object.'),
_(
'Set the bitmap font of _PARAM0_ to _PARAM1_ and the atlas to _PARAM2_'
),
'',
'res/actions/font24.png',
'res/actions/font.png'
)
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
.addParameter(
'bitmapFontResource',
_('Bitmap font resource name'),
'',
false
)
.addParameter(
'imageResource',
_('Texture atlas resource name'),
'',
false
)
.getCodeExtraInformation()
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
object
.addExpressionAndCondition(
@@ -451,10 +440,7 @@ module.exports = {
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
/**
@@ -462,9 +448,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {
registerEditorConfigurations: function (objectsEditorService) {
objectsEditorService.registerEditorConfiguration(
'BitmapText::BitmapTextObject',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
@@ -477,9 +461,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;
@@ -649,156 +631,144 @@ module.exports = {
};
/**
* Renderer for instances of BitmapText inside the IDE.
*
* @extends RenderedBitmapTextInstance
* @class RenderedBitmapTextInstance
* @constructor
* Return the path to the thumbnail of the specified object.
* This is called to update the PIXI object on the scene editor
*/
function RenderedBitmapTextInstance(
project,
layout,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
) {
RenderedInstance.call(
this,
class RenderedBitmapTextInstance extends RenderedInstance {
static getThumbnail(project, resourcesLoader, objectConfiguration) {
return 'JsPlatform/Extensions/bitmapfont24.png';
}
constructor(
project,
layout,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
// We'll track changes of the font to trigger the loading of the new font.
this._currentBitmapFontResourceName = '';
this._currentTextureAtlasResourceName = '';
this._pixiObject = new PIXI.BitmapText('', {
// Use a default font. The proper font will be loaded in `update` method.
fontName: getDefaultBitmapFont().font,
});
this._pixiObject.anchor.x = 0.5;
this._pixiObject.anchor.y = 0.5;
this._pixiContainer.addChild(this._pixiObject);
this.update();
}
RenderedBitmapTextInstance.prototype = Object.create(
RenderedInstance.prototype
);
/**
* Return the path to the thumbnail of the specified object.
*/
RenderedBitmapTextInstance.getThumbnail = function (
project,
resourcesLoader,
objectConfiguration
) {
return 'JsPlatform/Extensions/bitmapfont24.png';
};
// This is called to update the PIXI object on the scene editor
RenderedBitmapTextInstance.prototype.update = function () {
const properties = this._associatedObjectConfiguration.getProperties();
// Update the rendered text properties (note: Pixi is only
// applying changes if there were changed).
const rawText = properties.get('text').getValue();
this._pixiObject.text = rawText;
const opacity = properties.get('opacity').getValue();
this._pixiObject.alpha = opacity / 255;
const align = properties.get('align').getValue();
this._pixiObject.align = align;
const color = properties.get('tint').getValue();
this._pixiObject.tint =
objectsRenderingService.rgbOrHexToHexNumber(color);
const scale = properties.get('scale').getValue() || 1;
this._pixiObject.scale.set(scale);
// Track the changes in font to load the new requested font.
const bitmapFontResourceName = properties
.get('bitmapFontResourceName')
.getValue();
const textureAtlasResourceName = properties
.get('textureAtlasResourceName')
.getValue();
if (
this._currentBitmapFontResourceName !== bitmapFontResourceName ||
this._currentTextureAtlasResourceName !== textureAtlasResourceName
) {
// Release the old font (if it was installed).
releaseBitmapFont(this._pixiObject.fontName);
super(
project,
layout,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
// Temporarily go back to the default font, as the PIXI.BitmapText
// object does not support being displayed with a font not installed at all.
// It will be replaced as soon as the proper font is loaded.
this._pixiObject.fontName = getDefaultBitmapFont().font;
// We'll track changes of the font to trigger the loading of the new font.
this._currentBitmapFontResourceName = '';
this._currentTextureAtlasResourceName = '';
this._currentBitmapFontResourceName = bitmapFontResourceName;
this._currentTextureAtlasResourceName = textureAtlasResourceName;
obtainBitmapFont(
this._pixiResourcesLoader,
this._project,
this._currentBitmapFontResourceName,
this._currentTextureAtlasResourceName
).then((bitmapFont) => {
this._pixiObject.fontName = bitmapFont.font;
this._pixiObject.fontSize = bitmapFont.size;
this._pixiObject.dirty = true;
this._pixiObject = new PIXI.BitmapText('', {
// Use a default font. The proper font will be loaded in `update` method.
fontName: getDefaultBitmapFont().font,
});
this._pixiObject.anchor.x = 0.5;
this._pixiObject.anchor.y = 0.5;
this._pixiContainer.addChild(this._pixiObject);
this.update();
}
// Set up the wrapping width if enabled.
const wordWrap = properties.get('wordWrap').getValue() === 'true';
if (wordWrap && this._instance.hasCustomSize()) {
this._pixiObject.maxWidth =
this.getCustomWidth() / this._pixiObject.scale.x;
this._pixiObject.dirty = true;
} else {
this._pixiObject.maxWidth = 0;
this._pixiObject.dirty = true;
update() {
const properties = this._associatedObjectConfiguration.getProperties();
// Update the rendered text properties (note: Pixi is only
// applying changes if there were changed).
const rawText = properties.get('text').getValue();
this._pixiObject.text = rawText;
const opacity = +properties.get('opacity').getValue();
this._pixiObject.alpha = opacity / 255;
const align = properties.get('align').getValue();
this._pixiObject.align = align;
const color = properties.get('tint').getValue();
this._pixiObject.tint = objectsRenderingService.rgbOrHexToHexNumber(
color
);
const scale = +(properties.get('scale').getValue() || 1);
this._pixiObject.scale.set(scale);
// Track the changes in font to load the new requested font.
const bitmapFontResourceName = properties
.get('bitmapFontResourceName')
.getValue();
const textureAtlasResourceName = properties
.get('textureAtlasResourceName')
.getValue();
if (
this._currentBitmapFontResourceName !== bitmapFontResourceName ||
this._currentTextureAtlasResourceName !== textureAtlasResourceName
) {
// Release the old font (if it was installed).
releaseBitmapFont(this._pixiObject.fontName);
// Temporarily go back to the default font, as the PIXI.BitmapText
// object does not support being displayed with a font not installed at all.
// It will be replaced as soon as the proper font is loaded.
this._pixiObject.fontName = getDefaultBitmapFont().font;
this._currentBitmapFontResourceName = bitmapFontResourceName;
this._currentTextureAtlasResourceName = textureAtlasResourceName;
obtainBitmapFont(
this._pixiResourcesLoader,
this._project,
this._currentBitmapFontResourceName,
this._currentTextureAtlasResourceName
).then((bitmapFont) => {
this._pixiObject.fontName = bitmapFont.font;
this._pixiObject.fontSize = bitmapFont.size;
this._pixiObject.dirty = true;
});
}
// Set up the wrapping width if enabled.
const wordWrap = properties.get('wordWrap').getValue() === 'true';
if (wordWrap && this._instance.hasCustomSize()) {
this._pixiObject.maxWidth =
this.getCustomWidth() / this._pixiObject.scale.x;
this._pixiObject.dirty = true;
} else {
this._pixiObject.maxWidth = 0;
this._pixiObject.dirty = true;
}
this._pixiObject.position.x =
this._instance.getX() + (this._pixiObject.textWidth * scale) / 2;
this._pixiObject.position.y =
this._instance.getY() + (this._pixiObject.textHeight * scale) / 2;
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
}
this._pixiObject.position.x =
this._instance.getX() + (this._pixiObject.textWidth * scale) / 2;
this._pixiObject.position.y =
this._instance.getY() + (this._pixiObject.textHeight * scale) / 2;
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
};
onRemovedFromScene() {
RenderedInstance.prototype.onRemovedFromScene.call(this);
RenderedBitmapTextInstance.prototype.onRemovedFromScene = function () {
RenderedInstance.prototype.onRemovedFromScene.call(this);
const fontName = this._pixiObject.fontName;
this._pixiObject.destroy();
releaseBitmapFont(fontName);
}
const fontName = this._pixiObject.fontName;
this._pixiObject.destroy();
releaseBitmapFont(fontName);
};
/**
* Return the width of the instance, when it's not resized.
*/
getDefaultWidth() {
return this._pixiObject.width;
}
/**
* Return the width of the instance, when it's not resized.
*/
RenderedBitmapTextInstance.prototype.getDefaultWidth = function () {
return this._pixiObject.width;
};
/**
* Return the height of the instance, when it's not resized.
*/
RenderedBitmapTextInstance.prototype.getDefaultHeight = function () {
return this._pixiObject.height;
};
/**
* Return the height of the instance, when it's not resized.
*/
getDefaultHeight() {
return this._pixiObject.height;
}
}
objectsRenderingService.registerInstanceRenderer(
'BitmapText::BitmapTextObject',

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -114,10 +106,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,450 +13,389 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function(_/*: (string) => string */, gd/*: libGDevelop */) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension.setExtensionInformation(
"DeviceSensors",
_("Device sensors"),
_(
"Allow the game to access the sensors of a mobile device."
),
"Matthias Meike",
"Open source (MIT License)"
).setExtensionHelpPath("/all-features/device-sensors")
.setCategory('Input');
extension.addInstructionOrExpressionGroupMetadata(_("Device sensors"))
.setIcon("JsPlatform/Extensions/orientation_active32.png");
extension
.setExtensionInformation(
'DeviceSensors',
_('Device sensors'),
_('Allow the game to access the sensors of a mobile device.'),
'Matthias Meike',
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/device-sensors')
.setCategory('Input');
extension
.addInstructionOrExpressionGroupMetadata(_('Device sensors'))
.setIcon('JsPlatform/Extensions/orientation_active32.png');
extension
.addCondition(
"OrientationSensorActive",
_("Sensor active"),
'OrientationSensorActive',
_('Sensor active'),
_(
"The condition is true if the device orientation sensor is currently active"
'The condition is true if the device orientation sensor is currently active'
),
_("Orientation sensor is active"),
_("Orientation"),
"JsPlatform/Extensions/orientation_active32.png",
"JsPlatform/Extensions/orientation_active32.png"
_('Orientation sensor is active'),
_('Orientation'),
'JsPlatform/Extensions/orientation_active32.png',
'JsPlatform/Extensions/orientation_active32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.isActive");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.isActive');
extension
extension
.addCondition(
"OrientationAlpha",
_("Compare the value of orientation alpha"),
_(
"Compare the value of orientation alpha. (Range: 0 to 360°)"
),
_("the orientation alpha"),
_("Orientation"),
"JsPlatform/Extensions/orientation_alpha32.png",
"JsPlatform/Extensions/orientation_alpha32.png"
'OrientationAlpha',
_('Compare the value of orientation alpha'),
_('Compare the value of orientation alpha. (Range: 0 to 360°)'),
_('the orientation alpha'),
_('Orientation'),
'JsPlatform/Extensions/orientation_alpha32.png',
'JsPlatform/Extensions/orientation_alpha32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationAlpha");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationAlpha');
extension
extension
.addCondition(
"OrientationBeta",
_("Compare the value of orientation beta"),
_(
"Compare the value of orientation beta. (Range: -180 to 180°)"
),
_("the orientation beta"),
_("Orientation"),
"JsPlatform/Extensions/orientation_beta32.png",
"JsPlatform/Extensions/orientation_beta32.png"
'OrientationBeta',
_('Compare the value of orientation beta'),
_('Compare the value of orientation beta. (Range: -180 to 180°)'),
_('the orientation beta'),
_('Orientation'),
'JsPlatform/Extensions/orientation_beta32.png',
'JsPlatform/Extensions/orientation_beta32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationBeta");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationBeta');
extension
extension
.addCondition(
"OrientationGamma",
_("Compare the value of orientation gamma"),
_(
"Compare the value of orientation gamma. (Range: -90 to 90°)"
),
_("the orientation gamma"),
_("Orientation"),
"JsPlatform/Extensions/orientation_gamma32.png",
"JsPlatform/Extensions/orientation_gamma32.png"
'OrientationGamma',
_('Compare the value of orientation gamma'),
_('Compare the value of orientation gamma. (Range: -90 to 90°)'),
_('the orientation gamma'),
_('Orientation'),
'JsPlatform/Extensions/orientation_gamma32.png',
'JsPlatform/Extensions/orientation_gamma32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationGamma");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationGamma');
extension
.addAction(
"ActivateOrientationListener",
_("Activate orientation sensor"),
_("Activate the orientation sensor. (remember to turn it off again)"),
_("Activate the orientation sensor."),
_("Orientation"),
"JsPlatform/Extensions/orientation_active32.png",
"JsPlatform/Extensions/orientation_active32.png"
'ActivateOrientationListener',
_('Activate orientation sensor'),
_('Activate the orientation sensor. (remember to turn it off again)'),
_('Activate the orientation sensor.'),
_('Orientation'),
'JsPlatform/Extensions/orientation_active32.png',
'JsPlatform/Extensions/orientation_active32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.activateOrientationSensor");
.getCodeExtraInformation()
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName(
'gdjs.deviceSensors.orientation.activateOrientationSensor'
);
extension
.addAction(
"DeactivateOrientationListener",
_("Deactivate orientation sensor"),
_("Deactivate the orientation sensor."),
_("Deactivate the orientation sensor."),
_("Orientation"),
"JsPlatform/Extensions/orientation_inactive32.png",
"JsPlatform/Extensions/orientation_inactive32.png"
'DeactivateOrientationListener',
_('Deactivate orientation sensor'),
_('Deactivate the orientation sensor.'),
_('Deactivate the orientation sensor.'),
_('Orientation'),
'JsPlatform/Extensions/orientation_inactive32.png',
'JsPlatform/Extensions/orientation_inactive32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.deactivateOrientationSensor");
.getCodeExtraInformation()
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName(
'gdjs.deviceSensors.orientation.deactivateOrientationSensor'
);
extension
.addExpression(
"OrientationAbsolute",
_("Is Absolute"),
_("Get if the devices orientation is absolute and not relative"),
_("Orientation"),
"JsPlatform/Extensions/orientation_absolute16.png"
'OrientationAbsolute',
_('Is Absolute'),
_('Get if the devices orientation is absolute and not relative'),
_('Orientation'),
'JsPlatform/Extensions/orientation_absolute16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationAbsolute");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationAbsolute');
extension
.addExpression(
"OrientationAlpha",
_("Alpha value"),
_("Get the devices orientation Alpha (compass)"),
_("Orientation"),
"JsPlatform/Extensions/orientation_alpha16.png"
'OrientationAlpha',
_('Alpha value'),
_('Get the devices orientation Alpha (compass)'),
_('Orientation'),
'JsPlatform/Extensions/orientation_alpha16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationAlpha");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationAlpha');
extension
.addExpression(
"OrientationBeta",
_("Beta value"),
_("Get the devices orientation Beta"),
_("Orientation"),
"JsPlatform/Extensions/orientation_beta16.png"
'OrientationBeta',
_('Beta value'),
_('Get the devices orientation Beta'),
_('Orientation'),
'JsPlatform/Extensions/orientation_beta16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationBeta");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationBeta');
extension
.addExpression(
"OrientationGamma",
_("Gamma value"),
_("Get the devices orientation Gamma value"),
_("Orientation"),
"JsPlatform/Extensions/orientation_gamma16.png"
'OrientationGamma',
_('Gamma value'),
_('Get the devices orientation Gamma value'),
_('Orientation'),
'JsPlatform/Extensions/orientation_gamma16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationGamma");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationGamma');
extension
extension
.addCondition(
"MotionSensorActive",
_("Sensor active"),
'MotionSensorActive',
_('Sensor active'),
_(
"The condition is true if the device motion sensor is currently active"
'The condition is true if the device motion sensor is currently active'
),
_("Motion sensor is active"),
_("Motion"),
"JsPlatform/Extensions/motion_active32.png",
"JsPlatform/Extensions/motion_active32.png"
_('Motion sensor is active'),
_('Motion'),
'JsPlatform/Extensions/motion_active32.png',
'JsPlatform/Extensions/motion_active32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.isActive");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.isActive');
extension
extension
.addCondition(
"RotationAlpha",
_("Compare the value of rotation alpha"),
'RotationAlpha',
_('Compare the value of rotation alpha'),
_(
"Compare the value of rotation alpha. (Note: few devices support this sensor)"
'Compare the value of rotation alpha. (Note: few devices support this sensor)'
),
_("the rotation alpha"),
_("Motion"),
"JsPlatform/Extensions/motion_rotation_alpha32.png",
"JsPlatform/Extensions/motion_rotation_alpha32.png"
_('the rotation alpha'),
_('Motion'),
'JsPlatform/Extensions/motion_rotation_alpha32.png',
'JsPlatform/Extensions/motion_rotation_alpha32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value (m/s²)'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getRotationAlpha");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getRotationAlpha');
extension
extension
.addCondition(
"RotationBeta",
_("Compare the value of rotation beta"),
'RotationBeta',
_('Compare the value of rotation beta'),
_(
"Compare the value of rotation beta. (Note: few devices support this sensor)"
'Compare the value of rotation beta. (Note: few devices support this sensor)'
),
_("the rotation beta"),
_("Motion"),
"JsPlatform/Extensions/motion_rotation_beta32.png",
"JsPlatform/Extensions/motion_rotation_beta32.png"
_('the rotation beta'),
_('Motion'),
'JsPlatform/Extensions/motion_rotation_beta32.png',
'JsPlatform/Extensions/motion_rotation_beta32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value (m/s²)'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getRotationBeta");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getRotationBeta');
extension
extension
.addCondition(
"RotationGamma",
_("Compare the value of rotation gamma"),
'RotationGamma',
_('Compare the value of rotation gamma'),
_(
"Compare the value of rotation gamma. (Note: few devices support this sensor)"
'Compare the value of rotation gamma. (Note: few devices support this sensor)'
),
_("the rotation gamma"),
_("Motion"),
"JsPlatform/Extensions/motion_rotation_gamma32.png",
"JsPlatform/Extensions/motion_rotation_gamma32.png"
_('the rotation gamma'),
_('Motion'),
'JsPlatform/Extensions/motion_rotation_gamma32.png',
'JsPlatform/Extensions/motion_rotation_gamma32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value (m/s²)'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getRotationGamma");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getRotationGamma');
extension
extension
.addCondition(
"AccelerationX",
_("Compare the value of acceleration on X-axis"),
_(
"Compare the value of acceleration on the X-axis (m/s²)."
),
_("the acceleration X"),
_("Motion"),
"JsPlatform/Extensions/motion_acceleration_x32.png",
"JsPlatform/Extensions/motion_acceleration_x32.png"
'AccelerationX',
_('Compare the value of acceleration on X-axis'),
_('Compare the value of acceleration on the X-axis (m/s²).'),
_('the acceleration X'),
_('Motion'),
'JsPlatform/Extensions/motion_acceleration_x32.png',
'JsPlatform/Extensions/motion_acceleration_x32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value (m/s²)'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationX");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationX');
extension
extension
.addCondition(
"AccelerationY",
_("Compare the value of acceleration on Y-axis"),
_(
"Compare the value of acceleration on the Y-axis (m/s²)."
),
_("the acceleration Y"),
_("Motion"),
"JsPlatform/Extensions/motion_acceleration_y32.png",
"JsPlatform/Extensions/motion_acceleration_y32.png"
'AccelerationY',
_('Compare the value of acceleration on Y-axis'),
_('Compare the value of acceleration on the Y-axis (m/s²).'),
_('the acceleration Y'),
_('Motion'),
'JsPlatform/Extensions/motion_acceleration_y32.png',
'JsPlatform/Extensions/motion_acceleration_y32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value (m/s²)'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationY");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationY');
extension
extension
.addCondition(
"AccelerationZ",
_("Compare the value of acceleration on Z-axis"),
_(
"Compare the value of acceleration on the Z-axis (m/s²)."
),
_("the acceleration Z"),
_("Motion"),
"JsPlatform/Extensions/motion_acceleration_z32.png",
"JsPlatform/Extensions/motion_acceleration_z32.png"
'AccelerationZ',
_('Compare the value of acceleration on Z-axis'),
_('Compare the value of acceleration on the Z-axis (m/s²).'),
_('the acceleration Z'),
_('Motion'),
'JsPlatform/Extensions/motion_acceleration_z32.png',
'JsPlatform/Extensions/motion_acceleration_z32.png'
)
.addParameter("relationalOperator", _("Sign of the test"), "number")
.addParameter("expression", _("Value (m/s²)"))
.addParameter('relationalOperator', _('Sign of the test'), 'number')
.addParameter('expression', _('Value (m/s²)'))
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationZ");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationZ');
extension
.addAction(
"ActivateMotionListener",
_("Activate motion sensor"),
_("Activate the motion sensor. (remember to turn it off again)"),
_("Activate the motion sensor."),
_("Motion"),
"JsPlatform/Extensions/motion_active32.png",
"JsPlatform/Extensions/motion_active32.png"
'ActivateMotionListener',
_('Activate motion sensor'),
_('Activate the motion sensor. (remember to turn it off again)'),
_('Activate the motion sensor.'),
_('Motion'),
'JsPlatform/Extensions/motion_active32.png',
'JsPlatform/Extensions/motion_active32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.activateMotionSensor");
.getCodeExtraInformation()
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.activateMotionSensor');
extension
.addAction(
"DeactivateMotionListener",
_("Deactivate motion sensor"),
_("Deactivate the motion sensor."),
_("Deactivate the motion sensor."),
_("Motion"),
"JsPlatform/Extensions/motion_inactive32.png",
"JsPlatform/Extensions/motion_inactive32.png"
'DeactivateMotionListener',
_('Deactivate motion sensor'),
_('Deactivate the motion sensor.'),
_('Deactivate the motion sensor.'),
_('Motion'),
'JsPlatform/Extensions/motion_inactive32.png',
'JsPlatform/Extensions/motion_inactive32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.deactivateMotionSensor");
.getCodeExtraInformation()
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.deactivateMotionSensor');
extension
.addExpression(
"RotationAlpha",
_("Alpha value"),
_("Get the devices rotation Alpha"),
_("Motion"),
"JsPlatform/Extensions/motion_rotation_alpha16.png"
'RotationAlpha',
_('Alpha value'),
_('Get the devices rotation Alpha'),
_('Motion'),
'JsPlatform/Extensions/motion_rotation_alpha16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getRotationAlpha");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getRotationAlpha');
extension
.addExpression(
"RotationBeta",
_("Beta value"),
_("Get the devices rotation Beta"),
_("Motion"),
"JsPlatform/Extensions/motion_rotation_beta16.png"
'RotationBeta',
_('Beta value'),
_('Get the devices rotation Beta'),
_('Motion'),
'JsPlatform/Extensions/motion_rotation_beta16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getRotationBeta");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getRotationBeta');
extension
.addExpression(
"RotationGamma",
_("Gamma value"),
_("Get the devices rotation Gamma"),
_("Motion"),
"JsPlatform/Extensions/motion_rotation_gamma16.png"
'RotationGamma',
_('Gamma value'),
_('Get the devices rotation Gamma'),
_('Motion'),
'JsPlatform/Extensions/motion_rotation_gamma16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getRotationGamma");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getRotationGamma');
extension
extension
.addExpression(
"AccelerationX",
_("Acceleration X value"),
_("Get the devices acceleration on the X-axis (m/s²)"),
_("Motion"),
"JsPlatform/Extensions/motion_acceleration_x16.png"
'AccelerationX',
_('Acceleration X value'),
_('Get the devices acceleration on the X-axis (m/s²)'),
_('Motion'),
'JsPlatform/Extensions/motion_acceleration_x16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationX");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationX');
extension
extension
.addExpression(
"AccelerationY",
_("Acceleration Y value"),
_("Get the devices acceleration on the Y-axis (m/s²)"),
_("Motion"),
"JsPlatform/Extensions/motion_acceleration_y16.png"
'AccelerationY',
_('Acceleration Y value'),
_('Get the devices acceleration on the Y-axis (m/s²)'),
_('Motion'),
'JsPlatform/Extensions/motion_acceleration_y16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationY");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationY');
extension
extension
.addExpression(
"AccelerationZ",
_("Acceleration Z value"),
_("Get the devices acceleration on the Z-axis (m/s²)"),
_("Motion"),
"JsPlatform/Extensions/motion_acceleration_z16.png"
'AccelerationZ',
_('Acceleration Z value'),
_('Get the devices acceleration on the Z-axis (m/s²)'),
_('Motion'),
'JsPlatform/Extensions/motion_acceleration_z16.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/DeviceSensors/devicesensortools.js"
)
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationZ");
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationZ');
return extension;
},
runExtensionSanityTests: function(gd /*: libGDevelop */, extension /*: gdPlatformExtension*/) { return []; },
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -36,8 +28,9 @@ module.exports = {
)
.setExtensionHelpPath('/all-features/device-vibration')
.setCategory('User interface');
extension.addInstructionOrExpressionGroupMetadata(_("Device vibration"))
.setIcon("JsPlatform/Extensions/vibration_start32.png");
extension
.addInstructionOrExpressionGroupMetadata(_('Device vibration'))
.setIcon('JsPlatform/Extensions/vibration_start32.png');
extension
.addDependency()
@@ -99,10 +92,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -721,10 +713,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,28 +13,20 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension.setExtensionInformation(
'Effects',
'Effects',
'Lots of different effects to be used in your game.',
'Various contributors from PixiJS, PixiJS filters and GDevelop',
'MIT'
)
.setCategory('Visual effect')
.setExtensionHelpPath('/interface/scene-editor/layer-effects');
extension
.setExtensionInformation(
'Effects',
'Effects',
'Lots of different effects to be used in your game.',
'Various contributors from PixiJS, PixiJS filters and GDevelop',
'MIT'
)
.setCategory('Visual effect')
.setExtensionHelpPath('/interface/scene-editor/layer-effects');
// You can declare an effect here. Please order the effects by alphabetical order.
// This file is for common effects that are well-known/"battle-tested". If you have an
@@ -230,7 +223,11 @@ module.exports = {
const blurEffect = extension
.addEffect('Blur')
.setFullName(_('Blur (Gaussian, slow - prefer to use Kawase blur)'))
.setDescription(_('Blur the rendered image. This is slow, so prefer to use Kawase blur in most cases.'))
.setDescription(
_(
'Blur the rendered image. This is slow, so prefer to use Kawase blur in most cases.'
)
)
.markAsOnlyWorkingFor2D()
.addIncludeFile('Extensions/Effects/blur-pixi-filter.js');
const blurProperties = blurEffect.getProperties();
@@ -728,13 +725,11 @@ module.exports = {
const hslAdjustmentEffect = extension
.addEffect('HslAdjustment')
.setFullName(_('HSL Adjustment'))
.setDescription(
_(
'Adjust hue, saturation and lightness.'
)
)
.setDescription(_('Adjust hue, saturation and lightness.'))
.markAsOnlyWorkingFor2D()
.addIncludeFile('Extensions/Effects/pixi-filters/filter-hsl-adjustment.js')
.addIncludeFile(
'Extensions/Effects/pixi-filters/filter-hsl-adjustment.js'
)
.addIncludeFile('Extensions/Effects/hsl-adjustment-pixi-filter.js');
const hslAdjustmentProperties = hslAdjustmentEffect.getProperties();
hslAdjustmentProperties
@@ -767,7 +762,9 @@ module.exports = {
.addEffect('KawaseBlur')
.setFullName(_('Blur (Kawase, fast)'))
.setDescription(
_('Blur the rendered image, with much better performance than Gaussian blur.')
_(
'Blur the rendered image, with much better performance than Gaussian blur.'
)
)
.markAsOnlyWorkingFor2D()
.addIncludeFile('Extensions/Effects/pixi-filters/filter-kawase-blur.js')
@@ -816,9 +813,7 @@ module.exports = {
const motionBlurEffect = extension
.addEffect('MotionBlur')
.setFullName(_('Motion Blur'))
.setDescription(
_('Blur the rendered image to give a feeling of speed.')
)
.setDescription(_('Blur the rendered image to give a feeling of speed.'))
.markAsOnlyWorkingFor2D()
.addIncludeFile('Extensions/Effects/pixi-filters/filter-motion-blur.js')
.addIncludeFile('Extensions/Effects/motion-blur-pixi-filter.js');
@@ -1174,7 +1169,9 @@ module.exports = {
.setValue('0')
.setLabel(_('Elapsed time'))
.setType('number')
.setDescription('It can be set back to 0 to play the shockwave animation again.');
.setDescription(
'It can be set back to 0 to play the shockwave animation again.'
);
shockwaveEffectProperties
.getOrCreate('speed')
.setValue('500')
@@ -1311,10 +1308,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension.setExtensionInformation(
'MyDummyExtension',
@@ -153,7 +145,6 @@ module.exports = {
// Everything that is stored inside the behavior is in "behaviorContent" and is automatically
// saved/loaded to JSON.
var dummyBehavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
dummyBehavior.updateProperty = function (
behaviorContent,
propertyName,
@@ -170,7 +161,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
dummyBehavior.getProperties = function (behaviorContent) {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
@@ -187,7 +177,6 @@ module.exports = {
return behaviorProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
dummyBehavior.initializeContent = function (behaviorContent) {
behaviorContent.setStringAttribute('property1', 'Initial value 1');
behaviorContent.setBoolAttribute('property2', true);
@@ -201,6 +190,7 @@ module.exports = {
'',
'CppPlatform/Extensions/topdownmovementicon.png',
'DummyBehavior',
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
dummyBehavior,
new gd.BehaviorsSharedData()
)
@@ -215,7 +205,6 @@ module.exports = {
// Create a new gd.BehaviorSharedDataJsImplementation object and implement the methods
// that are called to get and set the properties of the shared data.
var dummyBehaviorWithSharedData = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
dummyBehaviorWithSharedData.updateProperty = function (
behaviorContent,
propertyName,
@@ -228,7 +217,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
dummyBehaviorWithSharedData.getProperties = function (behaviorContent) {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
@@ -238,13 +226,11 @@ module.exports = {
return behaviorProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
dummyBehaviorWithSharedData.initializeContent = function (behaviorContent) {
behaviorContent.setStringAttribute('property1', 'Initial value 1');
};
var sharedData = new gd.BehaviorSharedDataJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.updateProperty = function (
sharedContent,
propertyName,
@@ -257,7 +243,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.getProperties = function (sharedContent) {
var sharedProperties = new gd.MapStringPropertyDescriptor();
@@ -267,7 +252,6 @@ module.exports = {
return sharedProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.initializeContent = function (behaviorContent) {
behaviorContent.setStringAttribute(
'sharedProperty1',
@@ -284,6 +268,7 @@ module.exports = {
'',
'CppPlatform/Extensions/topdownmovementicon.png',
'DummyBehaviorWithSharedData',
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
dummyBehaviorWithSharedData,
sharedData
)
@@ -302,7 +287,6 @@ module.exports = {
// Everything that is stored inside the object is in "content" and is automatically
// saved/loaded to JSON.
var dummyObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
dummyObject.updateProperty = function (
objectContent,
propertyName,
@@ -327,7 +311,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
dummyObject.getProperties = function (objectContent) {
var objectProperties = new gd.MapStringPropertyDescriptor();
@@ -362,7 +345,6 @@ module.exports = {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object
dummyObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -382,7 +364,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
dummyObject.getInitialInstanceProperties = function (
content,
instance,
@@ -446,10 +427,7 @@ module.exports = {
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
const dummyBehavior = extension
.getBehaviorMetadata('MyDummyExtension::DummyBehavior')
.get();
@@ -474,9 +452,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {
registerEditorConfigurations: function (objectsEditorService) {
objectsEditorService.registerEditorConfiguration(
'MyDummyExtension::DummyObject',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
@@ -489,9 +465,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,368 +13,414 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function(_/*: (string) => string */, gd/*: libGDevelop */) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
"FacebookInstantGames",
_("Facebook Instant Games"),
'FacebookInstantGames',
_('Facebook Instant Games'),
_(
"Allow your game to send scores and interact with the Facebook Instant Games platform."
'Allow your game to send scores and interact with the Facebook Instant Games platform.'
),
"Florian Rival",
"Open source (MIT License)"
'Florian Rival',
'Open source (MIT License)'
)
.setExtensionHelpPath("/publishing/publishing-to-facebook-instant-games")
.setExtensionHelpPath('/publishing/publishing-to-facebook-instant-games')
.setCategory('Third-party');
extension.addInstructionOrExpressionGroupMetadata(_("Facebook Instant Games"))
.setIcon("JsPlatform/Extensions/facebookicon32.png");
extension
.addInstructionOrExpressionGroupMetadata(_('Facebook Instant Games'))
.setIcon('JsPlatform/Extensions/facebookicon32.png');
extension
.addAction(
"SavePlayerData",
_("Save player data"),
'SavePlayerData',
_('Save player data'),
_(
"Save the content of the given scene variable in the player data, stored on Facebook Instant Games servers"
'Save the content of the given scene variable in the player data, stored on Facebook Instant Games servers'
),
_(
"Save the content of _PARAM1_ in key _PARAM0_ of player data (store success message in _PARAM2_ or error in _PARAM3_)"
'Save the content of _PARAM1_ in key _PARAM0_ of player data (store success message in _PARAM2_ or error in _PARAM3_)'
),
_("Player data"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
_('Player data'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter("string", 'Data key name (e.g: "Lives")', "", false)
.addParameter("scenevar", "Scene variable with the content to save", "", false)
.addParameter('string', 'Data key name (e.g: "Lives")', '', false)
.addParameter(
"scenevar",
_("Variable where to store the success message (optional)"),
"",
true
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.setPlayerData");
extension
.addAction(
"LoadPlayerData",
_("Load player data"),
_("Load the player data with the given key in a variable"),
_(
"Load player data with key _PARAM0_ in _PARAM1_ (or error in _PARAM2_)"
),
_("Player data"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
)
.addParameter("string", _('Data key name (e.g: "Lives")'), "", false)
.addParameter(
"scenevar",
_("Variable where to store loaded data"),
"",
'scenevar',
'Scene variable with the content to save',
'',
false
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
'scenevar',
_('Variable where to store the success message (optional)'),
'',
true
)
.addParameter(
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.loadPlayerData");
.setFunctionName('gdjs.evtTools.facebookInstantGames.setPlayerData');
extension
.addAction(
"SavePlayerScore",
_("Save player score"),
'LoadPlayerData',
_('Load player data'),
_('Load the player data with the given key in a variable'),
_(
"Save the score, and optionally the content of the given variable in the player score, for the given metadata."
'Load player data with key _PARAM0_ in _PARAM1_ (or error in _PARAM2_)'
),
_(
"In leaderboard _PARAM0_, save score _PARAM1_ for the player and extra data from _PARAM2_ (store success message in _PARAM3_ or error in _PARAM4_)"
),
_("Leaderboards"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
_('Player data'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter('string', _('Data key name (e.g: "Lives")'), '', false)
.addParameter(
'scenevar',
_('Variable where to store loaded data'),
'',
false
)
.addParameter(
"string",
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName('gdjs.evtTools.facebookInstantGames.loadPlayerData');
extension
.addAction(
'SavePlayerScore',
_('Save player score'),
_(
'Save the score, and optionally the content of the given variable in the player score, for the given metadata.'
),
_(
'In leaderboard _PARAM0_, save score _PARAM1_ for the player and extra data from _PARAM2_ (store success message in _PARAM3_ or error in _PARAM4_)'
),
_('Leaderboards'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter(
'string',
'Leaderboard name (e.g: "PlayersBestTimes")',
"",
'',
false
)
.addParameter("expression", "Score to register for the player", "", false)
.addParameter('expression', 'Score to register for the player', '', false)
.addParameter(
"scenevar",
_("Optional variable with metadata to save"),
"",
'scenevar',
_('Optional variable with metadata to save'),
'',
true
)
.addParameter(
"scenevar",
_("Variable where to store the success message (optional)"),
"",
'scenevar',
_('Variable where to store the success message (optional)'),
'',
true
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.setPlayerScore");
extension
.addAction(
"LoadPlayerEntry",
_("Load player entry"),
_("Load the player entry in the given leaderboard"),
'scenevar',
_(
"Load player entry from leaderboard _PARAM0_. Set rank in _PARAM1_, score in _PARAM2_ (extra data if any in _PARAM3_ and error in _PARAM4_)"
'Variable where to store the error message (optional, if an error occurs)'
),
_("Leaderboards"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName('gdjs.evtTools.facebookInstantGames.setPlayerScore');
extension
.addAction(
'LoadPlayerEntry',
_('Load player entry'),
_('Load the player entry in the given leaderboard'),
_(
'Load player entry from leaderboard _PARAM0_. Set rank in _PARAM1_, score in _PARAM2_ (extra data if any in _PARAM3_ and error in _PARAM4_)'
),
_('Leaderboards'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter(
"string",
'string',
_('Leaderboard name (e.g: "PlayersBestTimes")'),
"",
'',
false
)
.addParameter(
"scenevar",
_("Variable where to store the player rank (of -1 if not ranked)"),
"",
'scenevar',
_('Variable where to store the player rank (of -1 if not ranked)'),
'',
true
)
.addParameter(
"scenevar",
_("Variable where to store the player score (of -1 if no score)"),
"",
'scenevar',
_('Variable where to store the player score (of -1 if no score)'),
'',
true
)
.addParameter(
"scenevar",
_("Variable where to store extra data (if any)"),
"",
'scenevar',
_('Variable where to store extra data (if any)'),
'',
true
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.getPlayerEntry");
.setFunctionName('gdjs.evtTools.facebookInstantGames.getPlayerEntry');
extension
.addCondition(
"AreAdsSupported",
_("Check if ads are supported"),
_("Check if showing ads is supported on this device (only mobile phones can show ads)"),
_("Ads can be shown on this device"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'AreAdsSupported',
_('Check if ads are supported'),
_(
'Check if showing ads is supported on this device (only mobile phones can show ads)'
),
_('Ads can be shown on this device'),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.areAdsSupported");
.setFunctionName('gdjs.evtTools.facebookInstantGames.areAdsSupported');
extension
.addCondition(
"IsInterstitialAdReady",
_("Is the interstitial ad ready"),
_("Check if the interstitial ad requested from Facebook is loaded and ready to be shown."),
_("The interstitial ad is loaded and ready to be shown"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'IsInterstitialAdReady',
_('Is the interstitial ad ready'),
_(
'Check if the interstitial ad requested from Facebook is loaded and ready to be shown.'
),
_('The interstitial ad is loaded and ready to be shown'),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.isInterstitialAdReady");
.setFunctionName(
'gdjs.evtTools.facebookInstantGames.isInterstitialAdReady'
);
extension
.addAction(
"LoadInterstitialAd",
_("Load and prepare an interstitial ad"),
_("Request and load an interstitial ad from Facebook, so that it is ready to be shown."),
_("Request and load an interstitial ad from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'LoadInterstitialAd',
_('Load and prepare an interstitial ad'),
_(
'Request and load an interstitial ad from Facebook, so that it is ready to be shown.'
),
_(
'Request and load an interstitial ad from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)'
),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter(
"string",
_("The Ad Placement id (can be found while setting up the ad on Facebook)"),
"",
'string',
_(
'The Ad Placement id (can be found while setting up the ad on Facebook)'
),
'',
false
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.loadInterstitialAd");
.setFunctionName('gdjs.evtTools.facebookInstantGames.loadInterstitialAd');
extension
.addAction(
"ShowInterstitialAd",
_("Show the loaded interstitial ad"),
_("Show the interstitial ad previously loaded in memory. This won't work if you did not load the interstitial before."),
_("Show the interstitial ad previously loaded in memory (if any error, store it in _PARAM0_)"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'ShowInterstitialAd',
_('Show the loaded interstitial ad'),
_(
"Show the interstitial ad previously loaded in memory. This won't work if you did not load the interstitial before."
),
_(
'Show the interstitial ad previously loaded in memory (if any error, store it in _PARAM0_)'
),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.showInterstitialAd");
.setFunctionName('gdjs.evtTools.facebookInstantGames.showInterstitialAd');
extension
.addCondition(
"IsRewardedVideoReady",
_("Is the rewarded video ready"),
_("Check if the rewarded video requested from Facebook is loaded and ready to be shown."),
_("The rewarded video is loaded and ready to be shown"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'IsRewardedVideoReady',
_('Is the rewarded video ready'),
_(
'Check if the rewarded video requested from Facebook is loaded and ready to be shown.'
),
_('The rewarded video is loaded and ready to be shown'),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.isRewardedVideoReady");
.setFunctionName(
'gdjs.evtTools.facebookInstantGames.isRewardedVideoReady'
);
extension
.addAction(
"LoadRewardedVideo",
_("Load and prepare a rewarded video"),
_("Request and load a rewarded video from Facebook, so that it is ready to be shown."),
_("Request and load a rewarded video from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'LoadRewardedVideo',
_('Load and prepare a rewarded video'),
_(
'Request and load a rewarded video from Facebook, so that it is ready to be shown.'
),
_(
'Request and load a rewarded video from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)'
),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter(
"string",
_("The Ad Placement id (can be found while setting up the ad on Facebook)"),
"",
'string',
_(
'The Ad Placement id (can be found while setting up the ad on Facebook)'
),
'',
false
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.loadRewardedVideo");
.setFunctionName('gdjs.evtTools.facebookInstantGames.loadRewardedVideo');
extension
.addAction(
"ShowRewardedVideo",
_("Show the loaded rewarded video"),
_("Show the rewarded video previously loaded in memory. This won't work if you did not load the video before."),
_("Show the rewarded video previously loaded in memory (if any error, store it in _PARAM0_)"),
_("Ads"),
"JsPlatform/Extensions/facebookicon32.png",
"JsPlatform/Extensions/facebookicon32.png"
'ShowRewardedVideo',
_('Show the loaded rewarded video'),
_(
"Show the rewarded video previously loaded in memory. This won't work if you did not load the video before."
),
_(
'Show the rewarded video previously loaded in memory (if any error, store it in _PARAM0_)'
),
_('Ads'),
'JsPlatform/Extensions/facebookicon32.png',
'JsPlatform/Extensions/facebookicon32.png'
)
.addParameter(
"scenevar",
_("Variable where to store the error message (optional, if an error occurs)"),
"",
'scenevar',
_(
'Variable where to store the error message (optional, if an error occurs)'
),
'',
true
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.showRewardedVideo");
.setFunctionName('gdjs.evtTools.facebookInstantGames.showRewardedVideo');
extension
.addStrExpression(
"PlayerId",
_("Player identifier"),
_("Get the player unique identifier"),
'PlayerId',
_('Player identifier'),
_('Get the player unique identifier'),
'',
"JsPlatform/Extensions/facebookicon32.png"
'JsPlatform/Extensions/facebookicon32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.getPlayerId");
.setFunctionName('gdjs.evtTools.facebookInstantGames.getPlayerId');
extension
.addStrExpression(
"PlayerName",
_("Player name"),
_("Get the player name"),
'PlayerName',
_('Player name'),
_('Get the player name'),
'',
"JsPlatform/Extensions/facebookicon32.png"
'JsPlatform/Extensions/facebookicon32.png'
)
.getCodeExtraInformation()
.setIncludeFile(
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
)
.setFunctionName("gdjs.evtTools.facebookInstantGames.getPlayerName");
.setFunctionName('gdjs.evtTools.facebookInstantGames.getPlayerName');
return extension;
},
runExtensionSanityTests: function(gd /*: libGDevelop */, extension /*: gdPlatformExtension*/) {
runExtensionSanityTests: function (gd, extension) {
return [];
}
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -11,11 +12,10 @@
*
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
@@ -2314,10 +2314,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension */
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

339
Extensions/JsExtensionTypes.d.ts vendored Normal file
View File

@@ -0,0 +1,339 @@
type GDNamespace = typeof import('../GDevelop.js/types');
// This is necessary for typescript to interpret the identifier PIXI as a namespace
// in this file and merge it with the other namespace declarations.
declare namespace PIXI {}
/**
* Expose functions to load PIXI textures or fonts, given the names of
* resources and a gd.Project.
*/
declare class PixiResourcesLoader {
burstCache();
async reloadResource(project: gd.Project, resourceName: string);
/**
* Reload the given resources.
*/
async reloadResources(
project: gd.Project,
resourceNames: Array<string>
): Promise<void>;
/**
* Return the PIXI texture represented by the given resource.
* If not loaded, it will load it.
*/
getPIXITexture(project: gd.Project, resourceName: string): PIXI.Texture;
/**
* Return the three.js texture associated to the specified resource name.
* Returns a placeholder texture if not found.
* @param project The project
* @param resourceName The name of the resource
* @returns The requested texture, or a placeholder if not found.
*/
getThreeTexture(project: gd.Project, resourceName: string): THREE.Texture;
/**
* Return the three.js material associated to the specified resource name.
* @param project The project
* @param resourceName The name of the resource
* @param options Set if the material should be transparent or not.
* @returns The requested material.
*/
getThreeMaterial(
project: gd.Project,
resourceName: string,
{ useTransparentTexture }: { useTransparentTexture: boolean }
): THREE.Material;
/**
* Return the three.js material associated to the specified resource name.
* @param project The project
* @param resourceName The name of the resource
* @param options
* @returns The requested material.
*/
get3DModel(
project: gd.Project,
resourceName: string
): Promise<THREE.THREE_ADDONS.GLTF>;
/**
* Return the Pixi spine data for the specified resource name.
* @param project The project
* @param spineName The name of the spine json resource
* @returns The requested spine skeleton.
*/
async getSpineData(
project: gd.Project,
spineName: string
): Promise<SpineDataOrLoadingError>;
/**
* Return the PIXI video texture represented by the given resource.
* If not loaded, it will load it.
* @returns The PIXI.Texture to be used. It can be loading, so you
* should listen to PIXI.Texture `update` event, and refresh your object
* if this event is triggered.
*/
getPIXIVideoTexture(project: gd.Project, resourceName: string): PIXI.Texture;
/**
* Load the given font from its url/filename.
* @returns a Promise that resolves with the font-family to be used
* to render a text with the font.
*/
loadFontFamily(project: gd.Project, resourceName: string): Promise<string>;
/**
* Get the font family name for the given font resource.
* The font won't be loaded.
* @returns The font-family to be used to render a text with the font.
*/
getFontFamily(project: gd.Project, resourceName: string): string;
/**
* Get the data from a bitmap font file (fnt/xml) resource in the IDE.
*/
getBitmapFontData(project: gd.Project, resourceName: string): Promise<any>;
getInvalidPIXITexture();
getResourceJsonData(project: gd.Project, resourceName: string);
}
/**
* RenderedInstance is the base class used for creating 2D renderers of instances,
* which display on the scene editor, using Pixi.js, the instance of an object (see InstancesEditor).
*/
class RenderedInstance {
_project: gd.Project;
_layout: gd.Layout;
_instance: gd.InitialInstance;
_associatedObjectConfiguration: gd.ObjectConfiguration;
_pixiContainer: PIXI.Container;
_pixiResourcesLoader: PixiResourcesLoader;
_pixiObject: PIXI.DisplayObject;
wasUsed: boolean;
constructor(
project: gd.Project,
layout: gd.Layout,
instance: gd.InitialInstance,
associatedObjectConfiguration: gd.ObjectConfiguration,
pixiContainer: PIXI.Container,
pixiResourcesLoader: PixiResourcesLoader
);
/**
* Convert an angle from degrees to radians.
*/
static toRad(angleInDegrees: number): number;
/**
* Called when the scene editor is rendered.
*/
update(): void;
getPixiObject(): PIXI.DisplayObject | null;
getInstance(): gd.InitialInstance;
/**
* Called to notify the instance renderer that its associated instance was removed from
* the scene. The PIXI object should probably be removed from the container: This is what
* the default implementation of the method does.
*/
onRemovedFromScene(): void;
getOriginX(): number;
getOriginY(): number;
getCenterX(): number;
getCenterY(): number;
getCustomWidth(): number;
getCustomHeight(): number;
getWidth(): number;
getHeight(): number;
getDepth(): number;
/**
* Return the width of the instance when the instance doesn't have a custom size.
*/
getDefaultWidth(): number;
/**
* Return the height of the instance when the instance doesn't have a custom size.
*/
getDefaultHeight(): number;
getDefaultDepth(): number;
}
/**
* Rendered3DInstance is the base class used for creating 3D renderers of instances,
* which display on the scene editor, using Three.js, the instance of an object (see InstancesEditor).
* It can also display 2D artifacts on Pixi 2D plane (3D object shadow projected on the plane for instance).
*/
class Rendered3DInstance {
_project: gd.Project;
_layout: gd.Layout;
_instance: gd.InitialInstance;
_associatedObjectConfiguration: gd.ObjectConfiguration;
_pixiContainer: PIXI.Container;
_threeGroup: THREE.Group;
_pixiResourcesLoader: PixiResourcesLoader;
_pixiObject: PIXI.DisplayObject;
_threeObject: THREE.Object3D | null;
wasUsed: boolean;
constructor(
project: gd.Project,
layout: gd.Layout,
instance: gd.InitialInstance,
associatedObjectConfiguration: gd.ObjectConfiguration,
pixiContainer: PIXI.Container,
threeGroup: THREE.Group,
pixiResourcesLoader: PixiResourcesLoader
);
/**
* Convert an angle from degrees to radians.
*/
static toRad(angleInDegrees: number): number;
/**
* Applies ratio to value without intermediary value to avoid precision issues.
*/
static applyRatio({
oldReferenceValue,
newReferenceValue,
valueToApplyTo,
}: {
oldReferenceValue: number;
newReferenceValue: number;
valueToApplyTo: number;
}): number;
/**
* Called when the scene editor is rendered.
*/
update(): void;
getPixiObject(): PIXI.DisplayObject;
getThreeObject(): THREE.Object3D;
getInstance(): gd.InitialInstance;
/**
* Called to notify the instance renderer that its associated instance was removed from
* the scene. The PIXI object should probably be removed from the container: This is what
* the default implementation of the method does.
*/
onRemovedFromScene(): void;
getOriginX(): number;
getOriginY(): number;
getCenterX(): number;
getCenterY(): number;
getWidth(): number;
getHeight(): number;
getDepth(): number;
/**
* Return the width of the instance when the instance doesn't have a custom size.
*/
getDefaultWidth(): number;
/**
* Return the height of the instance when the instance doesn't have a custom size.
*/
getDefaultHeight(): number;
/**
* Return the depth of the instance when the instance doesn't have a custom size.
*/
getDefaultDepth(): number;
}
declare type ObjectsRenderingService = {
gd: GDNamespace;
PIXI: PIXI;
THREE: typeof import('../newIDE/app/node_modules/three');
THREE_ADDONS: { SkeletonUtils: any };
RenderedInstance: typeof RenderedInstance;
Rendered3DInstance: typeof Rendered3DInstance;
registerInstanceRenderer: (objectType: string, renderer: any) => void;
registerInstance3DRenderer: (objectType: string, renderer: any) => void;
requireModule: (dirname: string, moduleName: string) => any;
getThumbnail: (
project: gd.Project,
objectConfiguration: gd.ObjectConfiguration
) => string;
rgbOrHexToHexNumber: (value: string) => number;
registerClearCache: (clearCache: (_: any) => void) => void;
};
declare type ObjectsEditorService = {
registerEditorConfiguration: (
objectType: string,
editorConfiguration: any
) => void;
getDefaultObjectJsImplementationPropertiesEditor: ({
helpPagePath: string,
}) => any;
};
declare type ExtensionModule = {
createExtension: (
_: (string) => string,
gd: GDNamespace
) => gd.PlatformExtension;
/**
* You can optionally add sanity tests that will check the basic working
* of your extension behaviors/objects by instantiating behaviors/objects
* and setting the property to a given value.
*
* If you don't have any tests, you can simply return an empty array.
*
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: (
gd: GDNamespace,
extension: gd.PlatformExtension
) => string[];
/**
* Register editors for objects.
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations?: (
objectsEditorService: ObjectsEditorService
) => void;
/**
* Register renderers for instance of objects on the scene editor.
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers?: (
objectsRenderingService: ObjectsRenderingService
) => void;
};

View File

@@ -1,35 +0,0 @@
// @flow
/**
* @file This file contains the (Flow) types that are used in the JavaScript
* extensions declaration (i.e: JsExtension.js files).
* Extension runtime files are in TypeScript (ts files) and not using Flow.
*
* If you do changes here, run `node import-GDJS-Runtime.js` (in newIDE/app/scripts),
* and be sure that the types declared here are reflecting the types exposed by the editor.
*
* Note that Flow comments are used to avoid having to preprocess this file and the
* JsExtension.js files through Babel. This allows to keep plain JS files, while allowing
* Flow static type checking to be run on them when integrated in the editor.
*/
/*::
export type ObjectsRenderingService = {
gd: libGDevelop,
PIXI: any,
THREE: any,
THREE_ADDONS: {SkeletonUtils: any},
RenderedInstance: any,
Rendered3DInstance: any,
registerInstanceRenderer: (objectType: string, renderer: any) => void,
registerInstance3DRenderer: (objectType: string, renderer: any) => void,
requireModule: (dirname: string, moduleName: string) => any,
getThumbnail: (project: gdProject, objectConfiguration: gdObjectConfiguration) => string,
rgbOrHexToHexNumber: (value: string) => number,
registerClearCache: (clearCache: any => void) => void,
};
export type ObjectsEditorService = {
registerEditorConfiguration: (objectType: string, editorConfiguration: any) => void,
getDefaultObjectJsImplementationPropertiesEditor: ({| helpPagePath: string |}) => any,
};
*/

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -42,7 +34,9 @@ module.exports = {
.addAction(
'SavePlayerScore',
_('Save player score'),
_("Save the player's score to the given leaderboard."),
_(
"Save the player's score to the given leaderboard. If the player is connected, the score will be attached to the connected player (unless disabled)."
),
_(
'Send to leaderboard _PARAM1_ the score _PARAM2_ with player name: _PARAM3_'
),
@@ -68,6 +62,12 @@ module.exports = {
.getCodeExtraInformation()
.setIncludeFile('Extensions/Leaderboards/sha256.js')
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.evtTools.leaderboards.savePlayerScore')
.setAsyncFunctionName('gdjs.evtTools.leaderboards.savePlayerScore');
@@ -95,8 +95,37 @@ module.exports = {
.getCodeExtraInformation()
.setIncludeFile('Extensions/Leaderboards/sha256.js')
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.evtTools.leaderboards.saveConnectedPlayerScore')
.setAsyncFunctionName('gdjs.evtTools.leaderboards.saveConnectedPlayerScore');
.setAsyncFunctionName(
'gdjs.evtTools.leaderboards.saveConnectedPlayerScore'
);
extension
.addAction(
'SetPreferSendConnectedPlayerScore',
_('Always attach scores to the connected player'),
_(
'Set if the score sent to a leaderboard is always attached to the connected player - if any. This is on by default.'
),
_('Always attach the score to the connected player: _PARAM1_'),
_('Setup'),
'JsPlatform/Extensions/leaderboard.svg',
'JsPlatform/Extensions/leaderboard.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('yesorno', _('Enable?'), '', false)
.setHelpPath('/all-features/leaderboards')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
.setFunctionName(
'gdjs.evtTools.leaderboards.setPreferSendConnectedPlayerScore'
);
extension
.addCondition(
@@ -279,6 +308,12 @@ module.exports = {
.setHelpPath('/all-features/leaderboards')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.evtTools.leaderboards.displayLeaderboard');
extension
@@ -299,10 +334,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -6,6 +6,7 @@ namespace gdjs {
export namespace evtTools {
export namespace leaderboards {
let _hasPlayerJustClosedLeaderboardView = false;
let _preferSendConnectedPlayerScore = true;
gdjs.registerRuntimeScenePostEventsCallback(() => {
// Set it back to false for the next frame.
@@ -24,6 +25,14 @@ namespace gdjs {
return shaObj.getHash('B64');
};
const leaderboardHostBaseUrl = 'https://gd.games';
// const leaderboardHostBaseUrl = 'http://localhost:4000';
type PublicLeaderboardEntry = {
id: string;
claimSecret?: string;
};
/**
* Hold the state of the save of a score for a leaderboard.
*/
@@ -45,7 +54,7 @@ namespace gdjs {
private _lastSavedPlayerId: string | null = null;
/** The id of the entry in the leaderboard, for the last score saved with success. */
lastSavedLeaderboardEntryId: string | null = null;
lastSavedLeaderboardEntry: PublicLeaderboardEntry | null = null;
/** Last error that happened when saving the score (useful if `hasScoreSavingErrored` is true). */
lastSaveError: string | null = null;
@@ -114,7 +123,7 @@ namespace gdjs {
playerId?: string;
score: number;
}): {
closeSaving: (leaderboardEntryId: string | null) => void;
closeSaving: (leaderboardEntry: PublicLeaderboardEntry) => void;
closeSavingWithError(errorCode: string);
} {
if (this._isAlreadySavingThisScore({ playerName, playerId, score })) {
@@ -159,7 +168,7 @@ namespace gdjs {
if (playerId) this._currentlySavingPlayerId = playerId;
return {
closeSaving: (leaderboardEntryId) => {
closeSaving: (leaderboardEntry) => {
if (savingPromise !== this.lastSavingPromise) {
logger.info(
'Score saving result received, but another save was launched in the meantime - ignoring the result of this one.'
@@ -174,7 +183,7 @@ namespace gdjs {
this._lastSavedScore = this._currentlySavingScore;
this._lastSavedPlayerName = this._currentlySavingPlayerName;
this._lastSavedPlayerId = this._currentlySavingPlayerId;
this.lastSavedLeaderboardEntryId = leaderboardEntryId;
this.lastSavedLeaderboardEntry = leaderboardEntry;
this.hasScoreBeenSaved = true;
resolveSavingPromise();
@@ -214,7 +223,7 @@ namespace gdjs {
let _leaderboardViewIframeLoading: boolean = false;
let _leaderboardViewIframeLoaded: boolean = false;
let _errorTimeoutId: NodeJS.Timeout | null = null;
let _leaderboardViewClosingCallback:
let _leaderboardMessageListener:
| ((event: MessageEvent) => void)
| null = null;
@@ -288,7 +297,7 @@ namespace gdjs {
authenticatedPlayerData?: { playerId: string; playerToken: string };
score: number;
runtimeScene: gdjs.RuntimeScene;
}) {
}): Promise<PublicLeaderboardEntry> {
const rootApi = runtimeScene
.getGame()
.isUsingGDevelopDevelopmentEnvironment()
@@ -339,18 +348,18 @@ namespace gdjs {
throw errorCode;
}
let leaderboardEntryId: string | null = null;
try {
const leaderboardEntry = await response.json();
leaderboardEntryId = leaderboardEntry.id;
return leaderboardEntry;
} catch (error) {
logger.warn(
'An error occurred when reading response but score has been saved:',
error
);
}
return leaderboardEntryId;
const errorCode = 'SAVED_ENTRY_CANT_BE_READ';
throw errorCode;
}
} catch (error) {
logger.error('Error while submitting a leaderboard score:', error);
const errorCode = 'REQUEST_NOT_SENT';
@@ -359,13 +368,27 @@ namespace gdjs {
}
};
export const setPreferSendConnectedPlayerScore = (
runtimeScene: gdjs.RuntimeScene,
enable: boolean
) => {
_preferSendConnectedPlayerScore = enable;
};
export const savePlayerScore = (
runtimeScene: gdjs.RuntimeScene,
leaderboardId: string,
score: float,
playerName: string
) =>
new gdjs.PromiseTask(
) => {
if (
_preferSendConnectedPlayerScore &&
gdjs.playerAuthentication.isAuthenticated()
) {
return saveConnectedPlayerScore(runtimeScene, leaderboardId, score);
}
return new gdjs.PromiseTask(
(async () => {
const scoreSavingState = (_scoreSavingStateByLeaderboard[
leaderboardId
@@ -380,13 +403,13 @@ namespace gdjs {
} = scoreSavingState.startSaving({ playerName, score });
try {
const leaderboardEntryId = await saveScore({
const leaderboardEntry = await saveScore({
leaderboardId,
playerName,
score,
runtimeScene,
});
closeSaving(leaderboardEntryId);
closeSaving(leaderboardEntry);
} catch (errorCode) {
closeSavingWithError(errorCode);
}
@@ -395,6 +418,7 @@ namespace gdjs {
}
})()
);
};
export const saveConnectedPlayerScore = (
runtimeScene: gdjs.RuntimeScene,
@@ -551,7 +575,64 @@ namespace gdjs {
displayLoader: boolean,
event: MessageEvent
) {
switch (event.data) {
const messageId =
typeof event.data === 'string' ? event.data : event.data.id;
switch (messageId) {
case 'playerAuthenticated':
gdjs.playerAuthentication.login({
runtimeScene,
userId: event.data.userId,
username: event.data.username,
userToken: event.data.userToken,
});
break;
case 'openPlayerAuthentication':
gdjs.playerAuthentication
.openAuthenticationWindow(runtimeScene)
.promise.then(({ status }) => {
if (
!_leaderboardViewIframe ||
!_leaderboardViewIframe.contentWindow
) {
logger.warn(
'Unable to transmit the new login status to the leaderboard view.'
);
return;
}
if (status === 'errored') {
_leaderboardViewIframe.contentWindow.postMessage(
{
id: 'onPlayerAuthenticationErrored',
},
leaderboardHostBaseUrl
);
return;
}
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (status === 'dismissed' || !playerId || !playerToken) {
_leaderboardViewIframe.contentWindow.postMessage(
{
id: 'onPlayerAuthenticationDismissed',
},
leaderboardHostBaseUrl
);
return;
}
_leaderboardViewIframe.contentWindow.postMessage(
{
id: 'onPlayerAuthenticated',
playerId,
playerUsername: gdjs.playerAuthentication.getUsername(),
playerToken: playerToken,
},
leaderboardHostBaseUrl
);
});
break;
case 'closeLeaderboardView':
_hasPlayerJustClosedLeaderboardView = true;
closeLeaderboardView(runtimeScene);
@@ -599,7 +680,7 @@ namespace gdjs {
'Leaderboard page did not send message in time. Closing leaderboard view.'
);
}
}, 5000);
}, 15000);
};
const displayLoaderInLeaderboardView = function (
@@ -701,15 +782,15 @@ namespace gdjs {
});
}
// If a save is being done for this leaderboard, wait for it to end so that the `lastSavedLeaderboardEntryId`
// If a save is being done for this leaderboard, wait for it to end so that the `lastSavedLeaderboardEntry`
// can be saved and then used to show the player score.
const scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
if (scoreSavingState && scoreSavingState.lastSavingPromise) {
await scoreSavingState.lastSavingPromise;
}
const lastSavedLeaderboardEntryId = scoreSavingState
? scoreSavingState.lastSavedLeaderboardEntryId
const lastSavedLeaderboardEntry = scoreSavingState
? scoreSavingState.lastSavedLeaderboardEntry
: null;
const gameId = gdjs.projectData.properties.projectUuid;
@@ -720,13 +801,30 @@ namespace gdjs {
const searchParams = new URLSearchParams();
searchParams.set('inGameEmbedded', 'true');
if (isDev) searchParams.set('dev', 'true');
if (lastSavedLeaderboardEntryId)
if (lastSavedLeaderboardEntry) {
searchParams.set(
'playerLeaderboardEntryId',
lastSavedLeaderboardEntryId
lastSavedLeaderboardEntry.id
);
if (lastSavedLeaderboardEntry.claimSecret) {
searchParams.set(
'playerLeaderboardEntryClaimSecret',
lastSavedLeaderboardEntry.claimSecret
);
}
}
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (playerId && playerToken) {
searchParams.set('playerId', playerId);
searchParams.set('playerToken', playerToken);
searchParams.set(
'playerUsername',
gdjs.playerAuthentication.getUsername()
);
}
const targetUrl = `https://gd.games/games/${gameId}/leaderboard/${leaderboardId}?${searchParams}`;
const targetUrl = `${leaderboardHostBaseUrl}/games/${gameId}/leaderboard/${leaderboardId}?${searchParams}`;
try {
const isAvailable = await checkLeaderboardAvailability(targetUrl);
@@ -772,7 +870,7 @@ namespace gdjs {
targetUrl
);
if (typeof window !== 'undefined') {
_leaderboardViewClosingCallback = (event: MessageEvent) => {
_leaderboardMessageListener = (event: MessageEvent) => {
receiveMessageFromLeaderboardView(
runtimeScene,
displayLoader,
@@ -781,7 +879,7 @@ namespace gdjs {
};
(window as any).addEventListener(
'message',
_leaderboardViewClosingCallback,
_leaderboardMessageListener,
true
);
}
@@ -836,10 +934,10 @@ namespace gdjs {
if (typeof window !== 'undefined') {
(window as any).removeEventListener(
'message',
_leaderboardViewClosingCallback,
_leaderboardMessageListener,
true
);
_leaderboardViewClosingCallback = null;
_leaderboardMessageListener = null;
}
domElementContainer.removeChild(_leaderboardViewIframe);
_leaderboardViewIframe = null;

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,32 +13,23 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension.setExtensionInformation(
'Lighting',
_('Lights'),
extension
.setExtensionInformation(
'Lighting',
_('Lights'),
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
'Harsimran Virk',
'MIT'
)
.setCategory('Visual effect')
.setTags("light");
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
'Harsimran Virk',
'MIT'
)
.setCategory('Visual effect')
.setTags('light');
const lightObstacleBehavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.updateProperty = function (
behaviorContent,
propertyName,
@@ -46,14 +38,12 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.getProperties = function (behaviorContent) {
const behaviorProperties = new gd.MapStringPropertyDescriptor();
return behaviorProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.initializeContent = function (behaviorContent) {};
extension
.addBehavior(
@@ -66,6 +56,7 @@ module.exports = {
'',
'CppPlatform/Extensions/lightObstacleIcon32.png',
'LightObstacleBehavior',
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
lightObstacleBehavior,
new gd.BehaviorsSharedData()
)
@@ -77,7 +68,6 @@ module.exports = {
const lightObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.updateProperty = function (
objectContent,
propertyName,
@@ -106,7 +96,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -160,7 +149,6 @@ module.exports = {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -172,7 +160,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.getInitialInstanceProperties = function (
content,
instance,
@@ -233,16 +220,11 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {
registerEditorConfigurations: function (objectsEditorService) {
objectsEditorService.registerEditorConfiguration(
'Lighting::LightObject',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
@@ -255,9 +237,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;
@@ -283,32 +263,34 @@ module.exports = {
);
this._radius = parseFloat(
this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('radius')
.getValue()
);
if (this._radius <= 0) this._radius = 1;
const color = objectsRenderingService.rgbOrHexToHexNumber(
this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('color')
.getValue()
);
// The icon in the middle.
const lightIconSprite = new PIXI.Sprite(PIXI.Texture.from('CppPlatform/Extensions/lightIcon32.png'));
const lightIconSprite = new PIXI.Sprite(
PIXI.Texture.from('CppPlatform/Extensions/lightIcon32.png')
);
lightIconSprite.anchor.x = 0.5;
lightIconSprite.anchor.y = 0.5;
// The circle to show the radius of the light.
const radiusBorderWidth = 2;
const radiusGraphics = new PIXI.Graphics();
radiusGraphics.lineStyle(
radiusBorderWidth,
color,
0.8
radiusGraphics.lineStyle(radiusBorderWidth, color, 0.8);
radiusGraphics.drawCircle(
0,
0,
Math.max(1, this._radius - radiusBorderWidth)
);
radiusGraphics.drawCircle(0, 0, Math.max(1, this._radius - radiusBorderWidth));
this._pixiObject = new PIXI.Container();
this._pixiObject.addChild(lightIconSprite);
@@ -326,11 +308,7 @@ module.exports = {
/**
* Return the path to the thumbnail of the specified object.
*/
static getThumbnail(
project,
resourcesLoader,
objectConfiguration
) {
static getThumbnail(project, resourcesLoader, objectConfiguration) {
return 'CppPlatform/Extensions/lightIcon32.png';
}

View File

@@ -3,6 +3,7 @@ GDevelop - LinkedObjects Extension
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
const logger = new gdjs.Logger('LinkedObjects');
/**
* Manages the links between objects.
*/
@@ -114,7 +115,14 @@ namespace gdjs {
if (this._links.has(linkedObject.id)) {
const otherObjList = this._links
.get(linkedObject.id)!
.linkedObjectMap.get(removedObject.getName())!;
.linkedObjectMap.get(removedObject.getName());
if (!otherObjList) {
logger.error(
`Can't find link from ${linkedObject.id} (${linkedObject.name}) to ${removedObject.id} (${removedObject.name})`
);
return;
}
const index = otherObjList.indexOf(removedObject);
if (index !== -1) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -422,6 +422,17 @@ namespace gdjs {
});
};
/**
* Forces the usage of a relay (TURN) server, to avoid sharing IP addresses with the other peers.
* @param shouldUseRelayServer Whether relay-only should be enabled or disabled.
*/
export const forceUseRelayServer = (shouldUseRelayServer: boolean) => {
peerConfig.config = peerConfig.config || {};
peerConfig.config.iceTransportPolicy = shouldUseRelayServer
? 'relay'
: 'all';
};
/**
* Overrides the default peer ID. Must be called before connecting to a broker.
* Overriding the ID may have unwanted consequences. Do not use this feature

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension /*: gdPlatformExtension */ = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -182,6 +174,33 @@ module.exports = {
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.useCustomICECandidate');
extension
.addAction(
'ForceRelayServer',
_('Disable IP address sharing'),
_(
'Disables the sharing of IP addresses with the other peers. ' +
'This action needs to be called BEFORE connecting to the broker server.'
),
_('Disable IP sharing: _PARAM0_'),
'',
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter(
'yesorno',
_('Disable sharing of IP addresses'),
'Generally, it is recommended to keep sharing of IP addressed enabled ' +
'to make connections faster and more often possible. ' +
'Disabling IP address sharing will force all connections to pass messages through a ' +
'TURN relay server, you can make P2P use one by adding one as an ICE candidate.',
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.forceUseRelayServer');
extension
.addAction(
'UseDefaultBroker',
@@ -474,10 +493,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -68,7 +68,10 @@ namespace gdjs {
max: objectData.particleLifeTimeMax,
},
// A negative flow is "infinite flow" (all particles burst)
frequency: objectData.flow < 0 ? 0.0001 : 1.0 / objectData.flow,
frequency:
objectData.flow < 0
? ParticleEmitterObjectPixiRenderer.frequencyMinimumValue
: 1.0 / objectData.flow,
spawnChance: 1,
particlesPerWave: objectData.flow < 0 ? objectData.maxParticleNb : 1,
maxParticles: objectData.maxParticleNb,
@@ -347,7 +350,10 @@ namespace gdjs {
}
setFlow(flow: number, tank: number): void {
this.emitter.frequency = flow < 0 ? 0.0001 : 1.0 / flow;
this.emitter.frequency =
flow < 0
? ParticleEmitterObjectPixiRenderer.frequencyMinimumValue
: 1.0 / flow;
this.emitter.emitterLifetime = ParticleEmitterObjectPixiRenderer.computeLifetime(
flow,
tank
@@ -420,11 +426,33 @@ namespace gdjs {
return this.started;
}
/**
* @returns `true` at the end of emission or at the start if it's paused.
* Returns false if there is no limit.
*/
_mayHaveEndedEmission(): boolean {
return (
// No end can be reached if there is no flow.
this.emitter.frequency >
ParticleEmitterObjectPixiRenderer.frequencyMinimumValue &&
// No end can be reached when there is no limit.
this.emitter.emitterLifetime >= 0 &&
// Pixi stops the emission at the end.
!this.emitter.emit &&
// Pixi reset `_emitterLife` to `emitterLifetime` at the end of emission
// so there is no way to know if it is the end or the start.
// @ts-ignore Use a private attribute.
this.emitter._emitterLife === this.emitter.emitterLifetime
);
}
static computeLifetime(flow: number, tank: number): float {
if (tank < 0) return -1;
else if (flow < 0) return 0.001;
else return (tank + 0.1) / flow;
}
private static readonly frequencyMinimumValue = 0.0001;
}
// @ts-ignore - Register the class to let the engine use it.

View File

@@ -113,6 +113,11 @@ namespace gdjs {
_additiveRenderingDirty: boolean = true;
// Don't mark texture as dirty if not using one.
_textureDirty: boolean;
/**
* `true` only when the emission is paused by events.
* It allows to tell the end of emission apart from it.
*/
_isEmissionPaused: boolean = false;
// @ts-ignore
_renderer: gdjs.ParticleEmitterObjectRenderer;
@@ -390,9 +395,11 @@ namespace gdjs {
this._additiveRenderingDirty = this._maxParticlesCountDirty = this._particleRotationSpeedDirty = false;
this._renderer.update(this.getElapsedTime() / 1000.0);
if (
this._renderer.hasStarted() &&
this.destroyWhenNoParticles &&
this.getParticleCount() === 0 &&
this.destroyWhenNoParticles
this._renderer.hasStarted() &&
!this._isEmissionPaused &&
this._renderer._mayHaveEndedEmission()
) {
this.deleteFromScene(instanceContainer);
}
@@ -803,10 +810,12 @@ namespace gdjs {
}
startEmission(): void {
this._isEmissionPaused = false;
this._renderer.start();
}
stopEmission(): void {
this._isEmissionPaused = true;
this._renderer.stop();
}

View File

@@ -37,6 +37,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
.SetValue(behaviorContent.GetBoolAttribute("allowDiagonals") ? "true"
: "false")
.SetGroup(_("Path smoothing"))
.SetAdvanced()
.SetType("Boolean");
properties["Acceleration"]
.SetLabel(_("Acceleration"))
@@ -99,6 +100,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
properties["ExtraBorder"]
.SetDescription(_("Extra border size"))
.SetGroup(_("Collision"))
.SetAdvanced()
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetValue(
@@ -108,6 +110,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
.SetValue(gd::String::From(
behaviorContent.GetDoubleAttribute("smoothingMaxCellGap")))
.SetGroup(_("Path smoothing"))
.SetAdvanced()
.SetDescription(_("It's recommended to leave a max gap of 1 cell. "
"Setting it to 0 disable the smoothing."));

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -35,13 +27,12 @@ module.exports = {
)
.setExtensionHelpPath('/behaviors/physics2')
.setCategory('Movement')
.setTags("physics, gravity, obstacle, collision");
.setTags('physics, gravity, obstacle, collision');
extension
.addInstructionOrExpressionGroupMetadata(_('Physics Engine 2.0'))
.setIcon('res/physics32.png');
var physics2Behavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
physics2Behavior.updateProperty = function (
behaviorContent,
propertyName,
@@ -51,104 +42,138 @@ module.exports = {
behaviorContent.getChild('bodyType').setStringValue(newValue);
return true;
}
if (propertyName === 'bullet') {
behaviorContent.getChild('bullet').setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'fixedRotation') {
behaviorContent
.getChild('fixedRotation')
.setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'canSleep') {
behaviorContent.getChild('canSleep').setBoolValue(newValue === '1');
return true;
}
if (propertyName === 'shape') {
behaviorContent.getChild('shape').setStringValue(newValue);
return true;
}
if (propertyName === 'shapeDimensionA') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('shapeDimensionA').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('shapeDimensionA')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'shapeDimensionB') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('shapeDimensionB').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('shapeDimensionB')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'shapeOffsetX') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('shapeOffsetX').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('shapeOffsetX')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'shapeOffsetY') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('shapeOffsetY').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('shapeOffsetY')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'polygonOrigin') {
behaviorContent.addChild('polygonOrigin').setStringValue(newValue);
return true;
}
if (propertyName === 'vertices') {
behaviorContent.addChild('vertices');
// $FlowFixMe
behaviorContent.setChild('vertices', gd.Serializer.fromJSON(newValue));
return true;
}
if (propertyName === 'density') {
behaviorContent
.getChild('density')
.setDoubleValue(parseFloat(newValue));
return true;
}
if (propertyName === 'friction') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('friction').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent.getChild('friction').setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'restitution') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('restitution').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('restitution')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'linearDamping') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('linearDamping').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('linearDamping')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'angularDamping') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('angularDamping').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('angularDamping')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'gravityScale') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.getChild('gravityScale').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
behaviorContent
.getChild('gravityScale')
.setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'layers') {
behaviorContent.getChild('layers').setIntValue(parseInt(newValue, 10));
return true;
}
if (propertyName === 'masks') {
behaviorContent.getChild('masks').setIntValue(parseInt(newValue, 10));
return true;
}
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
physics2Behavior.getProperties = function (behaviorContent) {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
@@ -312,7 +337,6 @@ module.exports = {
return behaviorProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
physics2Behavior.initializeContent = function (behaviorContent) {
behaviorContent.addChild('bodyType').setStringValue('Dynamic');
behaviorContent.addChild('bullet').setBoolValue(false);
@@ -336,40 +360,41 @@ module.exports = {
};
var sharedData = new gd.BehaviorSharedDataJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.updateProperty = function (
sharedContent,
propertyName,
newValue
) {
if (propertyName === 'gravityX') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
sharedContent.getChild('gravityX').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent.getChild('gravityX').setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'gravityY') {
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
sharedContent.getChild('gravityY').setDoubleValue(newValue);
const newValueAsNumber = parseFloat(newValue);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent.getChild('gravityY').setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'scaleX') {
newValue = parseInt(newValue, 10);
if (newValue !== newValue) return false;
sharedContent.getChild('scaleX').setDoubleValue(newValue);
const newValueAsNumber = parseInt(newValue, 10);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent.getChild('scaleX').setDoubleValue(newValueAsNumber);
return true;
}
if (propertyName === 'scaleY') {
newValue = parseInt(newValue, 10);
if (newValue !== newValue) return false;
sharedContent.getChild('scaleY').setDoubleValue(newValue);
const newValueAsNumber = parseInt(newValue, 10);
if (newValueAsNumber !== newValueAsNumber) return false;
sharedContent.getChild('scaleY').setDoubleValue(newValueAsNumber);
return true;
}
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.getProperties = function (sharedContent) {
var sharedProperties = new gd.MapStringPropertyDescriptor();
@@ -402,7 +427,6 @@ module.exports = {
return sharedProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.initializeContent = function (behaviorContent) {
behaviorContent.addChild('gravityX').setDoubleValue(0);
behaviorContent.addChild('gravityY').setDoubleValue(9.8);
@@ -422,6 +446,7 @@ module.exports = {
'',
'res/physics32.png',
'Physics2Behavior',
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
physics2Behavior,
sharedData
)
@@ -561,7 +586,8 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Time scale (1 by default)'))
.getCodeExtraInformation()
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.setFunctionName('gdjs.physics2.setTimeScale');
aut
@@ -775,10 +801,10 @@ module.exports = {
.setDefaultValue('true')
.getCodeExtraInformation()
.setFunctionName('setSleepingAllowed');
// Deprecated action (fixed typo):
aut
.addDuplicatedAction("SetSleepingaAllowed", "SetSleepingAllowed")
.addDuplicatedAction('SetSleepingaAllowed', 'SetSleepingAllowed')
.setHidden();
aut
@@ -1390,6 +1416,26 @@ module.exports = {
.getCodeExtraInformation()
.setFunctionName('getLinearVelocityAngle');
aut
.addAction(
'LinearVelocityAngle',
_('Linear velocity towards an angle'),
_('Set the linear velocity towards an angle.'),
_(
'Set the linear velocity of _PARAM0_ towards angle: _PARAM2_ degrees, speed: _PARAM3_ pixels per second'
),
_('Velocity'),
'res/physics32.png',
'res/physics32.png'
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angle'))
.addParameter('expression', _('Speed (in pixels per second)'))
.getCodeExtraInformation()
.setFunctionName('setLinearVelocityAngle')
.setGetter('getLinearVelocityAngle');
aut
.addExpression(
'LinearVelocityAngle',
@@ -1476,10 +1522,16 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('X component (N)'))
.addParameter('expression', _('Y component (N)'))
.setParameterLongDescription(_('A force is like an acceleration but depends on the mass.'))
.setParameterLongDescription(
_('A force is like an acceleration but depends on the mass.')
)
.addParameter('expression', _('Application point on X axis'))
.addParameter('expression', _('Application point on Y axis'))
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
.setParameterLongDescription(
_(
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyForce');
@@ -1499,10 +1551,16 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angle'))
.addParameter('expression', _('Length (N)'))
.setParameterLongDescription(_('A force is like an acceleration but depends on the mass.'))
.setParameterLongDescription(
_('A force is like an acceleration but depends on the mass.')
)
.addParameter('expression', _('Application point on X axis'))
.addParameter('expression', _('Application point on Y axis'))
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
.setParameterLongDescription(
_(
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyPolarForce');
@@ -1523,12 +1581,18 @@ module.exports = {
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Length (N)'))
.setParameterLongDescription(_('A force is like an acceleration but depends on the mass.'))
.setParameterLongDescription(
_('A force is like an acceleration but depends on the mass.')
)
.addParameter('expression', _('X position'))
.addParameter('expression', _('Y position'))
.addParameter('expression', _('Application point on X axis'))
.addParameter('expression', _('Application point on Y axis'))
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
.setParameterLongDescription(
_(
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyForceTowardPosition');
@@ -1546,18 +1610,18 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter(
'expression',
_('X component (N·s or kg·m·s⁻¹)')
.addParameter('expression', _('X component (N·s or kg·m·s⁻¹)'))
.addParameter('expression', _('Y component (N·s or kg·m·s⁻¹)'))
.setParameterLongDescription(
_('An impulse is like a speed addition but depends on the mass.')
)
.addParameter(
'expression',
_('Y component (N·s or kg·m·s⁻¹)')
)
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
.addParameter('expression', _('Application point on X axis'))
.addParameter('expression', _('Application point on Y axis'))
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
.setParameterLongDescription(
_(
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyImpulse');
@@ -1578,14 +1642,17 @@ module.exports = {
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angle'))
.addParameter(
'expression',
_('Length (N·s or kg·m·s⁻¹)')
.addParameter('expression', _('Length (N·s or kg·m·s⁻¹)'))
.setParameterLongDescription(
_('An impulse is like a speed addition but depends on the mass.')
)
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
.addParameter('expression', _('Application point on X axis'))
.addParameter('expression', _('Application point on Y axis'))
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
.setParameterLongDescription(
_(
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyPolarImpulse');
@@ -1605,16 +1672,19 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter(
'expression',
_('Length (N·s or kg·m·s⁻¹)')
.addParameter('expression', _('Length (N·s or kg·m·s⁻¹)'))
.setParameterLongDescription(
_('An impulse is like a speed addition but depends on the mass.')
)
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
.addParameter('expression', _('X position'))
.addParameter('expression', _('Y position'))
.addParameter('expression', _('Application point on X axis'))
.addParameter('expression', _('Application point on Y axis'))
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
.setParameterLongDescription(
_(
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyImpulseTowardPosition');
@@ -1633,7 +1703,9 @@ module.exports = {
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Torque (N·m)'))
.setParameterLongDescription(_('A torque is like a rotation acceleration but depends on the mass.'))
.setParameterLongDescription(
_('A torque is like a rotation acceleration but depends on the mass.')
)
.getCodeExtraInformation()
.setFunctionName('applyTorque');
@@ -1652,7 +1724,11 @@ module.exports = {
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angular impulse (N·m·s'))
.setParameterLongDescription(_('An impulse is like a rotation speed addition but depends on the mass.'))
.setParameterLongDescription(
_(
'An impulse is like a rotation speed addition but depends on the mass.'
)
)
.getCodeExtraInformation()
.setFunctionName('applyAngularImpulse');
@@ -4019,7 +4095,8 @@ module.exports = {
.addParameter('objectList', _('Object'), '', false)
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.setFunctionName('gdjs.physics2.objectsCollide');
extension
@@ -4037,7 +4114,8 @@ module.exports = {
.addParameter('objectList', _('Object'), '', false)
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.setFunctionName('gdjs.physics2.haveObjectsStartedColliding');
extension
@@ -4055,16 +4133,14 @@ module.exports = {
.addParameter('objectList', _('Object'), '', false)
.addCodeOnlyParameter('conditionInverted', '')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.setFunctionName('gdjs.physics2.haveObjectsStoppedColliding');
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
const dummyBehavior = extension
.getBehaviorMetadata('Physics2::Physics2Behavior')
.get();

View File

@@ -1429,6 +1429,23 @@ namespace gdjs {
);
}
setLinearVelocityAngle(angle: float, linearVelocity: float): void {
// If there is no body, set a new one
if (this._body === null) {
if (!this.createBody()) return;
}
const body = this._body!;
// Set the linear velocity toward an angle
angle = gdjs.toRad(angle);
body.SetLinearVelocity(
this.b2Vec2(
linearVelocity * Math.cos(angle) * this._sharedData.invScaleX,
linearVelocity * Math.sin(angle) * this._sharedData.invScaleY
)
);
}
getAngularVelocity(): float {
// If there is no body, set a new one
if (this._body === null) {

View File

@@ -872,6 +872,5 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("objectList", _("Object"), "", false)
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.AddParameter("objectList", _("Platforms"), "", false)
.AddCodeOnlyParameter("conditionInverted", "")
.SetFunctionName("gdjs.evtTools.platform.isOnPlatform");
.AddCodeOnlyParameter("conditionInverted", "");
}

View File

@@ -41,6 +41,17 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
.AddIncludeFile(
"Extensions/PlatformBehavior/platformtools.js");
std::map<gd::String, gd::InstructionMetadata>& extConditions =
GetAllConditions();
extConditions["PlatformBehavior::IsObjectOnGivenFloor"].SetFunctionName(
"gdjs.evtTools.platform.isOnPlatform")
.AddIncludeFile(
"Extensions/PlatformBehavior/platformruntimebehavior.js")
.AddIncludeFile(
"Extensions/PlatformBehavior/platformerobjectruntimebehavior.js")
.AddIncludeFile(
"Extensions/PlatformBehavior/platformtools.js");
{
std::map<gd::String, gd::InstructionMetadata>& autActions =
GetAllActionsForBehavior(

View File

@@ -1692,13 +1692,15 @@ namespace gdjs {
beforeUpdatingObstacles(timeDelta: float) {
const object = this._behavior.owner;
//Stick the object to the floor if its height has changed.
// Stick the object to the floor if its height has changed.
if (this._oldHeight !== object.getHeight()) {
object.setY(
this._floorLastY -
object.getHeight() +
(object.getY() - object.getDrawableY())
);
// TODO This should probably be done after the events because
// the character stays at the wrong place during 1 frame.
const deltaY =
((this._oldHeight - object.getHeight()) *
(object.getHeight() + object.getDrawableY() - object.getY())) /
object.getHeight();
object.setY(object.getY() + deltaY);
}
// Directly follow the floor movement on the Y axis by moving the character.
// For the X axis, we follow the floor movement using `_requestedDeltaX`

View File

@@ -262,6 +262,9 @@ namespace gdjs {
const behavior1 = object1.getBehavior(
behaviorName
) as PlatformerObjectRuntimeBehavior;
if (!behavior1) {
return false;
}
return behavior1.isOnFloorObject(object2);
}
}

View File

@@ -501,6 +501,22 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
);
});
});
it('can stay on a rotated platform when its height changes', function () {
const platform = addPlatformObject(runtimeScene);
platform.setPosition(0, -10);
platform.setAngle(-45);
object.setPosition(30, -32);
// Ensure the object falls on the platform
fallOnPlatform(10);
const oldY = object.getY();
expect(object.getY()).to.be.within(-40, -39);
object.setHeight(object.getHeight() - 8);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getY()).to.be(oldY + 8);
});
});
[0, 25].forEach((slopeMaxAngle) => {

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -72,9 +64,7 @@ module.exports = {
.addAction(
'HideAuthenticationBanner',
_('Hide authentication banner'),
_(
'Hide the authentication banner from the top of the game screen.'
),
_('Hide the authentication banner from the top of the game screen.'),
_('Hide the authentication banner'),
'',
'JsPlatform/Extensions/authentication.svg',
@@ -110,7 +100,10 @@ module.exports = {
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.openAuthenticationWindow');
.setFunctionName('gdjs.playerAuthentication.openAuthenticationWindow')
.setAsyncFunctionName(
'gdjs.playerAuthentication.openAuthenticationWindow'
);
extension
.addCondition(
@@ -226,10 +219,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -5,18 +5,25 @@ namespace gdjs {
platform,
isGameRegistered,
}: {
platform: 'cordova' | 'electron' | 'web';
platform:
| 'cordova'
| 'cordova-websocket'
| 'electron'
| 'web-iframe'
| 'web';
isGameRegistered: boolean;
}) =>
isGameRegistered
? {
title: 'Logging in...',
text1:
platform === 'cordova'
platform === 'cordova' || platform === 'cordova-websocket'
? "One moment, we're opening a window for you to log in."
: "One moment, we're opening a new page with your web browser for you to log in.",
text2:
'If the window did not open, please check your pop-up blocker and click the button below to try again.',
platform === 'cordova' || platform === 'cordova-websocket'
? ''
: 'If the window did not open, please check your pop-up blocker and click the button below to try again.',
}
: {
title: 'Publish your game!',
@@ -166,9 +173,14 @@ namespace gdjs {
*/
export const addAuthenticationTextsToLoadingContainer = (
loaderContainer: HTMLDivElement,
platform,
isGameRegistered,
wikiOpenAction
platform:
| 'cordova'
| 'cordova-websocket'
| 'electron'
| 'web-iframe'
| 'web',
isGameRegistered: boolean,
wikiOpenAction: (() => void) | null
) => {
const textContainer: HTMLDivElement = document.createElement('div');
textContainer.id = 'authentication-container-texts';

View File

@@ -15,7 +15,7 @@ namespace gdjs {
// Authentication display
let _authenticationWindow: Window | null = null; // For Web.
let _authenticationInAppWindow: Window | null = null; // For Cordova.
let _authenticationInAppWindow: any | null = null; // For Cordova.
let _authenticationRootContainer: HTMLDivElement | null = null;
let _authenticationLoaderContainer: HTMLDivElement | null = null;
let _authenticationIframeContainer: HTMLDivElement | null = null;
@@ -28,11 +28,10 @@ namespace gdjs {
let _authenticationMessageCallback:
| ((event: MessageEvent) => void)
| null = null;
let _cordovaAuthenticationMessageCallback:
| ((event: MessageEvent) => void)
| null = null;
let _websocket: WebSocket | null = null;
type AuthenticationWindowStatus = 'logged' | 'errored' | 'dismissed';
// Ensure that the condition "just logged in" is valid only for one frame.
gdjs.registerRuntimeScenePostEventsCallback(() => {
_justLoggedIn = false;
@@ -43,13 +42,15 @@ namespace gdjs {
// Then send a message to the parent iframe to say that the player auth is ready.
gdjs.registerFirstRuntimeSceneLoadedCallback(
(runtimeScene: RuntimeScene) => {
if (getPlatform(runtimeScene) !== 'web') {
if (getPlayerAuthPlatform(runtimeScene) !== 'web') {
// Automatic authentication is only valid when the game is hosted on GDevelop games platform.
return;
}
removeAuthenticationCallbacks(); // Remove any callback that could have been registered before.
_authenticationMessageCallback = (event: MessageEvent) => {
receiveAuthenticationMessage(runtimeScene, event, {
receiveAuthenticationMessage({
runtimeScene,
event,
checkOrigin: true,
});
};
@@ -98,18 +99,40 @@ namespace gdjs {
};
/**
* Helper returning the platform.
* Get the platform running the game, which changes how the authentication
* window is opened.
*/
const getPlatform = (
const getPlayerAuthPlatform = (
runtimeScene: RuntimeScene
): 'electron' | 'cordova' | 'web' => {
): 'electron' | 'cordova' | 'cordova-websocket' | 'web-iframe' | 'web' => {
const runtimeGame = runtimeScene.getGame();
const electron = runtimeGame.getRenderer().getElectron();
if (electron) {
// This can be a:
// - Preview in GDevelop desktop app.
// - Desktop game running on Electron.
return 'electron';
}
if (typeof cordova !== 'undefined') return 'cordova';
// This can be a:
// - Preview in GDevelop mobile app (iOS only)
if (shouldAuthenticationUseIframe(runtimeScene)) return 'web-iframe';
if (typeof cordova !== 'undefined') {
if (cordova.platformId === 'ios') {
// The game is an iOS app.
return 'cordova-websocket';
}
// The game is an Android app.
return 'cordova';
}
// This can be a:
// - Preview in GDevelop web-app
// - Preview in Gdevelop mobile app (Android only)
// - Web game (gd.games or any website/server) accessed via a desktop browser...
// - Or a web game accessed via a mobile browser (Android/iOS).
return 'web';
};
@@ -117,7 +140,8 @@ namespace gdjs {
* Check if, in some exceptional cases, we allow authentication
* to be done through a iframe.
* This is usually discouraged as the user can't verify that the authentication
* window is a genuine one. It's only to be used in trusted contexts.
* window is a genuine one. It's only to be used in trusted contexts (e.g:
* preview in the GDevelop mobile app).
*/
const shouldAuthenticationUseIframe = (runtimeScene: RuntimeScene) => {
const runtimeGameOptions = runtimeScene.getGame().getAdditionalOptions();
@@ -279,7 +303,10 @@ namespace gdjs {
const cleanUpAuthWindowAndCallbacks = (runtimeScene: RuntimeScene) => {
removeAuthenticationContainer(runtimeScene);
clearAuthenticationWindowTimeout();
// If there is a websocket communication (electron, cordova iOS), close it.
if (_websocket) {
logger.info('Closing authentication websocket connection.');
_websocket.close();
_websocket = null;
}
@@ -334,16 +361,17 @@ namespace gdjs {
}
};
/**
* When the game receives the authentication result, close all the
* authentication windows, display the notification and focus on the game.
*/
const handleLoggedInEvent = function (
runtimeScene: gdjs.RuntimeScene,
userId: string,
username: string | null,
userToken: string
) {
export const login = ({
runtimeScene,
userId,
username,
userToken,
}: {
runtimeScene: gdjs.RuntimeScene;
userId: string;
username: string | null;
userToken: string;
}) => {
saveAuthKeyToStorage({ userId, username, userToken });
cleanUpAuthWindowAndCallbacks(runtimeScene);
removeAuthenticationBanner(runtimeScene);
@@ -363,18 +391,23 @@ namespace gdjs {
domElementContainer,
_username || 'Anonymous'
);
focusOnGame(runtimeScene);
};
/**
* Reads the event sent by the authentication window and
* display the appropriate banner.
*/
const receiveAuthenticationMessage = function (
runtimeScene: gdjs.RuntimeScene,
event: MessageEvent,
{ checkOrigin }: { checkOrigin: boolean }
) {
const receiveAuthenticationMessage = function ({
runtimeScene,
event,
checkOrigin,
onDone,
}: {
runtimeScene: gdjs.RuntimeScene;
event: MessageEvent;
checkOrigin: boolean;
onDone?: (status: 'logged' | 'errored' | 'dismissed') => void;
}) {
const allowedOrigins = ['https://liluo.io', 'https://gd.games'];
// Check origin of message.
@@ -394,12 +427,14 @@ namespace gdjs {
throw new Error('Malformed message.');
}
handleLoggedInEvent(
login({
runtimeScene,
event.data.body.userId,
event.data.body.username,
event.data.body.token
);
userId: event.data.body.userId,
username: event.data.body.username,
userToken: event.data.body.token,
});
focusOnGame(runtimeScene);
if (onDone) onDone('logged');
break;
}
case 'alreadyAuthenticated': {
@@ -452,7 +487,7 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene
) => {
clearAuthenticationWindowTimeout();
const time = 12 * 60 * 1000; // 12 minutes, in case the user needs time to authenticate.
const time = 15 * 60 * 1000; // 15 minutes, in case the user needs time to authenticate.
_authenticationTimeoutId = setTimeout(() => {
logger.info(
'Authentication window did not send message in time. Closing it.'
@@ -487,6 +522,7 @@ namespace gdjs {
const onOpenAuthenticationWindow = () => {
openAuthenticationWindow(runtimeScene);
};
return _userToken
? authComponents.computeAuthenticatedBanner(
onOpenAuthenticationWindow,
@@ -558,6 +594,84 @@ namespace gdjs {
);
};
const setupWebsocketForAuthenticationWindow = (
runtimeScene: gdjs.RuntimeScene,
onOpenAuthenticationWindow: (options: {
connectionId: string;
resolve: (AuthenticationWindowStatus) => void;
}) => void
) =>
new Promise<AuthenticationWindowStatus>((resolve) => {
let hasFinishedAlready = false;
const wsPlayApi = runtimeScene
.getGame()
.isUsingGDevelopDevelopmentEnvironment()
? 'wss://api-ws-dev.gdevelop.io/play'
: 'wss://api-ws.gdevelop.io/play';
_websocket = new WebSocket(wsPlayApi);
_websocket.onopen = () => {
logger.info('Opened authentication websocket connection.');
// When socket is open, ask for the connectionId, so that we can open the authentication window.
if (_websocket) {
_websocket.send(JSON.stringify({ action: 'getConnectionId' }));
}
};
_websocket.onerror = () => {
logger.info('Error in authentication websocket connection.');
if (!hasFinishedAlready) {
hasFinishedAlready = true;
resolve('errored');
}
handleAuthenticationError(
runtimeScene,
'Error while connecting to the authentication server.'
);
};
_websocket.onclose = () => {
logger.info('Closing authentication websocket connection.');
if (!hasFinishedAlready) {
hasFinishedAlready = true;
resolve('dismissed');
}
};
_websocket.onmessage = (event) => {
if (event.data) {
const messageContent = JSON.parse(event.data);
switch (messageContent.type) {
case 'authenticationResult': {
const messageData = messageContent.data;
login({
runtimeScene,
userId: messageData.userId,
username: messageData.username,
userToken: messageData.token,
});
focusOnGame(runtimeScene);
hasFinishedAlready = true;
resolve('logged');
break;
}
case 'connectionId': {
const messageData = messageContent.data;
const connectionId = messageData.connectionId;
if (!connectionId) {
logger.error('No WebSocket connectionId received');
hasFinishedAlready = true;
resolve('errored');
return;
}
logger.info('WebSocket connectionId received.');
onOpenAuthenticationWindow({ connectionId, resolve });
break;
}
}
}
};
});
/**
* Helper to handle authentication window on Electron.
* We open a new window, and create a websocket to know when the user is logged in.
@@ -565,75 +679,67 @@ namespace gdjs {
const openAuthenticationWindowForElectron = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
) => {
const wsPlayApi = runtimeScene
.getGame()
.isUsingGDevelopDevelopmentEnvironment()
? 'wss://api-ws-dev.gdevelop.io/play'
: 'wss://api-ws.gdevelop.io/play';
_websocket = new WebSocket(wsPlayApi);
_websocket.onopen = () => {
// When socket is open, ask for the connectionId, so that we can open the authentication window.
if (_websocket) {
_websocket.send(JSON.stringify({ action: 'getConnectionId' }));
}
};
_websocket.onerror = () => {
handleAuthenticationError(
runtimeScene,
'Error while connecting to the authentication server.'
);
};
_websocket.onmessage = (event) => {
if (event.data) {
const messageContent = JSON.parse(event.data);
switch (messageContent.type) {
case 'authenticationResult': {
const messageData = messageContent.data;
handleLoggedInEvent(
runtimeScene,
messageData.userId,
messageData.username,
messageData.token
);
break;
}
case 'connectionId': {
const messageData = messageContent.data;
const connectionId = messageData.connectionId;
if (!connectionId) {
logger.error('No connectionId received');
return;
}
) =>
setupWebsocketForAuthenticationWindow(
runtimeScene,
({ connectionId }) => {
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
connectionId,
});
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
connectionId,
});
const electron = runtimeScene.getGame().getRenderer().getElectron();
const openWindow = () => electron.shell.openExternal(targetUrl);
const electron = runtimeScene
.getGame()
.getRenderer()
.getElectron();
const openWindow = () => electron.shell.openExternal(targetUrl);
openWindow();
openWindow();
// Add the link to the window in case a popup blocker is preventing the window from opening.
if (_authenticationTextContainer) {
authComponents.addAuthenticationUrlToTextsContainer(
openWindow,
_authenticationTextContainer
);
}
break;
}
// Add the link to the window in case a popup blocker is preventing the window from opening.
if (_authenticationTextContainer) {
authComponents.addAuthenticationUrlToTextsContainer(
openWindow,
_authenticationTextContainer
);
}
}
};
};
);
/**
* Helper to handle authentication window on Cordova on iOS.
* We open an InAppBrowser window, and listen to the websocket to know when the user is logged in.
*/
const openAuthenticationWindowForCordovaWithWebSocket = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
) =>
setupWebsocketForAuthenticationWindow(
runtimeScene,
({ connectionId, resolve }) => {
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
connectionId,
});
_authenticationInAppWindow = cordova.InAppBrowser.open(
targetUrl,
'authentication',
'location=yes,toolbarcolor=#000000,hidenavigationbuttons=yes,closebuttoncolor=#FFFFFF' // location=yes is important to show the URL bar to the user.
);
if (!_authenticationInAppWindow) {
resolve('errored');
return;
}
_authenticationInAppWindow.addEventListener(
'exit',
() => {
resolve('dismissed');
},
true
);
}
);
/**
* Helper to handle authentication window on Cordova.
@@ -642,32 +748,52 @@ namespace gdjs {
const openAuthenticationWindowForCordova = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
) => {
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
});
) =>
new Promise<AuthenticationWindowStatus>((resolve) => {
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
});
_authenticationInAppWindow = cordova.InAppBrowser.open(
targetUrl,
'authentication',
'location=yes' // location=yes is important to show the URL bar to the user.
);
// Listen to messages posted on the authentication window, so that we can
// know when the user is authenticated.
if (_authenticationInAppWindow) {
_cordovaAuthenticationMessageCallback = (event: MessageEvent) => {
receiveAuthenticationMessage(runtimeScene, event, {
checkOrigin: false, // For Cordova we don't check the origin, as the message is read from the InAppBrowser directly.
});
};
_authenticationInAppWindow = cordova.InAppBrowser.open(
targetUrl,
'authentication',
'location=yes,toolbarcolor=#000000,hidenavigationbuttons=yes,closebuttoncolor=#FFFFFF' // location=yes is important to show the URL bar to the user.
);
if (!_authenticationInAppWindow) {
resolve('errored');
return;
}
// Listen to messages posted on the authentication window, so that we can
// know when the user is authenticated.
let isDoneAlready = false;
_authenticationInAppWindow.addEventListener(
'message',
_cordovaAuthenticationMessageCallback,
(event: MessageEvent) => {
receiveAuthenticationMessage({
runtimeScene,
event,
checkOrigin: false, // For Cordova we don't check the origin, as the message is read from the InAppBrowser directly.
onDone: (status) => {
if (isDoneAlready) return;
isDoneAlready = true;
resolve(status);
},
});
},
true
);
}
};
_authenticationInAppWindow.addEventListener(
'exit',
() => {
if (isDoneAlready) return;
isDoneAlready = true;
resolve('dismissed');
},
true
);
});
/**
* Helper to handle authentication window on web.
@@ -676,37 +802,56 @@ namespace gdjs {
const openAuthenticationWindowForWeb = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
) => {
// If we're on a browser, open a new window.
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
});
// Listen to messages posted by the authentication window, so that we can
// know when the user is authenticated.
_authenticationMessageCallback = (event: MessageEvent) => {
receiveAuthenticationMessage(runtimeScene, event, {
checkOrigin: true,
) =>
new Promise<AuthenticationWindowStatus>((resolve) => {
// If we're on a browser, open a new window.
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
});
};
window.addEventListener('message', _authenticationMessageCallback, true);
const left = screen.width / 2 - 500 / 2;
const top = screen.height / 2 - 600 / 2;
const windowFeatures = `left=${left},top=${top},width=500,height=600`;
const openWindow = () =>
window.open(targetUrl, 'authentication', windowFeatures);
_authenticationWindow = openWindow();
// Add the link to the window in case a popup blocker is preventing the window from opening.
if (_authenticationTextContainer) {
authComponents.addAuthenticationUrlToTextsContainer(
openWindow,
_authenticationTextContainer
// Listen to messages posted by the authentication window, so that we can
// know when the user is authenticated.
let isDoneAlready = false;
_authenticationMessageCallback = (event: MessageEvent) => {
receiveAuthenticationMessage({
runtimeScene,
event,
checkOrigin: true,
onDone: (status) => {
if (isDoneAlready) return;
isDoneAlready = true;
resolve(status);
},
});
};
window.addEventListener(
'message',
_authenticationMessageCallback,
true
);
}
};
const left = screen.width / 2 - 500 / 2;
const top = screen.height / 2 - 600 / 2;
const windowFeatures = `left=${left},top=${top},width=500,height=600`;
const openWindow = () => {
_authenticationWindow = window.open(
targetUrl,
'authentication',
windowFeatures
);
};
openWindow();
// Add the link to the window in case a popup blocker is preventing the window from opening.
if (_authenticationTextContainer) {
authComponents.addAuthenticationUrlToTextsContainer(
openWindow,
_authenticationTextContainer
);
}
});
/**
* Helper to handle authentication iframe on web.
@@ -715,143 +860,197 @@ namespace gdjs {
const openAuthenticationIframeForWeb = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
) => {
if (
!_authenticationIframeContainer ||
!_authenticationLoaderContainer ||
!_authenticationTextContainer
) {
console.error(
"Can't open an authentication iframe - no iframe container, loader container or text container was opened for it."
);
return;
}
) =>
new Promise<AuthenticationWindowStatus>((resolve) => {
if (
!_authenticationIframeContainer ||
!_authenticationLoaderContainer ||
!_authenticationTextContainer
) {
console.error(
"Can't open an authentication iframe - no iframe container, loader container or text container was opened for it."
);
return;
}
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
});
// Listen to messages posted by the authentication window, so that we can
// know when the user is authenticated.
_authenticationMessageCallback = (event: MessageEvent) => {
receiveAuthenticationMessage(runtimeScene, event, {
checkOrigin: true,
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
});
};
window.addEventListener('message', _authenticationMessageCallback, true);
authComponents.displayIframeInsideAuthenticationContainer(
_authenticationIframeContainer,
_authenticationLoaderContainer,
_authenticationTextContainer,
targetUrl
);
};
// Listen to messages posted by the authentication window, so that we can
// know when the user is authenticated.
_authenticationMessageCallback = (event: MessageEvent) => {
receiveAuthenticationMessage({
runtimeScene,
event,
checkOrigin: true,
onDone: resolve,
});
};
window.addEventListener(
'message',
_authenticationMessageCallback,
true
);
authComponents.displayIframeInsideAuthenticationContainer(
_authenticationIframeContainer,
_authenticationLoaderContainer,
_authenticationTextContainer,
targetUrl
);
});
/**
* Action to display the authentication window to the user.
*/
export const openAuthenticationWindow = function (
export const openAuthenticationWindow = (
runtimeScene: gdjs.RuntimeScene
) {
// Create the authentication container for the player to wait.
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication window cannot be displayed."
);
return;
}
const onAuthenticationContainerDismissed = () => {
cleanUpAuthWindowAndCallbacks(runtimeScene);
displayAuthenticationBanner(runtimeScene);
};
const _gameId = gdjs.projectData.properties.projectUuid;
if (!_gameId) {
handleAuthenticationError(
runtimeScene,
'The game ID is missing, the authentication window cannot be opened.'
);
return;
}
// If the banner is displayed, hide it, so that it can be shown again if the user closes the window.
if (_authenticationBanner) _authenticationBanner.style.opacity = '0';
const platform = getPlatform(runtimeScene);
const {
rootContainer,
loaderContainer,
iframeContainer,
} = authComponents.computeAuthenticationContainer(
onAuthenticationContainerDismissed
);
_authenticationRootContainer = rootContainer;
_authenticationLoaderContainer = loaderContainer;
_authenticationIframeContainer = iframeContainer;
// Display the authentication window right away, to show a loader
// while the call for game registration is happening.
domElementContainer.appendChild(_authenticationRootContainer);
// If the game is registered, open the authentication window.
// Otherwise, open the window indicating that the game is not registered.
checkIfGameIsRegistered(runtimeScene.getGame(), _gameId)
.then((isGameRegistered) => {
if (_authenticationLoaderContainer) {
const electron = runtimeScene.getGame().getRenderer().getElectron();
const wikiOpenAction = electron
? () =>
electron.shell.openExternal(
'https://wiki.gdevelop.io/gdevelop5/publishing/web'
)
: null; // Only show a link if we're on electron.
_authenticationTextContainer = authComponents.addAuthenticationTextsToLoadingContainer(
_authenticationLoaderContainer,
platform,
isGameRegistered,
wikiOpenAction
): gdjs.PromiseTask<{ status: 'logged' | 'errored' | 'dismissed' }> =>
new gdjs.PromiseTask(
new Promise((resolve) => {
// Create the authentication container for the player to wait.
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication window cannot be displayed."
);
resolve({ status: 'errored' });
return;
}
if (isGameRegistered) {
const _gameId = gdjs.projectData.properties.projectUuid;
if (!_gameId) {
handleAuthenticationError(
runtimeScene,
'The game ID is missing, the authentication window cannot be opened.'
);
resolve({ status: 'errored' });
return;
}
let isDimissedAlready = false;
const onAuthenticationContainerDismissed = () => {
cleanUpAuthWindowAndCallbacks(runtimeScene);
displayAuthenticationBanner(runtimeScene);
isDimissedAlready = true;
resolve({ status: 'dismissed' });
};
// If the banner is displayed, hide it, so that it can be shown again if the user closes the window.
if (_authenticationBanner) _authenticationBanner.style.opacity = '0';
const playerAuthPlatform = getPlayerAuthPlatform(runtimeScene);
const {
rootContainer,
loaderContainer,
iframeContainer,
} = authComponents.computeAuthenticationContainer(
onAuthenticationContainerDismissed
);
_authenticationRootContainer = rootContainer;
_authenticationLoaderContainer = loaderContainer;
_authenticationIframeContainer = iframeContainer;
// Display the authentication window right away, to show a loader
// while the call for game registration is happening.
domElementContainer.appendChild(_authenticationRootContainer);
// If the game is registered, open the authentication window.
// Otherwise, open the window indicating that the game is not registered.
(async () => {
const isGameRegistered = await checkIfGameIsRegistered(
runtimeScene.getGame(),
_gameId
);
if (_authenticationLoaderContainer) {
const electron = runtimeScene
.getGame()
.getRenderer()
.getElectron();
const wikiOpenAction = electron
? () =>
electron.shell.openExternal(
'https://wiki.gdevelop.io/gdevelop5/publishing/web'
)
: null; // Only show a link if we're on electron.
_authenticationTextContainer = authComponents.addAuthenticationTextsToLoadingContainer(
_authenticationLoaderContainer,
playerAuthPlatform,
isGameRegistered,
wikiOpenAction
);
}
if (!isGameRegistered) return;
startAuthenticationWindowTimeout(runtimeScene);
// Based on which platform the game is running, we open the authentication window
// with a different window, with or without a websocket.
switch (platform) {
let status: AuthenticationWindowStatus;
switch (playerAuthPlatform) {
case 'electron':
openAuthenticationWindowForElectron(runtimeScene, _gameId);
// This can be a:
// - Preview in GDevelop desktop app.
// - Desktop game running on Electron.
status = await openAuthenticationWindowForElectron(
runtimeScene,
_gameId
);
break;
case 'cordova':
openAuthenticationWindowForCordova(runtimeScene, _gameId);
// The game is an Android app.
status = await openAuthenticationWindowForCordova(
runtimeScene,
_gameId
);
break;
case 'cordova-websocket':
// The game is an iOS app.
status = await openAuthenticationWindowForCordovaWithWebSocket(
runtimeScene,
_gameId
);
break;
case 'web-iframe':
// This can be a:
// - Preview in GDevelop mobile app (iOS only)
status = await openAuthenticationIframeForWeb(
runtimeScene,
_gameId
);
break;
case 'web':
default:
if (shouldAuthenticationUseIframe(runtimeScene)) {
openAuthenticationIframeForWeb(runtimeScene, _gameId);
} else {
openAuthenticationWindowForWeb(runtimeScene, _gameId);
}
// This can be a:
// - Preview in GDevelop web-app
// - Preview in Gdevelop mobile app (Android only)
// - Web game (gd.games or any website/server) accessed via a desktop browser...
// - Or a web game accessed via a mobile browser (Android/iOS).
status = await openAuthenticationWindowForWeb(
runtimeScene,
_gameId
);
break;
}
}
if (isDimissedAlready) return;
if (status === 'dismissed') {
onAuthenticationContainerDismissed();
}
resolve({ status });
})();
})
.catch((error) => {
handleAuthenticationError(
runtimeScene,
'Error while checking if the game is registered.'
);
logger.error(error);
});
};
);
/**
* Condition to check if the window is open, so that the game can be paused in the background.
@@ -901,8 +1100,6 @@ namespace gdjs {
true
);
_authenticationMessageCallback = null;
// No need to detach the callback from the InAppBrowser, as it's destroyed when the window is closed.
_cordovaAuthenticationMessageCallback = null;
}
};

View File

@@ -124,6 +124,25 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("The height of the ellipse"))
.SetFunctionName("DrawEllipse");
obj.AddAction("FilletRectangle",
_("Fillet Rectangle"),
_("Draw a fillet rectangle on screen"),
_("Draw from _PARAM1_;_PARAM2_ to _PARAM3_;_PARAM4_ a fillet "
"rectangle (fillet: _PARAM5_)"
"with _PARAM0_"),
_("Drawing"),
"res/actions/filletRectangle24.png",
"res/actions/filletRectangle.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("Left X position"))
.AddParameter("expression", _("Top Y position"))
.AddParameter("expression", _("Right X position"))
.AddParameter("expression", _("Bottom Y position"))
.AddParameter("expression", _("Fillet (in pixels)"))
.SetFunctionName("DrawFilletRectangle");
obj.AddAction("RoundedRectangle",
_("Rounded rectangle"),
_("Draw a rounded rectangle on screen"),

View File

@@ -47,6 +47,11 @@ class PrimitiveDrawingJsExtension : public gd::PlatformExtension {
GetAllActionsForObject(
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Ellipse"]
.SetFunctionName("drawEllipse");
GetAllActionsForObject(
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::FilletRectangle"]
.SetFunctionName("drawFilletRectangle");
GetAllActionsForObject(
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::RoundedRectangle"]
.SetFunctionName("drawRoundedRectangle");

View File

@@ -136,6 +136,25 @@ namespace gdjs {
this.invalidateBounds();
}
drawFilletRectangle(
x1: float,
y1: float,
x2: float,
y2: float,
fillet: float
) {
this.updateOutline();
this._graphics.beginFill(
this._object._fillColor,
this._object._fillOpacity / 255
);
//@ts-ignore from @pixi/graphics-extras
this._graphics.drawFilletRect(x1, y1, x2 - x1, y2 - y1, fillet);
this._graphics.closePath();
this._graphics.endFill();
this.invalidateBounds();
}
drawChamferRectangle(
x1: float,
y1: float,

View File

@@ -210,6 +210,22 @@ namespace gdjs {
this._renderer.drawEllipse(centerX, centerY, width, height);
}
drawFilletRectangle(
startX1: float,
startY1: float,
endX2: float,
endY2: float,
fillet: float
) {
this._renderer.drawFilletRectangle(
startX1,
startY1,
endX2,
endY2,
fillet
);
}
drawRoundedRectangle(
startX1: float,
startY1: float,

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -59,10 +51,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
@@ -83,10 +75,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
};

View File

@@ -1,4 +1,5 @@
// @flow
// @ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -12,18 +13,9 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
createExtension: function (_, gd) {
const extension = new gd.PlatformExtension();
extension
@@ -94,10 +86,7 @@ module.exports = {
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
runExtensionSanityTests: function (gd, extension) {
return [];
},
/**
@@ -105,17 +94,13 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {},
registerEditorConfigurations: function (objectsEditorService) {},
/**
* Register renderers for instance of objects on the scene editor.
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
registerInstanceRenderers: function (objectsRenderingService) {
const { PIXI, RenderedInstance, gd } = objectsRenderingService;
class RenderedSpineInstance extends RenderedInstance {
@@ -163,6 +148,9 @@ module.exports = {
this._instance.getX(),
this._instance.getY()
);
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
this.setAnimation(this._instance.getRawDoubleProperty('animation'));
@@ -203,6 +191,14 @@ module.exports = {
return -this._spineOriginOffsetY;
}
getCenterX() {
return this.getOriginX();
}
getCenterY() {
return this.getOriginY();
}
/**
* @param {number} index - animation index
*/
@@ -228,7 +224,6 @@ module.exports = {
const animation = configuration.getAnimation(index);
const source = animation.getSource();
const shouldLoop = animation.shouldLoop();
const scale = this.getScale();
// reset scale to track new animation range
// if custom size is set it will be reinitialized in update method

View File

@@ -138,16 +138,15 @@ namespace gdjs {
this._loadedSpineAtlases.set(resource, atlas);
callback(null, atlas);
};
const url = this._resourceLoader.getFullUrl(resource.file);
PIXI.Assets.setPreferences({
preferWorkers: false,
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(
resource.file
)
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
? 'use-credentials'
: 'anonymous',
});
PIXI.Assets.add(resource.name, resource.file, { images });
PIXI.Assets.add(resource.name, url, { images });
PIXI.Assets.load<pixi_spine.TextureAtlas | string>(resource.name).then(
(atlas) => {
/**

View File

@@ -65,15 +65,14 @@ namespace gdjs {
const spineAtlas = await this._spineAtlasManager.getOrLoad(
atlasResourceName
);
const url = this._resourceLoader.getFullUrl(resource.file);
PIXI.Assets.setPreferences({
preferWorkers: false,
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(
resource.file
)
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
? 'use-credentials'
: 'anonymous',
});
PIXI.Assets.add(resource.name, resource.file, { spineAtlas });
PIXI.Assets.add(resource.name, url, { spineAtlas });
const loadedJson = await PIXI.Assets.load(resource.name);
if (loadedJson.spineData) {

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