Compare commits

...

128 Commits

Author SHA1 Message Date
Florian Rival
3568a999f9 Update README [skip ci] [ci skip] 2025-09-18 16:48:48 +02:00
Florian Rival
207097bf03 Bump newIDE version 2025-09-18 10:01:44 +02:00
github-actions[bot]
0e3d2b9570 Update translations [skip ci] (#7831)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-09-18 10:01:32 +02:00
D8H
87fac429e8 Underline groups having missing behaviors in the Events Sheet (#7832) 2025-09-18 10:00:28 +02:00
D8H
3b1097931b Fix child object creation when called from another function (#7836) 2025-09-17 18:38:06 +02:00
D8H
ec7e408cd1 Fix button labels no longer updating in the editor (#7835) 2025-09-17 15:37:39 +02:00
Florian Rival
f8a99b9cfa Add prices and currency to store analytics (#7833)
Don't show in changelog
2025-09-16 15:44:35 +02:00
D8H
4c7231e6ae Fix a crash at runtime when behaviors are missing in functions (#7830) 2025-09-15 16:40:21 +02:00
D8H
883d32515c Rename "community" extensions as "experimental" extensions (#7828) 2025-09-15 14:04:31 +02:00
Florian Rival
f0f3c257fa Bump newIDE version 2025-09-14 16:11:02 +02:00
github-actions[bot]
42fce7d9ce Update translations [skip ci] (#7810)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-09-14 16:10:35 +02:00
Florian Rival
facac37fff Fix opening of the scene after creating project with AI
Don't show in changelog
2025-09-14 15:44:04 +02:00
Florian Rival
1272b601c6 Add many AI improvements (#7819)
* Allow the AI to start a game from a template
* Improved instances manipulation
* Better variables support
* Allow AI to swap an object by another one from the asset store
* Allow AI to change multiple properties at the same time
* Fix AI not properly removing behaviors associated to a behavior just removed
2025-09-13 18:01:35 +02:00
D8H
58e35cfaf5 Allow extensions to define labels for properties with string selectors (#7825) 2025-09-12 18:30:10 +02:00
D8H
2befc9781b Fix the list of assets suggested for sliders and toggle switches (#7826) 2025-09-12 15:42:10 +02:00
Florian Rival
8fb2872c36 Disable spell check on comments
Don't show in changelog
2025-09-12 10:25:41 +02:00
D8H
98033515c8 Fix a 1-frame delay when applying anchors to child-objects (#7820) 2025-09-11 15:19:45 +02:00
D8H
25c02cea2e Add a link to an help page for asynchronous functions (#7823) 2025-09-11 14:53:15 +02:00
D8H
6d5be78fec Forbid to import an extension which has the same name as a built-in one (#7822) 2025-09-11 14:52:49 +02:00
D8H
f6e60085db Fix missing extension dependencies in exported assets (#7821) 2025-09-11 14:52:32 +02:00
Florian Rival
a61648af70 Fix landscape/portrait orientation not working on iOS 2025-09-11 10:50:06 +02:00
D8H
2b496c6fd3 Fix custom object extraction behavior check (#7818)
- Don't show in changelog
2025-09-10 12:30:48 +02:00
Florian Rival
1e984f0965 Fix the chosen preset for AI not always valid when switching between chat and agent 2025-09-08 18:18:53 +02:00
Florian Rival
37bed36315 Make comment events editable exactly as they look (#7812) 2025-09-05 14:46:42 +02:00
D8H
5394cc5201 Forbid to add Physics behaviors on objects inside custom objects (#7809) 2025-09-04 19:37:57 +02:00
Florian Rival
5e3dfb0e9c Add condition to check if a key was just pressed (#7808) 2025-09-04 17:32:25 +02:00
Clément Pasteau
f6a6c981f8 Add a small intro explaining how to follow the courses (#7807) 2025-09-04 16:10:37 +02:00
Clément Pasteau
415c1bfd2f Fix bundles showing up in new object search (#7806) 2025-09-03 14:11:16 +02:00
github-actions[bot]
e4a911db25 Update translations [skip ci] (#7804)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-03 10:42:14 +02:00
Clément Pasteau
5fcd67d77b Additional bundles are added to the Learn & Shop sections (#7805)
* A premium bundle, including multiple courses, asset packs, templates and a gold subscription
* A curated platformer-specific bundle, including everything needed to learn & create a platformer game
2025-09-03 10:30:24 +02:00
Florian Rival
86db08ac3f Fix npm start on Windows
Don't show in changelog
2025-09-01 15:49:21 +02:00
github-actions[bot]
8d735fc726 Update translations [skip ci] (#7800)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-09-01 14:32:50 +02:00
Clément Pasteau
1e1f4bb2a3 Merge Bundle page into 1 component + small design fixes (#7803)
Do not show in changelog
2025-09-01 14:17:33 +02:00
Florian Rival
d8000aca10 Simplify editor README [skip ci] [ci skip] 2025-09-01 00:00:14 +02:00
Florian Rival
a2660ff0dc Allow to easily work on the game engine using the web-app development version (#7802) 2025-08-31 23:54:44 +02:00
Florian Rival
000d5785cf Improve AI agent performance by removing old object properties
Don't show in changelog
2025-08-30 17:29:49 +02:00
Florian Rival
9fe04712a9 Improve AI ability to update existing instances 2025-08-30 16:59:09 +02:00
Florian Rival
846afd9e0a Improve documentation with reference of all available effects. 2025-08-30 14:16:00 +02:00
Florian Rival
6125ff0f90 Allow AI to change layers/effects/scene and some game properties (#7799) 2025-08-29 19:15:50 +02:00
github-actions[bot]
a5428a8843 Update translations [skip ci] (#7798)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-08-29 15:58:38 +02:00
Ansel Games
19be45cda6 Fix grammar in text font size change warning (#7797) 2025-08-29 14:26:04 +02:00
Clément Pasteau
889c97cb27 Update Bundle Page with more details about content and limited offer (#7795) 2025-08-29 14:24:49 +02:00
Florian Rival
1d83da41a9 Improve physics extensions descriptions to tell about the scale and typical force values 2025-08-27 16:42:41 +02:00
github-actions[bot]
a65f2174eb Update translations [skip ci] (#7781)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-08-27 10:05:59 +02:00
D8H
9db493e87e Refactor tab opening (#7794)
- Don't show in changelogs
2025-08-26 17:29:42 +02:00
Florian Rival
49a3a18b51 Make clear in-app tutorials are free
Don't show in changelog
2025-08-26 15:53:55 +02:00
Florian Rival
0489e7036b Rework stories for TeamSection 2025-08-26 12:07:57 +02:00
Florian Rival
794d5a781c Add support for setting a full name for a student account (#7788) 2025-08-26 11:09:19 +02:00
D8H
c21dfbcc1f Optimize used resources search during export (#7790) 2025-08-25 20:12:17 +02:00
Clément Pasteau
cc75db6d09 Fix crash when accessing an owned archived product in the store (#7789)
Do not show in changelog
2025-08-25 17:03:30 +02:00
Florian Rival
48d35a50b5 Fix wrong redirection to AI tab when using initial-dialog=ask-ai in the web-app 2025-08-15 11:06:40 +02:00
Florian Rival
3a0888046f Add a selector to switch the AI used or choose a preset (#7782) 2025-08-14 16:14:39 +02:00
github-actions[bot]
7917994835 Update translations (#7763)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-08-14 13:55:44 +02:00
Florian Rival
9e2bab43f7 Rework display of course specializations
Don't show in changelog
2025-08-14 13:54:41 +02:00
Florian Rival
7e03f47f08 Bump newIDE version 2025-08-14 12:37:27 +02:00
Florian Rival
7c6137a4fc Ensure behaviors used in events generated by AI are automatically added to objects 2025-08-13 18:02:24 +02:00
D8H
0cbd6e2fe9 Fix cached materials not being cleared when unloading resources (#7780) 2025-08-11 15:50:22 +02:00
D8H
5acc1f5560 Remove unused imports (#7779)
Don't show in changelog
2025-08-11 12:30:49 +02:00
D8H
887693a90d Forbid camera zoom to be set to 0 (#7778) 2025-08-11 10:30:28 +02:00
D8H
fbb985833f Optimize JavaScript events (avoid a list copy) (#7775)
Only show in developer changelog
2025-08-09 17:58:12 +02:00
D8H
1b3734ff6b Add the unit for 3D angle parameter descriptions (#7774) 2025-08-08 17:13:06 +02:00
D8H
6288b30ac3 Add an alert when editing the default variant of an extension from the store (#7773) 2025-08-08 16:39:50 +02:00
D8H
ee435f7081 Fix the tint action of Sprite to handle floating point color components (#7772) 2025-08-08 15:57:19 +02:00
Gleb Volkov
d75b4eb2a9 Add debug flag to GDJS build script (#7771) 2025-08-08 14:17:26 +02:00
Florian Rival
5eeb505807 Fix tests
Don't show in changelog
2025-08-08 11:36:33 +02:00
Florian Rival
30566e35ce Fix changing language not reloading courses in this language 2025-08-08 10:14:57 +02:00
Florian Rival
e058b7f295 Add line height property for Text objects (#7769) 2025-08-07 18:47:02 +02:00
Florian Rival
902a30a9f8 Update capability user-friendly name and fix test
Don't show in changelog
2025-08-07 16:55:52 +02:00
Florian Rival
8669b94fb0 Improve project information sent to AI with the game resolution
Don't show in changelog
2025-08-05 14:29:58 +02:00
Florian Rival
7fb08aea62 Fix default light effects not added for scenes created by AI 2025-08-04 21:35:52 +02:00
Florian Rival
e7a1548b0e Fix default UI layer position for AI 2025-08-04 15:36:33 +02:00
Florian Rival
bdcb6f0533 Improve extension descriptions 2025-08-04 15:36:21 +02:00
github-actions[bot]
97849ce6f1 Update translations [skip ci] (#7760)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-08-01 18:05:51 +02:00
Clément Pasteau
d1c937caf4 Allow redeeming a code from the profile page directly (#7761) 2025-08-01 17:56:43 +02:00
Florian Rival
5ffe6279a2 Fix warning 2025-08-01 16:08:28 +02:00
Clément Pasteau
9260e2b77a Improve bundle listing (#7759)
Do not show in changelog
2025-08-01 15:44:16 +02:00
D8H
593465e2ec Optimize event-function calls (#7758) 2025-08-01 15:19:26 +02:00
github-actions[bot]
8820350760 Update translations [skip ci] (#7756)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-08-01 14:12:21 +02:00
Florian Rival
7e1668229a Change how bundle price is calculated to avoid API calls (#7757) 2025-08-01 13:50:26 +02:00
D8H
387b96b9a0 Allow anchors to set the wrapping width of bitmap texts and BBCode texts (#7755) 2025-08-01 11:47:10 +02:00
github-actions[bot]
5f52d786c6 Update translations [skip ci] (#7749)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-31 14:54:34 +02:00
Clément Pasteau
e0db597f9d A new Bundle to get started with GDevelop is now available (#7751)
* including multiple game templates and asset packs
* including a redemption code for a premium subscription
* including multiple official game dev courses
2025-07-31 14:18:05 +02:00
Florian Rival
41b0315ec6 Improve rating banner for course chapters 2025-07-30 18:49:27 +02:00
Florian Rival
a930a4085e Add basic button to rate premium course chapter 2025-07-30 14:56:46 +02:00
Florian Rival
d0dbbfac07 Add "StrReplaceOne" and "StrReplaceAll" expressions (#7750) 2025-07-30 10:10:29 +02:00
Florian Rival
3dc24b46f4 Fix warning
Don't show in changelog
2025-07-28 13:54:12 +02:00
Florian Rival
8e44a357b4 Fix Android build and player authentication sometimes not working (#7748)
- Player authentication window could not open if no action/condition related to player authentication was used
- Fix Android build by using an updated dependency for opening the authentication window
2025-07-28 12:55:58 +02:00
Florian Rival
dd462310cc Reduce network requests at startup by lazily loading course chapters when opened 2025-07-26 16:37:38 +02:00
Florian Rival
a1935fa0cd Reduce a bit more unnecessary fetches for course chapters
Don't show in changelog
2025-07-26 14:38:54 +02:00
Florian Rival
b45c57246b Add animation names to inspected object properties for AI 2025-07-26 14:21:44 +02:00
Florian Rival
c481ecd6b5 Bump newIDE version 2025-07-25 15:47:07 +02:00
github-actions[bot]
e0898dd9b0 Update translations [skip ci] (#7737)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-07-25 13:40:41 +02:00
Clément Pasteau
5561334efa Fix Send icon color (#7745) 2025-07-25 13:33:02 +02:00
Florian Rival
6c4bb4f79e Fix selection of face orientation in 3D Cube editor 2025-07-25 10:04:14 +02:00
D8H
8b2d2e2fe7 Fix "edit children" drop-down menu item activation (#7744)
- don't show in changelog
2025-07-24 14:04:25 +02:00
Florian Rival
49d128c964 Display "Ask AI" as a separate pane (or drawer on small screens) (#7738) 2025-07-24 13:20:52 +02:00
D8H
f24d1e0916 Add a deprecation message for custom objects using old "configuration overriding" (#7742) 2025-07-24 13:19:23 +02:00
D8H
9faa4c0c69 Fix button labels not refreshing when modified from the side panel (#7741) 2025-07-24 10:32:48 +02:00
D8H
a04b8f65db Allow to select a custom object variant in the properties panel (#7740)
* Also show a dialog to duplicate a variant before opening them if necessary.
2025-07-23 17:26:03 +02:00
D8H
e1cf7d23cd Various fixes for variants (#7739)
- Forbid to edit the default variant of published extensions
- Hide the children configuration from the side panel when a variant is used
- Fix the Z-order of nested custom objects in the editor
- Fix a memory crash when updating an extension where behaviors must be removed from child-objects
2025-07-22 19:06:33 +02:00
github-actions[bot]
b74b221844 Update translations [skip ci] (#7730)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2025-07-22 11:43:45 +02:00
Clément Pasteau
38affc15b4 Fix making too many calls for courses (#7736)
Do not show in changelog
2025-07-22 11:43:21 +02:00
D8H
948488d92b [Top-down movement] Fix the legacy turning back mode (#7735) 2025-07-21 22:07:00 +02:00
Florian Rival
f5902d0346 Enable visibility of 3D cube backface by default 2025-07-21 10:52:41 +02:00
Clément Pasteau
f28dc8e88a Fix images pixelated because of border (#7732)
Do not show in changelog
2025-07-18 14:37:06 +02:00
Clément Pasteau
1f41749fa3 Fix carousel mobile (#7729)
Do not show in changelog
2025-07-17 15:49:36 +02:00
Florian Rival
a4908a4d42 Add spell check option for text input (disabled by default) (#7728) 2025-07-17 14:39:30 +02:00
Clément Pasteau
aa7754e658 Fixes responsive design and courses (#7726)
Do not show in changelog
2025-07-17 10:20:02 +02:00
github-actions[bot]
58ea9387aa Update extension translations [skip ci] (#7727)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-17 10:17:37 +02:00
github-actions[bot]
775266c974 Update translations [skip ci] (#7722)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2025-07-17 10:15:12 +02:00
Florian Rival
eb9794cd1f Bump newIDE version 2025-07-17 09:42:20 +02:00
Florian Rival
130732adde Refactor to make choice between chat or agent explicit
Don't show in changelog
2025-07-17 00:32:05 +02:00
Florian Rival
7a98e73d61 Fix AI not drawing properly multiple instances 2025-07-17 00:08:25 +02:00
D8H
1f26b72b4b Fix Physics3D from also creating a solid when the character behavior is re-activated (#7723) 2025-07-16 19:52:38 +02:00
D8H
a15ffb5b47 Add missing onSceneObjectsDeleted for custom object tabs (#7724) 2025-07-16 19:51:41 +02:00
Florian Rival
1a5f72283a Update price tag design 2025-07-16 18:36:25 +02:00
github-actions[bot]
0460b283ba Update translations [skip ci] (#7703)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-16 12:08:11 +02:00
Yaroslav Nazarenko
e212e7c780 Add an action to join a specific lobby using its identifier, and an expression to get the current lobby ID (#7694) 2025-07-16 12:07:32 +02:00
Clément Pasteau
84100fc7cf Introducing a new Learn page (#7705)
* The Get Started page has been removed and replaced by the Learn page as the first page displayed when GDevelop launches
  * The Learn page has been completely reworked to put forward the different resources a creator can use to improve their skills with Game Creation
  * A new option in the Preferences allows users to define the Create page as the default first page on launch
* Courses can now be purchased as a whole instead of per chapter, making it simpler to follow a full course
  * Bundles with multiple courses are coming up soon!
2025-07-16 11:59:56 +02:00
D8H
11a8682b07 Fix a crash of the scene editor when a custom object extension is updated (#7720) 2025-07-16 11:40:28 +02:00
Florian Rival
d3a0bbdfb1 Disable autorun of npm start when opening VSCode [skip ci] [ci skip] (#7719)
Only show in developer changelog
2025-07-16 10:56:09 +02:00
D8H
15f3a45d6a Fix 3D impulse and force toward a point actions (#7716) 2025-07-15 15:27:31 +02:00
D8H
f0a4f352cc Fix effect default values (#7706) 2025-07-15 13:56:20 +02:00
D8H
d16b3e8154 Fix multiplayer synchronization of custom object positions (#7715)
---------

Co-authored-by: Clément Pasteau <4895034+ClementPasteau@users.noreply.github.com>
2025-07-15 13:38:23 +02:00
D8H
614fb97288 Fix 3D physics behaviors activation and deactivation (#7710) 2025-07-15 13:35:54 +02:00
D8H
8a40d3645a Fix the check box to stop sounds at scene start up not being applied properly (#7714) 2025-07-15 10:38:18 +02:00
D8H
2b7dadf2a8 Fix text input displayed position when inside a custom object whose layer is moved (#7713) 2025-07-14 17:47:44 +02:00
D8H
c338e16e4f Fix the "cursor is on" condition when used on custom object parent object (#7712) 2025-07-14 17:46:11 +02:00
544 changed files with 21762 additions and 9995 deletions

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@
.Spotlight-V100
.Trashes
Thumbs.db
.claude

3
.vscode/tasks.json vendored
View File

@@ -38,8 +38,7 @@
"presentation": {
"reveal": "silent"
},
"isBackground": true,
"runOptions": { "instanceLimit": 1, "runOn": "folderOpen" }
"isBackground": true
},
{
"type": "npm",

View File

@@ -329,7 +329,6 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
condition.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
@@ -357,6 +356,11 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
if (instrInfos.IsObjectInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
@@ -488,14 +492,16 @@ gd::String EventsCodeGenerator::GenerateConditionsListCode(
return outputCode;
}
void EventsCodeGenerator::CheckBehaviorParameters(
bool EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos) {
gd::ParameterMetadataTools::IterateOverParameters(
bool isAnyBehaviorMissing = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), instrInfos.parameters,
[this](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue,
const gd::String &lastObjectName) {
[this, &isAnyBehaviorMissing,
&instrInfos](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
if (ParameterMetadata::IsBehavior(parameterMetadata.GetType())) {
const gd::String &behaviorName = parameterValue.GetPlainString();
const gd::String &actualBehaviorType =
@@ -506,13 +512,25 @@ void EventsCodeGenerator::CheckBehaviorParameters(
if (!expectedBehaviorType.empty() &&
actualBehaviorType != expectedBehaviorType) {
const auto &objectParameterMetadata =
instrInfos.GetParameter(lastObjectIndex);
// Event functions crash if some objects in a group are missing
// the required behaviors, since they lose reference to the original
// objects. Missing behaviors are considered "fatal" only for
// ObjectList parameters, in order to minimize side effects on
// built-in functions.
if (objectParameterMetadata.GetType() == "objectList") {
isAnyBehaviorMissing = true;
}
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MissingBehavior, "",
actualBehaviorType, expectedBehaviorType, lastObjectName);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
if (diagnosticReport)
diagnosticReport->Add(projectDiagnostic);
}
}
});
return isAnyBehaviorMissing;
}
/**
@@ -552,7 +570,6 @@ gd::String EventsCodeGenerator::GenerateActionCode(
action.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
@@ -579,6 +596,11 @@ gd::String EventsCodeGenerator::GenerateActionCode(
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
// Call free function first if available
if (instrInfos.IsObjectInstruction()) {
@@ -769,7 +791,7 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
} else {
outputCode += actionCode;
}
outputCode += "}";
outputCode += "}\n";
}
return outputCode;

View File

@@ -837,7 +837,7 @@ protected:
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName);
void CheckBehaviorParameters(
bool CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos);

View File

@@ -18,21 +18,21 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("AnimatableCapability",
_("Animatable capability"),
_("Animate objects."),
_("Objects with animations"),
_("Actions and conditions for objects having animations (sprite, 3D models...)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Animatable capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects with animations"))
.SetIcon("res/actions/animation24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Animations and images"))
.SetIcon("res/actions/animation24.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"AnimatableBehavior",
_("Animatable capability"),
_("Objects with animations"),
"Animation",
_("Animate objects."),
_("Actions and conditions for objects having animations (sprite, 3D models...).."),
"",
"res/actions/animation24.png",
"AnimatableBehavior",

View File

@@ -18,8 +18,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("EffectCapability",
_("Effect capability"),
_("Apply visual effects to objects."),
_("Objects with effects"),
_("Actions/conditions to enable/disable and change parameters of visual effects applied on objects."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
@@ -28,9 +28,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
gd::BehaviorMetadata& aut = extension.AddBehavior(
"EffectBehavior",
_("Effect capability"),
_("Objects with effects"),
"Effect",
_("Apply visual effects to objects."),
_("Actions/conditions to enable/disable and change parameters of visual effects applied on objects."),
"",
"res/actions/effect_black.svg",
"EffectBehavior",

View File

@@ -18,8 +18,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("FlippableCapability",
_("Flippable capability"),
_("Flip objects."),
_("Flippable objects"),
_("Actions/conditions for objects which can be flipped horizontally or vertically."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
@@ -28,9 +28,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
gd::BehaviorMetadata& aut = extension.AddBehavior(
"FlippableBehavior",
_("Flippable capability"),
_("Flippable objects"),
"Flippable",
_("Flip objects."),
_("Actions/conditions for objects which can be flipped horizontally or vertically."),
"",
"res/actions/flipX24.png",
"FlippableBehavior",

View File

@@ -18,27 +18,30 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("OpacityCapability",
_("Opacity capability"),
_("Change the object opacity."),
_("Objects with opacity"),
_("Action/condition/expression to change or "
"check the opacity of an object (0-255)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Opacity capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects with opacity"))
.SetIcon("res/actions/opacity24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Visibility"))
.SetIcon("res/actions/opacity24.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"OpacityBehavior",
_("Opacity capability"),
"Opacity",
_("Change the object opacity."),
"",
"res/actions/opacity24.png",
"OpacityBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
gd::BehaviorMetadata& aut =
extension
.AddBehavior("OpacityBehavior",
_("Objects with opacity"),
"Opacity",
_("Action/condition/expression to change or check the "
"opacity of an object (0-255)."),
"",
"res/actions/opacity24.png",
"OpacityBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"number",
@@ -52,8 +55,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "OpacityBehavior")
.UseStandardParameters(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity (0-255)")))
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Opacity (0-255)")))
.SetFunctionName("setOpacity")
.SetGetter("getOpacity");
aut.GetAllExpressions()["Value"].SetGroup("");

View File

@@ -16,11 +16,13 @@ namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::PlatformExtension &extension) {
extension
.SetExtensionInformation("ResizableCapability",
_("Resizable capability"),
_("Change the object dimensions."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionInformation(
"ResizableCapability",
_("Resizable objects"),
_("Change or compare the size (width/height) of an object which can "
"be resized (i.e: most objects)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
@@ -28,9 +30,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::BehaviorMetadata &aut =
extension
.AddBehavior("ResizableBehavior",
_("Resizable capability"),
_("Resizable objects"),
"Resizable",
_("Change the object dimensions."),
_("Change or compare the size (width/height) of an "
"object which can be resized (i.e: most objects)."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",

View File

@@ -18,27 +18,30 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("ScalableCapability",
_("Scalable capability"),
_("Change the object scale."),
_("Scalable objects"),
_("Actions/conditions/expression to change or "
"check the scale of an object (default: 1)."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable capability"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable objects"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"ScalableBehavior",
_("Scalable capability"),
"Scale",
_("Change the object scale."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
gd::BehaviorMetadata& aut =
extension
.AddBehavior("ScalableBehavior",
_("Scalable objects"),
"Scale",
_("Actions/conditions/expression to change or check the "
"scale of an object (default: 1)."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"number",

View File

@@ -18,17 +18,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTextContainerExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("TextContainerCapability",
_("Text capability"),
_("Objects containing a text"),
_("Allows an object to contain a text, usually shown on screen, that can be modified."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Text capability"))
extension.AddInstructionOrExpressionGroupMetadata(_("Objects containing a text"))
.SetIcon("res/conditions/text24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TextContainerBehavior",
_("Text capability"),
_("Objects containing a text"),
"Text",
_("Allows an object to contain a text, usually shown on screen, that can be modified."),
"",

View File

@@ -16,7 +16,9 @@ BuiltinExtensionsImplementer::ImplementsCommonConversionsExtension(
.SetExtensionInformation(
"BuiltinCommonConversions",
_("Conversion"),
"Expressions to convert number, texts and quantities.",
"Expressions to convert numbers to string, strings to numbers, "
"angles (degrees from/to radians) and a GDevelop variable to/from a "
"JSON string.",
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/common-conversions");
@@ -41,7 +43,7 @@ BuiltinExtensionsImplementer::ImplementsCommonConversionsExtension(
extension
.AddStrExpression("LargeNumberToString",
_("Number > Text ( without scientific notation )"),
_("Number > Text (without scientific notation)"),
_("Convert the result of the expression to text, "
"without using the scientific notation"),
"",
@@ -72,7 +74,8 @@ BuiltinExtensionsImplementer::ImplementsCommonConversionsExtension(
_("Convert a variable to JSON"),
_("JSON"),
"res/conditions/toujours24_black.png")
.AddParameter("variable", _("The variable to be stringified"),
.AddParameter("variable",
_("The variable to be stringified"),
"AllowUndeclaredVariable");
// Deprecated

View File

@@ -15,10 +15,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
.SetExtensionInformation(
"BuiltinKeyboard",
_("Keyboard"),
_("Allows your game to respond to keyboard input. Note that this "
_("Conditions to check keys pressed on a keyboard. Note that this "
"does not work with on-screen keyboard on touch devices: use "
"instead conditions related to touch when making a game for "
"mobile/touchscreen devices."),
"instead mouse/touch conditions when making a game for "
"mobile/touchscreen devices or when making a new game from "
"scratch."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/keyboard")
@@ -51,10 +52,25 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
.SetHidden();
extension
.AddCondition("KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed"),
_("_PARAM1_ key is pressed"),
.AddCondition(
"KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed. This stays true as long as "
"the key is held down. To check if a key was pressed during "
"the frame, use \"Key just pressed\" instead."),
_("_PARAM1_ key is pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("keyboardKey", _("Key to check"))
.MarkAsSimple();
extension
.AddCondition("KeyFromTextJustPressed",
_("Key just pressed"),
_("Check if a key was just pressed."),
_("_PARAM1_ key was just pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
@@ -65,7 +81,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyFromTextReleased",
_("Key released"),
_("Check if a key was just released"),
_("Check if a key was just released."),
_("_PARAM1_ key is released"),
"",
"res/conditions/keyboard24.png",
@@ -84,7 +100,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "");
extension
extension
.AddCondition("AnyKeyReleased",
_("Any key released"),
_("Check if any key is released"),

View File

@@ -16,8 +16,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.SetExtensionInformation(
"BuiltinMouse",
_("Mouse and touch"),
"Conditions and actions to handle either the mouse or touches on "
"touchscreen. By default, conditions related to the mouse will also "
"Conditions, actions and expressions to handle either the mouse or "
"touches on a touchscreen. Notably: cursor position, mouse wheel, "
"mouse buttons, touch positions, started/end touches, etc...\n"
"\n"
"By default, conditions related to the mouse will also "
"handle the touches - so that it's easier to handle both in your "
"game. You can disable this behavior if you want to handle them "
"separately in different events.",
@@ -273,28 +276,26 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.SetHidden();
extension
.AddCondition(
"MouseButtonFromTextPressed",
_("Mouse button pressed or touch held"),
_("Check if the specified mouse button is pressed or "
"if a touch is in contact with the screen."),
_("Touch or _PARAM1_ mouse button is down"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCondition("MouseButtonFromTextPressed",
_("Mouse button pressed or touch held"),
_("Check if the specified mouse button is pressed or "
"if a touch is in contact with the screen."),
_("Touch or _PARAM1_ mouse button is down"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("mouseButton", _("Button to check"))
.MarkAsSimple();
extension
.AddCondition(
"MouseButtonFromTextReleased",
_("Mouse button released"),
_("Check if the specified mouse button was released."),
_("Touch or _PARAM1_ mouse button is released"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCondition("MouseButtonFromTextReleased",
_("Mouse button released"),
_("Check if the specified mouse button was released."),
_("Touch or _PARAM1_ mouse button is released"),
"",
"res/conditions/mouse24.png",
"res/conditions/mouse.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("mouseButton", _("Button to check"))
.MarkAsSimple();

View File

@@ -15,8 +15,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
.SetExtensionInformation(
"BuiltinNetwork",
_("Network"),
_("Features to send web requests, communicate with external \"APIs\" "
"and other network related tasks."),
_("Actions to send web requests, communicate with external \"APIs\" "
"and other network related tasks. Also contains an action to open "
"a URL on the device browser."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/network")

View File

@@ -4,8 +4,8 @@
* reserved. This project is released under the MIT License.
*/
#include "AllBuiltinExtensions.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Tools/Localization.h"
using namespace std;
namespace gd {
@@ -16,7 +16,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
.SetExtensionInformation(
"BuiltinScene",
_("Scene"),
_("Actions and conditions to manipulate the scenes during the game."),
_("Actions/conditions to change the current scene (or pause it and "
"launch another one, or go back to the previous one), check if a "
"scene or the game has just started/resumed, preload assets of a "
"scene, get the current scene name or loading progress, quit the "
"game, set background color, or disable input when focus is lost."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("" /*TODO: Add a documentation page for this */);
@@ -166,25 +170,28 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
.AddCodeOnlyParameter("currentScene", "");
extension
.AddAction("PrioritizeLoadingOfScene",
_("Preload scene"),
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.AddAction(
"PrioritizeLoadingOfScene",
_("Preload scene"),
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Name of the new scene"))
.MarkAsAdvanced();
extension.AddExpressionAndCondition("number",
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in background for a scene (between 0 and 1)."),
_("_PARAM1_ loading progress"),
_(""),
"res/actions/hourglass_black.svg")
extension
.AddExpressionAndCondition("number",
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in "
"background for a scene (between 0 and 1)."),
_("_PARAM1_ loading progress"),
_(""),
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
@@ -192,13 +199,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
.MarkAsAdvanced();
extension
.AddCondition("AreSceneAssetsLoaded",
_("Scene preloaded"),
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.AddCondition(
"AreSceneAssetsLoaded",
_("Scene preloaded"),
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/hourglass_black.svg",
"res/actions/hourglass_black.svg")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))

View File

@@ -15,12 +15,13 @@ namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("Sprite",
_("Sprite"),
_("Sprite are animated object which can be used "
"for most elements of a game."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionInformation(
"Sprite",
_("Sprite"),
_("Sprite are animated objects which can be used "
"for most elements of a 2D game."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects/sprite");
extension.AddInstructionOrExpressionGroupMetadata(_("Sprite"))
.SetIcon("CppPlatform/Extensions/spriteicon.png");
@@ -30,7 +31,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddObject<SpriteObject>("Sprite",
_("Sprite"),
_("Animated object which can be used for "
"most elements of a game."),
"most elements of a 2D game."),
"CppPlatform/Extensions/spriteicon.png")
.SetCategoryFullName(_("General"))
.SetOpenFullEditorLabel(_("Edit animations"))
@@ -645,11 +646,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
"res/actions/sprite.png")
.AddParameter("object", _("Object"), "Sprite");
obj.AddExpression("AnimationFrameCount",
_("Number of frames"),
_("Number of frames in the current animation of the object"),
_("Animations and images"),
"res/actions/sprite.png")
obj.AddExpression(
"AnimationFrameCount",
_("Number of frames"),
_("Number of frames in the current animation of the object"),
_("Animations and images"),
"res/actions/sprite.png")
.AddParameter("object", _("Object"), "Sprite");
// Deprecated

View File

@@ -16,7 +16,8 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
.SetExtensionInformation(
"BuiltinStringInstructions",
_("Text manipulation"),
"Provides expressions to manipulate strings (also called texts).",
"Provides expressions to manipulate strings (also called texts): new "
"line, upper/lowercase, substring, find, replace, etc...",
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("" /*TODO: Add a documentation page for this */);
@@ -191,7 +192,8 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
"res/conditions/toujours24_black.png")
.AddParameter("string", _("Text in which the replacement must be done"))
.AddParameter("string", _("Text to find inside the first text"))
.AddParameter("string", _("Replacement to put instead of the text to find"));
.AddParameter("string",
_("Replacement to put instead of the text to find"));
extension
.AddStrExpression("StrReplaceAll",
@@ -199,10 +201,11 @@ BuiltinExtensionsImplementer::ImplementsStringInstructionsExtension(
_("Replace all occurrences of a text by another."),
"",
"res/conditions/toujours24_black.png")
.AddParameter("string", _("Text in which the replacement(s) must be done"))
.AddParameter("string",
_("Text in which the replacement(s) must be done"))
.AddParameter("string", _("Text to find inside the first text"))
.AddParameter("string", _("Replacement to put instead of the text to find"));
.AddParameter("string",
_("Replacement to put instead of the text to find"));
}
} // namespace gd

View File

@@ -15,9 +15,12 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
.SetExtensionInformation(
"BuiltinTime",
_("Timers and time"),
"Actions and conditions to run timers, get the current time or "
"modify the time scale (speed at which the game is running - useful "
"for slow motion effects).",
"Actions and conditions to start, pause or reset scene timers, "
"modify the time scale (speed at which the game "
"is running - useful for slow motion effects). Also contains an "
"action that wait for a delay before running the next actions and "
"sub-events and expressions to read the time scale, time delta of "
"the last frame or timer elapsed time.",
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/timers-and-time");
@@ -192,26 +195,28 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("TimerElapsedTime",
_("Scene timer value"),
_("Value of a scene timer"),
_("Value of a scene timer (in seconds)"),
"",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("identifier", _("Timer's name"), "sceneTimer");
extension
.AddExpression("TimeFromStart",
_("Time elapsed since the beginning of the scene"),
_("Time elapsed since the beginning of the scene"),
"",
"res/actions/time.png")
.AddExpression(
"TimeFromStart",
_("Time elapsed since the beginning of the scene (in seconds)."),
_("Time elapsed since the beginning of the scene (in seconds)."),
"",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "");
extension
.AddExpression("TempsDebut",
_("Time elapsed since the beginning of the scene"),
_("Time elapsed since the beginning of the scene"),
"",
"res/actions/time.png")
.AddExpression(
"TempsDebut",
_("Time elapsed since the beginning of the scene (in seconds)."),
_("Time elapsed since the beginning of the scene (in seconds)."),
"",
"res/actions/time.png")
.SetHidden()
.AddCodeOnlyParameter("currentScene", "");
@@ -226,16 +231,21 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
extension
.AddExpression("Time",
_("Current time"),
_("Current time"),
_("Gives the current time"),
"",
"res/actions/time.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter(
"stringWithSelector",
_("Hour: hour - Minutes: min - Seconds: sec - Day of month: "
"mday - Months since January: mon - Year since 1900: year - Days "
"since Sunday: wday - Days since Jan 1st: yday - Timestamp (ms): "
"timestamp\""),
_("- Hour of the day: \"hour\"\n"
"- Minutes: \"min\"\n"
"- Seconds: \"sec\"\n"
"- Day of month: \"mday\"\n"
"- Months since January: \"mon\"\n"
"- Year since 1900: \"year\"\n"
"- Days since Sunday: \"wday\"\n"
"- Days since Jan 1st: \"yday\"\n"
"- Timestamp (ms): \"timestamp\""),
"[\"hour\", \"min\", \"sec\", \"mon\", \"year\", \"wday\", \"mday\", "
"\"yday\", \"timestamp\"]");
}

View File

@@ -15,16 +15,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsWindowExtension(
.SetExtensionInformation(
"BuiltinWindow",
_("Game window and resolution"),
"Provides actions and conditions to manipulate the game window. "
"Actions and conditions to manipulate the game window or change how "
"the game is resized according to the screen size. "
"Depending on the platform on which the game is running, not all of "
"these features can be applied.",
"these features can be applied.\n"
"Also contains expressions to read the screen size.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("User interface")
.SetExtensionHelpPath("/all-features/window");
extension
.AddInstructionOrExpressionGroupMetadata(
_("Game window and resolution"))
.AddInstructionOrExpressionGroupMetadata(_("Game window and resolution"))
.SetIcon("res/actions/window24.png");
extension

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -63,7 +63,6 @@ void EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
}
// Copy missing behaviors
auto &behaviors = object.GetAllBehaviorContents();
for (const auto &pair : defaultBehaviors) {
const auto &behaviorName = pair.first;
const auto &defaultBehavior = pair.second;
@@ -82,11 +81,9 @@ void EventsBasedObjectVariantHelper::ComplyVariantsToEventsBasedObject(
}
}
// Delete extra behaviors
for (auto it = behaviors.begin(); it != behaviors.end(); ++it) {
const auto &behaviorName = it->first;
for (auto &behaviorName : object.GetAllBehaviorNames()) {
if (!defaultObject->HasBehaviorNamed(behaviorName)) {
object.RemoveBehavior(behaviorName);
--it;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,8 @@
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
gd::String Effect::badStringParameterValue;
void Effect::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());

View File

@@ -34,39 +34,42 @@ class GD_CORE_API Effect {
void SetFolded(bool fold = true) { folded = fold; }
bool IsFolded() const { return folded; }
void SetDoubleParameter(const gd::String& name, double value) {
void SetDoubleParameter(const gd::String &name, double value) {
doubleParameters[name] = value;
}
double GetDoubleParameter(const gd::String& name) {
return doubleParameters[name];
double GetDoubleParameter(const gd::String &name) const {
auto itr = doubleParameters.find(name);
return itr == doubleParameters.end() ? 0 : itr->second;
}
bool HasDoubleParameter(const gd::String& name) {
bool HasDoubleParameter(const gd::String &name) const {
return doubleParameters.find(name) != doubleParameters.end();
}
void SetStringParameter(const gd::String& name, const gd::String& value) {
void SetStringParameter(const gd::String &name, const gd::String &value) {
stringParameters[name] = value;
}
const gd::String& GetStringParameter(const gd::String& name) {
return stringParameters[name];
const gd::String &GetStringParameter(const gd::String &name) const {
auto itr = stringParameters.find(name);
return itr == stringParameters.end() ? badStringParameterValue : itr->second;
}
bool HasStringParameter(const gd::String& name) {
bool HasStringParameter(const gd::String &name) const {
return stringParameters.find(name) != stringParameters.end();
}
void SetBooleanParameter(const gd::String& name, bool value) {
void SetBooleanParameter(const gd::String &name, bool value) {
booleanParameters[name] = value;
}
bool GetBooleanParameter(const gd::String& name) {
return booleanParameters[name];
bool GetBooleanParameter(const gd::String &name) const {
auto itr = booleanParameters.find(name);
return itr == booleanParameters.end() ? false : itr->second;
}
bool HasBooleanParameter(const gd::String& name) {
bool HasBooleanParameter(const gd::String &name) const {
return booleanParameters.find(name) != booleanParameters.end();
}
@@ -105,6 +108,9 @@ class GD_CORE_API Effect {
std::map<gd::String, double> doubleParameters; ///< Values of parameters being doubles, keyed by names.
std::map<gd::String, gd::String> stringParameters; ///< Values of parameters being strings, keyed by names.
std::map<gd::String, bool> booleanParameters; ///< Values of parameters being booleans, keyed by names.
static gd::String badStringParameterValue; ///< Empty string returned by
///< GeStringParameter
};
} // namespace gd

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,8 +5,6 @@ namespace gdjs {
type Object3DNetworkSyncDataType = {
// z is position on the Z axis, different from zo, which is Z order
z: number;
w: number;
h: number;
d: number;
rx: number;
ry: number;
@@ -116,8 +114,6 @@ namespace gdjs {
return {
...super.getNetworkSyncData(),
z: this.getZ(),
w: this.getWidth(),
h: this.getHeight(),
d: this.getDepth(),
rx: this.getRotationX(),
ry: this.getRotationY(),
@@ -130,8 +126,6 @@ namespace gdjs {
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
super.updateFromNetworkSyncData(networkSyncData);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.w !== undefined) this.setWidth(networkSyncData.w);
if (networkSyncData.h !== undefined) this.setHeight(networkSyncData.h);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
this.setRotationX(networkSyncData.rx);

View File

@@ -1,4 +1,12 @@
namespace gdjs {
type CustomObject3DNetworkSyncDataType = CustomObjectNetworkSyncDataType & {
z: float;
d: float;
rx: float;
ry: float;
ifz: boolean;
};
/**
* Base class for 3D custom objects.
*/
@@ -77,6 +85,30 @@ namespace gdjs {
}
}
getNetworkSyncData(): CustomObject3DNetworkSyncDataType {
return {
...super.getNetworkSyncData(),
z: this.getZ(),
d: this.getDepth(),
rx: this.getRotationX(),
ry: this.getRotationY(),
ifz: this.isFlippedZ(),
};
}
updateFromNetworkSyncData(
networkSyncData: CustomObject3DNetworkSyncDataType
): void {
super.updateFromNetworkSyncData(networkSyncData);
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
if (networkSyncData.rx !== undefined)
this.setRotationX(networkSyncData.rx);
if (networkSyncData.ry !== undefined)
this.setRotationY(networkSyncData.ry);
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
}
/**
* Set the object position on the Z axis.
*/

View File

@@ -162,7 +162,12 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationX')
.setGetter('getRotationX');
@@ -178,7 +183,12 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationY')
.setGetter('getRotationY');
@@ -196,7 +206,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundX');
@@ -214,7 +224,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundY');
@@ -232,7 +242,7 @@ module.exports = {
)
.addParameter('object', _('3D object'), '', false)
.addParameter('behavior', _('Behavior'), 'Base3DBehavior')
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setFunctionName('turnAroundZ');
}
@@ -242,7 +252,7 @@ module.exports = {
.addObject(
'Model3DObject',
_('3D Model'),
_('An animated 3D model.'),
_('An animated 3D model, useful for most elements of a 3D game.'),
'JsPlatform/Extensions/3d_model.svg',
new gd.Model3DObjectConfiguration()
)
@@ -594,7 +604,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setHidden()
.setFunctionName('setRotationX')
.setGetter('getRotationX');
@@ -611,7 +626,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setHidden()
.setFunctionName('setRotationY')
.setGetter('getRotationY');
@@ -630,7 +650,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundX');
@@ -649,7 +669,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundY');
@@ -668,7 +688,7 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D model'), 'Model3DObject', false)
.addParameter('number', _('Rotation angle'), '', false)
.addParameter('number', _('Angle to add (in degrees)'), '', false)
.markAsAdvanced()
.setHidden()
.setFunctionName('turnAroundZ');
@@ -1125,7 +1145,7 @@ module.exports = {
topFaceResourceName: '',
bottomFaceResourceName: '',
frontFaceVisible: true,
backFaceVisible: false,
backFaceVisible: true,
leftFaceVisible: true,
rightFaceVisible: true,
topFaceVisible: true,
@@ -1493,7 +1513,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationX')
.setHidden()
.setGetter('getRotationX');
@@ -1510,7 +1535,12 @@ module.exports = {
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D cube'), 'Cube3DObject', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Angle (in degrees)')
)
)
.setFunctionName('setRotationY')
.setHidden()
.setGetter('getRotationY');
@@ -1878,7 +1908,9 @@ module.exports = {
.addEffect('AmbientLight')
.setFullName(_('Ambient light'))
.setDescription(
_('A light that illuminates all objects from every direction.')
_(
'A light that illuminates all objects from every direction. Often used along with a Directional light (though a Hemisphere light can be used instead of an Ambient light).'
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
@@ -1899,7 +1931,11 @@ module.exports = {
const effect = extension
.addEffect('DirectionalLight')
.setFullName(_('Directional light'))
.setDescription(_('A very far light source like the sun.'))
.setDescription(
_(
"A very far light source like the sun. This is the light to use for casting shadows for 3D objects (other lights won't emit shadows). Often used along with a Hemisphere light."
)
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/DirectionalLight.js');
@@ -1983,7 +2019,7 @@ module.exports = {
.setFullName(_('Hemisphere light'))
.setDescription(
_(
'A light that illuminates objects from every direction with a gradient.'
'A light that illuminates objects from every direction with a gradient. Often used along with a Directional light.'
)
)
.markAsNotWorkingForObjects()

View File

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

View File

@@ -508,7 +508,7 @@ module.exports = {
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader,
propertyOverridings
getPropertyOverridings
) {
super(
project,
@@ -516,7 +516,7 @@ module.exports = {
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader,
propertyOverridings
getPropertyOverridings
);
const bbTextStyles = {
@@ -555,9 +555,11 @@ module.exports = {
gd.ObjectJsImplementation
);
const rawText = this._propertyOverridings.has('Text')
? this._propertyOverridings.get('Text')
: object.content.text;
const propertyOverridings = this.getPropertyOverridings();
const rawText =
propertyOverridings && propertyOverridings.has('Text')
? propertyOverridings.get('Text')
: object.content.text;
if (rawText !== this._pixiObject.text) {
this._pixiObject.text = rawText;
}

View File

@@ -383,6 +383,10 @@ namespace gdjs {
return this._renderer.getHeight();
}
override setWidth(width: float): void {
this.setWrappingWidth(width);
}
override getDrawableY(): float {
return (
this.getY() -

View File

@@ -631,7 +631,7 @@ module.exports = {
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader,
propertyOverridings
getPropertyOverridings
) {
super(
project,
@@ -639,7 +639,7 @@ module.exports = {
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader,
propertyOverridings
getPropertyOverridings
);
// We'll track changes of the font to trigger the loading of the new font.
@@ -665,9 +665,11 @@ module.exports = {
// Update the rendered text properties (note: Pixi is only
// applying changes if there were changed).
this._pixiObject.text = this._propertyOverridings.has('Text')
? this._propertyOverridings.get('Text')
: object.content.text;
const propertyOverridings = this.getPropertyOverridings();
this._pixiObject.text =
propertyOverridings && propertyOverridings.has('Text')
? propertyOverridings.get('Text')
: object.content.text;
const align = object.content.align;
this._pixiObject.align = align;

View File

@@ -426,6 +426,10 @@ namespace gdjs {
return this._renderer.getHeight();
}
override setWidth(width: float): void {
this.setWrappingWidth(width);
}
override getDrawableY(): float {
return (
this.getY() -

View File

@@ -21,7 +21,9 @@ module.exports = {
.setExtensionInformation(
'FileSystem',
_('File system'),
_('Access the filesystem of the operating system.'),
_(
'Access the filesystem of the operating system - only works on native, desktop games exported to Windows, Linux or macOS.'
),
'Matthias Meike',
'Open source (MIT License)'
)

View File

@@ -5,23 +5,25 @@ Copyright (c) 2008-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/
#include <iostream>
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Tools/Localization.h"
#include <iostream>
void DeclareInventoryExtension(gd::PlatformExtension& extension) {
extension.SetExtensionInformation(
"Inventory",
_("Inventories"),
_("Provides actions and conditions to add an inventory to your game, "
"with items in memory."),
"Florian Rival",
"Open source (MIT License)")
extension
.SetExtensionInformation(
"Inventory",
_("Inventories"),
_("Actions and conditions to store named inventories in memory, "
"with items (indexed by their name), a count for each of them, "
"a maximum count and an equipped state. Can be loaded/saved "
"from/to a GDevelop variable."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/all-features/inventory")
.SetCategory("Game mechanic");
extension
.AddInstructionOrExpressionGroupMetadata(_("Inventories"))
extension.AddInstructionOrExpressionGroupMetadata(_("Inventories"))
.SetIcon("CppPlatform/Extensions/Inventoryicon.png");
extension
@@ -164,14 +166,15 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.SetFunctionName("InventoryTools::IsEquipped");
extension
.AddAction("SerializeToVariable",
_("Save an inventory in a scene variable"),
_("Save all the items of the inventory in a scene variable, so that "
"it can be restored later."),
_("Save inventory _PARAM1_ in variable _PARAM2_"),
_("Variables"),
"CppPlatform/Extensions/Inventoryicon.png",
"CppPlatform/Extensions/Inventoryicon.png")
.AddAction(
"SerializeToVariable",
_("Save an inventory in a scene variable"),
_("Save all the items of the inventory in a scene variable, so that "
"it can be restored later."),
_("Save inventory _PARAM1_ in variable _PARAM2_"),
_("Variables"),
"CppPlatform/Extensions/Inventoryicon.png",
"CppPlatform/Extensions/Inventoryicon.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
@@ -204,13 +207,14 @@ void DeclareInventoryExtension(gd::PlatformExtension& extension) {
.SetFunctionName("InventoryTools::Count");
extension
.AddExpression("Maximum",
_("Item maximum"),
_("Get the maximum of an item in the inventory, or 0 if it is unlimited"),
"",
"CppPlatform/Extensions/Inventoryicon.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Maximum");
.AddExpression("Maximum",
_("Item maximum"),
_("Get the maximum of an item in the inventory, or 0 if "
"it is unlimited"),
"",
"CppPlatform/Extensions/Inventoryicon.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("string", _("Inventory name"))
.AddParameter("string", _("Item name"))
.SetFunctionName("InventoryTools::Maximum");
}

View File

@@ -27,7 +27,7 @@ class RenderedInstance {
associatedObjectConfiguration: gdObjectConfiguration,
pixiContainer: PIXI.Container,
pixiResourcesLoader: Class<PixiResourcesLoader>,
propertyOverridings: Map<string, string> = new Map<string, string>()
getPropertyOverridings: (() => Map<string, string>) | null = null
);
/**
@@ -80,6 +80,8 @@ class RenderedInstance {
getDefaultHeight(): number;
getDefaultDepth(): number;
getPropertyOverridings(): Map<string, string> | null;
}
/**
@@ -107,7 +109,8 @@ class Rendered3DInstance {
associatedObjectConfiguration: gdObjectConfiguration,
pixiContainer: PIXI.Container,
threeGroup: THREE.Group,
pixiResourcesLoader: Class<PixiResourcesLoader>
pixiResourcesLoader: Class<PixiResourcesLoader>,
getPropertyOverridings: (() => Map<string, string>) | null = null
);
/**
@@ -174,6 +177,8 @@ class Rendered3DInstance {
* Return the depth of the instance when the instance doesn't have a custom size.
*/
getDefaultDepth(): number;
getPropertyOverridings(): Map<string, string> | null;
}
declare type ObjectsRenderingService = {

View File

@@ -21,7 +21,9 @@ module.exports = {
.setExtensionInformation(
'Leaderboards',
_('Leaderboards'),
_('Allow your game to send scores to your leaderboards.'),
_(
'Allow your game to send scores to your leaderboards (anonymously or from the logged-in player) or display existing leaderboards to the player.'
),
'Florian Rival',
'Open source (MIT License)'
)
@@ -30,6 +32,12 @@ module.exports = {
.addInstructionOrExpressionGroupMetadata(_('Leaderboards'))
.setIcon('JsPlatform/Extensions/leaderboard.svg');
extension
.addDependency()
.setName('Safari View Controller Cordova plugin')
.setDependencyType('cordova')
.setExportName('@gdevelop/cordova-plugin-safariviewcontroller');
extension
.addAction(
'SavePlayerScore',

View File

@@ -22,7 +22,7 @@ module.exports = {
'Lighting',
_('Lights'),
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
'This provides a 2D light object, and a behavior to mark other 2D objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
'Harsimran Virk',
'MIT'
)
@@ -51,7 +51,7 @@ module.exports = {
_('Light Obstacle Behavior'),
'LightObstacleBehavior',
_(
'Flag objects as being obstacles to light. The light emitted by light objects will be stopped by the object.'
'Flag objects as being obstacles to 2D lights. The light emitted by light objects will be stopped by the object. This does not work on 3D objects and 3D games.'
),
'',
'CppPlatform/Extensions/lightObstacleIcon32.png',
@@ -164,7 +164,7 @@ module.exports = {
'LightObject',
_('Light'),
_(
'Displays a light on the scene, with a customizable radius and color. Add then the Light Obstacle behavior to the objects that must act as obstacle to the lights.'
'Displays a 2D light on the scene, with a customizable radius and color. Add then the Light Obstacle behavior to the objects that must act as obstacle to the lights.'
),
'CppPlatform/Extensions/lightIcon32.png',
lightObject

View File

@@ -239,7 +239,7 @@ namespace gdjs {
instanceContainer: gdjs.RuntimeInstanceContainer,
objectsLists: Hashtable<gdjs.RuntimeObject[]>,
obj: gdjs.RuntimeObject | null,
eventsFunctionContext: EventsFunctionContext | undefined
eventsFunctionContext: EventsFunctionContext | null | undefined
) {
if (obj === null) {
return false;

View File

@@ -21,7 +21,9 @@ module.exports = {
.setExtensionInformation(
'Multiplayer',
_('Multiplayer'),
_('Allow players to connect to lobbies and play together.'),
_(
'This allows players to join online lobbies and synchronize gameplay across devices without needing to manage servers or networking.\n\nUse the "Open game lobbies" action to let players join a game, and use conditions like "Lobby game has just started" to begin gameplay. Add the "Multiplayer object" behavior to game objects that should be synchronized, and assign or change their ownership using player numbers. Variables and game state (like scenes, scores, or timers) are automatically synced by the host, with options to change ownership or disable sync when needed. Common multiplayer logic —like handling joins/leaves, collisions, and host migration— is supported out-of-the-box for up to 8 players per game.'
),
'Florian Rival',
'Open source (MIT License)'
)
@@ -31,6 +33,79 @@ module.exports = {
.addInstructionOrExpressionGroupMetadata(_('Multiplayer'))
.setIcon('JsPlatform/Extensions/multiplayer.svg');
extension
.addDependency()
.setName('Safari View Controller Cordova plugin')
.setDependencyType('cordova')
.setExportName('@gdevelop/cordova-plugin-safariviewcontroller');
extension
.addStrExpression(
'CurrentLobbyID',
_('Current lobby ID'),
_('Returns current lobby ID.'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.getLobbyID');
extension
.addAction(
'QuickJoinWithLobbyID',
_('Join a specific lobby by its ID'),
_(
'Join a specific lobby. The player will join the game instantly if this is possible.'
),
_('Join a specific lobby by its ID _PARAM1_'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
'JsPlatform/Extensions/multiplayer.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('string', _('Lobby ID'), '', false)
.addParameter(
'yesorno',
_('Display loader while joining a lobby.'),
'',
true
)
.setDefaultValue('yes')
.addParameter(
'yesorno',
_('Display game lobbies if unable to join a specific one.'),
'',
true
)
.setDefaultValue('yes')
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.authenticateAndQuickJoinWithLobbyID');
extension
.addAction(
'QuickJoinLobby',

View File

@@ -293,6 +293,8 @@ namespace gdjs {
x: objectNetworkSyncData.x,
y: objectNetworkSyncData.y,
z: objectNetworkSyncData.z,
w: objectNetworkSyncData.w,
h: objectNetworkSyncData.h,
zo: objectNetworkSyncData.zo,
a: objectNetworkSyncData.a,
hid: objectNetworkSyncData.hid,
@@ -369,6 +371,9 @@ namespace gdjs {
this._lastSentBasicObjectSyncData = {
x: objectNetworkSyncData.x,
y: objectNetworkSyncData.y,
z: objectNetworkSyncData.z,
w: objectNetworkSyncData.w,
h: objectNetworkSyncData.h,
zo: objectNetworkSyncData.zo,
a: objectNetworkSyncData.a,
hid: objectNetworkSyncData.hid,

View File

@@ -17,9 +17,29 @@ namespace gdjs {
}[];
};
type LobbyStatus =
| 'waiting'
| 'starting'
| 'playing'
| 'migrating'
| 'migrated';
type LobbyConnectionStatus = 'waiting' | 'ready' | 'connected';
type InGamePlayerStatus = 'playing' | 'left';
type PlayerStatus = LobbyConnectionStatus | InGamePlayerStatus;
type LobbyPlayer = {
playerId: string;
status: PlayerStatus;
playerNumber: number;
};
type Lobby = {
id: string;
status: 'waiting' | 'starting' | 'playing' | 'migrating' | 'migrated';
minPlayers: number;
maxPlayers: number;
canJoinAfterStart: boolean;
players: LobbyPlayer[];
status: LobbyStatus;
};
type QuickJoinLobbyResponse =
@@ -105,6 +125,7 @@ namespace gdjs {
let _quickJoinLobbyFailureReason:
| 'FULL'
| 'NOT_ENOUGH_PLAYERS'
| 'DOES_NOT_EXIST'
| 'UNKNOWN'
| null = null;
let _lobbyId: string | null = null;
@@ -1697,11 +1718,87 @@ namespace gdjs {
}
};
export const authenticateAndQuickJoinLobby = async (
export const getLobbyID = (): string => {
return _lobbyId || '';
};
const quickJoinWithLobbyID = async (
runtimeScene: gdjs.RuntimeScene,
lobbyID: string,
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (_isQuickJoiningOrStartingAGame) return;
const _gameId = gdjs.projectData.properties.projectUuid;
if (!_gameId) {
logger.error(
'The game ID is missing, the quick join lobby action cannot continue.'
);
return;
}
_quickJoinLobbyFailureReason = null;
_isQuickJoiningOrStartingAGame = true;
if (displayLoader) {
gdjs.multiplayerComponents.displayLoader(runtimeScene, true);
}
const quickJoinWithLobbyIDRelativeUrl = `/play/game/${_gameId}/public-lobby/${lobbyID}`;
try {
const lobby: Lobby = await gdjs.evtTools.network.retryIfFailed(
{ times: 2 },
() =>
fetchAsPlayer({
relativeUrl: quickJoinWithLobbyIDRelativeUrl,
method: 'GET',
dev: isUsingGDevelopDevelopmentEnvironment,
})
);
const isFull = lobby.players.length === lobby.maxPlayers;
if (isFull) {
logger.error('Lobby is full - cannot quick join it.');
_quickJoinLobbyJustFailed = true;
_quickJoinLobbyFailureReason = 'FULL';
onLobbyQuickJoinFinished(runtimeScene);
if (openLobbiesPageIfFailure) {
openLobbiesWindow(runtimeScene);
}
return;
}
if (lobby.status === 'playing') {
_actionAfterJoiningLobby = 'JOIN_GAME';
} else if (lobby.status === 'waiting') {
if (lobby.players.length === 0) {
_actionAfterJoiningLobby = 'START_GAME';
} else {
_actionAfterJoiningLobby = 'OPEN_LOBBY_PAGE';
}
} else {
throw new Error(`Lobby in wrong status: ${lobby.status}`);
}
handleJoinLobbyEvent(runtimeScene, lobbyID);
} catch (error) {
const errorCode = parseInt(error.message.match(/\d{3}/)?.[0]);
if (errorCode === 404) {
logger.error('Lobby does not exist.');
_quickJoinLobbyFailureReason = 'DOES_NOT_EXIST';
} else {
logger.error('An error occurred while joining a lobby:', error);
_quickJoinLobbyFailureReason = 'UNKNOWN';
}
_quickJoinLobbyJustFailed = true;
onLobbyQuickJoinFinished(runtimeScene);
if (openLobbiesPageIfFailure) {
openLobbiesWindow(runtimeScene);
}
}
};
const isQuickJoiningTooFast = () => {
const requestDoneAt = Date.now();
if (_lastQuickJoinRequestDoneAt) {
if (requestDoneAt - _lastQuickJoinRequestDoneAt < 500) {
@@ -1709,12 +1806,18 @@ namespace gdjs {
logger.warn(
'Last request to quick join a lobby was sent too little time ago. Ignoring this one.'
);
return;
return true;
}
} else {
_lastQuickJoinRequestDoneAt = requestDoneAt;
}
return false;
};
const isNotCorrectlyAuthenticatedForQuickJoin = async (
runtimeScene: RuntimeScene
) => {
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!playerId || !playerToken) {
@@ -1724,14 +1827,43 @@ namespace gdjs {
.promise;
_isWaitingForLogin = false;
if (status === 'logged') {
await quickJoinLobby(
runtimeScene,
displayLoader,
openLobbiesPageIfFailure
);
if (status !== 'logged') {
return true;
}
}
return false;
};
export const authenticateAndQuickJoinWithLobbyID = async (
runtimeScene: gdjs.RuntimeScene,
lobbyID: string,
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (isQuickJoiningTooFast()) {
return;
}
if (await isNotCorrectlyAuthenticatedForQuickJoin(runtimeScene)) {
return;
}
await quickJoinWithLobbyID(
runtimeScene,
lobbyID,
displayLoader,
openLobbiesPageIfFailure
);
};
export const authenticateAndQuickJoinLobby = async (
runtimeScene: gdjs.RuntimeScene,
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (isQuickJoiningTooFast()) {
return;
}
if (await isNotCorrectlyAuthenticatedForQuickJoin(runtimeScene)) {
return;
}
await quickJoinLobby(

View File

@@ -389,26 +389,15 @@ namespace gdjs {
this.updatePosition();
}
setColor(rgbColor): void {
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
this._centerSprite.tint = gdjs.rgbToHexNumber(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
setColor(rgbOrHexColor: string): void {
const tint = gdjs.rgbOrHexStringToNumber(rgbOrHexColor);
this._centerSprite.tint = tint;
for (
let borderCounter = 0;
borderCounter < this._borderSprites.length;
borderCounter++
) {
this._borderSprites[borderCounter].tint = gdjs.rgbToHexNumber(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
this._borderSprites[borderCounter].tint = tint;
}
this._spritesContainer.cacheAsBitmap = false;
}
@@ -416,11 +405,11 @@ namespace gdjs {
getColor() {
const rgb = new PIXI.Color(this._centerSprite.tint).toRgbArray();
return (
Math.floor(rgb[0] * 255) +
Math.round(rgb[0] * 255) +
';' +
Math.floor(rgb[1] * 255) +
Math.round(rgb[1] * 255) +
';' +
Math.floor(rgb[2] * 255)
Math.round(rgb[2] * 255)
);
}

View File

@@ -25,8 +25,6 @@ namespace gdjs {
export type PanelSpriteObjectData = ObjectData & PanelSpriteObjectDataType;
export type PanelSpriteNetworkSyncDataType = {
wid: number;
hei: number;
op: number;
color: string;
};
@@ -124,8 +122,6 @@ namespace gdjs {
getNetworkSyncData(): PanelSpriteNetworkSyncData {
return {
...super.getNetworkSyncData(),
wid: this.getWidth(),
hei: this.getHeight(),
op: this.getOpacity(),
color: this.getColor(),
};
@@ -138,12 +134,6 @@ namespace gdjs {
// Texture is not synchronized, see if this is asked or not.
if (networkSyncData.wid !== undefined) {
this.setWidth(networkSyncData.wid);
}
if (networkSyncData.hei !== undefined) {
this.setHeight(networkSyncData.hei);
}
if (networkSyncData.op !== undefined) {
this.setOpacity(networkSyncData.op);
}

View File

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

View File

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

View File

@@ -630,24 +630,34 @@ namespace gdjs {
override onDeActivate() {
this._sharedData.removeFromBehaviorsList(this);
this._destroyBody();
}
override onActivate() {
this._sharedData.addToBehaviorsList(this);
}
override onDestroy() {
this._destroyedDuringFrameLogic = true;
this.onDeActivate();
}
_destroyBody() {
this.bodyUpdater.destroyBody();
this._contactsEndedThisFrame.length = 0;
this._contactsStartedThisFrame.length = 0;
this._currentContacts.length = 0;
}
override onActivate() {
this._sharedData.addToBehaviorsList(this);
this._contactsEndedThisFrame.length = 0;
this._contactsStartedThisFrame.length = 0;
this._currentContacts.length = 0;
this.updateBodyFromObject();
resetToDefaultBodyUpdater() {
this.bodyUpdater = new gdjs.Physics3DRuntimeBehavior.DefaultBodyUpdater(
this
);
}
override onDestroy() {
this._destroyedDuringFrameLogic = true;
this.onDeActivate();
resetToDefaultCollisionChecker() {
this.collisionChecker =
new gdjs.Physics3DRuntimeBehavior.DefaultCollisionChecker(this);
}
createShape(): Jolt.Shape {
@@ -1176,6 +1186,33 @@ namespace gdjs {
this._needToRecreateBody = true;
}
getShapeOffsetX(): float {
return this.shapeOffsetX;
}
setShapeOffsetX(shapeOffsetX: float): void {
this.shapeOffsetX = shapeOffsetX;
this._needToRecreateShape = true;
}
getShapeOffsetY(): float {
return this.shapeOffsetY;
}
setShapeOffsetY(shapeOffsetY: float): void {
this.shapeOffsetY = shapeOffsetY;
this._needToRecreateShape = true;
}
getShapeOffsetZ(): float {
return this.shapeOffsetZ;
}
setShapeOffsetZ(shapeOffsetZ: float): void {
this.shapeOffsetZ = shapeOffsetZ;
this._needToRecreateShape = true;
}
getFriction(): float {
return this.friction;
}
@@ -1540,9 +1577,9 @@ namespace gdjs {
}
const body = this._body!;
const deltaX = towardX - body.GetPosition().GetX();
const deltaY = towardY - body.GetPosition().GetY();
const deltaZ = towardZ - body.GetPosition().GetZ();
const deltaX = towardX - this.owner3D.getX();
const deltaY = towardY - this.owner3D.getY();
const deltaZ = towardZ - this.owner3D.getZ();
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
if (distanceSq === 0) {
return;
@@ -1600,19 +1637,16 @@ namespace gdjs {
length: float,
towardX: float,
towardY: float,
towardZ: float,
originX: float,
originY: float,
originZ: float
towardZ: float
): void {
if (this._body === null) {
if (!this._createBody()) return;
}
const body = this._body!;
const deltaX = towardX - originX;
const deltaY = towardY - originY;
const deltaZ = towardZ - originZ;
const deltaX = towardX - this.owner3D.getX();
const deltaY = towardY - this.owner3D.getY();
const deltaZ = towardZ - this.owner3D.getZ();
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
if (distanceSq === 0) {
return;
@@ -1621,12 +1655,7 @@ namespace gdjs {
this._sharedData.bodyInterface.AddImpulse(
body.GetID(),
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio),
this.getRVec3(
originX * this._sharedData.worldInvScale,
originY * this._sharedData.worldInvScale,
originZ * this._sharedData.worldInvScale
)
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio)
);
}

View File

@@ -29,6 +29,7 @@ namespace gdjs {
owner3D: gdjs.RuntimeObject3D;
private _physics3DBehaviorName: string;
private _physics3D: Physics3D | null = null;
private _isHookedToPhysicsStep = false;
_vehicleController: Jolt.WheeledVehicleController | null = null;
_stepListener: Jolt.VehicleConstraintStepListener | null = null;
_vehicleCollisionTester: Jolt.VehicleCollisionTesterCastCylinder | null =
@@ -153,13 +154,19 @@ namespace gdjs {
const behavior = this.owner.getBehavior(
this._physics3DBehaviorName
) as gdjs.Physics3DRuntimeBehavior;
if (!behavior.activated()) {
return null;
}
const sharedData = behavior._sharedData;
this._physics3D = {
behavior,
};
sharedData.registerHook(this);
if (!this._isHookedToPhysicsStep) {
sharedData.registerHook(this);
this._isHookedToPhysicsStep = true;
}
behavior.bodyUpdater =
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
@@ -330,25 +337,33 @@ namespace gdjs {
}
override onDeActivate() {
if (this._stepListener) {
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
if (!this._physics3D) {
return;
}
this._destroyBody();
}
override onActivate() {
if (this._stepListener) {
this._sharedData.physicsSystem.AddStepListener(this._stepListener);
const behavior = this.owner.getBehavior(
this._physics3DBehaviorName
) as gdjs.Physics3DRuntimeBehavior;
if (!behavior) {
return;
}
behavior._destroyBody();
}
override onDestroy() {
this._destroyedDuringFrameLogic = true;
this._destroyBody();
}
_destroyBody() {
if (!this._vehicleController) {
return;
}
this._destroyedDuringFrameLogic = true;
this.onDeActivate();
if (this._stepListener) {
// stepListener is removed by onDeActivate
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
Jolt.destroy(this._stepListener);
this._stepListener = null;
}
@@ -360,6 +375,8 @@ namespace gdjs {
// It is destroyed with the constraint.
this._vehicleCollisionTester = null;
if (this._physics3D) {
const { behavior } = this._physics3D;
behavior.resetToDefaultBodyUpdater();
this._physics3D = null;
}
}
@@ -1110,7 +1127,7 @@ namespace gdjs {
}
destroyBody() {
this.carBehavior.onDestroy();
this.carBehavior._destroyBody();
this.physicsBodyUpdater.destroyBody();
}
}

View File

@@ -41,6 +41,7 @@ namespace gdjs {
owner3D: gdjs.RuntimeObject3D;
private _physics3DBehaviorName: string;
private _physics3D: Physics3D | null = null;
private _isHookedToPhysicsStep = false;
character: Jolt.CharacterVirtual | null = null;
/**
* sharedData is a reference to the shared data of the scene, that registers
@@ -169,10 +170,15 @@ namespace gdjs {
if (this._physics3D) {
return this._physics3D;
}
if (!this.activated()) {
return null;
}
const behavior = this.owner.getBehavior(
this._physics3DBehaviorName
) as gdjs.Physics3DRuntimeBehavior;
if (!behavior.activated()) {
return null;
}
const sharedData = behavior._sharedData;
const jolt = sharedData.jolt;
const extendedUpdateSettings = new Jolt.ExtendedUpdateSettings();
@@ -196,7 +202,10 @@ namespace gdjs {
shapeFilter,
};
this.setStairHeightMax(this._stairHeightMax);
sharedData.registerHook(this);
if (!this._isHookedToPhysicsStep) {
sharedData.registerHook(this);
this._isHookedToPhysicsStep = true;
}
behavior.bodyUpdater =
new gdjs.PhysicsCharacter3DRuntimeBehavior.CharacterBodyUpdater(this);
@@ -390,36 +399,48 @@ namespace gdjs {
}
override onDeActivate() {
this.collisionChecker.clearContacts();
if (!this._physics3D) {
return;
}
this._destroyBody();
}
override onActivate() {}
override onActivate() {
const behavior = this.owner.getBehavior(
this._physics3DBehaviorName
) as gdjs.Physics3DRuntimeBehavior;
if (!behavior) {
return;
}
behavior._destroyBody();
}
override onDestroy() {
this._destroyedDuringFrameLogic = true;
this.onDeActivate();
this._destroyCharacter();
}
/**
* Remove the character and its body from the physics engine.
* This method is called when:
* - The Physics3D behavior is deactivated
* - This behavior is deactivated
* - The object is destroyed
*
* Only deactivating the character behavior won't destroy the character.
* Indeed, deactivated characters don't move as characters but still have collisions.
*/
_destroyCharacter() {
_destroyBody() {
if (this.character) {
if (this._canBePushed) {
this.charactersManager.removeCharacter(this.character);
Jolt.destroy(this.character.GetListener());
}
this.collisionChecker.clearContacts();
// The body is destroyed with the character.
Jolt.destroy(this.character);
this.character = null;
if (this._physics3D) {
const { behavior } = this._physics3D;
behavior.resetToDefaultBodyUpdater();
behavior.resetToDefaultCollisionChecker();
this._physics3D.behavior._body = null;
const {
extendedUpdateSettings,
@@ -1780,7 +1801,7 @@ namespace gdjs {
}
destroyBody() {
this.characterBehavior._destroyCharacter();
this.characterBehavior._destroyBody();
}
}

View File

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

View File

@@ -35,7 +35,7 @@ module.exports = {
.addDependency()
.setName('Safari View Controller Cordova plugin')
.setDependencyType('cordova')
.setExportName('cordova-plugin-safariviewcontroller');
.setExportName('@gdevelop/cordova-plugin-safariviewcontroller');
extension
.addAction(

View File

@@ -834,34 +834,51 @@ namespace gdjs {
authWindowOptions,
});
if (typeof SafariViewController === 'undefined') {
logger.error(
'Cordova plugin SafariViewController is not installed.'
);
resolve('errored');
return;
}
SafariViewController.isAvailable(function (available: boolean) {
if (available) {
SafariViewController.show(
{
url: targetUrl,
hidden: false,
animated: true,
transition: 'slide',
enterReaderModeIfAvailable: false,
barColor: '#000000',
tintColor: '#ffffff',
controlTintColor: '#ffffff',
},
function (result: any) {
// Other events are `opened` and `loaded`.
if (result.event === 'closed') {
resolve('dismissed');
}
},
function (error: any) {
logger.log('Error opening webview: ' + JSON.stringify(error));
resolve('errored');
}
if (!available) {
logger.error(
'Cordova plugin SafariViewController is installed but not available'
);
} else {
logger.error('Plugin SafariViewController is not available');
resolve('errored');
return;
}
logger.info(
'Opening authentication window for Cordova with SafariViewController.'
);
SafariViewController.show(
{
url: targetUrl,
hidden: false,
animated: true,
transition: 'slide',
enterReaderModeIfAvailable: false,
barColor: '#000000',
tintColor: '#ffffff',
controlTintColor: '#ffffff',
},
function (result: any) {
// Other events are `opened` and `loaded`.
if (result.event === 'closed') {
resolve('dismissed');
}
},
function (error: any) {
logger.log(
'Error opening authentication window: ' +
JSON.stringify(error)
);
resolve('errored');
}
);
});
}
);

View File

@@ -15,7 +15,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.SetExtensionInformation(
"PrimitiveDrawing",
_("Shape painter"),
_("This provides an object that can be used to draw arbitrary shapes "
_("An object that can be used to draw arbitrary 2D shapes "
"on the screen using events."),
"Florian Rival and Aurélien Vivet",
"Open source (MIT License)")
@@ -28,7 +28,7 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddObject<ShapePainterObject>(
"Drawer", //"Drawer" is kept for compatibility with GD<=3.6.76
_("Shape painter"),
_("Allows you to draw simple shapes on the screen using the "
_("Allows to draw simple 2D shapes on the screen using the "
"events."),
"CppPlatform/Extensions/primitivedrawingicon.png")
.SetCategoryFullName(_("Advanced"))
@@ -125,11 +125,11 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.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_"),
_("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")
@@ -142,7 +142,6 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Fillet (in pixels)"))
.SetFunctionName("DrawFilletRectangle");
obj.AddAction("RoundedRectangle",
_("Rounded rectangle"),
_("Draw a rounded rectangle on screen"),
@@ -170,54 +169,53 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
_("Drawing"),
"res/actions/chamferRectangle24.png",
"res/actions/chamferRectangle.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", _("Chamfer (in pixels)"))
.SetFunctionName("DrawChamferRectangle");
.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", _("Chamfer (in pixels)"))
.SetFunctionName("DrawChamferRectangle");
obj.AddAction("Torus",
_("Torus"),
_("Draw a torus on screen"),
_("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",
"res/actions/torus.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of center"))
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression", _("Inner Radius (in pixels)"))
.AddParameter("expression", _("Outer Radius (in pixels)"))
.AddParameter("expression", _("Start Arc (in degrees)"))
.AddParameter("expression", _("End Arc (in degrees)"))
.SetFunctionName("DrawTorus");
_("Torus"),
_("Draw a torus on screen"),
_("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",
"res/actions/torus.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of center"))
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression", _("Inner Radius (in pixels)"))
.AddParameter("expression", _("Outer Radius (in pixels)"))
.AddParameter("expression", _("Start Arc (in degrees)"))
.AddParameter("expression", _("End Arc (in degrees)"))
.SetFunctionName("DrawTorus");
obj.AddAction("RegularPolygon",
_("Regular Polygon"),
_("Draw a regular polygon on screen"),
_("Draw at _PARAM1_;_PARAM2_ a regular polygon with _PARAM3_ sides and radius: "
_("Draw at _PARAM1_;_PARAM2_ a regular polygon with _PARAM3_ "
"sides and radius: "
"_PARAM4_ (rotation: _PARAM5_) "
"with _PARAM0_"),
_("Drawing"),
"res/actions/regularPolygon24.png",
"res/actions/regularPolygon.png")
_("Drawing"),
"res/actions/regularPolygon24.png",
"res/actions/regularPolygon.png")
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of center"))
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression",
_("Number of sides of the polygon (minimum: 3)"))
.AddParameter("expression", _("Radius (in pixels)"))
.AddParameter("expression", _("Rotation (in degrees)"))
.SetFunctionName("DrawRegularPolygon");
.AddParameter("object", _("Shape Painter object"), "Drawer")
.AddParameter("expression", _("X position of center"))
.AddParameter("expression", _("Y position of center"))
.AddParameter("expression",
_("Number of sides of the polygon (minimum: 3)"))
.AddParameter("expression", _("Radius (in pixels)"))
.AddParameter("expression", _("Rotation (in degrees)"))
.SetFunctionName("DrawRegularPolygon");
obj.AddAction(
"Star",

View File

@@ -202,28 +202,18 @@ namespace gdjs {
this._loadingSpineAtlases.clear();
}
/**
* Unload the specified list of resources:
* this clears the Spine atlases loaded in this manager.
*
* Usually called when scene resoures are unloaded.
*
* @param resourcesList The list of specific resources
*/
unloadResourcesList(resourcesList: ResourceData[]): void {
resourcesList.forEach((resourceData) => {
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
if (loadedSpineAtlas) {
loadedSpineAtlas.dispose();
this._loadedSpineAtlases.delete(resourceData);
}
unloadResource(resourceData: ResourceData): void {
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
if (loadedSpineAtlas) {
loadedSpineAtlas.dispose();
this._loadedSpineAtlases.delete(resourceData);
}
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
if (loadingSpineAtlas) {
loadingSpineAtlas.then((atl) => atl.dispose());
this._loadingSpineAtlases.delete(resourceData);
}
});
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
if (loadingSpineAtlas) {
loadingSpineAtlas.then((atl) => atl.dispose());
this._loadingSpineAtlases.delete(resourceData);
}
}
}
}

View File

@@ -127,21 +127,11 @@ namespace gdjs {
this._loadedSpines.clear();
}
/**
* Unload the specified list of resources:
* this clears the Spine skeleton data loaded in this manager.
*
* Usually called when scene resoures are unloaded.
*
* @param resourcesList The list of specific resources
*/
unloadResourcesList(resourcesList: ResourceData[]): void {
resourcesList.forEach((resourceData) => {
const loadedSpine = this._loadedSpines.get(resourceData);
if (loadedSpine) {
this._loadedSpines.delete(resourceData);
}
});
unloadResource(resourceData: ResourceData): void {
const loadedSpine = this._loadedSpines.get(resourceData);
if (loadedSpine) {
this._loadedSpines.delete(resourceData);
}
}
}
}

View File

@@ -14,8 +14,6 @@ namespace gdjs {
export type SpineNetworkSyncDataType = {
opa: float;
wid: float;
hei: float;
scaX: float;
scaY: float;
flipX: boolean;
@@ -117,8 +115,6 @@ namespace gdjs {
return {
...super.getNetworkSyncData(),
opa: this._opacity,
wid: this.getWidth(),
hei: this.getHeight(),
scaX: this.getScaleX(),
scaY: this.getScaleY(),
flipX: this.isFlippedX(),
@@ -137,12 +133,6 @@ namespace gdjs {
if (syncData.opa !== undefined && syncData.opa !== this._opacity) {
this.setOpacity(syncData.opa);
}
if (syncData.wid !== undefined && syncData.wid !== this.getWidth()) {
this.setWidth(syncData.wid);
}
if (syncData.hei !== undefined && syncData.hei !== this.getHeight()) {
this.setHeight(syncData.hei);
}
if (syncData.scaX !== undefined && syncData.scaX !== this.getScaleX()) {
this.setScaleX(syncData.scaX);
}

View File

@@ -13,7 +13,8 @@ void DeclareSystemInfoExtension(gd::PlatformExtension& extension) {
.SetExtensionInformation(
"SystemInfo",
_("System information"),
_("Get information about the system and device running the game."),
_("Conditions to check if the device has a touchscreen, is a mobile, "
"or if the game runs as a preview."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Advanced");

View File

@@ -78,6 +78,9 @@ module.exports = {
} else if (propertyName === 'disabled') {
objectContent.disabled = newValue === '1';
return true;
} else if (propertyName === 'spellCheck') {
objectContent.spellCheck = newValue === '1';
return true;
} else if (propertyName === 'maxLength') {
objectContent.maxLength = newValue;
return true;
@@ -160,6 +163,13 @@ module.exports = {
.setLabel(_('Disabled'))
.setGroup(_('Field'));
objectProperties
.getOrCreate('spellCheck')
.setValue(objectContent.spellCheck ? 'true' : 'false')
.setType('boolean')
.setLabel(_('Enable spell check'))
.setGroup(_('Field'));
objectProperties
.getOrCreate('textColor')
.setValue(objectContent.textColor || '0;0;0')
@@ -272,6 +282,7 @@ module.exports = {
borderWidth: 1,
readOnly: false,
disabled: false,
spellCheck: false,
paddingX: 2,
paddingY: 1,
textAlign: 'left',
@@ -592,6 +603,21 @@ module.exports = {
.setFunctionName('setDisabled')
.setGetter('isDisabled');
object
.addExpressionAndConditionAndAction(
'boolean',
'SpellCheck',
_('Spell check enabled'),
_('spell check is enabled'),
_('spell check enabled'),
'',
'res/conditions/text24_black.png'
)
.addParameter('object', _('Text input'), 'TextInputObject', false)
.useStandardParameters('boolean', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setSpellCheck')
.setGetter('isSpellCheckEnabled');
// Other expressions/conditions/actions:
// Deprecated

View File

@@ -106,6 +106,7 @@ namespace gdjs {
this.updateBorderWidth();
this.updateDisabled();
this.updateReadOnly();
this.updateSpellCheck();
this.updateTextAlign();
this.updateMaxLength();
this.updatePadding();
@@ -342,6 +343,12 @@ namespace gdjs {
this._input.readOnly = this._object.isReadOnly();
}
updateSpellCheck() {
if (!this._input) return;
this._input.spellcheck = this._object.isSpellCheckEnabled();
}
updateMaxLength() {
const input = this._input;
if (!input) return;

View File

@@ -54,6 +54,7 @@ namespace gdjs {
disabled: boolean;
readOnly: boolean;
// ---- Values can be undefined because of support for these feature was added in v5.5.222.
spellCheck?: boolean;
paddingX?: float;
paddingY?: float;
textAlign?: SupportedTextAlign;
@@ -64,8 +65,6 @@ namespace gdjs {
export type TextInputNetworkSyncDataType = {
opa: float;
wid: float;
hei: float;
txt: string;
frn: string;
fs: number;
@@ -79,6 +78,7 @@ namespace gdjs {
bw: float;
dis: boolean;
ro: boolean;
sc: boolean;
};
export type TextInputNetworkSyncData = ObjectNetworkSyncData &
@@ -118,6 +118,7 @@ namespace gdjs {
private _borderWidth: float;
private _disabled: boolean;
private _readOnly: boolean;
private _spellCheck: boolean;
private _isSubmitted: boolean;
_renderer: TextInputRuntimeObjectRenderer;
@@ -142,6 +143,10 @@ namespace gdjs {
this._borderWidth = objectData.content.borderWidth;
this._disabled = objectData.content.disabled;
this._readOnly = objectData.content.readOnly;
this._spellCheck =
objectData.content.spellCheck !== undefined
? objectData.content.spellCheck
: false;
this._textAlign = parseTextAlign(objectData.content.textAlign);
this._maxLength = objectData.content.maxLength || 0;
this._paddingX =
@@ -228,6 +233,12 @@ namespace gdjs {
if (oldObjectData.content.readOnly !== newObjectData.content.readOnly) {
this.setReadOnly(newObjectData.content.readOnly);
}
if (
newObjectData.content.spellCheck !== undefined &&
oldObjectData.content.spellCheck !== newObjectData.content.spellCheck
) {
this.setSpellCheck(newObjectData.content.spellCheck);
}
if (
newObjectData.content.maxLength !== undefined &&
oldObjectData.content.maxLength !== newObjectData.content.maxLength
@@ -260,8 +271,6 @@ namespace gdjs {
return {
...super.getNetworkSyncData(),
opa: this.getOpacity(),
wid: this.getWidth(),
hei: this.getHeight(),
txt: this.getText(),
frn: this.getFontResourceName(),
fs: this.getFontSize(),
@@ -275,6 +284,7 @@ namespace gdjs {
bw: this.getBorderWidth(),
dis: this.isDisabled(),
ro: this.isReadOnly(),
sc: this.isSpellCheckEnabled(),
};
}
@@ -282,8 +292,6 @@ namespace gdjs {
super.updateFromNetworkSyncData(syncData);
if (syncData.opa !== undefined) this.setOpacity(syncData.opa);
if (syncData.wid !== undefined) this.setWidth(syncData.wid);
if (syncData.hei !== undefined) this.setHeight(syncData.hei);
if (syncData.txt !== undefined) this.setText(syncData.txt);
if (syncData.frn !== undefined) this.setFontResourceName(syncData.frn);
if (syncData.fs !== undefined) this.setFontSize(syncData.fs);
@@ -297,6 +305,7 @@ namespace gdjs {
if (syncData.bw !== undefined) this.setBorderWidth(syncData.bw);
if (syncData.dis !== undefined) this.setDisabled(syncData.dis);
if (syncData.ro !== undefined) this.setReadOnly(syncData.ro);
if (syncData.sc !== undefined) this.setSpellCheck(syncData.sc);
}
updatePreRender(instanceContainer: RuntimeInstanceContainer): void {
@@ -569,6 +578,15 @@ namespace gdjs {
return this._readOnly;
}
setSpellCheck(value: boolean) {
this._spellCheck = value;
this._renderer.updateSpellCheck();
}
isSpellCheckEnabled(): boolean {
return this._spellCheck;
}
isFocused(): boolean {
return this._renderer.isFocused();
}

View File

@@ -449,6 +449,16 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"), "Text")
.UseStandardParameters("number", gd::ParameterOptions::MakeNewOptions());
obj.AddExpressionAndConditionAndAction("number",
"LineHeight",
_("Line height"),
_("the line height of a text object"),
_("the line height"),
"",
"res/actions/font24.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();

View File

@@ -96,6 +96,14 @@ class TextObjectJsExtension : public gd::PlatformExtension {
.SetFunctionName("setOutlineThickness")
.SetGetter("getOutlineThickness");
GetAllExpressionsForObject("TextObject::Text")["LineHeight"]
.SetFunctionName("getLineHeight");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::LineHeight"]
.SetFunctionName("getLineHeight");
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetLineHeight"]
.SetFunctionName("setLineHeight")
.SetGetter("getLineHeight");
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
.SetFunctionName("showShadow");
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsShadowEnabled"]

View File

@@ -20,6 +20,7 @@ using namespace std;
TextObject::TextObject()
: text("Text"),
characterSize(20),
lineHeight(0),
fontName(""),
smoothed(true),
bold(false),
@@ -50,6 +51,10 @@ bool TextObject::UpdateProperty(const gd::String& propertyName,
characterSize = newValue.To<double>();
return true;
}
if (propertyName == "lineHeight") {
lineHeight = newValue.To<double>();
return true;
}
if (propertyName == "font") {
fontName = newValue;
return true;
@@ -129,6 +134,13 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Font"));
objectProperties["lineHeight"]
.SetValue(gd::String::From(lineHeight))
.SetType("number")
.SetLabel(_("Line height"))
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
.SetGroup(_("Font"));
objectProperties["font"]
.SetValue(fontName)
.SetType("resource")
@@ -271,6 +283,7 @@ void TextObject::DoUnserializeFrom(gd::Project& project,
SetCharacterSize(content.GetChild("characterSize", 0, "CharacterSize")
.GetValue()
.GetInt());
SetLineHeight(content.GetDoubleAttribute("lineHeight", 0));
smoothed = content.GetBoolAttribute("smoothed");
bold = content.GetBoolAttribute("bold");
italic = content.GetBoolAttribute("italic");
@@ -339,6 +352,7 @@ void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
content.AddChild("textAlignment").SetValue(GetTextAlignment());
content.AddChild("verticalTextAlignment").SetValue(GetVerticalTextAlignment());
content.AddChild("characterSize").SetValue(GetCharacterSize());
content.AddChild("lineHeight").SetValue(GetLineHeight());
content.AddChild("color").SetValue(GetColor());
content.SetAttribute("smoothed", smoothed);

View File

@@ -49,6 +49,12 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
*/
inline double GetCharacterSize() const { return characterSize; };
/** \brief Change the line height. */
inline void SetLineHeight(double value) { lineHeight = value; };
/** \brief Get the line height. */
inline double GetLineHeight() const { return lineHeight; };
/** \brief Return the name of the font resource used for the text.
*/
inline const gd::String& GetFontName() const { return fontName; };
@@ -120,6 +126,7 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
gd::String text;
double characterSize;
double lineHeight;
gd::String fontName;
bool smoothed;
bool bold, italic, underlined;

View File

@@ -86,6 +86,7 @@ namespace gdjs {
? style.dropShadowDistance + style.dropShadowBlur
: 0;
style.padding = Math.ceil(this._object._padding + extraPaddingForShadow);
style.lineHeight = this._object._lineHeight;
// Prevent spikey outlines by adding a miter limit
style.miterLimit = 3;

View File

@@ -22,6 +22,8 @@ namespace gdjs {
text: string;
textAlignment: string;
verticalTextAlignment: string;
/** The line height */
lineHeight: float;
isOutlineEnabled: boolean;
outlineThickness: float;
@@ -62,6 +64,7 @@ namespace gdjs {
sha: float;
shb: float;
pad: integer;
lh: float;
};
export type TextObjectNetworkSyncData = ObjectNetworkSyncData &
@@ -101,6 +104,8 @@ namespace gdjs {
_shadowAngle: float;
_shadowBlur: float;
_lineHeight: float;
_padding: integer = 5;
_str: string;
_renderer: gdjs.TextRuntimeObjectRenderer;
@@ -139,6 +144,7 @@ namespace gdjs {
this._shadowDistance = content.shadowDistance;
this._shadowBlur = content.shadowBlurRadius;
this._shadowAngle = content.shadowAngle;
this._lineHeight = content.lineHeight || 0;
this._renderer = new gdjs.TextRuntimeObjectRenderer(
this,
@@ -149,7 +155,7 @@ namespace gdjs {
this.onCreated();
}
updateFromObjectData(
override updateFromObjectData(
oldObjectData: TextObjectData,
newObjectData: TextObjectData
): boolean {
@@ -211,10 +217,13 @@ namespace gdjs {
if (oldContent.shadowBlurRadius !== newContent.shadowBlurRadius) {
this.setShadowBlurRadius(newContent.shadowBlurRadius);
}
if ((oldContent.lineHeight || 0) !== (newContent.lineHeight || 0)) {
this.setLineHeight(newContent.lineHeight || 0);
}
return true;
}
getNetworkSyncData(): TextObjectNetworkSyncData {
override getNetworkSyncData(): TextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
str: this._str,
@@ -238,11 +247,12 @@ namespace gdjs {
shd: this._shadowDistance,
sha: this._shadowAngle,
shb: this._shadowBlur,
lh: this._lineHeight,
pad: this._padding,
};
}
updateFromNetworkSyncData(
override updateFromNetworkSyncData(
networkSyncData: TextObjectNetworkSyncData
): void {
super.updateFromNetworkSyncData(networkSyncData);
@@ -312,28 +322,30 @@ namespace gdjs {
if (networkSyncData.shb !== undefined) {
this.setShadowBlurRadius(networkSyncData.shb);
}
if (networkSyncData.lh !== undefined) {
this.setLineHeight(networkSyncData.lh);
}
if (networkSyncData.pad !== undefined) {
this.setPadding(networkSyncData.pad);
}
}
getRendererObject() {
override getRendererObject() {
return this._renderer.getRendererObject();
}
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.ensureUpToDate();
}
onDestroyed(): void {
override onDestroyed(): void {
super.onDestroyed();
this._renderer.destroy();
}
/**
* Initialize the extra parameters that could be set for an instance.
*/
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
override extraInitializationFromInitialInstance(
initialInstanceData: InstanceData
) {
if (initialInstanceData.customSize) {
this.setWrappingWidth(initialInstanceData.width);
this.setWrapping(true);
@@ -353,27 +365,17 @@ namespace gdjs {
this._renderer.updatePosition();
}
/**
* Set object position on X axis.
*/
setX(x: float): void {
override setX(x: float): void {
super.setX(x);
this._updateTextPosition();
}
/**
* Set object position on Y axis.
*/
setY(y: float): void {
override setY(y: float): void {
super.setY(y);
this._updateTextPosition();
}
/**
* Set the angle of the object.
* @param angle The new angle of the object
*/
setAngle(angle: float): void {
override setAngle(angle: float): void {
super.setAngle(angle);
this._renderer.updateAngle();
}
@@ -455,6 +457,22 @@ namespace gdjs {
this._renderer.updateStyle();
}
/**
* Get the line height of the text.
*/
getLineHeight(): float {
return this._lineHeight;
}
/**
* Set the line height of the text.
* @param value The new line height for the text.
*/
setLineHeight(value: float): void {
this._lineHeight = value;
this._renderer.updateStyle();
}
/**
* Set the name of the resource to use for the font.
* @param fontResourceName The name of the font resource.
@@ -499,14 +517,14 @@ namespace gdjs {
/**
* Get width of the text.
*/
getWidth(): float {
override getWidth(): float {
return this._wrapping ? this._wrappingWidth : this._renderer.getWidth();
}
/**
* Get height of the text.
*/
getHeight(): float {
override getHeight(): float {
return this._renderer.getHeight();
}
@@ -584,16 +602,10 @@ namespace gdjs {
/**
* Change the text color.
* @param colorString color as a "R;G;B" string, for example: "255;0;0"
* @param rgbOrHexColor color as a "R;G;B" string, for example: "255;0;0"
*/
setColor(colorString: string): void {
const color = colorString.split(';');
if (color.length < 3) {
return;
}
this._color[0] = parseInt(color[0], 10);
this._color[1] = parseInt(color[1], 10);
this._color[2] = parseInt(color[2], 10);
setColor(rgbOrHexColor: string): void {
this._color = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
this._useGradient = false;
this._renderer.updateStyle();
}
@@ -685,11 +697,11 @@ namespace gdjs {
}
}
setWidth(width: float): void {
override setWidth(width: float): void {
this.setWrappingWidth(width);
}
getDrawableY(): float {
override getDrawableY(): float {
return (
this.getY() -
(this._verticalTextAlignment === 'center'
@@ -702,18 +714,12 @@ namespace gdjs {
/**
* Set the outline for the text object.
* @param str color as a "R;G;B" string, for example: "255;0;0"
* @param rgbOrHexColor 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(';');
if (color.length < 3) {
return;
}
this._outlineColor[0] = parseInt(color[0], 10);
this._outlineColor[1] = parseInt(color[1], 10);
this._outlineColor[2] = parseInt(color[2], 10);
setOutline(rgbOrHexColor: string, thickness: number): void {
this._outlineColor = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
this._outlineThickness = thickness;
this._renderer.updateStyle();
}
@@ -755,25 +761,19 @@ namespace gdjs {
/**
* Set the shadow for the text object.
* @param str color as a "R;G;B" string, for example: "255;0;0"
* @param rgbOrHexColor 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,
rgbOrHexColor: string,
distance: number,
blur: integer,
angle: float
): void {
const color = str.split(';');
if (color.length < 3) {
return;
}
this._shadowColor[0] = parseInt(color[0], 10);
this._shadowColor[1] = parseInt(color[1], 10);
this._shadowColor[2] = parseInt(color[2], 10);
this._shadowColor = gdjs.rgbOrHexToRGBColor(rgbOrHexColor);
this._shadowDistance = distance;
this._shadowBlur = blur;
this._shadowAngle = angle;
@@ -886,38 +886,18 @@ namespace gdjs {
strThirdColor: string,
strFourthColor: string
): void {
const colorFirst = strFirstColor.split(';');
const colorSecond = strSecondColor.split(';');
const colorThird = strThirdColor.split(';');
const colorFourth = strFourthColor.split(';');
this._gradient = [];
if (colorFirst.length == 3) {
this._gradient.push([
parseInt(colorFirst[0], 10),
parseInt(colorFirst[1], 10),
parseInt(colorFirst[2], 10),
]);
if (strFirstColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strFirstColor));
}
if (colorSecond.length == 3) {
this._gradient.push([
parseInt(colorSecond[0], 10),
parseInt(colorSecond[1], 10),
parseInt(colorSecond[2], 10),
]);
if (strSecondColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strSecondColor));
}
if (colorThird.length == 3) {
this._gradient.push([
parseInt(colorThird[0], 10),
parseInt(colorThird[1], 10),
parseInt(colorThird[2], 10),
]);
if (strThirdColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strThirdColor));
}
if (colorFourth.length == 3) {
this._gradient.push([
parseInt(colorFourth[0], 10),
parseInt(colorFourth[1], 10),
parseInt(colorFourth[2], 10),
]);
if (strFourthColor) {
this._gradient.push(gdjs.rgbOrHexToRGBColor(strFourthColor));
}
this._gradientType = strGradientType;
this._useGradient = this._gradient.length > 1 ? true : false;

View File

@@ -17,8 +17,6 @@ namespace gdjs {
export type SimpleTileMapNetworkSyncDataType = {
op: number;
ai: string;
wid: number;
hei: number;
// TODO: Support tilemap synchronization. Find an efficient way to send tiles changes.
};
@@ -170,8 +168,6 @@ namespace gdjs {
...super.getNetworkSyncData(),
op: this._opacity,
ai: this._atlasImage,
wid: this.getWidth(),
hei: this.getHeight(),
};
}
@@ -186,18 +182,6 @@ namespace gdjs {
) {
this.setOpacity(networkSyncData.op);
}
if (
networkSyncData.wid !== undefined &&
networkSyncData.wid !== this.getWidth()
) {
this.setWidth(networkSyncData.wid);
}
if (
networkSyncData.hei !== undefined &&
networkSyncData.hei !== this.getHeight()
) {
this.setHeight(networkSyncData.hei);
}
if (networkSyncData.ai !== undefined) {
// TODO: support changing the atlas texture
}

View File

@@ -26,8 +26,6 @@ namespace gdjs {
os: float;
fo: float;
oo: float;
wid: float;
hei: float;
};
export type TilemapCollisionMaskNetworkSyncData = ObjectNetworkSyncData &
@@ -202,8 +200,6 @@ namespace gdjs {
os: this.getOutlineSize(),
fo: this.getFillOpacity(),
oo: this.getOutlineOpacity(),
wid: this.getWidth(),
hei: this.getHeight(),
};
}
@@ -236,12 +232,6 @@ namespace gdjs {
if (networkSyncData.oo !== undefined) {
this.setOutlineOpacity(networkSyncData.oo);
}
if (networkSyncData.wid !== undefined) {
this.setWidth(networkSyncData.wid);
}
if (networkSyncData.hei !== undefined) {
this.setHeight(networkSyncData.hei);
}
}
extraInitializationFromInitialInstance(initialInstanceData): void {

View File

@@ -25,8 +25,6 @@ namespace gdjs {
lai: number;
lei: number;
asps: number;
wid: number;
hei: number;
};
export type TilemapNetworkSyncData = ObjectNetworkSyncData &
@@ -158,8 +156,6 @@ namespace gdjs {
lai: this._layerIndex,
lei: this._levelIndex,
asps: this._animationSpeedScale,
wid: this.getWidth(),
hei: this.getHeight(),
};
}
@@ -190,12 +186,6 @@ namespace gdjs {
if (networkSyncData.asps !== undefined) {
this.setAnimationSpeedScale(networkSyncData.asps);
}
if (networkSyncData.wid !== undefined) {
this.setWidth(networkSyncData.wid);
}
if (networkSyncData.hei !== undefined) {
this.setHeight(networkSyncData.hei);
}
}
extraInitializationFromInitialInstance(initialInstanceData): void {

View File

@@ -17,7 +17,7 @@ void DeclareTiledSpriteObjectExtension(gd::PlatformExtension& extension) {
.SetExtensionInformation(
"TiledSpriteObject",
_("Tiled Sprite Object"),
"Displays an image in a repeating pattern over an area. Useful for "
"Displays a 2D image in a repeating pattern over an area. Useful for "
"making backgrounds, including background that are scrolling when "
"the camera moves. This is more performant than using multiple "
"Sprite objects.",

View File

@@ -92,28 +92,18 @@ namespace gdjs {
-this._object._yOffset % this._tiledSprite.texture.height;
}
setColor(rgbColor: string): void {
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
this._tiledSprite.tint =
'0x' +
gdjs.rgbToHex(
parseInt(colors[0], 10),
parseInt(colors[1], 10),
parseInt(colors[2], 10)
);
setColor(rgbOrHexColor: string): void {
this._tiledSprite.tint = gdjs.rgbOrHexStringToNumber(rgbOrHexColor);
}
getColor() {
const rgb = new PIXI.Color(this._tiledSprite.tint).toRgbArray();
return (
Math.floor(rgb[0] * 255) +
Math.round(rgb[0] * 255) +
';' +
Math.floor(rgb[1] * 255) +
Math.round(rgb[1] * 255) +
';' +
Math.floor(rgb[2] * 255)
Math.round(rgb[2] * 255)
);
}

View File

@@ -15,8 +15,6 @@ namespace gdjs {
export type TiledSpriteObjectData = ObjectData & TiledSpriteObjectDataType;
export type TiledSpriteNetworkSyncDataType = {
wid: number;
hei: number;
xo: number;
yo: number;
op: number;
@@ -83,8 +81,6 @@ namespace gdjs {
getNetworkSyncData(): TiledSpriteNetworkSyncData {
return {
...super.getNetworkSyncData(),
wid: this.getWidth(),
hei: this.getHeight(),
xo: this.getXOffset(),
yo: this.getYOffset(),
op: this.getOpacity(),
@@ -99,12 +95,6 @@ namespace gdjs {
// Texture is not synchronized, see if this is asked or not.
if (networkSyncData.wid !== undefined) {
this.setWidth(networkSyncData.wid);
}
if (networkSyncData.hei !== undefined) {
this.setHeight(networkSyncData.hei);
}
if (networkSyncData.xo !== undefined) {
this.setXOffset(networkSyncData.xo);
}

View File

@@ -16,8 +16,11 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
.SetExtensionInformation(
"TopDownMovementBehavior",
_("Top-down movement"),
_("Allows to move objects in either 4 or 8 directions, with the "
"keyboard or using events."),
_("Allows to move an object in either 4 or 8 directions, with the "
"keyboard (default), a virtual stick (for this, also add the "
"\"Top-down multitouch controller mapper\" behavior and a"
"\"Multitouch Joystick\" object), gamepad or manually using "
"events."),
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
@@ -26,17 +29,17 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
extension.AddInstructionOrExpressionGroupMetadata(_("Top-down movement"))
.SetIcon("CppPlatform/Extensions/topdownmovementicon16.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TopDownMovementBehavior",
_("Top-down movement (4 or 8 directions)"),
"TopDownMovement",
_("Move objects left, up, right, and "
"down (and, optionally, diagonally)."),
"",
"CppPlatform/Extensions/topdownmovementicon.png",
"TopDownMovementBehavior",
std::make_shared<TopDownMovementBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
gd::BehaviorMetadata& aut =
extension.AddBehavior("TopDownMovementBehavior",
_("Top-down movement (4 or 8 directions)"),
"TopDownMovement",
_("Move objects left, up, right, and "
"down (and, optionally, diagonally)."),
"",
"CppPlatform/Extensions/topdownmovementicon.png",
"TopDownMovementBehavior",
std::make_shared<TopDownMovementBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
aut.AddAction("SimulateLeftKey",
_("Simulate left key press"),
@@ -119,7 +122,8 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddAction("SimulateStick",
_("Simulate stick control"),
_("Simulate a stick control."),
_("Simulate a stick control for _PARAM0_ with a _PARAM2_ angle and a _PARAM3_ force"),
_("Simulate a stick control for _PARAM0_ with a _PARAM2_ angle "
"and a _PARAM3_ force"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
@@ -130,25 +134,28 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsAdvanced()
.SetFunctionName("SimulateStick");
aut.AddScopedCondition("IsUsingControl",
_("Control pressed or simulated"),
_("A control was applied from a default control or simulated by an action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_("Top-down state"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
.AddParameter("stringWithSelector",
aut.AddScopedCondition(
"IsUsingControl",
_("Control pressed or simulated"),
_("A control was applied from a default control or simulated by an "
"action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_("Top-down state"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
.AddParameter("stringWithSelector",
_("Key"),
"[\"Left\", \"Right\", \"Up\", \"Down\", \"Stick\"]")
.MarkAsAdvanced();
.MarkAsAdvanced();
aut.AddExpression("StickAngle",
_("Stick angle"),
_("Return the angle of the simulated stick input (in degrees)"),
_("Top-down controls"),
"CppPlatform/Extensions/topdownmovementicon16.png")
aut.AddExpression(
"StickAngle",
_("Stick angle"),
_("Return the angle of the simulated stick input (in degrees)"),
_("Top-down controls"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior");
@@ -361,19 +368,19 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsAdvanced()
.SetHidden()
.SetFunctionName("GetAngle");
aut.AddScopedCondition(
"IsMovementAngleAround",
_("Angle of movement"),
_("Compare the angle of the top-down movement of the object."),
_("Angle of movement of _PARAM0_ is _PARAM2_ ± _PARAM3_°"),
aut.AddScopedCondition(
"IsMovementAngleAround",
_("Angle of movement"),
_("Compare the angle of the top-down movement of the object."),
_("Angle of movement of _PARAM0_ is _PARAM2_ ± _PARAM3_°"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
.AddParameter("expression", _("Angle (in degrees)"))
.AddParameter("expression", _("Tolerance (in degrees)"));
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
.AddParameter("expression", _("Angle (in degrees)"))
.AddParameter("expression", _("Tolerance (in degrees)"));
aut.AddCondition("XVelocity",
_("Speed on X axis"),

View File

@@ -507,10 +507,6 @@ namespace gdjs {
cos = 0;
}
const getAcceleratedSpeed = this._useLegacyTurnBack
? TopDownMovementRuntimeBehavior.getLegacyAcceleratedSpeed
: TopDownMovementRuntimeBehavior.getAcceleratedSpeed;
let currentSpeed = Math.hypot(this._xVelocity, this._yVelocity);
const dotProduct = this._xVelocity * cos + this._yVelocity * sin;
if (dotProduct < 0) {
@@ -518,13 +514,14 @@ namespace gdjs {
// Keep the negative velocity projected on the new direction.
currentSpeed = dotProduct;
}
const speed = getAcceleratedSpeed(
const speed = TopDownMovementRuntimeBehavior.getAcceleratedSpeed(
currentSpeed,
targetedSpeed,
this._maxSpeed,
this._acceleration,
this._deceleration,
timeDelta
timeDelta,
this._useLegacyTurnBack
);
this._xVelocity = speed * cos;
this._yVelocity = speed * sin;
@@ -599,10 +596,13 @@ namespace gdjs {
speedMax: float,
acceleration: float,
deceleration: float,
timeDelta: float
timeDelta: float,
useLegacyTurnBack: boolean = false
): float {
let newSpeed = currentSpeed;
const turningBackAcceleration = Math.max(acceleration, deceleration);
const turningBackAcceleration = useLegacyTurnBack
? acceleration
: Math.max(acceleration, deceleration);
if (targetedSpeed < 0) {
if (currentSpeed <= targetedSpeed) {
// Reduce the speed to match the stick force.
@@ -652,62 +652,6 @@ namespace gdjs {
return newSpeed;
}
private static getLegacyAcceleratedSpeed(
currentSpeed: float,
targetedSpeed: float,
speedMax: float,
acceleration: float,
deceleration: float,
timeDelta: float
): float {
let newSpeed = currentSpeed;
if (targetedSpeed < 0) {
if (currentSpeed <= targetedSpeed) {
// Reduce the speed to match the stick force.
newSpeed = Math.min(
targetedSpeed,
currentSpeed + deceleration * timeDelta
);
} else if (currentSpeed <= 0) {
// Accelerate
newSpeed -= Math.max(-speedMax, acceleration * timeDelta);
} else {
newSpeed = Math.max(
targetedSpeed,
currentSpeed - deceleration * timeDelta
);
}
} else if (targetedSpeed > 0) {
if (currentSpeed >= targetedSpeed) {
// Reduce the speed to match the stick force.
newSpeed = Math.max(
targetedSpeed,
currentSpeed - deceleration * timeDelta
);
} else if (currentSpeed >= 0) {
// Accelerate
newSpeed = Math.min(
speedMax,
currentSpeed + acceleration * timeDelta
);
} else {
newSpeed = Math.min(
targetedSpeed,
currentSpeed + deceleration * timeDelta
);
}
} else {
// Decelerate and stop.
if (currentSpeed < 0) {
newSpeed = Math.min(currentSpeed + deceleration * timeDelta, 0);
}
if (currentSpeed > 0) {
newSpeed = Math.max(currentSpeed - deceleration * timeDelta, 0);
}
}
return newSpeed;
}
simulateControl(input: string) {
if (input === 'Left') {
this._leftKey = true;

View File

@@ -347,17 +347,21 @@ describe('gdjs.TweenRuntimeBehavior', () => {
// Check that there is no NaN.
for (let i = 0; i < 11; i++) {
runtimeScene.renderAndStep(1000 / 60);
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(0);
// The tween tries to set the camera zoom to 0, but it has no effect
// because it doesn't make sense.
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(1);
}
});
it('can tween a layer camera zoom from 0', () => {
// The zoom stays at 1 because 0 doesn't make sense.
camera.setCameraZoom(runtimeScene, 0, '', 0);
// Here, it actually tweens from 1 to 1.
tween.tweenCameraZoom2(runtimeScene, 'MyTween', 1, '', 'linear', 0.25);
// A camera zoom of 0 doesn't make sense.
// Check that there is no NaN.
for (let i = 0; i < 11; i++) {
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(0);
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(1);
runtimeScene.renderAndStep(1000 / 60);
}
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(1);

View File

@@ -145,6 +145,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
shadowDistance: 4,
shadowAngle: 90,
shadowBlurRadius: 2,
lineHeight: 0,
},
});
runtimeScene.addObject(object);

View File

@@ -145,6 +145,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
shadowDistance: 4,
shadowAngle: 90,
shadowBlurRadius: 2,
lineHeight: 0,
},
});
runtimeScene.addObject(object);

View File

@@ -1326,11 +1326,11 @@ namespace gdjs {
lightness
);
owner.setColor(
Math.floor(rgbFromHslColor[0]) +
Math.round(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
Math.round(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
Math.round(rgbFromHslColor[2])
);
};
} else {
@@ -1439,12 +1439,11 @@ namespace gdjs {
if (!isColorable(this.owner)) return;
const owner = this.owner;
const rgbFromColor: string[] = owner.getColor().split(';');
if (rgbFromColor.length < 3) return;
const rgbFromColor = gdjs.rgbOrHexToRGBColor(owner.getColor());
const hslFromColor = gdjs.evtTools.tween.rgbToHsl(
parseFloat(rgbFromColor[0]),
parseFloat(rgbFromColor[1]),
parseFloat(rgbFromColor[2])
rgbFromColor[0],
rgbFromColor[1],
rgbFromColor[2]
);
const toH = animateHue ? toHue : hslFromColor[0];
@@ -1474,11 +1473,11 @@ namespace gdjs {
);
owner.setColor(
Math.floor(rgbFromHslColor[0]) +
Math.round(rgbFromHslColor[0]) +
';' +
Math.floor(rgbFromHslColor[1]) +
Math.round(rgbFromHslColor[1]) +
';' +
Math.floor(rgbFromHslColor[2])
Math.round(rgbFromHslColor[2])
);
},

View File

@@ -18,8 +18,6 @@ namespace gdjs {
export type VideoNetworkSyncDataType = {
op: float;
wid: float;
hei: float;
// We don't sync volume, as it's probably a user setting?
pla: boolean;
loop: boolean;
@@ -105,8 +103,6 @@ namespace gdjs {
return {
...super.getNetworkSyncData(),
op: this._opacity,
wid: this.getWidth(),
hei: this.getHeight(),
pla: this.isPlayed(),
loop: this.isLooped(),
ct: this.getCurrentTime(),
@@ -120,12 +116,6 @@ namespace gdjs {
if (this._opacity !== undefined && this._opacity && syncData.op) {
this.setOpacity(syncData.op);
}
if (this.getWidth() !== undefined && this.getWidth() !== syncData.wid) {
this.setWidth(syncData.wid);
}
if (this.getHeight() !== undefined && this.getHeight() !== syncData.hei) {
this.setHeight(syncData.hei);
}
if (syncData.pla !== undefined && this.isPlayed() !== syncData.pla) {
syncData.pla ? this.play() : this.pause();
}

View File

@@ -145,16 +145,19 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
// Generate the code setting up the context of the function.
gd::String fullPreludeCode = "let scopeInstanceContainer = null;\n" +
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
"runtimeScene.getOnceTriggers()");
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsFunctionsExtension.GetEventsFunctions()),
0, true),
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
"runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(), "",
fullPreludeCode, eventsFunction.GetEvents(), "",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -209,6 +212,7 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._runtimeScene;\n" +
"let scopeInstanceContainer = null;\n" +
// By convention of Behavior Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
@@ -294,6 +298,7 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._instanceContainer;\n" +
"let scopeInstanceContainer = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
@@ -624,7 +629,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// the cost of creating/storing it for each events function might not
// be worth it.
" if (objectsList) {\n" +
" const object = parentEventsFunctionContext ?\n" +
" const object = parentEventsFunctionContext && "
// Check if `objectName` is not a child object and is passed by
// parameter from a parent instance container.
"!(scopeInstanceContainer && "
"scopeInstanceContainer.isObjectRegistered(objectName)) ?\n" +
" "
"parentEventsFunctionContext.createObject(objectsList.firstKey()) "
":\n" +
@@ -634,7 +643,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
" objectsList.get(objectsList.firstKey()).push(object);\n" +
" "
"eventsFunctionContext._objectArraysMap[objectName].push(object);\n" +
" }\n" + " return object;" + " }\n" +
" }\n" + " return object;\n" + " }\n" +
// Unknown object, don't create anything:
" return null;\n" +
" },\n"
@@ -646,7 +655,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
"eventsFunctionContext._objectsMap[objectName];\n" +
" let count = 0;\n" + " if (objectsList) {\n" +
" for(const objectName in objectsList.items)\n" +
" count += parentEventsFunctionContext ?\n" +
" count += parentEventsFunctionContext && "
// Check if `objectName` is not a child object and is passed by
// parameter from a parent instance container.
"!(scopeInstanceContainer && "
"scopeInstanceContainer.isObjectRegistered(objectName)) ?\n" +
"parentEventsFunctionContext.getInstancesCountOnScene(objectName) "
":\n" +
" runtimeScene.getInstancesCountOnScene(objectName);\n" +
@@ -1228,14 +1241,11 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
// Code only parameter type
else if (metadata.GetType() == "objectsContext") {
argOutput =
"(typeof eventsFunctionContext !== 'undefined' ? eventsFunctionContext "
": runtimeScene)";
HasProjectAndLayout() ? "runtimeScene" : "eventsFunctionContext";
}
// Code only parameter type
else if (metadata.GetType() == "eventsFunctionContext") {
argOutput =
"(typeof eventsFunctionContext !== 'undefined' ? eventsFunctionContext "
": undefined)";
argOutput = HasProjectAndLayout() ? "null" : "eventsFunctionContext";
} else
return gd::EventsCodeGenerator::GenerateParameterCodes(
parameter,

View File

@@ -21,6 +21,9 @@ AdvancedExtension::AdvancedExtension() {
.SetCustomCodeGenerator([](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
gd::String expressionCode =
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
@@ -28,15 +31,16 @@ AdvancedExtension::AdvancedExtension() {
"number",
instruction.GetParameter(0).GetPlainString());
return "if (typeof eventsFunctionContext !== 'undefined') { "
"eventsFunctionContext.returnValue = " +
expressionCode + "; }";
return "eventsFunctionContext.returnValue = " + expressionCode + ";";
});
GetAllActions()["SetReturnString"]
.SetCustomCodeGenerator([](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
gd::String expressionCode =
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
@@ -44,29 +48,31 @@ AdvancedExtension::AdvancedExtension() {
"string",
instruction.GetParameter(0).GetPlainString());
return "if (typeof eventsFunctionContext !== 'undefined') { "
"eventsFunctionContext.returnValue = " +
expressionCode + "; }";
return "eventsFunctionContext.returnValue = " + expressionCode + ";";
});
GetAllActions()["SetReturnBoolean"]
.SetCustomCodeGenerator([](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
// This is duplicated from EventsCodeGenerator::GenerateParameterCodes
gd::String parameter = instruction.GetParameter(0).GetPlainString();
gd::String booleanCode =
(parameter == "True" || parameter == "Vrai") ? "true" : "false";
return "if (typeof eventsFunctionContext !== 'undefined') { "
"eventsFunctionContext.returnValue = " +
booleanCode + "; }";
return "eventsFunctionContext.returnValue = " + booleanCode + ";";
});
GetAllActions()["CopyArgumentToVariable"]
.SetCustomCodeGenerator([](gd::Instruction &instruction,
gd::EventsCodeGenerator &codeGenerator,
gd::EventsCodeGenerationContext &context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
// This is duplicated from EventsCodeGenerator::GenerateParameterCodes
gd::String parameter = instruction.GetParameter(0).GetPlainString();
gd::String variable =
@@ -74,17 +80,17 @@ AdvancedExtension::AdvancedExtension() {
codeGenerator, context, "scenevar", instruction.GetParameter(1),
"");
return "if (typeof eventsFunctionContext !== 'undefined') {\n"
"gdjs.Variable.copy(eventsFunctionContext.getArgument(" +
parameter + "), " + variable +
", false);\n"
"}\n";
return "gdjs.Variable.copy(eventsFunctionContext.getArgument(" +
parameter + "), " + variable + ", false);\n";
});
GetAllActions()["CopyArgumentToVariable2"]
.SetCustomCodeGenerator([](gd::Instruction &instruction,
gd::EventsCodeGenerator &codeGenerator,
gd::EventsCodeGenerationContext &context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
// This is duplicated from EventsCodeGenerator::GenerateParameterCodes
gd::String parameter = instruction.GetParameter(0).GetPlainString();
gd::String variable =
@@ -92,17 +98,17 @@ AdvancedExtension::AdvancedExtension() {
codeGenerator, context, "variable", instruction.GetParameter(1),
"");
return "if (typeof eventsFunctionContext !== 'undefined') {\n"
"gdjs.Variable.copy(eventsFunctionContext.getArgument(" +
parameter + "), " + variable +
", false);\n"
"}\n";
return "gdjs.Variable.copy(eventsFunctionContext.getArgument(" +
parameter + "), " + variable + ", false);\n";
});
GetAllActions()["CopyVariableToArgument"]
.SetCustomCodeGenerator([](gd::Instruction &instruction,
gd::EventsCodeGenerator &codeGenerator,
gd::EventsCodeGenerationContext &context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
// This is duplicated from EventsCodeGenerator::GenerateParameterCodes
gd::String parameter = instruction.GetParameter(0).GetPlainString();
gd::String variable =
@@ -110,17 +116,18 @@ AdvancedExtension::AdvancedExtension() {
codeGenerator, context, "scenevar", instruction.GetParameter(1),
"");
return "if (typeof eventsFunctionContext !== 'undefined') {\n"
"gdjs.Variable.copy(" +
variable + ", eventsFunctionContext.getArgument(" + parameter +
"), false);\n"
"}\n";
return "gdjs.Variable.copy(" + variable +
", eventsFunctionContext.getArgument(" + parameter +
"), false);\n";
});
GetAllActions()["CopyVariableToArgument2"]
.SetCustomCodeGenerator([](gd::Instruction &instruction,
gd::EventsCodeGenerator &codeGenerator,
gd::EventsCodeGenerationContext &context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("");
}
// This is duplicated from EventsCodeGenerator::GenerateParameterCodes
gd::String parameter = instruction.GetParameter(0).GetPlainString();
gd::String variable =
@@ -128,17 +135,18 @@ AdvancedExtension::AdvancedExtension() {
codeGenerator, context, "variable", instruction.GetParameter(1),
"");
return "if (typeof eventsFunctionContext !== 'undefined') {\n"
"gdjs.Variable.copy(" +
variable + ", eventsFunctionContext.getArgument(" + parameter +
"), false);\n"
"}\n";
return "gdjs.Variable.copy(" + variable +
", eventsFunctionContext.getArgument(" + parameter +
"), false);\n";
});
GetAllConditions()["GetArgumentAsBoolean"]
.SetCustomCodeGenerator([](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("false");
}
gd::String parameterNameCode =
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
@@ -146,10 +154,8 @@ AdvancedExtension::AdvancedExtension() {
"string",
instruction.GetParameter(0).GetPlainString());
gd::String valueCode =
gd::String(instruction.IsInverted() ? "!" : "") +
"(typeof eventsFunctionContext !== 'undefined' ? "
"!!eventsFunctionContext.getArgument(" +
parameterNameCode + ") : false)";
gd::String(instruction.IsInverted() ? "!" : "!!") +
"eventsFunctionContext.getArgument(" + parameterNameCode + ")";
gd::String outputCode =
codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", context) +
" = " + valueCode + ";\n";
@@ -161,6 +167,9 @@ AdvancedExtension::AdvancedExtension() {
.SetCustomCodeGenerator([](const std::vector<gd::Expression>& parameters,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("0");
}
gd::String parameterNameCode =
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
@@ -168,9 +177,8 @@ AdvancedExtension::AdvancedExtension() {
"string",
!parameters.empty() ? parameters[0].GetPlainString() : "");
return "(typeof eventsFunctionContext !== 'undefined' ? "
"Number(eventsFunctionContext.getArgument(" +
parameterNameCode + ")) || 0 : 0)";
return "(Number(eventsFunctionContext.getArgument(" + parameterNameCode +
")) || 0)";
});
GetAllStrExpressions()["GetArgumentAsString"]
@@ -178,6 +186,9 @@ AdvancedExtension::AdvancedExtension() {
.SetCustomCodeGenerator([](const std::vector<gd::Expression>& parameters,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
if (codeGenerator.HasProjectAndLayout()) {
return gd::String("\"\"");
}
gd::String parameterNameCode =
gd::ExpressionCodeGenerator::GenerateExpressionCode(
codeGenerator,
@@ -185,9 +196,8 @@ AdvancedExtension::AdvancedExtension() {
"string",
!parameters.empty() ? parameters[0].GetPlainString() : "");
return "(typeof eventsFunctionContext !== 'undefined' ? \"\" + "
"eventsFunctionContext.getArgument(" +
parameterNameCode + ") : \"\")";
return "\"\" + eventsFunctionContext.getArgument(" + parameterNameCode +
")";
});
GetAllConditions()["CompareArgumentAsNumber"]
@@ -210,12 +220,13 @@ AdvancedExtension::AdvancedExtension() {
codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", context);
return resultingBoolean + " = " +
gd::String(instruction.IsInverted() ? "!" : "") +
gd::String(instruction.IsInverted() ? "!" : "") + "(" +
codeGenerator.GenerateRelationalOperation(
operatorString,
"((typeof eventsFunctionContext !== 'undefined' ? "
"Number(eventsFunctionContext.getArgument(" +
parameterNameCode + ")) || 0 : 0)",
codeGenerator.HasProjectAndLayout()
? "0"
: "(Number(eventsFunctionContext.getArgument(" +
parameterNameCode + ")) || 0)",
operandCode) +
");\n";
});
@@ -240,12 +251,13 @@ AdvancedExtension::AdvancedExtension() {
codeGenerator.GenerateUpperScopeBooleanFullName("isConditionTrue", context);
return resultingBoolean + " = " +
gd::String(instruction.IsInverted() ? "!" : "") +
gd::String(instruction.IsInverted() ? "!" : "") + "(" +
codeGenerator.GenerateRelationalOperation(
operatorString,
"((typeof eventsFunctionContext !== 'undefined' ? "
"\"\" + eventsFunctionContext.getArgument(" +
parameterNameCode + ") : \"\")",
codeGenerator.HasProjectAndLayout()
? "\"\""
: "(\"\" + eventsFunctionContext.getArgument(" +
parameterNameCode + "))",
operandCode) +
");\n";
});

View File

@@ -821,8 +821,7 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
}
if (!codeGenerator.HasProjectAndLayout()) {
functionParameters += ", eventsFunctionContext";
callArguments += ", typeof eventsFunctionContext !== \'undefined\' ? "
"eventsFunctionContext : undefined";
callArguments += ", eventsFunctionContext";
}
// Generate the function code
@@ -842,13 +841,23 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
event.GetParameterObjects(),
parentContext.GetCurrentObject());
callingCode += "var objects = [];\n";
for (unsigned int i = 0; i < realObjects.size(); ++i) {
parentContext.ObjectsListNeeded(realObjects[i]);
if (realObjects.size() == 1) {
parentContext.ObjectsListNeeded(realObjects[0]);
callingCode +=
"objects.push.apply(objects," +
codeGenerator.GetObjectListName(realObjects[i], parentContext) +
");\n";
"const objects = " +
codeGenerator.GetObjectListName(realObjects[0], parentContext) +
";\n";
} else {
// Groups are rarely used in JS events so it's fine to make
// allocations.
callingCode += "const objects = [];\n";
for (unsigned int i = 0; i < realObjects.size(); ++i) {
parentContext.ObjectsListNeeded(realObjects[i]);
callingCode += "objects.push.apply(objects," +
codeGenerator.GetObjectListName(realObjects[i],
parentContext) +
");\n";
}
}
}

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