mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
239 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d73ae4c56e | ||
![]() |
339929e021 | ||
![]() |
cea1cf20f1 | ||
![]() |
4ec2705f75 | ||
![]() |
408f6f8134 | ||
![]() |
c64bac0010 | ||
![]() |
f8d5c89ebf | ||
![]() |
ca3bb40e96 | ||
![]() |
713d437b70 | ||
![]() |
09381c3836 | ||
![]() |
ea4c2e0827 | ||
![]() |
a5d1149c21 | ||
![]() |
119b0fadce | ||
![]() |
7111859768 | ||
![]() |
08dfb4d36b | ||
![]() |
cacd482af9 | ||
![]() |
3568a58019 | ||
![]() |
a32d5fcd7e | ||
![]() |
1a999fd2dd | ||
![]() |
76648764bb | ||
![]() |
c25803c122 | ||
![]() |
ba8d7f4e38 | ||
![]() |
e1c22f6994 | ||
![]() |
7b70f9172f | ||
![]() |
a871e0f2ec | ||
![]() |
f6499e1163 | ||
![]() |
d03e58636d | ||
![]() |
a7be928f2f | ||
![]() |
7c83610d28 | ||
![]() |
1a1e92b072 | ||
![]() |
5118f13e0b | ||
![]() |
f3ee18cdc7 | ||
![]() |
c5584f746e | ||
![]() |
706a6de94c | ||
![]() |
8c750b54dd | ||
![]() |
649d744664 | ||
![]() |
12a6fec18e | ||
![]() |
7fc4aa47f6 | ||
![]() |
483f78fa75 | ||
![]() |
35b80818dc | ||
![]() |
b3f19726dc | ||
![]() |
dd332e3cce | ||
![]() |
dc95a7511f | ||
![]() |
77130c7d2e | ||
![]() |
8d1c3e8290 | ||
![]() |
63570fd3c8 | ||
![]() |
6945409280 | ||
![]() |
e345b2726f | ||
![]() |
3a2a662f62 | ||
![]() |
73d850f933 | ||
![]() |
1f2293e7d6 | ||
![]() |
b038984097 | ||
![]() |
a45a5bfe7e | ||
![]() |
8cbefa0cde | ||
![]() |
1d607d474a | ||
![]() |
3cd6e36a73 | ||
![]() |
cfbec2df4a | ||
![]() |
2823fde86a | ||
![]() |
f29ae50f2e | ||
![]() |
ba40c67941 | ||
![]() |
c8ca3c2931 | ||
![]() |
0bb0969ab8 | ||
![]() |
3b0a7c442f | ||
![]() |
df3c95c466 | ||
![]() |
482d52ff5a | ||
![]() |
5a78c763ef | ||
![]() |
3393af4b3b | ||
![]() |
c2b77c9df7 | ||
![]() |
514e4692ab | ||
![]() |
210e59f201 | ||
![]() |
e2058052f2 | ||
![]() |
90dca41e20 | ||
![]() |
15b463dad1 | ||
![]() |
a97a1f8a86 | ||
![]() |
901f84daaa | ||
![]() |
6af50ae5c0 | ||
![]() |
f7bd4bee6e | ||
![]() |
4a4b015242 | ||
![]() |
75ee0b68e3 | ||
![]() |
778104447c | ||
![]() |
a4ddc0a6ef | ||
![]() |
3e713029e2 | ||
![]() |
7eb5402ee2 | ||
![]() |
25adb026b8 | ||
![]() |
54bd2960ac | ||
![]() |
1fde65b5e3 | ||
![]() |
b4295b4077 | ||
![]() |
c7a929374d | ||
![]() |
7571e64396 | ||
![]() |
198267d7cb | ||
![]() |
0982424d0d | ||
![]() |
29747690c2 | ||
![]() |
ad74532752 | ||
![]() |
80e5376f74 | ||
![]() |
cc24eab2aa | ||
![]() |
423c8165ad | ||
![]() |
0af00818dc | ||
![]() |
9f5c783d73 | ||
![]() |
ad65971c01 | ||
![]() |
85ef9a9561 | ||
![]() |
455d77fcdf | ||
![]() |
02093fec0f | ||
![]() |
8227ab9cad | ||
![]() |
f8eb91f3d2 | ||
![]() |
a260aa5e3e | ||
![]() |
4d8d93a550 | ||
![]() |
cf160bcca1 | ||
![]() |
44a0e22f97 | ||
![]() |
d47d3285b2 | ||
![]() |
2ee6590967 | ||
![]() |
cd77951e1a | ||
![]() |
fa4efef857 | ||
![]() |
bc8204d696 | ||
![]() |
b2eab5a327 | ||
![]() |
dba65822dd | ||
![]() |
8f155e4322 | ||
![]() |
13ebe5c588 | ||
![]() |
bcd8e5608a | ||
![]() |
14e444413d | ||
![]() |
a2f75bc0dc | ||
![]() |
6914e01a15 | ||
![]() |
90b5f7a322 | ||
![]() |
13cf9b1d0b | ||
![]() |
6fc0198298 | ||
![]() |
0999ba611d | ||
![]() |
119d1af76a | ||
![]() |
d6b4dacb1e | ||
![]() |
9edb3cfe91 | ||
![]() |
d44a1c3537 | ||
![]() |
8cc84e5728 | ||
![]() |
76130b43d4 | ||
![]() |
40b0823b91 | ||
![]() |
9b447e08e2 | ||
![]() |
10314a1911 | ||
![]() |
d0195719c2 | ||
![]() |
dd8c040381 | ||
![]() |
5e0c8a92aa | ||
![]() |
977c102ddc | ||
![]() |
849d79d5d7 | ||
![]() |
28f7c9ae0b | ||
![]() |
38e58327fa | ||
![]() |
3cc29efaa2 | ||
![]() |
65eb76fb57 | ||
![]() |
9eb721662f | ||
![]() |
b10b131010 | ||
![]() |
b5fd1bb351 | ||
![]() |
f1c9521625 | ||
![]() |
6ceb3c2c10 | ||
![]() |
43827876cd | ||
![]() |
62b746300a | ||
![]() |
769ebcd91c | ||
![]() |
70d5de16bf | ||
![]() |
7fbe1bd23d | ||
![]() |
de8a679e31 | ||
![]() |
a1a4029b35 | ||
![]() |
a377467031 | ||
![]() |
dd090fd1d7 | ||
![]() |
26ee9b3891 | ||
![]() |
ad18eab4ae | ||
![]() |
007fc36a2e | ||
![]() |
c8ebfde85b | ||
![]() |
d0005ba2cb | ||
![]() |
f623b352ee | ||
![]() |
16e2d8a005 | ||
![]() |
7654883cb1 | ||
![]() |
3ae5db2a49 | ||
![]() |
9ed002c879 | ||
![]() |
d4283c2350 | ||
![]() |
5a176d21e7 | ||
![]() |
bfdfd7f0fb | ||
![]() |
aae75f2232 | ||
![]() |
977092c0a3 | ||
![]() |
556688cedb | ||
![]() |
ce93dc5310 | ||
![]() |
d6d425db4f | ||
![]() |
2737e75639 | ||
![]() |
36eab18133 | ||
![]() |
48acbb12ee | ||
![]() |
21904e46f1 | ||
![]() |
94753ac053 | ||
![]() |
b160ee9b27 | ||
![]() |
f76e8a72b6 | ||
![]() |
5bc342688d | ||
![]() |
3e6204c0eb | ||
![]() |
7b1c340ad0 | ||
![]() |
fac724dc3f | ||
![]() |
9b4151f64c | ||
![]() |
6b5ab6c811 | ||
![]() |
5943092b0c | ||
![]() |
deb0c5ffc3 | ||
![]() |
c56fa03bf6 | ||
![]() |
0d49d449db | ||
![]() |
fdd702cd09 | ||
![]() |
5a8e4a7ca9 | ||
![]() |
2e4e91c21e | ||
![]() |
6ece930809 | ||
![]() |
c5baa81977 | ||
![]() |
d24be38874 | ||
![]() |
1efffbbb78 | ||
![]() |
090d76a368 | ||
![]() |
d44a9375de | ||
![]() |
5a2a3893f9 | ||
![]() |
0a6b0dc785 | ||
![]() |
02e99726dc | ||
![]() |
843055d8df | ||
![]() |
f7fda5cb5e | ||
![]() |
e529642aec | ||
![]() |
c8e10d7043 | ||
![]() |
5f0de0e9a7 | ||
![]() |
e51638ce4b | ||
![]() |
0fbd6a606a | ||
![]() |
2fa543c3db | ||
![]() |
38e35c9695 | ||
![]() |
ad13a1a101 | ||
![]() |
cb9d98d027 | ||
![]() |
064c3f1572 | ||
![]() |
9e5320f9d4 | ||
![]() |
a1826d355d | ||
![]() |
32a3a094d1 | ||
![]() |
ee7dc2654b | ||
![]() |
64ffad3c0a | ||
![]() |
dcc62f078f | ||
![]() |
d920f05dbc | ||
![]() |
ac82be800b | ||
![]() |
2c92ae4042 | ||
![]() |
465e934605 | ||
![]() |
248ba7675e | ||
![]() |
fd2b59ba45 | ||
![]() |
be54236ece | ||
![]() |
6edf63e98f | ||
![]() |
034f1ad9cc | ||
![]() |
94b8c31ac2 | ||
![]() |
57d1241e2d | ||
![]() |
eb4708ca87 | ||
![]() |
fac710780b | ||
![]() |
d28aac325a | ||
![]() |
306b341ee5 | ||
![]() |
1d8e04cb78 | ||
![]() |
3b2855de59 |
4
.github/workflows/update-translations.yml
vendored
4
.github/workflows/update-translations.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: "newIDE/app/package-lock.json"
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
working-directory: newIDE/app
|
||||
|
||||
- name: Create a Pull Request with the changes
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update translations [skip ci]
|
||||
branch: chore/update-translations
|
||||
|
@@ -714,6 +714,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
|
||||
metadata.GetType() == "tilesetResource" ||
|
||||
metadata.GetType() == "videoResource" ||
|
||||
metadata.GetType() == "model3DResource" ||
|
||||
metadata.GetType() == "atlasResource" ||
|
||||
metadata.GetType() == "spineResource" ||
|
||||
// Deprecated, old parameter names:
|
||||
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
|
||||
metadata.GetType() == "soundfile" || metadata.GetType() == "police") {
|
||||
|
@@ -1281,8 +1281,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Enable an effect on the object"),
|
||||
_("Enable effect _PARAM1_ on _PARAM0_: _PARAM2_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("yesorno", _("Enable?"))
|
||||
@@ -1297,8 +1297,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("objectEffectParameterName", _("Property name"))
|
||||
@@ -1315,8 +1315,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("objectEffectParameterName", _("Property name"))
|
||||
@@ -1332,8 +1332,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM2_ for effect _PARAM1_ of _PARAM0_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("objectEffectParameterName", _("Property name"))
|
||||
@@ -1347,8 +1347,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Check if the effect on an object is enabled."),
|
||||
_("Effect _PARAM1_ of _PARAM0_ is enabled"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.MarkAsSimple()
|
||||
|
@@ -27,7 +27,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Layers and cameras"))
|
||||
.SetIcon("res/conditions/camera24.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
|
||||
.SetIcon("res/actions/effect24.png");
|
||||
.SetIcon("res/actions/effect_black.svg");
|
||||
|
||||
extension
|
||||
.AddExpressionAndConditionAndAction(
|
||||
@@ -450,8 +450,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -469,8 +469,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -488,8 +488,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM3_ for effect _PARAM2_ of layer _PARAM1_: _PARAM4_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -504,8 +504,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
_("The effect on a layer is enabled"),
|
||||
_("Effect _PARAM2_ on layer _PARAM1_ is enabled"),
|
||||
_(""),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -518,8 +518,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
_("Enable an effect on a layer"),
|
||||
_("Enable effect _PARAM2_ on layer _PARAM1_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
|
@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
|
||||
.SetIcon("res/actions/effect24.png");
|
||||
.SetIcon("res/actions/effect_black.svg");
|
||||
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
"EffectBehavior",
|
||||
@@ -32,7 +32,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"Effect",
|
||||
_("Apply visual effects to objects."),
|
||||
"",
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect_black.svg",
|
||||
"EffectBehavior",
|
||||
std::make_shared<gd::Behavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
@@ -43,8 +43,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
_("Enable an effect on the object"),
|
||||
_("Enable effect _PARAM2_ on _PARAM0_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -58,8 +58,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -75,8 +75,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -91,8 +91,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM3_ for effect _PARAM2_ of _PARAM0_: _PARAM4_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -105,8 +105,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
_("Check if the effect on an object is enabled."),
|
||||
_("Effect _PARAM2_ of _PARAM0_ is enabled"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
|
@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
|
||||
.SetIcon("res/actions/effect24.png");
|
||||
.SetIcon("res/actions/effect_black.svg");
|
||||
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
"FlippableBehavior",
|
||||
|
@@ -114,6 +114,8 @@ public:
|
||||
|
||||
/**
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
virtual AbstractFunctionMetadata &
|
||||
SetIncludeFile(const gd::String &includeFile) = 0;
|
||||
|
@@ -13,12 +13,15 @@
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/BehaviorsSharedData.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Tools/MakeUnique.h"
|
||||
#include "GDCore/Tools/Log.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
const std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::badProperties;
|
||||
|
||||
BehaviorMetadata::BehaviorMetadata(
|
||||
const gd::String& extensionNamespace_,
|
||||
const gd::String& nameWithNamespace,
|
||||
@@ -47,8 +50,14 @@ BehaviorMetadata::BehaviorMetadata(
|
||||
"BehaviorMetadata is valid for: " + nameWithNamespace);
|
||||
}
|
||||
|
||||
if (instance) instance->SetTypeName(nameWithNamespace);
|
||||
if (sharedDatasInstance) sharedDatasInstance->SetTypeName(nameWithNamespace);
|
||||
if (instance) {
|
||||
instance->SetTypeName(nameWithNamespace);
|
||||
instance->InitializeContent();
|
||||
}
|
||||
if (sharedDatasInstance) {
|
||||
sharedDatasInstance->SetTypeName(nameWithNamespace);
|
||||
sharedDatasInstance->InitializeContent();
|
||||
}
|
||||
}
|
||||
|
||||
gd::InstructionMetadata& BehaviorMetadata::AddCondition(
|
||||
@@ -405,10 +414,30 @@ gd::Behavior& BehaviorMetadata::Get() const {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetProperties() const {
|
||||
if (!instance) {
|
||||
return badProperties;
|
||||
}
|
||||
// TODO Properties should be declared on BehaviorMetadata directly.
|
||||
// - Add 2 `properties` members (one for shared properties)
|
||||
// - Add methods to declare new properties
|
||||
return instance->GetProperties();
|
||||
}
|
||||
|
||||
gd::BehaviorsSharedData* BehaviorMetadata::GetSharedDataInstance() const {
|
||||
return sharedDatasInstance.get();
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetSharedProperties() const {
|
||||
if (!sharedDatasInstance) {
|
||||
return badProperties;
|
||||
}
|
||||
// TODO Properties should be declared on BehaviorMetadata directly.
|
||||
// - Add 2 `properties` members (one for shared properties)
|
||||
// - Add methods to declare new properties
|
||||
return sharedDatasInstance->GetProperties();
|
||||
}
|
||||
|
||||
const std::vector<gd::String>& BehaviorMetadata::GetRequiredBehaviorTypes() const {
|
||||
requiredBehaviors.clear();
|
||||
for (auto& property : Get().GetProperties()) {
|
||||
|
@@ -18,6 +18,7 @@ class BehaviorsSharedData;
|
||||
class MultipleInstructionMetadata;
|
||||
class InstructionMetadata;
|
||||
class ExpressionMetadata;
|
||||
class PropertyDescriptor;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
@@ -204,6 +205,8 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \note The requirement may vary depending on the platform: Most of the time,
|
||||
* the include file contains the declaration of the behavior.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
BehaviorMetadata& SetIncludeFile(const gd::String& includeFile) override;
|
||||
|
||||
@@ -302,6 +305,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
*/
|
||||
gd::Behavior& Get() const;
|
||||
|
||||
/**
|
||||
* \brief Called when the IDE wants to know about the custom properties of the
|
||||
* behavior.
|
||||
*
|
||||
* \return a std::map with properties names as key.
|
||||
* \see gd::PropertyDescriptor
|
||||
*/
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const;
|
||||
|
||||
/**
|
||||
* \brief Return the associated gd::BehaviorsSharedData, handling behavior
|
||||
* shared data, if any (nullptr if none).
|
||||
@@ -311,6 +323,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
*/
|
||||
gd::BehaviorsSharedData* GetSharedDataInstance() const;
|
||||
|
||||
/**
|
||||
* \brief Called when the IDE wants to know about the custom shared properties
|
||||
* of the behavior.
|
||||
*
|
||||
* \return a std::map with properties names as key.
|
||||
* \see gd::PropertyDescriptor
|
||||
*/
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetSharedProperties() const;
|
||||
|
||||
/**
|
||||
* \brief Return a reference to a map containing the names of the actions
|
||||
* (as keys) and the metadata associated with (as values).
|
||||
@@ -357,6 +378,8 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
|
||||
std::shared_ptr<gd::Behavior> instance;
|
||||
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance;
|
||||
|
||||
static const std::map<gd::String, gd::PropertyDescriptor> badProperties;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -65,6 +65,8 @@ class GD_CORE_API EffectMetadata {
|
||||
|
||||
/**
|
||||
* \brief Clear any existing include file and add the specified include file.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
EffectMetadata& SetIncludeFile(const gd::String& includeFile);
|
||||
|
||||
|
@@ -50,11 +50,11 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
|
||||
gd::ParameterMetadata::IsBehavior(type))
|
||||
// Prefix with the namespace if it's not already there.
|
||||
&& (supplementaryInformation.find(
|
||||
PlatformExtension::GetNamespaceSeparator()) != gd::String::npos)
|
||||
? supplementaryInformation
|
||||
: (supplementaryInformation.empty()
|
||||
PlatformExtension::GetNamespaceSeparator()) == gd::String::npos)
|
||||
? (supplementaryInformation.empty()
|
||||
? ""
|
||||
: extensionNamespace + supplementaryInformation)));
|
||||
: extensionNamespace + supplementaryInformation)
|
||||
: supplementaryInformation));
|
||||
|
||||
// TODO: Assert against supplementaryInformation === "emsc" (when running with
|
||||
// Emscripten), and warn about a missing argument when calling addParameter.
|
||||
|
@@ -288,6 +288,8 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
|
||||
|
||||
/**
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
ExpressionMetadata& SetIncludeFile(
|
||||
const gd::String& includeFile) override {
|
||||
|
@@ -68,11 +68,11 @@ InstructionMetadata& InstructionMetadata::AddParameter(
|
||||
gd::ParameterMetadata::IsBehavior(type))
|
||||
// Prefix with the namespace if it's not already there.
|
||||
&& (supplementaryInformation.find(
|
||||
PlatformExtension::GetNamespaceSeparator()) != gd::String::npos)
|
||||
? supplementaryInformation
|
||||
: (supplementaryInformation.empty()
|
||||
PlatformExtension::GetNamespaceSeparator()) == gd::String::npos)
|
||||
? (supplementaryInformation.empty()
|
||||
? ""
|
||||
: extensionNamespace + supplementaryInformation)));
|
||||
: extensionNamespace + supplementaryInformation)
|
||||
: supplementaryInformation));
|
||||
|
||||
// TODO: Assert against supplementaryInformation === "emsc" (when running with
|
||||
// Emscripten), and warn about a missing argument when calling addParameter.
|
||||
|
@@ -494,6 +494,8 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
|
||||
|
||||
/**
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
InstructionMetadata &SetIncludeFile(const gd::String &includeFile) override {
|
||||
codeExtraInformation.includeFiles.clear();
|
||||
|
@@ -142,6 +142,8 @@ public:
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \note The requirement may vary depending on the platform: Most of the time,
|
||||
* the include file contains the declaration of the behavior.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
virtual InstructionOrExpressionContainerMetadata &
|
||||
SetIncludeFile(const gd::String &includeFile) = 0;
|
||||
|
@@ -150,6 +150,10 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
MultipleInstructionMetadata &SetIncludeFile(const gd::String &includeFile) override {
|
||||
if (expression)
|
||||
expression->SetIncludeFile(includeFile);
|
||||
|
@@ -264,6 +264,8 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \note The requirement may vary depending on the platform: Most of the time,
|
||||
* the include file contains the declaration of the object.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
ObjectMetadata& SetIncludeFile(const gd::String& includeFile) override;
|
||||
|
||||
|
@@ -217,7 +217,9 @@ class GD_CORE_API ValueTypeMetadata {
|
||||
parameterType == "jsonResource" ||
|
||||
parameterType == "tilemapResource" ||
|
||||
parameterType == "tilesetResource" ||
|
||||
parameterType == "model3DResource";
|
||||
parameterType == "model3DResource" ||
|
||||
parameterType == "atlasResource" ||
|
||||
parameterType == "spineResource";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -285,22 +285,6 @@ class GD_CORE_API PlatformExtension {
|
||||
std::shared_ptr<gd::Behavior> instance,
|
||||
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance);
|
||||
|
||||
/**
|
||||
* \brief Declare a new events based behavior as being part of the extension.
|
||||
*
|
||||
* \param name The name of the behavior
|
||||
* \param fullname The user friendly name of the behavior
|
||||
* \param description The user friendly description of the behavior
|
||||
* \param group The behavior category label
|
||||
* \param icon The icon of the behavior.
|
||||
*/
|
||||
gd::BehaviorMetadata& AddEventsBasedBehavior(
|
||||
const gd::String& name_,
|
||||
const gd::String& fullname_,
|
||||
const gd::String& description_,
|
||||
const gd::String& group_,
|
||||
const gd::String& icon_);
|
||||
|
||||
/**
|
||||
* \brief Declare a new effect as being part of the extension.
|
||||
* \param name The internal name of the effect (also called effect type).
|
||||
|
@@ -39,7 +39,8 @@ struct VariableAndItsParent {
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Find the last parent (i.e: the variables container) of a node representing a variable.
|
||||
* \brief Find the last parent (i.e: the variables container) of a node
|
||||
* representing a variable.
|
||||
*
|
||||
* Useful for completions, to know which variables can be entered in a node.
|
||||
*
|
||||
@@ -48,13 +49,12 @@ struct VariableAndItsParent {
|
||||
class GD_CORE_API ExpressionVariableParentFinder
|
||||
: public ExpressionParser2NodeWorker {
|
||||
public:
|
||||
|
||||
static VariableAndItsParent GetLastParentOfNode(
|
||||
const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers,
|
||||
gd::ExpressionNode& node) {
|
||||
gd::ExpressionVariableParentFinder typeFinder(
|
||||
platform, projectScopedContainers);
|
||||
gd::ExpressionVariableParentFinder typeFinder(platform,
|
||||
projectScopedContainers);
|
||||
node.Visit(typeFinder);
|
||||
return typeFinder.variableAndItsParent;
|
||||
}
|
||||
@@ -70,7 +70,8 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
variableNode(nullptr),
|
||||
thisIsALegacyPrescopedVariable(false),
|
||||
bailOutBecauseEmptyVariableName(false),
|
||||
legacyPrescopedVariablesContainer(nullptr){};
|
||||
legacyPrescopedVariablesContainer(nullptr),
|
||||
variableAndItsParent{} {};
|
||||
|
||||
void OnVisitSubExpressionNode(SubExpressionNode& node) override {}
|
||||
void OnVisitOperatorNode(OperatorNode& node) override {}
|
||||
@@ -135,10 +136,10 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
}
|
||||
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
|
||||
if (node.name.empty() && node.child) {
|
||||
// A variable accessor should always have a name if it has a child (i.e: another accessor).
|
||||
// While the parser may have generated an empty name,
|
||||
// flag this so we avoid finding a wrong parent (and so, run the risk of giving
|
||||
// wrong autocompletions).
|
||||
// A variable accessor should always have a name if it has a child (i.e:
|
||||
// another accessor). While the parser may have generated an empty name,
|
||||
// flag this so we avoid finding a wrong parent (and so, run the risk of
|
||||
// giving wrong autocompletions).
|
||||
bailOutBecauseEmptyVariableName = true;
|
||||
}
|
||||
childVariableNames.insert(childVariableNames.begin(), node.name);
|
||||
@@ -300,7 +301,8 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
const std::vector<gd::String>& childVariableNames,
|
||||
size_t startIndex = 0) {
|
||||
if (bailOutBecauseEmptyVariableName)
|
||||
return {}; // Do not even attempt to find the parent if we had an issue when visiting nodes.
|
||||
return {}; // Do not even attempt to find the parent if we had an issue
|
||||
// when visiting nodes.
|
||||
|
||||
const gd::Variable* currentVariable = &variable;
|
||||
|
||||
@@ -332,8 +334,8 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
}
|
||||
}
|
||||
|
||||
// Return the last parent of the chain of variables (so not the last variable
|
||||
// but the one before it).
|
||||
// Return the last parent of the chain of variables (so not the last
|
||||
// variable but the one before it).
|
||||
return {.parentVariable = currentVariable};
|
||||
}
|
||||
|
||||
@@ -341,14 +343,16 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
const gd::VariablesContainer& variablesContainer,
|
||||
const std::vector<gd::String>& childVariableNames) {
|
||||
if (bailOutBecauseEmptyVariableName)
|
||||
return {}; // Do not even attempt to find the parent if we had an issue when visiting nodes.
|
||||
return {}; // Do not even attempt to find the parent if we had an issue
|
||||
// when visiting nodes.
|
||||
if (childVariableNames.empty())
|
||||
return {}; // There is no "parent" to the variables container itself.
|
||||
|
||||
const gd::String& firstChildName = *childVariableNames.begin();
|
||||
|
||||
const gd::Variable* variable = variablesContainer.Has(firstChildName) ?
|
||||
&variablesContainer.Get(firstChildName) : nullptr;
|
||||
const gd::Variable* variable = variablesContainer.Has(firstChildName)
|
||||
? &variablesContainer.Get(firstChildName)
|
||||
: nullptr;
|
||||
if (childVariableNames.size() == 1 || !variable)
|
||||
return {// Only one child: the parent is the variables container itself.
|
||||
.parentVariablesContainer = &variablesContainer};
|
||||
|
111
Core/GDCore/IDE/ObjectAssetSerializer.cpp
Normal file
111
Core/GDCore/IDE/ObjectAssetSerializer.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "ObjectAssetSerializer.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Project/AssetResourcePathCleaner.h"
|
||||
#include "GDCore/IDE/Project/ResourcesInUseHelper.h"
|
||||
#include "GDCore/IDE/Project/ResourcesRenamer.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/CustomBehavior.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Log.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
gd::String
|
||||
ObjectAssetSerializer::GetObjectExtensionName(const gd::Object &object) {
|
||||
const gd::String &type = object.GetType();
|
||||
const auto separatorIndex =
|
||||
type.find(PlatformExtension::GetNamespaceSeparator());
|
||||
return separatorIndex != std::string::npos ? type.substr(0, separatorIndex)
|
||||
: "";
|
||||
}
|
||||
|
||||
void ObjectAssetSerializer::SerializeTo(
|
||||
gd::Project &project, const gd::Object &object,
|
||||
const gd::String &objectFullName, SerializerElement &element,
|
||||
std::vector<gd::String> &usedResourceNames) {
|
||||
auto cleanObject = object.Clone();
|
||||
cleanObject->GetVariables().Clear();
|
||||
cleanObject->GetEffects().Clear();
|
||||
for (auto &&behaviorName : cleanObject->GetAllBehaviorNames()) {
|
||||
cleanObject->RemoveBehavior(behaviorName);
|
||||
}
|
||||
|
||||
gd::String extensionName = GetObjectExtensionName(*cleanObject);
|
||||
|
||||
element.SetAttribute("id", "");
|
||||
element.SetAttribute("name", "");
|
||||
element.SetAttribute("license", "");
|
||||
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
|
||||
auto &extension = project.GetEventsFunctionsExtension(extensionName);
|
||||
element.SetAttribute("description", extension.GetShortDescription());
|
||||
}
|
||||
element.SetAttribute("gdevelopVersion", "");
|
||||
element.SetAttribute("version", "");
|
||||
element.SetIntAttribute("animationsCount", 1);
|
||||
element.SetIntAttribute("maxFramesCount", 1);
|
||||
// TODO Find the right object dimensions.
|
||||
element.SetIntAttribute("width", 0);
|
||||
element.SetIntAttribute("height", 0);
|
||||
SerializerElement &authorsElement = element.AddChild("authors");
|
||||
authorsElement.ConsiderAsArrayOf("author");
|
||||
SerializerElement &tagsElement = element.AddChild("tags");
|
||||
tagsElement.ConsiderAsArrayOf("tag");
|
||||
|
||||
SerializerElement &objectAssetsElement = element.AddChild("objectAssets");
|
||||
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
|
||||
SerializerElement &objectAssetElement =
|
||||
objectAssetsElement.AddChild("objectAsset");
|
||||
|
||||
cleanObject->SerializeTo(objectAssetElement.AddChild("object"));
|
||||
|
||||
SerializerElement &resourcesElement =
|
||||
objectAssetElement.AddChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
auto &resourcesManager = project.GetResourcesManager();
|
||||
gd::ResourcesInUseHelper resourcesInUse(resourcesManager);
|
||||
cleanObject->GetConfiguration().ExposeResources(resourcesInUse);
|
||||
for (auto &&resourceName : resourcesInUse.GetAllResources()) {
|
||||
if (resourceName.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
usedResourceNames.push_back(resourceName);
|
||||
auto &resource = resourcesManager.GetResource(resourceName);
|
||||
SerializerElement &resourceElement = resourcesElement.AddChild("resource");
|
||||
resource.SerializeTo(resourceElement);
|
||||
resourceElement.SetAttribute("kind", resource.GetKind());
|
||||
resourceElement.SetAttribute("name", resource.GetName());
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// TODO This can be removed when the asset script no longer require it.
|
||||
SerializerElement &customizationElement =
|
||||
objectAssetElement.AddChild("customization");
|
||||
customizationElement.ConsiderAsArrayOf("empty");
|
||||
}
|
||||
} // namespace gd
|
57
Core/GDCore/IDE/ObjectAssetSerializer.h
Normal file
57
Core/GDCore/IDE/ObjectAssetSerializer.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class Object;
|
||||
class ExtensionDependency;
|
||||
class PropertyDescriptor;
|
||||
class Project;
|
||||
class Layout;
|
||||
class ArbitraryResourceWorker;
|
||||
class InitialInstance;
|
||||
class SerializerElement;
|
||||
class EffectsContainer;
|
||||
class AbstractFileSystem;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief Serialize objects into an asset for the store.
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API ObjectAssetSerializer {
|
||||
public:
|
||||
/**
|
||||
* \brief Serialize an object into an asset.
|
||||
*
|
||||
* \param project The project that contains the object and its resources.
|
||||
* It's not actually modified.
|
||||
* \param object The object to serialize as an asset.
|
||||
* \param objectFullName The object name with spaces instead of PascalCase.
|
||||
* \param element The element where the asset is serialize.
|
||||
* \param usedResourceNames Return the names of the resources used by the asset.
|
||||
*/
|
||||
static void
|
||||
SerializeTo(gd::Project &project, const gd::Object &object,
|
||||
const gd::String &objectFullName, SerializerElement &element,
|
||||
std::vector<gd::String> &usedResourceNames);
|
||||
|
||||
~ObjectAssetSerializer(){};
|
||||
|
||||
private:
|
||||
ObjectAssetSerializer(){};
|
||||
|
||||
static gd::String GetObjectExtensionName(const gd::Object &object);
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -52,6 +52,16 @@ void ArbitraryResourceWorker::ExposeModel3D(gd::String& resourceName){
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeAtlas(gd::String& resourceName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeSpine(gd::String& resourceName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeVideo(gd::String& videoName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
@@ -120,6 +130,7 @@ void ArbitraryResourceWorker::ExposeEmbeddeds(gd::String& resourceName) {
|
||||
|
||||
gd::String potentiallyUpdatedTargetResourceName = targetResourceName;
|
||||
ExposeResourceWithType(targetResource.GetKind(), potentiallyUpdatedTargetResourceName);
|
||||
ExposeEmbeddeds(potentiallyUpdatedTargetResourceName);
|
||||
|
||||
if (potentiallyUpdatedTargetResourceName != targetResourceName) {
|
||||
// The resource name was renamed. Also update the mapping.
|
||||
@@ -176,6 +187,14 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
|
||||
ExposeVideo(resourceName);
|
||||
return;
|
||||
}
|
||||
if (resourceType == "atlas") {
|
||||
ExposeAtlas(resourceName);
|
||||
return;
|
||||
}
|
||||
if (resourceType == "spine") {
|
||||
ExposeSpine(resourceName);
|
||||
return;
|
||||
}
|
||||
gd::LogError("Unexpected resource type: " + resourceType + " for: " + resourceName);
|
||||
return;
|
||||
}
|
||||
@@ -244,6 +263,14 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
|
||||
gd::String updatedParameterValue = parameterValue;
|
||||
worker.ExposeModel3D(updatedParameterValue);
|
||||
instruction.SetParameter(parameterIndex, updatedParameterValue);
|
||||
} else if (parameterMetadata.GetType() == "atlasResource") {
|
||||
gd::String updatedParameterValue = parameterValue;
|
||||
worker.ExposeAtlas(updatedParameterValue);
|
||||
instruction.SetParameter(parameterIndex, updatedParameterValue);
|
||||
} else if (parameterMetadata.GetType() == "spineResource") {
|
||||
gd::String updatedParameterValue = parameterValue;
|
||||
worker.ExposeSpine(updatedParameterValue);
|
||||
instruction.SetParameter(parameterIndex, updatedParameterValue);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -96,6 +96,16 @@ public:
|
||||
* \brief Expose a 3D model, which is always a reference to a "model3D" resource.
|
||||
*/
|
||||
virtual void ExposeModel3D(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* \brief Expose an atlas, which is always a reference to a "atlas" resource.
|
||||
*/
|
||||
virtual void ExposeAtlas(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* \brief Expose an spine, which is always a reference to a "spine" resource.
|
||||
*/
|
||||
virtual void ExposeSpine(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* \brief Expose a video, which is always a reference to a "video" resource.
|
||||
@@ -123,14 +133,15 @@ public:
|
||||
*/
|
||||
virtual void ExposeEmbeddeds(gd::String &resourceName);
|
||||
|
||||
protected:
|
||||
gd::ResourcesManager * resourcesManager;
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Expose a resource: resources that have a file are
|
||||
* exposed as file (see ExposeFile).
|
||||
*/
|
||||
void ExposeResource(gd::Resource &resource);
|
||||
|
||||
gd::ResourcesManager * resourcesManager;
|
||||
};
|
||||
|
||||
/**
|
||||
|
74
Core/GDCore/IDE/Project/AssetResourcePathCleaner.cpp
Normal file
74
Core/GDCore/IDE/Project/AssetResourcePathCleaner.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "AssetResourcePathCleaner.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/ResourcesManager.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
void AssetResourcePathCleaner::ExposeImage(gd::String &imageName) {
|
||||
ExposeResourceAsFile(imageName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeAudio(gd::String &audioName) {
|
||||
ExposeResourceAsFile(audioName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeFont(gd::String &fontName) {
|
||||
ExposeResourceAsFile(fontName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeJson(gd::String &jsonName) {
|
||||
ExposeResourceAsFile(jsonName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeTilemap(gd::String &tilemapName) {
|
||||
ExposeResourceAsFile(tilemapName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeTileset(gd::String &tilesetName) {
|
||||
ExposeResourceAsFile(tilesetName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeVideo(gd::String &videoName) {
|
||||
ExposeResourceAsFile(videoName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeBitmapFont(gd::String &bitmapFontName) {
|
||||
ExposeResourceAsFile(bitmapFontName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeResourceAsFile(gd::String &resourceName) {
|
||||
|
||||
auto &resource = resourcesManager->GetResource(resourceName);
|
||||
gd::String file = resource.GetFile();
|
||||
ExposeFile(file);
|
||||
|
||||
resourcesNameReverseMap[file] = resourceName;
|
||||
resourceName = file;
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeFile(gd::String &resourceFilePath) {
|
||||
|
||||
size_t slashPos = resourceFilePath.find_last_of("/");
|
||||
size_t antiSlashPos = resourceFilePath.find_last_of("\\");
|
||||
size_t baseNamePos =
|
||||
slashPos == String::npos
|
||||
? antiSlashPos == String::npos ? 0 : (antiSlashPos + 1)
|
||||
: antiSlashPos == String::npos ? (slashPos + 1)
|
||||
: slashPos > antiSlashPos ? (slashPos + 1)
|
||||
: (antiSlashPos + 1);
|
||||
gd::String baseName =
|
||||
baseNamePos != 0
|
||||
? resourceFilePath.substr(baseNamePos, resourceFilePath.length())
|
||||
: resourceFilePath;
|
||||
|
||||
resourcesFileNameMap[resourceFilePath] = baseName;
|
||||
resourceFilePath = baseName;
|
||||
}
|
||||
|
||||
} // namespace gd
|
65
Core/GDCore/IDE/Project/AssetResourcePathCleaner.h
Normal file
65
Core/GDCore/IDE/Project/AssetResourcePathCleaner.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
|
||||
#include "GDCore/String.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace gd {
|
||||
class AbstractFileSystem;
|
||||
class Project;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief AssetResourcePathCleaner is used when exporting an object as an asset.
|
||||
* It removes the folder from the path.
|
||||
*
|
||||
* \see ArbitraryResourceWorker
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API AssetResourcePathCleaner : public ArbitraryResourceWorker {
|
||||
public:
|
||||
AssetResourcePathCleaner(
|
||||
gd::ResourcesManager &resourcesManager,
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap_,
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap_)
|
||||
: ArbitraryResourceWorker(resourcesManager),
|
||||
resourcesFileNameMap(resourcesFileNameMap_),
|
||||
resourcesNameReverseMap(resourcesNameReverseMap_){};
|
||||
virtual ~AssetResourcePathCleaner(){};
|
||||
|
||||
void ExposeImage(gd::String &imageName) override;
|
||||
void ExposeAudio(gd::String &audioName) override;
|
||||
void ExposeFont(gd::String &fontName) override;
|
||||
void ExposeJson(gd::String &jsonName) override;
|
||||
void ExposeTilemap(gd::String &tilemapName) override;
|
||||
void ExposeTileset(gd::String &tilesetName) override;
|
||||
void ExposeVideo(gd::String &videoName) override;
|
||||
void ExposeBitmapFont(gd::String &bitmapFontName) override;
|
||||
void ExposeFile(gd::String &resource) override;
|
||||
|
||||
protected:
|
||||
void ExposeResourceAsFile(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* New file names that can be accessed by their original name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap;
|
||||
|
||||
/**
|
||||
* Original resource names that can be accessed by their new name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap;
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -79,6 +79,12 @@ public:
|
||||
virtual void ExposeModel3D(gd::String& otherResourceName) override {
|
||||
MatchResourceName(otherResourceName);
|
||||
};
|
||||
virtual void ExposeAtlas(gd::String& otherResourceName) override {
|
||||
MatchResourceName(otherResourceName);
|
||||
};
|
||||
virtual void ExposeSpine(gd::String& otherResourceName) override {
|
||||
MatchResourceName(otherResourceName);
|
||||
};
|
||||
|
||||
void MatchResourceName(gd::String& otherResourceName) {
|
||||
if (otherResourceName == resourceName) matchesResourceName = true;
|
||||
|
@@ -3,9 +3,10 @@
|
||||
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef PROJECTRESOURCESCOPIER_H
|
||||
#define PROJECTRESOURCESCOPIER_H
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class Project;
|
||||
class AbstractFileSystem;
|
||||
@@ -47,6 +48,7 @@ class GD_CORE_API ProjectResourcesCopier {
|
||||
bool updateOriginalProject,
|
||||
bool preserveAbsoluteFilenames = true,
|
||||
bool preserveDirectoryStructure = true);
|
||||
|
||||
private:
|
||||
static bool CopyAllResourcesTo(gd::Project& originalProject,
|
||||
gd::Project& clonedProject,
|
||||
@@ -57,5 +59,3 @@ private:
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // PROJECTRESOURCESCOPIER_H
|
||||
|
25
Core/GDCore/IDE/Project/ResourcesInUseHelper.cpp
Normal file
25
Core/GDCore/IDE/Project/ResourcesInUseHelper.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "ResourcesInUseHelper.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
const std::vector<gd::String> ResourcesInUseHelper::resourceTypes = {
|
||||
"image", "audio", "font", "json", "tilemap",
|
||||
"tileset", "video", "bitmapFont", "model3D"};
|
||||
|
||||
const std::vector<gd::String> &ResourcesInUseHelper::GetAllResources() {
|
||||
allResources.clear();
|
||||
for (auto &&resourceType : gd::ResourcesInUseHelper::resourceTypes) {
|
||||
for (auto &&resourceName : GetAll(resourceType)) {
|
||||
allResources.push_back(resourceName);
|
||||
}
|
||||
}
|
||||
return allResources;
|
||||
}
|
||||
|
||||
} // namespace gd
|
@@ -4,9 +4,7 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#ifndef IMAGESUSEDINVENTORIZER_H
|
||||
#define IMAGESUSEDINVENTORIZER_H
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
@@ -38,6 +36,7 @@ public:
|
||||
: gd::ArbitraryResourceWorker(resourcesManager){};
|
||||
virtual ~ResourcesInUseHelper(){};
|
||||
|
||||
const std::vector<gd::String>& GetAllResources();
|
||||
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
|
||||
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
@@ -47,6 +46,8 @@ public:
|
||||
std::set<gd::String>& GetAllVideos() { return GetAll("video"); };
|
||||
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
|
||||
std::set<gd::String>& GetAll3DModels() { return GetAll("model3D"); };
|
||||
std::set<gd::String>& GetAllAtlases() { return GetAll("atlas"); };
|
||||
std::set<gd::String>& GetAllSpines() { return GetAll("spine"); };
|
||||
std::set<gd::String>& GetAll(const gd::String& resourceType) {
|
||||
if (resourceType == "image") return allImages;
|
||||
if (resourceType == "audio") return allAudios;
|
||||
@@ -57,6 +58,8 @@ public:
|
||||
if (resourceType == "video") return allVideos;
|
||||
if (resourceType == "bitmapFont") return allBitmapFonts;
|
||||
if (resourceType == "model3D") return allModel3Ds;
|
||||
if (resourceType == "atlas") return allAtlases;
|
||||
if (resourceType == "spine") return allSpines;
|
||||
|
||||
return emptyResources;
|
||||
};
|
||||
@@ -64,35 +67,42 @@ public:
|
||||
virtual void ExposeFile(gd::String& resource) override{
|
||||
/*Don't care, we just list resource names*/
|
||||
};
|
||||
virtual void ExposeImage(gd::String& imageResourceName) override {
|
||||
allImages.insert(imageResourceName);
|
||||
virtual void ExposeImage(gd::String& resourceName) override {
|
||||
allImages.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeAudio(gd::String& audioResourceName) override {
|
||||
allAudios.insert(audioResourceName);
|
||||
virtual void ExposeAudio(gd::String& resourceName) override {
|
||||
allAudios.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
allFonts.insert(fontResourceName);
|
||||
virtual void ExposeFont(gd::String& resourceName) override {
|
||||
allFonts.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeJson(gd::String& jsonResourceName) override {
|
||||
allJsons.insert(jsonResourceName);
|
||||
virtual void ExposeJson(gd::String& resourceName) override {
|
||||
allJsons.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeTilemap(gd::String& tilemapResourceName) override {
|
||||
allTilemaps.insert(tilemapResourceName);
|
||||
virtual void ExposeTilemap(gd::String& resourceName) override {
|
||||
allTilemaps.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeTileset(gd::String& tilesetResourceName) override {
|
||||
allTilesets.insert(tilesetResourceName);
|
||||
virtual void ExposeTileset(gd::String& resourceName) override {
|
||||
allTilesets.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeVideo(gd::String& videoResourceName) override {
|
||||
allVideos.insert(videoResourceName);
|
||||
virtual void ExposeVideo(gd::String& resourceName) override {
|
||||
allVideos.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
|
||||
allBitmapFonts.insert(bitmapFontResourceName);
|
||||
virtual void ExposeBitmapFont(gd::String& resourceName) override {
|
||||
allBitmapFonts.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeModel3D(gd::String& resourceName) override {
|
||||
allModel3Ds.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeAtlas(gd::String& resourceName) override {
|
||||
allAtlases.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeSpine(gd::String& resourceName) override {
|
||||
allSpines.insert(resourceName);
|
||||
};
|
||||
|
||||
protected:
|
||||
std::vector<gd::String> allResources;
|
||||
std::set<gd::String> allImages;
|
||||
std::set<gd::String> allAudios;
|
||||
std::set<gd::String> allFonts;
|
||||
@@ -102,10 +112,11 @@ public:
|
||||
std::set<gd::String> allVideos;
|
||||
std::set<gd::String> allBitmapFonts;
|
||||
std::set<gd::String> allModel3Ds;
|
||||
std::set<gd::String> allAtlases;
|
||||
std::set<gd::String> allSpines;
|
||||
std::set<gd::String> emptyResources;
|
||||
|
||||
static const std::vector<gd::String> resourceTypes;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // IMAGESUSEDINVENTORIZER_H
|
||||
#endif
|
||||
|
@@ -28,7 +28,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
auto stripToFilenameOnly = [&]() {
|
||||
fs.MakeAbsolute(resourceFullFilename, baseDirectory);
|
||||
SetNewFilename(resourceFullFilename, fs.FileNameFrom(resourceFullFilename));
|
||||
resourceFilename = oldFilenames[resourceFullFilename];
|
||||
resourceFilename = newFilenames[resourceFullFilename];
|
||||
};
|
||||
|
||||
// if we do not want to preserve the folders at all,
|
||||
@@ -45,7 +45,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
gd::String relativeFilename = resourceFullFilename;
|
||||
if (fs.MakeRelative(relativeFilename, baseDirectory)) {
|
||||
SetNewFilename(resourceFullFilename, relativeFilename);
|
||||
resourceFilename = oldFilenames[resourceFullFilename];
|
||||
resourceFilename = newFilenames[resourceFullFilename];
|
||||
} else {
|
||||
// The filename cannot be made relative. Consider that it is absolute.
|
||||
// Just strip the filename to its file part
|
||||
@@ -63,7 +63,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
|
||||
void ResourcesMergingHelper::SetNewFilename(gd::String oldFilename,
|
||||
gd::String newFilename) {
|
||||
if (oldFilenames.find(oldFilename) != oldFilenames.end()) return;
|
||||
if (newFilenames.find(oldFilename) != newFilenames.end()) return;
|
||||
|
||||
// Extract baseName and extension from the new filename
|
||||
size_t extensionPos = newFilename.find_last_of(".");
|
||||
@@ -80,13 +80,13 @@ void ResourcesMergingHelper::SetNewFilename(gd::String oldFilename,
|
||||
gd::NewNameGenerator::Generate(
|
||||
baseName,
|
||||
[this, extension](const gd::String& newBaseName) {
|
||||
return newFilenames.find(newBaseName + extension) !=
|
||||
newFilenames.end();
|
||||
return oldFilenames.find(newBaseName + extension) !=
|
||||
oldFilenames.end();
|
||||
}) +
|
||||
extension;
|
||||
|
||||
oldFilenames[oldFilename] = finalFilename;
|
||||
newFilenames[finalFilename] = oldFilename;
|
||||
newFilenames[oldFilename] = finalFilename;
|
||||
oldFilenames[finalFilename] = oldFilename;
|
||||
}
|
||||
|
||||
void ResourcesMergingHelper::SetBaseDirectory(
|
||||
|
@@ -64,19 +64,25 @@ public:
|
||||
* the Base Directory.
|
||||
*/
|
||||
std::map<gd::String, gd::String>& GetAllResourcesOldAndNewFilename() {
|
||||
return oldFilenames;
|
||||
return newFilenames;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resources merging helper collects all resources filenames and update these
|
||||
* filenames.
|
||||
*/
|
||||
virtual void ExposeFile(gd::String& resource) override;
|
||||
void ExposeFile(gd::String& resource) override;
|
||||
|
||||
protected:
|
||||
void SetNewFilename(gd::String oldFilename, gd::String newFilename);
|
||||
|
||||
/**
|
||||
* Original file names that can be accessed by their new name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> oldFilenames;
|
||||
/**
|
||||
* New file names that can be accessed by their original name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> newFilenames;
|
||||
gd::String baseDirectory;
|
||||
bool preserveDirectoriesStructure; ///< If set to true, the directory
|
||||
|
@@ -65,6 +65,12 @@ class ResourcesRenamer : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeModel3D(gd::String& resourceName) override {
|
||||
RenameIfNeeded(resourceName);
|
||||
};
|
||||
virtual void ExposeAtlas(gd::String& resourceName) override {
|
||||
RenameIfNeeded(resourceName);
|
||||
};
|
||||
virtual void ExposeSpine(gd::String& resourceName) override {
|
||||
RenameIfNeeded(resourceName);
|
||||
};
|
||||
|
||||
private:
|
||||
void RenameIfNeeded(gd::String& resourceName) {
|
||||
|
@@ -80,6 +80,12 @@ private:
|
||||
void ExposeModel3D(gd::String &resourceName) override {
|
||||
AddUsedResource(resourceName);
|
||||
};
|
||||
void ExposeAtlas(gd::String &resourceName) override {
|
||||
AddUsedResource(resourceName);
|
||||
};
|
||||
void ExposeSpine(gd::String &resourceName) override {
|
||||
AddUsedResource(resourceName);
|
||||
};
|
||||
|
||||
std::set<gd::String> resourceNames;
|
||||
};
|
||||
|
@@ -16,8 +16,8 @@
|
||||
#include "GDCore/IDE/Events/BehaviorTypeRenamer.h"
|
||||
#include "GDCore/IDE/Events/CustomObjectTypeRenamer.h"
|
||||
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
|
||||
#include "GDCore/IDE/Events/EventsRefactorer.h"
|
||||
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
|
||||
#include "GDCore/IDE/Events/EventsRefactorer.h"
|
||||
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
|
||||
#include "GDCore/IDE/Events/ExpressionsParameterMover.h"
|
||||
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
|
||||
@@ -138,7 +138,8 @@ void WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
}
|
||||
}
|
||||
|
||||
VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
|
||||
VariablesChangeset
|
||||
WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
|
||||
gd::Project &project,
|
||||
const gd::SerializerElement &oldSerializedVariablesContainer,
|
||||
const gd::VariablesContainer &newVariablesContainer) {
|
||||
@@ -149,9 +150,9 @@ VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer
|
||||
|
||||
if (oldVariablesContainer.GetPersistentUuid() !=
|
||||
newVariablesContainer.GetPersistentUuid()) {
|
||||
gd::LogWarning(
|
||||
_("Called ComputeChangesetForVariablesContainer on variables containers "
|
||||
"that are different - they can't be compared."));
|
||||
gd::LogWarning(_(
|
||||
"Called ComputeChangesetForVariablesContainer on variables containers "
|
||||
"that are different - they can't be compared."));
|
||||
return changeset;
|
||||
}
|
||||
|
||||
@@ -192,14 +193,11 @@ VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
|
||||
gd::Project &project,
|
||||
const gd::VariablesContainer &newVariablesContainer,
|
||||
const gd::VariablesChangeset& changeset) {
|
||||
gd::Project &project, const gd::VariablesContainer &newVariablesContainer,
|
||||
const gd::VariablesChangeset &changeset) {
|
||||
gd::EventsVariableReplacer eventsVariableReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
newVariablesContainer,
|
||||
changeset.oldToNewVariableNames,
|
||||
changeset.removedVariableNames);
|
||||
project.GetCurrentPlatform(), newVariablesContainer,
|
||||
changeset.oldToNewVariableNames, changeset.removedVariableNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsVariableReplacer);
|
||||
}
|
||||
@@ -743,14 +741,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
|
||||
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
|
||||
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
|
||||
{oldPropertyName, newPropertyName}};
|
||||
std::unordered_set<gd::String> removedPropertyNames;
|
||||
gd::EventsPropertyReplacer eventsPropertyReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
properties,
|
||||
oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
|
||||
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsPropertyReplacer);
|
||||
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
@@ -813,14 +811,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
|
||||
EventsBasedBehavior::GetSharedPropertyExpressionName(newPropertyName));
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
|
||||
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
|
||||
{oldPropertyName, newPropertyName}};
|
||||
std::unordered_set<gd::String> removedPropertyNames;
|
||||
gd::EventsPropertyReplacer eventsPropertyReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
properties,
|
||||
oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
|
||||
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsPropertyReplacer);
|
||||
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
@@ -870,14 +868,14 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
|
||||
EventsBasedObject::GetPropertyExpressionName(newPropertyName));
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
|
||||
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
|
||||
{oldPropertyName, newPropertyName}};
|
||||
std::unordered_set<gd::String> removedPropertyNames;
|
||||
gd::EventsPropertyReplacer eventsPropertyReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
properties,
|
||||
oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
|
||||
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsPropertyReplacer);
|
||||
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
@@ -1351,7 +1349,6 @@ void WholeProjectRefactorer::DoRenameBehavior(
|
||||
gd::Project &project, const gd::String &oldBehaviorType,
|
||||
const gd::String &newBehaviorType,
|
||||
const gd::ProjectBrowser &projectBrowser) {
|
||||
|
||||
// Rename behavior in required behavior properties
|
||||
auto requiredBehaviorRenamer =
|
||||
gd::RequiredBehaviorRenamer(oldBehaviorType, newBehaviorType);
|
||||
@@ -1378,7 +1375,6 @@ void WholeProjectRefactorer::DoRenameBehavior(
|
||||
void WholeProjectRefactorer::DoRenameObject(
|
||||
gd::Project &project, const gd::String &oldObjectType,
|
||||
const gd::String &newObjectType, const gd::ProjectBrowser &projectBrowser) {
|
||||
|
||||
// Rename object type in objects lists.
|
||||
auto customObjectTypeRenamer =
|
||||
gd::CustomObjectTypeRenamer(oldObjectType, newObjectType);
|
||||
@@ -1398,7 +1394,8 @@ void WholeProjectRefactorer::DoRenameObject(
|
||||
void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
|
||||
gd::Project &project, gd::Layout &layout, const gd::String &objectName,
|
||||
bool isObjectGroup, bool removeEventsAndGroups) {
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
|
||||
// Remove object in the current layout
|
||||
if (removeEventsAndGroups) {
|
||||
@@ -1447,7 +1444,8 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
|
||||
if (oldName == newName || newName.empty() || oldName.empty())
|
||||
return;
|
||||
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
|
||||
// Rename object in the current layout
|
||||
gd::EventsRefactorer::RenameObjectInEvents(
|
||||
@@ -1536,10 +1534,19 @@ void WholeProjectRefactorer::RenameLayer(gd::Project &project,
|
||||
const gd::String &newName) {
|
||||
if (oldName == newName || newName.empty() || oldName.empty())
|
||||
return;
|
||||
|
||||
gd::ProjectElementRenamer projectElementRenamer(project.GetCurrentPlatform(),
|
||||
"layer", oldName, newName);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
layout.GetInitialInstances().MoveInstancesToLayer(oldName, newName);
|
||||
|
||||
std::vector<gd::String> externalLayoutsNames =
|
||||
GetAssociatedExternalLayouts(project, layout);
|
||||
for (gd::String name : externalLayoutsNames) {
|
||||
auto &externalLayout = project.GetExternalLayout(name);
|
||||
externalLayout.GetInitialInstances().MoveInstancesToLayer(oldName, newName);
|
||||
}
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
|
||||
@@ -1552,8 +1559,8 @@ void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "layerEffectName", oldName, newName);
|
||||
projectElementRenamer.SetLayerConstraint(layer.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
|
||||
@@ -1566,8 +1573,8 @@ void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "objectAnimationName", oldName, newName);
|
||||
projectElementRenamer.SetObjectConstraint(object.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
|
||||
@@ -1580,8 +1587,8 @@ void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "objectPointName", oldName, newName);
|
||||
projectElementRenamer.SetObjectConstraint(object.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
|
||||
@@ -1594,8 +1601,8 @@ void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "objectEffectName", oldName, newName);
|
||||
projectElementRenamer.SetObjectConstraint(object.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsBasedObject(
|
||||
@@ -1617,9 +1624,12 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsFunction(
|
||||
gd::ObjectsContainer &globalObjectsContainer,
|
||||
gd::ObjectsContainer &objectsContainer, const gd::String &objectName,
|
||||
bool isObjectGroup, bool removeEventsAndGroups) {
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
|
||||
// In practice, this is ok because we only deal with objects.
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it
|
||||
// does not have to construct one. In practice, this is ok because we only
|
||||
// deal with objects.
|
||||
auto projectScopedContainers =
|
||||
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(
|
||||
globalObjectsContainer, objectsContainer);
|
||||
|
||||
if (removeEventsAndGroups) {
|
||||
gd::EventsRefactorer::RemoveObjectInEvents(
|
||||
@@ -1655,9 +1665,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(
|
||||
gd::ObjectsContainer &globalObjectsContainer,
|
||||
gd::ObjectsContainer &objectsContainer, const gd::String &oldName,
|
||||
const gd::String &newName, bool isObjectGroup) {
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
|
||||
// In practice, this is ok because we only deal with objects.
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it
|
||||
// does not have to construct one. In practice, this is ok because we only
|
||||
// deal with objects.
|
||||
auto projectScopedContainers =
|
||||
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(
|
||||
globalObjectsContainer, objectsContainer);
|
||||
|
||||
gd::EventsRefactorer::RenameObjectInEvents(
|
||||
project.GetCurrentPlatform(), projectScopedContainers,
|
||||
@@ -1705,7 +1718,7 @@ void WholeProjectRefactorer::GlobalObjectOrGroupRemoved(
|
||||
if (layout.HasObjectNamed(objectName))
|
||||
continue;
|
||||
|
||||
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
|
||||
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
|
||||
removeEventsAndGroups);
|
||||
}
|
||||
}
|
||||
@@ -1753,7 +1766,8 @@ size_t WholeProjectRefactorer::GetLayoutAndExternalLayoutLayerInstancesCount(
|
||||
GetAssociatedExternalLayouts(project, layout);
|
||||
for (gd::String name : externalLayoutsNames) {
|
||||
auto &externalLayout = project.GetExternalLayout(name);
|
||||
count += externalLayout.GetInitialInstances().GetLayerInstancesCount(layerName);
|
||||
count +=
|
||||
externalLayout.GetInitialInstances().GetLayerInstancesCount(layerName);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
@@ -4,8 +4,6 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include <iostream>
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
|
@@ -155,6 +155,10 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
|
||||
worker.ExposeBitmapFont(newPropertyValue);
|
||||
} else if (resourceType == "model3D") {
|
||||
worker.ExposeModel3D(newPropertyValue);
|
||||
} else if (resourceType == "atlas") {
|
||||
worker.ExposeAtlas(newPropertyValue);
|
||||
} else if (resourceType == "spine") {
|
||||
worker.ExposeSpine(newPropertyValue);
|
||||
}
|
||||
|
||||
if (newPropertyValue != oldPropertyValue) {
|
||||
|
@@ -13,7 +13,8 @@ EventsBasedObject::EventsBasedObject()
|
||||
: AbstractEventsBasedEntity(
|
||||
"MyObject",
|
||||
gd::EventsFunctionsContainer::FunctionOwner::Object),
|
||||
ObjectsContainer() {
|
||||
ObjectsContainer(),
|
||||
isRenderedIn3D(false) {
|
||||
}
|
||||
|
||||
EventsBasedObject::~EventsBasedObject() {}
|
||||
|
@@ -39,6 +39,7 @@ void Layer::SetCameraCount(std::size_t n) {
|
||||
void Layer::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("name", GetName());
|
||||
element.SetAttribute("renderingType", GetRenderingType());
|
||||
element.SetAttribute("cameraType", GetCameraType());
|
||||
element.SetAttribute("visibility", GetVisibility());
|
||||
element.SetAttribute("isLocked", IsLocked());
|
||||
element.SetAttribute("isLightingLayer", IsLightingLayer());
|
||||
@@ -78,6 +79,7 @@ void Layer::SerializeTo(SerializerElement& element) const {
|
||||
void Layer::UnserializeFrom(const SerializerElement& element) {
|
||||
SetName(element.GetStringAttribute("name", "", "Name"));
|
||||
SetRenderingType(element.GetStringAttribute("renderingType", ""));
|
||||
SetCameraType(element.GetStringAttribute("cameraType", "perspective"));
|
||||
SetVisibility(element.GetBoolAttribute("visibility", true, "Visibility"));
|
||||
SetLocked(element.GetBoolAttribute("isLocked", false));
|
||||
SetLightingLayer(element.GetBoolAttribute("isLightingLayer", false));
|
||||
|
@@ -104,10 +104,17 @@ class GD_CORE_API Layer {
|
||||
const gd::String& GetName() const { return name; }
|
||||
|
||||
const gd::String& GetRenderingType() const { return renderingType; }
|
||||
|
||||
void SetRenderingType(const gd::String& renderingType_) {
|
||||
renderingType = renderingType_;
|
||||
}
|
||||
|
||||
const gd::String& GetCameraType() const { return cameraType; }
|
||||
|
||||
void SetCameraType(const gd::String& cameraType_) {
|
||||
cameraType = cameraType_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Change if layer is displayed or not
|
||||
*/
|
||||
@@ -268,6 +275,7 @@ class GD_CORE_API Layer {
|
||||
gd::String name; ///< The name of the layer
|
||||
gd::String renderingType; ///< The rendering type: "" (empty), "2d", "3d" or
|
||||
///< "2d+3d".
|
||||
gd::String cameraType;
|
||||
bool isVisible; ///< True if the layer is visible
|
||||
bool isLocked; ///< True if the layer is locked
|
||||
bool isLightingLayer; ///< True if the layer is used to display lights and
|
||||
|
@@ -120,9 +120,6 @@ class GD_CORE_API Object {
|
||||
*/
|
||||
const gd::String& GetType() const { return configuration->GetType(); }
|
||||
|
||||
/** \brief Shortcut to check if the object is a 3D object.
|
||||
*/
|
||||
bool Is3DObject() const { return configuration->Is3DObject(); }
|
||||
///@}
|
||||
|
||||
/** \name Behaviors management
|
||||
|
@@ -5,11 +5,8 @@
|
||||
*/
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
|
||||
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/CustomBehavior.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
@@ -20,7 +17,7 @@ namespace gd {
|
||||
|
||||
ObjectConfiguration::~ObjectConfiguration() {}
|
||||
|
||||
ObjectConfiguration::ObjectConfiguration(): is3DObject(false) {}
|
||||
ObjectConfiguration::ObjectConfiguration() {}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> ObjectConfiguration::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> nothing;
|
||||
|
@@ -63,20 +63,12 @@ class GD_CORE_API ObjectConfiguration {
|
||||
*/
|
||||
void SetType(const gd::String& type_) {
|
||||
type = type_;
|
||||
|
||||
// For now, as a shortcut, consider only the objects from the built-in 3D extension
|
||||
// to be 3D object.
|
||||
is3DObject = type.find("Scene3D::") == 0;
|
||||
}
|
||||
|
||||
/** \brief Return the type of the object.
|
||||
*/
|
||||
const gd::String& GetType() const { return type; }
|
||||
|
||||
/** \brief Shortcut to check if the object is a 3D object.
|
||||
*/
|
||||
bool Is3DObject() const { return is3DObject; }
|
||||
|
||||
/** \name Object properties
|
||||
* Reading and updating object configuration properties
|
||||
*/
|
||||
@@ -180,7 +172,6 @@ class GD_CORE_API ObjectConfiguration {
|
||||
protected:
|
||||
gd::String type; ///< Which type of object is represented by this
|
||||
///< configuration.
|
||||
bool is3DObject;
|
||||
|
||||
/**
|
||||
* \brief Derived object configuration can redefine this method to load
|
||||
|
@@ -102,11 +102,20 @@ std::unique_ptr<gd::Object> Project::CreateObject(
|
||||
behavior->SetDefaultBehavior(true);
|
||||
};
|
||||
|
||||
if (Project::HasEventsBasedObject(objectType)) {
|
||||
if (project.HasEventsBasedObject(objectType)) {
|
||||
// During project deserialization, event-based object metadata are not yet
|
||||
// generated.
|
||||
addDefaultBehavior("EffectCapability::EffectBehavior");
|
||||
addDefaultBehavior("ResizableCapability::ResizableBehavior");
|
||||
addDefaultBehavior("ScalableCapability::ScalableBehavior");
|
||||
addDefaultBehavior("FlippableCapability::FlippableBehavior");
|
||||
auto& eventBasedObject = project.GetEventsBasedObject(objectType);
|
||||
if (eventBasedObject.IsRenderedIn3D()) {
|
||||
addDefaultBehavior("Scene3D::Base3DBehavior");
|
||||
}
|
||||
else {
|
||||
addDefaultBehavior("OpacityCapability::OpacityBehavior");
|
||||
}
|
||||
} else {
|
||||
auto& objectMetadata =
|
||||
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
|
||||
|
@@ -29,7 +29,15 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
|
||||
for (const gd::String& information : extraInformation) {
|
||||
extraInformationElement.AddChild("").SetStringValue(information);
|
||||
}
|
||||
element.AddChild("hidden").SetBoolValue(hidden);
|
||||
if (hidden) {
|
||||
element.AddChild("hidden").SetBoolValue(hidden);
|
||||
}
|
||||
if (deprecated) {
|
||||
element.AddChild("deprecated").SetBoolValue(deprecated);
|
||||
}
|
||||
if (advanced) {
|
||||
element.AddChild("advanced").SetBoolValue(advanced);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
@@ -58,6 +66,12 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
hidden = element.HasChild("hidden")
|
||||
? element.GetChild("hidden").GetBoolValue()
|
||||
: false;
|
||||
deprecated = element.HasChild("deprecated")
|
||||
? element.GetChild("deprecated").GetBoolValue()
|
||||
: false;
|
||||
advanced = element.HasChild("advanced")
|
||||
? element.GetChild("advanced").GetBoolValue()
|
||||
: false;
|
||||
}
|
||||
|
||||
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {
|
||||
|
@@ -30,12 +30,16 @@ class GD_CORE_API PropertyDescriptor {
|
||||
* \param propertyValue The value of the property.
|
||||
*/
|
||||
PropertyDescriptor(gd::String propertyValue)
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false), measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false),
|
||||
deprecated(false), advanced(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
|
||||
|
||||
/**
|
||||
* \brief Empty constructor creating an empty property to be displayed.
|
||||
*/
|
||||
PropertyDescriptor() : hidden(false), measurementUnit(gd::MeasurementUnit::GetUndefined()) {};
|
||||
PropertyDescriptor()
|
||||
: hidden(false), deprecated(false), advanced(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()){};
|
||||
|
||||
/**
|
||||
* \brief Destructor
|
||||
@@ -142,6 +146,32 @@ class GD_CORE_API PropertyDescriptor {
|
||||
*/
|
||||
bool IsHidden() const { return hidden; }
|
||||
|
||||
/**
|
||||
* \brief Set if the property is deprecated.
|
||||
*/
|
||||
PropertyDescriptor& SetDeprecated(bool enable = true) {
|
||||
deprecated = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check if the property is deprecated.
|
||||
*/
|
||||
bool IsDeprecated() const { return deprecated; }
|
||||
|
||||
/**
|
||||
* \brief Set if the property is marked as advanced.
|
||||
*/
|
||||
PropertyDescriptor& SetAdvanced(bool enable = true) {
|
||||
advanced = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check if the property is marked as advanced.
|
||||
*/
|
||||
bool IsAdvanced() const { return advanced; }
|
||||
|
||||
/** \name Serialization
|
||||
*/
|
||||
///@{
|
||||
@@ -179,6 +209,8 @@ class GD_CORE_API PropertyDescriptor {
|
||||
///< choices, if a property is a displayed as a combo
|
||||
///< box.
|
||||
bool hidden;
|
||||
bool deprecated;
|
||||
bool advanced;
|
||||
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
|
||||
};
|
||||
|
||||
|
@@ -93,6 +93,10 @@ std::shared_ptr<Resource> ResourcesManager::CreateResource(
|
||||
return std::make_shared<BitmapFontResource>();
|
||||
else if (kind == "model3D")
|
||||
return std::make_shared<Model3DResource>();
|
||||
else if (kind == "atlas")
|
||||
return std::make_shared<AtlasResource>();
|
||||
else if (kind == "spine")
|
||||
return std::make_shared<SpineResource>();
|
||||
|
||||
std::cout << "Bad resource created (type: " << kind << ")" << std::endl;
|
||||
return std::make_shared<Resource>();
|
||||
@@ -756,6 +760,20 @@ void Model3DResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
|
||||
void AtlasResource::SetFile(const gd::String& newFile) {
|
||||
file = NormalizePathSeparator(newFile);
|
||||
}
|
||||
|
||||
void AtlasResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetUserAdded(element.GetBoolAttribute("userAdded"));
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
void AtlasResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
|
||||
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
|
||||
|
||||
ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
|
||||
|
@@ -373,6 +373,21 @@ class GD_CORE_API JsonResource : public Resource {
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a spine json file used by a project.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
*/
|
||||
class GD_CORE_API SpineResource : public JsonResource {
|
||||
public:
|
||||
SpineResource() : JsonResource() { SetKind("spine"); };
|
||||
virtual ~SpineResource(){};
|
||||
virtual SpineResource* Clone() const override {
|
||||
return new SpineResource(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a tilemap file used by a project.
|
||||
*
|
||||
@@ -507,6 +522,32 @@ class GD_CORE_API Model3DResource : public Resource {
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe an atlas file used by a project.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
*/
|
||||
class GD_CORE_API AtlasResource : public Resource {
|
||||
public:
|
||||
AtlasResource() : Resource() { SetKind("atlas"); };
|
||||
virtual ~AtlasResource(){};
|
||||
virtual AtlasResource* Clone() const override {
|
||||
return new AtlasResource(*this);
|
||||
}
|
||||
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
virtual bool UseFile() const override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
private:
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Inventory all resources used by a project
|
||||
*
|
||||
|
@@ -489,7 +489,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
|
||||
"Effect",
|
||||
_("Apply visual effects to objects."),
|
||||
"",
|
||||
"res/actions/effect24.png", "EffectBehavior",
|
||||
"res/actions/effect_black.svg", "EffectBehavior",
|
||||
std::make_shared<gd::Behavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
.SetHidden();
|
||||
|
@@ -203,6 +203,23 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node = parser.ParseExpression("abcd[0]");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"No object, variable or property with this name found.");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 0);
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("abcd[0].efg");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -214,6 +231,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node = parser.ParseExpression("abcd.efg.hij");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -762,6 +785,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node = parser.ParseExpression("abcd.efg.hij");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -1495,6 +1524,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyNonExistingSceneVariable");
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -1516,6 +1551,25 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables (2 levels, variable and child do not exist)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MyNonExistingSceneVariable.MyNonExistingChild");
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name.");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Valid object variables (1 level)") {
|
||||
{
|
||||
auto node =
|
||||
@@ -1586,6 +1640,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyNonExistingSpriteObject.MyVariable");
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
|
147
Core/tests/ObjectAssetSerializer.cpp
Normal file
147
Core/tests/ObjectAssetSerializer.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering common features of GDevelop Core.
|
||||
*/
|
||||
#include "GDCore/IDE/ObjectAssetSerializer.h"
|
||||
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/EventsList.h"
|
||||
#include "GDCore/Events/Serialization.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/CustomObjectConfiguration.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/ObjectsContainer.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/Variable.h"
|
||||
#include "GDCore/Serialization/Serializer.h"
|
||||
#include "GDCore/Tools/SystemStats.h"
|
||||
#include "GDCore/Tools/VersionWrapper.h"
|
||||
#include "catch.hpp"
|
||||
#include <string>
|
||||
|
||||
using namespace gd;
|
||||
|
||||
TEST_CASE("ObjectAssetSerializer", "[common]") {
|
||||
|
||||
SECTION("Can serialize custom objects as assets") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
|
||||
"MyEventsBasedObject", 0);
|
||||
eventsBasedObject.SetFullName("My events based object");
|
||||
eventsBasedObject.SetDescription("An events based object for test");
|
||||
eventsBasedObject.InsertNewObject(project, "MyExtension::Sprite", "MyChild",
|
||||
0);
|
||||
|
||||
auto &resourceManager = project.GetResourcesManager();
|
||||
gd::ImageResource imageResource;
|
||||
imageResource.SetName("assets/Idle.png");
|
||||
imageResource.SetFile("assets/Idle.png");
|
||||
imageResource.SetSmooth(true);
|
||||
resourceManager.AddResource(imageResource);
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &object = layout.InsertNewObject(
|
||||
project, "MyEventsExtension::MyEventsBasedObject", "MyObject", 0);
|
||||
auto &configuration = object.GetConfiguration();
|
||||
auto *customObjectConfiguration =
|
||||
dynamic_cast<gd::CustomObjectConfiguration *>(&configuration);
|
||||
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(
|
||||
&customObjectConfiguration->GetChildObjectConfiguration("MyChild"));
|
||||
REQUIRE(spriteConfiguration != nullptr);
|
||||
{
|
||||
gd::Animation animation;
|
||||
animation.SetName("Idle");
|
||||
animation.SetDirectionsCount(1);
|
||||
auto &direction = animation.GetDirection(0);
|
||||
gd::Sprite frame;
|
||||
frame.SetImageName("assets/Idle.png");
|
||||
direction.AddSprite(frame);
|
||||
|
||||
spriteConfiguration->AddAnimation(animation);
|
||||
}
|
||||
|
||||
SerializerElement assetElement;
|
||||
std::vector<gd::String> usedResourceNames;
|
||||
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
|
||||
assetElement, usedResourceNames);
|
||||
|
||||
// This list is used to copy resource files.
|
||||
REQUIRE(usedResourceNames.size() == 1);
|
||||
REQUIRE(usedResourceNames[0] == "assets/Idle.png");
|
||||
|
||||
// Check that the project is left untouched.
|
||||
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
|
||||
REQUIRE(resourceManager.GetResource("assets/Idle.png").GetFile() ==
|
||||
"assets/Idle.png");
|
||||
REQUIRE(!resourceManager.HasResource("Idle.png"));
|
||||
|
||||
REQUIRE(assetElement.HasChild("objectAssets"));
|
||||
auto &objectAssetsElement = assetElement.GetChild("objectAssets");
|
||||
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
|
||||
REQUIRE(objectAssetsElement.GetChildrenCount() == 1);
|
||||
auto &objectAssetElement = objectAssetsElement.GetChild(0);
|
||||
|
||||
REQUIRE(objectAssetElement.HasChild("requiredExtensions"));
|
||||
auto &requiredExtensionsElement =
|
||||
objectAssetElement.GetChild("requiredExtensions");
|
||||
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
|
||||
REQUIRE(requiredExtensionsElement.GetChildrenCount() == 1);
|
||||
auto &requiredExtensionElement = requiredExtensionsElement.GetChild(0);
|
||||
REQUIRE(requiredExtensionElement.GetStringAttribute("extensionName") ==
|
||||
"MyEventsExtension");
|
||||
|
||||
// Resources are renamed according to asset script naming conventions.
|
||||
REQUIRE(objectAssetElement.HasChild("resources"));
|
||||
auto &resourcesElement = objectAssetElement.GetChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
REQUIRE(resourcesElement.GetChildrenCount() == 1);
|
||||
{
|
||||
auto &resourceElement = resourcesElement.GetChild(0);
|
||||
REQUIRE(resourceElement.GetStringAttribute("name") == "assets/Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("file") == "assets/Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
|
||||
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
|
||||
}
|
||||
|
||||
// Resources used in object configuration are updated.
|
||||
REQUIRE(objectAssetElement.HasChild("object"));
|
||||
auto &objectElement = objectAssetElement.GetChild("object");
|
||||
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
|
||||
REQUIRE(objectElement.GetStringAttribute("type") ==
|
||||
"MyEventsExtension::MyEventsBasedObject");
|
||||
auto &childrenContentElement = objectElement.GetChild("childrenContent");
|
||||
|
||||
REQUIRE(childrenContentElement.HasChild("MyChild"));
|
||||
auto &childElement = childrenContentElement.GetChild("MyChild");
|
||||
REQUIRE(childElement.HasChild("animations"));
|
||||
auto &animationsElement = childElement.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
REQUIRE(animationsElement.GetChildrenCount() == 1);
|
||||
auto &animationElement = animationsElement.GetChild(0);
|
||||
|
||||
REQUIRE(animationElement.GetStringAttribute("name") == "Idle");
|
||||
auto &directionsElement = animationElement.GetChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
REQUIRE(directionsElement.GetChildrenCount() == 1);
|
||||
auto &directionElement = directionsElement.GetChild(0);
|
||||
auto &spritesElement = directionElement.GetChild("sprites");
|
||||
spritesElement.ConsiderAsArrayOf("sprite");
|
||||
REQUIRE(spritesElement.GetChildrenCount() == 1);
|
||||
auto &spriteElement = spritesElement.GetChild(0);
|
||||
REQUIRE(spriteElement.GetStringAttribute("image") == "assets/Idle.png");
|
||||
}
|
||||
}
|
@@ -3474,6 +3474,72 @@ TEST_CASE("RenameLayer", "[common]") {
|
||||
"MyExtension::CameraCenterX(\"layerA\")");
|
||||
}
|
||||
|
||||
SECTION("Renaming a layer also moves the instances on this layer and of the associated external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("My layout", 0);
|
||||
auto &otherLayout = project.InsertNewLayout("My other layout", 1);
|
||||
|
||||
layout.InsertNewLayer("My layer", 0);
|
||||
otherLayout.InsertNewLayer("My layer", 0);
|
||||
|
||||
auto &externalLayout =
|
||||
project.InsertNewExternalLayout("My external layout", 0);
|
||||
auto &otherExternalLayout =
|
||||
project.InsertNewExternalLayout("My other external layout", 0);
|
||||
externalLayout.SetAssociatedLayout("My layout");
|
||||
otherExternalLayout.SetAssociatedLayout("My other layout");
|
||||
|
||||
auto &initialInstances = layout.GetInitialInstances();
|
||||
auto &initialInstance1 = initialInstances.InsertNewInitialInstance();
|
||||
initialInstance1.SetLayer("My layer");
|
||||
auto &initialInstance2 = initialInstances.InsertNewInitialInstance();
|
||||
initialInstance2.SetLayer("My layer");
|
||||
auto &initialInstance3 = initialInstances.InsertNewInitialInstance();
|
||||
initialInstance3.SetLayer("");
|
||||
|
||||
auto &externalInitialInstances = externalLayout.GetInitialInstances();
|
||||
auto &externalInitialInstance1 = externalInitialInstances.InsertNewInitialInstance();
|
||||
externalInitialInstance1.SetLayer("My layer");
|
||||
auto &externalInitialInstance2 = externalInitialInstances.InsertNewInitialInstance();
|
||||
externalInitialInstance2.SetLayer("My layer");
|
||||
auto &externalInitialInstance3 = externalInitialInstances.InsertNewInitialInstance();
|
||||
externalInitialInstance3.SetLayer("");
|
||||
|
||||
auto &otherInitialInstances = otherLayout.GetInitialInstances();
|
||||
auto &otherInitialInstance1 = otherInitialInstances.InsertNewInitialInstance();
|
||||
otherInitialInstance1.SetLayer("My layer");
|
||||
|
||||
auto &otherExternalInitialInstances = otherExternalLayout.GetInitialInstances();
|
||||
auto &otherExternalInitialInstance1 = otherExternalInitialInstances.InsertNewInitialInstance();
|
||||
otherExternalInitialInstance1.SetLayer("My layer");
|
||||
|
||||
REQUIRE(initialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(initialInstance2.GetLayer() == "My layer");
|
||||
REQUIRE(initialInstance3.GetLayer() == "");
|
||||
REQUIRE(externalInitialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(externalInitialInstance2.GetLayer() == "My layer");
|
||||
REQUIRE(externalInitialInstance3.GetLayer() == "");
|
||||
REQUIRE(otherInitialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(otherExternalInitialInstance1.GetLayer() == "My layer");
|
||||
|
||||
gd::WholeProjectRefactorer::RenameLayer(project, layout, "My layer", "My new layer");
|
||||
|
||||
// Instances on the renamed layer are moved to the new layer.
|
||||
REQUIRE(initialInstance1.GetLayer() == "My new layer");
|
||||
REQUIRE(initialInstance2.GetLayer() == "My new layer");
|
||||
REQUIRE(initialInstance3.GetLayer() == "");
|
||||
// Instances on the renamed layer of external layouts are moved to the new layer.
|
||||
REQUIRE(externalInitialInstance1.GetLayer() == "My new layer");
|
||||
REQUIRE(externalInitialInstance2.GetLayer() == "My new layer");
|
||||
REQUIRE(externalInitialInstance3.GetLayer() == "");
|
||||
// Instances on the renamed layer of other layouts & external layouts are not moved.
|
||||
REQUIRE(otherInitialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(otherExternalInitialInstance1.GetLayer() == "My layer");
|
||||
}
|
||||
|
||||
SECTION("Can rename a layer when a layer parameter is empty") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
|
@@ -154,7 +154,23 @@ namespace gdjs {
|
||||
* @return The Z position of the rendered object.
|
||||
*/
|
||||
getDrawableZ(): float {
|
||||
return this.getZ();
|
||||
return this._z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bottom Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMinZ(): number {
|
||||
return this.getDrawableZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.getDrawableZ() + this.getDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,8 +24,8 @@ namespace gdjs {
|
||||
|
||||
updatePosition() {
|
||||
this._threeObject3D.position.set(
|
||||
this._object.x + this._object.getWidth() / 2,
|
||||
this._object.y + this._object.getHeight() / 2,
|
||||
this._object.getX() + this._object.getWidth() / 2,
|
||||
this._object.getY() + this._object.getHeight() / 2,
|
||||
this._object.getZ() + this._object.getDepth() / 2
|
||||
);
|
||||
}
|
||||
|
@@ -103,6 +103,27 @@ namespace gdjs {
|
||||
flipZ(enable: boolean): void;
|
||||
|
||||
isFlippedZ(): boolean;
|
||||
|
||||
/**
|
||||
* Return the bottom Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMinZ(): number;
|
||||
|
||||
/**
|
||||
* Return the top Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number;
|
||||
}
|
||||
|
||||
export namespace Base3DHandler {
|
||||
export const is3D = (
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return object.getZ && object.setZ;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,6 +223,14 @@ namespace gdjs {
|
||||
isFlippedZ(): boolean {
|
||||
return this.object.isFlippedZ();
|
||||
}
|
||||
|
||||
getUnrotatedAABBMinZ(): number {
|
||||
return this.object.getUnrotatedAABBMinZ();
|
||||
}
|
||||
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.object.getUnrotatedAABBMaxZ();
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);
|
||||
|
89
Extensions/3D/BloomEffect.ts
Normal file
89
Extensions/3D/BloomEffect.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::Bloom',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.UnrealBloomPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.UnrealBloomPass(
|
||||
new THREE.Vector2(256, 256),
|
||||
1,
|
||||
0,
|
||||
0
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'strength') {
|
||||
this.shaderPass.strength = value;
|
||||
}
|
||||
if (parameterName === 'radius') {
|
||||
this.shaderPass.radius = value;
|
||||
}
|
||||
if (parameterName === 'threshold') {
|
||||
this.shaderPass.threshold = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'strength') {
|
||||
return this.shaderPass.strength;
|
||||
}
|
||||
if (parameterName === 'radius') {
|
||||
return this.shaderPass.radius;
|
||||
}
|
||||
if (parameterName === 'threshold') {
|
||||
return this.shaderPass.threshold;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
80
Extensions/3D/BrightnessAndContrastEffect.ts
Normal file
80
Extensions/3D/BrightnessAndContrastEffect.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::BrightnessAndContrast',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.ShaderPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.BrightnessContrastShader
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'brightness') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
if (parameterName === 'contrast') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'brightness') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
if (parameterName === 'contrast') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
373
Extensions/3D/CustomRuntimeObject3D.ts
Normal file
373
Extensions/3D/CustomRuntimeObject3D.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
namespace gdjs {
|
||||
export interface Object3DDataContent {
|
||||
width: float;
|
||||
height: float;
|
||||
depth: float;
|
||||
}
|
||||
/** Base parameters for {@link gdjs.RuntimeObject3D} */
|
||||
export interface Object3DData extends ObjectData {
|
||||
/** The base parameters of the RuntimeObject3D */
|
||||
content: Object3DDataContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for 3D custom objects.
|
||||
*/
|
||||
export class CustomRuntimeObject3D
|
||||
extends gdjs.CustomRuntimeObject
|
||||
implements gdjs.Base3DHandler {
|
||||
/**
|
||||
* Position on the Z axis.
|
||||
*/
|
||||
private _z: float = 0;
|
||||
private _minZ: float = 0;
|
||||
private _maxZ: float = 0;
|
||||
private _scaleZ: float = 1;
|
||||
private _flippedZ: boolean = false;
|
||||
/**
|
||||
* Euler angle with the `ZYX` order.
|
||||
*
|
||||
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
|
||||
*/
|
||||
private _rotationX: float = 0;
|
||||
/**
|
||||
* Euler angle with the `ZYX` order.
|
||||
*
|
||||
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
|
||||
*/
|
||||
private _rotationY: float = 0;
|
||||
private _customCenterZ: float = 0;
|
||||
private static _temporaryVector = new THREE.Vector3();
|
||||
|
||||
constructor(
|
||||
parent: gdjs.RuntimeInstanceContainer,
|
||||
objectData: Object3DData & CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this._renderer.reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected _createRender() {
|
||||
const parent = this._runtimeScene;
|
||||
return new gdjs.CustomRuntimeObject3DRenderer(
|
||||
this,
|
||||
this._instanceContainer,
|
||||
parent
|
||||
);
|
||||
}
|
||||
|
||||
protected _reinitializeRenderer(): void {
|
||||
this.getRenderer().reinitialize(this, this.getParent());
|
||||
}
|
||||
|
||||
getRenderer(): gdjs.CustomRuntimeObject3DRenderer {
|
||||
return super.getRenderer() as gdjs.CustomRuntimeObject3DRenderer;
|
||||
}
|
||||
|
||||
get3DRendererObject() {
|
||||
// It can't be null because Three.js is always loaded
|
||||
// when a custom 3D object is used.
|
||||
return this.getRenderer().get3DRendererObject()!;
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
||||
super.extraInitializationFromInitialInstance(initialInstanceData);
|
||||
if (initialInstanceData.depth !== undefined)
|
||||
this.setDepth(initialInstanceData.depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object position on the Z axis.
|
||||
*/
|
||||
setZ(z: float): void {
|
||||
if (z === this._z) return;
|
||||
this._z = z;
|
||||
this.getRenderer().updatePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object position on the Z axis.
|
||||
*/
|
||||
getZ(): float {
|
||||
return this._z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Z position of the rendered object.
|
||||
*
|
||||
* For most objects, this will returns the same value as getZ(). But if the
|
||||
* object has an origin that is not the same as the point (0,0,0) of the
|
||||
* object displayed, getDrawableZ will differ.
|
||||
*
|
||||
* @return The Z position of the rendered object.
|
||||
*/
|
||||
getDrawableZ(): float {
|
||||
if (this._isUntransformedHitBoxesDirty) {
|
||||
this._updateUntransformedHitBoxes();
|
||||
}
|
||||
return this._minZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Z position of the object center, **relative to the object Z
|
||||
* position** (`getDrawableX`).
|
||||
*
|
||||
* Use `getCenterZInScene` to get the position of the center in the scene.
|
||||
*
|
||||
* @return the Z position of the object center, relative to
|
||||
* `getDrawableZ()`.
|
||||
*/
|
||||
getCenterZ(): float {
|
||||
return this.getDepth() / 2;
|
||||
}
|
||||
|
||||
getCenterZInScene(): float {
|
||||
return this.getDrawableZ() + this.getCenterZ();
|
||||
}
|
||||
|
||||
setCenterZInScene(z: float): void {
|
||||
this.setZ(z + this._z - (this.getDrawableZ() + this.getCenterZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bottom Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMinZ(): number {
|
||||
return this.getDrawableZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.getDrawableZ() + this.getDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object rotation on the X axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
setRotationX(angle: float): void {
|
||||
this._rotationX = angle;
|
||||
this.getRenderer().updateRotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object rotation on the Y axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
setRotationY(angle: float): void {
|
||||
this._rotationY = angle;
|
||||
this.getRenderer().updateRotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object rotation on the X axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
getRotationX(): float {
|
||||
return this._rotationX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object rotation on the Y axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
getRotationY(): float {
|
||||
return this._rotationY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the object around the scene x axis at its center.
|
||||
* @param deltaAngle the rotation angle
|
||||
*/
|
||||
turnAroundX(deltaAngle: float): void {
|
||||
const axisX = gdjs.CustomRuntimeObject3D._temporaryVector;
|
||||
axisX.set(1, 0, 0);
|
||||
|
||||
const mesh = this.get3DRendererObject();
|
||||
mesh.rotateOnWorldAxis(axisX, gdjs.toRad(deltaAngle));
|
||||
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
||||
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the object around the scene y axis at its center.
|
||||
* @param deltaAngle the rotation angle
|
||||
*/
|
||||
turnAroundY(deltaAngle: float): void {
|
||||
const axisY = gdjs.CustomRuntimeObject3D._temporaryVector;
|
||||
axisY.set(0, 1, 0);
|
||||
|
||||
const mesh = this.get3DRendererObject();
|
||||
mesh.rotateOnWorldAxis(axisY, gdjs.toRad(deltaAngle));
|
||||
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
||||
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the object around the scene z axis at its center.
|
||||
* @param deltaAngle the rotation angle
|
||||
*/
|
||||
turnAroundZ(deltaAngle: float): void {
|
||||
const axisZ = gdjs.CustomRuntimeObject3D._temporaryVector;
|
||||
axisZ.set(0, 0, 1);
|
||||
|
||||
const mesh = this.get3DRendererObject();
|
||||
mesh.rotateOnWorldAxis(axisZ, gdjs.toRad(deltaAngle));
|
||||
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
||||
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the internal width of the object according to its children.
|
||||
*/
|
||||
getUnscaledDepth(): float {
|
||||
if (this._isUntransformedHitBoxesDirty) {
|
||||
this._updateUntransformedHitBoxes();
|
||||
}
|
||||
return this._maxZ - this._minZ;
|
||||
}
|
||||
|
||||
_updateUntransformedHitBoxes(): void {
|
||||
super._updateUntransformedHitBoxes();
|
||||
|
||||
let minZ = Number.MAX_VALUE;
|
||||
let maxZ = -Number.MAX_VALUE;
|
||||
for (const childInstance of this._instanceContainer.getAdhocListOfAllInstances()) {
|
||||
if (!childInstance.isIncludedInParentCollisionMask()) {
|
||||
continue;
|
||||
}
|
||||
if (!gdjs.Base3DHandler.is3D(childInstance)) {
|
||||
continue;
|
||||
}
|
||||
minZ = Math.min(minZ, childInstance.getUnrotatedAABBMinZ());
|
||||
maxZ = Math.max(maxZ, childInstance.getUnrotatedAABBMaxZ());
|
||||
}
|
||||
if (minZ === Number.MAX_VALUE) {
|
||||
// The unscaled size can't be 0 because setWidth and setHeight wouldn't
|
||||
// have any effect.
|
||||
minZ = 0;
|
||||
maxZ = 1;
|
||||
}
|
||||
this._minZ = minZ;
|
||||
this._maxZ = maxZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the center Z from the local origin (0;0).
|
||||
*/
|
||||
getUnscaledCenterZ(): float {
|
||||
if (this.hasCustomRotationCenter()) {
|
||||
return this._customCenterZ;
|
||||
}
|
||||
if (this._isUntransformedHitBoxesDirty) {
|
||||
this._updateUntransformedHitBoxes();
|
||||
}
|
||||
return (this._minZ + this._maxZ) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* The center of rotation is defined relatively to the origin (the object
|
||||
* position).
|
||||
* This avoids the center to move when children push the bounds.
|
||||
*
|
||||
* When no custom center is defined, it will move
|
||||
* to stay at the center of the children bounds.
|
||||
*
|
||||
* @param x coordinate of the custom center
|
||||
* @param y coordinate of the custom center
|
||||
*/
|
||||
setRotationCenter3D(x: float, y: float, z: float) {
|
||||
this._customCenterZ = z;
|
||||
this.setRotationCenter(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object size on the Z axis (called "depth").
|
||||
*/
|
||||
getDepth(): float {
|
||||
return this.getUnscaledDepth() * this.getScaleZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object size on the Z axis (called "depth").
|
||||
*/
|
||||
setDepth(depth: float): void {
|
||||
const unscaledDepth = this.getUnscaledDepth();
|
||||
if (unscaledDepth !== 0) {
|
||||
this.setScaleZ(depth / unscaledDepth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on X, Y and Z axis of the object.
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScale(newScale: number): void {
|
||||
super.setScale(newScale);
|
||||
this.setScaleZ(newScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on Z axis of the object (changing its height).
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScaleZ(newScale: number): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleZ)) {
|
||||
return;
|
||||
}
|
||||
this._scaleZ = newScale * (this._flippedZ ? -1 : 1);
|
||||
this.getRenderer().updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
|
||||
*
|
||||
* @return the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
|
||||
*/
|
||||
getScale(): number {
|
||||
const scaleX = this.getScaleX();
|
||||
const scaleY = this.getScaleY();
|
||||
const scaleZ = this.getScaleZ();
|
||||
return scaleX === scaleY && scaleX === scaleZ
|
||||
? scaleX
|
||||
: Math.pow(scaleX * scaleY * scaleZ, 1 / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object on Z axis.
|
||||
*
|
||||
* @return the scale of the object on Z axis
|
||||
*/
|
||||
getScaleZ(): float {
|
||||
return Math.abs(this._scaleZ);
|
||||
}
|
||||
|
||||
flipZ(enable: boolean) {
|
||||
if (enable === this._flippedZ) {
|
||||
return;
|
||||
}
|
||||
this._flippedZ = enable;
|
||||
this.getRenderer().updateSize();
|
||||
}
|
||||
|
||||
isFlippedZ(): boolean {
|
||||
return this._flippedZ;
|
||||
}
|
||||
}
|
||||
}
|
135
Extensions/3D/CustomRuntimeObject3DRenderer.ts
Normal file
135
Extensions/3D/CustomRuntimeObject3DRenderer.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
namespace gdjs {
|
||||
/**
|
||||
* The renderer for a {@link gdjs.CustomRuntimeObject3D} using Three.js.
|
||||
*/
|
||||
export class CustomRuntimeObject3DRenderer
|
||||
implements gdjs.RuntimeInstanceContainerRenderer {
|
||||
_object: gdjs.CustomRuntimeObject3D;
|
||||
_instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer;
|
||||
_isContainerDirty: boolean = true;
|
||||
_threeGroup: THREE.Group;
|
||||
|
||||
constructor(
|
||||
object: gdjs.CustomRuntimeObject3D,
|
||||
instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer,
|
||||
parent: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = object;
|
||||
this._instanceContainer = instanceContainer;
|
||||
|
||||
this._threeGroup = new THREE.Group();
|
||||
this._threeGroup.rotation.order = 'ZYX';
|
||||
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
get3DRendererObject(): THREE.Object3D {
|
||||
return this._threeGroup;
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
reinitialize(
|
||||
object: gdjs.CustomRuntimeObject3D,
|
||||
parent: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
_updateThreeGroup() {
|
||||
const threeObject3D = this.get3DRendererObject();
|
||||
|
||||
const scaleX = this._object.getScaleX();
|
||||
const scaleY = this._object.getScaleY();
|
||||
const scaleZ = this._object.getScaleZ();
|
||||
const pivotX = this._object.getUnscaledCenterX() * scaleX;
|
||||
const pivotY = this._object.getUnscaledCenterY() * scaleY;
|
||||
const pivotZ = this._object.getUnscaledCenterZ() * scaleZ;
|
||||
|
||||
threeObject3D.rotation.set(
|
||||
gdjs.toRad(this._object.getRotationX()),
|
||||
gdjs.toRad(this._object.getRotationY()),
|
||||
gdjs.toRad(this._object.angle)
|
||||
);
|
||||
|
||||
threeObject3D.position.set(
|
||||
this._object.isFlippedX() ? pivotX : -pivotX,
|
||||
this._object.isFlippedY() ? pivotY : -pivotY,
|
||||
this._object.isFlippedZ() ? pivotZ : -pivotZ
|
||||
);
|
||||
threeObject3D.position.applyEuler(threeObject3D.rotation);
|
||||
threeObject3D.position.x += this._object.getX() + pivotX;
|
||||
threeObject3D.position.y += this._object.getY() + pivotY;
|
||||
threeObject3D.position.z += this._object.getZ() + pivotZ;
|
||||
|
||||
threeObject3D.scale.set(
|
||||
this._object.isFlippedX() ? -scaleX : scaleX,
|
||||
this._object.isFlippedY() ? -scaleY : scaleY,
|
||||
this._object.isFlippedZ() ? -scaleZ : scaleZ
|
||||
);
|
||||
|
||||
threeObject3D.visible = !this._object.hidden;
|
||||
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to make sure the object is ready to be rendered.
|
||||
*/
|
||||
ensureUpToDate() {
|
||||
if (this._isContainerDirty) {
|
||||
this._updateThreeGroup();
|
||||
}
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateX(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateY(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateAngle(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateRotation() {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateSize() {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateVisibility(): void {
|
||||
this._threeGroup.visible = !this._object.hidden;
|
||||
}
|
||||
|
||||
updateOpacity(): void {
|
||||
// Opacity is not handled by 3D custom objects.
|
||||
}
|
||||
|
||||
setLayerIndex(layer: gdjs.RuntimeLayer, index: float): void {
|
||||
// Layers are not handled for 3D custom objects.
|
||||
}
|
||||
}
|
||||
}
|
74
Extensions/3D/ExposureEffect.ts
Normal file
74
Extensions/3D/ExposureEffect.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::Exposure',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.ShaderPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.ExposureShader
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'exposure') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'exposure') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
80
Extensions/3D/HueAndSaturationEffect.ts
Normal file
80
Extensions/3D/HueAndSaturationEffect.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::HueAndSaturation',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.ShaderPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.HueSaturationShader
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'hue') {
|
||||
this.shaderPass.uniforms[parameterName].value = value / 180;
|
||||
}
|
||||
if (parameterName === 'saturation') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'hue') {
|
||||
return this.shaderPass.uniforms[parameterName].value * 180;
|
||||
}
|
||||
if (parameterName === 'saturation') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
@@ -247,7 +247,7 @@ module.exports = {
|
||||
'Model3DObject',
|
||||
_('3D Model'),
|
||||
_('An animated 3D model.'),
|
||||
'JsPlatform/Extensions/3d_box.svg',
|
||||
'JsPlatform/Extensions/3d_model.svg',
|
||||
new gd.Model3DObjectConfiguration()
|
||||
)
|
||||
.setCategoryFullName(_('General'))
|
||||
@@ -1939,6 +1939,102 @@ module.exports = {
|
||||
.setType('number')
|
||||
.setGroup(_('Orientation'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('HueAndSaturation')
|
||||
.setFullName(_('Hue and saturation'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Adjust hue and saturation.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/HueAndSaturationEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('hue')
|
||||
.setValue('0')
|
||||
.setLabel(_('Hue in degrees (between -180 and 180)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('saturation')
|
||||
.setValue('0')
|
||||
.setLabel(_('Saturation (between -1 and 1)'))
|
||||
.setType('number');
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('Exposure')
|
||||
.setFullName(_('Exposure'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Adjust exposure.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/ExposureEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('exposure')
|
||||
.setValue('1')
|
||||
.setLabel(_('Exposure (positive value)'))
|
||||
.setType('number');
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('Bloom')
|
||||
.setFullName(_('Bloom'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Apply a bloom effect.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/BloomEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('strength')
|
||||
.setValue('1')
|
||||
.setLabel(_('Strength (between 0 and 3)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('radius')
|
||||
.setValue('0')
|
||||
.setLabel(_('Radius (between 0 and 1)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('threshold')
|
||||
.setValue('0')
|
||||
.setLabel(_('Threshold (between 0 and 1)'))
|
||||
.setType('number');
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('BrightnessAndContrast')
|
||||
.setFullName(_('Brightness and contrast'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Adjust brightness and contrast.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/BrightnessAndContrastEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('brightness')
|
||||
.setValue('0')
|
||||
.setLabel(_('Brightness (between -1 and 1)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('contrast')
|
||||
.setValue('0')
|
||||
.setLabel(_('Contrast (between -1 and 1)'))
|
||||
.setType('number');
|
||||
}
|
||||
// Don't forget to update the alert condition in Model3DEditor.js when
|
||||
// adding a new light.
|
||||
|
||||
@@ -2624,6 +2720,8 @@ module.exports = {
|
||||
RenderedCube3DObject3DInstance
|
||||
);
|
||||
|
||||
const epsilon = 1 / (1 << 16);
|
||||
|
||||
class Model3DRendered2DInstance extends RenderedInstance {
|
||||
_modelOriginPoint = [0, 0, 0];
|
||||
|
||||
@@ -2695,7 +2793,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/3d_box.svg';
|
||||
return 'JsPlatform/Extensions/3d_model.svg';
|
||||
}
|
||||
|
||||
getOriginX() {
|
||||
@@ -2736,6 +2834,9 @@ module.exports = {
|
||||
originalDepth,
|
||||
keepAspectRatio
|
||||
) {
|
||||
// These formulas are also used in:
|
||||
// - gdjs.Model3DRuntimeObject3DRenderer._updateDefaultTransformation
|
||||
// - Model3DEditor.modelSize
|
||||
threeObject.rotation.set(
|
||||
(rotationX * Math.PI) / 180,
|
||||
(rotationY * Math.PI) / 180,
|
||||
@@ -2743,13 +2844,24 @@ module.exports = {
|
||||
);
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
const shouldKeepModelOrigin = !this._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
// Keep the origin as part of the model.
|
||||
// For instance, a model can be 1 face of a cube and we want to keep the
|
||||
// inside as part of the object even if it's just void.
|
||||
// It also avoids to have the origin outside of the object box.
|
||||
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
const modelWidth = boundingBox.max.x - boundingBox.min.x;
|
||||
const modelHeight = boundingBox.max.y - boundingBox.min.y;
|
||||
const modelDepth = boundingBox.max.z - boundingBox.min.z;
|
||||
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
|
||||
this._modelOriginPoint[0] =
|
||||
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] =
|
||||
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] =
|
||||
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
|
||||
|
||||
// The model is flipped on Y axis.
|
||||
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
|
||||
@@ -2758,19 +2870,10 @@ module.exports = {
|
||||
const centerPoint = this._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
-(
|
||||
boundingBox.min.x +
|
||||
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
|
||||
),
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(
|
||||
boundingBox.min.y +
|
||||
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
|
||||
),
|
||||
-(
|
||||
boundingBox.min.z +
|
||||
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
|
||||
)
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
-(boundingBox.min.z + modelDepth * centerPoint[2])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2783,9 +2886,9 @@ module.exports = {
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
const scaleX = 1 / modelWidth;
|
||||
const scaleY = 1 / modelHeight;
|
||||
const scaleZ = 1 / modelDepth;
|
||||
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
|
||||
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
|
||||
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
|
||||
|
||||
const scaleMatrix = new THREE.Matrix4();
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
@@ -2796,10 +2899,22 @@ module.exports = {
|
||||
|
||||
if (keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio = originalWidth / modelWidth;
|
||||
const heightRatio = originalHeight / modelHeight;
|
||||
const depthRatio = originalDepth / modelDepth;
|
||||
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(scaleRatio)) {
|
||||
scaleRatio = 1;
|
||||
}
|
||||
|
||||
this._defaultWidth = scaleRatio * modelWidth;
|
||||
this._defaultHeight = scaleRatio * modelHeight;
|
||||
@@ -2951,6 +3066,11 @@ module.exports = {
|
||||
return this.getHeight() * originPoint[1];
|
||||
}
|
||||
|
||||
getOriginZ() {
|
||||
const originPoint = this.getOriginPoint();
|
||||
return this.getDepth() * originPoint[2];
|
||||
}
|
||||
|
||||
getCenterX() {
|
||||
const centerPoint = this.getCenterPoint();
|
||||
return this.getWidth() * centerPoint[0];
|
||||
@@ -2961,6 +3081,11 @@ module.exports = {
|
||||
return this.getHeight() * centerPoint[1];
|
||||
}
|
||||
|
||||
getCenterZ() {
|
||||
const centerPoint = this.getCenterPoint();
|
||||
return this.getDepth() * centerPoint[2];
|
||||
}
|
||||
|
||||
getOriginPoint() {
|
||||
return this._originPoint || this._modelOriginPoint;
|
||||
}
|
||||
@@ -2986,13 +3111,25 @@ module.exports = {
|
||||
);
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
|
||||
const shouldKeepModelOrigin = !this._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
// Keep the origin as part of the model.
|
||||
// For instance, a model can be 1 face of a cube and we want to keep the
|
||||
// inside as part of the object even if it's just void.
|
||||
// It also avoids to have the origin outside of the object box.
|
||||
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
const modelWidth = boundingBox.max.x - boundingBox.min.x;
|
||||
const modelHeight = boundingBox.max.y - boundingBox.min.y;
|
||||
const modelDepth = boundingBox.max.z - boundingBox.min.z;
|
||||
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
|
||||
this._modelOriginPoint[0] =
|
||||
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] =
|
||||
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] =
|
||||
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
|
||||
|
||||
// The model is flipped on Y axis.
|
||||
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
|
||||
@@ -3001,19 +3138,10 @@ module.exports = {
|
||||
const centerPoint = this._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
-(
|
||||
boundingBox.min.x +
|
||||
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
|
||||
),
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(
|
||||
boundingBox.min.y +
|
||||
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
|
||||
),
|
||||
-(
|
||||
boundingBox.min.z +
|
||||
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
|
||||
)
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
-(boundingBox.min.z + modelDepth * centerPoint[2])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3026,9 +3154,9 @@ module.exports = {
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
const scaleX = 1 / modelWidth;
|
||||
const scaleY = 1 / modelHeight;
|
||||
const scaleZ = 1 / modelDepth;
|
||||
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
|
||||
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
|
||||
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
|
||||
|
||||
const scaleMatrix = new THREE.Matrix4();
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
@@ -3039,10 +3167,22 @@ module.exports = {
|
||||
|
||||
if (keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio = originalWidth / modelWidth;
|
||||
const heightRatio = originalHeight / modelHeight;
|
||||
const depthRatio = originalDepth / modelDepth;
|
||||
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(scaleRatio)) {
|
||||
scaleRatio = 1;
|
||||
}
|
||||
|
||||
this._defaultWidth = scaleRatio * modelWidth;
|
||||
this._defaultHeight = scaleRatio * modelHeight;
|
||||
|
@@ -1,6 +1,8 @@
|
||||
namespace gdjs {
|
||||
type FloatPoint3D = [float, float, float];
|
||||
|
||||
const epsilon = 1 / (1 << 16);
|
||||
|
||||
const removeMetalness = (material: THREE.Material): void => {
|
||||
//@ts-ignore
|
||||
if (material.metalness) {
|
||||
@@ -9,7 +11,7 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const removeMetalnessFromMesh = (node: THREE.Object3D<THREE.Event>) => {
|
||||
const removeMetalnessFromMesh = (node: THREE.Object3D) => {
|
||||
const mesh = node as THREE.Mesh;
|
||||
if (!mesh.material) {
|
||||
return;
|
||||
@@ -23,9 +25,8 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const traverseToRemoveMetalnessFromMeshes = (
|
||||
node: THREE.Object3D<THREE.Event>
|
||||
) => node.traverse(removeMetalnessFromMesh);
|
||||
const traverseToRemoveMetalnessFromMeshes = (node: THREE.Object3D) =>
|
||||
node.traverse(removeMetalnessFromMesh);
|
||||
|
||||
const convertToBasicMaterial = (
|
||||
material: THREE.Material
|
||||
@@ -44,7 +45,7 @@ namespace gdjs {
|
||||
return basicMaterial;
|
||||
};
|
||||
|
||||
const setBasicMaterialTo = (node: THREE.Object3D<THREE.Event>): void => {
|
||||
const setBasicMaterialTo = (node: THREE.Object3D): void => {
|
||||
const mesh = node as THREE.Mesh;
|
||||
if (!mesh.material) {
|
||||
return;
|
||||
@@ -59,9 +60,8 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const traverseToSetBasicMaterialFromMeshes = (
|
||||
node: THREE.Object3D<THREE.Event>
|
||||
) => node.traverse(setBasicMaterialTo);
|
||||
const traverseToSetBasicMaterialFromMeshes = (node: THREE.Object3D) =>
|
||||
node.traverse(setBasicMaterialTo);
|
||||
|
||||
class Model3DRuntimeObject3DRenderer extends gdjs.RuntimeObject3DRenderer {
|
||||
private _model3DRuntimeObject: gdjs.Model3DRuntimeObject;
|
||||
@@ -147,6 +147,9 @@ namespace gdjs {
|
||||
originalDepth: float,
|
||||
keepAspectRatio: boolean
|
||||
) {
|
||||
// These formulas are also used in:
|
||||
// - Model3DEditor.modelSize
|
||||
// - Model3DRendered2DInstance
|
||||
threeObject.rotation.set(
|
||||
gdjs.toRad(rotationX),
|
||||
gdjs.toRad(rotationY),
|
||||
@@ -155,12 +158,24 @@ namespace gdjs {
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
|
||||
const shouldKeepModelOrigin = !this._model3DRuntimeObject._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
// Keep the origin as part of the model.
|
||||
// For instance, a model can be 1 face of a cube and we want to keep the
|
||||
// inside as part of the object even if it's just void.
|
||||
// It also avoids to have the origin outside of the object box.
|
||||
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
const modelWidth = boundingBox.max.x - boundingBox.min.x;
|
||||
const modelHeight = boundingBox.max.y - boundingBox.min.y;
|
||||
const modelDepth = boundingBox.max.z - boundingBox.min.z;
|
||||
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
|
||||
this._modelOriginPoint[0] =
|
||||
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] =
|
||||
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] =
|
||||
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
|
||||
|
||||
// The model is flipped on Y axis.
|
||||
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
|
||||
@@ -169,19 +184,10 @@ namespace gdjs {
|
||||
const centerPoint = this._model3DRuntimeObject._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
-(
|
||||
boundingBox.min.x +
|
||||
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
|
||||
),
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(
|
||||
boundingBox.min.y +
|
||||
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
|
||||
),
|
||||
-(
|
||||
boundingBox.min.z +
|
||||
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
|
||||
)
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
-(boundingBox.min.z + modelDepth * centerPoint[2])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,9 +200,9 @@ namespace gdjs {
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
const scaleX = 1 / modelWidth;
|
||||
const scaleY = 1 / modelHeight;
|
||||
const scaleZ = 1 / modelDepth;
|
||||
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
|
||||
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
|
||||
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
|
||||
|
||||
const scaleMatrix = new THREE.Matrix4();
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
@@ -207,10 +213,22 @@ namespace gdjs {
|
||||
|
||||
if (keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio = originalWidth / modelWidth;
|
||||
const heightRatio = originalHeight / modelHeight;
|
||||
const depthRatio = originalDepth / modelDepth;
|
||||
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(scaleRatio)) {
|
||||
scaleRatio = 1;
|
||||
}
|
||||
|
||||
this._object._setOriginalWidth(scaleRatio * modelWidth);
|
||||
this._object._setOriginalHeight(scaleRatio * modelHeight);
|
||||
|
@@ -11,7 +11,11 @@ namespace gdjs {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const layerRenderer = layer.getRenderer();
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
const fov = threeCamera ? threeCamera.fov : assumedFovIn2D;
|
||||
const fov = threeCamera
|
||||
? threeCamera instanceof THREE.OrthographicCamera
|
||||
? null
|
||||
: threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
return layer.getCameraZ(fov, cameraIndex);
|
||||
};
|
||||
|
||||
@@ -24,7 +28,11 @@ namespace gdjs {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const layerRenderer = layer.getRenderer();
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
const fov = threeCamera ? threeCamera.fov : assumedFovIn2D;
|
||||
const fov = threeCamera
|
||||
? threeCamera instanceof THREE.OrthographicCamera
|
||||
? null
|
||||
: threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
layer.setCameraZ(z, fov, cameraIndex);
|
||||
};
|
||||
|
||||
@@ -213,8 +221,11 @@ namespace gdjs {
|
||||
const layerRenderer = layer.getRenderer();
|
||||
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
if (!threeCamera) return 45;
|
||||
return threeCamera.fov;
|
||||
return threeCamera
|
||||
? threeCamera instanceof THREE.OrthographicCamera
|
||||
? 0
|
||||
: threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
};
|
||||
|
||||
export const setFov = (
|
||||
@@ -227,7 +238,8 @@ namespace gdjs {
|
||||
const layerRenderer = layer.getRenderer();
|
||||
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
if (!threeCamera) return;
|
||||
if (!threeCamera || threeCamera instanceof THREE.OrthographicCamera)
|
||||
return;
|
||||
|
||||
threeCamera.fov = Math.min(Math.max(angle, 0), 180);
|
||||
layerRenderer.setThreeCameraDirty(true);
|
||||
|
@@ -436,6 +436,21 @@ module.exports = {
|
||||
|
||||
addSettersAndGettersToObject(object, setterAndGetterProperties, 'BBText');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
`SetFontFamily2`,
|
||||
_('Font family'),
|
||||
_('Set font family'),
|
||||
_('Set the font of _PARAM0_ to _PARAM1_'),
|
||||
'',
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font24.png'
|
||||
)
|
||||
.addParameter('object', 'BBText', 'BBText', false)
|
||||
.addParameter('fontResource', _('Font family'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName(`setFontFamily`);
|
||||
|
||||
const actions = object.getAllActions();
|
||||
const conditions = object.getAllConditions();
|
||||
const expressions = object.getAllExpressions();
|
||||
@@ -443,6 +458,9 @@ module.exports = {
|
||||
actions.get('BBText::SetOpacity').setHidden();
|
||||
conditions.get('BBText::IsOpacity').setHidden();
|
||||
expressions.get('GetOpacity').setHidden();
|
||||
// Action deprecated because it's using the `string` type instead of the more
|
||||
// user-friendly `fontResource` type.
|
||||
actions.get('BBText::SetFontFamily').setHidden();
|
||||
|
||||
return extension;
|
||||
},
|
||||
|
@@ -141,6 +141,7 @@ namespace gdjs {
|
||||
setBBText(text): void {
|
||||
this._text = text;
|
||||
this._renderer.updateText();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -26,6 +26,7 @@ set(
|
||||
TextEntryObject
|
||||
TextObject
|
||||
TiledSpriteObject
|
||||
Spine
|
||||
TopDownMovementBehavior)
|
||||
|
||||
# Automatically add all listed extensions
|
||||
|
@@ -3,6 +3,7 @@ GDevelop - LinkedObjects Extension
|
||||
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('LinkedObjects');
|
||||
/**
|
||||
* Manages the links between objects.
|
||||
*/
|
||||
@@ -114,7 +115,14 @@ namespace gdjs {
|
||||
if (this._links.has(linkedObject.id)) {
|
||||
const otherObjList = this._links
|
||||
.get(linkedObject.id)!
|
||||
.linkedObjectMap.get(removedObject.getName())!;
|
||||
.linkedObjectMap.get(removedObject.getName());
|
||||
|
||||
if (!otherObjList) {
|
||||
logger.error(
|
||||
`Can't find link from ${linkedObject.id} (${linkedObject.name}) to ${removedObject.id} (${removedObject.name})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = otherObjList.indexOf(removedObject);
|
||||
if (index !== -1) {
|
||||
|
@@ -24,58 +24,41 @@ namespace gdjs {
|
||||
runtimeObject: gdjs.RuntimeObject,
|
||||
objectData: any
|
||||
) {
|
||||
let texture = null;
|
||||
const graphics = new PIXI.Graphics();
|
||||
graphics.lineStyle(0, 0, 0);
|
||||
graphics.beginFill(gdjs.rgbToHexNumber(255, 255, 255), 1);
|
||||
if (objectData.rendererType === 'Point') {
|
||||
graphics.drawCircle(0, 0, objectData.rendererParam1);
|
||||
} else if (objectData.rendererType === 'Line') {
|
||||
graphics.drawRect(
|
||||
0,
|
||||
0,
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2
|
||||
);
|
||||
|
||||
// Draw an almost-invisible rectangle in the left hand to force PIXI to take a full texture with our line at the right hand
|
||||
graphics.beginFill(gdjs.rgbToHexNumber(255, 255, 255), 0.001);
|
||||
graphics.drawRect(
|
||||
0,
|
||||
0,
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2
|
||||
);
|
||||
} else if (objectData.textureParticleName) {
|
||||
const sprite = new PIXI.Sprite(
|
||||
(instanceContainer
|
||||
.getGame()
|
||||
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
|
||||
objectData.textureParticleName
|
||||
)
|
||||
);
|
||||
sprite.width = objectData.rendererParam1;
|
||||
sprite.height = objectData.rendererParam2;
|
||||
graphics.addChild(sprite);
|
||||
} else {
|
||||
graphics.drawRect(
|
||||
0,
|
||||
0,
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2
|
||||
);
|
||||
}
|
||||
graphics.endFill();
|
||||
|
||||
// Render the texture from graphics using the PIXI Renderer.
|
||||
// TODO: could be optimized by generating the texture only once per object type,
|
||||
// instead of at each object creation.
|
||||
const pixiRenderer = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getPIXIRenderer();
|
||||
//@ts-expect-error Pixi has wrong type definitions for this method
|
||||
texture = pixiRenderer.generateTexture(graphics);
|
||||
const imageManager = instanceContainer
|
||||
.getGame()
|
||||
.getImageManager() as gdjs.PixiImageManager;
|
||||
let particleTexture: PIXI.Texture = PIXI.Texture.WHITE;
|
||||
if (pixiRenderer) {
|
||||
if (objectData.rendererType === 'Point') {
|
||||
particleTexture = imageManager.getOrCreateDiskTexture(
|
||||
objectData.rendererParam1,
|
||||
pixiRenderer
|
||||
);
|
||||
} else if (objectData.rendererType === 'Line') {
|
||||
particleTexture = imageManager.getOrCreateRectangleTexture(
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2,
|
||||
pixiRenderer
|
||||
);
|
||||
} else if (objectData.textureParticleName) {
|
||||
particleTexture = imageManager.getOrCreateScaledTexture(
|
||||
objectData.textureParticleName,
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2,
|
||||
pixiRenderer
|
||||
);
|
||||
} else {
|
||||
particleTexture = imageManager.getOrCreateRectangleTexture(
|
||||
objectData.rendererParam1,
|
||||
objectData.rendererParam2,
|
||||
pixiRenderer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const configuration = {
|
||||
ease: undefined,
|
||||
@@ -198,7 +181,7 @@ namespace gdjs {
|
||||
{
|
||||
type: 'textureSingle',
|
||||
config: {
|
||||
texture: texture,
|
||||
texture: particleTexture,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -236,8 +219,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
update(delta: float): void {
|
||||
const wasEmitting = this.emitter.emit;
|
||||
this.emitter.update(delta);
|
||||
if (!this.started && this.getParticleCount() > 0) {
|
||||
if (!this.started && wasEmitting) {
|
||||
this.started = true;
|
||||
}
|
||||
}
|
||||
|
@@ -37,6 +37,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
.SetValue(behaviorContent.GetBoolAttribute("allowDiagonals") ? "true"
|
||||
: "false")
|
||||
.SetGroup(_("Path smoothing"))
|
||||
.SetAdvanced()
|
||||
.SetType("Boolean");
|
||||
properties["Acceleration"]
|
||||
.SetLabel(_("Acceleration"))
|
||||
@@ -99,6 +100,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
properties["ExtraBorder"]
|
||||
.SetDescription(_("Extra border size"))
|
||||
.SetGroup(_("Collision"))
|
||||
.SetAdvanced()
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetValue(
|
||||
@@ -108,6 +110,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("smoothingMaxCellGap")))
|
||||
.SetGroup(_("Path smoothing"))
|
||||
.SetAdvanced()
|
||||
.SetDescription(_("It's recommended to leave a max gap of 1 cell. "
|
||||
"Setting it to 0 disable the smoothing."));
|
||||
|
||||
|
@@ -150,7 +150,8 @@ PlatformerObjectBehavior::GetProperties(
|
||||
properties["UseLegacyTrajectory"]
|
||||
.SetLabel(_("Use frame rate dependent trajectories (deprecated, it's "
|
||||
"recommended to leave this unchecked)"))
|
||||
.SetGroup(_("Deprecated options (advanced)"))
|
||||
.SetGroup(_("Deprecated options"))
|
||||
.SetDeprecated()
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
|
||||
? "true"
|
||||
: "false")
|
||||
|
@@ -62,7 +62,7 @@ namespace gdjs {
|
||||
private _yGrabOffset: any;
|
||||
private _xGrabTolerance: any;
|
||||
|
||||
_useLegacyTrajectory: boolean = true;
|
||||
_useLegacyTrajectory: boolean;
|
||||
|
||||
_canGoDownFromJumpthru: boolean = false;
|
||||
|
||||
@@ -355,13 +355,25 @@ namespace gdjs {
|
||||
|
||||
private _updateSpeed(timeDelta: float): float {
|
||||
const previousSpeed = this._currentSpeed;
|
||||
//Change the speed according to the player's input.
|
||||
// @ts-ignore
|
||||
if (this._leftKey) {
|
||||
this._currentSpeed -= this._acceleration * timeDelta;
|
||||
}
|
||||
if (this._rightKey) {
|
||||
this._currentSpeed += this._acceleration * timeDelta;
|
||||
// Change the speed according to the player's input.
|
||||
// TODO Give priority to the last key for faster reaction time.
|
||||
if (this._leftKey !== this._rightKey) {
|
||||
if (this._leftKey) {
|
||||
if (this._currentSpeed <= 0) {
|
||||
this._currentSpeed -= this._acceleration * timeDelta;
|
||||
} else {
|
||||
// Turn back at least as fast as it would stop.
|
||||
this._currentSpeed -=
|
||||
Math.max(this._acceleration, this._deceleration) * timeDelta;
|
||||
}
|
||||
} else if (this._rightKey) {
|
||||
if (this._currentSpeed >= 0) {
|
||||
this._currentSpeed += this._acceleration * timeDelta;
|
||||
} else {
|
||||
this._currentSpeed +=
|
||||
Math.max(this._acceleration, this._deceleration) * timeDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Take deceleration into account only if no key is pressed.
|
||||
@@ -1680,13 +1692,15 @@ namespace gdjs {
|
||||
|
||||
beforeUpdatingObstacles(timeDelta: float) {
|
||||
const object = this._behavior.owner;
|
||||
//Stick the object to the floor if its height has changed.
|
||||
// Stick the object to the floor if its height has changed.
|
||||
if (this._oldHeight !== object.getHeight()) {
|
||||
object.setY(
|
||||
this._floorLastY -
|
||||
object.getHeight() +
|
||||
(object.getY() - object.getDrawableY())
|
||||
);
|
||||
// TODO This should probably be done after the events because
|
||||
// the character stays at the wrong place during 1 frame.
|
||||
const deltaY =
|
||||
((this._oldHeight - object.getHeight()) *
|
||||
(object.getHeight() + object.getDrawableY() - object.getY())) /
|
||||
object.getHeight();
|
||||
object.setY(object.getY() + deltaY);
|
||||
}
|
||||
// Directly follow the floor movement on the Y axis by moving the character.
|
||||
// For the X axis, we follow the floor movement using `_requestedDeltaX`
|
||||
|
@@ -501,6 +501,22 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('can stay on a rotated platform when its height changes', function () {
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
platform.setAngle(-45);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
const oldY = object.getY();
|
||||
expect(object.getY()).to.be.within(-40, -39);
|
||||
|
||||
object.setHeight(object.getHeight() - 8);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be(oldY + 8);
|
||||
});
|
||||
});
|
||||
|
||||
[0, 25].forEach((slopeMaxAngle) => {
|
||||
|
@@ -169,6 +169,23 @@ module.exports = {
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.getUsername');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'UserID',
|
||||
_('User ID'),
|
||||
_('Get the unique user ID of the authenticated player.'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.getUserId');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'IsPlayerAuthenticated',
|
||||
|
@@ -171,7 +171,7 @@ namespace gdjs {
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
return _userId || null;
|
||||
return _userId || '';
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -163,9 +163,9 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
obj.AddAction("Torus",
|
||||
_("Torus"),
|
||||
_("Draw a torus on screen"),
|
||||
_("Draw at _PARAM1_;_PARAM2_ a torus with inner radius"
|
||||
"_PARAM3_ and outer radius _PARAM4_ and "
|
||||
"with start arc _PARAM5_° and end arc _PARAM6_°"
|
||||
_("Draw at _PARAM1_;_PARAM2_ a torus with "
|
||||
"inner radius: _PARAM3_, outer radius: _PARAM4_ and "
|
||||
"with start arc angle: _PARAM5_°, end angle: _PARAM6_° "
|
||||
"with _PARAM0_"),
|
||||
_("Drawing"),
|
||||
"res/actions/torus24.png",
|
||||
|
20
Extensions/Spine/CMakeLists.txt
Normal file
20
Extensions/Spine/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
cmake_policy(SET CMP0015 NEW)
|
||||
|
||||
project(SpineObject)
|
||||
gd_add_extension_includes()
|
||||
|
||||
#Defines
|
||||
###
|
||||
gd_add_extension_definitions(SpineObject)
|
||||
|
||||
#The targets
|
||||
###
|
||||
include_directories(.)
|
||||
file(GLOB source_files *.cpp *.h)
|
||||
gd_add_clang_utils(SpineObject "${source_files}")
|
||||
gd_add_extension_target(SpineObject "${source_files}")
|
||||
|
||||
#Linker files for the IDE extension
|
||||
###
|
||||
gd_extension_link_libraries(SpineObject)
|
323
Extensions/Spine/JsExtension.js
Normal file
323
Extensions/Spine/JsExtension.js
Normal file
@@ -0,0 +1,323 @@
|
||||
// @flow
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
* ℹ️ Changes in this file are watched and automatically imported if the editor
|
||||
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
|
||||
*
|
||||
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
|
||||
* ⚠️ If you make a change and the extension is not loaded, open the developer console
|
||||
* and search for any errors.
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/*::
|
||||
// Import types to allow Flow to do static type checking on this file.
|
||||
// Extensions declaration are typed using Flow (like the editor), but the files
|
||||
// for the game engine are checked with TypeScript annotations.
|
||||
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'SpineObject',
|
||||
_('Spine (experimental)'),
|
||||
_('Displays a Spine animation.'),
|
||||
'Vladyslav Pohorielov',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/objects/spine')
|
||||
.setCategory('Advanced');
|
||||
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Spine'))
|
||||
.setIcon('JsPlatform/Extensions/spine.svg');
|
||||
|
||||
const object = extension
|
||||
.addObject(
|
||||
'SpineObject',
|
||||
_('Spine (experimental)'),
|
||||
_(
|
||||
'Display and smoothly animate a 2D object with skeletal animations made with Spine. Use files exported from Spine (json, atlas and image).'
|
||||
),
|
||||
'JsPlatform/Extensions/spine.svg',
|
||||
new gd.SpineObjectConfiguration()
|
||||
)
|
||||
.addDefaultBehavior('EffectCapability::EffectBehavior')
|
||||
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
|
||||
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
|
||||
.addDefaultBehavior('FlippableCapability::FlippableBehavior')
|
||||
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
|
||||
.addDefaultBehavior('AnimatableCapability::AnimatableBehavior')
|
||||
.setIncludeFile('Extensions/Spine/spineruntimeobject.js')
|
||||
.addIncludeFile('Extensions/Spine/spineruntimeobject-pixi-renderer.js')
|
||||
.addIncludeFile('Extensions/Spine/pixi-spine/pixi-spine.js')
|
||||
.addIncludeFile('Extensions/Spine/managers/pixi-spine-atlas-manager.js')
|
||||
.addIncludeFile('Extensions/Spine/managers/pixi-spine-manager.js')
|
||||
.setCategoryFullName(_('Advanced'));
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'Animation',
|
||||
_('Animation mixing duration'),
|
||||
_(
|
||||
'the duration of the smooth transition between 2 animations (in second)'
|
||||
),
|
||||
_('the animation mixing duration'),
|
||||
_('Animations and images'),
|
||||
'JsPlatform/Extensions/spine.svg'
|
||||
)
|
||||
.addParameter('object', _('Spine'), 'SpineObject')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setAnimationMixingDuration')
|
||||
.setGetter('getAnimationMixingDuration');
|
||||
|
||||
return extension;
|
||||
},
|
||||
|
||||
/**
|
||||
* You can optionally add sanity tests that will check the basic working
|
||||
* of your extension behaviors/objects by instantiating behaviors/objects
|
||||
* and setting the property to a given value.
|
||||
*
|
||||
* If you don't have any tests, you can simply return an empty array.
|
||||
*
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* Register editors for objects.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {},
|
||||
/**
|
||||
* Register renderers for instance of objects on the scene editor.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
const { PIXI, RenderedInstance, gd } = objectsRenderingService;
|
||||
|
||||
class RenderedSpineInstance extends RenderedInstance {
|
||||
_spine = null;
|
||||
_rect = new PIXI.Graphics();
|
||||
_initialWidth = null;
|
||||
_initialHeight = null;
|
||||
_animationIndex = -1;
|
||||
_spineOriginOffsetX = 0;
|
||||
_spineOriginOffsetY = 0;
|
||||
|
||||
constructor(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
) {
|
||||
super(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
// there is issue with spine selection. mouse events are not triggering during interaction.
|
||||
// create the invisible background rectangle to fill spine range.
|
||||
this._rect.alpha = 0;
|
||||
this._pixiObject = new PIXI.Container();
|
||||
this._pixiObject.addChild(this._rect);
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
|
||||
this._loadSpine();
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/spine.svg';
|
||||
}
|
||||
|
||||
update() {
|
||||
this._pixiObject.position.set(
|
||||
this._instance.getX(),
|
||||
this._instance.getY()
|
||||
);
|
||||
|
||||
this.setAnimation(this._instance.getRawDoubleProperty('animation'));
|
||||
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const { _spine: spine } = this;
|
||||
|
||||
if (spine) {
|
||||
spine.width = width;
|
||||
spine.height = height;
|
||||
const localBounds = spine.getLocalBounds(undefined, true);
|
||||
|
||||
this._spineOriginOffsetX = localBounds.x * spine.scale.x;
|
||||
this._spineOriginOffsetY = localBounds.y * spine.scale.y;
|
||||
this._rect.position.set(
|
||||
this._spineOriginOffsetX,
|
||||
this._spineOriginOffsetY
|
||||
);
|
||||
}
|
||||
|
||||
this._rect.clear();
|
||||
this._rect.beginFill(0xffffff);
|
||||
this._rect.lineStyle(1, 0xff0000);
|
||||
this._rect.drawRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns x coordinate of this spine origin offset
|
||||
*/
|
||||
getOriginX() {
|
||||
return -this._spineOriginOffsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns y coordinate of this spine origin offset
|
||||
*/
|
||||
getOriginY() {
|
||||
return -this._spineOriginOffsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index - animation index
|
||||
*/
|
||||
setAnimation(index) {
|
||||
const { _spine: spine } = this;
|
||||
const configuration = this._getConfiguration();
|
||||
|
||||
if (
|
||||
!spine ||
|
||||
configuration.hasNoAnimations() ||
|
||||
index === this._animationIndex
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Number.isInteger(index) || index < 0) {
|
||||
index = 0;
|
||||
} else if (configuration.getAnimationsCount() <= index) {
|
||||
index = configuration.getAnimationsCount() - 1;
|
||||
}
|
||||
|
||||
this._animationIndex = index;
|
||||
const animation = configuration.getAnimation(index);
|
||||
const source = animation.getSource();
|
||||
const shouldLoop = animation.shouldLoop();
|
||||
const scale = this.getScale();
|
||||
|
||||
// reset scale to track new animation range
|
||||
// if custom size is set it will be reinitialized in update method
|
||||
spine.scale.set(1, 1);
|
||||
spine.state.setAnimation(0, source, shouldLoop);
|
||||
spine.state.tracks[0].trackTime = 0;
|
||||
spine.update(0);
|
||||
spine.autoUpdate = false;
|
||||
this._initialWidth = spine.width * this.getScale();
|
||||
this._initialHeight = spine.height * this.getScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} default width
|
||||
*/
|
||||
getDefaultWidth() {
|
||||
return this._initialWidth !== null ? this._initialWidth : 256;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} default height
|
||||
*/
|
||||
getDefaultHeight() {
|
||||
return this._initialHeight !== null ? this._initialHeight : 256;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} defined scale
|
||||
*/
|
||||
getScale() {
|
||||
return Number(this._getProperties().get('scale').getValue()) || 1;
|
||||
}
|
||||
|
||||
onRemovedFromScene() {
|
||||
super.onRemovedFromScene();
|
||||
this._pixiObject.destroy({ children: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns this spine object configuration
|
||||
*/
|
||||
_getConfiguration() {
|
||||
return gd.asSpineConfiguration(this._associatedObjectConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns this object properties container
|
||||
*/
|
||||
_getProperties() {
|
||||
return this._associatedObjectConfiguration.getProperties();
|
||||
}
|
||||
|
||||
_loadSpine() {
|
||||
const properties = this._getProperties();
|
||||
const spineResourceName = properties
|
||||
.get('spineResourceName')
|
||||
.getValue();
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.getSpineData(this._project, spineResourceName)
|
||||
.then((spineDataOrLoadingError) => {
|
||||
if (!spineDataOrLoadingError.skeleton) {
|
||||
console.error(
|
||||
'Unable to load Spine (' +
|
||||
(spineDataOrLoadingError.loadingErrorReason ||
|
||||
'Unknown reason') +
|
||||
')',
|
||||
spineDataOrLoadingError.loadingError
|
||||
);
|
||||
this._spine = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._spine = new PIXI.Spine(spineDataOrLoadingError.skeleton);
|
||||
} catch (error) {
|
||||
console.error('Exception while loading Spine.', error);
|
||||
this._spine = null;
|
||||
return;
|
||||
}
|
||||
this._pixiObject.addChild(this._spine);
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
objectsRenderingService.registerInstanceRenderer(
|
||||
'SpineObject::SpineObject',
|
||||
RenderedSpineInstance
|
||||
);
|
||||
},
|
||||
};
|
165
Extensions/Spine/SpineObjectConfiguration.cpp
Normal file
165
Extensions/Spine/SpineObjectConfiguration.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
GDevelop - Spine Extension
|
||||
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "SpineObjectConfiguration.h"
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/Project/InitialInstance.h"
|
||||
#include "GDCore/Project/MeasurementUnit.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
SpineAnimation SpineObjectConfiguration::badAnimation;
|
||||
|
||||
SpineObjectConfiguration::SpineObjectConfiguration()
|
||||
: scale(1), spineResourceName("") {};
|
||||
|
||||
bool SpineObjectConfiguration::UpdateProperty(const gd::String &propertyName, const gd::String &newValue) {
|
||||
if (propertyName == "scale") {
|
||||
scale = newValue.To<double>();
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "spineResourceName") {
|
||||
spineResourceName = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
SpineObjectConfiguration::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> objectProperties;
|
||||
|
||||
objectProperties["scale"]
|
||||
.SetValue(gd::String::From(scale))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Scale"))
|
||||
.SetGroup(_("Default size"));
|
||||
|
||||
objectProperties["spineResourceName"]
|
||||
.SetValue(spineResourceName)
|
||||
.SetType("resource")
|
||||
.AddExtraInfo("spine")
|
||||
.SetLabel(_("Spine json"));
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
||||
bool SpineObjectConfiguration::UpdateInitialInstanceProperty(
|
||||
gd::InitialInstance &instance, const gd::String &propertyName,
|
||||
const gd::String &newValue, gd::Project &project, gd::Layout &layout
|
||||
) {
|
||||
if (propertyName == "animation") {
|
||||
instance.SetRawDoubleProperty("animation", std::max(0, newValue.empty() ? 0 : newValue.To<int>()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
SpineObjectConfiguration::GetInitialInstanceProperties(const gd::InitialInstance &instance, gd::Project &project, gd::Layout &layout) {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
properties["animation"] =
|
||||
gd::PropertyDescriptor(gd::String::From(instance.GetRawDoubleProperty("animation")))
|
||||
.SetLabel(_("Animation"))
|
||||
.SetType("number");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::DoUnserializeFrom(gd::Project &project, const gd::SerializerElement &element) {
|
||||
auto &content = element.GetChild("content");
|
||||
|
||||
scale = content.GetDoubleAttribute("scale");
|
||||
spineResourceName = content.GetStringAttribute("spineResourceName");
|
||||
|
||||
RemoveAllAnimations();
|
||||
auto &animationsElement = content.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
for (std::size_t i = 0; i < animationsElement.GetChildrenCount(); ++i) {
|
||||
auto &animationElement = animationsElement.GetChild(i);
|
||||
SpineAnimation animation;
|
||||
animation.SetName(animationElement.GetStringAttribute("name", ""));
|
||||
animation.SetSource(animationElement.GetStringAttribute("source", ""));
|
||||
animation.SetShouldLoop(animationElement.GetBoolAttribute("loop", false));
|
||||
AddAnimation(animation);
|
||||
}
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::DoSerializeTo(gd::SerializerElement &element) const {
|
||||
auto &content = element.AddChild("content");
|
||||
content.SetAttribute("scale", scale);
|
||||
content.SetAttribute("spineResourceName", spineResourceName);
|
||||
|
||||
auto &animationsElement = content.AddChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
for (auto &animation : animations) {
|
||||
auto &animationElement = animationsElement.AddChild("animation");
|
||||
animationElement.SetAttribute("name", animation.GetName());
|
||||
animationElement.SetAttribute("source", animation.GetSource());
|
||||
animationElement.SetAttribute("loop", animation.ShouldLoop());
|
||||
}
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker &worker) {
|
||||
worker.ExposeSpine(spineResourceName);
|
||||
worker.ExposeEmbeddeds(spineResourceName);
|
||||
}
|
||||
|
||||
const SpineAnimation &
|
||||
SpineObjectConfiguration::GetAnimation(std::size_t nb) const {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
SpineAnimation &SpineObjectConfiguration::GetAnimation(std::size_t nb) {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
bool SpineObjectConfiguration::HasAnimationNamed(const gd::String &name) const {
|
||||
return !name.empty() && (find_if(animations.begin(), animations.end(),
|
||||
[&name](const SpineAnimation &animation) {
|
||||
return animation.GetName() == name;
|
||||
}) != animations.end());
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::AddAnimation(const SpineAnimation &animation) {
|
||||
animations.push_back(animation);
|
||||
}
|
||||
|
||||
bool SpineObjectConfiguration::RemoveAnimation(std::size_t nb) {
|
||||
if (nb >= GetAnimationsCount())
|
||||
return false;
|
||||
|
||||
animations.erase(animations.begin() + nb);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::SwapAnimations(std::size_t firstIndex, std::size_t secondIndex) {
|
||||
if (firstIndex < animations.size() && secondIndex < animations.size() && firstIndex != secondIndex) {
|
||||
std::swap(animations[firstIndex], animations[secondIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::MoveAnimation(std::size_t oldIndex, std::size_t newIndex) {
|
||||
if (oldIndex >= animations.size() || newIndex >= animations.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto animation = animations[oldIndex];
|
||||
animations.erase(animations.begin() + oldIndex);
|
||||
animations.insert(animations.begin() + newIndex, animation);
|
||||
}
|
162
Extensions/Spine/SpineObjectConfiguration.h
Normal file
162
Extensions/Spine/SpineObjectConfiguration.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
GDevelop - Spine Extension
|
||||
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
namespace gd {
|
||||
class InitialInstance;
|
||||
class Project;
|
||||
} // namespace gd
|
||||
|
||||
class GD_EXTENSION_API SpineAnimation {
|
||||
public:
|
||||
SpineAnimation() : shouldLoop(false) {};
|
||||
virtual ~SpineAnimation(){};
|
||||
|
||||
/**
|
||||
* \brief Return the name of the animation
|
||||
*/
|
||||
const gd::String &GetName() const { return name; }
|
||||
|
||||
/**
|
||||
* \brief Change the name of the animation
|
||||
*/
|
||||
void SetName(const gd::String &name_) { name = name_; }
|
||||
|
||||
/**
|
||||
* \brief Return the name of the animation from the spine file.
|
||||
*/
|
||||
const gd::String &GetSource() const { return source; }
|
||||
|
||||
/**
|
||||
* \brief Change the name of the animation from the spine file.
|
||||
*/
|
||||
void SetSource(const gd::String &source_) { source = source_; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the animation should loop.
|
||||
*/
|
||||
const bool ShouldLoop() const { return shouldLoop; }
|
||||
|
||||
/**
|
||||
* \brief Change whether the animation should loop or not.
|
||||
*/
|
||||
void SetShouldLoop(bool shouldLoop_) { shouldLoop = shouldLoop_; }
|
||||
|
||||
private:
|
||||
gd::String name;
|
||||
gd::String source;
|
||||
bool shouldLoop;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Spine object configuration is used for storage and for the IDE.
|
||||
*/
|
||||
class GD_EXTENSION_API SpineObjectConfiguration : public gd::ObjectConfiguration {
|
||||
public:
|
||||
SpineObjectConfiguration();
|
||||
virtual ~SpineObjectConfiguration(){};
|
||||
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const override {
|
||||
return gd::make_unique<SpineObjectConfiguration>(*this);
|
||||
}
|
||||
|
||||
virtual void ExposeResources(gd::ArbitraryResourceWorker &worker) override;
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>GetProperties() const override;
|
||||
|
||||
virtual bool UpdateProperty(const gd::String &name, const gd::String &value) override;
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>
|
||||
GetInitialInstanceProperties(const gd::InitialInstance &instance,
|
||||
gd::Project &project,
|
||||
gd::Layout &layout) override;
|
||||
|
||||
virtual bool UpdateInitialInstanceProperty(gd::InitialInstance &instance,
|
||||
const gd::String &name,
|
||||
const gd::String &value,
|
||||
gd::Project &project,
|
||||
gd::Layout &layout) override;
|
||||
|
||||
/** \name Animations
|
||||
* Methods related to animations management
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
*/
|
||||
const SpineAnimation &GetAnimation(std::size_t nb) const;
|
||||
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
*/
|
||||
SpineAnimation &GetAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Return the number of animations this object has.
|
||||
*/
|
||||
std::size_t GetAnimationsCount() const { return animations.size(); };
|
||||
|
||||
/**
|
||||
* \brief Return true if the animation called "name" exists.
|
||||
*/
|
||||
bool HasAnimationNamed(const gd::String& name) const;
|
||||
|
||||
/**
|
||||
* \brief Add an animation at the end of the existing ones.
|
||||
*/
|
||||
void AddAnimation(const SpineAnimation &animation);
|
||||
|
||||
/**
|
||||
* \brief Remove an animation.
|
||||
*/
|
||||
bool RemoveAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Remove all animations.
|
||||
*/
|
||||
void RemoveAllAnimations() { animations.clear(); }
|
||||
|
||||
/**
|
||||
* \brief Return true if the object hasn't any animation.
|
||||
*/
|
||||
bool HasNoAnimations() const { return animations.empty(); }
|
||||
|
||||
/**
|
||||
* \brief Swap the position of two animations
|
||||
*/
|
||||
void SwapAnimations(std::size_t firstIndex, std::size_t secondIndex);
|
||||
|
||||
/**
|
||||
* \brief Change the position of the specified animation
|
||||
*/
|
||||
void MoveAnimation(std::size_t oldIndex, std::size_t newIndex);
|
||||
|
||||
/**
|
||||
* \brief Return a read-only reference to the vector containing all the
|
||||
* animation of the object.
|
||||
*/
|
||||
const std::vector<SpineAnimation> &GetAllAnimations() const {
|
||||
return animations;
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
protected:
|
||||
virtual void DoUnserializeFrom(gd::Project &project, const gd::SerializerElement &element) override;
|
||||
virtual void DoSerializeTo(gd::SerializerElement &element) const override;
|
||||
|
||||
private:
|
||||
double scale;
|
||||
|
||||
gd::String spineResourceName;
|
||||
|
||||
std::vector<SpineAnimation> animations;
|
||||
|
||||
static SpineAnimation badAnimation;
|
||||
};
|
198
Extensions/Spine/managers/pixi-spine-atlas-manager.ts
Normal file
198
Extensions/Spine/managers/pixi-spine-atlas-manager.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
/** The callback called when a text that was requested is loaded (or an error occurred). */
|
||||
export type SpineAtlasManagerRequestCallback = (
|
||||
error: Error | null,
|
||||
content?: pixi_spine.TextureAtlas
|
||||
) => void;
|
||||
|
||||
const atlasKinds: ResourceKind[] = ['atlas'];
|
||||
|
||||
/**
|
||||
* AtlasManager loads atlas files with pixi loader, using the "atlas" resources
|
||||
* registered in the game resources and process them to Pixi TextureAtlas.
|
||||
*
|
||||
* Contrary to audio/fonts, text files are loaded asynchronously, when requested.
|
||||
* You should properly handle errors, and give the developer/player a way to know
|
||||
* that loading failed.
|
||||
*/
|
||||
export class SpineAtlasManager implements gdjs.ResourceManager {
|
||||
private _imageManager: ImageManager;
|
||||
private _resourceLoader: ResourceLoader;
|
||||
private _loadedSpineAtlases = new gdjs.ResourceCache<
|
||||
pixi_spine.TextureAtlas
|
||||
>();
|
||||
private _loadingSpineAtlases = new gdjs.ResourceCache<
|
||||
Promise<pixi_spine.TextureAtlas>
|
||||
>();
|
||||
|
||||
/**
|
||||
* @param resources The resources data of the game.
|
||||
* @param resourcesLoader The resources loader of the game.
|
||||
*/
|
||||
constructor(
|
||||
resourceLoader: gdjs.ResourceLoader,
|
||||
imageManager: ImageManager
|
||||
) {
|
||||
this._resourceLoader = resourceLoader;
|
||||
this._imageManager = imageManager;
|
||||
}
|
||||
|
||||
getResourceKinds(): ResourceKind[] {
|
||||
return atlasKinds;
|
||||
}
|
||||
|
||||
async processResource(resourceName: string): Promise<void> {
|
||||
// Do nothing because pixi-spine parses resources by itself.
|
||||
}
|
||||
|
||||
async loadResource(resourceName: string): Promise<void> {
|
||||
await this.getOrLoad(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns promisified loaded atlas resource if it is availble, loads it otherwise.
|
||||
*
|
||||
* @param resources The data of resource to load.
|
||||
*/
|
||||
getOrLoad(resourceName: string): Promise<pixi_spine.TextureAtlas> {
|
||||
const resource = this._getAtlasResource(resourceName);
|
||||
|
||||
if (!resource) {
|
||||
return Promise.reject(
|
||||
`Unable to find atlas for resource '${resourceName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
let loadingPromise = this._loadingSpineAtlases.get(resource);
|
||||
|
||||
if (!loadingPromise) {
|
||||
loadingPromise = new Promise<pixi_spine.TextureAtlas>(
|
||||
(resolve, reject) => {
|
||||
const onLoad: SpineAtlasManagerRequestCallback = (
|
||||
error,
|
||||
content
|
||||
) => {
|
||||
if (error) {
|
||||
return reject(
|
||||
`Error while preloading a spine atlas resource: ${error}`
|
||||
);
|
||||
}
|
||||
if (!content) {
|
||||
return reject(
|
||||
`Cannot reach texture atlas for resource '${resourceName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
resolve(content);
|
||||
};
|
||||
|
||||
this.load(resource, onLoad);
|
||||
}
|
||||
);
|
||||
|
||||
this._loadingSpineAtlases.set(resource, loadingPromise);
|
||||
}
|
||||
|
||||
return loadingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load specified atlas resource and pass it to callback once it is loaded.
|
||||
*
|
||||
* @param resources The data of resource to load.
|
||||
* @param callback The callback to pass atlas to it once it is loaded.
|
||||
*/
|
||||
load(
|
||||
resource: ResourceData,
|
||||
callback: SpineAtlasManagerRequestCallback
|
||||
): void {
|
||||
const game = this._resourceLoader.getRuntimeGame();
|
||||
const embeddedResourcesNames = game.getEmbeddedResourcesNames(
|
||||
resource.name
|
||||
);
|
||||
|
||||
if (!embeddedResourcesNames.length)
|
||||
return callback(
|
||||
new Error(`${resource.name} do not have image metadata!`)
|
||||
);
|
||||
|
||||
const images = embeddedResourcesNames.reduce<{
|
||||
[key: string]: PIXI.Texture;
|
||||
}>((imagesMap, embeddedResourceName) => {
|
||||
const mappedResourceName = game.resolveEmbeddedResource(
|
||||
resource.name,
|
||||
embeddedResourceName
|
||||
);
|
||||
imagesMap[
|
||||
embeddedResourceName
|
||||
] = this._imageManager.getOrLoadPIXITexture(mappedResourceName);
|
||||
|
||||
return imagesMap;
|
||||
}, {});
|
||||
const onLoad = (atlas: pixi_spine.TextureAtlas) => {
|
||||
this._loadedSpineAtlases.set(resource, atlas);
|
||||
callback(null, atlas);
|
||||
};
|
||||
const url = this._resourceLoader.getFullUrl(resource.file);
|
||||
|
||||
PIXI.Assets.setPreferences({
|
||||
preferWorkers: false,
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
|
||||
? 'use-credentials'
|
||||
: 'anonymous',
|
||||
});
|
||||
PIXI.Assets.add(resource.name, url, { images });
|
||||
PIXI.Assets.load<pixi_spine.TextureAtlas | string>(resource.name).then(
|
||||
(atlas) => {
|
||||
/**
|
||||
* Ideally atlas of TextureAtlas should be passed here
|
||||
* but there is known issue in case of preloaded images (see https://github.com/pixijs/spine/issues/537)
|
||||
*
|
||||
* Here covered all possible ways to make it work fine if issue is fixed in pixi-spine or after migration to spine-pixi
|
||||
*/
|
||||
if (typeof atlas === 'string') {
|
||||
new pixi_spine.TextureAtlas(
|
||||
atlas,
|
||||
(textureName, textureCb) =>
|
||||
textureCb(images[textureName].baseTexture),
|
||||
onLoad
|
||||
);
|
||||
} else {
|
||||
onLoad(atlas);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given atlas resource was loaded (preloaded or loaded with `load`).
|
||||
* @param resourceName The name of the atlas resource.
|
||||
* @returns true if the content of the atlas resource is loaded, false otherwise.
|
||||
*/
|
||||
isLoaded(resourceName: string): boolean {
|
||||
return !!this._loadedSpineAtlases.getFromName(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Pixi TextureAtlas for the given resource that is already loaded (preloaded or loaded with `load`).
|
||||
* If the resource is not loaded, `null` will be returned.
|
||||
* @param resourceName The name of the atlas resource.
|
||||
* @returns the TextureAtlas of the atlas if loaded, `null` otherwise.
|
||||
*/
|
||||
getAtlasTexture(resourceName: string): pixi_spine.TextureAtlas | null {
|
||||
return this._loadedSpineAtlases.getFromName(resourceName);
|
||||
}
|
||||
|
||||
private _getAtlasResource(resourceName: string): ResourceData | null {
|
||||
const resource = this._resourceLoader.getResource(resourceName);
|
||||
return resource && this.getResourceKinds().includes(resource.kind)
|
||||
? resource
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
119
Extensions/Spine/managers/pixi-spine-manager.ts
Normal file
119
Extensions/Spine/managers/pixi-spine-manager.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Spine Manager');
|
||||
|
||||
const resourceKinds: ResourceKind[] = ['spine'];
|
||||
|
||||
/**
|
||||
* SpineManager manages pixi spine skeleton data.
|
||||
*/
|
||||
export class SpineManager implements gdjs.ResourceManager {
|
||||
private _spineAtlasManager: SpineAtlasManager;
|
||||
private _resourceLoader: ResourceLoader;
|
||||
private _loadedSpines = new gdjs.ResourceCache<pixi_spine.ISkeletonData>();
|
||||
|
||||
/**
|
||||
* @param resourceDataArray The resources data of the game.
|
||||
* @param resourcesLoader The resources loader of the game.
|
||||
*/
|
||||
constructor(
|
||||
resourceLoader: gdjs.ResourceLoader,
|
||||
spineAtlasManager: SpineAtlasManager
|
||||
) {
|
||||
this._resourceLoader = resourceLoader;
|
||||
this._spineAtlasManager = spineAtlasManager;
|
||||
}
|
||||
|
||||
getResourceKinds(): ResourceKind[] {
|
||||
return resourceKinds;
|
||||
}
|
||||
|
||||
async processResource(resourceName: string): Promise<void> {
|
||||
// Do nothing because pixi-spine parses resources by itself.
|
||||
}
|
||||
|
||||
async loadResource(resourceName: string): Promise<void> {
|
||||
const resource = this._getSpineResource(resourceName);
|
||||
|
||||
if (!resource) {
|
||||
return logger.error(
|
||||
`Unable to find spine json for resource ${resourceName}.`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const game = this._resourceLoader.getRuntimeGame();
|
||||
const embeddedResourcesNames = game.getEmbeddedResourcesNames(
|
||||
resource.name
|
||||
);
|
||||
|
||||
// there should be exactly one file which is pointing to atlas
|
||||
if (embeddedResourcesNames.length !== 1) {
|
||||
return logger.error(
|
||||
`Unable to find atlas metadata for resource spine json ${resourceName}.`
|
||||
);
|
||||
}
|
||||
|
||||
const atlasResourceName = game.resolveEmbeddedResource(
|
||||
resource.name,
|
||||
embeddedResourcesNames[0]
|
||||
);
|
||||
const spineAtlas = await this._spineAtlasManager.getOrLoad(
|
||||
atlasResourceName
|
||||
);
|
||||
const url = this._resourceLoader.getFullUrl(resource.file);
|
||||
PIXI.Assets.setPreferences({
|
||||
preferWorkers: false,
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
|
||||
? 'use-credentials'
|
||||
: 'anonymous',
|
||||
});
|
||||
PIXI.Assets.add(resource.name, url, { spineAtlas });
|
||||
const loadedJson = await PIXI.Assets.load(resource.name);
|
||||
|
||||
if (loadedJson.spineData) {
|
||||
this._loadedSpines.set(resource, loadedJson.spineData);
|
||||
} else {
|
||||
logger.error(
|
||||
`Loader cannot process spine resource ${resource.name} correctly.`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error while preloading spine resource ${resource.name}: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object for the given resource that is already loaded (preloaded or loaded with `loadJson`).
|
||||
* If the resource is not loaded, `null` will be returned.
|
||||
*
|
||||
* @param resourceName The name of the spine skeleton.
|
||||
* @returns the spine skeleton if loaded, `null` otherwise.
|
||||
*/
|
||||
getSpine(resourceName: string): pixi_spine.ISkeletonData | null {
|
||||
return this._loadedSpines.getFromName(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given spine skeleton was loaded.
|
||||
* @param resourceName The name of the spine skeleton.
|
||||
* @returns true if the content of the spine skeleton is loaded, false otherwise.
|
||||
*/
|
||||
isSpineLoaded(resourceName: string): boolean {
|
||||
return !!this._loadedSpines.getFromName(resourceName);
|
||||
}
|
||||
|
||||
private _getSpineResource(resourceName: string): ResourceData | null {
|
||||
const resource = this._resourceLoader.getResource(resourceName);
|
||||
return resource && this.getResourceKinds().includes(resource.kind)
|
||||
? resource
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
14
Extensions/Spine/pixi-spine/pixi-spine.js
Normal file
14
Extensions/Spine/pixi-spine/pixi-spine.js
Normal file
File diff suppressed because one or more lines are too long
202
Extensions/Spine/spineruntimeobject-pixi-renderer.ts
Normal file
202
Extensions/Spine/spineruntimeobject-pixi-renderer.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
namespace gdjs {
|
||||
const isSpine = (obj: any): obj is pixi_spine.Spine =>
|
||||
obj instanceof pixi_spine.Spine;
|
||||
|
||||
export class SpineRuntimeObjectPixiRenderer {
|
||||
private _object: gdjs.SpineRuntimeObject;
|
||||
private _rendererObject: pixi_spine.Spine | PIXI.Container;
|
||||
private _isAnimationComplete = true;
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
* @param instanceContainer The container in which the object is
|
||||
*/
|
||||
constructor(
|
||||
runtimeObject: gdjs.SpineRuntimeObject,
|
||||
private instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
this._rendererObject = this.constructRendererObject();
|
||||
if (isSpine(this._rendererObject)) {
|
||||
this._rendererObject.autoUpdate = false;
|
||||
}
|
||||
|
||||
this.updatePosition();
|
||||
this.updateAngle();
|
||||
this.updateOpacity();
|
||||
this.updateScale();
|
||||
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._rendererObject, runtimeObject.getZOrder());
|
||||
}
|
||||
|
||||
updateAnimation(timeDelta: float) {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return;
|
||||
}
|
||||
this._rendererObject.update(timeDelta);
|
||||
}
|
||||
|
||||
getRendererObject(): pixi_spine.Spine | PIXI.Container {
|
||||
return this._rendererObject;
|
||||
}
|
||||
|
||||
getOriginOffset(): PIXI.Point {
|
||||
if (!isSpine(this._rendererObject)) return new PIXI.Point(0, 0);
|
||||
|
||||
const localBounds = this._rendererObject.getLocalBounds(undefined, true);
|
||||
|
||||
return new PIXI.Point(
|
||||
localBounds.x * this._rendererObject.scale.x,
|
||||
localBounds.y * this._rendererObject.scale.y
|
||||
);
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
this._rendererObject.destroy();
|
||||
}
|
||||
|
||||
updateScale(): void {
|
||||
const scaleX = Math.max(
|
||||
this._object._originalScale * this._object.getScaleX(),
|
||||
0
|
||||
);
|
||||
const scaleY = Math.max(
|
||||
this._object._originalScale * this._object.getScaleY(),
|
||||
0
|
||||
);
|
||||
this._rendererObject.scale.x = this._object.isFlippedX()
|
||||
? -scaleX
|
||||
: scaleX;
|
||||
this._rendererObject.scale.y = this._object.isFlippedY()
|
||||
? -scaleY
|
||||
: scaleY;
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
this._rendererObject.position.x = this._object.x;
|
||||
this._rendererObject.position.y = this._object.y;
|
||||
}
|
||||
|
||||
updateAngle(): void {
|
||||
this._rendererObject.rotation = gdjs.toRad(this._object.angle);
|
||||
}
|
||||
|
||||
updateOpacity(): void {
|
||||
this._rendererObject.alpha = this._object.getOpacity() / 255;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._rendererObject.width;
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._rendererObject.height;
|
||||
}
|
||||
|
||||
setWidth(width: float): void {
|
||||
this._rendererObject.width = width;
|
||||
}
|
||||
|
||||
setHeight(height: float): void {
|
||||
this._rendererObject.height = height;
|
||||
}
|
||||
|
||||
getUnscaledWidth(): float {
|
||||
return Math.abs(
|
||||
(this._rendererObject.width * this._object._originalScale) /
|
||||
this._rendererObject.scale.x
|
||||
);
|
||||
}
|
||||
|
||||
getUnscaledHeight(): float {
|
||||
return Math.abs(
|
||||
(this._rendererObject.height * this._object._originalScale) /
|
||||
this._rendererObject.scale.y
|
||||
);
|
||||
}
|
||||
|
||||
setMixing(from: string, to: string, duration: number): void {
|
||||
if (!isSpine(this._rendererObject)) return;
|
||||
|
||||
this._rendererObject.stateData.setMix(from, to, duration);
|
||||
}
|
||||
|
||||
setAnimation(animation: string, loop: boolean): void {
|
||||
if (isSpine(this._rendererObject)) {
|
||||
const onCompleteListener: pixi_spine.IAnimationStateListener = {
|
||||
complete: () => {
|
||||
this._isAnimationComplete = true;
|
||||
(this._rendererObject as pixi_spine.Spine).state.removeListener(
|
||||
onCompleteListener
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
this._isAnimationComplete = false;
|
||||
this._rendererObject.state.addListener(onCompleteListener);
|
||||
this._rendererObject.state.setAnimation(0, animation, loop);
|
||||
this._rendererObject.update(0);
|
||||
}
|
||||
}
|
||||
|
||||
getAnimationDuration(sourceAnimationName: string) {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return 0;
|
||||
}
|
||||
const animation = this._rendererObject.spineData.findAnimation(
|
||||
sourceAnimationName
|
||||
);
|
||||
return animation ? animation.duration : 0;
|
||||
}
|
||||
|
||||
getAnimationElapsedTime(): number {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return 0;
|
||||
}
|
||||
const tracks = this._rendererObject.state.tracks;
|
||||
if (tracks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
// This should be fine because only 1 track is used.
|
||||
const track = tracks[0];
|
||||
// @ts-ignore TrackEntry.getAnimationTime is not exposed.
|
||||
return track.getAnimationTime();
|
||||
}
|
||||
|
||||
setAnimationElapsedTime(time: number): void {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return;
|
||||
}
|
||||
const tracks = this._rendererObject.state.tracks;
|
||||
if (tracks.length === 0) {
|
||||
return;
|
||||
}
|
||||
const track = tracks[0];
|
||||
track.trackTime = time;
|
||||
}
|
||||
|
||||
isAnimationComplete(): boolean {
|
||||
return this._isAnimationComplete;
|
||||
}
|
||||
|
||||
private constructRendererObject(): pixi_spine.Spine | PIXI.Container {
|
||||
const game = this.instanceContainer.getGame();
|
||||
const spineManager = game.getSpineManager();
|
||||
|
||||
if (
|
||||
!spineManager ||
|
||||
!spineManager.isSpineLoaded(this._object.spineResourceName)
|
||||
) {
|
||||
return new PIXI.Container();
|
||||
}
|
||||
|
||||
return new pixi_spine.Spine(
|
||||
spineManager.getSpine(this._object.spineResourceName)!
|
||||
);
|
||||
}
|
||||
}
|
||||
export const SpineRuntimeObjectRenderer = SpineRuntimeObjectPixiRenderer;
|
||||
}
|
406
Extensions/Spine/spineruntimeobject.ts
Normal file
406
Extensions/Spine/spineruntimeobject.ts
Normal file
@@ -0,0 +1,406 @@
|
||||
namespace gdjs {
|
||||
type SpineAnimation = { name: string; source: string; loop: boolean };
|
||||
|
||||
export type SpineObjectDataType = {
|
||||
content: {
|
||||
opacity: float;
|
||||
scale: float;
|
||||
timeScale: float;
|
||||
spineResourceName: string;
|
||||
animations: SpineAnimation[];
|
||||
};
|
||||
};
|
||||
export type SpineObjectData = ObjectData & SpineObjectDataType;
|
||||
|
||||
export class SpineRuntimeObject
|
||||
extends gdjs.RuntimeObject
|
||||
implements
|
||||
gdjs.Resizable,
|
||||
gdjs.Scalable,
|
||||
gdjs.Animatable,
|
||||
gdjs.OpacityHandler {
|
||||
private _opacity: float = 255;
|
||||
private _scaleX: number = 1;
|
||||
private _scaleY: number = 1;
|
||||
_originalScale: number;
|
||||
private _flippedX: boolean = false;
|
||||
private _flippedY: boolean = false;
|
||||
private _animations: SpineAnimation[];
|
||||
private _currentAnimationIndex = -1;
|
||||
private _animationSpeedScale: float = 1;
|
||||
private _animationPaused: boolean = false;
|
||||
private _isPausedFrameDirty = false;
|
||||
/** The duration in second for the smooth transition between 2 animations */
|
||||
private _animationMixingDuration: number;
|
||||
private _renderer: gdjs.SpineRuntimeObjectPixiRenderer;
|
||||
|
||||
readonly spineResourceName: string;
|
||||
|
||||
/**
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param objectData The object data used to initialize the object
|
||||
*/
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: SpineObjectData
|
||||
) {
|
||||
super(instanceContainer, objectData);
|
||||
|
||||
this._animations = objectData.content.animations;
|
||||
this._originalScale = objectData.content.scale;
|
||||
this.spineResourceName = objectData.content.spineResourceName;
|
||||
this._animationMixingDuration = 0.1;
|
||||
this._renderer = new gdjs.SpineRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer
|
||||
);
|
||||
this.setAnimationIndex(0);
|
||||
this._renderer.updateAnimation(0);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (this._animationPaused) {
|
||||
if (this._isPausedFrameDirty) {
|
||||
this._renderer.updateAnimation(0);
|
||||
this.invalidateHitboxes();
|
||||
this._isPausedFrameDirty = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const elapsedTime = this.getElapsedTime() / 1000;
|
||||
this._renderer.updateAnimation(elapsedTime * this._animationSpeedScale);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getRendererObject(): pixi_spine.Spine | PIXI.Container {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
updateFromObjectData(
|
||||
oldObjectData: SpineObjectData,
|
||||
newObjectData: SpineObjectData
|
||||
): boolean {
|
||||
super.updateFromObjectData(oldObjectData, newObjectData);
|
||||
|
||||
if (oldObjectData.content.scale !== newObjectData.content.scale) {
|
||||
this._originalScale = newObjectData.content.scale;
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(
|
||||
initialInstanceData: InstanceData
|
||||
): void {
|
||||
const animationData = initialInstanceData.numberProperties.find(
|
||||
(data) => data.name === 'animation'
|
||||
);
|
||||
const animationIndex = animationData
|
||||
? animationData.value
|
||||
: this._currentAnimationIndex;
|
||||
|
||||
this.setAnimationIndexWithMixing(animationIndex, 0);
|
||||
|
||||
if (initialInstanceData.customSize) {
|
||||
this.setSize(initialInstanceData.width, initialInstanceData.height);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
}
|
||||
|
||||
getDrawableX(): number {
|
||||
const originOffset = this._renderer.getOriginOffset();
|
||||
|
||||
return this.getX() + originOffset.x;
|
||||
}
|
||||
|
||||
getDrawableY(): number {
|
||||
const originOffset = this._renderer.getOriginOffset();
|
||||
|
||||
return this.getY() + originOffset.y;
|
||||
}
|
||||
|
||||
onDestroyed(): void {
|
||||
super.onDestroyed();
|
||||
this._renderer.onDestroy();
|
||||
}
|
||||
|
||||
setX(x: float): void {
|
||||
super.setX(x);
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
setY(y: float): void {
|
||||
super.setY(y);
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
setAngle(angle: float): void {
|
||||
super.setAngle(angle);
|
||||
this._renderer.updateAngle();
|
||||
}
|
||||
|
||||
setOpacity(opacity: float): void {
|
||||
this._opacity = Math.max(0, Math.min(255, opacity));
|
||||
this._renderer.updateOpacity();
|
||||
}
|
||||
|
||||
getOpacity(): float {
|
||||
return this._opacity;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._renderer.getWidth();
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._renderer.getHeight();
|
||||
}
|
||||
|
||||
setWidth(newWidth: float): void {
|
||||
const unscaledWidth = this._renderer.getUnscaledWidth();
|
||||
if (unscaledWidth !== 0) {
|
||||
this.setScaleX(newWidth / unscaledWidth);
|
||||
}
|
||||
}
|
||||
|
||||
setHeight(newHeight: float): void {
|
||||
const unscaledHeight = this._renderer.getUnscaledHeight();
|
||||
if (unscaledHeight !== 0) {
|
||||
this.setScaleY(newHeight / unscaledHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setSize(newWidth: number, newHeight: number): void {
|
||||
this.setWidth(newWidth);
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
setScale(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (
|
||||
newScale === Math.abs(this._scaleX) &&
|
||||
newScale === Math.abs(this._scaleY)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._scaleX = newScale * (this._flippedX ? -1 : 1);
|
||||
this._scaleY = newScale * (this._flippedY ? -1 : 1);
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
setScaleX(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleX)) {
|
||||
return;
|
||||
}
|
||||
this._scaleX = newScale * (this._flippedX ? -1 : 1);
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
setScaleY(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleY)) {
|
||||
return;
|
||||
}
|
||||
this._scaleY = newScale * (this._flippedY ? -1 : 1);
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*
|
||||
* @return the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*/
|
||||
getScale(): float {
|
||||
const scaleX = Math.abs(this._scaleX);
|
||||
const scaleY = Math.abs(this._scaleY);
|
||||
return scaleX === scaleY ? scaleX : Math.sqrt(scaleX * scaleY);
|
||||
}
|
||||
|
||||
getScaleY(): float {
|
||||
return Math.abs(this._scaleY);
|
||||
}
|
||||
|
||||
getScaleX(): float {
|
||||
return Math.abs(this._scaleX);
|
||||
}
|
||||
|
||||
isFlippedX(): boolean {
|
||||
return this._flippedX;
|
||||
}
|
||||
|
||||
isFlippedY(): boolean {
|
||||
return this._flippedY;
|
||||
}
|
||||
|
||||
flipX(enable: boolean) {
|
||||
if (enable !== this._flippedX) {
|
||||
this._scaleX *= -1;
|
||||
this._flippedX = enable;
|
||||
this.invalidateHitboxes();
|
||||
this._renderer.updateScale();
|
||||
}
|
||||
}
|
||||
|
||||
flipY(enable: boolean) {
|
||||
if (enable !== this._flippedY) {
|
||||
this._scaleY *= -1;
|
||||
this._flippedY = enable;
|
||||
this.invalidateHitboxes();
|
||||
this._renderer.updateScale();
|
||||
}
|
||||
}
|
||||
|
||||
setAnimationIndex(animationIndex: number): void {
|
||||
this.setAnimationIndexWithMixing(
|
||||
animationIndex,
|
||||
this._animationMixingDuration
|
||||
);
|
||||
}
|
||||
|
||||
setAnimationIndexWithMixing(
|
||||
animationIndex: number,
|
||||
mixingDuration: number
|
||||
): void {
|
||||
if (
|
||||
this._animations.length === 0 ||
|
||||
this._currentAnimationIndex === animationIndex ||
|
||||
!this.isAnimationIndex(animationIndex)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const previousAnimation = this._animations[this._currentAnimationIndex];
|
||||
const newAnimation = this._animations[animationIndex];
|
||||
this._currentAnimationIndex = animationIndex;
|
||||
|
||||
if (previousAnimation) {
|
||||
this._renderer.setMixing(
|
||||
previousAnimation.source,
|
||||
newAnimation.source,
|
||||
mixingDuration
|
||||
);
|
||||
}
|
||||
this._renderer.setAnimation(newAnimation.source, newAnimation.loop);
|
||||
this._isPausedFrameDirty = true;
|
||||
}
|
||||
|
||||
setAnimationName(animationName: string): void {
|
||||
this.setAnimationNameWithMixing(
|
||||
animationName,
|
||||
this._animationMixingDuration
|
||||
);
|
||||
}
|
||||
|
||||
setAnimationNameWithMixing(
|
||||
animationName: string,
|
||||
mixingDuration: number
|
||||
): void {
|
||||
this.setAnimationIndexWithMixing(
|
||||
this.getAnimationIndexFor(animationName),
|
||||
mixingDuration
|
||||
);
|
||||
}
|
||||
|
||||
getAnimationIndexFor(animationName: string): number {
|
||||
return this._animations.findIndex(
|
||||
(animation) => animation.name === animationName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the duration in second for the smooth transition between 2 animations.
|
||||
*/
|
||||
getAnimationMixingDuration(): number {
|
||||
return this._animationMixingDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the duration in second for the smooth transition between 2 animations.
|
||||
*/
|
||||
setAnimationMixingDuration(animationMixingDuration: number): void {
|
||||
this._animationMixingDuration = animationMixingDuration;
|
||||
}
|
||||
|
||||
getAnimationIndex(): number {
|
||||
return this._currentAnimationIndex;
|
||||
}
|
||||
|
||||
getAnimationName(): string {
|
||||
return this.isAnimationIndex(this._currentAnimationIndex)
|
||||
? this._animations[this._currentAnimationIndex].name
|
||||
: '';
|
||||
}
|
||||
|
||||
isAnimationIndex(animationIndex: number): boolean {
|
||||
return (
|
||||
Number.isInteger(animationIndex) &&
|
||||
animationIndex >= 0 &&
|
||||
animationIndex < this._animations.length
|
||||
);
|
||||
}
|
||||
|
||||
hasAnimationEnded(): boolean {
|
||||
return this._renderer.isAnimationComplete();
|
||||
}
|
||||
|
||||
isAnimationPaused() {
|
||||
return this._animationPaused;
|
||||
}
|
||||
|
||||
pauseAnimation() {
|
||||
this._animationPaused = true;
|
||||
}
|
||||
|
||||
resumeAnimation() {
|
||||
this._animationPaused = false;
|
||||
}
|
||||
|
||||
getAnimationSpeedScale() {
|
||||
return this._animationSpeedScale;
|
||||
}
|
||||
|
||||
setAnimationSpeedScale(ratio: float): void {
|
||||
this._animationSpeedScale = ratio;
|
||||
}
|
||||
|
||||
getAnimationElapsedTime(): number {
|
||||
if (this._animations.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this._renderer.getAnimationElapsedTime();
|
||||
}
|
||||
|
||||
setAnimationElapsedTime(time: number): void {
|
||||
if (this._animations.length === 0) {
|
||||
return;
|
||||
}
|
||||
this._renderer.setAnimationElapsedTime(time);
|
||||
this._isPausedFrameDirty = true;
|
||||
}
|
||||
|
||||
getAnimationDuration(): number {
|
||||
if (this._animations.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this._renderer.getAnimationDuration(
|
||||
this._animations[this._currentAnimationIndex].source
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.registerObject('SpineObject::SpineObject', gdjs.SpineRuntimeObject);
|
||||
}
|
@@ -383,7 +383,7 @@ module.exports = {
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'TextInputObject', false)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.addParameter('fontResource', _('Font resource name'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFontResourceName');
|
||||
@@ -400,9 +400,23 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Text input'), 'TextInputObject', false)
|
||||
.useStandardParameters(
|
||||
'string',
|
||||
gd.ParameterOptions.makeNewOptions().setDescription(_('Input type'))
|
||||
) // TODO: stringWithSelector?
|
||||
'stringWithSelector',
|
||||
gd.ParameterOptions.makeNewOptions()
|
||||
.setDescription(_('Input type'))
|
||||
.setTypeExtraInfo(
|
||||
JSON.stringify([
|
||||
'text',
|
||||
'text area',
|
||||
'email',
|
||||
'password',
|
||||
'number',
|
||||
'telephone number',
|
||||
'url',
|
||||
'search',
|
||||
'email',
|
||||
])
|
||||
)
|
||||
)
|
||||
.setFunctionName('setInputType')
|
||||
.setGetter('getInputType');
|
||||
|
||||
|
@@ -204,6 +204,7 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("color", _("Third Color"))
|
||||
.AddParameter("color", _("Fourth Color"));
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("SetOutline",
|
||||
_("Outline"),
|
||||
_("Change the outline of the text. A thickness of 0 disables "
|
||||
@@ -213,20 +214,63 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
_("Effects"),
|
||||
"res/actions/textOutline24.png",
|
||||
"res/actions/textOutline.png")
|
||||
|
||||
.SetHidden()
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.AddParameter("color", _("Color"))
|
||||
.AddParameter("expression", _("Thickness"));
|
||||
|
||||
obj.AddScopedAction("SetOutlineEnabled",
|
||||
_("Enable outline"),
|
||||
_("Enable or disable the outline of the text."),
|
||||
_("Enable the outline of _PARAM0_: _PARAM1_"),
|
||||
_("Outline"),
|
||||
"res/actions/textOutline24.png",
|
||||
"res/actions/textOutline.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.AddParameter("yesorno", _("Enable outline"), "", true)
|
||||
.SetDefaultValue("yes");
|
||||
|
||||
obj.AddScopedCondition("IsOutlineEnabled",
|
||||
_("Outline enabled"),
|
||||
_("Check if the text outline is enabled."),
|
||||
_("The outline of _PARAM0_ is enabled"),
|
||||
_("Outline"),
|
||||
"res/actions/textOutline24.png",
|
||||
"res/actions/textOutline.png")
|
||||
.AddParameter("object", _("Object"), "Text");
|
||||
|
||||
obj.AddScopedAction("SetOutlineColor",
|
||||
_("Outline color"),
|
||||
_("Change the outline color of the text."),
|
||||
_("Change the text outline color of _PARAM0_ to _PARAM1_"),
|
||||
_("Outline"),
|
||||
"res/actions/textOutline24.png",
|
||||
"res/actions/textOutline.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.AddParameter("color", _("Color"));
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "OutlineThickness",
|
||||
_("Outline thickness"),
|
||||
_("the outline thickness of the text"),
|
||||
_("the text outline thickness"),
|
||||
_("Outline"),
|
||||
"res/actions/textOutline24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Thickness")));
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("SetShadow",
|
||||
_("Text shadow"),
|
||||
_("Change the shadow of the text."),
|
||||
_("Change the shadow of _PARAM0_ to color _PARAM1_ distance "
|
||||
"_PARAM2_ blur _PARAM3_ angle _PARAM4_"),
|
||||
_("Effects/Shadow"),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png",
|
||||
"res/actions/textShadow.png")
|
||||
|
||||
.SetHidden()
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.AddParameter("color", _("Color"))
|
||||
.AddParameter("expression", _("Distance"))
|
||||
@@ -234,15 +278,82 @@ void DeclareTextObjectExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("expression", _("Angle"));
|
||||
|
||||
obj.AddAction("ShowShadow",
|
||||
_("Show Shadow"),
|
||||
_("Show the shadow of the text."),
|
||||
_("Show the shadow of _PARAM0_: _PARAM1_"),
|
||||
_("Effects/Shadow"),
|
||||
_("Enable shadow"),
|
||||
_("Enable or disable the shadow of the text."),
|
||||
_("Enable the shadow of _PARAM0_: _PARAM1_"),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png",
|
||||
"res/actions/textShadow.png")
|
||||
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.AddParameter("yesorno", _("Show the shadow"));
|
||||
.AddParameter("yesorno", _("Show the shadow"), "", true)
|
||||
.SetDefaultValue("yes");
|
||||
|
||||
obj.AddScopedCondition("IsShadowEnabled",
|
||||
_("Shadow enabled"),
|
||||
_("Check if the text shadow is enabled."),
|
||||
_("The shadow of _PARAM0_ is enabled"),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png",
|
||||
"res/actions/textShadow.png")
|
||||
.AddParameter("object", _("Object"), "Text");
|
||||
|
||||
obj.AddScopedAction("SetShadowColor",
|
||||
_("Shadow color"),
|
||||
_("Change the shadow color of the text."),
|
||||
_("Change the shadow color of _PARAM0_ to _PARAM1_"),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png",
|
||||
"res/actions/textShadow.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.AddParameter("color", _("Color"));
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "ShadowOpacity",
|
||||
_("Shadow opacity"),
|
||||
_("the shadow opacity of the text"),
|
||||
_("the shadow opacity "),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Opacity (0 - 255)")));
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "ShadowDistance",
|
||||
_("Shadow distance"),
|
||||
_("the shadow distance of the text"),
|
||||
_("the shadow distance "),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Distance")));
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "ShadowAngle",
|
||||
_("Shadow angle"),
|
||||
_("the shadow angle of the text"),
|
||||
_("the shadow angle "),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Angle (in degrees)")));
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "ShadowBlurRadius",
|
||||
_("Shadow blur radius"),
|
||||
_("the shadow blur radius of the text"),
|
||||
_("the shadow blur radius "),
|
||||
_("Shadow"),
|
||||
"res/actions/textShadow24.png")
|
||||
.AddParameter("object", _("Object"), "Text")
|
||||
.UseStandardParameters(
|
||||
"number",
|
||||
gd::ParameterOptions::MakeNewOptions().SetDescription(
|
||||
_("Blur radius")));
|
||||
|
||||
// Deprecated
|
||||
obj.AddAction("Opacity",
|
||||
|
@@ -127,10 +127,60 @@ class TextObjectJsExtension : public gd::PlatformExtension {
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetOutline"]
|
||||
.SetFunctionName("setOutline");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineEnabled"]
|
||||
.SetFunctionName("setOutlineEnabled");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsOutlineEnabled"]
|
||||
.SetFunctionName("isOutlineEnabled");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineColor"]
|
||||
.SetFunctionName("setOutlineColor");
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::OutlineThickness"]
|
||||
.SetFunctionName("getOutlineThickness");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::OutlineThickness"]
|
||||
.SetFunctionName("getOutlineThickness");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetOutlineThickness"]
|
||||
.SetFunctionName("setOutlineThickness")
|
||||
.SetGetter("getOutlineThickness");
|
||||
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::SetShadow"]
|
||||
.SetFunctionName("setShadow");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::ShowShadow"]
|
||||
.SetFunctionName("showShadow");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::IsShadowEnabled"]
|
||||
.SetFunctionName("isShadowEnabled");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowColor"]
|
||||
.SetFunctionName("setShadowColor");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowOpacity"]
|
||||
.SetFunctionName("getShadowOpacity");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowOpacity"]
|
||||
.SetFunctionName("getShadowOpacity");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowOpacity"]
|
||||
.SetFunctionName("setShadowOpacity")
|
||||
.SetGetter("getShadowOpacity");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowDistance"]
|
||||
.SetFunctionName("getShadowDistance");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowDistance"]
|
||||
.SetFunctionName("getShadowDistance");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowDistance"]
|
||||
.SetFunctionName("setShadowDistance")
|
||||
.SetGetter("getShadowDistance");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowAngle"]
|
||||
.SetFunctionName("getShadowAngle");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowAngle"]
|
||||
.SetFunctionName("getShadowAngle");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowAngle"]
|
||||
.SetFunctionName("setShadowAngle")
|
||||
.SetGetter("getShadowAngle");
|
||||
|
||||
GetAllExpressionsForObject("TextObject::Text")["TextObject::Text::ShadowBlurRadius"]
|
||||
.SetFunctionName("getShadowBlurRadius");
|
||||
GetAllConditionsForObject("TextObject::Text")["TextObject::Text::ShadowBlurRadius"]
|
||||
.SetFunctionName("getShadowBlurRadius");
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Text::SetShadowBlurRadius"]
|
||||
.SetFunctionName("setShadowBlurRadius")
|
||||
.SetGetter("getShadowBlurRadius");
|
||||
|
||||
// Unimplemented actions and conditions:
|
||||
GetAllActionsForObject("TextObject::Text")["TextObject::Font"]
|
||||
|
@@ -12,10 +12,8 @@ This project is released under the MIT License.
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "TextObject.h"
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -27,10 +25,17 @@ TextObject::TextObject()
|
||||
bold(false),
|
||||
italic(false),
|
||||
underlined(false),
|
||||
colorR(0),
|
||||
colorG(0),
|
||||
colorB(0),
|
||||
textAlignment("left")
|
||||
color("0;0;0"),
|
||||
textAlignment("left"),
|
||||
isOutlineEnabled(false),
|
||||
outlineThickness(2),
|
||||
outlineColor("255;255;255"),
|
||||
isShadowEnabled(false),
|
||||
shadowColor("0;0;0"),
|
||||
shadowOpacity(127),
|
||||
shadowAngle(90),
|
||||
shadowDistance(4),
|
||||
shadowBlurRadius(2)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -38,41 +43,98 @@ TextObject::~TextObject(){};
|
||||
|
||||
void TextObject::DoUnserializeFrom(gd::Project& project,
|
||||
const gd::SerializerElement& element) {
|
||||
SetString(element.GetChild("string", 0, "String").GetValue().GetString());
|
||||
SetFontName(element.GetChild("font", 0, "Font").GetValue().GetString());
|
||||
SetTextAlignment(element.GetChild("textAlignment").GetValue().GetString());
|
||||
SetCharacterSize(element.GetChild("characterSize", 0, "CharacterSize")
|
||||
// Compatibility with GD <= 5.3.188
|
||||
// end of compatibility code
|
||||
bool isLegacy = !element.HasChild("content");
|
||||
auto &content = isLegacy ? element : element.GetChild("content");
|
||||
|
||||
SetFontName(content.GetChild("font", 0, "Font").GetValue().GetString());
|
||||
SetTextAlignment(content.GetChild("textAlignment").GetValue().GetString());
|
||||
SetCharacterSize(content.GetChild("characterSize", 0, "CharacterSize")
|
||||
.GetValue()
|
||||
.GetInt());
|
||||
SetColor(element.GetChild("color", 0, "Color").GetIntAttribute("r", 255),
|
||||
element.GetChild("color", 0, "Color").GetIntAttribute("g", 255),
|
||||
element.GetChild("color", 0, "Color").GetIntAttribute("b", 255));
|
||||
smoothed = content.GetBoolAttribute("smoothed");
|
||||
bold = content.GetBoolAttribute("bold");
|
||||
italic = content.GetBoolAttribute("italic");
|
||||
underlined = content.GetBoolAttribute("underlined");
|
||||
|
||||
smoothed = element.GetBoolAttribute("smoothed");
|
||||
bold = element.GetBoolAttribute("bold");
|
||||
italic = element.GetBoolAttribute("italic");
|
||||
underlined = element.GetBoolAttribute("underlined");
|
||||
// Compatibility with GD <= 5.3.188
|
||||
if (isLegacy) {
|
||||
SetText(content.GetChild("string", 0, "String").GetValue().GetString());
|
||||
SetColor(
|
||||
gd::String::From(
|
||||
content.GetChild("color", 0, "Color").GetIntAttribute("r", 255)) +
|
||||
";" +
|
||||
gd::String::From(
|
||||
content.GetChild("color", 0, "Color").GetIntAttribute("g", 255)) +
|
||||
";" +
|
||||
gd::String::From(
|
||||
content.GetChild("color", 0, "Color").GetIntAttribute("b", 255)));
|
||||
} else
|
||||
// end of compatibility code
|
||||
{
|
||||
SetText(content.GetStringAttribute("text"));
|
||||
SetColor(content.GetStringAttribute("color", "0;0;0"));
|
||||
|
||||
SetOutlineEnabled(content.GetBoolAttribute("isOutlineEnabled", false));
|
||||
SetOutlineThickness(content.GetIntAttribute("outlineThickness", 2));
|
||||
SetOutlineColor(content.GetStringAttribute("outlineColor", "255;255;255"));
|
||||
|
||||
SetShadowEnabled(content.GetBoolAttribute("isShadowEnabled", false));
|
||||
SetShadowColor(content.GetStringAttribute("shadowColor", "0;0;0"));
|
||||
SetShadowOpacity(content.GetIntAttribute("shadowOpacity", 127));
|
||||
SetShadowAngle(content.GetIntAttribute("shadowAngle", 90));
|
||||
SetShadowDistance(content.GetIntAttribute("shadowDistance", 4));
|
||||
SetShadowBlurRadius(content.GetIntAttribute("shadowBlurRadius", 2));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void TextObject::DoSerializeTo(gd::SerializerElement& element) const {
|
||||
element.AddChild("string").SetValue(GetString());
|
||||
element.AddChild("font").SetValue(GetFontName());
|
||||
element.AddChild("textAlignment").SetValue(GetTextAlignment());
|
||||
element.AddChild("characterSize").SetValue(GetCharacterSize());
|
||||
element.AddChild("color")
|
||||
.SetAttribute("r", (int)GetColorR())
|
||||
.SetAttribute("g", (int)GetColorG())
|
||||
.SetAttribute("b", (int)GetColorB());
|
||||
// Allow users to rollback to 5.3.188 or older releases without loosing their configuration.
|
||||
// TODO Remove this in a few releases.
|
||||
// Compatibility with GD <= 5.3.188
|
||||
{
|
||||
element.AddChild("string").SetValue(GetText());
|
||||
element.AddChild("font").SetValue(GetFontName());
|
||||
element.AddChild("textAlignment").SetValue(GetTextAlignment());
|
||||
element.AddChild("characterSize").SetValue(GetCharacterSize());
|
||||
auto colorComponents = GetColor().Split(';');
|
||||
element.AddChild("color")
|
||||
.SetAttribute("r", colorComponents.size() == 3 ? colorComponents[0].To<int>() : 0)
|
||||
.SetAttribute("g", colorComponents.size() == 3 ? colorComponents[1].To<int>() : 0)
|
||||
.SetAttribute("b", colorComponents.size() == 3 ? colorComponents[2].To<int>() : 0);
|
||||
element.SetAttribute("smoothed", smoothed);
|
||||
element.SetAttribute("bold", bold);
|
||||
element.SetAttribute("italic", italic);
|
||||
element.SetAttribute("underlined", underlined);
|
||||
}
|
||||
// end of compatibility code
|
||||
|
||||
auto& content = element.AddChild("content");
|
||||
content.AddChild("text").SetValue(GetText());
|
||||
content.AddChild("font").SetValue(GetFontName());
|
||||
content.AddChild("textAlignment").SetValue(GetTextAlignment());
|
||||
content.AddChild("characterSize").SetValue(GetCharacterSize());
|
||||
content.AddChild("color").SetValue(GetColor());
|
||||
|
||||
element.SetAttribute("smoothed", smoothed);
|
||||
element.SetAttribute("bold", bold);
|
||||
element.SetAttribute("italic", italic);
|
||||
element.SetAttribute("underlined", underlined);
|
||||
content.SetAttribute("smoothed", smoothed);
|
||||
content.SetAttribute("bold", bold);
|
||||
content.SetAttribute("italic", italic);
|
||||
content.SetAttribute("underlined", underlined);
|
||||
|
||||
content.SetAttribute("isOutlineEnabled", isOutlineEnabled);
|
||||
content.SetAttribute("outlineThickness", outlineThickness);
|
||||
content.SetAttribute("outlineColor", outlineColor);
|
||||
|
||||
content.SetAttribute("isShadowEnabled", isShadowEnabled);
|
||||
content.SetAttribute("shadowColor", shadowColor);
|
||||
content.SetAttribute("shadowOpacity", shadowOpacity);
|
||||
content.SetAttribute("shadowAngle", shadowAngle);
|
||||
content.SetAttribute("shadowDistance", shadowDistance);
|
||||
content.SetAttribute("shadowBlurRadius", shadowBlurRadius);
|
||||
}
|
||||
|
||||
void TextObject::ExposeResources(
|
||||
gd::ArbitraryResourceWorker& worker) {
|
||||
worker.ExposeFont(fontName);
|
||||
}
|
||||
#endif
|
||||
|
@@ -5,8 +5,8 @@ Copyright (c) 2008-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef TEXTOBJECT_H
|
||||
#define TEXTOBJECT_H
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
namespace gd {
|
||||
class Project;
|
||||
@@ -29,11 +29,11 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
|
||||
|
||||
/** \brief Change the text.
|
||||
*/
|
||||
inline void SetString(const gd::String& str) { text = str; };
|
||||
inline void SetText(const gd::String& str) { text = str; };
|
||||
|
||||
/** \brief Get the text.
|
||||
*/
|
||||
inline const gd::String& GetString() const { return text; };
|
||||
inline const gd::String& GetText() const { return text; };
|
||||
|
||||
/** \brief Change the character size.
|
||||
*/
|
||||
@@ -66,31 +66,63 @@ class GD_EXTENSION_API TextObject : public gd::ObjectConfiguration {
|
||||
void SetSmooth(bool smooth) { smoothed = smooth; };
|
||||
bool IsSmoothed() const { return smoothed; };
|
||||
|
||||
void SetColor(unsigned int r, unsigned int g, unsigned int b) {
|
||||
colorR = r;
|
||||
colorG = g;
|
||||
colorB = b;
|
||||
void SetColor(const gd::String& color_) {
|
||||
color = color_;
|
||||
};
|
||||
unsigned int GetColorR() const { return colorR; };
|
||||
unsigned int GetColorG() const { return colorG; };
|
||||
unsigned int GetColorB() const { return colorB; };
|
||||
inline const gd::String& GetColor() const { return color; };
|
||||
|
||||
void SetOutlineEnabled(bool smooth) { isOutlineEnabled = smooth; };
|
||||
bool IsOutlineEnabled() const { return isOutlineEnabled; };
|
||||
|
||||
void SetOutlineThickness(double value) { outlineThickness = value; };
|
||||
double GetOutlineThickness() const { return outlineThickness; };
|
||||
|
||||
void SetOutlineColor(const gd::String& color) {
|
||||
outlineColor = color;
|
||||
};
|
||||
const gd::String& GetOutlineColor() const { return outlineColor; };
|
||||
|
||||
void SetShadowEnabled(bool smooth) { isShadowEnabled = smooth; };
|
||||
bool IsShadowEnabled() const { return isShadowEnabled; };
|
||||
|
||||
void SetShadowColor(const gd::String& color) {
|
||||
shadowColor = color;
|
||||
};
|
||||
const gd::String& GetShadowColor() const { return shadowColor; };
|
||||
|
||||
void SetShadowOpacity(double value) { shadowOpacity = value; };
|
||||
double GetShadowOpacity() const { return shadowOpacity; };
|
||||
|
||||
void SetShadowAngle(double value) { shadowAngle = value; };
|
||||
double GetShadowAngle() const { return shadowAngle; };
|
||||
|
||||
void SetShadowDistance(double value) { shadowDistance = value; };
|
||||
double GetShadowDistance() const { return shadowDistance; };
|
||||
|
||||
void SetShadowBlurRadius(double value) { shadowBlurRadius = value; };
|
||||
double GetShadowBlurRadius() const { return shadowBlurRadius; };
|
||||
|
||||
private:
|
||||
virtual void DoUnserializeFrom(gd::Project& project,
|
||||
const gd::SerializerElement& element);
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual void DoSerializeTo(gd::SerializerElement& element) const;
|
||||
#endif
|
||||
|
||||
gd::String text;
|
||||
double characterSize;
|
||||
gd::String fontName;
|
||||
bool smoothed;
|
||||
bool bold, italic, underlined;
|
||||
unsigned int colorR;
|
||||
unsigned int colorG;
|
||||
unsigned int colorB;
|
||||
gd::String color;
|
||||
gd::String textAlignment;
|
||||
};
|
||||
|
||||
#endif // TEXTOBJECT_H
|
||||
bool isOutlineEnabled;
|
||||
double outlineThickness;
|
||||
gd::String outlineColor;
|
||||
|
||||
bool isShadowEnabled;
|
||||
gd::String shadowColor;
|
||||
double shadowOpacity;
|
||||
double shadowAngle;
|
||||
double shadowDistance;
|
||||
double shadowBlurRadius;
|
||||
};
|
||||
|
@@ -69,17 +69,23 @@ namespace gdjs {
|
||||
this._object._outlineColor[1],
|
||||
this._object._outlineColor[2]
|
||||
);
|
||||
style.strokeThickness = this._object._outlineThickness;
|
||||
style.strokeThickness = this._object._isOutlineEnabled
|
||||
? this._object._outlineThickness
|
||||
: 0;
|
||||
style.dropShadow = this._object._shadow;
|
||||
style.dropShadowColor = gdjs.rgbToHexNumber(
|
||||
this._object._shadowColor[0],
|
||||
this._object._shadowColor[1],
|
||||
this._object._shadowColor[2]
|
||||
);
|
||||
style.dropShadowAlpha = this._object._shadowOpacity / 255;
|
||||
style.dropShadowBlur = this._object._shadowBlur;
|
||||
style.dropShadowAngle = this._object._shadowAngle;
|
||||
style.dropShadowAngle = gdjs.toRad(this._object._shadowAngle);
|
||||
style.dropShadowDistance = this._object._shadowDistance;
|
||||
style.padding = this._object._padding;
|
||||
const extraPaddingForShadow = style.dropShadow
|
||||
? style.dropShadowDistance + style.dropShadowBlur
|
||||
: 0;
|
||||
style.padding = Math.ceil(this._object._padding + extraPaddingForShadow);
|
||||
|
||||
// Prevent spikey outlines by adding a miter limit
|
||||
style.miterLimit = 3;
|
||||
|
@@ -5,28 +5,35 @@
|
||||
namespace gdjs {
|
||||
/** Base parameters for gdjs.TextRuntimeObject */
|
||||
export type TextObjectDataType = {
|
||||
/** The size of the characters */
|
||||
characterSize: number;
|
||||
/** The font name */
|
||||
font: string;
|
||||
/** Is Bold? */
|
||||
bold: boolean;
|
||||
/** Is Italic? */
|
||||
italic: boolean;
|
||||
/** Is Underlined? */
|
||||
underlined: boolean;
|
||||
/** The text color in an RGB representation */
|
||||
color: {
|
||||
/** The Red level from 0 to 255 */
|
||||
r: number;
|
||||
/** The Green level from 0 to 255 */
|
||||
g: number;
|
||||
/** The Blue level from 0 to 255 */
|
||||
b: number;
|
||||
content: {
|
||||
/** The size of the characters */
|
||||
characterSize: number;
|
||||
/** The font name */
|
||||
font: string;
|
||||
/** Is Bold? */
|
||||
bold: boolean;
|
||||
/** Is Italic? */
|
||||
italic: boolean;
|
||||
/** Is Underlined? */
|
||||
underlined: boolean;
|
||||
/** The text color in an RGB representation */
|
||||
color: string;
|
||||
/** The text of the object */
|
||||
text: string;
|
||||
textAlignment: string;
|
||||
|
||||
isOutlineEnabled: boolean;
|
||||
outlineThickness: float;
|
||||
/** The outline color in an RGB representation */
|
||||
outlineColor: string;
|
||||
isShadowEnabled: boolean;
|
||||
/** The shadow color in an RGB representation */
|
||||
shadowColor: string;
|
||||
shadowOpacity: float;
|
||||
shadowDistance: float;
|
||||
shadowAngle: float;
|
||||
shadowBlurRadius: float;
|
||||
};
|
||||
/** The text of the object */
|
||||
string: string;
|
||||
textAlignment: string;
|
||||
};
|
||||
|
||||
export type TextObjectData = ObjectData & TextObjectDataType;
|
||||
@@ -50,13 +57,18 @@ namespace gdjs {
|
||||
_textAlign: string = 'left';
|
||||
_wrapping: boolean = false;
|
||||
_wrappingWidth: float = 1;
|
||||
_outlineThickness: number = 0;
|
||||
_outlineColor: integer[] = [255, 255, 255];
|
||||
_shadow: boolean = false;
|
||||
_shadowColor: integer[] = [0, 0, 0];
|
||||
_shadowDistance: number = 1;
|
||||
_shadowBlur: integer = 1;
|
||||
_shadowAngle: float = 0;
|
||||
|
||||
_isOutlineEnabled: boolean;
|
||||
_outlineThickness: float;
|
||||
_outlineColor: integer[];
|
||||
|
||||
_shadow: boolean;
|
||||
_shadowColor: integer[];
|
||||
_shadowOpacity: float;
|
||||
_shadowDistance: float;
|
||||
_shadowAngle: float;
|
||||
_shadowBlur: float;
|
||||
|
||||
_padding: integer = 5;
|
||||
_str: string;
|
||||
_renderer: gdjs.TextRuntimeObjectRenderer;
|
||||
@@ -74,18 +86,27 @@ namespace gdjs {
|
||||
textObjectData: TextObjectData
|
||||
) {
|
||||
super(instanceContainer, textObjectData);
|
||||
this._characterSize = Math.max(1, textObjectData.characterSize);
|
||||
this._fontName = textObjectData.font;
|
||||
this._bold = textObjectData.bold;
|
||||
this._italic = textObjectData.italic;
|
||||
this._underlined = textObjectData.underlined;
|
||||
this._color = [
|
||||
textObjectData.color.r,
|
||||
textObjectData.color.g,
|
||||
textObjectData.color.b,
|
||||
];
|
||||
this._str = textObjectData.string;
|
||||
this._textAlign = textObjectData.textAlignment;
|
||||
const content = textObjectData.content;
|
||||
this._characterSize = Math.max(1, content.characterSize);
|
||||
this._fontName = content.font;
|
||||
this._bold = content.bold;
|
||||
this._italic = content.italic;
|
||||
this._underlined = content.underlined;
|
||||
this._color = gdjs.rgbOrHexToRGBColor(content.color);
|
||||
this._str = content.text;
|
||||
this._textAlign = content.textAlignment;
|
||||
|
||||
this._isOutlineEnabled = content.isOutlineEnabled;
|
||||
this._outlineThickness = content.outlineThickness;
|
||||
this._outlineColor = gdjs.rgbOrHexToRGBColor(content.outlineColor);
|
||||
|
||||
this._shadow = content.isShadowEnabled;
|
||||
this._shadowColor = gdjs.rgbOrHexToRGBColor(content.shadowColor);
|
||||
this._shadowOpacity = content.shadowOpacity;
|
||||
this._shadowDistance = content.shadowDistance;
|
||||
this._shadowBlur = content.shadowBlurRadius;
|
||||
this._shadowAngle = content.shadowAngle;
|
||||
|
||||
this._renderer = new gdjs.TextRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer
|
||||
@@ -99,40 +120,58 @@ namespace gdjs {
|
||||
oldObjectData: TextObjectData,
|
||||
newObjectData: TextObjectData
|
||||
): boolean {
|
||||
if (oldObjectData.characterSize !== newObjectData.characterSize) {
|
||||
this.setCharacterSize(newObjectData.characterSize);
|
||||
const oldContent = oldObjectData.content;
|
||||
const newContent = newObjectData.content;
|
||||
if (oldContent.characterSize !== newContent.characterSize) {
|
||||
this.setCharacterSize(newContent.characterSize);
|
||||
}
|
||||
if (oldObjectData.font !== newObjectData.font) {
|
||||
this.setFontName(newObjectData.font);
|
||||
if (oldContent.font !== newContent.font) {
|
||||
this.setFontName(newContent.font);
|
||||
}
|
||||
if (oldObjectData.bold !== newObjectData.bold) {
|
||||
this.setBold(newObjectData.bold);
|
||||
if (oldContent.bold !== newContent.bold) {
|
||||
this.setBold(newContent.bold);
|
||||
}
|
||||
if (oldObjectData.italic !== newObjectData.italic) {
|
||||
this.setItalic(newObjectData.italic);
|
||||
if (oldContent.italic !== newContent.italic) {
|
||||
this.setItalic(newContent.italic);
|
||||
}
|
||||
if (
|
||||
oldObjectData.color.r !== newObjectData.color.r ||
|
||||
oldObjectData.color.g !== newObjectData.color.g ||
|
||||
oldObjectData.color.b !== newObjectData.color.b
|
||||
) {
|
||||
this.setColor(
|
||||
'' +
|
||||
newObjectData.color.r +
|
||||
';' +
|
||||
newObjectData.color.g +
|
||||
';' +
|
||||
newObjectData.color.b
|
||||
);
|
||||
if (oldContent.color !== newContent.color) {
|
||||
this.setColor(newContent.color);
|
||||
}
|
||||
if (oldObjectData.string !== newObjectData.string) {
|
||||
this.setString(newObjectData.string);
|
||||
if (oldContent.text !== newContent.text) {
|
||||
this.setText(newContent.text);
|
||||
}
|
||||
if (oldObjectData.underlined !== newObjectData.underlined) {
|
||||
if (oldContent.underlined !== newContent.underlined) {
|
||||
return false;
|
||||
}
|
||||
if (oldObjectData.textAlignment !== newObjectData.textAlignment) {
|
||||
this.setTextAlignment(newObjectData.textAlignment);
|
||||
if (oldContent.textAlignment !== newContent.textAlignment) {
|
||||
this.setTextAlignment(newContent.textAlignment);
|
||||
}
|
||||
if (oldContent.isOutlineEnabled !== newContent.isOutlineEnabled) {
|
||||
this.setOutlineEnabled(newContent.isOutlineEnabled);
|
||||
}
|
||||
if (oldContent.outlineThickness !== newContent.outlineThickness) {
|
||||
this.setOutlineThickness(newContent.outlineThickness);
|
||||
}
|
||||
if (oldContent.outlineColor !== newContent.outlineColor) {
|
||||
this.setOutlineColor(newContent.outlineColor);
|
||||
}
|
||||
if (oldContent.isShadowEnabled !== newContent.isShadowEnabled) {
|
||||
this.showShadow(newContent.isShadowEnabled);
|
||||
}
|
||||
if (oldContent.shadowColor !== newContent.shadowColor) {
|
||||
this.setShadowColor(newContent.shadowColor);
|
||||
}
|
||||
if (oldContent.shadowOpacity !== newContent.shadowOpacity) {
|
||||
this.setShadowOpacity(newContent.shadowOpacity);
|
||||
}
|
||||
if (oldContent.shadowDistance !== newContent.shadowDistance) {
|
||||
this.setShadowDistance(newContent.shadowDistance);
|
||||
}
|
||||
if (oldContent.shadowAngle !== newContent.shadowAngle) {
|
||||
this.setShadowAngle(newContent.shadowAngle);
|
||||
}
|
||||
if (oldContent.shadowBlurRadius !== newContent.shadowBlurRadius) {
|
||||
this.setShadowBlurRadius(newContent.shadowBlurRadius);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -173,7 +212,7 @@ namespace gdjs {
|
||||
/**
|
||||
* Set object position on X axis.
|
||||
*/
|
||||
setX(x): void {
|
||||
setX(x: float): void {
|
||||
super.setX(x);
|
||||
this._updateTextPosition();
|
||||
}
|
||||
@@ -181,7 +220,7 @@ namespace gdjs {
|
||||
/**
|
||||
* Set object position on Y axis.
|
||||
*/
|
||||
setY(y): void {
|
||||
setY(y: float): void {
|
||||
super.setY(y);
|
||||
this._updateTextPosition();
|
||||
}
|
||||
@@ -198,7 +237,7 @@ namespace gdjs {
|
||||
/**
|
||||
* Set object opacity.
|
||||
*/
|
||||
setOpacity(opacity): void {
|
||||
setOpacity(opacity: float): void {
|
||||
if (opacity < 0) {
|
||||
opacity = 0;
|
||||
}
|
||||
@@ -292,7 +331,7 @@ namespace gdjs {
|
||||
* Set bold for the object text.
|
||||
* @param enable {boolean} true to have a bold text, false otherwise.
|
||||
*/
|
||||
setBold(enable): void {
|
||||
setBold(enable: boolean): void {
|
||||
this._bold = enable;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
@@ -308,7 +347,7 @@ namespace gdjs {
|
||||
* Set italic for the object text.
|
||||
* @param enable {boolean} true to have an italic text, false otherwise.
|
||||
*/
|
||||
setItalic(enable): void {
|
||||
setItalic(enable: boolean): void {
|
||||
this._italic = enable;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
@@ -485,6 +524,7 @@ namespace gdjs {
|
||||
* Set the outline for the text object.
|
||||
* @param str color as a "R;G;B" string, for example: "255;0;0"
|
||||
* @param thickness thickness of the outline (0 = disabled)
|
||||
* @deprecated Prefer independent setters.
|
||||
*/
|
||||
setOutline(str: string, thickness: number): void {
|
||||
const color = str.split(';');
|
||||
@@ -498,12 +538,48 @@ namespace gdjs {
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
isOutlineEnabled(): boolean {
|
||||
return this._isOutlineEnabled;
|
||||
}
|
||||
|
||||
setOutlineEnabled(enable: boolean): void {
|
||||
this._isOutlineEnabled = enable;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the outline thickness of the text object.
|
||||
* @return the outline thickness
|
||||
*/
|
||||
getOutlineThickness(): number {
|
||||
return this._outlineThickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the outline thickness of the text object.
|
||||
* @param value the outline thickness
|
||||
*/
|
||||
setOutlineThickness(value: float): void {
|
||||
this._outlineThickness = value;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow color of the text object.
|
||||
* @param color the shadow color as a "R;G;B" string, for example: "255;0;0"
|
||||
*/
|
||||
setOutlineColor(color: string): void {
|
||||
this._outlineColor = gdjs.rgbOrHexToRGBColor(color);
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow for the text object.
|
||||
* @param str color as a "R;G;B" string, for example: "255;0;0"
|
||||
* @param distance distance between the shadow and the text, in pixels.
|
||||
* @param blur amount of shadow blur, in pixels.
|
||||
* @param angle shadow offset direction, in degrees.
|
||||
* @deprecated Prefer independent setters.
|
||||
*/
|
||||
setShadow(
|
||||
str: string,
|
||||
@@ -525,6 +601,96 @@ namespace gdjs {
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
isShadowEnabled(): boolean {
|
||||
return this._shadow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the shadow of the text object.
|
||||
* @param enable true to show the shadow, false to hide it
|
||||
*/
|
||||
showShadow(enable: boolean): void {
|
||||
this._shadow = enable;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shadow opacity of the text object.
|
||||
* @return the opacity (0 - 255)
|
||||
*/
|
||||
getShadowOpacity(): number {
|
||||
return this._shadowOpacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow opacity of the text object.
|
||||
* @param value the opacity (0 - 255)
|
||||
*/
|
||||
setShadowOpacity(value: float): void {
|
||||
this._shadowOpacity = value;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shadow offset distance of the text object.
|
||||
* @return the shadow offset distance
|
||||
*/
|
||||
getShadowDistance(): number {
|
||||
return this._shadowDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow offset distance of the text object.
|
||||
* @param value the shadow offset distance
|
||||
*/
|
||||
setShadowDistance(value: float): void {
|
||||
this._shadowDistance = value;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shadow offset angle of the text object.
|
||||
* @return the shadow offset angle in degrees
|
||||
*/
|
||||
getShadowAngle(): number {
|
||||
return this._shadowAngle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow offset angle of the text object.
|
||||
* @param value the shadow offset angle in degrees
|
||||
*/
|
||||
setShadowAngle(value: float): void {
|
||||
this._shadowAngle = value;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shadow blur radius of the text object.
|
||||
* @return the shadow blur radius
|
||||
*/
|
||||
getShadowBlurRadius(): number {
|
||||
return this._shadowBlur;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow blur radius of the text object.
|
||||
* @param value the shadow blur radius
|
||||
*/
|
||||
setShadowBlurRadius(value: float): void {
|
||||
this._shadowBlur = value;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shadow color of the text object.
|
||||
* @param color the shadow color as a "R;G;B" string, for example: "255;0;0"
|
||||
*/
|
||||
setShadowColor(color: string): void {
|
||||
this._shadowColor = gdjs.rgbOrHexToRGBColor(color);
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gradient for the text object.
|
||||
* @param strFirstColor color as a "R;G;B" string, for example: "255;0;0"
|
||||
@@ -578,15 +744,6 @@ namespace gdjs {
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the shadow of the text object.
|
||||
* @param enable true to show the shadow, false to hide it
|
||||
*/
|
||||
showShadow(enable: boolean): void {
|
||||
this._shadow = enable;
|
||||
this._renderer.updateStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get padding of the text object.
|
||||
* @return number of pixels around the text before it gets cropped
|
||||
|
@@ -96,7 +96,7 @@ TopDownMovementBehavior::GetProperties(
|
||||
.SetType("Boolean");
|
||||
|
||||
gd::String viewpoint = behaviorContent.GetStringAttribute("viewpoint");
|
||||
gd::String viewpointStr = _("Viewpoint");
|
||||
gd::String viewpointStr = _("Top-Down");
|
||||
if (viewpoint == "TopDown")
|
||||
viewpointStr = _("Top-Down");
|
||||
else if (viewpoint == "PixelIsometry")
|
||||
@@ -108,6 +108,7 @@ TopDownMovementBehavior::GetProperties(
|
||||
properties["Viewpoint"]
|
||||
.SetLabel(_("Viewpoint"))
|
||||
.SetGroup(_("Viewpoint"))
|
||||
.SetAdvanced()
|
||||
.SetValue(viewpointStr)
|
||||
.SetType("Choice")
|
||||
.AddExtraInfo(_("Top-Down"))
|
||||
@@ -117,6 +118,7 @@ TopDownMovementBehavior::GetProperties(
|
||||
properties["CustomIsometryAngle"]
|
||||
.SetLabel(_("Custom isometry angle (between 1deg and 44deg)"))
|
||||
.SetGroup(_("Viewpoint"))
|
||||
.SetAdvanced()
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetValue(gd::String::From(
|
||||
@@ -126,6 +128,7 @@ TopDownMovementBehavior::GetProperties(
|
||||
properties["MovementAngleOffset"]
|
||||
.SetLabel(_("Movement angle offset"))
|
||||
.SetGroup(_("Viewpoint"))
|
||||
.SetAdvanced()
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetDegreeAngle())
|
||||
.SetValue(gd::String::From(
|
||||
|
@@ -567,7 +567,7 @@ module.exports = {
|
||||
'Progress',
|
||||
_('Tween progress'),
|
||||
_('the progress of a tween (between 0.0 and 1.0)'),
|
||||
_('the progress of a tween'),
|
||||
_('the progress of the scene tween _PARAM1_'),
|
||||
_('Scene Tweens'),
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
@@ -893,7 +893,7 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectPositionXTween2');
|
||||
|
||||
// deprecated use the 3D Tween extension
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
'AddObjectPositionZTween',
|
||||
@@ -926,6 +926,38 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectPositionZTween');
|
||||
|
||||
behavior
|
||||
.addAction(
|
||||
'AddObjectPositionZTween2',
|
||||
_('Tween object Z position'),
|
||||
_(
|
||||
'Tweens an object Z position (3D objects only) from its current Z position to a new one.'
|
||||
),
|
||||
_(
|
||||
'Tween the Z position of _PARAM0_ to _PARAM4_ with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Position'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To Z'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectPositionZTween2');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1079,6 +1111,38 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectDepthTween');
|
||||
|
||||
behavior
|
||||
.addAction(
|
||||
'AddObjectDepthTween2',
|
||||
_('Tween object depth'),
|
||||
_(
|
||||
'Tweens an object depth (suitable 3D objects only) from its current depth to a new one.'
|
||||
),
|
||||
_(
|
||||
'Tween the depth of _PARAM0_ to _PARAM4_ with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Size'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To depth'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectDepthTween2');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1203,6 +1267,66 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectAngleTween2');
|
||||
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectRotationXTween',
|
||||
_('Tween object rotation on X axis'),
|
||||
_('Tweens an object rotation on X axis from its current angle to a new one.'),
|
||||
_(
|
||||
'Tween the rotation on X axis of _PARAM0_ to _PARAM4_° with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Angle'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectRotationXTween');
|
||||
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectRotationYTween',
|
||||
_('Tween object rotation on Y axis'),
|
||||
_('Tweens an object rotation on Y axis from its current angle to a new one.'),
|
||||
_(
|
||||
'Tween the rotation on Y axis of _PARAM0_ to _PARAM4_° with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Angle'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectRotationYTween');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1239,6 +1363,7 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectScaleTween');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectScaleTween2',
|
||||
@@ -1253,6 +1378,7 @@ module.exports = {
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.setHidden()
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
@@ -1273,6 +1399,39 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectScaleTween2');
|
||||
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectScaleTween3',
|
||||
_('Tween object scale'),
|
||||
_(
|
||||
'Tweens an object scale from its current value to a new one (note: the scale can never be 0 or less).'
|
||||
),
|
||||
_(
|
||||
'Tween the scale of _PARAM0_ to _PARAM3_ (from center: _PARAM7_) with easing _PARAM4_ over _PARAM5_ seconds as _PARAM2_'
|
||||
),
|
||||
_('Size'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.addParameter('yesorno', _('Scale from center of object'), '', false)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectScaleTween3');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1900,7 +2059,7 @@ module.exports = {
|
||||
'Progress',
|
||||
_('Tween progress'),
|
||||
_('the progress of a tween (between 0.0 and 1.0)'),
|
||||
_('the progress of a tween'),
|
||||
_('the progress of the tween _PARAM2_'),
|
||||
'',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
|
@@ -10,210 +10,256 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
};
|
||||
|
||||
/** @type {gdjs.RuntimeScene} */
|
||||
let layout;
|
||||
let runtimeScene;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
beforeEach(() => {
|
||||
layout = createScene();
|
||||
layout.getLayer('').setTimeScale(1.5);
|
||||
runtimeScene = createScene();
|
||||
runtimeScene.getLayer('').setTimeScale(1.5);
|
||||
});
|
||||
|
||||
const tween = gdjs.evtTools.tween;
|
||||
const camera = gdjs.evtTools.camera;
|
||||
|
||||
it("can get default values for tweens that don't exist", () => {
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(0);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(0);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(0);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(0);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
});
|
||||
|
||||
it('can play a tween till the end', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
|
||||
// Tween actions don't change the value directly.
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(200);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(200);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(0);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(200);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(200);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(0);
|
||||
|
||||
let oldAngle;
|
||||
let oldValue;
|
||||
let oldProgress;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
oldAngle = camera.getCameraRotation(layout, '', 0);
|
||||
oldValue = tween.getValue(layout, 'MyTween');
|
||||
oldProgress = tween.getProgress(layout, 'MyTween');
|
||||
oldAngle = camera.getCameraRotation(runtimeScene, '', 0);
|
||||
oldValue = tween.getValue(runtimeScene, 'MyTween');
|
||||
oldProgress = tween.getProgress(runtimeScene, 'MyTween');
|
||||
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be.above(oldAngle);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be.above(oldValue);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be.above(oldProgress);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be.above(
|
||||
oldAngle
|
||||
);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be.above(oldValue);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be.above(
|
||||
oldProgress
|
||||
);
|
||||
}
|
||||
// The tween reaches the end
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(600);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(600);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(1);
|
||||
|
||||
// The value is not changed after the tween is finished
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(600);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(600);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(1);
|
||||
|
||||
// The value is not set to the targeted value over and over
|
||||
// after the tween is finished.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
});
|
||||
|
||||
it('can pause and resume a tween', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
|
||||
// The tween starts
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
|
||||
// Pause the tween
|
||||
tween.pauseSceneTween(layout, 'MyTween');
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
tween.pauseSceneTween(runtimeScene, 'MyTween');
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
}
|
||||
|
||||
// The value is not overridden during the pause.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
// Resume the tween
|
||||
tween.resumeSceneTween(layout, 'MyTween');
|
||||
tween.resumeSceneTween(runtimeScene, 'MyTween');
|
||||
|
||||
// Tween actions don't change the value directly.
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(440);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(440);
|
||||
});
|
||||
|
||||
it('can stop and restart a tween', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
|
||||
// Start the tween
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
|
||||
// Stop the tween
|
||||
tween.stopSceneTween(layout, 'MyTween', false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
tween.stopSceneTween(runtimeScene, 'MyTween', false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
}
|
||||
|
||||
// The value is not overridden by a stopped tween.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
// A stopped tween can't be resumed.
|
||||
tween.resumeSceneTween(layout, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
tween.resumeSceneTween(runtimeScene, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
|
||||
// Restart the tween
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 623, '', 'linear', 0.25);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
623,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(373);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(373);
|
||||
});
|
||||
|
||||
it('can remove and recreate a tween', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
|
||||
// Start the tween
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
|
||||
// Remove the tween
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
tween.removeSceneTween(layout, 'MyTween');
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
tween.removeSceneTween(runtimeScene, 'MyTween');
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
}
|
||||
|
||||
// The value is not overridden after the tween has been removed.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
// A removed tween can't be resumed.
|
||||
tween.resumeSceneTween(layout, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
tween.resumeSceneTween(runtimeScene, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
|
||||
// Recreate the tween
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 623, '', 'linear', 0.25);
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
623,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(373);
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(373);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
});
|
||||
|
||||
const checkProgress = (steps, getValueFunctions) => {
|
||||
@@ -222,7 +268,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
}
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const oldValues = getValueFunctions.map((getValue) => getValue());
|
||||
layout.renderAndStep(1000 / 60);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
for (let index = 0; index < oldValues.length; index++) {
|
||||
expect(getValueFunctions[index]()).not.to.be(oldValues[index]);
|
||||
@@ -231,10 +277,10 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
};
|
||||
|
||||
it('can tween a scene variable', () => {
|
||||
const variable = layout.getVariables().get('MyVariable');
|
||||
const variable = runtimeScene.getVariables().get('MyVariable');
|
||||
variable.setNumber(200);
|
||||
tween.tweenVariableNumber3(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
variable,
|
||||
600,
|
||||
@@ -247,7 +293,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
|
||||
it('can tween a layer value', () => {
|
||||
tween.addLayerValueTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
200,
|
||||
600,
|
||||
@@ -256,13 +302,13 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
false,
|
||||
''
|
||||
);
|
||||
checkProgress(6, () => tween.getValue(layout, 'MyTween'));
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(440);
|
||||
checkProgress(6, () => tween.getValue(runtimeScene, 'MyTween'));
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a layout value', () => {
|
||||
tween.addLayoutValueTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
200,
|
||||
600,
|
||||
@@ -270,39 +316,69 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
0.25 / 1.5,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => tween.getValue(layout, 'MyTween'));
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(440);
|
||||
checkProgress(6, () => tween.getValue(runtimeScene, 'MyTween'));
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a layer camera position', () => {
|
||||
camera.setCameraX(layout, 200, '', 0);
|
||||
camera.setCameraY(layout, 300, '', 0);
|
||||
tween.tweenCamera2(layout, 'MyTween', 600, 900, '', 'linear', 0.25);
|
||||
camera.setCameraX(runtimeScene, 200, '', 0);
|
||||
camera.setCameraY(runtimeScene, 300, '', 0);
|
||||
tween.tweenCamera2(runtimeScene, 'MyTween', 600, 900, '', 'linear', 0.25);
|
||||
checkProgress(6, [
|
||||
() => camera.getCameraX(layout, '', 0),
|
||||
() => camera.getCameraY(layout, '', 0),
|
||||
() => camera.getCameraX(runtimeScene, '', 0),
|
||||
() => camera.getCameraY(runtimeScene, '', 0),
|
||||
]);
|
||||
expect(camera.getCameraX(layout, '', 0)).to.be(440);
|
||||
expect(camera.getCameraY(layout, '', 0)).to.be(660);
|
||||
expect(camera.getCameraX(runtimeScene, '', 0)).to.be(440);
|
||||
expect(camera.getCameraY(runtimeScene, '', 0)).to.be(660);
|
||||
});
|
||||
|
||||
it('can tween a layer camera zoom', () => {
|
||||
camera.setCameraZoom(layout, 200, '', 0);
|
||||
tween.tweenCameraZoom2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
checkProgress(6, () => camera.getCameraZoom(layout, '', 0));
|
||||
camera.setCameraZoom(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraZoom2(runtimeScene, 'MyTween', 600, '', 'linear', 0.25);
|
||||
checkProgress(6, () => camera.getCameraZoom(runtimeScene, '', 0));
|
||||
// The interpolation is exponential.
|
||||
expect(camera.getCameraZoom(layout, '', 0)).to.be(386.6364089863524);
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(386.6364089863524);
|
||||
});
|
||||
|
||||
it('can tween a layer camera zoom to 0', () => {
|
||||
camera.setCameraZoom(runtimeScene, 1, '', 0);
|
||||
tween.tweenCameraZoom2(runtimeScene, 'MyTween', 0, '', '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++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('can tween a layer camera zoom from 0', () => {
|
||||
camera.setCameraZoom(runtimeScene, 0, '', 0);
|
||||
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);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(1);
|
||||
});
|
||||
|
||||
it('can tween a layer camera rotation', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
checkProgress(6, () => camera.getCameraRotation(layout, '', 0));
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(440);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
checkProgress(6, () => camera.getCameraRotation(runtimeScene, '', 0));
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a number effect property', () => {
|
||||
const layer = layout.getLayer('');
|
||||
const layer = runtimeScene.getLayer('');
|
||||
layer.addEffect({
|
||||
effectType: 'Outline',
|
||||
name: 'MyEffect',
|
||||
@@ -311,7 +387,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
booleanParameters: {},
|
||||
});
|
||||
tween.tweenNumberEffectPropertyTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
@@ -329,7 +405,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
});
|
||||
|
||||
it('can tween a color effect property', () => {
|
||||
const layer = layout.getLayer('');
|
||||
const layer = runtimeScene.getLayer('');
|
||||
layer.addEffect({
|
||||
effectType: 'Outline',
|
||||
name: 'MyEffect',
|
||||
@@ -338,7 +414,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
booleanParameters: {},
|
||||
});
|
||||
tween.tweenColorEffectPropertyTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
'255;192;128',
|
||||
'',
|
||||
|
@@ -85,6 +85,32 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
const addCube = (runtimeScene) => {
|
||||
const object = new gdjs.Cube3DRuntimeObject(runtimeScene, {
|
||||
name: 'Cube',
|
||||
type: 'Scene3D::Cube3DObject',
|
||||
effects: [],
|
||||
variables: [],
|
||||
behaviors: [
|
||||
{
|
||||
type: 'Tween::TweenBehavior',
|
||||
name: behaviorName,
|
||||
},
|
||||
],
|
||||
// @ts-ignore
|
||||
content: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
depth: 64,
|
||||
},
|
||||
});
|
||||
runtimeScene.addObject(object);
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
@@ -100,18 +126,25 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
name: behaviorName,
|
||||
},
|
||||
],
|
||||
characterSize: 20,
|
||||
font: '',
|
||||
bold: false,
|
||||
italic: false,
|
||||
underlined: false,
|
||||
color: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
content: {
|
||||
characterSize: 20,
|
||||
font: '',
|
||||
bold: false,
|
||||
italic: false,
|
||||
underlined: false,
|
||||
color: '0;0;0',
|
||||
text: '',
|
||||
textAlignment: 'left',
|
||||
isOutlineEnabled: false,
|
||||
outlineThickness: 2,
|
||||
outlineColor: '255;255;255',
|
||||
isShadowEnabled: false,
|
||||
shadowColor: '0;0;0',
|
||||
shadowOpacity: 128,
|
||||
shadowDistance: 4,
|
||||
shadowAngle: 90,
|
||||
shadowBlurRadius: 2,
|
||||
},
|
||||
string: '',
|
||||
textAlignment: 'left',
|
||||
});
|
||||
runtimeScene.addObject(object);
|
||||
return object;
|
||||
@@ -123,6 +156,8 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
let object;
|
||||
/** @type {gdjs.SpriteRuntimeObject} */
|
||||
let sprite;
|
||||
/** @type {gdjs.Cube3DRuntimeObject} */
|
||||
let cube;
|
||||
/** @type {gdjs.TextRuntimeObject} */
|
||||
let textObject;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
@@ -130,18 +165,23 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let spriteBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let cubeBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let textObjectBehavior;
|
||||
beforeEach(() => {
|
||||
runtimeScene = createScene();
|
||||
runtimeScene.getLayer('').setTimeScale(1.5);
|
||||
object = addObject(runtimeScene);
|
||||
sprite = addSprite(runtimeScene);
|
||||
cube = addCube(runtimeScene);
|
||||
textObject = addTextObject(runtimeScene);
|
||||
//@ts-ignore
|
||||
behavior = object.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
spriteBehavior = sprite.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
cubeBehavior = cube.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
textObjectBehavior = textObject.getBehavior(behaviorName);
|
||||
});
|
||||
|
||||
@@ -216,6 +256,19 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getY()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the position on Z axis', () => {
|
||||
cube.setZ(200);
|
||||
cubeBehavior.addObjectPositionZTween(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
250 / 1.5,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getZ());
|
||||
expect(cube.getZ()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the angle', () => {
|
||||
object.setAngle(200);
|
||||
behavior.addObjectAngleTween('MyTween', 600, 'linear', 250 / 1.5, false);
|
||||
@@ -237,6 +290,19 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getHeight()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the depth', () => {
|
||||
cube.setDepth(200);
|
||||
cubeBehavior.addObjectDepthTween(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
250 / 1.5,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getDepth());
|
||||
expect(cube.getDepth()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the opacity', () => {
|
||||
sprite.setOpacity(128);
|
||||
spriteBehavior.addObjectOpacityTween(
|
||||
@@ -424,4 +490,46 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(sprite.getX()).to.be(-7580);
|
||||
expect(sprite.getY()).to.be(-11120);
|
||||
});
|
||||
|
||||
it('can tween the scales in seconds', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scales from center in seconds', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
true
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getX()).to.be(-5872.3650875632775);
|
||||
expect(sprite.getY()).to.be(-8558.547631344918);
|
||||
});
|
||||
});
|
||||
|
@@ -85,6 +85,32 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
const addCube = (runtimeScene) => {
|
||||
const object = new gdjs.Cube3DRuntimeObject(runtimeScene, {
|
||||
name: 'Cube',
|
||||
type: 'Scene3D::Cube3DObject',
|
||||
effects: [],
|
||||
variables: [],
|
||||
behaviors: [
|
||||
{
|
||||
type: 'Tween::TweenBehavior',
|
||||
name: behaviorName,
|
||||
},
|
||||
],
|
||||
// @ts-ignore
|
||||
content: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
depth: 64,
|
||||
},
|
||||
});
|
||||
runtimeScene.addObject(object);
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
@@ -100,18 +126,25 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
name: behaviorName,
|
||||
},
|
||||
],
|
||||
characterSize: 20,
|
||||
font: '',
|
||||
bold: false,
|
||||
italic: false,
|
||||
underlined: false,
|
||||
color: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
content: {
|
||||
characterSize: 20,
|
||||
font: '',
|
||||
bold: false,
|
||||
italic: false,
|
||||
underlined: false,
|
||||
color: '0;0;0',
|
||||
text: '',
|
||||
textAlignment: 'left',
|
||||
isOutlineEnabled: false,
|
||||
outlineThickness: 2,
|
||||
outlineColor: '255;255;255',
|
||||
isShadowEnabled: false,
|
||||
shadowColor: '0;0;0',
|
||||
shadowOpacity: 128,
|
||||
shadowDistance: 4,
|
||||
shadowAngle: 90,
|
||||
shadowBlurRadius: 2,
|
||||
},
|
||||
string: '',
|
||||
textAlignment: 'left',
|
||||
});
|
||||
runtimeScene.addObject(object);
|
||||
return object;
|
||||
@@ -123,6 +156,8 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
let object;
|
||||
/** @type {gdjs.SpriteRuntimeObject} */
|
||||
let sprite;
|
||||
/** @type {gdjs.Cube3DRuntimeObject} */
|
||||
let cube;
|
||||
/** @type {gdjs.TextRuntimeObject} */
|
||||
let textObject;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
@@ -130,18 +165,23 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let spriteBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let cubeBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let textObjectBehavior;
|
||||
beforeEach(() => {
|
||||
runtimeScene = createScene();
|
||||
runtimeScene.getLayer('').setTimeScale(1.5);
|
||||
object = addObject(runtimeScene);
|
||||
sprite = addSprite(runtimeScene);
|
||||
cube = addCube(runtimeScene);
|
||||
textObject = addTextObject(runtimeScene);
|
||||
//@ts-ignore
|
||||
behavior = object.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
spriteBehavior = sprite.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
cubeBehavior = cube.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
textObjectBehavior = textObject.getBehavior(behaviorName);
|
||||
});
|
||||
|
||||
@@ -404,6 +444,20 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getY()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the position on Z axis', () => {
|
||||
cube.setZ(200);
|
||||
cubeBehavior.addObjectPositionZTween2(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getZ());
|
||||
expect(cube.getZ()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the angle', () => {
|
||||
object.setAngle(200);
|
||||
behavior.addObjectAngleTween2('MyTween', 600, 'linear', 0.25, false);
|
||||
@@ -411,6 +465,34 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getAngle()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the rotation X', () => {
|
||||
cube.setRotationX(200);
|
||||
cubeBehavior.addObjectRotationXTween(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getRotationX());
|
||||
expect(cube.getRotationX()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the rotation Y', () => {
|
||||
cube.setRotationY(200);
|
||||
cubeBehavior.addObjectRotationYTween(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getRotationY());
|
||||
expect(cube.getRotationY()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the width', () => {
|
||||
object.setWidth(200);
|
||||
behavior.addObjectWidthTween2('MyTween', 600, 'linear', 0.25, false);
|
||||
@@ -425,6 +507,20 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getHeight()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the depth', () => {
|
||||
cube.setDepth(200);
|
||||
cubeBehavior.addObjectDepthTween2(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getDepth());
|
||||
expect(cube.getDepth()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a number effect property', () => {
|
||||
sprite.addEffect({
|
||||
effectType: 'Outline',
|
||||
@@ -540,6 +636,53 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on X axis to 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(1);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
0,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 0 directly.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(sprite.getScaleX()).to.be(0);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on X axis from 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(0);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
1,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 1 directly at the end.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getScaleX()).to.be(1);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on X axis from center', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
@@ -576,6 +719,53 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on Y axis to 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleY(1);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
0,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 0 directly.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(sprite.getScaleY()).to.be(0);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on Y axis from 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleY(0);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
1,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 1 directly at the end.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getScaleY()).to.be(1);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on Y axis from center', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleY(200);
|
||||
@@ -623,45 +813,126 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getY()).to.be(660);
|
||||
});
|
||||
|
||||
it('can tween the scales', () => {
|
||||
it('can tween the scale', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
sprite.setScale(200);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
checkProgress(6, () => sprite.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getScale()).to.be(386.6364089863524);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale to 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScale(1);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
0,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 0 directly.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale from 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScale(0);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
1,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 1 directly at the end.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getScale()).to.be(1);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scales from center', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
sprite.setScale(200);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
true
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
checkProgress(6, () => sprite.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getScale()).to.be(386.6364089863524);
|
||||
expect(sprite.getX()).to.be(-5872.3650875632775);
|
||||
expect(sprite.getY()).to.be(-8558.547631344918);
|
||||
expect(sprite.getY()).to.be(-5572.3650875632775);
|
||||
});
|
||||
|
||||
it('can tween the scale of a cube', () => {
|
||||
cube.setPosition(100, 400);
|
||||
cube.setZ(800);
|
||||
cube.setScale(200);
|
||||
cubeBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(cube.getScale()).to.be(386.6364089863524);
|
||||
expect(cube.getX()).to.be(100);
|
||||
expect(cube.getY()).to.be(400);
|
||||
expect(cube.getZ()).to.be(800);
|
||||
});
|
||||
|
||||
it('can tween the scales of a cube from center', () => {
|
||||
cube.setPosition(100, 400);
|
||||
cube.setZ(800);
|
||||
cube.setScale(200);
|
||||
cubeBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
true
|
||||
);
|
||||
checkProgress(6, () => cube.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(cube.getScale()).to.be(386.6364089863524);
|
||||
expect(cube.getX()).to.be(-5872.3650875632775);
|
||||
expect(cube.getY()).to.be(-5572.3650875632775);
|
||||
expect(cube.getZ()).to.be(-5172.3650875632775);
|
||||
});
|
||||
});
|
||||
|
@@ -15,27 +15,44 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
function isScalable(
|
||||
o: gdjs.RuntimeObject
|
||||
): o is gdjs.RuntimeObject & gdjs.Scalable {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return o.setScaleX && o.setScaleY && o.getScaleX && o.getScaleY;
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.Scalable {
|
||||
return (
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
object.setScaleX &&
|
||||
//@ts-ignore
|
||||
object.setScaleY &&
|
||||
//@ts-ignore
|
||||
object.getScaleX &&
|
||||
//@ts-ignore
|
||||
object.getScaleY
|
||||
);
|
||||
}
|
||||
|
||||
function isOpaque(
|
||||
o: gdjs.RuntimeObject
|
||||
): o is gdjs.RuntimeObject & gdjs.OpacityHandler {
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.OpacityHandler {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return o.setOpacity && o.getOpacity;
|
||||
return object.setOpacity && object.getOpacity;
|
||||
}
|
||||
|
||||
function isColorable(o: gdjs.RuntimeObject): o is IColorable {
|
||||
function is3D(
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.Base3DHandler {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return o.setColor && o.getColor;
|
||||
return object.getZ && object.setZ;
|
||||
}
|
||||
|
||||
function isCharacterScalable(o: gdjs.RuntimeObject): o is ICharacterScalable {
|
||||
function isColorable(object: gdjs.RuntimeObject): object is IColorable {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return o.setCharacterSize && o.getCharacterSize;
|
||||
return object.setColor && object.getColor;
|
||||
}
|
||||
|
||||
function isCharacterScalable(
|
||||
object: gdjs.RuntimeObject
|
||||
): object is ICharacterScalable {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return object.setCharacterSize && object.getCharacterSize;
|
||||
}
|
||||
|
||||
const linearInterpolation = gdjs.evtTools.common.lerp;
|
||||
@@ -456,7 +473,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Tween an object Z position.
|
||||
* @deprecated Use the 3D Tween extension instead.
|
||||
* @deprecated Use addObjectPositionZTween2 instead.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toZ The target Z position
|
||||
* @param easing Easing function identifier
|
||||
@@ -469,14 +486,59 @@ namespace gdjs {
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectPositionZTween(
|
||||
identifier,
|
||||
toZ,
|
||||
easing,
|
||||
duration / 1000,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner.getRuntimeScene()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object Z position.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toZ The target Z position
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectPositionZTween2(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toZ: number,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectPositionZTween(
|
||||
identifier,
|
||||
toZ,
|
||||
easing,
|
||||
duration,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner
|
||||
);
|
||||
}
|
||||
|
||||
private _addObjectPositionZTween(
|
||||
identifier: string,
|
||||
toZ: number,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean,
|
||||
timeSource: gdjs.evtTools.tween.TimeSource
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
|
||||
if (!is3D(owner)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner.getRuntimeScene(),
|
||||
duration / 1000,
|
||||
timeSource,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getZ(),
|
||||
@@ -558,6 +620,72 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween a 3D object rotation X.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toAngle The target angle
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectRotationXTween(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toAngle: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!is3D(owner)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getRotationX(),
|
||||
toAngle,
|
||||
(value: float) => owner.setRotationX(value),
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween a 3D object rotation Y.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toAngle The target angle
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectRotationYTween(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toAngle: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!is3D(owner)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getRotationY(),
|
||||
toAngle,
|
||||
(value: float) => owner.setRotationY(value),
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object scale.
|
||||
* @deprecated Use addObjectScaleTween2 instead.
|
||||
@@ -593,6 +721,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Tween an object scale.
|
||||
* @deprecated Use addObjectScaleXTween2 and addObjectScaleYTween2 instead.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toScaleX The target X-scale
|
||||
* @param toScaleY The target Y-scale
|
||||
@@ -666,6 +795,68 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object scale.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toScale The target scale
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
* @param scaleFromCenterOfObject Scale the transform from the center of the object (or point that is called center), not the top-left origin
|
||||
*/
|
||||
addObjectScaleTween3(
|
||||
identifier: string,
|
||||
toScale: number,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean,
|
||||
scaleFromCenterOfObject: boolean
|
||||
) {
|
||||
this._addObjectScaleXTween(
|
||||
identifier,
|
||||
toScale,
|
||||
easing,
|
||||
duration,
|
||||
destroyObjectWhenFinished,
|
||||
scaleFromCenterOfObject,
|
||||
this.owner,
|
||||
exponentialInterpolation
|
||||
);
|
||||
const owner = this.owner;
|
||||
if (!isScalable(owner)) return;
|
||||
|
||||
// This action doesn't require 3D capabilities.
|
||||
// So, gdjs.RuntimeObject3D may not exist
|
||||
// when the 3D extension is not used.
|
||||
const owner3d = is3D(owner) ? owner : null;
|
||||
|
||||
const setValue = scaleFromCenterOfObject
|
||||
? (scale: float) => {
|
||||
const oldX = owner.getCenterXInScene();
|
||||
const oldY = owner.getCenterYInScene();
|
||||
const oldZ = owner3d ? owner3d.getCenterZInScene() : 0;
|
||||
owner.setScale(scale);
|
||||
owner.setCenterXInScene(oldX);
|
||||
owner.setCenterYInScene(oldY);
|
||||
if (owner3d) {
|
||||
owner3d.setCenterZInScene(oldZ);
|
||||
}
|
||||
}
|
||||
: (scale: float) => owner.setScale(scale);
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
duration,
|
||||
easing,
|
||||
exponentialInterpolation,
|
||||
owner.getScale(),
|
||||
toScale,
|
||||
setValue,
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object X-scale.
|
||||
* @deprecated Use addObjectScaleXTween2 instead.
|
||||
@@ -1519,7 +1710,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Tween an object depth.
|
||||
* @deprecated Use the 3D Tween extension instead.
|
||||
* @deprecated Use addObjectDepthTween2 instead.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toDepth The target depth
|
||||
* @param easing Easing function identifier
|
||||
@@ -1532,14 +1723,59 @@ namespace gdjs {
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectDepthTween(
|
||||
identifier,
|
||||
toDepth,
|
||||
easing,
|
||||
duration / 1000,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner.getRuntimeScene()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object depth.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toDepth The target depth
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectDepthTween2(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toDepth: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectDepthTween(
|
||||
identifier,
|
||||
toDepth,
|
||||
easing,
|
||||
duration,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner
|
||||
);
|
||||
}
|
||||
|
||||
private _addObjectDepthTween(
|
||||
identifier: string,
|
||||
toDepth: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean,
|
||||
timeSource: gdjs.evtTools.tween.TimeSource
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
|
||||
if (!is3D(owner)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner.getRuntimeScene(),
|
||||
duration / 1000,
|
||||
timeSource,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getDepth(),
|
||||
|
2
GDJS/.gitignore
vendored
2
GDJS/.gitignore
vendored
@@ -1 +1 @@
|
||||
/node_modules
|
||||
/node_modules
|
@@ -125,14 +125,18 @@ gd::ObjectMetadata &MetadataDeclarationHelper::DeclareObjectMetadata(
|
||||
// Note: EventsFunctionsExtension should be used instead of
|
||||
// PlatformExtension but this line will be removed soon.
|
||||
.SetCategoryFullName(extension.GetCategory())
|
||||
// Update Project::CreateObject when default behavior are added.
|
||||
// Update Project::CreateObject when default behaviors are added.
|
||||
.AddDefaultBehavior("EffectCapability::EffectBehavior")
|
||||
.AddDefaultBehavior("ResizableCapability::ResizableBehavior")
|
||||
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
|
||||
.AddDefaultBehavior("FlippableCapability::FlippableBehavior")
|
||||
.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
|
||||
.AddDefaultBehavior("FlippableCapability::FlippableBehavior");
|
||||
if (eventsBasedObject.IsRenderedIn3D()) {
|
||||
objectMetadata.MarkAsRenderedIn3D();
|
||||
objectMetadata
|
||||
.MarkAsRenderedIn3D()
|
||||
.AddDefaultBehavior("Scene3D::Base3DBehavior");
|
||||
}
|
||||
else {
|
||||
objectMetadata.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
|
||||
}
|
||||
|
||||
// TODO EBO Use full type to identify object to avoid collision.
|
||||
@@ -933,18 +937,15 @@ MetadataDeclarationHelper::DeclareObjectInstructionMetadata(
|
||||
|
||||
gd::String MetadataDeclarationHelper::GetStringifiedExtraInfo(
|
||||
const gd::PropertyDescriptor &property) {
|
||||
gd::String stringifiedExtraInfo = "";
|
||||
if (property.GetType() == "Choice") {
|
||||
stringifiedExtraInfo += "[";
|
||||
for (size_t i = 0; i < property.GetExtraInfo().size(); i++) {
|
||||
stringifiedExtraInfo += property.GetExtraInfo().at(i);
|
||||
if (i < property.GetExtraInfo().size() - 1) {
|
||||
stringifiedExtraInfo += ",";
|
||||
}
|
||||
}
|
||||
stringifiedExtraInfo += "]";
|
||||
if (property.GetType() != "Choice") {
|
||||
return "";
|
||||
}
|
||||
return stringifiedExtraInfo;
|
||||
SerializerElement element;
|
||||
element.ConsiderAsArray();
|
||||
for (auto&& value : property.GetExtraInfo()) {
|
||||
element.AddChild("").SetStringValue(value);
|
||||
}
|
||||
return Serializer::ToJSON(element);
|
||||
}
|
||||
|
||||
gd::String
|
||||
@@ -968,6 +969,9 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
addObjectAndBehaviorParameters) {
|
||||
auto &propertyType = property.GetType();
|
||||
|
||||
auto group = (eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName())
|
||||
+ " " + property.GetGroup() + " properties";
|
||||
|
||||
auto uncapitalizedLabel =
|
||||
UncapitalizeFirstLetter(property.GetLabel()) || property.GetName();
|
||||
if (propertyType == "Boolean") {
|
||||
@@ -977,7 +981,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
.FindAndReplace("<property_name>", uncapitalizedLabel),
|
||||
_("Property <property_name> of _PARAM0_ is true")
|
||||
.FindAndReplace("<property_name>", uncapitalizedLabel),
|
||||
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
|
||||
group,
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
addObjectAndBehaviorParameters(conditionMetadata);
|
||||
conditionMetadata.SetFunctionName(getterName);
|
||||
@@ -992,7 +996,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
.FindAndReplace("<property_value>",
|
||||
"_PARAM" + gd::String::From(valueParameterIndex) +
|
||||
"_"),
|
||||
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
|
||||
group,
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
addObjectAndBehaviorParameters(setterActionMetadata);
|
||||
setterActionMetadata
|
||||
@@ -1007,7 +1011,7 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
.FindAndReplace("<property_name>", uncapitalizedLabel),
|
||||
_("Toggle property <property_name> of _PARAM0_")
|
||||
.FindAndReplace("<property_name>", uncapitalizedLabel),
|
||||
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
|
||||
group,
|
||||
GetExtensionIconUrl(extension), GetExtensionIconUrl(extension));
|
||||
addObjectAndBehaviorParameters(toggleActionMetadata);
|
||||
toggleActionMetadata.SetFunctionName(toggleFunctionName);
|
||||
@@ -1018,13 +1022,14 @@ void MetadataDeclarationHelper::DeclarePropertyInstructionAndExpression(
|
||||
parameterOptions.SetTypeExtraInfo(typeExtraInfo);
|
||||
auto propertyInstructionMetadata =
|
||||
entityMetadata.AddExpressionAndConditionAndAction(
|
||||
gd::ValueTypeMetadata::ConvertPropertyTypeToValueType(propertyType),
|
||||
gd::ValueTypeMetadata::GetPrimitiveValueType(
|
||||
gd::ValueTypeMetadata::ConvertPropertyTypeToValueType(propertyType)),
|
||||
expressionName, propertyLabel,
|
||||
_("the property value for the <property_name>")
|
||||
.FindAndReplace("<property_name>", uncapitalizedLabel),
|
||||
_("the property value for the <property_name>")
|
||||
.FindAndReplace("<property_name>", uncapitalizedLabel),
|
||||
eventsBasedEntity.GetFullName() || eventsBasedEntity.GetName(),
|
||||
group,
|
||||
GetExtensionIconUrl(extension));
|
||||
addObjectAndBehaviorParameters(propertyInstructionMetadata);
|
||||
propertyInstructionMetadata
|
||||
@@ -1213,20 +1218,39 @@ void MetadataDeclarationHelper::DeclareObjectInternalInstructions(
|
||||
// Objects are identified by their name alone.
|
||||
auto &objectType = eventsBasedObject.GetName();
|
||||
|
||||
objectMetadata
|
||||
.AddScopedAction(
|
||||
"SetRotationCenter", _("Center of rotation"),
|
||||
_("Change the center of rotation of an object relatively to the "
|
||||
"object origin."),
|
||||
_("Change the center of rotation of _PARAM0_ to _PARAM1_, _PARAM2_"),
|
||||
_("Angle"), "res/actions/position24_black.png",
|
||||
"res/actions/position_black.png")
|
||||
.AddParameter("object", _("Object"), objectType)
|
||||
.AddParameter("number", _("X position"))
|
||||
.AddParameter("number", _("Y position"))
|
||||
.MarkAsAdvanced()
|
||||
.SetPrivate()
|
||||
.SetFunctionName("setRotationCenter");
|
||||
if (eventsBasedObject.IsRenderedIn3D()) {
|
||||
objectMetadata
|
||||
.AddScopedAction(
|
||||
"SetRotationCenter", _("Center of rotation"),
|
||||
_("Change the center of rotation of an object relatively to the "
|
||||
"object origin."),
|
||||
_("Change the center of rotation of _PARAM0_ to _PARAM1_ ; _PARAM2_ ; _PARAM3_"),
|
||||
_("Angle"), "res/actions/position24_black.png",
|
||||
"res/actions/position_black.png")
|
||||
.AddParameter("object", _("Object"), objectType)
|
||||
.AddParameter("number", _("X position"))
|
||||
.AddParameter("number", _("Y position"))
|
||||
.AddParameter("number", _("Z position"))
|
||||
.MarkAsAdvanced()
|
||||
.SetPrivate()
|
||||
.SetFunctionName("setRotationCenter3D");
|
||||
}
|
||||
else {
|
||||
objectMetadata
|
||||
.AddScopedAction(
|
||||
"SetRotationCenter", _("Center of rotation"),
|
||||
_("Change the center of rotation of an object relatively to the "
|
||||
"object origin."),
|
||||
_("Change the center of rotation of _PARAM0_ to _PARAM1_ ; _PARAM2_"),
|
||||
_("Angle"), "res/actions/position24_black.png",
|
||||
"res/actions/position_black.png")
|
||||
.AddParameter("object", _("Object"), objectType)
|
||||
.AddParameter("number", _("X position"))
|
||||
.AddParameter("number", _("Y position"))
|
||||
.MarkAsAdvanced()
|
||||
.SetPrivate()
|
||||
.SetFunctionName("setRotationCenter");
|
||||
}
|
||||
}
|
||||
|
||||
void MetadataDeclarationHelper::AddParameter(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user