Compare commits
48 Commits
v5.5.232
...
fix/disabl
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8b2c32bd6a | ||
![]() |
15f3a45d6a | ||
![]() |
f0a4f352cc | ||
![]() |
d16b3e8154 | ||
![]() |
614fb97288 | ||
![]() |
8a40d3645a | ||
![]() |
2b7dadf2a8 | ||
![]() |
c338e16e4f | ||
![]() |
aded08471d | ||
![]() |
cccb59b1c5 | ||
![]() |
3592fb7e62 | ||
![]() |
307c92991c | ||
![]() |
4b3f077669 | ||
![]() |
352bae518e | ||
![]() |
c958f4d522 | ||
![]() |
35bbb37ad2 | ||
![]() |
1d48acc841 | ||
![]() |
87702edccc | ||
![]() |
1f0ba7c19a | ||
![]() |
b4d08a99ad | ||
![]() |
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 |
3
.vscode/tasks.json
vendored
@@ -38,8 +38,7 @@
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"isBackground": true,
|
||||
"runOptions": { "instanceLimit": 1, "runOn": "folderOpen" }
|
||||
"isBackground": true
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
|
@@ -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"))
|
||||
|
@@ -8,6 +8,8 @@
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
gd::String Effect::badStringParameterValue;
|
||||
|
||||
void Effect::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("name", GetName());
|
||||
|
@@ -3,8 +3,7 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef GDCORE_EFFECT_H
|
||||
#define GDCORE_EFFECT_H
|
||||
#pragma once
|
||||
#include <map>
|
||||
namespace gd {
|
||||
class SerializerElement;
|
||||
@@ -35,28 +34,43 @@ class GD_CORE_API Effect {
|
||||
void SetFolded(bool fold = true) { folded = fold; }
|
||||
bool IsFolded() const { return folded; }
|
||||
|
||||
void SetDoubleParameter(const gd::String& name, double value) {
|
||||
void SetDoubleParameter(const gd::String &name, double value) {
|
||||
doubleParameters[name] = value;
|
||||
}
|
||||
|
||||
double GetDoubleParameter(const gd::String& name) {
|
||||
return doubleParameters[name];
|
||||
double GetDoubleParameter(const gd::String &name) const {
|
||||
auto itr = doubleParameters.find(name);
|
||||
return itr == doubleParameters.end() ? 0 : itr->second;
|
||||
}
|
||||
|
||||
void SetStringParameter(const gd::String& name, const gd::String& value) {
|
||||
bool HasDoubleParameter(const gd::String &name) const {
|
||||
return doubleParameters.find(name) != doubleParameters.end();
|
||||
}
|
||||
|
||||
void SetStringParameter(const gd::String &name, const gd::String &value) {
|
||||
stringParameters[name] = value;
|
||||
}
|
||||
|
||||
const gd::String& GetStringParameter(const gd::String& name) {
|
||||
return stringParameters[name];
|
||||
const gd::String &GetStringParameter(const gd::String &name) const {
|
||||
auto itr = stringParameters.find(name);
|
||||
return itr == stringParameters.end() ? badStringParameterValue : itr->second;
|
||||
}
|
||||
|
||||
void SetBooleanParameter(const gd::String& name, bool value) {
|
||||
bool HasStringParameter(const gd::String &name) const {
|
||||
return stringParameters.find(name) != stringParameters.end();
|
||||
}
|
||||
|
||||
void SetBooleanParameter(const gd::String &name, bool value) {
|
||||
booleanParameters[name] = value;
|
||||
}
|
||||
|
||||
bool GetBooleanParameter(const gd::String& name) {
|
||||
return booleanParameters[name];
|
||||
bool GetBooleanParameter(const gd::String &name) const {
|
||||
auto itr = booleanParameters.find(name);
|
||||
return itr == booleanParameters.end() ? false : itr->second;
|
||||
}
|
||||
|
||||
bool HasBooleanParameter(const gd::String &name) const {
|
||||
return booleanParameters.find(name) != booleanParameters.end();
|
||||
}
|
||||
|
||||
const std::map<gd::String, double>& GetAllDoubleParameters() const {
|
||||
@@ -94,7 +108,9 @@ class GD_CORE_API Effect {
|
||||
std::map<gd::String, double> doubleParameters; ///< Values of parameters being doubles, keyed by names.
|
||||
std::map<gd::String, gd::String> stringParameters; ///< Values of parameters being strings, keyed by names.
|
||||
std::map<gd::String, bool> booleanParameters; ///< Values of parameters being booleans, keyed by names.
|
||||
|
||||
static gd::String badStringParameterValue; ///< Empty string returned by
|
||||
///< GeStringParameter
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
#endif
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -5,8 +5,6 @@ namespace gdjs {
|
||||
type Object3DNetworkSyncDataType = {
|
||||
// z is position on the Z axis, different from zo, which is Z order
|
||||
z: number;
|
||||
w: number;
|
||||
h: number;
|
||||
d: number;
|
||||
rx: number;
|
||||
ry: number;
|
||||
@@ -116,8 +114,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
z: this.getZ(),
|
||||
w: this.getWidth(),
|
||||
h: this.getHeight(),
|
||||
d: this.getDepth(),
|
||||
rx: this.getRotationX(),
|
||||
ry: this.getRotationY(),
|
||||
@@ -130,8 +126,6 @@ namespace gdjs {
|
||||
updateFromNetworkSyncData(networkSyncData: Object3DNetworkSyncData) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
||||
if (networkSyncData.w !== undefined) this.setWidth(networkSyncData.w);
|
||||
if (networkSyncData.h !== undefined) this.setHeight(networkSyncData.h);
|
||||
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
||||
if (networkSyncData.rx !== undefined)
|
||||
this.setRotationX(networkSyncData.rx);
|
||||
|
@@ -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._isReceivingShadow;
|
||||
boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
|
||||
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];
|
||||
|
@@ -1,4 +1,12 @@
|
||||
namespace gdjs {
|
||||
type CustomObject3DNetworkSyncDataType = CustomObjectNetworkSyncDataType & {
|
||||
z: float;
|
||||
d: float;
|
||||
rx: float;
|
||||
ry: float;
|
||||
ifz: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for 3D custom objects.
|
||||
*/
|
||||
@@ -77,6 +85,30 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getNetworkSyncData(): CustomObject3DNetworkSyncDataType {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
z: this.getZ(),
|
||||
d: this.getDepth(),
|
||||
rx: this.getRotationX(),
|
||||
ry: this.getRotationY(),
|
||||
ifz: this.isFlippedZ(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: CustomObject3DNetworkSyncDataType
|
||||
): void {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
||||
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
||||
if (networkSyncData.rx !== undefined)
|
||||
this.setRotationX(networkSyncData.rx);
|
||||
if (networkSyncData.ry !== undefined)
|
||||
this.setRotationY(networkSyncData.ry);
|
||||
if (networkSyncData.ifz !== undefined) this.flipZ(networkSyncData.ifz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object position on the Z axis.
|
||||
*/
|
||||
|
@@ -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,63 @@ 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 _top: string = 'Z+';
|
||||
private _elevation: float = 45;
|
||||
private _rotation: float = 0;
|
||||
private _shadowMapSize: float = 1024;
|
||||
private _minimumShadowBias: float = 0;
|
||||
private _distanceFromCamera: float = 1500;
|
||||
private _frustumSize: float = 4000;
|
||||
|
||||
private _isEnabled: boolean = false;
|
||||
private _light: THREE.DirectionalLight;
|
||||
private _shadowMapDirty = true;
|
||||
private _shadowCameraDirty = true;
|
||||
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;
|
||||
|
||||
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 +98,12 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.add(this.rotationObject);
|
||||
scene.add(this._light);
|
||||
scene.add(this._light.target);
|
||||
if (this._shadowCameraHelper) {
|
||||
scene.add(this._shadowCameraHelper);
|
||||
}
|
||||
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
@@ -65,82 +115,164 @@ 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 {
|
||||
// Apply any update to the camera or shadow map size.
|
||||
this._updateShadowCamera();
|
||||
this._updateShadowMapSize();
|
||||
|
||||
// Avoid shadow acne due to depth buffer precision.
|
||||
const biasMultiplier =
|
||||
this._shadowMapSize < 1024
|
||||
? 2
|
||||
: this._shadowMapSize < 2048
|
||||
? 1.25
|
||||
: 1;
|
||||
this._light.shadow.bias = -this._minimumShadowBias * biasMultiplier;
|
||||
|
||||
// Apply update to the light position and its target.
|
||||
// By doing this, the shadows are "following" the GDevelop camera.
|
||||
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)) *
|
||||
Math.cos(gdjs.toRad(this._elevation));
|
||||
const posLightY =
|
||||
roundedY +
|
||||
this._distanceFromCamera *
|
||||
Math.sin(gdjs.toRad(this._rotation)) *
|
||||
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;
|
||||
} else if (parameterName === 'minimumShadowBias') {
|
||||
this._minimumShadowBias = 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;
|
||||
} else if (parameterName === 'minimumShadowBias') {
|
||||
return this._minimumShadowBias;
|
||||
}
|
||||
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();
|
||||
this._top = value;
|
||||
}
|
||||
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;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
@@ -18,18 +18,15 @@ namespace gdjs {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
light: THREE.HemisphereLight;
|
||||
rotationObject: THREE.Group;
|
||||
_top: string = 'Z+';
|
||||
_elevation: float = 90;
|
||||
_rotation: float = 0;
|
||||
|
||||
_isEnabled: boolean = false;
|
||||
top: string = 'Y-';
|
||||
elevation: float = 45;
|
||||
rotation: float = 0;
|
||||
_light: THREE.HemisphereLight;
|
||||
|
||||
constructor() {
|
||||
this.light = new THREE.HemisphereLight();
|
||||
this.light.position.set(1, 0, 0);
|
||||
this.rotationObject = new THREE.Group();
|
||||
this.rotationObject.add(this.light);
|
||||
this._light = new THREE.HemisphereLight();
|
||||
this.updateRotation();
|
||||
}
|
||||
|
||||
@@ -54,7 +51,7 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.add(this.rotationObject);
|
||||
scene.add(this._light);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
@@ -66,96 +63,106 @@ namespace gdjs {
|
||||
if (!scene) {
|
||||
return false;
|
||||
}
|
||||
scene.remove(this.rotationObject);
|
||||
scene.remove(this._light);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
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._elevation = value;
|
||||
this.updateRotation();
|
||||
} else if (parameterName === 'rotation') {
|
||||
this.rotation = value;
|
||||
this._rotation = value;
|
||||
this.updateRotation();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {
|
||||
if (parameterName === 'skyColor') {
|
||||
this.light.color = new THREE.Color(
|
||||
this._light.color = new THREE.Color(
|
||||
gdjs.rgbOrHexStringToNumber(value)
|
||||
);
|
||||
}
|
||||
if (parameterName === 'groundColor') {
|
||||
this.light.groundColor = new THREE.Color(
|
||||
this._light.groundColor = new THREE.Color(
|
||||
gdjs.rgbOrHexStringToNumber(value)
|
||||
);
|
||||
}
|
||||
if (parameterName === 'top') {
|
||||
this.top = value;
|
||||
this._top = value;
|
||||
this.updateRotation();
|
||||
}
|
||||
}
|
||||
updateColorParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'skyColor') {
|
||||
this.light.color.setHex(value);
|
||||
this._light.color.setHex(value);
|
||||
}
|
||||
if (parameterName === 'groundColor') {
|
||||
this.light.groundColor.setHex(value);
|
||||
this._light.groundColor.setHex(value);
|
||||
}
|
||||
}
|
||||
getColorParameter(parameterName: string): number {
|
||||
if (parameterName === 'skyColor') {
|
||||
return this.light.color.getHex();
|
||||
return this._light.color.getHex();
|
||||
}
|
||||
if (parameterName === 'groundColor') {
|
||||
return this.light.groundColor.getHex();
|
||||
return this._light.groundColor.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);
|
||||
if (this._top === 'Y-') {
|
||||
// `rotation` at 0° becomes a light from Z+.
|
||||
this._light.position.set(
|
||||
Math.cos(gdjs.toRad(-this._rotation + 90)) *
|
||||
Math.cos(gdjs.toRad(this._elevation)),
|
||||
-Math.sin(gdjs.toRad(this._elevation)),
|
||||
Math.sin(gdjs.toRad(-this._rotation + 90)) *
|
||||
Math.cos(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);
|
||||
// `rotation` at 0° is a light from the right of the screen.
|
||||
this._light.position.set(
|
||||
Math.cos(gdjs.toRad(this._rotation)) *
|
||||
Math.cos(gdjs.toRad(this._elevation)),
|
||||
Math.sin(gdjs.toRad(this._rotation)) *
|
||||
Math.cos(gdjs.toRad(this._elevation)),
|
||||
Math.sin(gdjs.toRad(this._elevation))
|
||||
);
|
||||
}
|
||||
}
|
||||
getNetworkSyncData(): HemisphereLightFilterNetworkSyncData {
|
||||
return {
|
||||
i: this.light.intensity,
|
||||
sc: this.light.color.getHex(),
|
||||
gc: this.light.groundColor.getHex(),
|
||||
e: this.elevation,
|
||||
r: this.rotation,
|
||||
t: this.top,
|
||||
i: this._light.intensity,
|
||||
sc: this._light.color.getHex(),
|
||||
gc: this._light.groundColor.getHex(),
|
||||
e: this._elevation,
|
||||
r: this._rotation,
|
||||
t: this._top,
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(
|
||||
syncData: HemisphereLightFilterNetworkSyncData
|
||||
): void {
|
||||
this.light.intensity = syncData.i;
|
||||
this.light.color.setHex(syncData.sc);
|
||||
this.light.groundColor.setHex(syncData.gc);
|
||||
this.elevation = syncData.e;
|
||||
this.rotation = syncData.r;
|
||||
this.top = syncData.t;
|
||||
this._light.intensity = syncData.i;
|
||||
this._light.color.setHex(syncData.sc);
|
||||
this._light.groundColor.setHex(syncData.gc);
|
||||
this._elevation = syncData.e;
|
||||
this._rotation = syncData.r;
|
||||
this._top = syncData.t;
|
||||
this.updateRotation();
|
||||
}
|
||||
})();
|
||||
|
@@ -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,11 +1085,29 @@ module.exports = {
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('materialType')
|
||||
.setValue(objectContent.materialType || 'Basic')
|
||||
.setValue(objectContent.materialType || 'StandardWithoutMetalness')
|
||||
.setType('choice')
|
||||
.addExtraInfo('Basic')
|
||||
.addExtraInfo('StandardWithoutMetalness')
|
||||
.setLabel(_('Material type'));
|
||||
.addChoice('Basic', _('Basic (no lighting, no shadows)'))
|
||||
.addChoice(
|
||||
'StandardWithoutMetalness',
|
||||
_('Standard (without metalness)')
|
||||
)
|
||||
.setLabel(_('Material type'))
|
||||
.setGroup(_('Lighting'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('isCastingShadow')
|
||||
.setValue(objectContent.isCastingShadow ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Shadow casting'))
|
||||
.setGroup(_('Lighting'));
|
||||
|
||||
objectProperties
|
||||
.getOrCreate('isReceivingShadow')
|
||||
.setValue(objectContent.isReceivingShadow ? 'true' : 'false')
|
||||
.setType('boolean')
|
||||
.setLabel(_('Shadow receiving'))
|
||||
.setGroup(_('Lighting'));
|
||||
|
||||
return objectProperties;
|
||||
};
|
||||
@@ -1116,8 +1136,10 @@ module.exports = {
|
||||
rightFaceResourceRepeat: false,
|
||||
topFaceResourceRepeat: false,
|
||||
bottomFaceResourceRepeat: false,
|
||||
materialType: 'Basic',
|
||||
materialType: 'StandardWithoutMetalness',
|
||||
tint: '255;255;255',
|
||||
isCastingShadow: true,
|
||||
isReceivingShadow: true,
|
||||
};
|
||||
|
||||
Cube3DObject.updateInitialInstanceProperty = function (
|
||||
@@ -1894,11 +1916,11 @@ module.exports = {
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('top')
|
||||
.setValue('Y-')
|
||||
.setValue('Z+')
|
||||
.setLabel(_('3D world top'))
|
||||
.setType('choice')
|
||||
.addExtraInfo('Y-')
|
||||
.addExtraInfo('Z+')
|
||||
.addExtraInfo('Y-')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('elevation')
|
||||
@@ -1913,6 +1935,47 @@ module.exports = {
|
||||
.setLabel(_('Rotation (in degrees)'))
|
||||
.setType('number')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('isCastingShadow')
|
||||
.setValue('false')
|
||||
.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('minimumShadowBias')
|
||||
.setValue('0')
|
||||
.setLabel(_('Shadow bias'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Use this to avoid "shadow acne" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 1.25 for medium quality and 2 for low quality.'
|
||||
)
|
||||
)
|
||||
.setType('number')
|
||||
.setGroup(_('Shadows'))
|
||||
.setAdvanced(true);
|
||||
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')
|
||||
.setGroup(_('Shadows'))
|
||||
.setAdvanced(true);
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
@@ -1944,11 +2007,11 @@ module.exports = {
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('top')
|
||||
.setValue('Y-')
|
||||
.setValue('Z+')
|
||||
.setLabel(_('3D world top'))
|
||||
.setType('choice')
|
||||
.addExtraInfo('Y-')
|
||||
.addExtraInfo('Z+')
|
||||
.addExtraInfo('Y-')
|
||||
.setGroup(_('Orientation'));
|
||||
properties
|
||||
.getOrCreate('elevation')
|
||||
@@ -3210,6 +3273,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,20 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
objectProperties["materialType"]
|
||||
.SetValue(materialType.empty() ? "Basic" : materialType)
|
||||
.SetType("choice")
|
||||
.AddExtraInfo("Basic")
|
||||
.AddExtraInfo("StandardWithoutMetalness")
|
||||
.AddExtraInfo("KeepOriginal")
|
||||
.SetLabel(_("Material"));
|
||||
.AddChoice("Basic", _("Basic (no lighting, no shadows)"))
|
||||
.AddChoice("StandardWithoutMetalness", _("Standard (without metalness)"))
|
||||
.AddChoice("KeepOriginal", _("Keep original"))
|
||||
.SetLabel(_("Material"))
|
||||
.SetGroup(_("Lighting"));
|
||||
|
||||
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 +174,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 +189,20 @@ Model3DObjectConfiguration::GetProperties() const {
|
||||
.SetGroup(_("Animations"))
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetSecond());
|
||||
|
||||
objectProperties["isCastingShadow"]
|
||||
.SetValue(isCastingShadow ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow casting"))
|
||||
.SetGroup(_("Lighting"));
|
||||
|
||||
objectProperties["isReceivingShadow"]
|
||||
.SetValue(isReceivingShadow ? "true" : "false")
|
||||
.SetType("boolean")
|
||||
.SetLabel(_("Shadow receiving"))
|
||||
.SetGroup(_("Lighting"));
|
||||
|
||||
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
||||
@@ -210,6 +235,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 +266,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:
|
||||
|
@@ -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'));
|
||||
|
||||
|
@@ -293,6 +293,8 @@ namespace gdjs {
|
||||
x: objectNetworkSyncData.x,
|
||||
y: objectNetworkSyncData.y,
|
||||
z: objectNetworkSyncData.z,
|
||||
w: objectNetworkSyncData.w,
|
||||
h: objectNetworkSyncData.h,
|
||||
zo: objectNetworkSyncData.zo,
|
||||
a: objectNetworkSyncData.a,
|
||||
hid: objectNetworkSyncData.hid,
|
||||
@@ -369,6 +371,9 @@ namespace gdjs {
|
||||
this._lastSentBasicObjectSyncData = {
|
||||
x: objectNetworkSyncData.x,
|
||||
y: objectNetworkSyncData.y,
|
||||
z: objectNetworkSyncData.z,
|
||||
w: objectNetworkSyncData.w,
|
||||
h: objectNetworkSyncData.h,
|
||||
zo: objectNetworkSyncData.zo,
|
||||
a: objectNetworkSyncData.a,
|
||||
hid: objectNetworkSyncData.hid,
|
||||
|
@@ -25,8 +25,6 @@ namespace gdjs {
|
||||
export type PanelSpriteObjectData = ObjectData & PanelSpriteObjectDataType;
|
||||
|
||||
export type PanelSpriteNetworkSyncDataType = {
|
||||
wid: number;
|
||||
hei: number;
|
||||
op: number;
|
||||
color: string;
|
||||
};
|
||||
@@ -124,8 +122,6 @@ namespace gdjs {
|
||||
getNetworkSyncData(): PanelSpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
op: this.getOpacity(),
|
||||
color: this.getColor(),
|
||||
};
|
||||
@@ -138,12 +134,6 @@ namespace gdjs {
|
||||
|
||||
// Texture is not synchronized, see if this is asked or not.
|
||||
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
if (networkSyncData.op !== undefined) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
|
@@ -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');
|
||||
@@ -927,6 +927,54 @@ module.exports = {
|
||||
.setFunctionName('setDensity')
|
||||
.setGetter('getDensity');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'ShapeOffsetX',
|
||||
_('Shape offset X'),
|
||||
_('the object shape offset on X.'),
|
||||
_('the shape offset on X'),
|
||||
_('Body settings'),
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setShapeOffsetX')
|
||||
.setGetter('getShapeOffsetX');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'ShapeOffsetY',
|
||||
_('Shape offset Y'),
|
||||
_('the object shape offset on Y.'),
|
||||
_('the shape offset on Y'),
|
||||
_('Body settings'),
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setShapeOffsetY')
|
||||
.setGetter('getShapeOffsetY');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'ShapeOffsetZ',
|
||||
_('Shape offset Z'),
|
||||
_('the object shape offset on Z.'),
|
||||
_('the shape offset on Z'),
|
||||
_('Body settings'),
|
||||
'JsPlatform/Extensions/physics3d.svg'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics3DBehavior')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setShapeOffsetZ')
|
||||
.setGetter('getShapeOffsetZ');
|
||||
|
||||
aut
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
@@ -1054,7 +1102,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 +1138,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 +1318,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 +1592,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
|
||||
|
@@ -630,10 +630,7 @@ namespace gdjs {
|
||||
|
||||
override onDeActivate() {
|
||||
this._sharedData.removeFromBehaviorsList(this);
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
override onActivate() {
|
||||
@@ -650,6 +647,24 @@ namespace gdjs {
|
||||
this.onDeActivate();
|
||||
}
|
||||
|
||||
_destroyBody() {
|
||||
this.bodyUpdater.destroyBody();
|
||||
this._contactsEndedThisFrame.length = 0;
|
||||
this._contactsStartedThisFrame.length = 0;
|
||||
this._currentContacts.length = 0;
|
||||
}
|
||||
|
||||
resetToDefaultBodyUpdater() {
|
||||
this.bodyUpdater = new gdjs.Physics3DRuntimeBehavior.DefaultBodyUpdater(
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
resetToDefaultCollisionChecker() {
|
||||
this.collisionChecker =
|
||||
new gdjs.Physics3DRuntimeBehavior.DefaultCollisionChecker(this);
|
||||
}
|
||||
|
||||
createShape(): Jolt.Shape {
|
||||
if (
|
||||
this.massCenterOffsetX === 0 &&
|
||||
@@ -927,9 +942,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 +951,7 @@ namespace gdjs {
|
||||
if (!this._body) {
|
||||
return;
|
||||
}
|
||||
bodyID = this._body.GetID();
|
||||
const bodyID = this._body.GetID();
|
||||
bodyInterface.SetLinearVelocity(
|
||||
bodyID,
|
||||
this.getVec3(linearVelocityX, linearVelocityY, linearVelocityZ)
|
||||
@@ -1178,6 +1191,33 @@ namespace gdjs {
|
||||
this._needToRecreateBody = true;
|
||||
}
|
||||
|
||||
getShapeOffsetX(): float {
|
||||
return this.shapeOffsetX;
|
||||
}
|
||||
|
||||
setShapeOffsetX(shapeOffsetX: float): void {
|
||||
this.shapeOffsetX = shapeOffsetX;
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getShapeOffsetY(): float {
|
||||
return this.shapeOffsetY;
|
||||
}
|
||||
|
||||
setShapeOffsetY(shapeOffsetY: float): void {
|
||||
this.shapeOffsetY = shapeOffsetY;
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getShapeOffsetZ(): float {
|
||||
return this.shapeOffsetZ;
|
||||
}
|
||||
|
||||
setShapeOffsetZ(shapeOffsetZ: float): void {
|
||||
this.shapeOffsetZ = shapeOffsetZ;
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getFriction(): float {
|
||||
return this.friction;
|
||||
}
|
||||
@@ -1542,9 +1582,9 @@ namespace gdjs {
|
||||
}
|
||||
const body = this._body!;
|
||||
|
||||
const deltaX = towardX - body.GetPosition().GetX();
|
||||
const deltaY = towardY - body.GetPosition().GetY();
|
||||
const deltaZ = towardZ - body.GetPosition().GetZ();
|
||||
const deltaX = towardX - this.owner3D.getX();
|
||||
const deltaY = towardY - this.owner3D.getY();
|
||||
const deltaZ = towardZ - this.owner3D.getZ();
|
||||
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSq === 0) {
|
||||
return;
|
||||
@@ -1602,19 +1642,16 @@ namespace gdjs {
|
||||
length: float,
|
||||
towardX: float,
|
||||
towardY: float,
|
||||
towardZ: float,
|
||||
originX: float,
|
||||
originY: float,
|
||||
originZ: float
|
||||
towardZ: float
|
||||
): void {
|
||||
if (this._body === null) {
|
||||
if (!this._createBody()) return;
|
||||
}
|
||||
const body = this._body!;
|
||||
|
||||
const deltaX = towardX - originX;
|
||||
const deltaY = towardY - originY;
|
||||
const deltaZ = towardZ - originZ;
|
||||
const deltaX = towardX - this.owner3D.getX();
|
||||
const deltaY = towardY - this.owner3D.getY();
|
||||
const deltaZ = towardZ - this.owner3D.getZ();
|
||||
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSq === 0) {
|
||||
return;
|
||||
@@ -1623,12 +1660,7 @@ namespace gdjs {
|
||||
|
||||
this._sharedData.bodyInterface.AddImpulse(
|
||||
body.GetID(),
|
||||
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio),
|
||||
this.getRVec3(
|
||||
originX * this._sharedData.worldInvScale,
|
||||
originY * this._sharedData.worldInvScale,
|
||||
originZ * this._sharedData.worldInvScale
|
||||
)
|
||||
this.getVec3(deltaX * ratio, deltaY * ratio, deltaZ * ratio)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -29,6 +29,7 @@ namespace gdjs {
|
||||
owner3D: gdjs.RuntimeObject3D;
|
||||
private _physics3DBehaviorName: string;
|
||||
private _physics3D: Physics3D | null = null;
|
||||
private _isHookedToPhysicsStep = false;
|
||||
_vehicleController: Jolt.WheeledVehicleController | null = null;
|
||||
_stepListener: Jolt.VehicleConstraintStepListener | null = null;
|
||||
_vehicleCollisionTester: Jolt.VehicleCollisionTesterCastCylinder | null =
|
||||
@@ -153,13 +154,19 @@ namespace gdjs {
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
if (!behavior.activated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sharedData = behavior._sharedData;
|
||||
|
||||
this._physics3D = {
|
||||
behavior,
|
||||
};
|
||||
sharedData.registerHook(this);
|
||||
if (!this._isHookedToPhysicsStep) {
|
||||
sharedData.registerHook(this);
|
||||
this._isHookedToPhysicsStep = true;
|
||||
}
|
||||
|
||||
behavior.bodyUpdater =
|
||||
new gdjs.PhysicsCar3DRuntimeBehavior.VehicleBodyUpdater(
|
||||
@@ -330,25 +337,33 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override onDeActivate() {
|
||||
if (this._stepListener) {
|
||||
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
|
||||
if (!this._physics3D) {
|
||||
return;
|
||||
}
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
override onActivate() {
|
||||
if (this._stepListener) {
|
||||
this._sharedData.physicsSystem.AddStepListener(this._stepListener);
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
if (!behavior) {
|
||||
return;
|
||||
}
|
||||
behavior._destroyBody();
|
||||
}
|
||||
|
||||
override onDestroy() {
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
_destroyBody() {
|
||||
if (!this._vehicleController) {
|
||||
return;
|
||||
}
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this.onDeActivate();
|
||||
if (this._stepListener) {
|
||||
// stepListener is removed by onDeActivate
|
||||
this._sharedData.physicsSystem.RemoveStepListener(this._stepListener);
|
||||
Jolt.destroy(this._stepListener);
|
||||
this._stepListener = null;
|
||||
}
|
||||
@@ -360,6 +375,8 @@ namespace gdjs {
|
||||
// It is destroyed with the constraint.
|
||||
this._vehicleCollisionTester = null;
|
||||
if (this._physics3D) {
|
||||
const { behavior } = this._physics3D;
|
||||
behavior.resetToDefaultBodyUpdater();
|
||||
this._physics3D = null;
|
||||
}
|
||||
}
|
||||
@@ -733,7 +750,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setWheelOffsetZ(wheelOffsetZ: float): void {
|
||||
this._wheelOffsetY = wheelOffsetZ;
|
||||
this._wheelOffsetZ = wheelOffsetZ;
|
||||
this._updateWheels();
|
||||
}
|
||||
|
||||
@@ -783,11 +800,11 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
hasFrontWheelDrive(): boolean {
|
||||
return this._hasBackWheelDrive;
|
||||
return this._hasFrontWheelDrive;
|
||||
}
|
||||
|
||||
setFrontWheelDrive(hasFrontWheelDrive: boolean): void {
|
||||
this._hasBackWheelDrive = hasFrontWheelDrive;
|
||||
this._hasFrontWheelDrive = hasFrontWheelDrive;
|
||||
this.invalidateShape();
|
||||
}
|
||||
|
||||
@@ -1110,7 +1127,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
destroyBody() {
|
||||
this.carBehavior.onDestroy();
|
||||
this.carBehavior._destroyBody();
|
||||
this.physicsBodyUpdater.destroyBody();
|
||||
}
|
||||
}
|
||||
|
@@ -41,6 +41,7 @@ namespace gdjs {
|
||||
owner3D: gdjs.RuntimeObject3D;
|
||||
private _physics3DBehaviorName: string;
|
||||
private _physics3D: Physics3D | null = null;
|
||||
private _isHookedToPhysicsStep = false;
|
||||
character: Jolt.CharacterVirtual | null = null;
|
||||
/**
|
||||
* sharedData is a reference to the shared data of the scene, that registers
|
||||
@@ -169,10 +170,15 @@ namespace gdjs {
|
||||
if (this._physics3D) {
|
||||
return this._physics3D;
|
||||
}
|
||||
if (!this.activated()) {
|
||||
return null;
|
||||
}
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
|
||||
if (!behavior.activated()) {
|
||||
return null;
|
||||
}
|
||||
const sharedData = behavior._sharedData;
|
||||
const jolt = sharedData.jolt;
|
||||
const extendedUpdateSettings = new Jolt.ExtendedUpdateSettings();
|
||||
@@ -196,7 +202,10 @@ namespace gdjs {
|
||||
shapeFilter,
|
||||
};
|
||||
this.setStairHeightMax(this._stairHeightMax);
|
||||
sharedData.registerHook(this);
|
||||
if (!this._isHookedToPhysicsStep) {
|
||||
sharedData.registerHook(this);
|
||||
this._isHookedToPhysicsStep = true;
|
||||
}
|
||||
|
||||
behavior.bodyUpdater =
|
||||
new gdjs.PhysicsCharacter3DRuntimeBehavior.CharacterBodyUpdater(this);
|
||||
@@ -390,36 +399,48 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override onDeActivate() {
|
||||
this.collisionChecker.clearContacts();
|
||||
if (!this._physics3D) {
|
||||
return;
|
||||
}
|
||||
this._destroyBody();
|
||||
}
|
||||
|
||||
override onActivate() {}
|
||||
override onActivate() {
|
||||
const behavior = this.owner.getBehavior(
|
||||
this._physics3DBehaviorName
|
||||
) as gdjs.Physics3DRuntimeBehavior;
|
||||
if (!behavior) {
|
||||
return;
|
||||
}
|
||||
behavior._destroyBody();
|
||||
}
|
||||
|
||||
override onDestroy() {
|
||||
this._destroyedDuringFrameLogic = true;
|
||||
this.onDeActivate();
|
||||
this._destroyCharacter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the character and its body from the physics engine.
|
||||
* This method is called when:
|
||||
* - The Physics3D behavior is deactivated
|
||||
* - This behavior is deactivated
|
||||
* - The object is destroyed
|
||||
*
|
||||
* Only deactivating the character behavior won't destroy the character.
|
||||
* Indeed, deactivated characters don't move as characters but still have collisions.
|
||||
*/
|
||||
_destroyCharacter() {
|
||||
_destroyBody() {
|
||||
if (this.character) {
|
||||
if (this._canBePushed) {
|
||||
this.charactersManager.removeCharacter(this.character);
|
||||
Jolt.destroy(this.character.GetListener());
|
||||
}
|
||||
this.collisionChecker.clearContacts();
|
||||
// The body is destroyed with the character.
|
||||
Jolt.destroy(this.character);
|
||||
this.character = null;
|
||||
if (this._physics3D) {
|
||||
const { behavior } = this._physics3D;
|
||||
behavior.resetToDefaultBodyUpdater();
|
||||
behavior.resetToDefaultCollisionChecker();
|
||||
this._physics3D.behavior._body = null;
|
||||
const {
|
||||
extendedUpdateSettings,
|
||||
@@ -1780,7 +1801,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
destroyBody() {
|
||||
this.characterBehavior._destroyCharacter();
|
||||
this.characterBehavior._destroyBody();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,6 @@ namespace gdjs {
|
||||
|
||||
export type SpineNetworkSyncDataType = {
|
||||
opa: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
scaX: float;
|
||||
scaY: float;
|
||||
flipX: boolean;
|
||||
@@ -117,8 +115,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
opa: this._opacity,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
scaX: this.getScaleX(),
|
||||
scaY: this.getScaleY(),
|
||||
flipX: this.isFlippedX(),
|
||||
@@ -137,12 +133,6 @@ namespace gdjs {
|
||||
if (syncData.opa !== undefined && syncData.opa !== this._opacity) {
|
||||
this.setOpacity(syncData.opa);
|
||||
}
|
||||
if (syncData.wid !== undefined && syncData.wid !== this.getWidth()) {
|
||||
this.setWidth(syncData.wid);
|
||||
}
|
||||
if (syncData.hei !== undefined && syncData.hei !== this.getHeight()) {
|
||||
this.setHeight(syncData.hei);
|
||||
}
|
||||
if (syncData.scaX !== undefined && syncData.scaX !== this.getScaleX()) {
|
||||
this.setScaleX(syncData.scaX);
|
||||
}
|
||||
|
@@ -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'));
|
||||
|
||||
|
@@ -64,8 +64,6 @@ namespace gdjs {
|
||||
|
||||
export type TextInputNetworkSyncDataType = {
|
||||
opa: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
txt: string;
|
||||
frn: string;
|
||||
fs: number;
|
||||
@@ -260,8 +258,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
opa: this.getOpacity(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
txt: this.getText(),
|
||||
frn: this.getFontResourceName(),
|
||||
fs: this.getFontSize(),
|
||||
@@ -282,8 +278,6 @@ namespace gdjs {
|
||||
super.updateFromNetworkSyncData(syncData);
|
||||
|
||||
if (syncData.opa !== undefined) this.setOpacity(syncData.opa);
|
||||
if (syncData.wid !== undefined) this.setWidth(syncData.wid);
|
||||
if (syncData.hei !== undefined) this.setHeight(syncData.hei);
|
||||
if (syncData.txt !== undefined) this.setText(syncData.txt);
|
||||
if (syncData.frn !== undefined) this.setFontResourceName(syncData.frn);
|
||||
if (syncData.fs !== undefined) this.setFontSize(syncData.fs);
|
||||
|
@@ -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'))
|
||||
);
|
||||
|
@@ -17,8 +17,6 @@ namespace gdjs {
|
||||
export type SimpleTileMapNetworkSyncDataType = {
|
||||
op: number;
|
||||
ai: string;
|
||||
wid: number;
|
||||
hei: number;
|
||||
// TODO: Support tilemap synchronization. Find an efficient way to send tiles changes.
|
||||
};
|
||||
|
||||
@@ -170,8 +168,6 @@ namespace gdjs {
|
||||
...super.getNetworkSyncData(),
|
||||
op: this._opacity,
|
||||
ai: this._atlasImage,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -186,18 +182,6 @@ namespace gdjs {
|
||||
) {
|
||||
this.setOpacity(networkSyncData.op);
|
||||
}
|
||||
if (
|
||||
networkSyncData.wid !== undefined &&
|
||||
networkSyncData.wid !== this.getWidth()
|
||||
) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (
|
||||
networkSyncData.hei !== undefined &&
|
||||
networkSyncData.hei !== this.getHeight()
|
||||
) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
if (networkSyncData.ai !== undefined) {
|
||||
// TODO: support changing the atlas texture
|
||||
}
|
||||
|
@@ -26,8 +26,6 @@ namespace gdjs {
|
||||
os: float;
|
||||
fo: float;
|
||||
oo: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
};
|
||||
|
||||
export type TilemapCollisionMaskNetworkSyncData = ObjectNetworkSyncData &
|
||||
@@ -202,8 +200,6 @@ namespace gdjs {
|
||||
os: this.getOutlineSize(),
|
||||
fo: this.getFillOpacity(),
|
||||
oo: this.getOutlineOpacity(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -236,12 +232,6 @@ namespace gdjs {
|
||||
if (networkSyncData.oo !== undefined) {
|
||||
this.setOutlineOpacity(networkSyncData.oo);
|
||||
}
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(initialInstanceData): void {
|
||||
|
@@ -25,8 +25,6 @@ namespace gdjs {
|
||||
lai: number;
|
||||
lei: number;
|
||||
asps: number;
|
||||
wid: number;
|
||||
hei: number;
|
||||
};
|
||||
|
||||
export type TilemapNetworkSyncData = ObjectNetworkSyncData &
|
||||
@@ -158,8 +156,6 @@ namespace gdjs {
|
||||
lai: this._layerIndex,
|
||||
lei: this._levelIndex,
|
||||
asps: this._animationSpeedScale,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,12 +186,6 @@ namespace gdjs {
|
||||
if (networkSyncData.asps !== undefined) {
|
||||
this.setAnimationSpeedScale(networkSyncData.asps);
|
||||
}
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(initialInstanceData): void {
|
||||
|
@@ -15,8 +15,6 @@ namespace gdjs {
|
||||
export type TiledSpriteObjectData = ObjectData & TiledSpriteObjectDataType;
|
||||
|
||||
export type TiledSpriteNetworkSyncDataType = {
|
||||
wid: number;
|
||||
hei: number;
|
||||
xo: number;
|
||||
yo: number;
|
||||
op: number;
|
||||
@@ -83,8 +81,6 @@ namespace gdjs {
|
||||
getNetworkSyncData(): TiledSpriteNetworkSyncData {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
xo: this.getXOffset(),
|
||||
yo: this.getYOffset(),
|
||||
op: this.getOpacity(),
|
||||
@@ -99,12 +95,6 @@ namespace gdjs {
|
||||
|
||||
// Texture is not synchronized, see if this is asked or not.
|
||||
|
||||
if (networkSyncData.wid !== undefined) {
|
||||
this.setWidth(networkSyncData.wid);
|
||||
}
|
||||
if (networkSyncData.hei !== undefined) {
|
||||
this.setHeight(networkSyncData.hei);
|
||||
}
|
||||
if (networkSyncData.xo !== undefined) {
|
||||
this.setXOffset(networkSyncData.xo);
|
||||
}
|
||||
|
@@ -18,8 +18,6 @@ namespace gdjs {
|
||||
|
||||
export type VideoNetworkSyncDataType = {
|
||||
op: float;
|
||||
wid: float;
|
||||
hei: float;
|
||||
// We don't sync volume, as it's probably a user setting?
|
||||
pla: boolean;
|
||||
loop: boolean;
|
||||
@@ -105,8 +103,6 @@ namespace gdjs {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
op: this._opacity,
|
||||
wid: this.getWidth(),
|
||||
hei: this.getHeight(),
|
||||
pla: this.isPlayed(),
|
||||
loop: this.isLooped(),
|
||||
ct: this.getCurrentTime(),
|
||||
@@ -120,12 +116,6 @@ namespace gdjs {
|
||||
if (this._opacity !== undefined && this._opacity && syncData.op) {
|
||||
this.setOpacity(syncData.op);
|
||||
}
|
||||
if (this.getWidth() !== undefined && this.getWidth() !== syncData.wid) {
|
||||
this.setWidth(syncData.wid);
|
||||
}
|
||||
if (this.getHeight() !== undefined && this.getHeight() !== syncData.hei) {
|
||||
this.setHeight(syncData.hei);
|
||||
}
|
||||
if (syncData.pla !== undefined && this.isPlayed() !== syncData.pla) {
|
||||
syncData.pla ? this.play() : this.pause();
|
||||
}
|
||||
|
@@ -17,6 +17,11 @@ namespace gdjs {
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
export type CustomObjectNetworkSyncDataType = ObjectNetworkSyncData & {
|
||||
ifx: boolean;
|
||||
ify: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* An object that contains other object.
|
||||
*
|
||||
@@ -216,6 +221,26 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(): CustomObjectNetworkSyncDataType {
|
||||
return {
|
||||
...super.getNetworkSyncData(),
|
||||
ifx: this.isFlippedX(),
|
||||
ify: this.isFlippedY(),
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
networkSyncData: CustomObjectNetworkSyncDataType
|
||||
) {
|
||||
super.updateFromNetworkSyncData(networkSyncData);
|
||||
if (networkSyncData.ifx !== undefined) {
|
||||
this.flipX(networkSyncData.ifx);
|
||||
}
|
||||
if (networkSyncData.ify !== undefined) {
|
||||
this.flipY(networkSyncData.ify);
|
||||
}
|
||||
}
|
||||
|
||||
override extraInitializationFromInitialInstance(
|
||||
initialInstanceData: InstanceData
|
||||
) {
|
||||
@@ -253,7 +278,12 @@ namespace gdjs {
|
||||
// Let behaviors do something before the object is destroyed.
|
||||
super.onDeletedFromScene();
|
||||
// Destroy the children.
|
||||
this._instanceContainer.onDestroyFromScene(this._runtimeScene);
|
||||
this._instanceContainer.onDeletedFromScene(this._runtimeScene);
|
||||
}
|
||||
|
||||
override onDestroyed(): void {
|
||||
this._instanceContainer._destroy();
|
||||
super.onDestroyed();
|
||||
}
|
||||
|
||||
override update(parent: gdjs.RuntimeInstanceContainer): void {
|
||||
@@ -276,6 +306,8 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* This method is called when the preview is being hot-reloaded.
|
||||
*
|
||||
* Custom objects implement this method with code generated from events.
|
||||
*/
|
||||
onHotReloading(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
@@ -284,6 +316,8 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* This method is called each tick after events are done.
|
||||
*
|
||||
* Custom objects implement this method with code generated from events.
|
||||
* @param parent The instanceContainer owning the object
|
||||
*/
|
||||
doStepPostEvents(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
@@ -291,6 +325,8 @@ namespace gdjs {
|
||||
/**
|
||||
* This method is called when the object is being removed from its parent
|
||||
* container and is about to be destroyed/reused later.
|
||||
*
|
||||
* Custom objects implement this method with code generated from events.
|
||||
*/
|
||||
onDestroy(parent: gdjs.RuntimeInstanceContainer) {}
|
||||
|
||||
|
@@ -71,7 +71,7 @@ namespace gdjs {
|
||||
eventsBasedObjectVariantData: EventsBasedObjectVariantData
|
||||
) {
|
||||
if (this._isLoaded) {
|
||||
this.onDestroyFromScene(this._parent);
|
||||
this.onDeletedFromScene(this._parent);
|
||||
}
|
||||
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
@@ -186,26 +186,25 @@ namespace gdjs {
|
||||
*
|
||||
* @param instanceContainer The container owning the object.
|
||||
*/
|
||||
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
onDeletedFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (!this._isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the objects they are being destroyed
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
object.onDeletedFromScene();
|
||||
// The object can free all its resource directly...
|
||||
object.onDestroyed();
|
||||
}
|
||||
// ...as its container cache `_instancesRemoved` is also destroy.
|
||||
this._destroy();
|
||||
|
||||
this._isLoaded = false;
|
||||
}
|
||||
|
||||
_destroy() {
|
||||
override _destroy() {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
object.onDestroyed();
|
||||
}
|
||||
// It should not be necessary to reset these variables, but this help
|
||||
// ensuring that all memory related to the container is released immediately.
|
||||
super._destroy();
|
||||
@@ -383,11 +382,9 @@ namespace gdjs {
|
||||
): FloatPoint {
|
||||
const position = result || [0, 0];
|
||||
this._customObject.applyObjectTransformation(sceneX, sceneY, position);
|
||||
return this._parent.convertInverseCoords(
|
||||
position[0],
|
||||
position[1],
|
||||
position
|
||||
);
|
||||
return this._parent
|
||||
.getLayer(this._customObject.getLayer())
|
||||
.convertInverseCoords(position[0], position[1], 0, position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -116,6 +116,9 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getRuntimeLayer(): gdjs.RuntimeLayer {
|
||||
return this;
|
||||
}
|
||||
getRenderer(): gdjs.LayerRenderer {
|
||||
return this._renderer;
|
||||
}
|
||||
|
@@ -738,7 +738,6 @@ namespace gdjs {
|
||||
// scene (see `_hotReloadRuntimeInstanceContainer` call from
|
||||
// `_hotReloadRuntimeSceneInstances`).
|
||||
objects: mergedChildObjectDataList,
|
||||
childrenContent: mergedChildObjectDataList,
|
||||
};
|
||||
return mergedObjectConfiguration;
|
||||
});
|
||||
|
@@ -380,11 +380,8 @@ namespace gdjs {
|
||||
.isMouseInsideCanvas();
|
||||
};
|
||||
|
||||
const _cursorIsOnObject = function (
|
||||
obj: gdjs.RuntimeObject,
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
return obj.cursorOnObject(instanceContainer);
|
||||
const _cursorIsOnObject = function (obj: gdjs.RuntimeObject) {
|
||||
return obj.cursorOnObject();
|
||||
};
|
||||
|
||||
export const cursorOnObject = function (
|
||||
@@ -397,7 +394,7 @@ namespace gdjs {
|
||||
_cursorIsOnObject,
|
||||
objectsLists,
|
||||
inverted,
|
||||
instanceContainer
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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(
|
||||
|
@@ -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.
|
||||
|
@@ -292,7 +292,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Called to reset the object to its default state. This is used for objects that are
|
||||
* "recycled": they are dismissed (at which point `onDestroyFromScene` is called) but still
|
||||
* "recycled": they are dismissed (at which point `onDeletedFromScene` is called) but still
|
||||
* stored in a cache to be reused next time an object must be created. At this point,
|
||||
* `reinitialize` will be called. The object must then work as if it was a newly constructed
|
||||
* object.
|
||||
@@ -484,6 +484,8 @@ namespace gdjs {
|
||||
return {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
w: this.getWidth(),
|
||||
h: this.getHeight(),
|
||||
zo: this.zOrder,
|
||||
a: this.angle,
|
||||
hid: this.hidden,
|
||||
@@ -512,6 +514,12 @@ namespace gdjs {
|
||||
if (networkSyncData.y !== undefined) {
|
||||
this.setY(networkSyncData.y);
|
||||
}
|
||||
if (networkSyncData.w !== undefined) {
|
||||
this.setWidth(networkSyncData.w);
|
||||
}
|
||||
if (networkSyncData.h !== undefined) {
|
||||
this.setHeight(networkSyncData.h);
|
||||
}
|
||||
if (networkSyncData.zo !== undefined) {
|
||||
this.setZOrder(networkSyncData.zo);
|
||||
}
|
||||
@@ -596,7 +604,7 @@ namespace gdjs {
|
||||
/**
|
||||
* Remove an object from a scene.
|
||||
*
|
||||
* Do not change/redefine this method. Instead, redefine the onDestroyFromScene method.
|
||||
* Do not change/redefine this method. Instead, redefine the onDeletedFromScene method.
|
||||
*/
|
||||
deleteFromScene(): void {
|
||||
if (this._livingOnScene) {
|
||||
@@ -614,9 +622,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the object is destroyed (because it is removed from a scene or the scene
|
||||
* is being unloaded). If you redefine this function, **make sure to call the original method**
|
||||
* (`RuntimeObject.prototype.onDestroyFromScene.call(this, runtimeScene);`).
|
||||
* Called when the object is deleted (because it is removed from a scene or
|
||||
* the scene is being unloaded). The object is not actually destroyed and
|
||||
* can still be used by events.
|
||||
*
|
||||
* If you redefine this function, **make sure to call the original method**
|
||||
* (`super.onDeletedFromScene();`).
|
||||
*/
|
||||
onDeletedFromScene(): void {
|
||||
const theLayer = this._runtimeScene.getLayer(this.layer);
|
||||
@@ -635,6 +646,10 @@ namespace gdjs {
|
||||
this.clearEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on deleted objects after all events has been executed for the
|
||||
* current frame and the object can be safely destroyed.
|
||||
*/
|
||||
onDestroyed(): void {}
|
||||
|
||||
/**
|
||||
@@ -2714,11 +2729,12 @@ namespace gdjs {
|
||||
*
|
||||
* @return true if the cursor, or any touch, is on the object.
|
||||
*/
|
||||
cursorOnObject(instanceContainer: gdjs.RuntimeInstanceContainer): boolean {
|
||||
cursorOnObject(): boolean {
|
||||
const workingPoint: FloatPoint = gdjs.staticArray(
|
||||
RuntimeObject.prototype.cursorOnObject
|
||||
) as FloatPoint;
|
||||
workingPoint.length = 2;
|
||||
const instanceContainer = this.getInstanceContainer();
|
||||
const inputManager = instanceContainer.getGame().getInputManager();
|
||||
const layer = instanceContainer.getLayer(this.layer);
|
||||
const mousePos = layer.convertCoords(
|
||||
|
@@ -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
|
||||
@@ -306,7 +308,7 @@ namespace gdjs {
|
||||
this.onGameResolutionResized();
|
||||
}
|
||||
|
||||
_destroy() {
|
||||
override _destroy() {
|
||||
// It should not be necessary to reset these variables, but this help
|
||||
// ensuring that all memory related to the RuntimeScene is released immediately.
|
||||
super._destroy();
|
||||
@@ -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.';
|
||||
|
11
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -52,6 +52,10 @@ declare type BasicObjectNetworkSyncData = {
|
||||
y: number;
|
||||
/** The position of the instance on the Z axis. Defined only for 3D games */
|
||||
z?: number;
|
||||
/** The width of the instance */
|
||||
w: number;
|
||||
/** The height of the instance */
|
||||
h: number;
|
||||
/** Z order of the instance */
|
||||
zo: number;
|
||||
/** The angle of the instance. */
|
||||
@@ -169,6 +173,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 {
|
||||
@@ -370,6 +376,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 +445,5 @@ declare type ResourceKind =
|
||||
| 'bitmapFont'
|
||||
| 'model3D'
|
||||
| 'atlas'
|
||||
| 'spine';
|
||||
| 'spine'
|
||||
| 'fake-resource-kind-for-testing-only';
|
||||
|
@@ -225,7 +225,7 @@ const identifyClassNames = (code) => {
|
||||
.replace(/( hide\w*\(.*\))(: any)? {/g, '$1: void {')
|
||||
.replace(/( reset\w*\(.*\))(: any)? {/g, '$1: void {')
|
||||
.replace(/( deleteFromScene\w*\(.*\))(: any)? {/g, '$1: void {')
|
||||
.replace(/( onDestroyFromScene\w*\(.*\))(: any)? {/g, '$1: void {')
|
||||
.replace(/( onDeletedFromScene\w*\(.*\))(: any)? {/g, '$1: void {')
|
||||
.replace(/( enable\w*\(.*\))(: any)? {/g, '$1: void {')
|
||||
// Members:
|
||||
.replace(/(\w*): any = (\d)/g, '$1: number = $2')
|
||||
|
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 |
@@ -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);
|
||||
});
|
||||
});
|
@@ -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 {
|
||||
@@ -1049,10 +1059,13 @@ interface Effect {
|
||||
|
||||
void SetDoubleParameter([Const] DOMString name, double value);
|
||||
double GetDoubleParameter([Const] DOMString name);
|
||||
boolean HasDoubleParameter([Const] DOMString name);
|
||||
void SetStringParameter([Const] DOMString name, [Const] DOMString value);
|
||||
[Const, Ref] DOMString GetStringParameter([Const] DOMString name);
|
||||
boolean HasStringParameter([Const] DOMString name);
|
||||
void SetBooleanParameter([Const] DOMString name, boolean value);
|
||||
boolean GetBooleanParameter([Const] DOMString name);
|
||||
boolean HasBooleanParameter([Const] DOMString name);
|
||||
[Const, Ref] MapStringDouble GetAllDoubleParameters();
|
||||
[Const, Ref] MapStringString GetAllStringParameters();
|
||||
[Const, Ref] MapStringBoolean GetAllBooleanParameters();
|
||||
@@ -1136,6 +1149,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 +1173,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)
|
||||
|
@@ -475,7 +475,7 @@ class RuntimeObject {
|
||||
}
|
||||
|
||||
/** @param {RuntimeScene} runtimeScene */
|
||||
onDestroyFromScene(runtimeScene) {
|
||||
onDeletedFromScene(runtimeScene) {
|
||||
// Note: these mocks don't support behaviors nor layers or effects.
|
||||
|
||||
this.destroyCallbacks.forEach((c) => c());
|
||||
@@ -789,7 +789,7 @@ class RuntimeScene {
|
||||
}
|
||||
|
||||
//Notify the object it was removed from the scene
|
||||
obj.onDestroyFromScene(this);
|
||||
obj.onDeletedFromScene(this);
|
||||
}
|
||||
|
||||
getObjects(objectName) {
|
||||
|
24
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 {
|
||||
@@ -858,10 +866,13 @@ export class Effect extends EmscriptenObject {
|
||||
isFolded(): boolean;
|
||||
setDoubleParameter(name: string, value: number): void;
|
||||
getDoubleParameter(name: string): number;
|
||||
hasDoubleParameter(name: string): boolean;
|
||||
setStringParameter(name: string, value: string): void;
|
||||
getStringParameter(name: string): string;
|
||||
hasStringParameter(name: string): boolean;
|
||||
setBooleanParameter(name: string, value: boolean): void;
|
||||
getBooleanParameter(name: string): boolean;
|
||||
hasBooleanParameter(name: string): boolean;
|
||||
getAllDoubleParameters(): MapStringDouble;
|
||||
getAllStringParameters(): MapStringString;
|
||||
getAllBooleanParameters(): MapStringBoolean;
|
||||
@@ -936,6 +947,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 +970,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;
|
||||
|
@@ -9,10 +9,13 @@ declare class gdEffect {
|
||||
isFolded(): boolean;
|
||||
setDoubleParameter(name: string, value: number): void;
|
||||
getDoubleParameter(name: string): number;
|
||||
hasDoubleParameter(name: string): boolean;
|
||||
setStringParameter(name: string, value: string): void;
|
||||
getStringParameter(name: string): string;
|
||||
hasStringParameter(name: string): boolean;
|
||||
setBooleanParameter(name: string, value: boolean): void;
|
||||
getBooleanParameter(name: string): boolean;
|
||||
hasBooleanParameter(name: string): boolean;
|
||||
getAllDoubleParameters(): gdMapStringDouble;
|
||||
getAllStringParameters(): gdMapStringString;
|
||||
getAllBooleanParameters(): gdMapStringBoolean;
|
||||
|
@@ -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>;
|
||||
|
@@ -1,2 +1,3 @@
|
||||
# Prettier has troubles handling the generic typing with flow in this file.
|
||||
src/Utils/UseOptimisticState.js
|
||||
src/Utils/UseOptimisticState.js
|
||||
src/Utils/Sha256.js
|
||||
|
@@ -441,6 +441,17 @@
|
||||
}
|
||||
},
|
||||
"rst": {
|
||||
"ai-generated-event-move-handle": {
|
||||
"gradient-color-1": {
|
||||
"value": "#9932cc"
|
||||
},
|
||||
"gradient-color-2": {
|
||||
"value": "#fa7ff1"
|
||||
},
|
||||
"gradient-color-3": {
|
||||
"value": "#9932cc"
|
||||
}
|
||||
},
|
||||
"move-handle": {
|
||||
"background-color": {
|
||||
"value": "#494949"
|
||||
|
@@ -136,12 +136,26 @@ const getQuotaOrCreditsText = ({
|
||||
>
|
||||
<div>
|
||||
{isMobile ? (
|
||||
<Trans>{quota.max - quota.current} free requests left</Trans>
|
||||
increaseQuotaOffering === 'subscribe' ? (
|
||||
<Trans>{quota.max - quota.current} trial requests left</Trans>
|
||||
) : (
|
||||
<Trans>{quota.max - quota.current} free requests left</Trans>
|
||||
)
|
||||
) : quota.period === '30days' ? (
|
||||
increaseQuotaOffering === 'subscribe' ? (
|
||||
<Trans>
|
||||
{quota.max - quota.current} free trial requests left this month
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
{quota.max - quota.current} of {quota.max} free requests left this
|
||||
month
|
||||
</Trans>
|
||||
)
|
||||
) : quota.period === '1day' ? (
|
||||
<Trans>{quota.max - quota.current} free requests left today</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
{quota.max - quota.current} of {quota.max} free requests left this
|
||||
month
|
||||
</Trans>
|
||||
<Trans>{quota.max - quota.current} free requests left</Trans>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@@ -2,7 +2,10 @@
|
||||
import * as React from 'react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type RenderEditorContainerPropsWithRef } from '../MainFrame/EditorContainers/BaseEditor';
|
||||
import {
|
||||
type RenderEditorContainerPropsWithRef,
|
||||
type SceneEventsOutsideEditorChanges,
|
||||
} from '../MainFrame/EditorContainers/BaseEditor';
|
||||
import { type ObjectWithContext } from '../ObjectsList/EnumerateObjects';
|
||||
import Paper from '../UI/Paper';
|
||||
import { AiRequestChat, type AiRequestChatInterface } from './AiRequestChat';
|
||||
@@ -48,6 +51,7 @@ import {
|
||||
} from '../Utils/Analytics/EventSender';
|
||||
import { useCreateAiProjectDialog } from './UseCreateAiProjectDialog';
|
||||
import { type ExampleShortHeader } from '../Utils/GDevelopServices/Example';
|
||||
import { prepareAiUserContent } from './PrepareAiUserContent';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -131,7 +135,9 @@ const useProcessFunctionCalls = ({
|
||||
string,
|
||||
Array<EditorFunctionCallResult>
|
||||
) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (scene: gdLayout) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (
|
||||
changes: SceneEventsOutsideEditorChanges
|
||||
) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|}) => {
|
||||
const { ensureExtensionInstalled } = useEnsureExtensionInstalled({
|
||||
@@ -473,7 +479,9 @@ type Props = {|
|
||||
| 'none',
|
||||
|}
|
||||
) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (scene: gdLayout) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (
|
||||
changes: SceneEventsOutsideEditorChanges
|
||||
) => void,
|
||||
onExtensionInstalled: (extensionNames: Array<string>) => void,
|
||||
|};
|
||||
|
||||
@@ -487,7 +495,9 @@ export type AskAiEditorInterface = {|
|
||||
objectWithContext: ObjectWithContext
|
||||
) => void,
|
||||
onSceneObjectsDeleted: (scene: gdLayout) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (scene: gdLayout) => void,
|
||||
onSceneEventsModifiedOutsideEditor: (
|
||||
changes: SceneEventsOutsideEditorChanges
|
||||
) => void,
|
||||
startNewChat: () => void,
|
||||
|};
|
||||
|
||||
@@ -714,11 +724,17 @@ export const AskAiEditor = React.memo<Props>(
|
||||
|
||||
setSendingAiRequest(null, true);
|
||||
|
||||
const preparedAiUserContent = await prepareAiUserContent({
|
||||
getAuthorizationHeader,
|
||||
userId: profile.id,
|
||||
simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
});
|
||||
|
||||
const aiRequest = await createAiRequest(getAuthorizationHeader, {
|
||||
userRequest: userRequest,
|
||||
userId: profile.id,
|
||||
gameProjectJson: simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
...preparedAiUserContent,
|
||||
payWithCredits,
|
||||
gameId: project ? project.getProjectUuid() : null,
|
||||
fileMetadata,
|
||||
@@ -863,13 +879,19 @@ export const AskAiEditor = React.memo<Props>(
|
||||
)
|
||||
: null;
|
||||
|
||||
const preparedAiUserContent = await prepareAiUserContent({
|
||||
getAuthorizationHeader,
|
||||
userId: profile.id,
|
||||
simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
});
|
||||
|
||||
const aiRequest: AiRequest = await retryIfFailed({ times: 2 }, () =>
|
||||
addMessageToAiRequest(getAuthorizationHeader, {
|
||||
userId: profile.id,
|
||||
aiRequestId: selectedAiRequestId,
|
||||
functionCallOutputs,
|
||||
gameProjectJson: simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
...preparedAiUserContent,
|
||||
payWithCredits,
|
||||
userMessage,
|
||||
})
|
||||
|
233
newIDE/app/src/AiGeneration/PrepareAiUserContent.js
Normal file
@@ -0,0 +1,233 @@
|
||||
// @flow
|
||||
import axios from 'axios';
|
||||
import { retryIfFailed } from '../Utils/RetryIfFailed';
|
||||
import {
|
||||
createAiUserContentPresignedUrls,
|
||||
type AiUserContentPresignedUrlsResult,
|
||||
} from '../Utils/GDevelopServices/Generation';
|
||||
import jsSHA from '../Utils/Sha256';
|
||||
|
||||
type UploadInfo = {
|
||||
uploadedAt: number,
|
||||
userRelativeKey: string | null,
|
||||
};
|
||||
|
||||
const makeUploadCache = ({
|
||||
minimalContentLength,
|
||||
}: {|
|
||||
minimalContentLength: number | null,
|
||||
|}) => {
|
||||
const uploadCacheByHash: {
|
||||
[string]: UploadInfo,
|
||||
} = {};
|
||||
|
||||
return {
|
||||
getUserRelativeKey: (hash: string | null) => {
|
||||
if (!hash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
(uploadCacheByHash[hash] && uploadCacheByHash[hash].userRelativeKey) ||
|
||||
null
|
||||
);
|
||||
},
|
||||
storeUpload: (hash: string | null, uploadInfo: UploadInfo) => {
|
||||
if (!hash) return;
|
||||
uploadCacheByHash[hash] = uploadInfo;
|
||||
},
|
||||
shouldUpload: ({
|
||||
hash,
|
||||
contentLength,
|
||||
}: {|
|
||||
hash: string | null,
|
||||
contentLength: number,
|
||||
|}) => {
|
||||
if (!hash) {
|
||||
// No hash, so no content to upload.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (minimalContentLength && contentLength < minimalContentLength) {
|
||||
// The content is too small to be uploaded.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
uploadCacheByHash[hash] &&
|
||||
uploadCacheByHash[hash].uploadedAt > Date.now() - 1000 * 60 * 30
|
||||
) {
|
||||
// The content was already uploaded recently (and recently enough so that it has not expired in such a short time).
|
||||
// We don't need to upload it again.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The content was not uploaded, or not recently: we'll upload it now.
|
||||
return true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const projectSpecificExtensionsSummaryUploadCache = makeUploadCache({
|
||||
minimalContentLength: null, // Always upload the project specific extensions summary.
|
||||
});
|
||||
const gameProjectJsonUploadCache = makeUploadCache({
|
||||
minimalContentLength: 10 * 1000, // Roughly 10KB.
|
||||
});
|
||||
|
||||
const computeSha256 = (payload: string): string => {
|
||||
const shaObj = new jsSHA('SHA-256', 'TEXT', { encoding: 'UTF8' });
|
||||
shaObj.update(payload);
|
||||
return shaObj.getHash('HEX');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare the user content to be used by the AI.
|
||||
* It either uploads the content (and avoid uploading it again for a while)
|
||||
* so that the request will just refer to the key where it's stored, or
|
||||
* return the content so it's sent as part of the request itself (if it's small enough).
|
||||
*/
|
||||
export const prepareAiUserContent = async ({
|
||||
getAuthorizationHeader,
|
||||
userId,
|
||||
simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
}: {|
|
||||
getAuthorizationHeader: () => Promise<string>,
|
||||
userId: string,
|
||||
simplifiedProjectJson: string | null,
|
||||
projectSpecificExtensionsSummaryJson: string | null,
|
||||
|}) => {
|
||||
// Hash the contents, if provided, to then upload it only once (as long as the hash stays
|
||||
// the same, no need to re-upload it for a while).
|
||||
// If the content is not provided, no hash is computed because there is no content to upload.
|
||||
const startTime = Date.now();
|
||||
const gameProjectJsonHash = simplifiedProjectJson
|
||||
? computeSha256(simplifiedProjectJson)
|
||||
: null;
|
||||
const projectSpecificExtensionsSummaryJsonHash = projectSpecificExtensionsSummaryJson
|
||||
? computeSha256(projectSpecificExtensionsSummaryJson)
|
||||
: null;
|
||||
const endTime = Date.now();
|
||||
console.info(
|
||||
`Hash of simplified project json and project specific extensions summary json took ${(
|
||||
endTime - startTime
|
||||
).toFixed(2)}ms`
|
||||
);
|
||||
|
||||
const shouldUploadProjectSpecificExtensionsSummary = projectSpecificExtensionsSummaryUploadCache.shouldUpload(
|
||||
{
|
||||
hash: projectSpecificExtensionsSummaryJsonHash,
|
||||
contentLength: projectSpecificExtensionsSummaryJson
|
||||
? projectSpecificExtensionsSummaryJson.length
|
||||
: 0,
|
||||
}
|
||||
);
|
||||
|
||||
const shouldUploadGameProjectJson = gameProjectJsonUploadCache.shouldUpload({
|
||||
hash: gameProjectJsonHash,
|
||||
contentLength: simplifiedProjectJson ? simplifiedProjectJson.length : 0,
|
||||
});
|
||||
|
||||
if (
|
||||
shouldUploadGameProjectJson ||
|
||||
shouldUploadProjectSpecificExtensionsSummary
|
||||
) {
|
||||
const startTime = Date.now();
|
||||
const {
|
||||
gameProjectJsonSignedUrl,
|
||||
gameProjectJsonUserRelativeKey,
|
||||
projectSpecificExtensionsSummaryJsonSignedUrl,
|
||||
projectSpecificExtensionsSummaryJsonUserRelativeKey,
|
||||
}: AiUserContentPresignedUrlsResult = await retryIfFailed(
|
||||
{ times: 3 },
|
||||
() =>
|
||||
createAiUserContentPresignedUrls(getAuthorizationHeader, {
|
||||
userId,
|
||||
gameProjectJsonHash: shouldUploadGameProjectJson
|
||||
? gameProjectJsonHash
|
||||
: null,
|
||||
projectSpecificExtensionsSummaryJsonHash: shouldUploadProjectSpecificExtensionsSummary
|
||||
? projectSpecificExtensionsSummaryJsonHash
|
||||
: null,
|
||||
})
|
||||
);
|
||||
|
||||
const uploadedAt = Date.now();
|
||||
|
||||
await Promise.all([
|
||||
gameProjectJsonSignedUrl
|
||||
? retryIfFailed({ times: 3 }, () =>
|
||||
axios.put(gameProjectJsonSignedUrl, simplifiedProjectJson, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// Allow any arbitrary large file to be sent
|
||||
maxContentLength: Infinity,
|
||||
})
|
||||
).then(() => {
|
||||
gameProjectJsonUploadCache.storeUpload(gameProjectJsonHash, {
|
||||
uploadedAt,
|
||||
userRelativeKey: gameProjectJsonUserRelativeKey || null,
|
||||
});
|
||||
})
|
||||
: null,
|
||||
projectSpecificExtensionsSummaryJsonSignedUrl
|
||||
? retryIfFailed({ times: 3 }, () =>
|
||||
axios.put(
|
||||
projectSpecificExtensionsSummaryJsonSignedUrl,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// Allow any arbitrary large file to be sent
|
||||
maxContentLength: Infinity,
|
||||
}
|
||||
)
|
||||
).then(() => {
|
||||
projectSpecificExtensionsSummaryUploadCache.storeUpload(
|
||||
projectSpecificExtensionsSummaryJsonHash,
|
||||
{
|
||||
uploadedAt,
|
||||
userRelativeKey:
|
||||
projectSpecificExtensionsSummaryJsonUserRelativeKey || null,
|
||||
}
|
||||
);
|
||||
})
|
||||
: null,
|
||||
]);
|
||||
|
||||
const endTime = Date.now();
|
||||
console.info(
|
||||
`Upload of ${[
|
||||
shouldUploadGameProjectJson ? 'simplified project' : null,
|
||||
shouldUploadProjectSpecificExtensionsSummary
|
||||
? 'project specific extensions summary'
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' and ')} took ${(endTime - startTime).toFixed(2)}ms`
|
||||
);
|
||||
}
|
||||
|
||||
// Get the key at which the content was uploaded, if it was uploaded.
|
||||
// If not, the content will be sent as part of the request instead of the upload key.
|
||||
const gameProjectJsonUserRelativeKey = gameProjectJsonUploadCache.getUserRelativeKey(
|
||||
gameProjectJsonHash
|
||||
);
|
||||
const projectSpecificExtensionsSummaryJsonUserRelativeKey = projectSpecificExtensionsSummaryUploadCache.getUserRelativeKey(
|
||||
projectSpecificExtensionsSummaryJsonHash
|
||||
);
|
||||
|
||||
return {
|
||||
gameProjectJsonUserRelativeKey,
|
||||
gameProjectJson: gameProjectJsonUserRelativeKey
|
||||
? null
|
||||
: simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJsonUserRelativeKey,
|
||||
projectSpecificExtensionsSummaryJson: projectSpecificExtensionsSummaryJsonUserRelativeKey
|
||||
? null
|
||||
: projectSpecificExtensionsSummaryJson,
|
||||
};
|
||||
};
|
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
import { type EventsGenerationResult } from '../EditorFunctions';
|
||||
import { makeSimplifiedProjectBuilder } from '../EditorFunctions/SimplifiedProject/SimplifiedProject';
|
||||
import { prepareAiUserContent } from './PrepareAiUserContent';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -40,21 +41,24 @@ export const useGenerateEvents = ({ project }: {| project: ?gdProject |}) => {
|
||||
if (!profile) throw new Error('User should be authenticated.');
|
||||
|
||||
const simplifiedProjectBuilder = makeSimplifiedProjectBuilder(gd);
|
||||
const simplifiedProjectJson = JSON.stringify(
|
||||
simplifiedProjectBuilder.getSimplifiedProject(project, {})
|
||||
);
|
||||
const projectSpecificExtensionsSummaryJson = JSON.stringify(
|
||||
simplifiedProjectBuilder.getProjectSpecificExtensionsSummary(project)
|
||||
);
|
||||
|
||||
const preparedAiUserContent = await prepareAiUserContent({
|
||||
getAuthorizationHeader,
|
||||
userId: profile.id,
|
||||
simplifiedProjectJson,
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
});
|
||||
|
||||
const createResult = await retryIfFailed({ times: 2 }, () =>
|
||||
createAiGeneratedEvent(getAuthorizationHeader, {
|
||||
userId: profile.id,
|
||||
partialGameProjectJson: JSON.stringify(
|
||||
simplifiedProjectBuilder.getSimplifiedProject(project, {
|
||||
scopeToScene: sceneName,
|
||||
}),
|
||||
null,
|
||||
2
|
||||
),
|
||||
projectSpecificExtensionsSummaryJson,
|
||||
...preparedAiUserContent,
|
||||
sceneName,
|
||||
eventsDescription,
|
||||
extensionNamesList,
|
||||
|
@@ -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:
|
||||
|