Compare commits

...

200 Commits

Author SHA1 Message Date
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
Clément Pasteau
85ef9a9561 Fix casing gdevelop (#6323)
Do not show in changelog
2024-02-07 12:40:24 +01:00
AlexandreS
455d77fcdf Add possibility to cancel edition with Piskel/Jfxr/Yarn from the editor (#6319) 2024-02-05 18:12:43 +01:00
github-actions[bot]
02093fec0f Update translations [skip ci] (#6300)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-02-05 10:51:30 +01:00
Clément Pasteau
8227ab9cad Fix add assets button from shop being always visible (#6316) 2024-02-05 10:43:57 +01:00
D8H
f8eb91f3d2 Fix a crash when exporting assets sharing the same resources (#6314) 2024-02-03 13:41:09 +01:00
AlexandreS
a260aa5e3e Hide play tab for student profiles (#6313) 2024-02-02 11:13:37 +01:00
AlexandreS
4d8d93a550 Put Instances list search bar at the same place as the other panels in Scene editor (#6312) 2024-02-02 10:23:22 +01:00
AlexandreS
cf160bcca1 Improve game templates and examples display on build page (#6309) 2024-02-01 15:25:49 +01:00
D8H
44a0e22f97 Add autocompletion for "input type" action of text input objects (#6308) 2024-02-01 11:54:09 +01:00
D8H
d47d3285b2 Fix particle emitters not deleting itself when all their particles die before being displayed (#6306) 2024-01-31 20:12:28 +01:00
D8H
2ee6590967 Avoid event-functions to be selected when their menu is shown (#6301) 2024-01-30 11:44:16 +01:00
AlexandreS
cd77951e1a Fix game template/example search when there are only game templates matching (#6302) 2024-01-30 11:37:24 +01:00
D8H
fa4efef857 Fix a missing parameter in the sentence of the tween progress condition (#6299) 2024-01-30 10:35:25 +01:00
AlexandreS
bc8204d696 Remove cancel button when not logging in with provider (#6298)
Don't show in changelog
2024-01-30 10:07:18 +01:00
github-actions[bot]
b2eab5a327 Update translations [skip ci] (#6285)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-30 09:38:18 +01:00
Clément Pasteau
dba65822dd Avoid showing a loader on profile and user chip on subsequent loads (#6296) 2024-01-30 09:23:09 +01:00
AlexandreS
8f155e4322 Fix version history displaying loader on locally-stored projects (#6295) 2024-01-29 17:34:30 +01:00
D8H
13ebe5c588 Select the highest 3D objects first (#6294) 2024-01-29 16:18:48 +01:00
AlexandreS
bcd8e5608a Fix action/condition selector not updating on object change (#6291) 2024-01-29 14:18:44 +01:00
D8H
14e444413d Use the object name as the default new image name (#6293) 2024-01-29 14:01:53 +01:00
D8H
a2f75bc0dc Show installed version in the extension detail dialog (#6292) 2024-01-29 12:37:30 +01:00
D8H
6914e01a15 Fix behavior function renaming (#6290) 2024-01-29 11:18:23 +01:00
D8H
90b5f7a322 Fix the orthographic camera zoom update and its default Z position (#6288) 2024-01-29 11:17:57 +01:00
AlexandreS
13cf9b1d0b Show error box when there's an error logging in with provider (#6284)
Do not show in changelog
2024-01-26 16:28:48 +01:00
Clément Pasteau
6fc0198298 Bump version to 5.3.188 (#6283) 2024-01-26 16:26:11 +01:00
github-actions[bot]
0999ba611d Update translations [skip ci] (#6265)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-26 16:25:56 +01:00
Clément Pasteau
119d1af76a Game template bundles can be displayed and bought (#6275)
Do not show in changelog
2024-01-26 16:17:58 +01:00
AlexandreS
d6b4dacb1e Add possibility to sign up using Google, GitHub and Apple (#6249) 2024-01-26 15:46:18 +01:00
D8H
9edb3cfe91 Fix missing collision masks on new Sprite objects (#6282) 2024-01-26 15:25:12 +01:00
D8H
d44a1c3537 Generate particle textures only once (#6280) 2024-01-26 14:33:52 +01:00
AlexandreS
8cc84e5728 Fix legacy plans not showing in the user profile
Also: adds back the possibility to cancel a legacy subscription
2024-01-26 14:17:59 +01:00
D8H
76130b43d4 Handle export of assets with animations sharing frames (#6274) 2024-01-26 13:46:42 +01:00
D8H
40b0823b91 Fix a memory leak in the 2D particle emitter (#6278) 2024-01-26 12:23:13 +01:00
D8H
9b447e08e2 Forbid life-cycle functions to be renamed (#6269) 2024-01-25 14:53:07 +01:00
Aurélien Vivet
10314a1911 Fix focus not set in deletion confirmation dialog (#6270) 2024-01-25 14:16:17 +01:00
D8H
d0195719c2 Fix the 2 columns layout that may show hidden properties (#6268) 2024-01-25 12:18:52 +01:00
D8H
dd8c040381 Fix a crash when displaying the contextual menu of titles in object group lists (#6263) 2024-01-25 11:45:17 +01:00
D8H
5e0c8a92aa Fix a crash in the sprite editor of a child-object (#6266) 2024-01-25 11:44:31 +01:00
github-actions[bot]
977c102ddc Update translations [skip ci] (#6264)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2024-01-25 10:58:54 +01:00
Clément Pasteau
849d79d5d7 Avoid creating multiple canvas for ThreeJS (#6259) 2024-01-25 10:58:24 +01:00
D8H
28f7c9ae0b Fix a crash when the scale tween action is used in a 2D game (#6260) 2024-01-25 10:49:34 +01:00
github-actions[bot]
38e58327fa Update translations [skip ci] (#6244)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-24 11:22:04 +01:00
Clément Pasteau
3cc29efaa2 Remove report button on editor crash to avoid cluttering Github issues (#6247)
Do not show in changelog
2024-01-24 11:14:38 +01:00
Clément Pasteau
65eb76fb57 use newer electron builder (#6227)
Co-authored-by: romw314 <106016361+romw314@users.noreply.github.com>
2024-01-24 11:14:19 +01:00
AlexandreS
9eb721662f Remove useless margins (#6242)
Don't show in changelog
2024-01-23 14:22:39 +01:00
github-actions[bot]
b10b131010 Update translations [skip ci] (#6238)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2024-01-23 14:13:37 +01:00
Tristan Rhodes
b5fd1bb351 Improved wording "Stop music and sounds at the beginning of this scene" (#6240) 2024-01-23 14:05:31 +01:00
Aurélien Vivet
f1c9521625 Fix sentence of the Draw a Torus action (#6239) 2024-01-23 11:00:28 +01:00
github-actions[bot]
6ceb3c2c10 Update translations [skip ci] (#6234)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2024-01-22 15:38:34 +01:00
AlexandreS
43827876cd Fix subscription changes (#6236)
Don't show in changelog
2024-01-22 15:37:15 +01:00
AlexandreS
62b746300a Adapt credit banner button color to each theme (#6237)
Don't show in changelog
2024-01-22 15:29:33 +01:00
AlexandreS
769ebcd91c Fix credit banner text color on light themes (#6235)
Don't show in changelog
2024-01-22 11:12:57 +01:00
Florian Rival
70d5de16bf Fix Windows code signing (#6233) 2024-01-21 22:03:44 +01:00
Clément Pasteau
7fbe1bd23d Bump version to 5.3.187 (#6214) 2024-01-18 17:58:12 +01:00
github-actions[bot]
de8a679e31 Update translations [skip ci] (#6217)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2024-01-18 17:57:31 +01:00
Clément Pasteau
a1a4029b35 Introduce GDevelop credits and Marketing Campaigns (#6209)
* Credits can now be purchased from your Profile
* They can be used to purchase marketing campaigns, in order to promote your game on gd.games, with GDevelop social networks, or even within GDevelop itself.
* In the upcoming months, credits will unlock more and more exclusive perks in the app
2024-01-18 17:20:28 +01:00
Clément Pasteau
a377467031 Revert "Type JsExtensions files with typescript" (#6220)
Don't show in changelog
2024-01-18 16:01:28 +01:00
AlexandreS
dd090fd1d7 Abort login with providers (#6215)
Don't show in changelog
2024-01-18 12:32:01 +01:00
883 changed files with 41353 additions and 16457 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

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

@@ -5,6 +5,8 @@
*/
#include "ObjectAssetSerializer.h"
#include <algorithm>
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
@@ -37,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, gd::String> &resourcesFileNameMap) {
std::vector<gd::String> &usedResourceNames) {
auto cleanObject = object.Clone();
cleanObject->GetVariables().Clear();
cleanObject->GetEffects().Clear();
@@ -47,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", "");
@@ -84,27 +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",
resourcesFileNameMap.find(oldFilePath) !=
resourcesFileNameMap.end()
? resourcesFileNameMap[oldFilePath]
: oldFilePath);
resourceElement.SetAttribute("name", resource.GetName());
}
SerializerElement &requiredExtensionsElement =
@@ -122,101 +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, gd::String> &resourcesFileNameMap,
std::map<gd::String, gd::String> &resourcesNameReverseMap) {
gd::AssetResourcePathCleaner assetResourcePathCleaner(
project.GetResourcesManager(), resourcesFileNameMap,
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());
std::map<gd::String, 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()) {
gd::LogWarning("The resource \"" + oldName +
"\" is shared by several animations.");
continue;
}
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);
normalizedFileNames[oldName] = newName;
}
}
for (std::map<gd::String, gd::String>::const_iterator it =
resourcesFileNameMap.begin();
it != resourcesFileNameMap.end(); ++it) {
if (!it->first.empty()) {
gd::String originFile = it->first;
gd::String destinationFile = it->second;
resourcesFileNameMap[originFile] = normalizedFileNames[destinationFile];
}
}
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()) {
gd::String newResourceName = it->first;
gd::String oldResourceName = it->second;
resourcesNameReverseMap[normalizedFileNames[newResourceName]] =
oldResourceName;
}
}
}
}
} // namespace gd

View File

@@ -39,25 +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.
* \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, 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, 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

@@ -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,18 +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, 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"] == "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"));
@@ -112,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);
}
@@ -143,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

@@ -803,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,
@@ -852,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();
@@ -1092,7 +1090,6 @@ module.exports = {
})
);
// $FlowExpectedError
Cube3DObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -1104,7 +1101,6 @@ module.exports = {
return false;
};
// $FlowExpectedError
Cube3DObject.getInitialInstanceProperties = function (
content,
instance,
@@ -1931,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.
@@ -1960,7 +2036,7 @@ module.exports = {
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (ObjectsRenderingService) {
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const Rendered3DInstance = objectsRenderingService.Rendered3DInstance;
const PIXI = objectsRenderingService.PIXI;
@@ -1968,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],
};
@@ -2046,6 +2108,11 @@ module.exports = {
};
class RenderedCube3DObject2DInstance extends RenderedInstance {
/** @type {number} */
_centerX = 0;
/** @type {number} */
_centerY = 0;
constructor(
project,
layout,
@@ -2062,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());
@@ -2095,8 +2161,6 @@ module.exports = {
}
static getThumbnail(project, resourcesLoader, objectConfiguration) {
const instance = this._instance;
const textureResourceName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
objectConfiguration
);
@@ -2611,6 +2675,8 @@ module.exports = {
RenderedCube3DObject3DInstance
);
const epsilon = 1 / (1 << 16);
class Model3DRendered2DInstance extends RenderedInstance {
_modelOriginPoint = [0, 0, 0];
@@ -2733,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];
@@ -2748,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])
);
}
@@ -2773,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.
@@ -2786,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;
@@ -2941,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];
@@ -2951,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;
}
@@ -2977,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];
@@ -2991,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])
);
}
@@ -3016,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.
@@ -3029,10 +3122,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;

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

@@ -33,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'))
@@ -63,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',

View File

@@ -13,6 +13,8 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
const stringifyOptions = (options) => '["' + options.join('","') + '"]';
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (_, gd) {
@@ -32,7 +34,6 @@ module.exports = {
.setIcon('JsPlatform/Extensions/bbcode32.png');
var objectBBText = new gd.ObjectJsImplementation();
// $FlowExpectedError
objectBBText.updateProperty = function (
objectContent,
propertyName,
@@ -49,7 +50,6 @@ module.exports = {
return false;
};
// $FlowExpectedError
objectBBText.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -128,7 +128,6 @@ module.exports = {
})
);
// $FlowExpectedError
objectBBText.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -139,7 +138,6 @@ module.exports = {
) {
return false;
};
// $FlowExpectedError
objectBBText.getInitialInstanceProperties = function (
content,
instance,
@@ -425,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();
@@ -432,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;
},
@@ -469,7 +485,6 @@ module.exports = {
*/
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;
const MultiStyleText = objectsRenderingService.requireModule(
__dirname,
'pixi-multistyle-text/dist/pixi-multistyle-text.umd'
@@ -478,151 +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

@@ -34,7 +34,6 @@ module.exports = {
.setIcon('JsPlatform/Extensions/bitmapfont32.png');
const bitmapTextObject = new gd.ObjectJsImplementation();
// $FlowExpectedError
bitmapTextObject.updateProperty = function (
objectContent,
propertyName,
@@ -51,7 +50,6 @@ module.exports = {
return false;
};
// $FlowExpectedError
bitmapTextObject.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -132,7 +130,6 @@ module.exports = {
})
);
// $FlowExpectedError
bitmapTextObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -143,7 +140,6 @@ module.exports = {
) {
return false;
};
// $FlowExpectedError
bitmapTextObject.getInitialInstanceProperties = function (
content,
instance,
@@ -635,157 +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

@@ -145,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,
@@ -162,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();
@@ -179,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);
@@ -193,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()
)
@@ -207,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,
@@ -220,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();
@@ -230,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,
@@ -249,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();
@@ -259,7 +252,6 @@ module.exports = {
return sharedProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
sharedData.initializeContent = function (behaviorContent) {
behaviorContent.setStringAttribute(
'sharedProperty1',
@@ -276,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
)
@@ -294,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,
@@ -319,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();
@@ -354,7 +345,6 @@ module.exports = {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object
dummyObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -374,7 +364,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
dummyObject.getInitialInstanceProperties = function (
content,
instance,

View File

@@ -1,12 +1,171 @@
declare type GDNamespace = typeof import('../GDevelop.js/types');
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 {}
/**
* 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: Class<PixiResourcesLoader>;
_pixiObject: PIXI.DisplayObject;
wasUsed: boolean;
constructor(
project: gdProject,
layout: gdLayout,
instance: gdInitialInstance,
associatedObjectConfiguration: gdObjectConfiguration,
pixiContainer: PIXI.Container,
pixiResourcesLoader: Class<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: gdProject;
_layout: gdLayout;
_instance: gdInitialInstance;
_associatedObjectConfiguration: gdObjectConfiguration;
_pixiContainer: PIXI.Container;
_threeGroup: THREE.Group;
_pixiResourcesLoader: Class<PixiResourcesLoader>;
_pixiObject: PIXI.DisplayObject;
_threeObject: THREE.Object3D | null;
wasUsed: boolean;
constructor(
project: gdProject,
layout: gdLayout,
instance: gdInitialInstance,
associatedObjectConfiguration: gdObjectConfiguration,
pixiContainer: PIXI.Container,
threeGroup: THREE.Group,
pixiResourcesLoader: Class<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;
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: typeof import('../newIDE/app/node_modules/pixi.js');
PIXI: PIXI;
THREE: typeof import('../newIDE/app/node_modules/three');
THREE_ADDONS: { SkeletonUtils: any };
RenderedInstance: any;
Rendered3DInstance: 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;

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

@@ -30,7 +30,6 @@ module.exports = {
.setTags('light');
const lightObstacleBehavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.updateProperty = function (
behaviorContent,
propertyName,
@@ -39,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(
@@ -59,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()
)
@@ -70,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,
@@ -99,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();
@@ -153,7 +149,6 @@ module.exports = {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -165,7 +160,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.getInitialInstanceProperties = function (
content,
instance,
@@ -269,14 +263,14 @@ 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()
);

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

@@ -174,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',

View File

@@ -24,58 +24,41 @@ namespace gdjs {
runtimeObject: gdjs.RuntimeObject,
objectData: any
) {
let texture = null;
const graphics = new PIXI.Graphics();
graphics.lineStyle(0, 0, 0);
graphics.beginFill(gdjs.rgbToHexNumber(255, 255, 255), 1);
if (objectData.rendererType === 'Point') {
graphics.drawCircle(0, 0, objectData.rendererParam1);
} else if (objectData.rendererType === 'Line') {
graphics.drawRect(
0,
0,
objectData.rendererParam1,
objectData.rendererParam2
);
// Draw an almost-invisible rectangle in the left hand to force PIXI to take a full texture with our line at the right hand
graphics.beginFill(gdjs.rgbToHexNumber(255, 255, 255), 0.001);
graphics.drawRect(
0,
0,
objectData.rendererParam1,
objectData.rendererParam2
);
} else if (objectData.textureParticleName) {
const sprite = new PIXI.Sprite(
(instanceContainer
.getGame()
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
objectData.textureParticleName
)
);
sprite.width = objectData.rendererParam1;
sprite.height = objectData.rendererParam2;
graphics.addChild(sprite);
} else {
graphics.drawRect(
0,
0,
objectData.rendererParam1,
objectData.rendererParam2
);
}
graphics.endFill();
// Render the texture from graphics using the PIXI Renderer.
// TODO: could be optimized by generating the texture only once per object type,
// instead of at each object creation.
const pixiRenderer = instanceContainer
.getGame()
.getRenderer()
.getPIXIRenderer();
//@ts-expect-error Pixi has wrong type definitions for this method
texture = pixiRenderer.generateTexture(graphics);
const imageManager = instanceContainer
.getGame()
.getImageManager() as gdjs.PixiImageManager;
let particleTexture: PIXI.Texture = PIXI.Texture.WHITE;
if (pixiRenderer) {
if (objectData.rendererType === 'Point') {
particleTexture = imageManager.getOrCreateDiskTexture(
objectData.rendererParam1,
pixiRenderer
);
} else if (objectData.rendererType === 'Line') {
particleTexture = imageManager.getOrCreateRectangleTexture(
objectData.rendererParam1,
objectData.rendererParam2,
pixiRenderer
);
} else if (objectData.textureParticleName) {
particleTexture = imageManager.getOrCreateScaledTexture(
objectData.textureParticleName,
objectData.rendererParam1,
objectData.rendererParam2,
pixiRenderer
);
} else {
particleTexture = imageManager.getOrCreateRectangleTexture(
objectData.rendererParam1,
objectData.rendererParam2,
pixiRenderer
);
}
}
const configuration = {
ease: undefined,
@@ -198,7 +181,7 @@ namespace gdjs {
{
type: 'textureSingle',
config: {
texture: texture,
texture: particleTexture,
},
},
{
@@ -236,8 +219,9 @@ namespace gdjs {
}
update(delta: float): void {
const wasEmitting = this.emitter.emit;
this.emitter.update(delta);
if (!this.started && this.getParticleCount() > 0) {
if (!this.started && wasEmitting) {
this.started = true;
}
}

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

@@ -33,7 +33,6 @@ module.exports = {
.setIcon('res/physics32.png');
var physics2Behavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
physics2Behavior.updateProperty = function (
behaviorContent,
propertyName,
@@ -43,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();
@@ -304,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);
@@ -328,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();
@@ -394,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);
@@ -414,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
)
@@ -1382,6 +1415,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',

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

@@ -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

@@ -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

@@ -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"),
@@ -163,9 +182,9 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
obj.AddAction("Torus",
_("Torus"),
_("Draw a torus on screen"),
_("Draw at _PARAM1_;_PARAM2_ a torus with inner radius"
"_PARAM3_ and outer radius _PARAM4_ and "
"with start arc _PARAM5_° and end arc _PARAM6_°"
_("Draw at _PARAM1_;_PARAM2_ a torus with "
"inner radius: _PARAM3_, outer radius: _PARAM4_ and "
"with start arc angle: _PARAM5_°, end angle: _PARAM6_° "
"with _PARAM0_"),
_("Drawing"),
"res/actions/torus24.png",

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
@@ -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) {

File diff suppressed because one or more lines are too long

View File

@@ -124,6 +124,16 @@ namespace gdjs {
return this.getY() + originOffset.y;
}
getCenterX(): float {
const originOffset = this._renderer.getOriginOffset();
return -originOffset.x;
}
getCenterY(): float {
const originOffset = this._renderer.getOriginOffset();
return -originOffset.y;
}
onDestroyed(): void {
super.onDestroyed();
this._renderer.onDestroy();

View File

@@ -31,7 +31,6 @@ module.exports = {
.setIcon('JsPlatform/Extensions/text_input.svg');
const textInputObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
textInputObject.updateProperty = function (
objectContent,
propertyName,
@@ -86,7 +85,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
textInputObject.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
@@ -224,7 +222,6 @@ module.exports = {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object
textInputObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -243,7 +240,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
textInputObject.getInitialInstanceProperties = function (
content,
instance,
@@ -375,7 +371,7 @@ module.exports = {
'res/actions/font24.png',
'res/actions/font.png'
)
.addParameter('object', _('Bitmap text'), 'TextInputObject', false)
.addParameter('object', _('Text input'), 'TextInputObject', false)
.addParameter('fontResource', _('Font resource name'), '', false)
.getCodeExtraInformation()
.setFunctionName('setFontResourceName');
@@ -392,9 +388,23 @@ module.exports = {
)
.addParameter('object', _('Text input'), 'TextInputObject', false)
.useStandardParameters(
'string',
gd.ParameterOptions.makeNewOptions().setDescription(_('Input type'))
) // TODO: stringWithSelector?
'stringWithSelector',
gd.ParameterOptions.makeNewOptions()
.setDescription(_('Input type'))
.setTypeExtraInfo(
JSON.stringify([
'text',
'text area',
'email',
'password',
'number',
'telephone number',
'url',
'search',
'email',
])
)
)
.setFunctionName('setInputType')
.setGetter('getInputType');

View File

@@ -40,38 +40,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
// Deprecated
obj.AddAction("String",
_("Modify the text"),
_("Modify the text of a Text object."),
_("the text"),
"",
"res/actions/text24_black.png",
"res/actions/text_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"string",
gd::ParameterOptions::MakeNewOptions().SetDescription(_("Text")))
.SetFunctionName("SetString")
.SetGetter("GetString");
// Deprecated
obj.AddCondition("String",
_("Compare the text"),
_("Compare the text of a Text object."),
_("the text"),
"",
"res/conditions/text24_black.png",
"res/conditions/text_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"string",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Text to compare to")))
.SetFunctionName("GetString");
obj.AddAction("Font",
_("Font"),
_("Change the font of the text."),
@@ -84,94 +52,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("police", _("Font"))
.SetFunctionName("ChangeFont");
// Deprecated
obj.AddCondition("ScaleX",
_("Scale on X axis"),
_("Compare the scale of the text on the X axis"),
_("the scale on the X axis"),
"Scale",
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale to compare to (1 by default)")))
.SetHidden()
.SetFunctionName("GetScaleX");
// Deprecated
obj.AddAction(
"ScaleX",
_("Scale on X axis"),
_("Modify the scale of the text on the X axis (default scale is 1)"),
_("the scale on the X axis"),
_("Scale"),
"res/actions/scaleWidth24_black.png",
"res/actions/scaleWidth_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.SetHidden()
.SetFunctionName("SetScaleX");
// Deprecated
obj.AddCondition("ScaleY",
_("Scale on Y axis"),
_("Compare the scale of the text on the Y axis"),
_("the scale on the Y axis"),
"Scale",
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale to compare to (1 by default)")))
.SetHidden()
.SetFunctionName("GetScaleY");
// Deprecated
obj.AddAction(
"ScaleY",
_("Scale on Y axis"),
_("Modify the scale of the text on the Y axis (default scale is 1)"),
_("the scale on the Y axis"),
_("Scale"),
"res/actions/scaleHeight24_black.png",
"res/actions/scaleHeight_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.SetHidden()
.SetFunctionName("SetScaleY");
// Deprecated
obj.AddAction(
"Scale",
_("Scale"),
_("Modify the scale of the specified object (default scale is 1)"),
_("the scale"),
_("Scale"),
"res/actions/scale24_black.png",
"res/actions/scale_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.SetHidden()
.SetFunctionName("SetScale");
obj.AddAction(
"ChangeColor",
_("Color"),
@@ -204,6 +84,7 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("color", _("Third Color"))
.AddParameter("color", _("Fourth Color"));
// Deprecated
obj.AddAction("SetOutline",
_("Outline"),
_("Change the outline of the text. A thickness of 0 disables "
@@ -213,20 +94,63 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
_("Effects"),
"res/actions/textOutline24.png",
"res/actions/textOutline.png")
.SetHidden()
.AddParameter("object", _("Object"), "Text")
.AddParameter("color", _("Color"))
.AddParameter("expression", _("Thickness"));
obj.AddScopedAction("SetOutlineEnabled",
_("Enable outline"),
_("Enable or disable the outline of the text."),
_("Enable the outline of _PARAM0_: _PARAM1_"),
_("Outline"),
"res/actions/textOutline24.png",
"res/actions/textOutline.png")
.AddParameter("object", _("Object"), "Text")
.AddParameter("yesorno", _("Enable outline"), "", true)
.SetDefaultValue("yes");
obj.AddScopedCondition("IsOutlineEnabled",
_("Outline enabled"),
_("Check if the text outline is enabled."),
_("The outline of _PARAM0_ is enabled"),
_("Outline"),
"res/actions/textOutline24.png",
"res/actions/textOutline.png")
.AddParameter("object", _("Object"), "Text");
obj.AddScopedAction("SetOutlineColor",
_("Outline color"),
_("Change the outline color of the text."),
_("Change the text outline color of _PARAM0_ to _PARAM1_"),
_("Outline"),
"res/actions/textOutline24.png",
"res/actions/textOutline.png")
.AddParameter("object", _("Object"), "Text")
.AddParameter("color", _("Color"));
obj.AddExpressionAndConditionAndAction("number", "OutlineThickness",
_("Outline thickness"),
_("the outline thickness of the text"),
_("the text outline thickness"),
_("Outline"),
"res/actions/textOutline24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Thickness")));
// Deprecated
obj.AddAction("SetShadow",
_("Text shadow"),
_("Change the shadow of the text."),
_("Change the shadow of _PARAM0_ to color _PARAM1_ distance "
"_PARAM2_ blur _PARAM3_ angle _PARAM4_"),
_("Effects/Shadow"),
_("Shadow"),
"res/actions/textShadow24.png",
"res/actions/textShadow.png")
.SetHidden()
.AddParameter("object", _("Object"), "Text")
.AddParameter("color", _("Color"))
.AddParameter("expression", _("Distance"))
@@ -234,52 +158,82 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Angle"));
obj.AddAction("ShowShadow",
_("Show Shadow"),
_("Show the shadow of the text."),
_("Show the shadow of _PARAM0_: _PARAM1_"),
_("Effects/Shadow"),
_("Enable shadow"),
_("Enable or disable the shadow of the text."),
_("Enable the shadow of _PARAM0_: _PARAM1_"),
_("Shadow"),
"res/actions/textShadow24.png",
"res/actions/textShadow.png")
.AddParameter("object", _("Object"), "Text")
.AddParameter("yesorno", _("Show the shadow"));
.AddParameter("yesorno", _("Show the shadow"), "", true)
.SetDefaultValue("yes");
// Deprecated
obj.AddAction("Opacity",
_("Text opacity"),
_("Change the opacity of a Text. 0 is fully transparent, 255 "
"is opaque (default)."),
_("the opacity"),
"",
"res/actions/opacity24.png",
"res/actions/opacity.png")
obj.AddScopedCondition("IsShadowEnabled",
_("Shadow enabled"),
_("Check if the text shadow is enabled."),
_("The shadow of _PARAM0_ is enabled"),
_("Shadow"),
"res/actions/textShadow24.png",
"res/actions/textShadow.png")
.AddParameter("object", _("Object"), "Text");
obj.AddScopedAction("SetShadowColor",
_("Shadow color"),
_("Change the shadow color of the text."),
_("Change the shadow color of _PARAM0_ to _PARAM1_"),
_("Shadow"),
"res/actions/textShadow24.png",
"res/actions/textShadow.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
.AddParameter("color", _("Color"));
obj.AddExpressionAndConditionAndAction("number", "ShadowOpacity",
_("Shadow opacity"),
_("the shadow opacity of the text"),
_("the shadow opacity "),
_("Shadow"),
"res/actions/textShadow24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity (0-255)")))
.SetFunctionName("SetOpacity")
.SetGetter("GetOpacity")
.SetHidden();
// Deprecated
obj.AddCondition("Opacity",
_("Opacity"),
_("Compare the opacity of a Text object, between 0 (fully "
"transparent) to 255 (opaque)."),
_("the opacity"),
"",
"res/conditions/opacity24.png",
"res/conditions/opacity.png")
_("Opacity (0 - 255)")));
obj.AddExpressionAndConditionAndAction("number", "ShadowDistance",
_("Shadow distance"),
_("the shadow distance of the text"),
_("the shadow distance "),
_("Shadow"),
"res/actions/textShadow24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
.UseStandardParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity to compare to (0-255)")))
.SetFunctionName("GetOpacity")
.SetHidden();
_("Distance")));
obj.AddExpressionAndConditionAndAction("number", "ShadowAngle",
_("Shadow angle"),
_("the shadow angle of the text"),
_("the shadow angle "),
_("Shadow"),
"res/actions/textShadow24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Angle (in degrees)")));
obj.AddExpressionAndConditionAndAction("number", "ShadowBlurRadius",
_("Shadow blur radius"),
_("the shadow blur radius of the text"),
_("the shadow blur radius "),
_("Shadow"),
"res/actions/textShadow24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Blur radius")));
obj.AddAction("SetSmooth",
_("Smoothing"),
@@ -373,37 +327,6 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "Text")
.SetFunctionName("IsUnderlined");
obj.AddAction("Angle",
_("Angle"),
_("Modify the angle of a Text object."),
_("the angle"),
_("Rotation"),
"res/actions/rotate24_black.png",
"res/actions/rotate_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Angle (in degrees)")))
.SetFunctionName("SetAngle")
.SetGetter("GetAngle");
obj.AddCondition("Angle",
_("Angle"),
_("Compare the value of the angle of a Text object."),
_("the angle"),
_("Rotation"),
"res/conditions/rotate24_black.png",
"res/conditions/rotate_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Angle to compare to (in degrees)")))
.SetFunctionName("GetAngle");
obj.AddCondition("Padding",
_("Padding"),
_("Compare the number of pixels around a text object. If "
@@ -517,6 +440,143 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
"res/actions/textPadding_black.png")
.AddParameter("object", _("Object"), "Text");
obj.AddExpressionAndConditionAndAction("number",
"FontSize",
_("Font size"),
_("the font size of a text object"),
_("the font size"),
"",
"res/conditions/characterSize24.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
// Support for deprecated "Size" actions/conditions:
obj.AddDuplicatedAction("Size", "Text::SetFontSize").SetHidden();
obj.AddDuplicatedCondition("Size", "Text::FontSize").SetHidden();
// Deprecated
obj.AddAction("Angle",
_("Angle"),
_("Modify the angle of a Text object."),
_("the angle"),
_("Rotation"),
"res/actions/rotate24_black.png",
"res/actions/rotate_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Angle (in degrees)")))
.SetHidden()
.SetFunctionName("SetAngle")
.SetGetter("GetAngle");
// Deprecated
obj.AddCondition("Angle",
_("Angle"),
_("Compare the value of the angle of a Text object."),
_("the angle"),
_("Rotation"),
"res/conditions/rotate24_black.png",
"res/conditions/rotate_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Angle to compare to (in degrees)")))
.SetHidden()
.SetFunctionName("GetAngle");
// Deprecated
obj.AddCondition("ScaleX",
_("Scale on X axis"),
_("Compare the scale of the text on the X axis"),
_("the scale on the X axis"),
"Scale",
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale to compare to (1 by default)")))
.SetHidden()
.SetFunctionName("GetScaleX");
// Deprecated
obj.AddAction(
"ScaleX",
_("Scale on X axis"),
_("Modify the scale of the text on the X axis (default scale is 1)"),
_("the scale on the X axis"),
_("Scale"),
"res/actions/scaleWidth24_black.png",
"res/actions/scaleWidth_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.SetHidden()
.SetFunctionName("SetScaleX");
// Deprecated
obj.AddCondition("ScaleY",
_("Scale on Y axis"),
_("Compare the scale of the text on the Y axis"),
_("the scale on the Y axis"),
"Scale",
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale to compare to (1 by default)")))
.SetHidden()
.SetFunctionName("GetScaleY");
// Deprecated
obj.AddAction(
"ScaleY",
_("Scale on Y axis"),
_("Modify the scale of the text on the Y axis (default scale is 1)"),
_("the scale on the Y axis"),
_("Scale"),
"res/actions/scaleHeight24_black.png",
"res/actions/scaleHeight_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.SetHidden()
.SetFunctionName("SetScaleY");
// Deprecated
obj.AddAction(
"Scale",
_("Scale"),
_("Modify the scale of the specified object (default scale is 1)"),
_("the scale"),
_("Scale"),
"res/actions/scale24_black.png",
"res/actions/scale_black.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.SetHidden()
.SetFunctionName("SetScale");
// Deprecated
obj.AddExpression("ScaleX",
_("X Scale of a Text object"),
@@ -537,6 +597,43 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.SetHidden()
.SetFunctionName("GetScaleY");
// Deprecated
obj.AddAction("Opacity",
_("Text opacity"),
_("Change the opacity of a Text. 0 is fully transparent, 255 "
"is opaque (default)."),
_("the opacity"),
"",
"res/actions/opacity24.png",
"res/actions/opacity.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity (0-255)")))
.SetFunctionName("SetOpacity")
.SetGetter("GetOpacity")
.SetHidden();
// Deprecated
obj.AddCondition("Opacity",
_("Opacity"),
_("Compare the opacity of a Text object, between 0 (fully "
"transparent) to 255 (opaque)."),
_("the opacity"),
"",
"res/conditions/opacity24.png",
"res/conditions/opacity.png")
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity to compare to (0-255)")))
.SetFunctionName("GetOpacity")
.SetHidden();
// Deprecated
obj.AddExpression("Opacity",
_("Opacity of a Text object"),
@@ -547,30 +644,52 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.SetFunctionName("GetOpacity")
.SetHidden();
// Deprecated
obj.AddExpression("Angle",
_("Angle"),
_("Angle"),
_("Rotation"),
"res/actions/rotate_black.png")
.AddParameter("object", _("Object"), "Text")
.SetHidden()
.SetFunctionName("GetAngle");
obj.AddExpressionAndConditionAndAction("number",
"FontSize",
_("Font size"),
_("the font size of a text object"),
_("the font size"),
"",
"res/conditions/characterSize24.png")
// Deprecated
obj.AddAction("String",
_("Modify the text"),
_("Modify the text of a Text object."),
_("the text"),
"",
"res/actions/text24_black.png",
"res/actions/text_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
.UseStandardOperatorParameters(
"string",
gd::ParameterOptions::MakeNewOptions().SetDescription(_("Text")))
.SetFunctionName("SetString")
.SetGetter("GetString");
// Support for deprecated "Size" actions/conditions:
obj.AddDuplicatedAction("Size", "Text::SetFontSize").SetHidden();
obj.AddDuplicatedCondition("Size", "Text::FontSize").SetHidden();
// Deprecated
obj.AddCondition("String",
_("Compare the text"),
_("Compare the text of a Text object."),
_("the text"),
"",
"res/conditions/text24_black.png",
"res/conditions/text_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Text")
.UseStandardRelationalOperatorParameters(
"string",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Text to compare to")))
.SetFunctionName("GetString");
// Deprecated
obj.AddStrExpression(
"String", _("Text"), _("Text"), _("Text"), "res/texteicon.png")
.AddParameter("object", _("Object"), "Text")
.SetHidden()
.SetFunctionName("GetString");
}

View File

@@ -29,24 +29,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
.SetIncludeFile("Extensions/TextObject/textruntimeobject.js")
.AddIncludeFile(
"Extensions/TextObject/textruntimeobject-pixi-renderer.js");
GetAllActionsForObject("TextObject::Text")["TextObject::Scale"]
.SetFunctionName("setScale")
.SetGetter("getScaleMean");
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleX"]
.SetFunctionName("setScaleX")
.SetGetter("getScaleX");
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleX"]
.SetFunctionName("getScaleX");
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleY"]
.SetFunctionName("setScaleY")
.SetGetter("getScaleY");
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleY"]
.SetFunctionName("getScaleY");
GetAllActionsForObject("TextObject::Text")["TextObject::String"]
.SetFunctionName("setString")
.SetGetter("getString");
GetAllConditionsForObject("TextObject::Text")["TextObject::String"]
.SetFunctionName("getString");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetFontSize"]
.SetFunctionName("setCharacterSize")
@@ -56,24 +38,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
GetAllExpressionsForObject("TextObject::Text")["FontSize"]
.SetFunctionName("getCharacterSize");
// Deprecated actions/conditions (use "FontSize"/"SetFontSize" instead):
GetAllActionsForObject("TextObject::Text")["TextObject::Size"]
.SetFunctionName("setCharacterSize")
.SetGetter("getCharacterSize");
GetAllConditionsForObject("TextObject::Text")["TextObject::Size"]
.SetFunctionName("getCharacterSize");
GetAllActionsForObject("TextObject::Text")["TextObject::Angle"]
.SetFunctionName("setAngle")
.SetGetter("getAngle");
GetAllConditionsForObject("TextObject::Text")["TextObject::Angle"]
.SetFunctionName("getAngle");
GetAllActionsForObject("TextObject::Text")["TextObject::Opacity"]
.SetFunctionName("setOpacity")
.SetGetter("getOpacity");
GetAllConditionsForObject("TextObject::Text")["TextObject::Opacity"]
.SetFunctionName("getOpacity");
GetAllActionsForObject("TextObject::Text")["TextObject::SetBold"]
.SetFunctionName("setBold");
GetAllConditionsForObject("TextObject::Text")["TextObject::IsBold"]
@@ -108,16 +72,6 @@ class TextObjectJsExtension : public gd::PlatformExtension {
GetAllExpressionsForObject("TextObject::Text")["Padding"]
.SetFunctionName("getPadding");
GetAllExpressionsForObject("TextObject::Text")["ScaleX"]
.SetFunctionName("getScaleX");
GetAllExpressionsForObject("TextObject::Text")["ScaleY"]
.SetFunctionName("getScaleY");
GetAllExpressionsForObject("TextObject::Text")["Opacity"]
.SetFunctionName("getOpacity");
GetAllExpressionsForObject("TextObject::Text")["Angle"]
.SetFunctionName("getAngle");
GetAllStrExpressionsForObject("TextObject::Text")["String"]
.SetFunctionName("getString");
GetAllActionsForObject("TextObject::Text")["TextObject::ChangeColor"]
.SetFunctionName("setColor");
@@ -125,12 +79,113 @@ class TextObjectJsExtension : public gd::PlatformExtension {
GetAllActionsForObject("TextObject::Text")["TextObject::SetGradient"]
.SetFunctionName("setGradient");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineEnabled"]
.SetFunctionName("setOutlineEnabled");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsOutlineEnabled"]
.SetFunctionName("isOutlineEnabled");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineColor"]
.SetFunctionName("setOutlineColor");
GetAllExpressionsForObject("TextObject::Text")["OutlineThickness"]
.SetFunctionName("getOutlineThickness");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::OutlineThickness"]
.SetFunctionName("getOutlineThickness");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineThickness"]
.SetFunctionName("setOutlineThickness")
.SetGetter("getOutlineThickness");
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
.SetFunctionName("showShadow");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsShadowEnabled"]
.SetFunctionName("isShadowEnabled");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowColor"]
.SetFunctionName("setShadowColor");
GetAllExpressionsForObject("TextObject::Text")["ShadowOpacity"]
.SetFunctionName("getShadowOpacity");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowOpacity"]
.SetFunctionName("getShadowOpacity");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowOpacity"]
.SetFunctionName("setShadowOpacity")
.SetGetter("getShadowOpacity");
GetAllExpressionsForObject("TextObject::Text")["ShadowDistance"]
.SetFunctionName("getShadowDistance");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowDistance"]
.SetFunctionName("getShadowDistance");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowDistance"]
.SetFunctionName("setShadowDistance")
.SetGetter("getShadowDistance");
GetAllExpressionsForObject("TextObject::Text")["ShadowAngle"]
.SetFunctionName("getShadowAngle");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowAngle"]
.SetFunctionName("getShadowAngle");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowAngle"]
.SetFunctionName("setShadowAngle")
.SetGetter("getShadowAngle");
GetAllExpressionsForObject("TextObject::Text")["ShadowBlurRadius"]
.SetFunctionName("getShadowBlurRadius");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowBlurRadius"]
.SetFunctionName("getShadowBlurRadius");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowBlurRadius"]
.SetFunctionName("setShadowBlurRadius")
.SetGetter("getShadowBlurRadius");
// Deprecated actions/conditions (use "FontSize"/"SetFontSize" instead):
GetAllActionsForObject("TextObject::Text")["TextObject::Size"]
.SetFunctionName("setCharacterSize")
.SetGetter("getCharacterSize");
GetAllConditionsForObject("TextObject::Text")["TextObject::Size"]
.SetFunctionName("getCharacterSize");
// Deprecated: now available for all objects.
GetAllActionsForObject("TextObject::Text")["TextObject::Angle"]
.SetFunctionName("setAngle")
.SetGetter("getAngle");
GetAllConditionsForObject("TextObject::Text")["TextObject::Angle"]
.SetFunctionName("getAngle");
GetAllExpressionsForObject("TextObject::Text")["Angle"]
.SetFunctionName("getAngle");
// Deprecated: available through capabilities.
GetAllActionsForObject("TextObject::Text")["TextObject::Scale"]
.SetFunctionName("setScale")
.SetGetter("getScaleMean");
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleX"]
.SetFunctionName("setScaleX")
.SetGetter("getScaleX");
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleX"]
.SetFunctionName("getScaleX");
GetAllActionsForObject("TextObject::Text")["TextObject::ScaleY"]
.SetFunctionName("setScaleY")
.SetGetter("getScaleY");
GetAllConditionsForObject("TextObject::Text")["TextObject::ScaleY"]
.SetFunctionName("getScaleY");
GetAllExpressionsForObject("TextObject::Text")["ScaleX"]
.SetFunctionName("getScaleX");
GetAllExpressionsForObject("TextObject::Text")["ScaleY"]
.SetFunctionName("getScaleY");
GetAllActionsForObject("TextObject::Text")["TextObject::String"]
.SetFunctionName("setString")
.SetGetter("getString");
GetAllStrExpressionsForObject("TextObject::Text")["String"]
.SetFunctionName("getString");
GetAllConditionsForObject("TextObject::Text")["TextObject::String"]
.SetFunctionName("getString");
GetAllExpressionsForObject("TextObject::Text")["Opacity"]
.SetFunctionName("getOpacity");
GetAllActionsForObject("TextObject::Text")["TextObject::Opacity"]
.SetFunctionName("setOpacity")
.SetGetter("getOpacity");
GetAllConditionsForObject("TextObject::Text")["TextObject::Opacity"]
.SetFunctionName("getOpacity");
// Deprecated: split into several instructions.
GetAllActionsForObject("TextObject::Text")["TextObject::SetOutline"]
.SetFunctionName("setOutline");
GetAllActionsForObject("TextObject::Text")["TextObject::SetShadow"]
.SetFunctionName("setShadow");
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
.SetFunctionName("showShadow");
// Unimplemented actions and conditions:
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]

View File

@@ -12,10 +12,8 @@ This project is released under the MIT License.
#include "GDCore/Serialization/SerializerElement.h"
#include "TextObject.h"
#if defined(GD_IDE_ONLY)
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#endif
using namespace std;
@@ -27,10 +25,17 @@ TextObject::TextObject()
bold(false),
italic(false),
underlined(false),
colorR(0),
colorG(0),
colorB(0),
textAlignment("left")
color("0;0;0"),
textAlignment("left"),
isOutlineEnabled(false),
outlineThickness(2),
outlineColor("255;255;255"),
isShadowEnabled(false),
shadowColor("0;0;0"),
shadowOpacity(127),
shadowAngle(90),
shadowDistance(4),
shadowBlurRadius(2)
{
}
@@ -38,41 +43,98 @@ TextObject::~TextObject(){};
void TextObject::DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element) {
SetString(element.GetChild("string", 0, "String").GetValue().GetString());
SetFontName(element.GetChild("font", 0, "Font").GetValue().GetString());
SetTextAlignment(element.GetChild("textAlignment").GetValue().GetString());
SetCharacterSize(element.GetChild("characterSize", 0, "CharacterSize")
// Compatibility with GD <= 5.3.188
// end of compatibility code
bool isLegacy = !element.HasChild("content");
auto &content = isLegacy ? element : element.GetChild("content");
SetFontName(content.GetChild("font", 0, "Font").GetValue().GetString());
SetTextAlignment(content.GetChild("textAlignment").GetValue().GetString());
SetCharacterSize(content.GetChild("characterSize", 0, "CharacterSize")
.GetValue()
.GetInt());
SetColor(element.GetChild("color", 0, "Color").GetIntAttribute("r", 255),
element.GetChild("color", 0, "Color").GetIntAttribute("g", 255),
element.GetChild("color", 0, "Color").GetIntAttribute("b", 255));
smoothed = content.GetBoolAttribute("smoothed");
bold = content.GetBoolAttribute("bold");
italic = content.GetBoolAttribute("italic");
underlined = content.GetBoolAttribute("underlined");
smoothed = element.GetBoolAttribute("smoothed");
bold = element.GetBoolAttribute("bold");
italic = element.GetBoolAttribute("italic");
underlined = element.GetBoolAttribute("underlined");
// Compatibility with GD <= 5.3.188
if (isLegacy) {
SetText(content.GetChild("string", 0, "String").GetValue().GetString());
SetColor(
gd::String::From(
content.GetChild("color", 0, "Color").GetIntAttribute("r", 255)) +
";" +
gd::String::From(
content.GetChild("color", 0, "Color").GetIntAttribute("g", 255)) +
";" +
gd::String::From(
content.GetChild("color", 0, "Color").GetIntAttribute("b", 255)));
} else
// end of compatibility code
{
SetText(content.GetStringAttribute("text"));
SetColor(content.GetStringAttribute("color", "0;0;0"));
SetOutlineEnabled(content.GetBoolAttribute("isOutlineEnabled", false));
SetOutlineThickness(content.GetIntAttribute("outlineThickness", 2));
SetOutlineColor(content.GetStringAttribute("outlineColor", "255;255;255"));
SetShadowEnabled(content.GetBoolAttribute("isShadowEnabled", false));
SetShadowColor(content.GetStringAttribute("shadowColor", "0;0;0"));
SetShadowOpacity(content.GetIntAttribute("shadowOpacity", 127));
SetShadowAngle(content.GetIntAttribute("shadowAngle", 90));
SetShadowDistance(content.GetIntAttribute("shadowDistance", 4));
SetShadowBlurRadius(content.GetIntAttribute("shadowBlurRadius", 2));
}
}
#if defined(GD_IDE_ONLY)
void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
element.AddChild("string").SetValue(GetString());
element.AddChild("font").SetValue(GetFontName());
element.AddChild("textAlignment").SetValue(GetTextAlignment());
element.AddChild("characterSize").SetValue(GetCharacterSize());
element.AddChild("color")
.SetAttribute("r", (int)GetColorR())
.SetAttribute("g", (int)GetColorG())
.SetAttribute("b", (int)GetColorB());
// Allow users to rollback to 5.3.188 or older releases without loosing their configuration.
// TODO Remove this in a few releases.
// Compatibility with GD <= 5.3.188
{
element.AddChild("string").SetValue(GetText());
element.AddChild("font").SetValue(GetFontName());
element.AddChild("textAlignment").SetValue(GetTextAlignment());
element.AddChild("characterSize").SetValue(GetCharacterSize());
auto colorComponents = GetColor().Split(';');
element.AddChild("color")
.SetAttribute("r", colorComponents.size() == 3 ? colorComponents[0].To<int>() : 0)
.SetAttribute("g", colorComponents.size() == 3 ? colorComponents[1].To<int>() : 0)
.SetAttribute("b", colorComponents.size() == 3 ? colorComponents[2].To<int>() : 0);
element.SetAttribute("smoothed", smoothed);
element.SetAttribute("bold", bold);
element.SetAttribute("italic", italic);
element.SetAttribute("underlined", underlined);
}
// end of compatibility code
auto& content = element.AddChild("content");
content.AddChild("text").SetValue(GetText());
content.AddChild("font").SetValue(GetFontName());
content.AddChild("textAlignment").SetValue(GetTextAlignment());
content.AddChild("characterSize").SetValue(GetCharacterSize());
content.AddChild("color").SetValue(GetColor());
element.SetAttribute("smoothed", smoothed);
element.SetAttribute("bold", bold);
element.SetAttribute("italic", italic);
element.SetAttribute("underlined", underlined);
content.SetAttribute("smoothed", smoothed);
content.SetAttribute("bold", bold);
content.SetAttribute("italic", italic);
content.SetAttribute("underlined", underlined);
content.SetAttribute("isOutlineEnabled", isOutlineEnabled);
content.SetAttribute("outlineThickness", outlineThickness);
content.SetAttribute("outlineColor", outlineColor);
content.SetAttribute("isShadowEnabled", isShadowEnabled);
content.SetAttribute("shadowColor", shadowColor);
content.SetAttribute("shadowOpacity", shadowOpacity);
content.SetAttribute("shadowAngle", shadowAngle);
content.SetAttribute("shadowDistance", shadowDistance);
content.SetAttribute("shadowBlurRadius", shadowBlurRadius);
}
void TextObject::ExposeResources(
gd::ArbitraryResourceWorker& worker) {
worker.ExposeFont(fontName);
}
#endif

View File

@@ -5,8 +5,8 @@ Copyright (c) 2008-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#ifndef TEXTOBJECT_H
#define TEXTOBJECT_H
#pragma once
#include "GDCore/Project/ObjectConfiguration.h"
namespace gd {
class Project;
@@ -29,11 +29,11 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
/** \brief Change the text.
*/
inline void SetString(const gd::String& str) { text = str; };
inline void SetText(const gd::String& str) { text = str; };
/** \brief Get the text.
*/
inline const gd::String& GetString() const { return text; };
inline const gd::String& GetText() const { return text; };
/** \brief Change the character size.
*/
@@ -66,31 +66,63 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
void SetSmooth(bool smooth) { smoothed = smooth; };
bool IsSmoothed() const { return smoothed; };
void SetColor(unsigned int r, unsigned int g, unsigned int b) {
colorR = r;
colorG = g;
colorB = b;
void SetColor(const gd::String& color_) {
color = color_;
};
unsigned int GetColorR() const { return colorR; };
unsigned int GetColorG() const { return colorG; };
unsigned int GetColorB() const { return colorB; };
inline const gd::String& GetColor() const { return color; };
void SetOutlineEnabled(bool smooth) { isOutlineEnabled = smooth; };
bool IsOutlineEnabled() const { return isOutlineEnabled; };
void SetOutlineThickness(double value) { outlineThickness = value; };
double GetOutlineThickness() const { return outlineThickness; };
void SetOutlineColor(const gd::String& color) {
outlineColor = color;
};
const gd::String& GetOutlineColor() const { return outlineColor; };
void SetShadowEnabled(bool smooth) { isShadowEnabled = smooth; };
bool IsShadowEnabled() const { return isShadowEnabled; };
void SetShadowColor(const gd::String& color) {
shadowColor = color;
};
const gd::String& GetShadowColor() const { return shadowColor; };
void SetShadowOpacity(double value) { shadowOpacity = value; };
double GetShadowOpacity() const { return shadowOpacity; };
void SetShadowAngle(double value) { shadowAngle = value; };
double GetShadowAngle() const { return shadowAngle; };
void SetShadowDistance(double value) { shadowDistance = value; };
double GetShadowDistance() const { return shadowDistance; };
void SetShadowBlurRadius(double value) { shadowBlurRadius = value; };
double GetShadowBlurRadius() const { return shadowBlurRadius; };
private:
virtual void DoUnserializeFrom(gd::Project& project,
const gd::SerializerElement& element);
#if defined(GD_IDE_ONLY)
virtual void DoSerializeTo(gd::SerializerElement& element) const;
#endif
gd::String text;
double characterSize;
gd::String fontName;
bool smoothed;
bool bold, italic, underlined;
unsigned int colorR;
unsigned int colorG;
unsigned int colorB;
gd::String color;
gd::String textAlignment;
};
#endif // TEXTOBJECT_H
bool isOutlineEnabled;
double outlineThickness;
gd::String outlineColor;
bool isShadowEnabled;
gd::String shadowColor;
double shadowOpacity;
double shadowAngle;
double shadowDistance;
double shadowBlurRadius;
};

View File

@@ -69,17 +69,23 @@ namespace gdjs {
this._object._outlineColor[1],
this._object._outlineColor[2]
);
style.strokeThickness = this._object._outlineThickness;
style.strokeThickness = this._object._isOutlineEnabled
? this._object._outlineThickness
: 0;
style.dropShadow = this._object._shadow;
style.dropShadowColor = gdjs.rgbToHexNumber(
this._object._shadowColor[0],
this._object._shadowColor[1],
this._object._shadowColor[2]
);
style.dropShadowAlpha = this._object._shadowOpacity / 255;
style.dropShadowBlur = this._object._shadowBlur;
style.dropShadowAngle = this._object._shadowAngle;
style.dropShadowAngle = gdjs.toRad(this._object._shadowAngle);
style.dropShadowDistance = this._object._shadowDistance;
style.padding = this._object._padding;
const extraPaddingForShadow = style.dropShadow
? style.dropShadowDistance + style.dropShadowBlur
: 0;
style.padding = Math.ceil(this._object._padding + extraPaddingForShadow);
// Prevent spikey outlines by adding a miter limit
style.miterLimit = 3;

View File

@@ -5,28 +5,35 @@
namespace gdjs {
/** Base parameters for gdjs.TextRuntimeObject */
export type TextObjectDataType = {
/** The size of the characters */
characterSize: number;
/** The font name */
font: string;
/** Is Bold? */
bold: boolean;
/** Is Italic? */
italic: boolean;
/** Is Underlined? */
underlined: boolean;
/** The text color in an RGB representation */
color: {
/** The Red level from 0 to 255 */
r: number;
/** The Green level from 0 to 255 */
g: number;
/** The Blue level from 0 to 255 */
b: number;
content: {
/** The size of the characters */
characterSize: number;
/** The font name */
font: string;
/** Is Bold? */
bold: boolean;
/** Is Italic? */
italic: boolean;
/** Is Underlined? */
underlined: boolean;
/** The text color in an RGB representation */
color: string;
/** The text of the object */
text: string;
textAlignment: string;
isOutlineEnabled: boolean;
outlineThickness: float;
/** The outline color in an RGB representation */
outlineColor: string;
isShadowEnabled: boolean;
/** The shadow color in an RGB representation */
shadowColor: string;
shadowOpacity: float;
shadowDistance: float;
shadowAngle: float;
shadowBlurRadius: float;
};
/** The text of the object */
string: string;
textAlignment: string;
};
export type TextObjectData = ObjectData & TextObjectDataType;
@@ -49,14 +56,20 @@ namespace gdjs {
opacity: float = 255;
_textAlign: string = 'left';
_wrapping: boolean = false;
_wrappingWidth: float = 1;
_outlineThickness: number = 0;
_outlineColor: integer[] = [255, 255, 255];
_shadow: boolean = false;
_shadowColor: integer[] = [0, 0, 0];
_shadowDistance: number = 1;
_shadowBlur: integer = 1;
_shadowAngle: float = 0;
// A wrapping of 1 makes games crash on Firefox
_wrappingWidth: float = 100;
_isOutlineEnabled: boolean;
_outlineThickness: float;
_outlineColor: integer[];
_shadow: boolean;
_shadowColor: integer[];
_shadowOpacity: float;
_shadowDistance: float;
_shadowAngle: float;
_shadowBlur: float;
_padding: integer = 5;
_str: string;
_renderer: gdjs.TextRuntimeObjectRenderer;
@@ -74,18 +87,27 @@ namespace gdjs {
textObjectData: TextObjectData
) {
super(instanceContainer, textObjectData);
this._characterSize = Math.max(1, textObjectData.characterSize);
this._fontName = textObjectData.font;
this._bold = textObjectData.bold;
this._italic = textObjectData.italic;
this._underlined = textObjectData.underlined;
this._color = [
textObjectData.color.r,
textObjectData.color.g,
textObjectData.color.b,
];
this._str = textObjectData.string;
this._textAlign = textObjectData.textAlignment;
const content = textObjectData.content;
this._characterSize = Math.max(1, content.characterSize);
this._fontName = content.font;
this._bold = content.bold;
this._italic = content.italic;
this._underlined = content.underlined;
this._color = gdjs.rgbOrHexToRGBColor(content.color);
this._str = content.text;
this._textAlign = content.textAlignment;
this._isOutlineEnabled = content.isOutlineEnabled;
this._outlineThickness = content.outlineThickness;
this._outlineColor = gdjs.rgbOrHexToRGBColor(content.outlineColor);
this._shadow = content.isShadowEnabled;
this._shadowColor = gdjs.rgbOrHexToRGBColor(content.shadowColor);
this._shadowOpacity = content.shadowOpacity;
this._shadowDistance = content.shadowDistance;
this._shadowBlur = content.shadowBlurRadius;
this._shadowAngle = content.shadowAngle;
this._renderer = new gdjs.TextRuntimeObjectRenderer(
this,
instanceContainer
@@ -99,40 +121,58 @@ namespace gdjs {
oldObjectData: TextObjectData,
newObjectData: TextObjectData
): boolean {
if (oldObjectData.characterSize !== newObjectData.characterSize) {
this.setCharacterSize(newObjectData.characterSize);
const oldContent = oldObjectData.content;
const newContent = newObjectData.content;
if (oldContent.characterSize !== newContent.characterSize) {
this.setCharacterSize(newContent.characterSize);
}
if (oldObjectData.font !== newObjectData.font) {
this.setFontName(newObjectData.font);
if (oldContent.font !== newContent.font) {
this.setFontName(newContent.font);
}
if (oldObjectData.bold !== newObjectData.bold) {
this.setBold(newObjectData.bold);
if (oldContent.bold !== newContent.bold) {
this.setBold(newContent.bold);
}
if (oldObjectData.italic !== newObjectData.italic) {
this.setItalic(newObjectData.italic);
if (oldContent.italic !== newContent.italic) {
this.setItalic(newContent.italic);
}
if (
oldObjectData.color.r !== newObjectData.color.r ||
oldObjectData.color.g !== newObjectData.color.g ||
oldObjectData.color.b !== newObjectData.color.b
) {
this.setColor(
'' +
newObjectData.color.r +
';' +
newObjectData.color.g +
';' +
newObjectData.color.b
);
if (oldContent.color !== newContent.color) {
this.setColor(newContent.color);
}
if (oldObjectData.string !== newObjectData.string) {
this.setString(newObjectData.string);
if (oldContent.text !== newContent.text) {
this.setText(newContent.text);
}
if (oldObjectData.underlined !== newObjectData.underlined) {
if (oldContent.underlined !== newContent.underlined) {
return false;
}
if (oldObjectData.textAlignment !== newObjectData.textAlignment) {
this.setTextAlignment(newObjectData.textAlignment);
if (oldContent.textAlignment !== newContent.textAlignment) {
this.setTextAlignment(newContent.textAlignment);
}
if (oldContent.isOutlineEnabled !== newContent.isOutlineEnabled) {
this.setOutlineEnabled(newContent.isOutlineEnabled);
}
if (oldContent.outlineThickness !== newContent.outlineThickness) {
this.setOutlineThickness(newContent.outlineThickness);
}
if (oldContent.outlineColor !== newContent.outlineColor) {
this.setOutlineColor(newContent.outlineColor);
}
if (oldContent.isShadowEnabled !== newContent.isShadowEnabled) {
this.showShadow(newContent.isShadowEnabled);
}
if (oldContent.shadowColor !== newContent.shadowColor) {
this.setShadowColor(newContent.shadowColor);
}
if (oldContent.shadowOpacity !== newContent.shadowOpacity) {
this.setShadowOpacity(newContent.shadowOpacity);
}
if (oldContent.shadowDistance !== newContent.shadowDistance) {
this.setShadowDistance(newContent.shadowDistance);
}
if (oldContent.shadowAngle !== newContent.shadowAngle) {
this.setShadowAngle(newContent.shadowAngle);
}
if (oldContent.shadowBlurRadius !== newContent.shadowBlurRadius) {
this.setShadowBlurRadius(newContent.shadowBlurRadius);
}
return true;
}
@@ -155,8 +195,8 @@ namespace gdjs {
*/
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
if (initialInstanceData.customSize) {
this.setWrapping(true);
this.setWrappingWidth(initialInstanceData.width);
this.setWrapping(true);
} else {
this.setWrapping(false);
}
@@ -173,7 +213,7 @@ namespace gdjs {
/**
* Set object position on X axis.
*/
setX(x): void {
setX(x: float): void {
super.setX(x);
this._updateTextPosition();
}
@@ -181,7 +221,7 @@ namespace gdjs {
/**
* Set object position on Y axis.
*/
setY(y): void {
setY(y: float): void {
super.setY(y);
this._updateTextPosition();
}
@@ -198,7 +238,7 @@ namespace gdjs {
/**
* Set object opacity.
*/
setOpacity(opacity): void {
setOpacity(opacity: float): void {
if (opacity < 0) {
opacity = 0;
}
@@ -292,7 +332,7 @@ namespace gdjs {
* Set bold for the object text.
* @param enable {boolean} true to have a bold text, false otherwise.
*/
setBold(enable): void {
setBold(enable: boolean): void {
this._bold = enable;
this._renderer.updateStyle();
}
@@ -308,7 +348,7 @@ namespace gdjs {
* Set italic for the object text.
* @param enable {boolean} true to have an italic text, false otherwise.
*/
setItalic(enable): void {
setItalic(enable: boolean): void {
this._italic = enable;
this._renderer.updateStyle();
}
@@ -474,17 +514,22 @@ namespace gdjs {
if (width <= 1) {
width = 1;
}
if (this._wrappingWidth === width) return;
if (this._wrappingWidth === width) {
return;
}
this._wrappingWidth = width;
this._renderer.updateStyle();
this.invalidateHitboxes();
if (this._wrapping) {
this._renderer.updateStyle();
this.invalidateHitboxes();
}
}
/**
* Set the outline for the text object.
* @param str color as a "R;G;B" string, for example: "255;0;0"
* @param thickness thickness of the outline (0 = disabled)
* @deprecated Prefer independent setters.
*/
setOutline(str: string, thickness: number): void {
const color = str.split(';');
@@ -498,12 +543,48 @@ namespace gdjs {
this._renderer.updateStyle();
}
isOutlineEnabled(): boolean {
return this._isOutlineEnabled;
}
setOutlineEnabled(enable: boolean): void {
this._isOutlineEnabled = enable;
this._renderer.updateStyle();
}
/**
* Get the outline thickness of the text object.
* @return the outline thickness
*/
getOutlineThickness(): number {
return this._outlineThickness;
}
/**
* Set the outline thickness of the text object.
* @param value the outline thickness
*/
setOutlineThickness(value: float): void {
this._outlineThickness = value;
this._renderer.updateStyle();
}
/**
* Set the shadow color of the text object.
* @param color the shadow color as a "R;G;B" string, for example: "255;0;0"
*/
setOutlineColor(color: string): void {
this._outlineColor = gdjs.rgbOrHexToRGBColor(color);
this._renderer.updateStyle();
}
/**
* Set the shadow for the text object.
* @param str color as a "R;G;B" string, for example: "255;0;0"
* @param distance distance between the shadow and the text, in pixels.
* @param blur amount of shadow blur, in pixels.
* @param angle shadow offset direction, in degrees.
* @deprecated Prefer independent setters.
*/
setShadow(
str: string,
@@ -525,6 +606,96 @@ namespace gdjs {
this._renderer.updateStyle();
}
isShadowEnabled(): boolean {
return this._shadow;
}
/**
* Show the shadow of the text object.
* @param enable true to show the shadow, false to hide it
*/
showShadow(enable: boolean): void {
this._shadow = enable;
this._renderer.updateStyle();
}
/**
* Get the shadow opacity of the text object.
* @return the opacity (0 - 255)
*/
getShadowOpacity(): number {
return this._shadowOpacity;
}
/**
* Set the shadow opacity of the text object.
* @param value the opacity (0 - 255)
*/
setShadowOpacity(value: float): void {
this._shadowOpacity = value;
this._renderer.updateStyle();
}
/**
* Get the shadow offset distance of the text object.
* @return the shadow offset distance
*/
getShadowDistance(): number {
return this._shadowDistance;
}
/**
* Set the shadow offset distance of the text object.
* @param value the shadow offset distance
*/
setShadowDistance(value: float): void {
this._shadowDistance = value;
this._renderer.updateStyle();
}
/**
* Get the shadow offset angle of the text object.
* @return the shadow offset angle in degrees
*/
getShadowAngle(): number {
return this._shadowAngle;
}
/**
* Set the shadow offset angle of the text object.
* @param value the shadow offset angle in degrees
*/
setShadowAngle(value: float): void {
this._shadowAngle = value;
this._renderer.updateStyle();
}
/**
* Get the shadow blur radius of the text object.
* @return the shadow blur radius
*/
getShadowBlurRadius(): number {
return this._shadowBlur;
}
/**
* Set the shadow blur radius of the text object.
* @param value the shadow blur radius
*/
setShadowBlurRadius(value: float): void {
this._shadowBlur = value;
this._renderer.updateStyle();
}
/**
* Set the shadow color of the text object.
* @param color the shadow color as a "R;G;B" string, for example: "255;0;0"
*/
setShadowColor(color: string): void {
this._shadowColor = gdjs.rgbOrHexToRGBColor(color);
this._renderer.updateStyle();
}
/**
* Set the gradient for the text object.
* @param strFirstColor color as a "R;G;B" string, for example: "255;0;0"
@@ -578,15 +749,6 @@ namespace gdjs {
this._renderer.updateStyle();
}
/**
* Show the shadow of the text object.
* @param enable true to show the shadow, false to hide it
*/
showShadow(enable: boolean): void {
this._shadow = enable;
this._renderer.updateStyle();
}
/**
* Get padding of the text object.
* @return number of pixels around the text before it gets cropped

View File

@@ -15,10 +15,13 @@
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/** @type {ExtensionModule} */
/**
* @param {gd.PlatformExtension} extension
* @param {(translationSource: string) => string} _
* @param {GDNamespace} gd
*/
const defineTileMap = function (extension, _, gd) {
var objectTileMap = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
objectTileMap.updateProperty = function (
objectContent,
propertyName,
@@ -59,7 +62,6 @@ const defineTileMap = function (extension, _, gd) {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
objectTileMap.getProperties = function (objectContent) {
var objectProperties = new gd.MapStringPropertyDescriptor();
@@ -158,7 +160,6 @@ const defineTileMap = function (extension, _, gd) {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object
objectTileMap.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -169,7 +170,6 @@ const defineTileMap = function (extension, _, gd) {
) {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
objectTileMap.getInitialInstanceProperties = function (
content,
instance,
@@ -598,13 +598,13 @@ const defineTileMap = function (extension, _, gd) {
.setFunctionName('setHeight');
};
const defineCollisionMask = function (
extension,
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
/**
* @param {gd.PlatformExtension} extension
* @param {(translationSource: string) => string} _
* @param {GDNamespace} gd
*/
const defineCollisionMask = function (extension, _, gd) {
var collisionMaskObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
collisionMaskObject.updateProperty = function (
objectContent,
propertyName,
@@ -649,7 +649,6 @@ const defineCollisionMask = function (
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
collisionMaskObject.getProperties = function (objectContent) {
var objectProperties = new gd.MapStringPropertyDescriptor();
@@ -758,7 +757,6 @@ const defineCollisionMask = function (
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object
collisionMaskObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -769,7 +767,6 @@ const defineCollisionMask = function (
) {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
collisionMaskObject.getInitialInstanceProperties = function (
content,
instance,
@@ -1023,6 +1020,7 @@ const defineCollisionMask = function (
.setFunctionName('setHeight');
};
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
@@ -1221,33 +1219,33 @@ module.exports = {
updateTileMap() {
// Get the tileset resource to use
const tilemapAtlasImage = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('tilemapAtlasImage')
.getValue();
const tilemapJsonFile = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('tilemapJsonFile')
.getValue();
const tilesetJsonFile = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('tilesetJsonFile')
.getValue();
const layerIndex = parseInt(
this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('layerIndex')
.getValue(),
10
);
const levelIndex = parseInt(
this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('levelIndex')
.getValue(),
10
);
const displayMode = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('displayMode')
.getValue();
@@ -1289,7 +1287,7 @@ module.exports = {
}
/** @type {TileMapHelper.TileTextureCache} */
const textureCache = manager.getOrLoadTextureCache(
manager.getOrLoadTextureCache(
this._loadTileMapWithCallback.bind(this),
(textureName) =>
this._pixiResourcesLoader.getPIXITexture(
@@ -1504,43 +1502,45 @@ module.exports = {
* This is used to reload the Tilemap
*/
updateTileMap() {
// Get the tileset resource to use
// This might become useful in the future
/*
const tilemapAtlasImage = this._associatedObjectConfiguration
.getProperties(this.project)
.get('tilemapAtlasImage')
.getValue();
*/
const tilemapJsonFile = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('tilemapJsonFile')
.getValue();
const tilesetJsonFile = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('tilesetJsonFile')
.getValue();
const collisionMaskTag = this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('collisionMaskTag')
.getValue();
const outlineColor = objectsRenderingService.rgbOrHexToHexNumber(
this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('outlineColor')
.getValue()
);
const fillColor = objectsRenderingService.rgbOrHexToHexNumber(
this._associatedObjectConfiguration
.getProperties(this.project)
.getProperties()
.get('fillColor')
.getValue()
);
const outlineOpacity =
this._associatedObjectConfiguration
.getProperties(this.project)
+this._associatedObjectConfiguration
.getProperties()
.get('outlineOpacity')
.getValue() / 255;
const fillOpacity =
this._associatedObjectConfiguration
.getProperties(this.project)
+this._associatedObjectConfiguration
.getProperties()
.get('fillOpacity')
.getValue() / 255;
const outlineSize = 1;

View File

@@ -1,5 +1,5 @@
declare namespace PIXI {
namespace tilemap {
export namespace tilemap {
/**
* The renderer plugin for canvas. It isn't registered by default.
*

View File

@@ -96,7 +96,7 @@ TopDownMovementBehavior::GetProperties(
.SetType("Boolean");
gd::String viewpoint = behaviorContent.GetStringAttribute("viewpoint");
gd::String viewpointStr = _("Viewpoint");
gd::String viewpointStr = _("Top-Down");
if (viewpoint == "TopDown")
viewpointStr = _("Top-Down");
else if (viewpoint == "PixelIsometry")
@@ -108,6 +108,7 @@ TopDownMovementBehavior::GetProperties(
properties["Viewpoint"]
.SetLabel(_("Viewpoint"))
.SetGroup(_("Viewpoint"))
.SetAdvanced()
.SetValue(viewpointStr)
.SetType("Choice")
.AddExtraInfo(_("Top-Down"))
@@ -117,6 +118,7 @@ TopDownMovementBehavior::GetProperties(
properties["CustomIsometryAngle"]
.SetLabel(_("Custom isometry angle (between 1deg and 44deg)"))
.SetGroup(_("Viewpoint"))
.SetAdvanced()
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
.SetValue(gd::String::From(
@@ -126,6 +128,7 @@ TopDownMovementBehavior::GetProperties(
properties["MovementAngleOffset"]
.SetLabel(_("Movement angle offset"))
.SetGroup(_("Viewpoint"))
.SetAdvanced()
.SetType("Number")
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
.SetValue(gd::String::From(

View File

@@ -563,7 +563,7 @@ module.exports = {
'Progress',
_('Tween progress'),
_('the progress of a tween (between 0.0 and 1.0)'),
_('the progress of a tween'),
_('the progress of the scene tween _PARAM1_'),
_('Scene Tweens'),
'JsPlatform/Extensions/tween_behavior32.png'
)
@@ -593,7 +593,6 @@ module.exports = {
const tweenBehavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
tweenBehavior.updateProperty = function (
behaviorContent,
propertyName,
@@ -602,13 +601,11 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
tweenBehavior.getProperties = function (behaviorContent) {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
return behaviorProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
tweenBehavior.initializeContent = function (behaviorContent) {};
const behavior = extension
@@ -622,6 +619,7 @@ module.exports = {
'',
'JsPlatform/Extensions/tween_behavior32.png',
'TweenBehavior',
// @ts-ignore - TODO: Fix tweenBehavior being an BehaviorJsImplementation instead of an Behavior
tweenBehavior,
new gd.BehaviorsSharedData()
)
@@ -1770,7 +1768,7 @@ module.exports = {
)
.setDefaultValue('no')
.getCodeExtraInformation()
.setFunctionName('addNumberEffectPropertyTween');
.setFunctionName('addColorEffectPropertyTween');
// deprecated
behavior
@@ -2071,7 +2069,7 @@ module.exports = {
'Progress',
_('Tween progress'),
_('the progress of a tween (between 0.0 and 1.0)'),
_('the progress of a tween'),
_('the progress of the tween _PARAM2_'),
'',
'JsPlatform/Extensions/tween_behavior32.png'
)

View File

@@ -126,18 +126,25 @@ describe('gdjs.TweenRuntimeBehavior', () => {
name: behaviorName,
},
],
characterSize: 20,
font: '',
bold: false,
italic: false,
underlined: false,
color: {
r: 0,
g: 0,
b: 0,
content: {
characterSize: 20,
font: '',
bold: false,
italic: false,
underlined: false,
color: '0;0;0',
text: '',
textAlignment: 'left',
isOutlineEnabled: false,
outlineThickness: 2,
outlineColor: '255;255;255',
isShadowEnabled: false,
shadowColor: '0;0;0',
shadowOpacity: 128,
shadowDistance: 4,
shadowAngle: 90,
shadowBlurRadius: 2,
},
string: '',
textAlignment: 'left',
});
runtimeScene.addObject(object);
return object;

View File

@@ -126,18 +126,25 @@ describe('gdjs.TweenRuntimeBehavior', () => {
name: behaviorName,
},
],
characterSize: 20,
font: '',
bold: false,
italic: false,
underlined: false,
color: {
r: 0,
g: 0,
b: 0,
content: {
characterSize: 20,
font: '',
bold: false,
italic: false,
underlined: false,
color: '0;0;0',
text: '',
textAlignment: 'left',
isOutlineEnabled: false,
outlineThickness: 2,
outlineColor: '255;255;255',
isShadowEnabled: false,
shadowColor: '0;0;0',
shadowOpacity: 128,
shadowDistance: 4,
shadowAngle: 90,
shadowBlurRadius: 2,
},
string: '',
textAlignment: 'left',
});
runtimeScene.addObject(object);
return object;

View File

@@ -15,27 +15,44 @@ namespace gdjs {
}
function isScalable(
o: gdjs.RuntimeObject
): o is gdjs.RuntimeObject & gdjs.Scalable {
//@ts-ignore We are checking if the methods are present.
return o.setScaleX && o.setScaleY && o.getScaleX && o.getScaleY;
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject & gdjs.Scalable {
return (
//@ts-ignore We are checking if the methods are present.
object.setScaleX &&
//@ts-ignore
object.setScaleY &&
//@ts-ignore
object.getScaleX &&
//@ts-ignore
object.getScaleY
);
}
function isOpaque(
o: gdjs.RuntimeObject
): o is gdjs.RuntimeObject & gdjs.OpacityHandler {
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject & gdjs.OpacityHandler {
//@ts-ignore We are checking if the methods are present.
return o.setOpacity && o.getOpacity;
return object.setOpacity && object.getOpacity;
}
function isColorable(o: gdjs.RuntimeObject): o is IColorable {
function is3D(
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject & gdjs.Base3DHandler {
//@ts-ignore We are checking if the methods are present.
return o.setColor && o.getColor;
return object.getZ && object.setZ;
}
function isCharacterScalable(o: gdjs.RuntimeObject): o is ICharacterScalable {
function isColorable(object: gdjs.RuntimeObject): object is IColorable {
//@ts-ignore We are checking if the methods are present.
return o.setCharacterSize && o.getCharacterSize;
return object.setColor && object.getColor;
}
function isCharacterScalable(
object: gdjs.RuntimeObject
): object is ICharacterScalable {
//@ts-ignore We are checking if the methods are present.
return object.setCharacterSize && object.getCharacterSize;
}
const linearInterpolation = gdjs.evtTools.common.lerp;
@@ -516,7 +533,7 @@ namespace gdjs {
timeSource: gdjs.evtTools.tween.TimeSource
) {
const { owner } = this;
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
if (!is3D(owner)) return;
this._tweens.addSimpleTween(
identifier,
@@ -621,7 +638,7 @@ namespace gdjs {
destroyObjectWhenFinished: boolean
) {
const { owner } = this;
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
if (!is3D(owner)) return;
this._tweens.addSimpleTween(
identifier,
@@ -654,7 +671,7 @@ namespace gdjs {
destroyObjectWhenFinished: boolean
) {
const { owner } = this;
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
if (!is3D(owner)) return;
this._tweens.addSimpleTween(
identifier,
@@ -808,7 +825,10 @@ namespace gdjs {
const owner = this.owner;
if (!isScalable(owner)) return;
const owner3d = owner instanceof gdjs.RuntimeObject3D ? owner : null;
// This action doesn't require 3D capabilities.
// So, gdjs.RuntimeObject3D may not exist
// when the 3D extension is not used.
const owner3d = is3D(owner) ? owner : null;
const setValue = scaleFromCenterOfObject
? (scale: float) => {
@@ -1750,7 +1770,7 @@ namespace gdjs {
timeSource: gdjs.evtTools.tween.TimeSource
) {
const { owner } = this;
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
if (!is3D(owner)) return;
this._tweens.addSimpleTween(
identifier,

View File

@@ -1,5 +1,6 @@
//@ts-check
/// <reference path="../JsExtensionTypes.d.ts" />
/**
* This is a declaration of an extension for GDevelop 5.
*
@@ -34,7 +35,6 @@ module.exports = {
.setIcon('JsPlatform/Extensions/videoicon16.png');
var videoObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object
videoObject.updateProperty = function (
objectContent,
propertyName,
@@ -59,7 +59,6 @@ module.exports = {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
videoObject.getProperties = function (objectContent) {
var objectProperties = new gd.MapStringPropertyDescriptor();
@@ -99,7 +98,6 @@ module.exports = {
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object
videoObject.updateInitialInstanceProperty = function (
objectContent,
instance,
@@ -110,7 +108,6 @@ module.exports = {
) {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object
videoObject.getInitialInstanceProperties = function (
content,
instance,
@@ -127,6 +124,7 @@ module.exports = {
_('Video'),
_('Displays a video.'),
'JsPlatform/Extensions/videoicon32.png',
// @ts-ignore - TODO: Fix videoObject being an ObjectJsImplementation instead of an ObjectConfiguration
videoObject
)
.setIncludeFile('Extensions/Video/videoruntimeobject.js')
@@ -637,7 +635,7 @@ module.exports = {
}
// Update opacity
const opacity = this._associatedObjectConfiguration
const opacity = +this._associatedObjectConfiguration
.getProperties()
.get('Opacity')
.getValue();

View File

@@ -7,7 +7,11 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Tools/EventsCodeNameMangler.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
#include "GDCore/IDE/WholeProjectBrowser.h"
#include "GDCore/Project/CustomBehavior.h"
#include "GDCore/Project/CustomBehaviorsSharedData.h"
#include "GDCore/Project/EventsBasedObject.h"
@@ -125,14 +129,25 @@ gd::ObjectMetadata &MetadataDeclarationHelper::DeclareObjectMetadata(
// Note: EventsFunctionsExtension should be used instead of
// PlatformExtension but this line will be removed soon.
.SetCategoryFullName(extension.GetCategory())
// Update Project::CreateObject when default behavior are added.
.AddDefaultBehavior("EffectCapability::EffectBehavior")
.AddDefaultBehavior("ResizableCapability::ResizableBehavior")
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
.AddDefaultBehavior("FlippableCapability::FlippableBehavior")
.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
.AddDefaultBehavior("FlippableCapability::FlippableBehavior");
if (eventsBasedObject.IsRenderedIn3D()) {
objectMetadata.MarkAsRenderedIn3D();
objectMetadata
.MarkAsRenderedIn3D()
.AddDefaultBehavior("Scene3D::Base3DBehavior");
}
else {
objectMetadata.AddDefaultBehavior("EffectCapability::EffectBehavior");
objectMetadata.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
}
if (eventsBasedObject.IsAnimatable()) {
objectMetadata
.AddDefaultBehavior("AnimatableCapability::AnimatableBehavior");
}
if (eventsBasedObject.IsTextContainer()) {
objectMetadata
.AddDefaultBehavior("TextContainerCapability::TextContainerBehavior");
}
// TODO EBO Use full type to identify object to avoid collision.
@@ -933,18 +948,15 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
gd::String MetadataDeclarationHelper::GetStringifiedExtraInfo(
const gd::PropertyDescriptor &property) {
gd::String stringifiedExtraInfo = "";
if (property.GetType() == "Choice") {
stringifiedExtraInfo += "[";
for (size_t i = 0; i < property.GetExtraInfo().size(); i++) {
stringifiedExtraInfo += property.GetExtraInfo().at(i);
if (i < property.GetExtraInfo().size() - 1) {
stringifiedExtraInfo += ",";
}
}
stringifiedExtraInfo += "]";
if (property.GetType() != "Choice") {
return "";
}
return stringifiedExtraInfo;
SerializerElement element;
element.ConsiderAsArray();
for (auto&& value : property.GetExtraInfo()) {
element.AddChild("").SetStringValue(value);
}
return Serializer::ToJSON(element);
}
gd::String
@@ -968,6 +980,9 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
addObjectAndBehaviorParameters) {
auto &propertyType = property.GetType();
auto group = (eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName())
+ " " + property.GetGroup() + " properties";
auto uncapitalizedLabel =
UncapitalizeFirstLetter(property.GetLabel()) || property.GetName();
if (propertyType == "Boolean") {
@@ -977,7 +992,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
.FindAndReplace("<property_name>", uncapitalizedLabel),
_("Property <property_name> of _PARAM0_ is true")
.FindAndReplace("<property_name>", uncapitalizedLabel),
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
group,
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
addObjectAndBehaviorParameters(conditionMetadata);
conditionMetadata.SetFunctionName(getterName);
@@ -992,7 +1007,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
.FindAndReplace("<property_value>",
"_PARAM" + gd::String::From(valueParameterIndex) +
"_"),
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
group,
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
addObjectAndBehaviorParameters(setterActionMetadata);
setterActionMetadata
@@ -1007,7 +1022,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
.FindAndReplace("<property_name>", uncapitalizedLabel),
_("Toggle property <property_name> of _PARAM0_")
.FindAndReplace("<property_name>", uncapitalizedLabel),
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
group,
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
addObjectAndBehaviorParameters(toggleActionMetadata);
toggleActionMetadata.SetFunctionName(toggleFunctionName);
@@ -1018,13 +1033,14 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
parameterOptions.SetTypeExtraInfo(typeExtraInfo);
auto propertyInstructionMetadata =
entityMetadata.AddExpressionAndConditionAndAction(
gd::ValueTypeMetadata::ConvertPropertyTypeToValueType(propertyType),
gd::ValueTypeMetadata::GetPrimitiveValueType(
gd::ValueTypeMetadata::ConvertPropertyTypeToValueType(propertyType)),
expressionName, propertyLabel,
_("the property value for the <property_name>")
.FindAndReplace("<property_name>", uncapitalizedLabel),
_("the property value for the <property_name>")
.FindAndReplace("<property_name>", uncapitalizedLabel),
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
group,
GetExtensionIconUrl(extension));
addObjectAndBehaviorParameters(propertyInstructionMetadata);
propertyInstructionMetadata
@@ -1213,20 +1229,39 @@ void MetadataDeclarationHelper::DeclareObjectInternalInstructions(
// Objects are identified by their name alone.
auto &objectType = eventsBasedObject.GetName();
objectMetadata
.AddScopedAction(
"SetRotationCenter", _("Center of rotation"),
_("Change the center of rotation of an object relatively to the "
"object origin."),
_("Change the center of rotation of _PARAM0_ to _PARAM1_, _PARAM2_"),
_("Angle"), "res/actions/position24_black.png",
"res/actions/position_black.png")
.AddParameter("object", _("Object"), objectType)
.AddParameter("number", _("X position"))
.AddParameter("number", _("Y position"))
.MarkAsAdvanced()
.SetPrivate()
.SetFunctionName("setRotationCenter");
if (eventsBasedObject.IsRenderedIn3D()) {
objectMetadata
.AddScopedAction(
"SetRotationCenter", _("Center of rotation"),
_("Change the center of rotation of an object relatively to the "
"object origin."),
_("Change the center of rotation of _PARAM0_ to _PARAM1_ ; _PARAM2_ ; _PARAM3_"),
_("Angle"), "res/actions/position24_black.png",
"res/actions/position_black.png")
.AddParameter("object", _("Object"), objectType)
.AddParameter("number", _("X position"))
.AddParameter("number", _("Y position"))
.AddParameter("number", _("Z position"))
.MarkAsAdvanced()
.SetPrivate()
.SetFunctionName("setRotationCenter3D");
}
else {
objectMetadata
.AddScopedAction(
"SetRotationCenter", _("Center of rotation"),
_("Change the center of rotation of an object relatively to the "
"object origin."),
_("Change the center of rotation of _PARAM0_ to _PARAM1_ ; _PARAM2_"),
_("Angle"), "res/actions/position24_black.png",
"res/actions/position_black.png")
.AddParameter("object", _("Object"), objectType)
.AddParameter("number", _("X position"))
.AddParameter("number", _("Y position"))
.MarkAsAdvanced()
.SetPrivate()
.SetFunctionName("setRotationCenter");
}
}
void MetadataDeclarationHelper::AddParameter(
@@ -1486,7 +1521,7 @@ gd::BehaviorMetadata &MetadataDeclarationHelper::GenerateBehaviorMetadata(
}
gd::ObjectMetadata &MetadataDeclarationHelper::GenerateObjectMetadata(
const gd::Project &project, gd::PlatformExtension &extension,
gd::Project &project, gd::PlatformExtension &extension,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsBasedObject &eventsBasedObject,
std::map<gd::String, gd::String> &objectMethodMangledNames) {
@@ -1521,7 +1556,58 @@ gd::ObjectMetadata &MetadataDeclarationHelper::GenerateObjectMetadata(
instructionOrExpression.SetPrivate();
}
UpdateCustomObjectDefaultBehaviors(project, objectMetadata);
return objectMetadata;
}
class DefaultBehaviorUpdater : public gd::ArbitraryObjectsWorker {
public:
DefaultBehaviorUpdater(const gd::Project &project_,
const gd::ObjectMetadata &objectMetadata_)
: project(project_), objectMetadata(objectMetadata_){};
virtual ~DefaultBehaviorUpdater(){};
private:
void DoVisitObject(gd::Object &object) override {
if (object.GetType() != objectMetadata.GetName()) {
return;
}
auto &defaultBehaviorTypes = objectMetadata.GetDefaultBehaviors();
for (const gd::String &behaviorName : object.GetAllBehaviorNames()) {
const auto &behavior = object.GetBehavior(behaviorName);
if (behavior.IsDefaultBehavior()) {
object.RemoveBehavior(behaviorName);
}
}
auto &platform = project.GetCurrentPlatform();
for (const gd::String &behaviorType : defaultBehaviorTypes) {
auto &behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(platform, behaviorType);
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
gd::LogWarning("Object: " + object.GetType() +
" has an unknown default behavior: " + behaviorType);
continue;
}
const gd::String &behaviorName = behaviorMetadata.GetDefaultName();
auto *behavior =
object.AddNewBehavior(project, behaviorType, behaviorName);
behavior->SetDefaultBehavior(true);
}
}
const gd::Project &project;
const gd::ObjectMetadata &objectMetadata;
};
void MetadataDeclarationHelper::UpdateCustomObjectDefaultBehaviors(
gd::Project &project, const gd::ObjectMetadata &objectMetadata) {
gd::WholeProjectBrowser projectBrowser;
auto defaultBehaviorUpdater = DefaultBehaviorUpdater(project, objectMetadata);
projectBrowser.ExposeObjects(project, defaultBehaviorUpdater);
}
} // namespace gdjs

View File

@@ -61,7 +61,7 @@ public:
std::map<gd::String, gd::String> &behaviorMethodMangledNames);
static gd::ObjectMetadata &GenerateObjectMetadata(
const gd::Project &project, gd::PlatformExtension &extension,
gd::Project &project, gd::PlatformExtension &extension,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsBasedObject &eventsBasedObject,
std::map<gd::String, gd::String> &objectMethodMangledNames);
@@ -313,6 +313,10 @@ private:
gd::MultipleInstructionMetadata &multipleInstructionMetadata,
const int userDefinedFirstParameterIndex);
static void
UpdateCustomObjectDefaultBehaviors(gd::Project &project,
const gd::ObjectMetadata &objectMetadata);
static gd::String RemoveTrailingDot(const gd::String &description);
static gd::String

View File

@@ -78,7 +78,7 @@ gd::String ObjectCodeGenerator::GenerateRuntimeObjectCompleteCode(
methodFullyQualifiedName,
"that._onceTriggers",
functionName == doStepPreEventsFunctionName
? GenerateDoStepPreEventsPreludeCode()
? GenerateDoStepPreEventsPreludeCode(eventsBasedObject)
: "",
functionName == onCreatedFunctionName
? "gdjs.CustomRuntimeObject.prototype.onCreated.call(this);\n"
@@ -109,6 +109,77 @@ gd::String ObjectCodeGenerator::GenerateRuntimeObjectCompleteCode(
}
return updateFromObjectCode;
},
// generateInitializeAnimatableCode
[&]() {
return gd::String(R"jscode_template(
this._animator = new gdjs.SpriteAnimator(
objectData.animatable.animations,
gdjs.RENDERER_CLASS_NAME.getAnimationFrameTextureManager(
parentInstanceContainer.getGame().getImageManager()));
)jscode_template")
.FindAndReplace("RENDERER_CLASS_NAME", eventsBasedObject.IsRenderedIn3D() ? "CustomRuntimeObject3DRenderer" : "CustomRuntimeObject2DRenderer");
},
// generateAnimatableCode
[&]() {
return gd::String(R"jscode_template(
// gdjs.Animatable interface implementation
getAnimator() {
return this._animator;
}
getAnimationIndex() {
return this._animator.getAnimationIndex();
}
setAnimationIndex(animationIndex) {
this._animator.setAnimationIndex(animationIndex);
}
getAnimationName() {
return this._animator.getAnimationName();
}
setAnimationName(animationName) {
this._animator.setAnimationName(animationName);
}
hasAnimationEnded() {
return this._animator.hasAnimationEnded();
}
isAnimationPaused() {
return this._animator.isAnimationPaused();
}
pauseAnimation() {
this._animator.pauseAnimation();
}
resumeAnimation() {
this._animator.resumeAnimation();
}
getAnimationSpeedScale() {
return this._animator.getAnimationSpeedScale();
}
setAnimationSpeedScale(ratio) {
this._animator.setAnimationSpeedScale(ratio);
}
getAnimationElapsedTime() {
return this._animator.getAnimationElapsedTime();
}
setAnimationElapsedTime(time) {
this._animator.setAnimationElapsedTime(time);
}
getAnimationDuration() {
return this._animator.getAnimationDuration();
}
)jscode_template");
},
// generateTextContainerCode
[&]() {
return gd::String(R"jscode_template(
// gdjs.TextContainer interface implementation
_text = '';
getText() {
return this._text;
}
setText(text) {
this._text = text;
}
)jscode_template");
});
}
@@ -119,14 +190,17 @@ gd::String ObjectCodeGenerator::GenerateRuntimeObjectTemplateCode(
std::function<gd::String()> generateInitializePropertiesCode,
std::function<gd::String()> generatePropertiesCode,
std::function<gd::String()> generateMethodsCode,
std::function<gd::String()> generateUpdateFromObjectDataCode) {
std::function<gd::String()> generateUpdateFromObjectDataCode,
std::function<gd::String()> generateInitializeAnimatableCode,
std::function<gd::String()> generateAnimatableCode,
std::function<gd::String()> generateTextContainerCode) {
return gd::String(R"jscode_template(
CODE_NAMESPACE = CODE_NAMESPACE || {};
/**
* Object generated from OBJECT_FULL_NAME
*/
CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends gdjs.CustomRuntimeObject {
CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends RUNTIME_OBJECT_BASE_CLASS_NAME {
constructor(parentInstanceContainer, objectData) {
super(parentInstanceContainer, objectData);
this._parentInstanceContainer = parentInstanceContainer;
@@ -134,6 +208,7 @@ CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends
this._onceTriggers = new gdjs.OnceTriggers();
this._objectData = {};
INITIALIZE_PROPERTIES_CODE
INITIALIZE_ANIMATABLE_CODE
// It calls the onCreated super implementation at the end.
this.onCreated();
@@ -149,6 +224,10 @@ CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends
// Properties:
PROPERTIES_CODE
ANIMATABLE_CODE
TEXT_CONTAINER_CODE
}
// Methods:
@@ -161,11 +240,19 @@ gdjs.registerObject("EXTENSION_NAME::OBJECT_NAME", CODE_NAMESPACE.RUNTIME_OBJECT
.FindAndReplace("OBJECT_FULL_NAME", eventsBasedObject.GetFullName())
.FindAndReplace("RUNTIME_OBJECT_CLASSNAME",
eventsBasedObject.GetName())
.FindAndReplace("RUNTIME_OBJECT_BASE_CLASS_NAME",
eventsBasedObject.IsRenderedIn3D() ? "gdjs.CustomRuntimeObject3D" : "gdjs.CustomRuntimeObject2D")
.FindAndReplace("CODE_NAMESPACE", codeNamespace)
.FindAndReplace("INITIALIZE_PROPERTIES_CODE",
generateInitializePropertiesCode())
.FindAndReplace("INITIALIZE_ANIMATABLE_CODE",
eventsBasedObject.IsAnimatable()
? generateInitializeAnimatableCode()
: "")
.FindAndReplace("UPDATE_FROM_OBJECT_DATA_CODE", generateUpdateFromObjectDataCode())
.FindAndReplace("PROPERTIES_CODE", generatePropertiesCode())
.FindAndReplace("ANIMATABLE_CODE", eventsBasedObject.IsAnimatable() ? generateAnimatableCode() : "")
.FindAndReplace("TEXT_CONTAINER_CODE", eventsBasedObject.IsTextContainer() ? generateTextContainerCode() : "")
.FindAndReplace("METHODS_CODE", generateMethodsCode());
;
}
@@ -263,11 +350,18 @@ CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME.prototype.doStepPreEvents = function() {
.FindAndReplace("RUNTIME_OBJECT_CLASSNAME",
eventsBasedObject.GetName())
.FindAndReplace("CODE_NAMESPACE", codeNamespace)
.FindAndReplace("PRELUDE_CODE", GenerateDoStepPreEventsPreludeCode());
}
.FindAndReplace("PRELUDE_CODE",
GenerateDoStepPreEventsPreludeCode(eventsBasedObject));}
gd::String ObjectCodeGenerator::GenerateDoStepPreEventsPreludeCode() {
return "this._onceTriggers.startNewFrame();";
gd::String ObjectCodeGenerator::GenerateDoStepPreEventsPreludeCode(
const gd::EventsBasedObject& eventsBasedObject) {
gd::String doStepPreEventsPreludeCode;
doStepPreEventsPreludeCode += "this._onceTriggers.startNewFrame();";
if (eventsBasedObject.IsAnimatable()) {
doStepPreEventsPreludeCode +=
"\nthis._animator.step(this.getElapsedTime() / 1000);";
}
return doStepPreEventsPreludeCode;
}
} // namespace gdjs

View File

@@ -74,7 +74,10 @@ class ObjectCodeGenerator {
std::function<gd::String()> generateInitializePropertiesCode,
std::function<gd::String()> generateMethodsCode,
std::function<gd::String()> generatePropertiesCode,
std::function<gd::String()> generateUpdateFromObjectDataCode);
std::function<gd::String()> generateUpdateFromObjectDataCode,
std::function<gd::String()> generateInitializeAnimatableCode,
std::function<gd::String()> generateAnimatableCode,
std::function<gd::String()> generateTextContainerCode);
gd::String GenerateRuntimeObjectPropertyTemplateCode(
const gd::EventsBasedObject& eventsBasedObject,
@@ -104,7 +107,8 @@ class ObjectCodeGenerator {
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace);
gd::String GenerateDoStepPreEventsPreludeCode();
gd::String GenerateDoStepPreEventsPreludeCode(
const gd::EventsBasedObject& eventsBasedObject);
gd::Project& project;

View File

@@ -25,6 +25,11 @@ NetworkExtension::NetworkExtension() {
"gdjs.evtTools.network.enableMetrics");
GetAllActions()["LaunchFile"].SetFunctionName("gdjs.evtTools.window.openURL");
AddDependency()
.SetName("InAppBrowser Cordova plugin")
.SetDependencyType("cordova")
.SetExportName("cordova-plugin-inappbrowser");
StripUnimplementedInstructionsAndExpressions();
}

View File

@@ -152,11 +152,6 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
scenesUsedResources);
includesFiles.push_back(codeOutputDir + "/data.js");
// Export a WebManifest with project metadata
if (!fs.WriteToFile(exportDir + "/manifest.webmanifest",
helper.GenerateWebManifest(exportedProject)))
gd::LogError("Unable to export WebManifest.");
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
helper.ExportIncludesAndLibs(resourcesFiles, exportDir, false);
@@ -204,6 +199,9 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
return false;
} else {
if (!exportProject(options.exportPath)) return false;
if (!helper.ExportHtml5Files(exportedProject, options.exportPath))
return false;
}
return true;

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