Compare commits
38 Commits
fix/ai-mob
...
skybox
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e3532f9a3d | ||
![]() |
213e323d3b | ||
![]() |
ba5566f070 | ||
![]() |
ed3cd34832 | ||
![]() |
4cc8256037 | ||
![]() |
aef74ba19e | ||
![]() |
8acaa06e42 | ||
![]() |
27ee85b5d4 | ||
![]() |
bbe2d1854e | ||
![]() |
d338690ff5 | ||
![]() |
571a6d8c1a | ||
![]() |
ddb5157c0a | ||
![]() |
64f01354bc | ||
![]() |
37fd99e542 | ||
![]() |
23be4a5849 | ||
![]() |
64c0ee8f98 | ||
![]() |
e5ecce3abf | ||
![]() |
5c71a4da56 | ||
![]() |
dff99b79cb | ||
![]() |
5fe46ea8ea | ||
![]() |
4a590adac4 | ||
![]() |
279d41cdb7 | ||
![]() |
5cf65a9f62 | ||
![]() |
08b05c13b6 | ||
![]() |
eb55c85f4e | ||
![]() |
8a243440db | ||
![]() |
b3e4e6b89c | ||
![]() |
a1a25f6df4 | ||
![]() |
6114a6cec1 | ||
![]() |
5058964937 | ||
![]() |
4488675540 | ||
![]() |
6a2d2c9e67 | ||
![]() |
b43c42d763 | ||
![]() |
69112183d4 | ||
![]() |
a4c2778b8d | ||
![]() |
f26e56c3bf | ||
![]() |
f5f9944fc4 | ||
![]() |
9467caf1e9 |
@@ -18,8 +18,8 @@ jobs:
|
||||
# Build the **entire** app for macOS (including the GDevelop.js library).
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 14.2.0
|
||||
resource_class: macos.m1.large.gen1
|
||||
xcode: 16.4.0
|
||||
resource_class: m4pro.medium
|
||||
steps:
|
||||
- checkout
|
||||
# Install Rosetta for AWS CLI and disable TSO to speed up S3 uploads (https://support.circleci.com/hc/en-us/articles/19334402064027-Troubleshooting-slow-uploads-to-S3-for-jobs-using-an-m1-macOS-resource-class)
|
||||
|
@@ -37,8 +37,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.SetIcon("res/actions/position24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Angle"))
|
||||
.SetIcon("res/actions/direction24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
|
||||
.SetIcon("res/actions/scale24_black.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
|
||||
"res/actions/scale24_black.png");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<gd::ObjectConfiguration>(
|
||||
"", _("Base object"), _("Base object"), "res/objeticon24.png");
|
||||
@@ -838,14 +838,13 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.MarkAsAdvanced()
|
||||
.SetRelevantForLayoutEventsOnly();
|
||||
|
||||
obj.AddAction(
|
||||
"PushBooleanToObjectVariable",
|
||||
_("Add value to object array variable"),
|
||||
_("Adds a boolean to the end of an object array variable."),
|
||||
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
|
||||
_("Variables ❯ Arrays and structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
obj.AddAction("PushBooleanToObjectVariable",
|
||||
_("Add value to object array variable"),
|
||||
_("Adds a boolean to the end of an object array variable."),
|
||||
_("Add value _PARAM2_ to array variable _PARAM1_ of _PARAM0_"),
|
||||
_("Variables ❯ Arrays and structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectvar", _("Array variable"))
|
||||
.AddParameter("trueorfalse", _("Boolean to add"))
|
||||
@@ -1575,7 +1574,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
extension
|
||||
.AddAction("Create",
|
||||
_("Create an object"),
|
||||
_("Create an object at specified position"),
|
||||
_("Create an instance of the object at the specified position."
|
||||
"The created object instance will be available for the next "
|
||||
"actions and sub-events."),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ "
|
||||
"(layer: _PARAM4_)"),
|
||||
"",
|
||||
|
@@ -72,7 +72,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
|
||||
extension
|
||||
.AddExpression("normalize",
|
||||
_("Normalize a value between `min` and `max` to a value between 0 and 1."),
|
||||
_("Normalize a value between `min` and `max` to a value "
|
||||
"between 0 and 1."),
|
||||
_("Remap a value between 0 and 1."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
@@ -124,7 +125,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("mod",
|
||||
_("Modulo"),
|
||||
_("x mod y"),
|
||||
_("Compute \"x mod y\". GDevelop does NOT support the \% "
|
||||
"operator. Use this mod(x, y) function instead."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("x (as in x mod y)"))
|
||||
@@ -184,11 +186,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("asinh",
|
||||
_("Arcsine"),
|
||||
_("Arcsine"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"asinh", _("Arcsine"), _("Arcsine"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -218,11 +217,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("cbrt",
|
||||
_("Cube root"),
|
||||
_("Cube root"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cbrt", _("Cube root"), _("Cube root"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -260,12 +256,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"cos",
|
||||
_("Cosine"),
|
||||
_("Cosine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -293,29 +290,20 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("int",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"int", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("rint",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"rint", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("round",
|
||||
_("Round"),
|
||||
_("Round a number"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"round", _("Round"), _("Round a number"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -324,8 +312,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
_("Round a number to the Nth decimal place"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"))
|
||||
.AddParameter("expression", _("Expression"), "", true);
|
||||
.AddParameter("expression", _("Number to Round"))
|
||||
.AddParameter("expression", _("Decimal Places"), "", true);
|
||||
|
||||
extension
|
||||
.AddExpression("exp",
|
||||
@@ -336,19 +324,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("log",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"log", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("ln",
|
||||
_("Logarithm"),
|
||||
_("Logarithm"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"ln", _("Logarithm"), _("Logarithm"), "", "res/mathfunction.png")
|
||||
.SetHidden()
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
@@ -387,11 +369,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("The exponent (n in x^n)"));
|
||||
|
||||
extension
|
||||
.AddExpression("sec",
|
||||
_("Secant"),
|
||||
_("Secant"),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sec", _("Secant"), _("Secant"), "", "res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -403,12 +382,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"sin",
|
||||
_("Sine"),
|
||||
_("Sine of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `sin(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -428,12 +408,13 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
.AddExpression("tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"tan",
|
||||
_("Tangent"),
|
||||
_("Tangent of an angle (in radian). "
|
||||
"If you want to use degrees, use`ToRad`: `tan(ToRad(45))`."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Expression"));
|
||||
|
||||
extension
|
||||
@@ -463,26 +444,28 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
.AddParameter("expression", _("x (in a+(b-a)*x)"));
|
||||
|
||||
extension
|
||||
.AddExpression("XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"XFromAngleAndDistance",
|
||||
_("X position from angle and distance"),
|
||||
_("Compute the X position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
extension
|
||||
.AddExpression("YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddExpression(
|
||||
"YFromAngleAndDistance",
|
||||
_("Y position from angle and distance"),
|
||||
_("Compute the Y position when given an angle and distance "
|
||||
"relative to the origin (0;0). This is also known as "
|
||||
"getting the cartesian coordinates of a 2D vector, using "
|
||||
"its polar coordinates."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Angle, in degrees"))
|
||||
.AddParameter("expression", _("Distance"));
|
||||
|
||||
@@ -497,7 +480,8 @@ BuiltinExtensionsImplementer::ImplementsMathematicalToolsExtension(
|
||||
extension
|
||||
.AddExpression("lerpAngle",
|
||||
_("Lerp (Linear interpolation) between two angles"),
|
||||
_("Linearly interpolates between two angles (in degrees) by taking the shortest direction around the circle."),
|
||||
_("Linearly interpolates between two angles (in degrees) "
|
||||
"by taking the shortest direction around the circle."),
|
||||
"",
|
||||
"res/mathfunction.png")
|
||||
.AddParameter("expression", _("Starting angle, in degrees"))
|
||||
|
@@ -250,25 +250,28 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
|
||||
}
|
||||
const auto &eventsBasedObject = project->GetEventsBasedObject(GetType());
|
||||
|
||||
if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
|
||||
if (IsForcedToOverrideEventsBasedObjectChildrenConfiguration()) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
else if (eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
|
||||
for (auto &childObject : eventsBasedObject.GetVariants()
|
||||
.GetVariant(variantName)
|
||||
.GetObjects()
|
||||
.GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
} else if (isMarkedAsOverridingEventsBasedObjectChildrenConfiguration) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
auto &configuration = GetChildObjectConfiguration(childObject->GetName());
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
} else {
|
||||
if (variantName.empty() ||
|
||||
!eventsBasedObject.GetVariants().HasVariantNamed(variantName)) {
|
||||
for (auto &childObject :
|
||||
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
} else {
|
||||
for (auto &childObject : eventsBasedObject.GetVariants()
|
||||
.GetVariant(variantName)
|
||||
.GetObjects()
|
||||
.GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
for (auto &childObject :
|
||||
eventsBasedObject.GetDefaultVariant().GetObjects().GetObjects()) {
|
||||
childObject->GetConfiguration().ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -78,6 +78,15 @@ public:
|
||||
variantName = variantName_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy events-based objects don't have any instance in their default
|
||||
* variant since there wasn't a graphical editor at the time. In this case,
|
||||
* the editor doesn't allow to choose a variant, but a variant may have stayed
|
||||
* after a user rolled back the extension. This variant must be ignored.
|
||||
*
|
||||
* @return true when its events-based object doesn't have any initial
|
||||
* instance.
|
||||
*/
|
||||
bool IsForcedToOverrideEventsBasedObjectChildrenConfiguration() const;
|
||||
|
||||
bool IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() const {
|
||||
|
@@ -36,7 +36,7 @@ namespace gd {
|
||||
|
||||
gd::BehaviorsSharedData Layout::badBehaviorSharedData("", "");
|
||||
|
||||
Layout::Layout(const Layout &other)
|
||||
Layout::Layout(const Layout& other)
|
||||
: objectsContainer(gd::ObjectsContainer::SourceType::Scene) {
|
||||
Init(other);
|
||||
}
|
||||
@@ -54,6 +54,8 @@ Layout::Layout()
|
||||
backgroundColorG(209),
|
||||
backgroundColorB(209),
|
||||
stopSoundsOnStartup(true),
|
||||
resourcesPreloading("inherit"),
|
||||
resourcesUnloading("inherit"),
|
||||
standardSortMethod(true),
|
||||
disableInputWhenNotFocused(true),
|
||||
variables(gd::VariablesContainer::SourceType::Scene),
|
||||
@@ -244,6 +246,10 @@ void Layout::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("title", GetWindowDefaultTitle());
|
||||
element.SetAttribute("standardSortMethod", standardSortMethod);
|
||||
element.SetAttribute("stopSoundsOnStartup", stopSoundsOnStartup);
|
||||
if (resourcesPreloading != "inherit")
|
||||
element.SetAttribute("resourcesPreloading", resourcesPreloading);
|
||||
if (resourcesUnloading != "inherit")
|
||||
element.SetAttribute("resourcesUnloading", resourcesUnloading);
|
||||
element.SetAttribute("disableInputWhenNotFocused",
|
||||
disableInputWhenNotFocused);
|
||||
|
||||
@@ -304,6 +310,10 @@ void Layout::UnserializeFrom(gd::Project& project,
|
||||
element.GetStringAttribute("title", "(No title)", "titre"));
|
||||
standardSortMethod = element.GetBoolAttribute("standardSortMethod");
|
||||
stopSoundsOnStartup = element.GetBoolAttribute("stopSoundsOnStartup");
|
||||
resourcesPreloading =
|
||||
element.GetStringAttribute("resourcesPreloading", "inherit");
|
||||
resourcesUnloading =
|
||||
element.GetStringAttribute("resourcesUnloading", "inherit");
|
||||
disableInputWhenNotFocused =
|
||||
element.GetBoolAttribute("disableInputWhenNotFocused");
|
||||
|
||||
@@ -391,6 +401,8 @@ void Layout::Init(const Layout& other) {
|
||||
standardSortMethod = other.standardSortMethod;
|
||||
title = other.title;
|
||||
stopSoundsOnStartup = other.stopSoundsOnStartup;
|
||||
resourcesPreloading = other.resourcesPreloading;
|
||||
resourcesUnloading = other.resourcesUnloading;
|
||||
disableInputWhenNotFocused = other.disableInputWhenNotFocused;
|
||||
initialInstances = other.initialInstances;
|
||||
layers = other.layers;
|
||||
|
@@ -349,6 +349,36 @@ class GD_CORE_API Layout {
|
||||
* launched
|
||||
*/
|
||||
bool StopSoundsOnStartup() const { return stopSoundsOnStartup; }
|
||||
|
||||
/**
|
||||
* Set when the scene must preload its resources: `at-startup`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
void SetResourcesPreloading(gd::String resourcesPreloading_) {
|
||||
resourcesPreloading = resourcesPreloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scene must preload its resources: `at-startup`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
const gd::String& GetResourcesPreloading() const {
|
||||
return resourcesPreloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the scene must unload its resources: `at-scene-exit`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
void SetResourcesUnloading(gd::String resourcesUnloading_) {
|
||||
resourcesUnloading = resourcesUnloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scene must unload its resources: `at-scene-exit`, `never` or
|
||||
* `inherit` (default).
|
||||
*/
|
||||
const gd::String& GetResourcesUnloading() const { return resourcesUnloading; }
|
||||
///@}
|
||||
|
||||
/** \name Saving and loading
|
||||
@@ -381,6 +411,10 @@ class GD_CORE_API Layout {
|
||||
behaviorsSharedData; ///< Initial shared datas of behaviors
|
||||
bool stopSoundsOnStartup = true; ///< True to make the scene stop all sounds at
|
||||
///< startup.
|
||||
gd::String
|
||||
resourcesPreloading; ///< `at-startup`, `never` or `inherit` (default).
|
||||
gd::String
|
||||
resourcesUnloading; ///< `at-scene-exit`, `never` or `inherit` (default).
|
||||
bool standardSortMethod = true; ///< True to sort objects using standard sort.
|
||||
bool disableInputWhenNotFocused = true; /// If set to true, the input must be
|
||||
/// disabled when the window do not have the
|
||||
|
@@ -74,7 +74,9 @@ Project::Project()
|
||||
gdMinorVersion(gd::VersionWrapper::Minor()),
|
||||
gdBuildVersion(gd::VersionWrapper::Build()),
|
||||
variables(gd::VariablesContainer::SourceType::Global),
|
||||
objectsContainer(gd::ObjectsContainer::SourceType::Global) {}
|
||||
objectsContainer(gd::ObjectsContainer::SourceType::Global),
|
||||
sceneResourcesPreloading("at-startup"),
|
||||
sceneResourcesUnloading("never") {}
|
||||
|
||||
Project::~Project() {}
|
||||
|
||||
@@ -1166,6 +1168,13 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
else
|
||||
std::cout << "ERROR: The project current platform is NULL.";
|
||||
|
||||
if (sceneResourcesPreloading != "at-startup") {
|
||||
propElement.SetAttribute("sceneResourcesPreloading", sceneResourcesPreloading);
|
||||
}
|
||||
if (sceneResourcesUnloading != "never") {
|
||||
propElement.SetAttribute("sceneResourcesUnloading", sceneResourcesUnloading);
|
||||
}
|
||||
|
||||
resourcesManager.SerializeTo(element.AddChild("resources"));
|
||||
objectsContainer.SerializeObjectsTo(element.AddChild("objects"));
|
||||
objectsContainer.SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
|
||||
@@ -1307,6 +1316,9 @@ void Project::Init(const gd::Project& game) {
|
||||
variables = game.GetVariables();
|
||||
|
||||
projectFile = game.GetProjectFile();
|
||||
|
||||
sceneResourcesPreloading = game.sceneResourcesPreloading;
|
||||
sceneResourcesUnloading = game.sceneResourcesUnloading;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -964,6 +964,37 @@ class GD_CORE_API Project {
|
||||
*/
|
||||
ResourcesManager& GetResourcesManager() { return resourcesManager; }
|
||||
|
||||
/**
|
||||
* Set when the scenes must preload their resources: `at-startup`, `never`
|
||||
* (default).
|
||||
*/
|
||||
void SetSceneResourcesPreloading(gd::String sceneResourcesPreloading_) {
|
||||
sceneResourcesPreloading = sceneResourcesPreloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scenes must preload their resources: `at-startup`, `never`
|
||||
* (default).
|
||||
*/
|
||||
const gd::String& GetSceneResourcesPreloading() const {
|
||||
return sceneResourcesPreloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the scenes must unload their resources: `at-scene-exit`, `never`
|
||||
* (default).
|
||||
*/
|
||||
void SetSceneResourcesUnloading(gd::String sceneResourcesUnloading_) {
|
||||
sceneResourcesUnloading = sceneResourcesUnloading_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get when the scenes must unload their resources: `at-scene-exit`, `never`
|
||||
* (default).
|
||||
*/
|
||||
const gd::String& GetSceneResourcesUnloading() const {
|
||||
return sceneResourcesUnloading;
|
||||
}
|
||||
///@}
|
||||
|
||||
/** \name Variable management
|
||||
@@ -1121,6 +1152,10 @@ class GD_CORE_API Project {
|
||||
ExtensionProperties
|
||||
extensionProperties; ///< The properties of the extensions.
|
||||
gd::WholeProjectDiagnosticReport wholeProjectDiagnosticReport;
|
||||
gd::String sceneResourcesPreloading; ///< `at-startup` or `never`
|
||||
///< (default: `at-startup`).
|
||||
gd::String sceneResourcesUnloading; ///< `at-scene-exit` or `never`
|
||||
///< (default: `never`).
|
||||
mutable unsigned int gdMajorVersion =
|
||||
0; ///< The GD major version used the last
|
||||
///< time the project was saved.
|
||||
|
@@ -7,9 +7,9 @@
|
||||
#define GDCORE_PROPERTYDESCRIPTOR
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Project/MeasurementUnit.h"
|
||||
#include "GDCore/Project/QuickCustomization.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class SerializerElement;
|
||||
@@ -17,6 +17,19 @@ class SerializerElement;
|
||||
|
||||
namespace gd {
|
||||
|
||||
class GD_CORE_API PropertyDescriptorChoice {
|
||||
public:
|
||||
PropertyDescriptorChoice(const gd::String& value, const gd::String& label)
|
||||
: value(value), label(label) {}
|
||||
|
||||
const gd::String& GetValue() const { return value; }
|
||||
const gd::String& GetLabel() const { return label; }
|
||||
|
||||
private:
|
||||
gd::String value;
|
||||
gd::String label;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Used to describe a property shown in a property grid.
|
||||
* \see gd::Object
|
||||
@@ -31,8 +44,12 @@ class GD_CORE_API PropertyDescriptor {
|
||||
* \param propertyValue The value of the property.
|
||||
*/
|
||||
PropertyDescriptor(gd::String propertyValue)
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false),
|
||||
deprecated(false), advanced(false),
|
||||
: currentValue(propertyValue),
|
||||
type("string"),
|
||||
label(""),
|
||||
hidden(false),
|
||||
deprecated(false),
|
||||
advanced(false),
|
||||
hasImpactOnOtherProperties(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()),
|
||||
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {}
|
||||
@@ -41,10 +58,13 @@ class GD_CORE_API PropertyDescriptor {
|
||||
* \brief Empty constructor creating an empty property to be displayed.
|
||||
*/
|
||||
PropertyDescriptor()
|
||||
: hidden(false), deprecated(false), advanced(false),
|
||||
: hidden(false),
|
||||
deprecated(false),
|
||||
advanced(false),
|
||||
hasImpactOnOtherProperties(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()),
|
||||
quickCustomizationVisibility(QuickCustomization::Visibility::Default){};
|
||||
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Destructor
|
||||
@@ -88,13 +108,20 @@ class GD_CORE_API PropertyDescriptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Change the group where this property is displayed to the user, if any.
|
||||
* \brief Change the group where this property is displayed to the user, if
|
||||
* any.
|
||||
*/
|
||||
PropertyDescriptor& SetGroup(gd::String group_) {
|
||||
group = group_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
PropertyDescriptor& AddChoice(const gd::String& value,
|
||||
const gd::String& label) {
|
||||
choices.push_back(PropertyDescriptorChoice(value, label));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set and replace the additional information for the property.
|
||||
*/
|
||||
@@ -118,7 +145,8 @@ class GD_CORE_API PropertyDescriptor {
|
||||
/**
|
||||
* \brief Change the unit of measurement of the property value.
|
||||
*/
|
||||
PropertyDescriptor& SetMeasurementUnit(const gd::MeasurementUnit &measurementUnit_) {
|
||||
PropertyDescriptor& SetMeasurementUnit(
|
||||
const gd::MeasurementUnit& measurementUnit_) {
|
||||
measurementUnit = measurementUnit_;
|
||||
return *this;
|
||||
}
|
||||
@@ -128,14 +156,18 @@ class GD_CORE_API PropertyDescriptor {
|
||||
const gd::String& GetLabel() const { return label; }
|
||||
const gd::String& GetDescription() const { return description; }
|
||||
const gd::String& GetGroup() const { return group; }
|
||||
const gd::MeasurementUnit& GetMeasurementUnit() const { return measurementUnit; }
|
||||
const gd::MeasurementUnit& GetMeasurementUnit() const {
|
||||
return measurementUnit;
|
||||
}
|
||||
|
||||
const std::vector<gd::String>& GetExtraInfo() const {
|
||||
return extraInformation;
|
||||
}
|
||||
|
||||
std::vector<gd::String>& GetExtraInfo() {
|
||||
return extraInformation;
|
||||
std::vector<gd::String>& GetExtraInfo() { return extraInformation; }
|
||||
|
||||
const std::vector<PropertyDescriptorChoice>& GetChoices() const {
|
||||
return choices;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,23 +210,26 @@ class GD_CORE_API PropertyDescriptor {
|
||||
bool IsAdvanced() const { return advanced; }
|
||||
|
||||
/**
|
||||
* \brief Check if the property has impact on other properties - which means a change
|
||||
* must re-render other properties.
|
||||
* \brief Check if the property has impact on other properties - which means a
|
||||
* change must re-render other properties.
|
||||
*/
|
||||
bool HasImpactOnOtherProperties() const { return hasImpactOnOtherProperties; }
|
||||
|
||||
/**
|
||||
* \brief Set if the property has impact on other properties - which means a change
|
||||
* must re-render other properties.
|
||||
* \brief Set if the property has impact on other properties - which means a
|
||||
* change must re-render other properties.
|
||||
*/
|
||||
PropertyDescriptor& SetHasImpactOnOtherProperties(bool enable) {
|
||||
hasImpactOnOtherProperties = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QuickCustomization::Visibility GetQuickCustomizationVisibility() const { return quickCustomizationVisibility; }
|
||||
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
|
||||
return quickCustomizationVisibility;
|
||||
}
|
||||
|
||||
PropertyDescriptor& SetQuickCustomizationVisibility(QuickCustomization::Visibility visibility) {
|
||||
PropertyDescriptor& SetQuickCustomizationVisibility(
|
||||
QuickCustomization::Visibility visibility) {
|
||||
quickCustomizationVisibility = visibility;
|
||||
return *this;
|
||||
}
|
||||
@@ -231,15 +266,17 @@ class GD_CORE_API PropertyDescriptor {
|
||||
gd::String label; //< The user-friendly property name
|
||||
gd::String description; //< The user-friendly property description
|
||||
gd::String group; //< The user-friendly property group
|
||||
std::vector<PropertyDescriptorChoice>
|
||||
choices; //< The optional choices for the property.
|
||||
std::vector<gd::String>
|
||||
extraInformation; ///< Can be used to store for example the available
|
||||
///< choices, if a property is a displayed as a combo
|
||||
///< box.
|
||||
extraInformation; ///< Can be used to store an additional information
|
||||
///< like an object type.
|
||||
bool hidden;
|
||||
bool deprecated;
|
||||
bool advanced;
|
||||
bool hasImpactOnOtherProperties;
|
||||
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
|
||||
gd::MeasurementUnit
|
||||
measurementUnit; //< The unit of measurement of the property vale.
|
||||
QuickCustomization::Visibility quickCustomizationVisibility;
|
||||
};
|
||||
|
||||
|
@@ -25,6 +25,8 @@ namespace gdjs {
|
||||
topFaceVisible: boolean;
|
||||
bottomFaceVisible: boolean;
|
||||
tint: string | undefined;
|
||||
isCastingShadow: boolean;
|
||||
isReceivingShadow: boolean;
|
||||
materialType: 'Basic' | 'StandardWithoutMetalness';
|
||||
};
|
||||
}
|
||||
@@ -71,8 +73,10 @@ namespace gdjs {
|
||||
string,
|
||||
];
|
||||
_materialType: gdjs.Cube3DRuntimeObject.MaterialType =
|
||||
gdjs.Cube3DRuntimeObject.MaterialType.Basic;
|
||||
gdjs.Cube3DRuntimeObject.MaterialType.StandardWithoutMetalness;
|
||||
_tint: string;
|
||||
_isCastingShadow: boolean = true;
|
||||
_isReceivingShadow: boolean = true;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -121,6 +125,8 @@ namespace gdjs {
|
||||
];
|
||||
|
||||
this._tint = objectData.content.tint || '255;255;255';
|
||||
this._isCastingShadow = objectData.content.isCastingShadow || false;
|
||||
this._isReceivingShadow = objectData.content.isReceivingShadow || false;
|
||||
|
||||
this._materialType = this._convertMaterialType(
|
||||
objectData.content.materialType
|
||||
@@ -430,6 +436,18 @@ namespace gdjs {
|
||||
) {
|
||||
this.setMaterialType(newObjectData.content.materialType);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isCastingShadow !==
|
||||
newObjectData.content.isCastingShadow
|
||||
) {
|
||||
this.updateShadowCasting(newObjectData.content.isCastingShadow);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isReceivingShadow !==
|
||||
newObjectData.content.isReceivingShadow
|
||||
) {
|
||||
this.updateShadowReceiving(newObjectData.content.isReceivingShadow);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -531,6 +549,14 @@ namespace gdjs {
|
||||
this._materialType = newMaterialType;
|
||||
this._renderer._updateMaterials();
|
||||
}
|
||||
updateShadowCasting(value: boolean) {
|
||||
this._isCastingShadow = value;
|
||||
this._renderer.updateShadowCasting();
|
||||
}
|
||||
updateShadowReceiving(value: boolean) {
|
||||
this._isReceivingShadow = value;
|
||||
this._renderer.updateShadowReceiving();
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Cube3DRuntimeObject {
|
||||
|
@@ -81,13 +81,14 @@ namespace gdjs {
|
||||
.map((_, index) =>
|
||||
getFaceMaterial(runtimeObject, materialIndexToFaceIndex[index])
|
||||
);
|
||||
|
||||
const boxMesh = new THREE.Mesh(geometry, materials);
|
||||
|
||||
super(runtimeObject, instanceContainer, boxMesh);
|
||||
this._boxMesh = boxMesh;
|
||||
this._cube3DRuntimeObject = runtimeObject;
|
||||
|
||||
boxMesh.receiveShadow = this._cube3DRuntimeObject._isCastingShadow;
|
||||
boxMesh.castShadow = this._cube3DRuntimeObject._isReceivingShadow;
|
||||
this.updateSize();
|
||||
this.updatePosition();
|
||||
this.updateRotation();
|
||||
@@ -114,6 +115,13 @@ namespace gdjs {
|
||||
new THREE.BufferAttribute(new Float32Array(tints), 3)
|
||||
);
|
||||
}
|
||||
updateShadowCasting() {
|
||||
this._boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
|
||||
}
|
||||
updateShadowReceiving() {
|
||||
this._boxMesh.receiveShadow =
|
||||
this._cube3DRuntimeObject._isReceivingShadow;
|
||||
}
|
||||
|
||||
updateFace(faceIndex: integer) {
|
||||
const materialIndex = faceIndexToMaterialIndex[faceIndex];
|
||||
|
@@ -34,7 +34,6 @@ namespace gdjs {
|
||||
objectData: gdjs.Object3DData & gdjs.CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this._renderer.reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected override _createRender() {
|
||||
|
@@ -44,10 +44,7 @@ namespace gdjs {
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
this._threeGroup.clear();
|
||||
}
|
||||
|
||||
_updateThreeGroup() {
|
||||
|
@@ -6,6 +6,7 @@ namespace gdjs {
|
||||
r: number;
|
||||
t: string;
|
||||
}
|
||||
const shadowHelper = false;
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::DirectionalLight',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
@@ -17,19 +18,74 @@ namespace gdjs {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
light: THREE.DirectionalLight;
|
||||
rotationObject: THREE.Group;
|
||||
_isEnabled: boolean = false;
|
||||
top: string = 'Y-';
|
||||
elevation: float = 45;
|
||||
rotation: float = 0;
|
||||
private _light: THREE.DirectionalLight;
|
||||
private _isEnabled: boolean = false;
|
||||
private _top: string = 'Y-';
|
||||
private _elevation: float = 45;
|
||||
private _rotation: float = 0;
|
||||
|
||||
private _shadowMapDirty = true;
|
||||
private _shadowMapSize: float = 1024;
|
||||
|
||||
private _shadowCameraDirty = true;
|
||||
private _distanceFromCamera: float = 1500;
|
||||
private _frustumSize: float = 4000;
|
||||
private _shadowCameraHelper: THREE.CameraHelper | null;
|
||||
|
||||
constructor() {
|
||||
this.light = new THREE.DirectionalLight();
|
||||
this.light.position.set(1, 0, 0);
|
||||
this.rotationObject = new THREE.Group();
|
||||
this.rotationObject.add(this.light);
|
||||
this.updateRotation();
|
||||
this._light = new THREE.DirectionalLight();
|
||||
|
||||
if (shadowHelper) {
|
||||
this._shadowCameraHelper = new THREE.CameraHelper(
|
||||
this._light.shadow.camera
|
||||
);
|
||||
} else {
|
||||
this._shadowCameraHelper = null;
|
||||
}
|
||||
|
||||
this._light.shadow.camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
private _updateShadowCamera(): void {
|
||||
if (!this._shadowCameraDirty) {
|
||||
return;
|
||||
}
|
||||
this._shadowCameraDirty = false;
|
||||
|
||||
this._light.shadow.camera.near = 1;
|
||||
this._light.shadow.camera.far = this._distanceFromCamera + 10000;
|
||||
this._light.shadow.camera.right = this._frustumSize / 2;
|
||||
this._light.shadow.camera.left = -this._frustumSize / 2;
|
||||
this._light.shadow.camera.top = this._frustumSize / 2;
|
||||
this._light.shadow.camera.bottom = -this._frustumSize / 2;
|
||||
}
|
||||
|
||||
private _updateShadowMapSize(): void {
|
||||
if (!this._shadowMapDirty) {
|
||||
return;
|
||||
}
|
||||
this._shadowMapDirty = false;
|
||||
|
||||
// Avoid shadow acne due to depth buffer precision. We choose a value
|
||||
// small enough to avoid "peter panning" but not too small to avoid
|
||||
// shadow acne on low/medium quality shadow maps.
|
||||
// If needed, this could become a parameter of the effect.
|
||||
this._light.shadow.bias =
|
||||
this._shadowMapSize < 1024
|
||||
? -0.002
|
||||
: this._shadowMapSize < 2048
|
||||
? -0.001
|
||||
: -0.0008;
|
||||
|
||||
this._light.shadow.mapSize.set(
|
||||
this._shadowMapSize,
|
||||
this._shadowMapSize
|
||||
);
|
||||
|
||||
// Force the recreation of the shadow map texture:
|
||||
this._light.shadow.map?.dispose();
|
||||
this._light.shadow.map = null;
|
||||
this._light.shadow.needsUpdate = true;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
@@ -53,7 +109,46 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.add(this.rotationObject);
|
||||
const skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
|
||||
|
||||
const ft = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_north.png'
|
||||
);
|
||||
const bk = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_south.png'
|
||||
);
|
||||
const up = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_up.png'
|
||||
);
|
||||
const dn = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_down.png'
|
||||
);
|
||||
const rt = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_east.png'
|
||||
);
|
||||
const lf = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_west.png'
|
||||
);
|
||||
|
||||
const materialArray = [
|
||||
new THREE.MeshBasicMaterial({ map: rt, side: THREE.BackSide }), // Right
|
||||
new THREE.MeshBasicMaterial({ map: up, side: THREE.BackSide }), // Left
|
||||
new THREE.MeshBasicMaterial({ map: lf, side: THREE.BackSide }), // Top
|
||||
new THREE.MeshBasicMaterial({ map: dn, side: THREE.BackSide }), // Bottom
|
||||
new THREE.MeshBasicMaterial({ map: bk, side: THREE.BackSide }), // Front
|
||||
new THREE.MeshBasicMaterial({ map: ft, side: THREE.BackSide }), // Back
|
||||
];
|
||||
const skybox = new THREE.Mesh(skyboxGeo, materialArray);
|
||||
skybox.position.set(0, 0, 0);
|
||||
skybox.frustumCulled = false;
|
||||
|
||||
scene.add(skybox);
|
||||
scene.add(this._light);
|
||||
scene.add(this._light.target);
|
||||
if (this._shadowCameraHelper) {
|
||||
scene.add(this._shadowCameraHelper);
|
||||
}
|
||||
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
@@ -65,82 +160,145 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.remove(this.rotationObject);
|
||||
scene.remove(this._light);
|
||||
scene.remove(this._light.target);
|
||||
if (this._shadowCameraHelper) {
|
||||
scene.remove(this._shadowCameraHelper);
|
||||
}
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {
|
||||
this._updateShadowCamera();
|
||||
this._updateShadowMapSize();
|
||||
|
||||
if (!target.getRuntimeLayer) {
|
||||
return;
|
||||
}
|
||||
const layer = target.getRuntimeLayer();
|
||||
const x = layer.getCameraX();
|
||||
const y = layer.getCameraY();
|
||||
const z = layer.getCameraZ(layer.getInitialCamera3DFieldOfView());
|
||||
|
||||
const roundedX = Math.floor(x / 100) * 100;
|
||||
const roundedY = Math.floor(y / 100) * 100;
|
||||
const roundedZ = Math.floor(z / 100) * 100;
|
||||
if (this._top === 'Y-') {
|
||||
const posLightX =
|
||||
roundedX +
|
||||
this._distanceFromCamera *
|
||||
Math.cos(gdjs.toRad(this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightY =
|
||||
roundedY -
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._elevation));
|
||||
const posLightZ =
|
||||
roundedZ +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
this._light.position.set(posLightX, posLightY, posLightZ);
|
||||
this._light.target.position.set(roundedX, roundedY, roundedZ);
|
||||
} else {
|
||||
const posLightX =
|
||||
roundedX +
|
||||
this._distanceFromCamera *
|
||||
Math.cos(gdjs.toRad(this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightY =
|
||||
roundedY +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightZ =
|
||||
roundedZ +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._elevation));
|
||||
|
||||
this._light.position.set(posLightX, posLightY, posLightZ);
|
||||
this._light.target.position.set(roundedX, roundedY, roundedZ);
|
||||
}
|
||||
}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'intensity') {
|
||||
this.light.intensity = value;
|
||||
this._light.intensity = value;
|
||||
} else if (parameterName === 'elevation') {
|
||||
this.elevation = value;
|
||||
this.updateRotation();
|
||||
this._elevation = value;
|
||||
} else if (parameterName === 'rotation') {
|
||||
this.rotation = value;
|
||||
this.updateRotation();
|
||||
this._rotation = value;
|
||||
} else if (parameterName === 'distanceFromCamera') {
|
||||
this._distanceFromCamera = value;
|
||||
} else if (parameterName === 'frustumSize') {
|
||||
this._frustumSize = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'intensity') {
|
||||
return this.light.intensity;
|
||||
return this._light.intensity;
|
||||
} else if (parameterName === 'elevation') {
|
||||
return this.elevation;
|
||||
return this._elevation;
|
||||
} else if (parameterName === 'rotation') {
|
||||
return this.rotation;
|
||||
return this._rotation;
|
||||
} else if (parameterName === 'distanceFromCamera') {
|
||||
return this._distanceFromCamera;
|
||||
} else if (parameterName === 'frustumSize') {
|
||||
return this._frustumSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {
|
||||
if (parameterName === 'color') {
|
||||
this.light.color = new THREE.Color(
|
||||
this._light.color = new THREE.Color(
|
||||
gdjs.rgbOrHexStringToNumber(value)
|
||||
);
|
||||
}
|
||||
if (parameterName === 'top') {
|
||||
this.top = value;
|
||||
this.updateRotation();
|
||||
if (parameterName === 'shadowQuality') {
|
||||
if (value === 'low' && this._shadowMapSize !== 512) {
|
||||
this._shadowMapSize = 512;
|
||||
this._shadowMapDirty = true;
|
||||
}
|
||||
if (value === 'medium' && this._shadowMapSize !== 1024) {
|
||||
this._shadowMapSize = 1024;
|
||||
this._shadowMapDirty = true;
|
||||
}
|
||||
if (value === 'high' && this._shadowMapSize !== 2048) {
|
||||
this._shadowMapSize = 2048;
|
||||
this._shadowMapDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateColorParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'color') {
|
||||
this.light.color.setHex(value);
|
||||
this._light.color.setHex(value);
|
||||
}
|
||||
}
|
||||
getColorParameter(parameterName: string): number {
|
||||
if (parameterName === 'color') {
|
||||
return this.light.color.getHex();
|
||||
return this._light.color.getHex();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
updateRotation() {
|
||||
if (this.top === 'Z+') {
|
||||
// 0° is a light from the right of the screen.
|
||||
this.rotationObject.rotation.z = gdjs.toRad(this.rotation);
|
||||
this.rotationObject.rotation.y = -gdjs.toRad(this.elevation);
|
||||
} else {
|
||||
// 0° becomes a light from Z+.
|
||||
this.rotationObject.rotation.y = gdjs.toRad(this.rotation - 90);
|
||||
this.rotationObject.rotation.z = -gdjs.toRad(this.elevation);
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {
|
||||
if (parameterName === 'isCastingShadow') {
|
||||
this._light.castShadow = value;
|
||||
}
|
||||
}
|
||||
getNetworkSyncData(): DirectionalLightFilterNetworkSyncData {
|
||||
return {
|
||||
i: this.light.intensity,
|
||||
c: this.light.color.getHex(),
|
||||
e: this.elevation,
|
||||
r: this.rotation,
|
||||
t: this.top,
|
||||
i: this._light.intensity,
|
||||
c: this._light.color.getHex(),
|
||||
e: this._elevation,
|
||||
r: this._rotation,
|
||||
t: this._top,
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(syncData: any): void {
|
||||
this.light.intensity = syncData.i;
|
||||
this.light.color.setHex(syncData.c);
|
||||
this.elevation = syncData.e;
|
||||
this.rotation = syncData.r;
|
||||
this.top = syncData.t;
|
||||
this.updateRotation();
|
||||
this._light.intensity = syncData.i;
|
||||
this._light.color.setHex(syncData.c);
|
||||
this._elevation = syncData.e;
|
||||
this._rotation = syncData.r;
|
||||
this._top = syncData.t;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
@@ -859,7 +859,9 @@ module.exports = {
|
||||
propertyName === 'rightFaceResourceRepeat' ||
|
||||
propertyName === 'topFaceResourceRepeat' ||
|
||||
propertyName === 'bottomFaceResourceRepeat' ||
|
||||
propertyName === 'enableTextureTransparency'
|
||||
propertyName === 'enableTextureTransparency' ||
|
||||
propertyName === 'isCastingShadow' ||
|
||||
propertyName === 'isReceivingShadow'
|
||||
) {
|
||||
objectContent[propertyName] = newValue === '1';
|
||||
return true;
|
||||
@@ -887,8 +889,8 @@ module.exports = {
|
||||
.getOrCreate('facesOrientation')
|
||||
.setValue(objectContent.facesOrientation || 'Y')
|
||||
.setType('choice')
|
||||
.addExtraInfo('Y')
|
||||
.addExtraInfo('Z')
|
||||
.addChoice('Y', 'Y')
|
||||
.addChoice('Z', 'Z')
|
||||
.setLabel(_('Faces orientation'))
|
||||
.setDescription(
|
||||
_(
|
||||
@@ -948,8 +950,8 @@ module.exports = {
|
||||
.getOrCreate('backFaceUpThroughWhichAxisRotation')
|
||||
.setValue(objectContent.backFaceUpThroughWhichAxisRotation || 'X')
|
||||
.setType('choice')
|
||||
.addExtraInfo('X')
|
||||
.addExtraInfo('Y')
|
||||
.addChoice('X', 'X')
|
||||
.addChoice('Y', 'Y')
|
||||
.setLabel(_('Back face orientation'))
|
||||
.setDescription(
|
||||
_(
|
||||
@@ -1083,12 +1085,29 @@ module.exports = {
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('materialType')
|
||||
.setValue(objectContent.materialType || 'Basic')
|
||||
.setValue(objectContent.materialType || 'StandardWithoutMetalness')
|
||||
.setType('choice')
|
||||
.addExtraInfo('Basic')
|
||||
.addExtraInfo('StandardWithoutMetalness')
|
||||
.addChoice('Basic', _('Basic (no lighting, no shadows)'))
|
||||
.addChoice(
|
||||
'StandardWithoutMetalness',
|
||||
_('Standard (without metalness)')
|
||||
)
|
||||
.setLabel(_('Material type'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('isCastingShadow')
|
||||
.setValue(objectContent.isCastingShadow ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Shadow casting'))
|
||||
.setGroup(_('Shadows'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('isReceivingShadow')
|
||||
.setValue(objectContent.isReceivingShadow ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Shadow receiving'))
|
||||
.setGroup(_('Shadows'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
Cube3DObject.content = {
|
||||
@@ -1116,8 +1135,10 @@ module.exports = {
|
||||
rightFaceResourceRepeat: false,
|
||||
topFaceResourceRepeat: false,
|
||||
bottomFaceResourceRepeat: false,
|
||||
materialType: 'Basic',
|
||||
materialType: 'StandardWithoutMetalness',
|
||||
tint: '255;255;255',
|
||||
isCastingShadow: true,
|
||||
isReceivingShadow: true,
|
||||
};
|
||||
|
||||
Cube3DObject.updateInitialInstanceProperty = function (
|
||||
@@ -1913,6 +1934,34 @@ module.exports = {
|
||||
.setLabel(_('Rotation (in degrees)'))
|
||||
.setType('number')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('isCastingShadow')
|
||||
.setValue('true')
|
||||
.setLabel(_('Shadow casting'))
|
||||
.setType('boolean')
|
||||
.setGroup(_('Shadows'));
|
||||
properties
|
||||
.getOrCreate('shadowQuality')
|
||||
.setValue('medium')
|
||||
.addChoice('low', _('Low quality'))
|
||||
.addChoice('medium', _('Medium quality'))
|
||||
.addChoice('high', _('High quality'))
|
||||
.setLabel(_('Shadow quality'))
|
||||
.setType('choice')
|
||||
.setGroup(_('Shadows'));
|
||||
properties
|
||||
.getOrCreate('frustumSize')
|
||||
.setValue('4000')
|
||||
.setLabel(_('Shadow frustum size'))
|
||||
.setType('number')
|
||||
.setGroup(_('Shadows'))
|
||||
.setAdvanced(true);
|
||||
properties
|
||||
.getOrCreate('distanceFromCamera')
|
||||
.setValue('1500')
|
||||
.setLabel(_("Distance from layer's camera"))
|
||||
.setType('number')
|
||||
.setAdvanced(true);
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
@@ -3210,6 +3259,8 @@ module.exports = {
|
||||
|
||||
this._threeObject = new THREE.Group();
|
||||
this._threeObject.rotation.order = 'ZYX';
|
||||
this._threeObject.castShadow = true;
|
||||
this._threeObject.receiveShadow = true;
|
||||
this._threeGroup.add(this._threeObject);
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,7 @@ Model3DObjectConfiguration::Model3DObjectConfiguration()
|
||||
: width(100), height(100), depth(100), rotationX(0), rotationY(0),
|
||||
rotationZ(0), modelResourceName(""), materialType("StandardWithoutMetalness"),
|
||||
originLocation("ModelOrigin"), centerLocation("ModelOrigin"),
|
||||
keepAspectRatio(true), crossfadeDuration(0.1f) {}
|
||||
keepAspectRatio(true), crossfadeDuration(0.1f), isCastingShadow(true), isReceivingShadow(true) {}
|
||||
|
||||
bool Model3DObjectConfiguration::UpdateProperty(const gd::String &propertyName,
|
||||
const gd::String &newValue) {
|
||||
@@ -75,6 +75,16 @@ bool Model3DObjectConfiguration::UpdateProperty(const gd::String &propertyName,
|
||||
crossfadeDuration = newValue.To<double>();
|
||||
return true;
|
||||
}
|
||||
if(propertyName == "isCastingShadow")
|
||||
{
|
||||
isCastingShadow = newValue == "1";
|
||||
return true;
|
||||
}
|
||||
if(propertyName == "isReceivingShadow")
|
||||
{
|
||||
isReceivingShadow = newValue == "1";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -143,19 +153,19 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
objectProperties["materialType"]
|
||||
.SetValue(materialType.empty() ? "Basic" : materialType)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("Basic")
|
||||
.AddExtraInfo("StandardWithoutMetalness")
|
||||
.AddExtraInfo("KeepOriginal")
|
||||
.AddChoice("Basic", _("Basic (no lighting, no shadows)"))
|
||||
.AddChoice("StandardWithoutMetalness", _("Standard (without metalness)"))
|
||||
.AddChoice("KeepOriginal", _("Keep original"))
|
||||
.SetLabel(_("Material"));
|
||||
|
||||
objectProperties["originLocation"]
|
||||
.SetValue(originLocation.empty() ? "TopLeft" : originLocation)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("ModelOrigin")
|
||||
.AddExtraInfo("TopLeft")
|
||||
.AddExtraInfo("ObjectCenter")
|
||||
.AddExtraInfo("BottomCenterZ")
|
||||
.AddExtraInfo("BottomCenterY")
|
||||
.AddChoice("ModelOrigin", _("Model origin"))
|
||||
.AddChoice("TopLeft", _("Top left"))
|
||||
.AddChoice("ObjectCenter", _("Object center"))
|
||||
.AddChoice("BottomCenterZ", _("Bottom center (Z)"))
|
||||
.AddChoice("BottomCenterY", _("Bottom center (Y)"))
|
||||
.SetLabel(_("Origin point"))
|
||||
.SetGroup(_("Points"))
|
||||
.SetAdvanced(true);
|
||||
@@ -163,10 +173,10 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
objectProperties["centerLocation"]
|
||||
.SetValue(centerLocation.empty() ? "ObjectCenter" : centerLocation)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("ModelOrigin")
|
||||
.AddExtraInfo("ObjectCenter")
|
||||
.AddExtraInfo("BottomCenterZ")
|
||||
.AddExtraInfo("BottomCenterY")
|
||||
.AddChoice("ModelOrigin", _("Model origin"))
|
||||
.AddChoice("ObjectCenter", _("Object center"))
|
||||
.AddChoice("BottomCenterZ", _("Bottom center (Z)"))
|
||||
.AddChoice("BottomCenterY", _("Bottom center (Y)"))
|
||||
.SetLabel(_("Center point"))
|
||||
.SetGroup(_("Points"))
|
||||
.SetAdvanced(true);
|
||||
@@ -178,6 +188,20 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
.SetGroup(_("Animations"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond());
|
||||
|
||||
objectProperties["isCastingShadow"]
|
||||
.SetValue(isCastingShadow ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow casting"))
|
||||
.SetGroup(_("Shadows"));
|
||||
|
||||
objectProperties["isReceivingShadow"]
|
||||
.SetValue(isReceivingShadow ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow receiving"))
|
||||
.SetGroup(_("Shadows"));
|
||||
|
||||
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
||||
@@ -210,6 +234,8 @@ void Model3DObjectConfiguration::DoUnserializeFrom(
|
||||
centerLocation = content.GetStringAttribute("centerLocation");
|
||||
keepAspectRatio = content.GetBoolAttribute("keepAspectRatio");
|
||||
crossfadeDuration = content.GetDoubleAttribute("crossfadeDuration");
|
||||
isCastingShadow = content.GetBoolAttribute("isCastingShadow");
|
||||
isReceivingShadow = content.GetBoolAttribute("isReceivingShadow");
|
||||
|
||||
RemoveAllAnimations();
|
||||
auto &animationsElement = content.GetChild("animations");
|
||||
@@ -239,6 +265,8 @@ void Model3DObjectConfiguration::DoSerializeTo(
|
||||
content.SetAttribute("centerLocation", centerLocation);
|
||||
content.SetAttribute("keepAspectRatio", keepAspectRatio);
|
||||
content.SetAttribute("crossfadeDuration", crossfadeDuration);
|
||||
content.SetAttribute("isCastingShadow", isCastingShadow);
|
||||
content.SetAttribute("isReceivingShadow", isReceivingShadow);
|
||||
|
||||
auto &animationsElement = content.AddChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
|
@@ -160,6 +160,8 @@ public:
|
||||
const gd::String& GetCenterLocation() const { return centerLocation; };
|
||||
|
||||
bool shouldKeepAspectRatio() const { return keepAspectRatio; };
|
||||
bool shouldCastShadow() const { return isCastingShadow; };
|
||||
bool shouldReceiveShadow() const { return isReceivingShadow; };
|
||||
///@}
|
||||
|
||||
protected:
|
||||
@@ -182,6 +184,8 @@ private:
|
||||
gd::String centerLocation;
|
||||
|
||||
bool keepAspectRatio;
|
||||
bool isCastingShadow;
|
||||
bool isReceivingShadow;
|
||||
|
||||
std::vector<Model3DAnimation> animations;
|
||||
static Model3DAnimation badAnimation; //< Bad animation when an out of bound
|
||||
|
@@ -38,6 +38,8 @@ namespace gdjs {
|
||||
| 'BottomCenterY';
|
||||
animations: Model3DAnimation[];
|
||||
crossfadeDuration: float;
|
||||
isCastingShadow: boolean;
|
||||
isReceivingShadow: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -101,6 +103,8 @@ namespace gdjs {
|
||||
_animationSpeedScale: float = 1;
|
||||
_animationPaused: boolean = false;
|
||||
_crossfadeDuration: float = 0;
|
||||
_isCastingShadow: boolean = true;
|
||||
_isReceivingShadow: boolean = true;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
@@ -123,6 +127,8 @@ namespace gdjs {
|
||||
objectData.content.materialType
|
||||
);
|
||||
|
||||
this.setIsCastingShadow(objectData.content.isCastingShadow);
|
||||
this.setIsReceivingShadow(objectData.content.isReceivingShadow);
|
||||
this.onModelChanged(objectData);
|
||||
|
||||
this._crossfadeDuration = objectData.content.crossfadeDuration || 0;
|
||||
@@ -195,6 +201,18 @@ namespace gdjs {
|
||||
newObjectData.content.centerLocation
|
||||
);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isCastingShadow !==
|
||||
newObjectData.content.isCastingShadow
|
||||
) {
|
||||
this.setIsCastingShadow(newObjectData.content.isCastingShadow);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.isReceivingShadow !==
|
||||
newObjectData.content.isReceivingShadow
|
||||
) {
|
||||
this.setIsReceivingShadow(newObjectData.content.isReceivingShadow);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -358,6 +376,16 @@ namespace gdjs {
|
||||
return this._renderer.hasAnimationEnded();
|
||||
}
|
||||
|
||||
setIsCastingShadow(value: boolean): void {
|
||||
this._isCastingShadow = value;
|
||||
this._renderer._updateShadow();
|
||||
}
|
||||
|
||||
setIsReceivingShadow(value: boolean): void {
|
||||
this._isReceivingShadow = value;
|
||||
this._renderer._updateShadow();
|
||||
}
|
||||
|
||||
setCrossfadeDuration(duration: number): void {
|
||||
if (this._crossfadeDuration === duration) return;
|
||||
this._crossfadeDuration = duration;
|
||||
|
@@ -286,6 +286,7 @@ namespace gdjs {
|
||||
this.get3DRendererObject().remove(this._threeObject);
|
||||
this.get3DRendererObject().add(threeObject);
|
||||
this._threeObject = threeObject;
|
||||
this._updateShadow();
|
||||
|
||||
// Start the current animation on the new 3D object.
|
||||
this._animationMixer = new THREE.AnimationMixer(root);
|
||||
@@ -323,6 +324,13 @@ namespace gdjs {
|
||||
return this._originalModel.animations[animationIndex].name;
|
||||
}
|
||||
|
||||
_updateShadow() {
|
||||
this._threeObject.traverse((child) => {
|
||||
child.castShadow = this._model3DRuntimeObject._isCastingShadow;
|
||||
child.receiveShadow = this._model3DRuntimeObject._isReceivingShadow;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if animation has ended.
|
||||
* The animation had ended if:
|
||||
|
BIN
Extensions/3D/clouds1_down.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
Extensions/3D/clouds1_east.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
Extensions/3D/clouds1_north.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
Extensions/3D/clouds1_south.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
Extensions/3D/clouds1_up.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
Extensions/3D/clouds1_west.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
@@ -14,6 +14,7 @@ describe('gdjs.AnchorRuntimeBehavior', () => {
|
||||
effects: [],
|
||||
content: {},
|
||||
childrenContent: {},
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
});
|
||||
runtimeScene.addObject(customObject);
|
||||
customObject.setPosition(500, 250);
|
||||
|
@@ -75,9 +75,9 @@ module.exports = {
|
||||
.getOrCreate('align')
|
||||
.setValue(objectContent.align)
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.addChoice('left', _('Left'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('right', _('Right'))
|
||||
.setLabel(_('Base alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
@@ -88,9 +88,9 @@ module.exports = {
|
||||
.getOrCreate('verticalTextAlignment')
|
||||
.setValue(objectContent.verticalTextAlignment)
|
||||
.setType('choice')
|
||||
.addExtraInfo('top')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('bottom')
|
||||
.addChoice('top', _('Top'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('bottom', _('Bottom'))
|
||||
.setLabel(_('Vertical alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
|
@@ -61,9 +61,9 @@ module.exports = {
|
||||
.getOrCreate('align')
|
||||
.setValue(objectContent.align)
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.addChoice('left', _('Left'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('right', _('Right'))
|
||||
.setLabel(_('Alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
@@ -74,9 +74,9 @@ module.exports = {
|
||||
.getOrCreate('verticalTextAlignment')
|
||||
.setValue(objectContent.verticalTextAlignment)
|
||||
.setType('choice')
|
||||
.addExtraInfo('top')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('bottom')
|
||||
.addChoice('top', _('Top'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('bottom', _('Bottom'))
|
||||
.setLabel(_('Vertical alignment'))
|
||||
.setGroup(_('Appearance'));
|
||||
|
||||
|
@@ -194,9 +194,9 @@ ParticleEmitterObject::GetProperties() const {
|
||||
: GetRendererType() == Line ? "Line"
|
||||
: "Image")
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("Circle")
|
||||
.AddExtraInfo("Line")
|
||||
.AddExtraInfo("Image")
|
||||
.AddChoice("Circle", _("Circle"))
|
||||
.AddChoice("Line", _("Line"))
|
||||
.AddChoice("Image", _("Image"))
|
||||
.SetLabel(_("Particle type"))
|
||||
.SetHasImpactOnOtherProperties(true);
|
||||
|
||||
|
@@ -818,7 +818,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -852,7 +852,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -886,7 +886,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('yesorno', _('Can sleep?'), '', false)
|
||||
.addParameter('yesorno', _('Can sleep'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setSleepingAllowed');
|
||||
@@ -1296,7 +1296,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Layer (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1332,7 +1332,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Mask (1 - 16)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -2409,7 +2409,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointLimits');
|
||||
|
||||
@@ -2488,7 +2488,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableRevoluteJointMotor');
|
||||
|
||||
@@ -2727,7 +2727,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointLimits');
|
||||
|
||||
@@ -2806,7 +2806,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enablePrismaticJointMotor');
|
||||
|
||||
@@ -3486,7 +3486,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Joint ID'))
|
||||
.addParameter('yesorno', _('Enable?'))
|
||||
.addParameter('yesorno', _('Enable'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableWheelJointMotor');
|
||||
|
||||
|
@@ -274,7 +274,7 @@ module.exports = {
|
||||
.setLabel('Fixed Rotation')
|
||||
.setDescription(
|
||||
_(
|
||||
"If enabled, the object won't rotate and will stay at the same angle. Useful for characters for example."
|
||||
"If enabled, the object won't rotate and will stay at the same angle."
|
||||
)
|
||||
)
|
||||
.setGroup(_('Movement'));
|
||||
@@ -845,7 +845,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Treat as bullet?'), '', false)
|
||||
.addParameter('yesorno', _('Treat as bullet'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBullet');
|
||||
@@ -870,7 +870,7 @@ module.exports = {
|
||||
'SetFixedRotation',
|
||||
_('Fixed rotation'),
|
||||
_(
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate."
|
||||
"Enable or disable an object fixed rotation. If enabled the object won't be able to rotate. This action has no effect on characters."
|
||||
),
|
||||
_('Set _PARAM0_ fixed rotation: _PARAM2_'),
|
||||
_('Dynamics'),
|
||||
@@ -879,7 +879,7 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('yesorno', _('Fixed rotation?'), '', false)
|
||||
.addParameter('yesorno', _('Fixed rotation'), '', false)
|
||||
.setDefaultValue('false')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setFixedRotation');
|
||||
@@ -1054,7 +1054,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Layer (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableLayer');
|
||||
@@ -1090,7 +1090,7 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.addParameter('expression', _('Mask (1 - 8)'))
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.addParameter('yesorno', _('Enable'), '', false)
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('enableMask');
|
||||
@@ -1270,7 +1270,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Application point on Z axis'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
'Use `MassCenterX`, `MassCenterY` and `MassCenterZ` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
@@ -1544,6 +1544,19 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterY');
|
||||
|
||||
aut
|
||||
.addExpression(
|
||||
'MassCenterZ',
|
||||
_('Mass center Z'),
|
||||
_('Mass center Z'),
|
||||
'',
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getMassCenterZ');
|
||||
}
|
||||
// Collision
|
||||
extension
|
||||
|
@@ -927,9 +927,7 @@ namespace gdjs {
|
||||
const angularVelocityY = angularVelocity.GetY();
|
||||
const angularVelocityZ = angularVelocity.GetZ();
|
||||
|
||||
let bodyID = this._body.GetID();
|
||||
bodyInterface.RemoveBody(bodyID);
|
||||
bodyInterface.DestroyBody(bodyID);
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
@@ -938,7 +936,7 @@ namespace gdjs {
|
||||
if (!this._body) {
|
||||
return;
|
||||
}
|
||||
bodyID = this._body.GetID();
|
||||
const bodyID = this._body.GetID();
|
||||
bodyInterface.SetLinearVelocity(
|
||||
bodyID,
|
||||
this.getVec3(linearVelocityX, linearVelocityY, linearVelocityZ)
|
||||
|
@@ -733,7 +733,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setWheelOffsetZ(wheelOffsetZ: float): void {
|
||||
this._wheelOffsetY = wheelOffsetZ;
|
||||
this._wheelOffsetZ = wheelOffsetZ;
|
||||
this._updateWheels();
|
||||
}
|
||||
|
||||
@@ -783,11 +783,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
hasFrontWheelDrive(): boolean {
|
||||
return this._hasBackWheelDrive;
|
||||
return this._hasFrontWheelDrive;
|
||||
}
|
||||
|
||||
setFrontWheelDrive(hasFrontWheelDrive: boolean): void {
|
||||
this._hasBackWheelDrive = hasFrontWheelDrive;
|
||||
this._hasFrontWheelDrive = hasFrontWheelDrive;
|
||||
this.invalidateShape();
|
||||
}
|
||||
|
||||
|
@@ -486,7 +486,16 @@ namespace gdjs {
|
||||
this._state.beforeMovingX();
|
||||
|
||||
//Ensure the object is not stuck
|
||||
if (this._separateFromPlatforms(this._potentialCollidingObjects, true)) {
|
||||
const hasPopOutOfPlatform = this._separateFromPlatforms(
|
||||
this._potentialCollidingObjects,
|
||||
true
|
||||
);
|
||||
if (hasPopOutOfPlatform && !this._jumpKey) {
|
||||
// TODO This is probably unnecessary because `_canJump` is already set
|
||||
// to true when entering the `OnFloor` state.
|
||||
// This is wrongly allowing double jumps when characters are flipped
|
||||
// with an offset center.
|
||||
|
||||
//After being unstuck, the object must be able to jump again.
|
||||
this._canJump = true;
|
||||
}
|
||||
|
@@ -246,10 +246,10 @@ std::map<gd::String, gd::PropertyDescriptor> ShapePainterObject::GetProperties()
|
||||
objectProperties["antialiasing"]
|
||||
.SetValue(GetAntialiasing())
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("none")
|
||||
.AddExtraInfo("low")
|
||||
.AddExtraInfo("medium")
|
||||
.AddExtraInfo("high")
|
||||
.AddChoice("none", _("None"))
|
||||
.AddChoice("low", _("Low quality"))
|
||||
.AddChoice("medium", _("Medium quality"))
|
||||
.AddChoice("high", _("High quality"))
|
||||
.SetGroup(_("Drawing"))
|
||||
.SetLabel(_("Antialiasing"))
|
||||
.SetDescription(_("Antialiasing mode"));
|
||||
|
@@ -195,11 +195,35 @@ namespace gdjs {
|
||||
}
|
||||
/**
|
||||
* To be called when the game is disposed.
|
||||
* Clear the Spine Atlases loaded in this manager.
|
||||
* Clear the Spine atlases loaded in this manager.
|
||||
*/
|
||||
dispose(): void {
|
||||
this._loadedSpineAtlases.clear();
|
||||
this._loadingSpineAtlases.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the Spine atlases loaded in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
|
||||
if (loadedSpineAtlas) {
|
||||
loadedSpineAtlas.dispose();
|
||||
this._loadedSpineAtlases.delete(resourceData);
|
||||
}
|
||||
|
||||
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
|
||||
if (loadingSpineAtlas) {
|
||||
loadingSpineAtlas.then((atl) => atl.dispose());
|
||||
this._loadingSpineAtlases.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -126,5 +126,22 @@ namespace gdjs {
|
||||
dispose(): void {
|
||||
this._loadedSpines.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the Spine skeleton data loaded in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedSpine = this._loadedSpines.get(resourceData);
|
||||
if (loadedSpine) {
|
||||
this._loadedSpines.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -131,14 +131,14 @@ module.exports = {
|
||||
.getOrCreate('inputType')
|
||||
.setValue(objectContent.inputType || '')
|
||||
.setType('choice')
|
||||
.addExtraInfo('text')
|
||||
.addExtraInfo('text area')
|
||||
.addExtraInfo('email')
|
||||
.addExtraInfo('password')
|
||||
.addExtraInfo('number')
|
||||
.addExtraInfo('telephone number')
|
||||
.addExtraInfo('url')
|
||||
.addExtraInfo('search')
|
||||
.addChoice('text', _('Text'))
|
||||
.addChoice('text area', _('Text area'))
|
||||
.addChoice('email', _('Email'))
|
||||
.addChoice('password', _('Password'))
|
||||
.addChoice('number', _('Number'))
|
||||
.addChoice('telephone number', _('Telephone number'))
|
||||
.addChoice('url', _('URL'))
|
||||
.addChoice('search', _('Search'))
|
||||
.setLabel(_('Input type'))
|
||||
.setDescription(
|
||||
_(
|
||||
@@ -250,9 +250,9 @@ module.exports = {
|
||||
.getOrCreate('textAlign')
|
||||
.setValue(objectContent.textAlign || 'left')
|
||||
.setType('choice')
|
||||
.addExtraInfo('left')
|
||||
.addExtraInfo('center')
|
||||
.addExtraInfo('right')
|
||||
.addChoice('left', _('Left'))
|
||||
.addChoice('center', _('Center'))
|
||||
.addChoice('right', _('Right'))
|
||||
.setLabel(_('Text alignment'))
|
||||
.setGroup(_('Field appearance'));
|
||||
|
||||
|
@@ -160,9 +160,9 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
|
||||
objectProperties["textAlignment"]
|
||||
.SetValue(textAlignment)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("left")
|
||||
.AddExtraInfo("center")
|
||||
.AddExtraInfo("right")
|
||||
.AddChoice("left", _("Left"))
|
||||
.AddChoice("center", _("Center"))
|
||||
.AddChoice("right", _("Right"))
|
||||
.SetLabel(_("Alignment"))
|
||||
.SetDescription(_("Alignment of the text when multiple lines are displayed"))
|
||||
.SetGroup(_("Font"))
|
||||
@@ -171,9 +171,9 @@ std::map<gd::String, gd::PropertyDescriptor> TextObject::GetProperties() const {
|
||||
objectProperties["verticalTextAlignment"]
|
||||
.SetValue(verticalTextAlignment)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("top")
|
||||
.AddExtraInfo("center")
|
||||
.AddExtraInfo("bottom")
|
||||
.AddChoice("top", _("Top"))
|
||||
.AddChoice("center", _("Center"))
|
||||
.AddChoice("bottom", _("Bottom"))
|
||||
.SetLabel(_("Vertical alignment"))
|
||||
.SetGroup(_("Font"))
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
@@ -102,9 +102,9 @@ const defineTileMap = function (extension, _, gd) {
|
||||
'displayMode',
|
||||
new gd.PropertyDescriptor(objectContent.displayMode)
|
||||
.setType('choice')
|
||||
.addExtraInfo('visible')
|
||||
.addExtraInfo('all')
|
||||
.addExtraInfo('index')
|
||||
.addChoice('visible', _('Visible layers'))
|
||||
.addChoice('all', _('All layers'))
|
||||
.addChoice('index', _('Only the layer with the specified index'))
|
||||
.setLabel(_('Display mode'))
|
||||
.setGroup(_('Appearance'))
|
||||
);
|
||||
|
@@ -14,6 +14,7 @@ namespace gdjs {
|
||||
animatable?: SpriteAnimationData[];
|
||||
variant: string;
|
||||
childrenContent: { [objectName: string]: ObjectConfiguration & any };
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -111,9 +112,19 @@ namespace gdjs {
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
eventsBasedObjectData.defaultVariant.instances.length == 0;
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
if (customObjectData.variant) {
|
||||
if (
|
||||
customObjectData.variant &&
|
||||
!isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
) {
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
variantIndex < eventsBasedObjectData.variants.length;
|
||||
@@ -154,10 +165,12 @@ namespace gdjs {
|
||||
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
|
||||
super.reinitialize(objectData);
|
||||
|
||||
this._initializeFromObjectData(objectData);
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(objectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
// When changing the variant, the instance is like a new instance.
|
||||
// We call again `onCreated` at the end, like done by the constructor
|
||||
// the first time it's created.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
@@ -172,6 +185,34 @@ namespace gdjs {
|
||||
newObjectData.animatable || []
|
||||
);
|
||||
}
|
||||
if (oldObjectData.variant !== newObjectData.variant) {
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const hasInnerAreaChanged =
|
||||
oldObjectData.isInnerAreaFollowingParentSize &&
|
||||
this._instanceContainer._initialInnerArea &&
|
||||
this._innerArea &&
|
||||
(this._instanceContainer._initialInnerArea.min[0] !==
|
||||
this._innerArea.min[0] ||
|
||||
this._instanceContainer._initialInnerArea.min[1] !==
|
||||
this._innerArea.min[1] ||
|
||||
this._instanceContainer._initialInnerArea.max[0] !==
|
||||
this._innerArea.max[0] ||
|
||||
this._instanceContainer._initialInnerArea.max[1] !==
|
||||
this._innerArea.max[1]);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(newObjectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
this.onCreated();
|
||||
|
||||
// Keep the custom size
|
||||
if (hasInnerAreaChanged) {
|
||||
this.setWidth(width);
|
||||
this.setHeight(height);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,6 @@ namespace gdjs {
|
||||
objectData: ObjectData & CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this.getRenderer().reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected override _createRender(): gdjs.CustomRuntimeObject2DRenderer {
|
||||
|
@@ -24,7 +24,7 @@ namespace gdjs {
|
||||
*
|
||||
* @see gdjs.CustomRuntimeObject._innerArea
|
||||
**/
|
||||
private _initialInnerArea: {
|
||||
_initialInnerArea: {
|
||||
min: [float, float, float];
|
||||
max: [float, float, float];
|
||||
} | null = null;
|
||||
@@ -47,6 +47,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
addLayer(layerData: LayerData) {
|
||||
if (this._layers.containsKey(layerData.name)) {
|
||||
return;
|
||||
}
|
||||
const layer = new gdjs.RuntimeCustomObjectLayer(layerData, this);
|
||||
this._layers.put(layerData.name, layer);
|
||||
this._orderedLayers.push(layer);
|
||||
@@ -71,6 +74,10 @@ namespace gdjs {
|
||||
this.onDestroyFromScene(this._parent);
|
||||
}
|
||||
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
this._setOriginalInnerArea(eventsBasedObjectVariantData);
|
||||
|
||||
// Registering objects
|
||||
@@ -83,7 +90,8 @@ namespace gdjs {
|
||||
// The children configuration override only applies to the default variant.
|
||||
if (
|
||||
customObjectData.childrenContent &&
|
||||
!eventsBasedObjectVariantData.name
|
||||
(!eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
|
||||
) {
|
||||
this.registerObject({
|
||||
...childObjectData,
|
||||
|
@@ -162,5 +162,29 @@ namespace gdjs {
|
||||
this._invalidModel.scene.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the models, resources loaded and destroy 3D models loaders in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedThreeModel = this._loadedThreeModels.get(resourceData);
|
||||
if (loadedThreeModel) {
|
||||
loadedThreeModel.scene.clear();
|
||||
this._loadedThreeModels.delete(resourceData);
|
||||
}
|
||||
|
||||
const downloadedArrayBuffer =
|
||||
this._downloadedArrayBuffers.get(resourceData);
|
||||
if (downloadedArrayBuffer) {
|
||||
this._downloadedArrayBuffers.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('ResourceLoader');
|
||||
const debugLogger = new gdjs.Logger('ResourceLoader - debug').enable(false);
|
||||
|
||||
const addSearchParameterToUrl = (
|
||||
url: string,
|
||||
@@ -96,17 +97,15 @@ namespace gdjs {
|
||||
*/
|
||||
private _globalResources: Array<string>;
|
||||
/**
|
||||
* Resources by scene names.
|
||||
* Resources and the loading state of each scene, indexed by scene name.
|
||||
*/
|
||||
private _sceneResources: Map<string, Array<string>>;
|
||||
/**
|
||||
* Keep track of which scene whose resources has already be pre-loaded.
|
||||
*/
|
||||
private _sceneNamesToLoad: Set<string>;
|
||||
/**
|
||||
* Keep track of which scene whose resources has already be loaded.
|
||||
*/
|
||||
private _sceneNamesToMakeReady: Set<string>;
|
||||
private _sceneLoadingStates: Map<
|
||||
string,
|
||||
{
|
||||
resourceNames: Array<string>;
|
||||
status: 'not-loaded' | 'loaded' | 'ready';
|
||||
}
|
||||
> = new Map();
|
||||
/**
|
||||
* A queue of scenes whose resources are still to be pre-loaded.
|
||||
*/
|
||||
@@ -127,11 +126,12 @@ namespace gdjs {
|
||||
private _spineManager: SpineManager | null = null;
|
||||
|
||||
/**
|
||||
* Only used by events.
|
||||
* The name of the scene for which resources are currently being loaded.
|
||||
*/
|
||||
private currentLoadingSceneName: string = '';
|
||||
/**
|
||||
* Only used by events.
|
||||
* The progress, between 0 and 1, of the loading of the resource, for the
|
||||
* scene that is being loaded (see `currentLoadingSceneName`).
|
||||
*/
|
||||
private currentSceneLoadingProgress: float = 0;
|
||||
/**
|
||||
@@ -144,8 +144,8 @@ namespace gdjs {
|
||||
/**
|
||||
* @param runtimeGame The game.
|
||||
* @param resourceDataArray The resources data of the game.
|
||||
* @param globalResources The resources needed for any layer.
|
||||
* @param layoutDataArray The resources used by each layer.
|
||||
* @param globalResources The resources needed for any scene.
|
||||
* @param layoutDataArray The resources used by each scene.
|
||||
*/
|
||||
constructor(
|
||||
runtimeGame: RuntimeGame,
|
||||
@@ -158,9 +158,6 @@ namespace gdjs {
|
||||
this._globalResources = globalResources;
|
||||
|
||||
// These 3 attributes are filled by `setResources`.
|
||||
this._sceneResources = new Map<string, Array<string>>();
|
||||
this._sceneNamesToLoad = new Set<string>();
|
||||
this._sceneNamesToMakeReady = new Set<string>();
|
||||
this.setResources(resourceDataArray, globalResources, layoutDataArray);
|
||||
|
||||
this._imageManager = new gdjs.ImageManager(this);
|
||||
@@ -224,23 +221,31 @@ namespace gdjs {
|
||||
): void {
|
||||
this._globalResources = globalResources;
|
||||
|
||||
this._sceneResources.clear();
|
||||
this._sceneNamesToLoad.clear();
|
||||
this._sceneNamesToMakeReady.clear();
|
||||
this._sceneLoadingStates.clear();
|
||||
|
||||
for (const layoutData of layoutDataArray) {
|
||||
this._sceneResources.set(
|
||||
layoutData.name,
|
||||
layoutData.usedResources.map((resource) => resource.name)
|
||||
);
|
||||
this._sceneNamesToLoad.add(layoutData.name);
|
||||
this._sceneNamesToMakeReady.add(layoutData.name);
|
||||
this._sceneLoadingStates.set(layoutData.name, {
|
||||
resourceNames: layoutData.usedResources.map(
|
||||
(resource) => resource.name
|
||||
),
|
||||
status: 'not-loaded',
|
||||
});
|
||||
}
|
||||
// TODO Clearing the queue doesn't abort the running task, but it should
|
||||
// not matter as resource loading is really fast in preview mode.
|
||||
this._sceneToLoadQueue.length = 0;
|
||||
for (let index = layoutDataArray.length - 1; index >= 0; index--) {
|
||||
const layoutData = layoutDataArray[index];
|
||||
this._sceneToLoadQueue.push(new SceneLoadingTask(layoutData.name));
|
||||
|
||||
const resourcesPreloading = layoutData.resourcesPreloading || 'inherit';
|
||||
const resolvedResourcesPreloading =
|
||||
resourcesPreloading === 'inherit'
|
||||
? this._runtimeGame.getSceneResourcesPreloading()
|
||||
: resourcesPreloading;
|
||||
|
||||
if (resolvedResourcesPreloading === 'at-startup') {
|
||||
this._sceneToLoadQueue.push(new SceneLoadingTask(layoutData.name));
|
||||
}
|
||||
}
|
||||
|
||||
this._resources.clear();
|
||||
@@ -271,8 +276,10 @@ namespace gdjs {
|
||||
onProgress(loadedCount, this._resources.size);
|
||||
}
|
||||
);
|
||||
this._sceneNamesToLoad.clear();
|
||||
this._sceneNamesToMakeReady.clear();
|
||||
|
||||
for (const sceneLoadingState of this._sceneLoadingStates.values()) {
|
||||
sceneLoadingState.status = 'ready';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,17 +289,21 @@ namespace gdjs {
|
||||
firstSceneName: string,
|
||||
onProgress: (count: number, total: number) => void
|
||||
): Promise<void> {
|
||||
const sceneResources = this._sceneResources.get(firstSceneName);
|
||||
if (!sceneResources) {
|
||||
const firstSceneState = this._sceneLoadingStates.get(firstSceneName);
|
||||
if (!firstSceneState) {
|
||||
logger.warn(
|
||||
'Can\'t load resource for unknown scene: "' + firstSceneName + '".'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let loadedCount = 0;
|
||||
const resources = [...this._globalResources, ...sceneResources.values()];
|
||||
const resourceNames = [
|
||||
...this._globalResources,
|
||||
...firstSceneState.resourceNames,
|
||||
];
|
||||
await processAndRetryIfNeededWithPromisePool(
|
||||
resources,
|
||||
resourceNames,
|
||||
maxForegroundConcurrency,
|
||||
maxAttempt,
|
||||
async (resourceName) => {
|
||||
@@ -304,11 +315,11 @@ namespace gdjs {
|
||||
await this._loadResource(resource);
|
||||
await this._processResource(resource);
|
||||
loadedCount++;
|
||||
onProgress(loadedCount, resources.length);
|
||||
onProgress(loadedCount, resourceNames.length);
|
||||
}
|
||||
);
|
||||
this._setSceneAssetsLoaded(firstSceneName);
|
||||
this._setSceneAssetsReady(firstSceneName);
|
||||
|
||||
firstSceneState.status = 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,17 +329,32 @@ namespace gdjs {
|
||||
* scenes.
|
||||
*/
|
||||
async loadAllSceneInBackground(): Promise<void> {
|
||||
if (this.currentLoadingSceneName) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogger.log('Loading all scene resources, in background.');
|
||||
while (this._sceneToLoadQueue.length > 0) {
|
||||
debugLogger.log(
|
||||
`Still resources of ${this._sceneToLoadQueue.length} scene(s) to load: ${this._sceneToLoadQueue.map((task) => task.sceneName).join(', ')}`
|
||||
);
|
||||
const task = this._sceneToLoadQueue[this._sceneToLoadQueue.length - 1];
|
||||
if (task === undefined) {
|
||||
continue;
|
||||
}
|
||||
this.currentLoadingSceneName = task.sceneName;
|
||||
if (!this.areSceneAssetsLoaded(task.sceneName)) {
|
||||
debugLogger.log(
|
||||
`Loading (but not processing) resources for scene ${task.sceneName}.`
|
||||
);
|
||||
await this._doLoadSceneResources(
|
||||
task.sceneName,
|
||||
async (count, total) => task.onProgress(count, total)
|
||||
);
|
||||
debugLogger.log(
|
||||
`Done loading (but not processing) resources for scene ${task.sceneName}.`
|
||||
);
|
||||
|
||||
// A scene may have been moved last while awaiting resources to be
|
||||
// downloaded (see _prioritizeScene).
|
||||
this._sceneToLoadQueue.splice(
|
||||
@@ -340,6 +366,7 @@ namespace gdjs {
|
||||
this._sceneToLoadQueue.pop();
|
||||
}
|
||||
}
|
||||
debugLogger.log(`Scene resources loading finished.`);
|
||||
this.currentLoadingSceneName = '';
|
||||
}
|
||||
|
||||
@@ -347,16 +374,17 @@ namespace gdjs {
|
||||
sceneName: string,
|
||||
onProgress?: (count: number, total: number) => Promise<void>
|
||||
): Promise<void> {
|
||||
const sceneResources = this._sceneResources.get(sceneName);
|
||||
if (!sceneResources) {
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) {
|
||||
logger.warn(
|
||||
'Can\'t load resource for unknown scene: "' + sceneName + '".'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let loadedCount = 0;
|
||||
await processAndRetryIfNeededWithPromisePool(
|
||||
[...sceneResources.values()],
|
||||
sceneState.resourceNames,
|
||||
this._isLoadingInForeground
|
||||
? maxForegroundConcurrency
|
||||
: maxBackgroundConcurrency,
|
||||
@@ -369,11 +397,13 @@ namespace gdjs {
|
||||
}
|
||||
await this._loadResource(resource);
|
||||
loadedCount++;
|
||||
this.currentSceneLoadingProgress = loadedCount / this._resources.size;
|
||||
onProgress && (await onProgress(loadedCount, this._resources.size));
|
||||
this.currentSceneLoadingProgress =
|
||||
loadedCount / sceneState.resourceNames.length;
|
||||
onProgress &&
|
||||
(await onProgress(loadedCount, sceneState.resourceNames.length));
|
||||
}
|
||||
);
|
||||
this._setSceneAssetsLoaded(sceneName);
|
||||
sceneState.status = 'loaded';
|
||||
}
|
||||
|
||||
private async _loadResource(resource: ResourceData): Promise<void> {
|
||||
@@ -405,8 +435,8 @@ namespace gdjs {
|
||||
}
|
||||
await this.loadSceneResources(sceneName, onProgress);
|
||||
|
||||
const sceneResources = this._sceneResources.get(sceneName);
|
||||
if (!sceneResources) {
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) {
|
||||
logger.warn(
|
||||
'Can\'t load resource for unknown scene: "' + sceneName + '".'
|
||||
);
|
||||
@@ -414,7 +444,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
let parsedCount = 0;
|
||||
for (const resourceName of sceneResources) {
|
||||
for (const resourceName of sceneState.resourceNames) {
|
||||
const resource = this._resources.get(resourceName);
|
||||
if (!resource) {
|
||||
logger.warn('Unable to find resource "' + resourceName + '".');
|
||||
@@ -422,9 +452,10 @@ namespace gdjs {
|
||||
}
|
||||
await this._processResource(resource);
|
||||
parsedCount++;
|
||||
onProgress && (await onProgress(parsedCount, sceneResources.length));
|
||||
onProgress &&
|
||||
(await onProgress(parsedCount, sceneState.resourceNames.length));
|
||||
}
|
||||
this._setSceneAssetsReady(sceneName);
|
||||
sceneState.status = 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,15 +469,25 @@ namespace gdjs {
|
||||
sceneName: string,
|
||||
onProgress?: (count: number, total: number) => void
|
||||
): Promise<void> {
|
||||
debugLogger.log(
|
||||
`Prioritization of loading of resources for scene ${sceneName} was requested.`
|
||||
);
|
||||
|
||||
this._isLoadingInForeground = true;
|
||||
const task = this._prioritizeScene(sceneName);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!task) {
|
||||
this._isLoadingInForeground = false;
|
||||
debugLogger.log(
|
||||
`Loading of resources for scene ${sceneName} was immediately resolved.`
|
||||
);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
task.registerCallback(() => {
|
||||
debugLogger.log(
|
||||
`Loading of resources for scene ${sceneName} just finished.`
|
||||
);
|
||||
this._isLoadingInForeground = false;
|
||||
resolve();
|
||||
}, onProgress);
|
||||
@@ -463,6 +504,51 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when a scene is unloaded.
|
||||
*/
|
||||
unloadSceneResources({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
}: {
|
||||
unloadedSceneName: string;
|
||||
newSceneName: string | null;
|
||||
}): void {
|
||||
if (!unloadedSceneName) return;
|
||||
debugLogger.log(
|
||||
`Unloading of resources for scene ${unloadedSceneName} was requested.`
|
||||
);
|
||||
|
||||
const sceneUniqueResourcesByKindMap =
|
||||
this._getResourcesByKindOnlyUsedInUnloadedScene({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
});
|
||||
|
||||
for (const [kindResourceManager, resourceManager] of this
|
||||
._resourceManagersMap) {
|
||||
const resources =
|
||||
sceneUniqueResourcesByKindMap.get(kindResourceManager);
|
||||
if (resources) {
|
||||
debugLogger.log(
|
||||
`Unloading of resources of kind ${kindResourceManager} for scene ${unloadedSceneName}: `,
|
||||
resources.map((resource) => resource.name).join(', ')
|
||||
);
|
||||
resourceManager.unloadResourcesList(resources);
|
||||
}
|
||||
}
|
||||
|
||||
debugLogger.log(
|
||||
`Unloading of resources for scene ${unloadedSceneName} finished.`
|
||||
);
|
||||
|
||||
const sceneState = this._sceneLoadingStates.get(unloadedSceneName);
|
||||
if (sceneState) {
|
||||
sceneState.status = 'not-loaded';
|
||||
}
|
||||
// TODO: mark the scene as unloaded so it's not automatically loaded again eagerly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a given scene at the end of the queue.
|
||||
*
|
||||
@@ -470,16 +556,41 @@ namespace gdjs {
|
||||
* this scene will be the next to be loaded.
|
||||
*/
|
||||
private _prioritizeScene(sceneName: string): SceneLoadingTask | null {
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) return null;
|
||||
if (sceneState.status === 'loaded' || sceneState.status === 'ready') {
|
||||
debugLogger.log(
|
||||
`Scene ${sceneName} is already loaded. Skipping prioritization.`
|
||||
);
|
||||
|
||||
// The scene is already loaded, nothing to do.
|
||||
return null;
|
||||
}
|
||||
|
||||
// The scene is not loaded: either prioritize it or add it to the loading queue.
|
||||
const taskIndex = this._sceneToLoadQueue.findIndex(
|
||||
(task) => task.sceneName === sceneName
|
||||
);
|
||||
if (taskIndex < 0) {
|
||||
// The scene is already loaded.
|
||||
return null;
|
||||
let task: SceneLoadingTask;
|
||||
if (taskIndex !== -1) {
|
||||
// There is already a task for this scene in the queue.
|
||||
// Move it so that it's loaded first.
|
||||
task = this._sceneToLoadQueue[taskIndex];
|
||||
this._sceneToLoadQueue.splice(taskIndex, 1);
|
||||
this._sceneToLoadQueue.push(task);
|
||||
} else {
|
||||
// There is no task for this scene in the queue.
|
||||
// It might be because the scene was unloaded or never loaded.
|
||||
// In this case, we need to add a new task to the queue.
|
||||
task = new SceneLoadingTask(sceneName);
|
||||
this._sceneToLoadQueue.push(task);
|
||||
}
|
||||
const task = this._sceneToLoadQueue[taskIndex];
|
||||
this._sceneToLoadQueue.splice(taskIndex, 1);
|
||||
this._sceneToLoadQueue.push(task);
|
||||
|
||||
// Re-start the loading process in the background. While at the beginning of the game
|
||||
// it's not needed because already launched, a scene might be unloaded. This means
|
||||
// that we then need to relaunch the loading process.
|
||||
this.loadAllSceneInBackground();
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -511,7 +622,10 @@ namespace gdjs {
|
||||
* (but maybe not parsed).
|
||||
*/
|
||||
areSceneAssetsLoaded(sceneName: string): boolean {
|
||||
return !this._sceneNamesToLoad.has(sceneName);
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) return false;
|
||||
|
||||
return sceneState.status === 'loaded' || sceneState.status === 'ready';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,15 +633,10 @@ namespace gdjs {
|
||||
* parsed.
|
||||
*/
|
||||
areSceneAssetsReady(sceneName: string): boolean {
|
||||
return !this._sceneNamesToMakeReady.has(sceneName);
|
||||
}
|
||||
const sceneState = this._sceneLoadingStates.get(sceneName);
|
||||
if (!sceneState) return false;
|
||||
|
||||
private _setSceneAssetsLoaded(sceneName: string): void {
|
||||
this._sceneNamesToLoad.delete(sceneName);
|
||||
}
|
||||
|
||||
private _setSceneAssetsReady(sceneName: string): void {
|
||||
this._sceneNamesToMakeReady.delete(sceneName);
|
||||
return sceneState.status === 'ready';
|
||||
}
|
||||
|
||||
getResource(resourceName: string): ResourceData | null {
|
||||
@@ -636,6 +745,70 @@ namespace gdjs {
|
||||
getSpineAtlasManager(): gdjs.SpineAtlasManager | null {
|
||||
return this._spineAtlasManager;
|
||||
}
|
||||
|
||||
injectMockResourceManagerForTesting(
|
||||
resourceKind: ResourceKind,
|
||||
resourceManager: ResourceManager
|
||||
) {
|
||||
this._resourceManagersMap.set(resourceKind, resourceManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the map of resources that are only used in the scene that is being unloaded,
|
||||
* and that are not used in any other loaded scene (or the scene that is coming next).
|
||||
*/
|
||||
private _getResourcesByKindOnlyUsedInUnloadedScene({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
}: {
|
||||
unloadedSceneName: string;
|
||||
newSceneName: string | null;
|
||||
}): Map<ResourceKind, ResourceData[]> {
|
||||
const unloadedSceneState =
|
||||
this._sceneLoadingStates.get(unloadedSceneName);
|
||||
if (!unloadedSceneState) {
|
||||
return new Map<ResourceKind, ResourceData[]>();
|
||||
}
|
||||
|
||||
// Construct the set of all resources to unload. These are the resources
|
||||
// used in the scene that is being unloaded minus all the resources used
|
||||
// by the other scenes that are loaded (and the possible scene that is coming next).
|
||||
const resourceNamesToUnload = new Set<string>(
|
||||
unloadedSceneState.resourceNames
|
||||
);
|
||||
for (const [
|
||||
sceneName,
|
||||
sceneState,
|
||||
] of this._sceneLoadingStates.entries()) {
|
||||
if (sceneName === unloadedSceneName) continue;
|
||||
|
||||
if (
|
||||
sceneName === newSceneName ||
|
||||
sceneState.status === 'loaded' ||
|
||||
sceneState.status === 'ready'
|
||||
) {
|
||||
sceneState.resourceNames.forEach((resourceName) => {
|
||||
resourceNamesToUnload.delete(resourceName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const result = new Map<ResourceKind, ResourceData[]>();
|
||||
resourceNamesToUnload.forEach((resourceName) => {
|
||||
const resourceData = this._resources.get(resourceName);
|
||||
if (!resourceData) return;
|
||||
|
||||
const kind = resourceData.kind;
|
||||
const resources = result.get(kind);
|
||||
if (resources) {
|
||||
resources.push(resourceData);
|
||||
} else {
|
||||
result.set(kind, [resourceData]);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
type PromiseError<T> = { item: T; error: Error };
|
||||
|
@@ -35,5 +35,15 @@ namespace gdjs {
|
||||
* Using the manager after calling this method is undefined behavior.
|
||||
*/
|
||||
dispose(): void;
|
||||
|
||||
/**
|
||||
* Should clear all specified resources data and anything stored by this manager
|
||||
* for these resources.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources that need to be clear
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void;
|
||||
}
|
||||
}
|
||||
|
@@ -811,6 +811,8 @@ namespace gdjs {
|
||||
this._objectsCtor = new Hashtable();
|
||||
this._allInstancesList = [];
|
||||
this._instancesRemoved = [];
|
||||
this._layersCameraCoordinates = {};
|
||||
this._initialBehaviorSharedData = new Hashtable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -116,6 +116,9 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getRuntimeLayer(): gdjs.RuntimeLayer {
|
||||
return this;
|
||||
}
|
||||
getRenderer(): gdjs.LayerRenderer {
|
||||
return this._renderer;
|
||||
}
|
||||
|
@@ -362,8 +362,7 @@ namespace gdjs {
|
||||
if (objectsLists.items.hasOwnProperty(name)) {
|
||||
const allObjects = objectsContext.getObjects(name);
|
||||
const objectsList = objectsLists.items[name];
|
||||
objectsList.length = 0;
|
||||
objectsList.push.apply(objectsList, allObjects);
|
||||
gdjs.copyArray(allObjects, objectsList);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@@ -205,6 +205,28 @@ namespace gdjs {
|
||||
this._loadedFontFamily.clear();
|
||||
this._loadedFontFamilySet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the caches of loaded font families.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const resource = this._loadedFontFamily.get(resourceData);
|
||||
if (resource) {
|
||||
this._loadedFontFamily.delete(resourceData);
|
||||
}
|
||||
|
||||
const fontName = this._getFontFamilyFromFilename(resourceData);
|
||||
if (fontName) {
|
||||
this._loadedFontFamilySet.delete(fontName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Register the class to let the engine use it.
|
||||
|
@@ -939,6 +939,28 @@ namespace gdjs {
|
||||
dispose(): void {
|
||||
this.unloadAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this unloads all audio from the specified resources from memory.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const musicRes = this._loadedMusics.get(resourceData);
|
||||
if (musicRes) {
|
||||
this.unloadAudio(resourceData.name, true);
|
||||
}
|
||||
|
||||
const soundRes = this._loadedSounds.get(resourceData);
|
||||
if (soundRes) {
|
||||
this.unloadAudio(resourceData.name, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the class to let the engine use it.
|
||||
|
@@ -208,5 +208,26 @@ namespace gdjs {
|
||||
this._loadedJsons.clear();
|
||||
this._callbacks.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the JSONs loaded in this manager.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedJson = this._loadedJsons.get(resourceData);
|
||||
if (loadedJson) {
|
||||
this._loadedJsons.delete(resourceData);
|
||||
}
|
||||
|
||||
const callback = this._callbacks.get(resourceData);
|
||||
if (callback) {
|
||||
this._callbacks.delete(resourceData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -66,6 +66,7 @@ namespace gdjs {
|
||||
*/
|
||||
export class Logger {
|
||||
private readonly group: string;
|
||||
private enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* Create a new logger with the given group name.
|
||||
@@ -76,21 +77,30 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
log(...messages: any[]): void {
|
||||
if (!this.enabled) return;
|
||||
loggerOutput.log(this.group, objectsToString(messages), 'info');
|
||||
}
|
||||
|
||||
info(...messages: any[]): void {
|
||||
if (!this.enabled) return;
|
||||
loggerOutput.log(this.group, objectsToString(messages), 'info');
|
||||
}
|
||||
|
||||
warn(...messages: any[]): void {
|
||||
if (!this.enabled) return;
|
||||
loggerOutput.log(this.group, objectsToString(messages), 'warning');
|
||||
}
|
||||
|
||||
error(...messages: any[]): void {
|
||||
if (!this.enabled) return;
|
||||
loggerOutput.log(this.group, objectsToString(messages), 'error');
|
||||
}
|
||||
|
||||
enable(enabled: boolean): gdjs.Logger {
|
||||
this.enabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give access to the console output used by default by the logger.
|
||||
* This can be useful to restore the default log method if you overrode it
|
||||
|
@@ -50,12 +50,7 @@ namespace gdjs {
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer
|
||||
.getRenderer()
|
||||
.addRendererObject(this._pixiContainer, object.getZOrder());
|
||||
}
|
||||
this._pixiContainer.removeChildren();
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
|
@@ -307,6 +307,34 @@ namespace gdjs {
|
||||
this._pixiBitmapFontsToUninstall.length = 0;
|
||||
this._loadedFontsData.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this uninstalls fonts from memory and clear cache of loaded fonts.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const loadedFont = this._loadedFontsData.get(resourceData);
|
||||
if (loadedFont) {
|
||||
this._loadedFontsData.delete(resourceData);
|
||||
}
|
||||
|
||||
for (const bitmapFontInstallKey in this._pixiBitmapFontsInUse) {
|
||||
if (bitmapFontInstallKey.endsWith(resourceData.file))
|
||||
PIXI.BitmapFont.uninstall(bitmapFontInstallKey);
|
||||
}
|
||||
|
||||
for (const bitmapFontInstallKey of this._pixiBitmapFontsToUninstall) {
|
||||
if (bitmapFontInstallKey.endsWith(resourceData.file))
|
||||
PIXI.BitmapFont.uninstall(bitmapFontInstallKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the class to let the engine use it.
|
||||
|
@@ -17,6 +17,7 @@ namespace gdjs {
|
||||
getName: () => string;
|
||||
getRendererObject: () => RendererObjectInterface | null | undefined;
|
||||
get3DRendererObject: () => THREE.Object3D | null | undefined;
|
||||
getRuntimeLayer?: () => gdjs.RuntimeLayer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -519,6 +519,37 @@ namespace gdjs {
|
||||
}
|
||||
this._scaledTextures.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload the specified list of resources:
|
||||
* this clears the cache of loaded textures associated to these resources.
|
||||
*
|
||||
* Usually called when scene resoures are unloaded.
|
||||
*
|
||||
* @param resourcesList The list of specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList: ResourceData[]): void {
|
||||
resourcesList.forEach((resourceData) => {
|
||||
const resourceName = resourceData.name;
|
||||
const resource = this._loadedTextures.get(resourceData);
|
||||
if (resource) {
|
||||
resource.destroy(true);
|
||||
this._loadedTextures.delete(resourceData);
|
||||
}
|
||||
|
||||
const threeTexture = this._loadedThreeTextures.get(resourceName);
|
||||
if (threeTexture) {
|
||||
threeTexture.dispose();
|
||||
this._loadedThreeTextures.remove(resourceName);
|
||||
}
|
||||
|
||||
const threeMaterials = this._loadedThreeMaterials.get(resourceName);
|
||||
if (threeMaterials) {
|
||||
threeMaterials.dispose();
|
||||
this._loadedThreeMaterials.remove(resourceName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Register the class to let the engine use it.
|
||||
|
@@ -97,6 +97,8 @@ namespace gdjs {
|
||||
!gdjs.evtTools.common.isMobile()),
|
||||
preserveDrawingBuffer: true, // Keep to true to allow screenshots.
|
||||
});
|
||||
this._threeRenderer.shadowMap.enabled = true;
|
||||
this._threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
this._threeRenderer.useLegacyLights = true;
|
||||
this._threeRenderer.autoClear = false;
|
||||
this._threeRenderer.setSize(
|
||||
|
@@ -11,6 +11,8 @@ namespace gdjs {
|
||||
private _profilerText: PIXI.Text | null = null;
|
||||
private _showCursorAtNextRender: boolean = false;
|
||||
private _threeRenderer: THREE.WebGLRenderer | null = null;
|
||||
private loader: THREE.CubeTextureLoader;
|
||||
private texture: THREE.CubeTexture;
|
||||
private _layerRenderingMetrics: {
|
||||
rendered2DLayersCount: number;
|
||||
rendered3DLayersCount: number;
|
||||
@@ -33,6 +35,17 @@ namespace gdjs {
|
||||
this._threeRenderer = this._runtimeGameRenderer
|
||||
? this._runtimeGameRenderer.getThreeRenderer()
|
||||
: null;
|
||||
|
||||
this.loader = new THREE.CubeTextureLoader();
|
||||
|
||||
this.texture = this.loader.load([
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_east.png', // px
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_west.png', // nx
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_up.png', // py
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_down.png', // ny
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_north.png', // pz
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/Extensions/3D/clouds1_south.png', // nz
|
||||
]);
|
||||
}
|
||||
|
||||
onGameResolutionResized() {
|
||||
@@ -209,9 +222,11 @@ namespace gdjs {
|
||||
);
|
||||
threeRenderer.resetState();
|
||||
if (this._runtimeScene.getClearCanvas()) threeRenderer.clear();
|
||||
threeScene.background = new THREE.Color(
|
||||
this._runtimeScene.getBackgroundColor()
|
||||
);
|
||||
|
||||
threeScene.background = this.texture;
|
||||
// threeScene.background = new THREE.Color(
|
||||
// this._runtimeScene.getBackgroundColor()
|
||||
// );
|
||||
|
||||
isFirstRender = false;
|
||||
} else {
|
||||
|
@@ -184,6 +184,9 @@ namespace gdjs {
|
||||
*/
|
||||
_embeddedResourcesMappings: Map<string, Record<string, string>>;
|
||||
|
||||
_sceneResourcesPreloading: 'at-startup' | 'never';
|
||||
_sceneResourcesUnloading: 'at-scene-exit' | 'never';
|
||||
|
||||
/**
|
||||
* Optional client to connect to a debugger server.
|
||||
*/
|
||||
@@ -223,6 +226,11 @@ namespace gdjs {
|
||||
this._data = data;
|
||||
this._updateSceneAndExtensionsData();
|
||||
|
||||
this._sceneResourcesPreloading =
|
||||
this._data.properties.sceneResourcesPreloading || 'at-startup';
|
||||
this._sceneResourcesUnloading =
|
||||
this._data.properties.sceneResourcesUnloading || 'never';
|
||||
|
||||
this._resourcesLoader = new gdjs.ResourceLoader(
|
||||
this,
|
||||
data.resources.resources,
|
||||
@@ -245,6 +253,7 @@ namespace gdjs {
|
||||
this._antialiasingMode = this._data.properties.antialiasingMode;
|
||||
this._isAntialisingEnabledOnMobile =
|
||||
this._data.properties.antialisingEnabledOnMobile;
|
||||
|
||||
this._renderer = new gdjs.RuntimeGameRenderer(
|
||||
this,
|
||||
this._options.forceFullscreen || false
|
||||
@@ -363,6 +372,14 @@ namespace gdjs {
|
||||
return this._variablesByExtensionName.get(extensionName) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gdjs.ResourceLoader of the RuntimeGame.
|
||||
* @return The resource loader.
|
||||
*/
|
||||
getResourceLoader(): gdjs.ResourceLoader {
|
||||
return this._resourcesLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gdjs.SoundManager of the RuntimeGame.
|
||||
* @return The sound manager.
|
||||
@@ -764,6 +781,22 @@ namespace gdjs {
|
||||
return this._resourcesLoader.areSceneAssetsReady(sceneName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scene resources preloading mode.
|
||||
* It can be overriden by each scene.
|
||||
*/
|
||||
getSceneResourcesPreloading(): 'at-startup' | 'never' {
|
||||
return this._sceneResourcesPreloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scene resources unloading mode.
|
||||
* It can be overriden by each scene.
|
||||
*/
|
||||
getSceneResourcesUnloading(): 'at-scene-exit' | 'never' {
|
||||
return this._sceneResourcesUnloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all assets needed to display the 1st scene, displaying progress in
|
||||
* renderer.
|
||||
|
@@ -23,6 +23,7 @@ namespace gdjs {
|
||||
_timeManager: TimeManager;
|
||||
_gameStopRequested: boolean = false;
|
||||
_requestedScene: string = '';
|
||||
_resourcesUnloading: 'at-scene-exit' | 'never' | 'inherit' = 'inherit';
|
||||
private _asyncTasksManager = new gdjs.AsyncTasksManager();
|
||||
|
||||
/** True if loadFromScene was called and the scene is being played. */
|
||||
@@ -141,6 +142,7 @@ namespace gdjs {
|
||||
this._runtimeGame.getRenderer().setWindowTitle(sceneData.title);
|
||||
}
|
||||
this._name = sceneData.name;
|
||||
this._resourcesUnloading = sceneData.resourcesUnloading || 'inherit';
|
||||
this.setBackgroundColor(sceneData.r, sceneData.v, sceneData.b);
|
||||
|
||||
//Load layers
|
||||
@@ -578,6 +580,13 @@ namespace gdjs {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the strategy to unload resources of this scene.
|
||||
*/
|
||||
getResourcesUnloading(): 'at-scene-exit' | 'never' | 'inherit' {
|
||||
return this._resourcesUnloading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an identifier for a new object of the scene.
|
||||
*/
|
||||
|
@@ -63,10 +63,14 @@ namespace gdjs {
|
||||
this.pop();
|
||||
} else if (request === gdjs.SceneChangeRequest.PUSH_SCENE) {
|
||||
this.push(currentScene.getRequestedScene());
|
||||
} else if (request === gdjs.SceneChangeRequest.REPLACE_SCENE) {
|
||||
this.replace(currentScene.getRequestedScene());
|
||||
} else if (request === gdjs.SceneChangeRequest.CLEAR_SCENES) {
|
||||
this.replace(currentScene.getRequestedScene(), true);
|
||||
} else if (
|
||||
request === gdjs.SceneChangeRequest.REPLACE_SCENE ||
|
||||
request === gdjs.SceneChangeRequest.CLEAR_SCENES
|
||||
) {
|
||||
this.replace(
|
||||
currentScene.getRequestedScene(),
|
||||
request === gdjs.SceneChangeRequest.CLEAR_SCENES
|
||||
);
|
||||
} else {
|
||||
logger.error('Unrecognized change in scene stack: ' + request);
|
||||
}
|
||||
@@ -101,7 +105,10 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
scene.unloadScene();
|
||||
this._unloadSceneAndPossiblyResources({
|
||||
scene,
|
||||
newSceneName: null,
|
||||
});
|
||||
}
|
||||
|
||||
// Tell the new current scene it's being resumed
|
||||
@@ -140,6 +147,7 @@ namespace gdjs {
|
||||
this._loadNewScene(newSceneName);
|
||||
this._isNextLayoutLoading = false;
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -186,7 +194,7 @@ namespace gdjs {
|
||||
while (this._stack.length !== 0) {
|
||||
let scene = this._stack.pop();
|
||||
if (scene) {
|
||||
scene.unloadScene();
|
||||
this._unloadSceneAndPossiblyResources({ scene, newSceneName });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -194,7 +202,7 @@ namespace gdjs {
|
||||
if (this._stack.length !== 0) {
|
||||
let scene = this._stack.pop();
|
||||
if (scene) {
|
||||
scene.unloadScene();
|
||||
this._unloadSceneAndPossiblyResources({ scene, newSceneName });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,14 +380,52 @@ namespace gdjs {
|
||||
* Unload all the scenes and clear the stack.
|
||||
*/
|
||||
dispose(): void {
|
||||
for (const item of this._stack) {
|
||||
item.unloadScene();
|
||||
while (this._stack.length > 0) {
|
||||
const scene = this._stack.pop();
|
||||
if (scene) {
|
||||
this._unloadSceneAndPossiblyResources({
|
||||
scene,
|
||||
newSceneName: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._stack.length = 0;
|
||||
this._wasDisposed = true;
|
||||
}
|
||||
|
||||
private _unloadSceneAndPossiblyResources({
|
||||
scene,
|
||||
newSceneName,
|
||||
}: {
|
||||
scene: gdjs.RuntimeScene;
|
||||
newSceneName: string | null;
|
||||
}): void {
|
||||
const unloadedSceneName = scene.getName();
|
||||
|
||||
const resourcesUnloading = scene.getResourcesUnloading();
|
||||
const resolvedResourcesUnloading =
|
||||
resourcesUnloading === 'inherit'
|
||||
? this._runtimeGame.getSceneResourcesUnloading()
|
||||
: resourcesUnloading;
|
||||
|
||||
const shouldUnloadResources =
|
||||
resolvedResourcesUnloading === 'at-scene-exit' &&
|
||||
// Unload resources only if it's the last scene with this name in the stack.
|
||||
newSceneName !== scene.getName() &&
|
||||
this._stack.every((scene) => scene.getName() !== unloadedSceneName);
|
||||
|
||||
scene.unloadScene();
|
||||
// After this point, `scene` is no longer valid and should not be used anymore.
|
||||
// It was "disposed".
|
||||
|
||||
if (shouldUnloadResources) {
|
||||
this._runtimeGame.getResourceLoader().unloadSceneResources({
|
||||
unloadedSceneName,
|
||||
newSceneName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _throwIfDisposed(): void {
|
||||
if (this._wasDisposed) {
|
||||
throw 'The scene stack has been disposed and should not be used anymore.';
|
||||
|
9
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -169,6 +169,8 @@ declare interface LayoutData extends InstanceContainerData {
|
||||
title: string;
|
||||
behaviorsSharedData: BehaviorSharedData[];
|
||||
usedResources: ResourceReference[];
|
||||
resourcesPreloading?: 'at-startup' | 'never' | 'inherit';
|
||||
resourcesUnloading?: 'at-scene-exit' | 'never' | 'inherit';
|
||||
}
|
||||
|
||||
declare interface LayoutNetworkSyncData {
|
||||
@@ -229,7 +231,7 @@ declare interface EventsBasedObjectVariantData extends InstanceContainerData {
|
||||
/**
|
||||
* A value shared by every object instances.
|
||||
*
|
||||
* @see gdjs.CustomRuntimeObjectInstanceContainer._originalInnerArea
|
||||
* @see gdjs.CustomRuntimeObjectInstanceContainer._initialInnerArea
|
||||
**/
|
||||
_initialInnerArea: {
|
||||
min: [float, float, float];
|
||||
@@ -370,6 +372,8 @@ declare interface ProjectPropertiesData {
|
||||
extensionProperties: Array<ExtensionProperty>;
|
||||
useDeprecatedZeroAsDefaultZOrder?: boolean;
|
||||
projectUuid?: string;
|
||||
sceneResourcesPreloading?: 'at-startup' | 'never';
|
||||
sceneResourcesUnloading?: 'at-scene-exit' | 'never';
|
||||
}
|
||||
|
||||
declare interface ExtensionProperty {
|
||||
@@ -437,4 +441,5 @@ declare type ResourceKind =
|
||||
| 'bitmapFont'
|
||||
| 'model3D'
|
||||
| 'atlas'
|
||||
| 'spine';
|
||||
| 'spine'
|
||||
| 'fake-resource-kind-for-testing-only';
|
||||
|
After Width: | Height: | Size: 277 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 654 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 926 B |
@@ -16,6 +16,7 @@ describe('gdjs.CustomRuntimeObject', function () {
|
||||
name: 'MyCustomObject',
|
||||
type: 'MyExtension::MyEventsBasedObject',
|
||||
variant: '',
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
variables: [],
|
||||
behaviors: [],
|
||||
effects: [],
|
||||
|
@@ -1,24 +1,47 @@
|
||||
// @ts-check
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
// implements gdjs.ResourceManager
|
||||
/**
|
||||
* @implements {gdjs.ResourceManager}
|
||||
*/
|
||||
gdjs.MockedResourceManager = class MockedResourceManager {
|
||||
|
||||
loadResourcePromises = new Map();
|
||||
loadResourceCallbacks = new Map();
|
||||
disposedResources = new Set();
|
||||
loadedResources = new Set();
|
||||
waitingForProcessing = new Set();
|
||||
readyResources = new Set();
|
||||
|
||||
loadResource(resourceName) {
|
||||
const that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
that.loadResourceCallbacks.set(resourceName, resolve);
|
||||
if (
|
||||
this.loadedResources.has(resourceName) ||
|
||||
this.waitingForProcessing.has(resourceName)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const existingPromise = this.loadResourcePromises.get(resourceName);
|
||||
if (existingPromise) {
|
||||
return existingPromise;
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve) => {
|
||||
this.loadResourceCallbacks.set(resourceName, resolve);
|
||||
});
|
||||
this.loadResourcePromises.set(resourceName, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
async processResource(resourceName) {}
|
||||
async processResource(resourceName) {
|
||||
// Mark resource as fully processed
|
||||
this.readyResources.add(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} resourceName
|
||||
* @param {string} resourceName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isResourceDownloadPending(resourceName) {
|
||||
@@ -26,15 +49,64 @@ gdjs.MockedResourceManager = class MockedResourceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} resourceName
|
||||
* @param {string} resourceName
|
||||
*/
|
||||
markPendingResourcesAsLoaded(resourceName) {
|
||||
const loadResourceCallback = this.loadResourceCallbacks.get(resourceName);
|
||||
loadResourceCallback();
|
||||
this.loadResourceCallbacks.delete(resourceName);
|
||||
if (loadResourceCallback) {
|
||||
this.loadedResources.add(resourceName);
|
||||
loadResourceCallback();
|
||||
this.loadResourceCallbacks.delete(resourceName);
|
||||
this.loadResourcePromises.delete(resourceName);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Resource ${resourceName} was not being loaded, so cannot be marked as loaded.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a resource is loaded (but maybe not yet processed)
|
||||
*/
|
||||
isResourceLoaded(resourceName) {
|
||||
return this.loadedResources.has(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a resource has been disposed
|
||||
*/
|
||||
isResourceDisposed(resourceName) {
|
||||
return this.disposedResources.has(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose all resources
|
||||
*/
|
||||
dispose() {
|
||||
for (const resourceName of this.loadedResources) {
|
||||
this.disposedResources.add(resourceName);
|
||||
}
|
||||
this.loadedResources.clear();
|
||||
this.loadResourceCallbacks.clear();
|
||||
this.loadResourcePromises.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose specific resources
|
||||
*/
|
||||
unloadResourcesList(resourcesList) {
|
||||
for (const resource of resourcesList) {
|
||||
this.disposedResources.add(resource.name);
|
||||
this.loadedResources.delete(resource.name);
|
||||
this.loadResourceCallbacks.delete(resource.name);
|
||||
this.loadResourcePromises.delete(resource.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ResourceKind[]}
|
||||
*/
|
||||
getResourceKinds() {
|
||||
return ['fake-heavy-resource'];
|
||||
return ['fake-resource-kind-for-testing-only'];
|
||||
}
|
||||
}
|
347
GDJS/tests/tests/ResourceLoader.js
Normal file
@@ -0,0 +1,347 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Tests for gdjs.ResourceLoader.
|
||||
*/
|
||||
describe('gdjs.ResourceLoader', () => {
|
||||
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
||||
|
||||
/** @returns {LayoutData} */
|
||||
const createSceneData = (name, usedResources) => {
|
||||
return {
|
||||
r: 0,
|
||||
v: 0,
|
||||
b: 0,
|
||||
mangledName: name,
|
||||
name,
|
||||
objects: [],
|
||||
layers: [],
|
||||
instances: [],
|
||||
behaviorsSharedData: [],
|
||||
stopSoundsOnStartup: false,
|
||||
title: '',
|
||||
variables: [],
|
||||
usedResources,
|
||||
};
|
||||
};
|
||||
|
||||
/** @type {{layouts?: LayoutData[], resources?: ResourcesData}} */
|
||||
const gameSettingsWithThreeScenes = {
|
||||
layouts: [
|
||||
createSceneData('Scene1', [
|
||||
{ name: 'scene1-resource1.png' },
|
||||
{ name: 'scene1-resource2.png' },
|
||||
]),
|
||||
createSceneData('Scene2', [
|
||||
{ name: 'scene2-resource1.png' },
|
||||
{ name: 'shared-resource.png' },
|
||||
]),
|
||||
createSceneData('Scene3', [
|
||||
{ name: 'scene3-resource1.png' },
|
||||
{ name: 'shared-resource.png' },
|
||||
]),
|
||||
],
|
||||
resources: {
|
||||
resources: [
|
||||
{
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'scene1-resource1.png',
|
||||
metadata: '',
|
||||
file: 'scene1-resource1.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'scene1-resource2.png',
|
||||
metadata: '',
|
||||
file: 'scene1-resource2.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'scene2-resource1.png',
|
||||
metadata: '',
|
||||
file: 'scene2-resource1.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'scene3-resource1.png',
|
||||
metadata: '',
|
||||
file: 'scene3-resource1.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'shared-resource.png',
|
||||
metadata: '',
|
||||
file: 'shared-resource.png',
|
||||
userAdded: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
it('should load first scene resources, then others in background', async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithThreeScenes);
|
||||
const resourceLoader = runtimeGame.getResourceLoader();
|
||||
resourceLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
|
||||
// Initially, no scene assets should be loaded
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene1')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene2')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene3')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene1')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene2')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene3')).to.be(false);
|
||||
|
||||
// Start loading first scene and background loading
|
||||
runtimeGame.loadFirstAssetsAndStartBackgroundLoading('Scene1');
|
||||
|
||||
// Scene1 resources should be pending download
|
||||
expect(
|
||||
mockedResourceManager.isResourceDownloadPending('scene1-resource1.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDownloadPending('scene1-resource2.png')
|
||||
).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene1')).to.be(false);
|
||||
|
||||
// Mark Scene1 resources as loaded
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource1.png');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource2.png');
|
||||
await delay(10);
|
||||
|
||||
// Scene1 should now be ready
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene1')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene1')).to.be(true);
|
||||
|
||||
// Background loading should have started for Scene2
|
||||
await delay(20); // Wait for background loading to start
|
||||
expect(
|
||||
mockedResourceManager.isResourceDownloadPending('scene2-resource1.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDownloadPending('shared-resource.png')
|
||||
).to.be(true);
|
||||
|
||||
// Mark Scene2 resources as loaded
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene2-resource1.png');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('shared-resource.png');
|
||||
await delay(10);
|
||||
|
||||
// Scene2 should now be loaded
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene2')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene2')).to.be(false);
|
||||
|
||||
// Scene 2 resources can be processed so the scene is fully ready:
|
||||
resourceLoader.loadAndProcessSceneResources('Scene2');
|
||||
await delay(10);
|
||||
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene2')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene2')).to.be(true);
|
||||
|
||||
// Scene 3 resources are not loaded nor processed yet:
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene3')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene3')).to.be(false);
|
||||
|
||||
// Background loading should have started for Scene3
|
||||
await delay(20); // Wait for background loading to start
|
||||
expect(
|
||||
mockedResourceManager.isResourceDownloadPending('scene3-resource1.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDownloadPending('shared-resource.png')
|
||||
// shared-resource.png should already be loaded, so not pending:
|
||||
).to.be(false);
|
||||
|
||||
// Mark Scene3 resources as loaded
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene3-resource1.png');
|
||||
await delay(10);
|
||||
|
||||
// Scene3 should now be loaded
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene3')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene3')).to.be(false);
|
||||
|
||||
// Scene3 resources can be processed so the scene is fully ready:
|
||||
resourceLoader.loadAndProcessSceneResources('Scene3');
|
||||
await delay(10);
|
||||
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene3')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene3')).to.be(true);
|
||||
});
|
||||
|
||||
it('should unload only resources unique to the unloaded scene', async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithThreeScenes);
|
||||
const resourceLoader = runtimeGame.getResourceLoader();
|
||||
resourceLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
|
||||
// Load all resources for all scenes
|
||||
resourceLoader.loadGlobalAndFirstSceneResources('Scene1', () => {});
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource1.png');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource2.png');
|
||||
|
||||
resourceLoader.loadAndProcessSceneResources('Scene2');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene2-resource1.png');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('shared-resource.png');
|
||||
await delay(10);
|
||||
|
||||
resourceLoader.loadAndProcessSceneResources('Scene3');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene3-resource1.png');
|
||||
await delay(10);
|
||||
|
||||
// Verify all resources are loaded
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene1')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene2')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene3')).to.be(true);
|
||||
|
||||
// Verify no resources are disposed initially
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene1-resource1.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene1-resource2.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene2-resource1.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene3-resource1.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('shared-resource.png')
|
||||
).to.be(false);
|
||||
|
||||
// Simulate Scene2 and Scene3 being loaded/active by marking Scene1 as unloaded
|
||||
// while Scene2 will be the new scene
|
||||
resourceLoader.unloadSceneResources({
|
||||
unloadedSceneName: 'Scene1',
|
||||
newSceneName: 'Scene2',
|
||||
});
|
||||
|
||||
// Only Scene1-specific resources should be disposed
|
||||
// shared-resource.png should NOT be disposed because it's used in Scene2 and Scene3
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene1-resource1.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene1-resource2.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene2-resource1.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene3-resource1.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('shared-resource.png')
|
||||
).to.be(false);
|
||||
|
||||
// Scene1 should be marked as not loaded
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene1')).to.be(false);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene1')).to.be(false);
|
||||
|
||||
// Other scenes should still be loaded
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene2')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene2')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsLoaded('Scene3')).to.be(true);
|
||||
expect(resourceLoader.areSceneAssetsReady('Scene3')).to.be(true);
|
||||
});
|
||||
|
||||
it('should unload shared resources only when no other scene uses them', async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithThreeScenes);
|
||||
const resourceLoader = runtimeGame.getResourceLoader();
|
||||
resourceLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
|
||||
// Load all resources for all scenes
|
||||
resourceLoader.loadAllResources(() => {});
|
||||
await delay(10);
|
||||
|
||||
// First, unload Scene2 (which shares resources with Scene3)
|
||||
resourceLoader.unloadSceneResources({
|
||||
unloadedSceneName: 'Scene2',
|
||||
newSceneName: 'Scene3',
|
||||
});
|
||||
|
||||
// Only Scene2-specific resources should be disposed
|
||||
// shared-resource.png should NOT be disposed because it's still used in Scene3
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene2-resource1.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('shared-resource.png')
|
||||
).to.be(false);
|
||||
|
||||
// Now unload Scene3 (which also uses shared-resource.png)
|
||||
resourceLoader.unloadSceneResources({
|
||||
unloadedSceneName: 'Scene3',
|
||||
newSceneName: 'Scene1',
|
||||
});
|
||||
|
||||
// Now shared-resource.png should be disposed because no loaded scene uses it
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene3-resource1.png')
|
||||
).to.be(true);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('shared-resource.png')
|
||||
).to.be(true);
|
||||
|
||||
// Scene1 resources should still be loaded
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene1-resource1.png')
|
||||
).to.be(false);
|
||||
expect(
|
||||
mockedResourceManager.isResourceDisposed('scene1-resource2.png')
|
||||
).to.be(false);
|
||||
});
|
||||
|
||||
it('should handle background scene loading progress correctly', async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithThreeScenes);
|
||||
const resourceLoader = runtimeGame.getResourceLoader();
|
||||
resourceLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
|
||||
// Initially progress should be 0
|
||||
expect(resourceLoader.getSceneLoadingProgress('Scene1')).to.be(0);
|
||||
expect(resourceLoader.getSceneLoadingProgress('Scene2')).to.be(0);
|
||||
|
||||
// Start loading first scene
|
||||
runtimeGame.loadFirstAssetsAndStartBackgroundLoading('Scene1');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource1.png');
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene1-resource2.png');
|
||||
await delay(10);
|
||||
|
||||
// Progress should still be 0 until resources start loading
|
||||
expect(resourceLoader.getSceneLoadingProgress('Scene2')).to.be(0);
|
||||
|
||||
// Mark first resource as loaded
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('scene2-resource1.png');
|
||||
await delay(10);
|
||||
|
||||
// Progress should be partial (1 out of 2 resources)
|
||||
console.log(resourceLoader.getSceneLoadingProgress('Scene2'));
|
||||
expect(resourceLoader.getSceneLoadingProgress('Scene2')).to.be(0.5);
|
||||
|
||||
// Mark second resource as loaded
|
||||
mockedResourceManager.markPendingResourcesAsLoaded('shared-resource.png');
|
||||
await delay(10);
|
||||
|
||||
// Progress should be complete (1.0)
|
||||
expect(resourceLoader.getSceneLoadingProgress('Scene2')).to.be(1);
|
||||
});
|
||||
});
|
@@ -100,6 +100,7 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
|
||||
effects: [],
|
||||
content: {},
|
||||
childrenContent: {},
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
};
|
||||
|
||||
/** @type {LayerData} */
|
||||
|
@@ -6,7 +6,7 @@
|
||||
describe('gdjs.SceneStack', () => {
|
||||
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
||||
|
||||
const createSene = (name, usedResources) => {
|
||||
const createSceneData = (name, usedResources) => {
|
||||
return {
|
||||
r: 0,
|
||||
v: 0,
|
||||
@@ -24,10 +24,13 @@ describe('gdjs.SceneStack', () => {
|
||||
};
|
||||
};
|
||||
|
||||
/** @type {{layouts?: LayoutData[], resources?: ResourcesData}} */
|
||||
const gameSettings = {
|
||||
layouts: [
|
||||
createSene('Scene 1', []),
|
||||
createSene('Scene 2', [{ name: 'base/tests-utils/assets/64x64.jpg' }]),
|
||||
createSceneData('Scene 1', []),
|
||||
createSceneData('Scene 2', [
|
||||
{ name: 'base/tests-utils/assets/64x64.jpg' },
|
||||
]),
|
||||
],
|
||||
resources: {
|
||||
resources: [
|
||||
@@ -43,11 +46,11 @@ describe('gdjs.SceneStack', () => {
|
||||
};
|
||||
|
||||
it('should support pushing, replacing and popping scenes', async () => {
|
||||
//@ts-ignore
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettings);
|
||||
let sceneStack = runtimeGame._sceneStack;
|
||||
// Async asset loading is not tested here.
|
||||
await runtimeGame._resourcesLoader.loadAllResources(() => {});
|
||||
const resourcesLoader = runtimeGame.getResourceLoader();
|
||||
await resourcesLoader.loadAllResources(() => {});
|
||||
|
||||
// Set up some scene callbacks.
|
||||
/** @type gdjs.RuntimeScene | null */
|
||||
@@ -146,38 +149,39 @@ describe('gdjs.SceneStack', () => {
|
||||
gdjs._unregisterCallback(onRuntimeSceneResumed);
|
||||
});
|
||||
|
||||
/** @type {{layouts?: LayoutData[], resources?: ResourcesData}} */
|
||||
const gameSettingsWithHeavyResource = {
|
||||
layouts: [
|
||||
createSene('Scene 1', [{ name: 'fake-heavy-resource1.png' }]),
|
||||
createSene('Scene 2', [{ name: 'fake-heavy-resource2.png' }]),
|
||||
createSene('Scene 3', [{ name: 'fake-heavy-resource3.png' }]),
|
||||
createSene('Scene 4', [{ name: 'fake-heavy-resource4.png' }]),
|
||||
createSceneData('Scene 1', [{ name: 'fake-heavy-resource1.png' }]),
|
||||
createSceneData('Scene 2', [{ name: 'fake-heavy-resource2.png' }]),
|
||||
createSceneData('Scene 3', [{ name: 'fake-heavy-resource3.png' }]),
|
||||
createSceneData('Scene 4', [{ name: 'fake-heavy-resource4.png' }]),
|
||||
],
|
||||
resources: {
|
||||
resources: [
|
||||
{
|
||||
kind: 'fake-heavy-resource',
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'fake-heavy-resource1.png',
|
||||
metadata: '',
|
||||
file: 'fake-heavy-resource1.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-heavy-resource',
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'fake-heavy-resource2.png',
|
||||
metadata: '',
|
||||
file: 'fake-heavy-resource2.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-heavy-resource',
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'fake-heavy-resource3.png',
|
||||
metadata: '',
|
||||
file: 'fake-heavy-resource3.png',
|
||||
userAdded: true,
|
||||
},
|
||||
{
|
||||
kind: 'fake-heavy-resource',
|
||||
kind: 'fake-resource-kind-for-testing-only',
|
||||
name: 'fake-heavy-resource4.png',
|
||||
metadata: '',
|
||||
file: 'fake-heavy-resource4.png',
|
||||
@@ -189,11 +193,11 @@ describe('gdjs.SceneStack', () => {
|
||||
|
||||
it('can start a layout when all its assets are already downloaded', async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
//@ts-ignore
|
||||
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithHeavyResource);
|
||||
runtimeGame._resourcesLoader._resourceManagersMap.set(
|
||||
//@ts-ignore
|
||||
'fake-heavy-resource',
|
||||
const resourcesLoader = runtimeGame.getResourceLoader();
|
||||
resourcesLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
let sceneStack = runtimeGame._sceneStack;
|
||||
@@ -299,11 +303,11 @@ describe('gdjs.SceneStack', () => {
|
||||
|
||||
it('can start a layout while assets loading and wait them to finish', async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
//@ts-ignore
|
||||
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithHeavyResource);
|
||||
runtimeGame._resourcesLoader._resourceManagersMap.set(
|
||||
//@ts-ignore
|
||||
'fake-heavy-resource',
|
||||
const resourcesLoader = runtimeGame.getResourceLoader();
|
||||
resourcesLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
let sceneStack = runtimeGame._sceneStack;
|
||||
@@ -404,11 +408,10 @@ describe('gdjs.SceneStack', () => {
|
||||
|
||||
it("can start a layout which assets loading didn't stated yet and wait them to finish", async () => {
|
||||
const mockedResourceManager = new gdjs.MockedResourceManager();
|
||||
//@ts-ignore
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithHeavyResource);
|
||||
runtimeGame._resourcesLoader._resourceManagersMap.set(
|
||||
//@ts-ignore
|
||||
'fake-heavy-resource',
|
||||
const resourcesLoader = runtimeGame.getResourceLoader();
|
||||
resourcesLoader.injectMockResourceManagerForTesting(
|
||||
'fake-resource-kind-for-testing-only',
|
||||
mockedResourceManager
|
||||
);
|
||||
let sceneStack = runtimeGame._sceneStack;
|
||||
|
@@ -666,6 +666,11 @@ interface Project {
|
||||
[Ref] ObjectsContainer GetObjects();
|
||||
[Ref] ResourcesManager GetResourcesManager();
|
||||
|
||||
void SetSceneResourcesPreloading([Const] DOMString resourcesPreloading);
|
||||
[Const, Ref] DOMString GetSceneResourcesPreloading();
|
||||
void SetSceneResourcesUnloading([Const] DOMString resourcesUnloading);
|
||||
[Const, Ref] DOMString GetSceneResourcesUnloading();
|
||||
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
|
||||
@@ -1002,6 +1007,11 @@ interface Layout {
|
||||
|
||||
void SetStopSoundsOnStartup(boolean enable);
|
||||
boolean StopSoundsOnStartup();
|
||||
|
||||
void SetResourcesPreloading([Const] DOMString resourcesPreloading);
|
||||
[Const, Ref] DOMString GetResourcesPreloading();
|
||||
void SetResourcesUnloading([Const] DOMString resourcesUnloading);
|
||||
[Const, Ref] DOMString GetResourcesUnloading();
|
||||
};
|
||||
|
||||
interface ExternalEvents {
|
||||
@@ -1136,6 +1146,17 @@ interface LayersContainer {
|
||||
void UnserializeLayersFrom([Const, Ref] SerializerElement element);
|
||||
};
|
||||
|
||||
interface PropertyDescriptorChoice {
|
||||
void PropertyDescriptorChoice([Const] DOMString value, [Const] DOMString label);
|
||||
[Const, Ref] DOMString GetValue();
|
||||
[Const, Ref] DOMString GetLabel();
|
||||
};
|
||||
|
||||
interface VectorPropertyDescriptorChoice {
|
||||
unsigned long size();
|
||||
[Const, Ref] PropertyDescriptorChoice at(unsigned long index);
|
||||
};
|
||||
|
||||
interface PropertyDescriptor {
|
||||
void PropertyDescriptor([Const] DOMString propValue);
|
||||
|
||||
@@ -1149,6 +1170,8 @@ interface PropertyDescriptor {
|
||||
[Const, Ref] DOMString GetDescription();
|
||||
[Ref] PropertyDescriptor SetGroup([Const] DOMString label);
|
||||
[Const, Ref] DOMString GetGroup();
|
||||
[Ref] PropertyDescriptor AddChoice([Const] DOMString value, [Const] DOMString label);
|
||||
[Const, Ref] VectorPropertyDescriptorChoice GetChoices();
|
||||
[Ref] PropertyDescriptor AddExtraInfo([Const] DOMString type);
|
||||
[Ref] PropertyDescriptor SetExtraInfo([Const, Ref] VectorString info);
|
||||
[Ref] VectorString GetExtraInfo();
|
||||
|
@@ -481,6 +481,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice;
|
||||
|
||||
// Customize some functions implementation thanks to WRAPPED_* macros
|
||||
// The original names will be reconstructed in the js file (see postjs.js)
|
||||
|
21
GDevelop.js/types.d.ts
vendored
@@ -619,6 +619,10 @@ export class Project extends EmscriptenObject {
|
||||
getVariables(): VariablesContainer;
|
||||
getObjects(): ObjectsContainer;
|
||||
getResourcesManager(): ResourcesManager;
|
||||
setSceneResourcesPreloading(resourcesPreloading: string): void;
|
||||
getSceneResourcesPreloading(): string;
|
||||
setSceneResourcesUnloading(resourcesUnloading: string): void;
|
||||
getSceneResourcesUnloading(): string;
|
||||
serializeTo(element: SerializerElement): void;
|
||||
unserializeFrom(element: SerializerElement): void;
|
||||
getWholeProjectDiagnosticReport(): WholeProjectDiagnosticReport;
|
||||
@@ -823,6 +827,10 @@ export class Layout extends EmscriptenObject {
|
||||
unserializeFrom(project: Project, element: SerializerElement): void;
|
||||
setStopSoundsOnStartup(enable: boolean): void;
|
||||
stopSoundsOnStartup(): boolean;
|
||||
setResourcesPreloading(resourcesPreloading: string): void;
|
||||
getResourcesPreloading(): string;
|
||||
setResourcesUnloading(resourcesUnloading: string): void;
|
||||
getResourcesUnloading(): string;
|
||||
}
|
||||
|
||||
export class ExternalEvents extends EmscriptenObject {
|
||||
@@ -936,6 +944,17 @@ export class LayersContainer extends EmscriptenObject {
|
||||
unserializeLayersFrom(element: SerializerElement): void;
|
||||
}
|
||||
|
||||
export class PropertyDescriptorChoice extends EmscriptenObject {
|
||||
constructor(value: string, label: string);
|
||||
getValue(): string;
|
||||
getLabel(): string;
|
||||
}
|
||||
|
||||
export class VectorPropertyDescriptorChoice extends EmscriptenObject {
|
||||
size(): number;
|
||||
at(index: number): PropertyDescriptorChoice;
|
||||
}
|
||||
|
||||
export class PropertyDescriptor extends EmscriptenObject {
|
||||
constructor(propValue: string);
|
||||
setValue(value: string): PropertyDescriptor;
|
||||
@@ -948,6 +967,8 @@ export class PropertyDescriptor extends EmscriptenObject {
|
||||
getDescription(): string;
|
||||
setGroup(label: string): PropertyDescriptor;
|
||||
getGroup(): string;
|
||||
addChoice(value: string, label: string): PropertyDescriptor;
|
||||
getChoices(): VectorPropertyDescriptorChoice;
|
||||
addExtraInfo(type: string): PropertyDescriptor;
|
||||
setExtraInfo(info: VectorString): PropertyDescriptor;
|
||||
getExtraInfo(): VectorString;
|
||||
|
@@ -32,6 +32,10 @@ declare class gdLayout {
|
||||
unserializeFrom(project: gdProject, element: gdSerializerElement): void;
|
||||
setStopSoundsOnStartup(enable: boolean): void;
|
||||
stopSoundsOnStartup(): boolean;
|
||||
setResourcesPreloading(resourcesPreloading: string): void;
|
||||
getResourcesPreloading(): string;
|
||||
setResourcesUnloading(resourcesUnloading: string): void;
|
||||
getResourcesUnloading(): string;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -107,6 +107,10 @@ declare class gdProject {
|
||||
getVariables(): gdVariablesContainer;
|
||||
getObjects(): gdObjectsContainer;
|
||||
getResourcesManager(): gdResourcesManager;
|
||||
setSceneResourcesPreloading(resourcesPreloading: string): void;
|
||||
getSceneResourcesPreloading(): string;
|
||||
setSceneResourcesUnloading(resourcesUnloading: string): void;
|
||||
getSceneResourcesUnloading(): string;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(element: gdSerializerElement): void;
|
||||
getWholeProjectDiagnosticReport(): gdWholeProjectDiagnosticReport;
|
||||
|
@@ -11,6 +11,8 @@ declare class gdPropertyDescriptor {
|
||||
getDescription(): string;
|
||||
setGroup(label: string): gdPropertyDescriptor;
|
||||
getGroup(): string;
|
||||
addChoice(value: string, label: string): gdPropertyDescriptor;
|
||||
getChoices(): gdVectorPropertyDescriptorChoice;
|
||||
addExtraInfo(type: string): gdPropertyDescriptor;
|
||||
setExtraInfo(info: gdVectorString): gdPropertyDescriptor;
|
||||
getExtraInfo(): gdVectorString;
|
||||
|
8
GDevelop.js/types/gdpropertydescriptorchoice.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdPropertyDescriptorChoice {
|
||||
constructor(value: string, label: string): void;
|
||||
getValue(): string;
|
||||
getLabel(): string;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
7
GDevelop.js/types/gdvectorpropertydescriptorchoice.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdVectorPropertyDescriptorChoice {
|
||||
size(): number;
|
||||
at(index: number): gdPropertyDescriptorChoice;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -105,6 +105,8 @@ declare class libGDevelop {
|
||||
EffectsContainer: Class<gdEffectsContainer>;
|
||||
Layer: Class<gdLayer>;
|
||||
LayersContainer: Class<gdLayersContainer>;
|
||||
PropertyDescriptorChoice: Class<gdPropertyDescriptorChoice>;
|
||||
VectorPropertyDescriptorChoice: Class<gdVectorPropertyDescriptorChoice>;
|
||||
PropertyDescriptor: Class<gdPropertyDescriptor>;
|
||||
MeasurementUnit: Class<gdMeasurementUnit>;
|
||||
MeasurementBaseUnit: Class<gdMeasurementBaseUnit>;
|
||||
|
@@ -30,6 +30,21 @@
|
||||
justify-content: center;
|
||||
|
||||
animation: new-chat-appear 0.5s;
|
||||
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.aiRequestChatContainer {
|
||||
display: flex;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
flex: 1 1 0%;
|
||||
min-height: 0px;
|
||||
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.thinkingText {
|
||||
|
@@ -37,6 +37,7 @@ import Hammer from '../../UI/CustomSvgIcons/Hammer';
|
||||
import { ChatMessages } from './ChatMessages';
|
||||
import Send from '../../UI/CustomSvgIcons/Send';
|
||||
import { FeedbackBanner } from './FeedbackBanner';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const TOO_MANY_USER_MESSAGES_WARNING_COUNT = 5;
|
||||
const TOO_MANY_USER_MESSAGES_ERROR_COUNT = 10;
|
||||
@@ -330,6 +331,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
const subscriptionBanner =
|
||||
quota && quota.limitReached && increaseQuotaOffering !== 'none' ? (
|
||||
<GetSubscriptionCard
|
||||
placementId="ai-requests"
|
||||
subscriptionDialogOpeningReason={
|
||||
increaseQuotaOffering === 'subscribe'
|
||||
? 'AI requests (subscribe)'
|
||||
@@ -386,7 +388,13 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
|
||||
if (!aiRequest) {
|
||||
return (
|
||||
<div className={classes.newChatContainer}>
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.newChatContainer]: true,
|
||||
// Move the entire screen up when the soft keyboard is open:
|
||||
'avoid-soft-keyboard': true,
|
||||
})}
|
||||
>
|
||||
<ColumnStackLayout justifyContent="center" expand>
|
||||
<Line noMargin justifyContent="center">
|
||||
<RobotIcon rotating size={40} />
|
||||
@@ -620,11 +628,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<Column
|
||||
expand
|
||||
alignItems="stretch"
|
||||
justifyContent="stretch"
|
||||
useFullHeight
|
||||
<div
|
||||
className={classNames({
|
||||
[classes.aiRequestChatContainer]: true,
|
||||
})}
|
||||
>
|
||||
<ScrollView ref={scrollViewRef} style={styles.chatScrollView}>
|
||||
<ChatMessages
|
||||
@@ -663,6 +670,10 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
userMessage: userRequestTextPerAiRequestId[aiRequestId] || '',
|
||||
});
|
||||
}}
|
||||
className={classNames({
|
||||
// Move the form up when the soft keyboard is open:
|
||||
'avoid-soft-keyboard': true,
|
||||
})}
|
||||
>
|
||||
<ColumnStackLayout
|
||||
justifyContent="stretch"
|
||||
@@ -798,7 +809,7 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</form>
|
||||
</Column>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@@ -27,13 +27,20 @@ import ThreeDotsMenu from '../../UI/CustomSvgIcons/ThreeDotsMenu';
|
||||
import useAlertDialog from '../../UI/Alert/useAlertDialog';
|
||||
import ExtensionInstallDialog from '../ExtensionStore/ExtensionInstallDialog';
|
||||
import { getIDEVersion } from '../../Version';
|
||||
import InAppTutorialContext from '../../InAppTutorial/InAppTutorialContext';
|
||||
|
||||
export const useExtensionUpdateAlertDialog = () => {
|
||||
const { showConfirmation } = useAlertDialog();
|
||||
const { currentlyRunningInAppTutorial } = React.useContext(
|
||||
InAppTutorialContext
|
||||
);
|
||||
return async (
|
||||
project: gdProject,
|
||||
behaviorShortHeader: BehaviorShortHeader
|
||||
): Promise<boolean> => {
|
||||
if (currentlyRunningInAppTutorial) {
|
||||
return false;
|
||||
}
|
||||
return await showConfirmation({
|
||||
title: t`Extension update`,
|
||||
message:
|
||||
|
@@ -49,11 +49,15 @@ import ErrorBoundary from '../UI/ErrorBoundary';
|
||||
import type { ObjectFolderOrObjectWithContext } from '../ObjectsList/EnumerateObjectFolderOrObject';
|
||||
import LoaderModal from '../UI/LoaderModal';
|
||||
import { AssetStoreNavigatorContext } from './AssetStoreNavigator';
|
||||
import InAppTutorialContext from '../InAppTutorial/InAppTutorialContext';
|
||||
|
||||
const isDev = Window.isDev();
|
||||
|
||||
export const useExtensionUpdateAlertDialog = () => {
|
||||
const { showConfirmation, showDeleteConfirmation } = useAlertDialog();
|
||||
const { currentlyRunningInAppTutorial } = React.useContext(
|
||||
InAppTutorialContext
|
||||
);
|
||||
return async ({
|
||||
project,
|
||||
outOfDateExtensionShortHeaders,
|
||||
@@ -61,6 +65,9 @@ export const useExtensionUpdateAlertDialog = () => {
|
||||
project: gdProject,
|
||||
outOfDateExtensionShortHeaders: Array<ExtensionShortHeader>,
|
||||
|}): Promise<string> => {
|
||||
if (currentlyRunningInAppTutorial) {
|
||||
return 'skip';
|
||||
}
|
||||
const breakingChanges = new Map<
|
||||
ExtensionShortHeader,
|
||||
Array<ExtensionChange>
|
||||
|
@@ -656,6 +656,7 @@ const PrivateAssetPackInformationPage = ({
|
||||
analyticsMetadata: {
|
||||
reason: 'Claim asset pack',
|
||||
recommendedPlanId: 'gdevelop_gold',
|
||||
placementId: 'claim-asset-pack',
|
||||
},
|
||||
filter: 'individual',
|
||||
})
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import { mapFor, mapVector } from '../Utils/MapFor';
|
||||
import { type Schema, type Instance } from '.';
|
||||
import { type ResourceKind } from '../ResourcesList/ResourceSource';
|
||||
import { type Field } from '.';
|
||||
@@ -112,14 +112,19 @@ const createField = (
|
||||
};
|
||||
} else if (valueType === 'choice') {
|
||||
// Choice is a "string" (with a selector for the user in the UI)
|
||||
const choices = property
|
||||
const choices = mapVector(property.getChoices(), choice => ({
|
||||
value: choice.getValue(),
|
||||
label: choice.getLabel(),
|
||||
}));
|
||||
const deprecatedChoices = property
|
||||
.getExtraInfo()
|
||||
.toJSArray()
|
||||
.map(value => ({ value, label: value }));
|
||||
|
||||
return {
|
||||
name,
|
||||
valueType: 'string',
|
||||
getChoices: () => choices,
|
||||
getChoices: () => [...choices, ...deprecatedChoices],
|
||||
getValue: (instance: Instance): string => {
|
||||
return getProperties(instance)
|
||||
.get(name)
|
||||
|
@@ -52,6 +52,10 @@ export type ValueFieldCommonProperties = {|
|
||||
onEditButtonClick?: () => void,
|
||||
getValueFromDisplayedValue?: string => string,
|
||||
getDisplayedValueFromValue?: string => string,
|
||||
|
||||
// Only used for effects for now:
|
||||
advanced?: boolean,
|
||||
defaultValue?: string | number | boolean,
|
||||
|};
|
||||
|
||||
// "Primitive" value fields are "simple" fields.
|
||||
|
@@ -174,6 +174,7 @@ const LockedCourseChapterPreview = React.forwardRef<Props, HTMLDivElement>(
|
||||
analyticsMetadata: {
|
||||
reason: 'Unlock course chapter',
|
||||
recommendedPlanId: 'gdevelop_silver',
|
||||
placementId: 'unlock-course-chapter',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { getInstancesInLayoutForLayer } from '../Utils/Layout';
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import { mapFor, mapVector } from '../Utils/MapFor';
|
||||
import { SafeExtractor } from '../Utils/SafeExtractor';
|
||||
import { serializeToJSObject } from '../Utils/Serializer';
|
||||
import { type AiGeneratedEvent } from '../Utils/GDevelopServices/Generation';
|
||||
@@ -251,7 +251,10 @@ const makeShortTextForNamedProperty = (
|
||||
|
||||
const choices =
|
||||
type.toLowerCase() === 'choice'
|
||||
? property.getExtraInfo().toJSArray()
|
||||
? [
|
||||
...mapVector(property.getChoices(), choice => choice.getValue()),
|
||||
...property.getExtraInfo().toJSArray(),
|
||||
]
|
||||
: null;
|
||||
const information = [
|
||||
type,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import { mapFor } from '../Utils/MapFor';
|
||||
import { mapFor, mapVector } from '../Utils/MapFor';
|
||||
import { type Schema } from '../CompactPropertiesEditor';
|
||||
import { type ResourceKind } from '../ResourcesList/ResourceSource';
|
||||
import flatten from 'lodash/flatten';
|
||||
@@ -49,6 +49,8 @@ export const enumerateEffectsMetadata = (
|
||||
const getLabel = () => propertyLabel;
|
||||
const getDescription = () => propertyDescription;
|
||||
const getExtraDescription = () => parameterName;
|
||||
const advanced = property.isAdvanced();
|
||||
const defaultValue = property.getValue();
|
||||
|
||||
if (valueType === 'number') {
|
||||
return {
|
||||
@@ -61,6 +63,8 @@ export const enumerateEffectsMetadata = (
|
||||
getLabel,
|
||||
getDescription,
|
||||
getExtraDescription,
|
||||
advanced,
|
||||
defaultValue: parseFloat(defaultValue) || 0,
|
||||
};
|
||||
} else if (valueType === 'boolean') {
|
||||
return {
|
||||
@@ -73,6 +77,8 @@ export const enumerateEffectsMetadata = (
|
||||
getLabel,
|
||||
getDescription,
|
||||
getExtraDescription,
|
||||
advanced,
|
||||
defaultValue: defaultValue === 'true',
|
||||
};
|
||||
} else if (valueType === 'resource') {
|
||||
// Resource is a "string" (with a selector in the UI)
|
||||
@@ -90,6 +96,8 @@ export const enumerateEffectsMetadata = (
|
||||
getLabel,
|
||||
getDescription,
|
||||
getExtraDescription,
|
||||
advanced,
|
||||
defaultValue,
|
||||
};
|
||||
} else if (valueType === 'color') {
|
||||
return {
|
||||
@@ -102,16 +110,22 @@ export const enumerateEffectsMetadata = (
|
||||
getLabel,
|
||||
getDescription,
|
||||
getExtraDescription,
|
||||
advanced,
|
||||
defaultValue,
|
||||
};
|
||||
} else if (valueType === 'choice') {
|
||||
const choices = property
|
||||
const choices = mapVector(property.getChoices(), choice => ({
|
||||
value: choice.getValue(),
|
||||
label: choice.getLabel(),
|
||||
}));
|
||||
const deprecatedChoices = property
|
||||
.getExtraInfo()
|
||||
.toJSArray()
|
||||
.map(value => ({ value, label: value }));
|
||||
return {
|
||||
name: parameterName,
|
||||
valueType: 'string',
|
||||
getChoices: () => choices,
|
||||
getChoices: () => [...choices, ...deprecatedChoices],
|
||||
getValue: (effect: gdEffect) =>
|
||||
effect.getStringParameter(parameterName),
|
||||
setValue: (effect: gdEffect, newValue: string) =>
|
||||
@@ -119,6 +133,8 @@ export const enumerateEffectsMetadata = (
|
||||
getLabel,
|
||||
getDescription,
|
||||
getExtraDescription,
|
||||
advanced,
|
||||
defaultValue,
|
||||
};
|
||||
} else {
|
||||
console.error(
|
||||
|
@@ -53,6 +53,8 @@ import PasteIcon from '../UI/CustomSvgIcons/Clipboard';
|
||||
import CopyIcon from '../UI/CustomSvgIcons/Copy';
|
||||
import { type ConnectDragSource } from 'react-dnd';
|
||||
import ResponsiveFlatButton from '../UI/ResponsiveFlatButton';
|
||||
import { Accordion, AccordionHeader, AccordionBody } from '../UI/Accordion';
|
||||
import { type Field } from '../CompactPropertiesEditor';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -132,14 +134,12 @@ const Effect = React.forwardRef(
|
||||
ref
|
||||
) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
|
||||
const preferences = React.useContext(PreferencesContext);
|
||||
const showEffectParameterNames =
|
||||
preferences.values.showEffectParameterNames;
|
||||
const setShowEffectParameterNames = preferences.setShowEffectParameterNames;
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const isClipboardContainingEffects = Clipboard.has(EFFECTS_CLIPBOARD_KIND);
|
||||
|
||||
const renameEffect = React.useCallback(
|
||||
@@ -190,6 +190,42 @@ const Effect = React.forwardRef(
|
||||
effectType
|
||||
);
|
||||
|
||||
const parametersSchema = effectMetadata && effectMetadata.parametersSchema;
|
||||
const basicPropertiesSchema = React.useMemo(
|
||||
() =>
|
||||
parametersSchema
|
||||
? parametersSchema.filter(param => param.valueType && !param.advanced)
|
||||
: [],
|
||||
[parametersSchema]
|
||||
);
|
||||
const advancedPropertiesSchema = React.useMemo(
|
||||
() =>
|
||||
parametersSchema
|
||||
? parametersSchema.filter(param => param.valueType && param.advanced)
|
||||
: [],
|
||||
[parametersSchema]
|
||||
);
|
||||
|
||||
const areAdvancedPropertiesModified = React.useMemo(
|
||||
() => {
|
||||
return advancedPropertiesSchema.some((field: Field) => {
|
||||
const name = field.valueType ? field.name : null;
|
||||
if (!name) return false;
|
||||
|
||||
const current =
|
||||
field.valueType === 'number'
|
||||
? effect.getDoubleParameter(name)
|
||||
: field.valueType === 'boolean'
|
||||
? effect.getBooleanParameter(name)
|
||||
: effect.getStringParameter(name);
|
||||
|
||||
const defaultValue = field.valueType ? field.defaultValue : null;
|
||||
return current !== defaultValue;
|
||||
});
|
||||
},
|
||||
[advancedPropertiesSchema, effect]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -286,33 +322,61 @@ const Effect = React.forwardRef(
|
||||
/>
|
||||
<Spacer />
|
||||
</div>
|
||||
{effectType && (
|
||||
{effectMetadata && (
|
||||
<Line expand noMargin>
|
||||
<Column expand>
|
||||
{effectMetadata ? (
|
||||
<React.Fragment>
|
||||
<Line>
|
||||
<BackgroundText>
|
||||
<MarkdownText source={effectMetadata.description} />
|
||||
</BackgroundText>
|
||||
</Line>
|
||||
<PropertiesEditor
|
||||
key={effect.getEffectType()}
|
||||
instances={[effect]}
|
||||
schema={effectMetadata.parametersSchema}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
renderExtraDescriptionText={
|
||||
showEffectParameterNames
|
||||
? parameterName =>
|
||||
i18n._(
|
||||
t`Property name in events: \`${parameterName}\` `
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
) : null}
|
||||
<Line>
|
||||
<BackgroundText>
|
||||
<MarkdownText source={effectMetadata.description} />
|
||||
</BackgroundText>
|
||||
</Line>
|
||||
<PropertiesEditor
|
||||
key={effect.getEffectType() + '-basic'}
|
||||
instances={[effect]}
|
||||
schema={basicPropertiesSchema}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
renderExtraDescriptionText={
|
||||
showEffectParameterNames
|
||||
? parameterName =>
|
||||
i18n._(
|
||||
t`Property name in events: \`${parameterName}\` `
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{advancedPropertiesSchema.length > 0 && (
|
||||
<Accordion
|
||||
defaultExpanded={areAdvancedPropertiesModified}
|
||||
noMargin
|
||||
>
|
||||
<AccordionHeader noMargin>
|
||||
<Text size="sub-title">
|
||||
<Trans>Advanced properties</Trans>
|
||||
</Text>
|
||||
</AccordionHeader>
|
||||
<AccordionBody disableGutters>
|
||||
<Column expand noMargin>
|
||||
<PropertiesEditor
|
||||
key={effect.getEffectType() + '-advanced'}
|
||||
instances={[effect]}
|
||||
schema={advancedPropertiesSchema}
|
||||
project={project}
|
||||
resourceManagementProps={resourceManagementProps}
|
||||
renderExtraDescriptionText={
|
||||
showEffectParameterNames
|
||||
? parameterName =>
|
||||
i18n._(
|
||||
t`Property name in events: \`${parameterName}\` `
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Column>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
<Spacer />
|
||||
</Column>
|
||||
</Line>
|
||||
|