mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
80 Commits
restore-ve
...
v5.3.187
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9eb721662f | ||
![]() |
b10b131010 | ||
![]() |
b5fd1bb351 | ||
![]() |
f1c9521625 | ||
![]() |
6ceb3c2c10 | ||
![]() |
43827876cd | ||
![]() |
62b746300a | ||
![]() |
769ebcd91c | ||
![]() |
70d5de16bf | ||
![]() |
7fbe1bd23d | ||
![]() |
de8a679e31 | ||
![]() |
a1a4029b35 | ||
![]() |
a377467031 | ||
![]() |
dd090fd1d7 | ||
![]() |
26ee9b3891 | ||
![]() |
ad18eab4ae | ||
![]() |
007fc36a2e | ||
![]() |
c8ebfde85b | ||
![]() |
d0005ba2cb | ||
![]() |
f623b352ee | ||
![]() |
16e2d8a005 | ||
![]() |
7654883cb1 | ||
![]() |
3ae5db2a49 | ||
![]() |
9ed002c879 | ||
![]() |
d4283c2350 | ||
![]() |
5a176d21e7 | ||
![]() |
bfdfd7f0fb | ||
![]() |
aae75f2232 | ||
![]() |
977092c0a3 | ||
![]() |
556688cedb | ||
![]() |
ce93dc5310 | ||
![]() |
d6d425db4f | ||
![]() |
2737e75639 | ||
![]() |
36eab18133 | ||
![]() |
48acbb12ee | ||
![]() |
21904e46f1 | ||
![]() |
94753ac053 | ||
![]() |
b160ee9b27 | ||
![]() |
f76e8a72b6 | ||
![]() |
5bc342688d | ||
![]() |
3e6204c0eb | ||
![]() |
7b1c340ad0 | ||
![]() |
fac724dc3f | ||
![]() |
9b4151f64c | ||
![]() |
6b5ab6c811 | ||
![]() |
5943092b0c | ||
![]() |
deb0c5ffc3 | ||
![]() |
c56fa03bf6 | ||
![]() |
0d49d449db | ||
![]() |
fdd702cd09 | ||
![]() |
5a8e4a7ca9 | ||
![]() |
2e4e91c21e | ||
![]() |
6ece930809 | ||
![]() |
c5baa81977 | ||
![]() |
d24be38874 | ||
![]() |
1efffbbb78 | ||
![]() |
090d76a368 | ||
![]() |
d44a9375de | ||
![]() |
5a2a3893f9 | ||
![]() |
0a6b0dc785 | ||
![]() |
02e99726dc | ||
![]() |
843055d8df | ||
![]() |
f7fda5cb5e | ||
![]() |
e529642aec | ||
![]() |
c8e10d7043 | ||
![]() |
5f0de0e9a7 | ||
![]() |
e51638ce4b | ||
![]() |
0fbd6a606a | ||
![]() |
2fa543c3db | ||
![]() |
38e35c9695 | ||
![]() |
ad13a1a101 | ||
![]() |
cb9d98d027 | ||
![]() |
064c3f1572 | ||
![]() |
9e5320f9d4 | ||
![]() |
a1826d355d | ||
![]() |
32a3a094d1 | ||
![]() |
ee7dc2654b | ||
![]() |
64ffad3c0a | ||
![]() |
dcc62f078f | ||
![]() |
d920f05dbc |
@@ -714,6 +714,8 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
|
||||
metadata.GetType() == "tilesetResource" ||
|
||||
metadata.GetType() == "videoResource" ||
|
||||
metadata.GetType() == "model3DResource" ||
|
||||
metadata.GetType() == "atlasResource" ||
|
||||
metadata.GetType() == "spineResource" ||
|
||||
// Deprecated, old parameter names:
|
||||
metadata.GetType() == "password" || metadata.GetType() == "musicfile" ||
|
||||
metadata.GetType() == "soundfile" || metadata.GetType() == "police") {
|
||||
|
@@ -1281,8 +1281,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Enable an effect on the object"),
|
||||
_("Enable effect _PARAM1_ on _PARAM0_: _PARAM2_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("yesorno", _("Enable?"))
|
||||
@@ -1297,8 +1297,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("objectEffectParameterName", _("Property name"))
|
||||
@@ -1315,8 +1315,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("objectEffectParameterName", _("Property name"))
|
||||
@@ -1332,8 +1332,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM2_ for effect _PARAM1_ of _PARAM0_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.AddParameter("objectEffectParameterName", _("Property name"))
|
||||
@@ -1347,8 +1347,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Check if the effect on an object is enabled."),
|
||||
_("Effect _PARAM1_ of _PARAM0_ is enabled"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
.MarkAsSimple()
|
||||
|
@@ -27,7 +27,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Layers and cameras"))
|
||||
.SetIcon("res/conditions/camera24.png");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
|
||||
.SetIcon("res/actions/effect24.png");
|
||||
.SetIcon("res/actions/effect_black.svg");
|
||||
|
||||
extension
|
||||
.AddExpressionAndConditionAndAction(
|
||||
@@ -450,8 +450,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -469,8 +469,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -488,8 +488,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM3_ for effect _PARAM2_ of layer _PARAM1_: _PARAM4_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -504,8 +504,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
_("The effect on a layer is enabled"),
|
||||
_("Effect _PARAM2_ on layer _PARAM1_ is enabled"),
|
||||
_(""),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
@@ -518,8 +518,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
_("Enable an effect on a layer"),
|
||||
_("Enable effect _PARAM2_ on layer _PARAM1_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("layer", _("Layer"), "", true)
|
||||
.SetDefaultValue("\"\"")
|
||||
|
@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
|
||||
.SetIcon("res/actions/effect24.png");
|
||||
.SetIcon("res/actions/effect_black.svg");
|
||||
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
"EffectBehavior",
|
||||
@@ -32,7 +32,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"Effect",
|
||||
_("Apply visual effects to objects."),
|
||||
"",
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect_black.svg",
|
||||
"EffectBehavior",
|
||||
std::make_shared<gd::Behavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
@@ -43,8 +43,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
_("Enable an effect on the object"),
|
||||
_("Enable effect _PARAM2_ on _PARAM0_: _PARAM3_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -58,8 +58,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -75,8 +75,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -91,8 +91,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
"names) in the effects window."),
|
||||
_("Enable _PARAM3_ for effect _PARAM2_ of _PARAM0_: _PARAM4_"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
@@ -105,8 +105,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
|
||||
_("Check if the effect on an object is enabled."),
|
||||
_("Effect _PARAM2_ of _PARAM0_ is enabled"),
|
||||
_("Effects"),
|
||||
"res/actions/effect24.png",
|
||||
"res/actions/effect.png")
|
||||
"res/actions/effect_black.svg",
|
||||
"res/actions/effect_black.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
|
||||
.AddParameter("objectEffectName", _("Effect name"))
|
||||
|
@@ -24,7 +24,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsFlippableExtension(
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionHelpPath("/objects");
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Effects"))
|
||||
.SetIcon("res/actions/effect24.png");
|
||||
.SetIcon("res/actions/effect_black.svg");
|
||||
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
"FlippableBehavior",
|
||||
|
@@ -50,11 +50,11 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
|
||||
gd::ParameterMetadata::IsBehavior(type))
|
||||
// Prefix with the namespace if it's not already there.
|
||||
&& (supplementaryInformation.find(
|
||||
PlatformExtension::GetNamespaceSeparator()) != gd::String::npos)
|
||||
? supplementaryInformation
|
||||
: (supplementaryInformation.empty()
|
||||
PlatformExtension::GetNamespaceSeparator()) == gd::String::npos)
|
||||
? (supplementaryInformation.empty()
|
||||
? ""
|
||||
: extensionNamespace + supplementaryInformation)));
|
||||
: extensionNamespace + supplementaryInformation)
|
||||
: supplementaryInformation));
|
||||
|
||||
// TODO: Assert against supplementaryInformation === "emsc" (when running with
|
||||
// Emscripten), and warn about a missing argument when calling addParameter.
|
||||
|
@@ -68,11 +68,11 @@ InstructionMetadata& InstructionMetadata::AddParameter(
|
||||
gd::ParameterMetadata::IsBehavior(type))
|
||||
// Prefix with the namespace if it's not already there.
|
||||
&& (supplementaryInformation.find(
|
||||
PlatformExtension::GetNamespaceSeparator()) != gd::String::npos)
|
||||
? supplementaryInformation
|
||||
: (supplementaryInformation.empty()
|
||||
PlatformExtension::GetNamespaceSeparator()) == gd::String::npos)
|
||||
? (supplementaryInformation.empty()
|
||||
? ""
|
||||
: extensionNamespace + supplementaryInformation)));
|
||||
: extensionNamespace + supplementaryInformation)
|
||||
: supplementaryInformation));
|
||||
|
||||
// TODO: Assert against supplementaryInformation === "emsc" (when running with
|
||||
// Emscripten), and warn about a missing argument when calling addParameter.
|
||||
|
@@ -217,7 +217,9 @@ class GD_CORE_API ValueTypeMetadata {
|
||||
parameterType == "jsonResource" ||
|
||||
parameterType == "tilemapResource" ||
|
||||
parameterType == "tilesetResource" ||
|
||||
parameterType == "model3DResource";
|
||||
parameterType == "model3DResource" ||
|
||||
parameterType == "atlasResource" ||
|
||||
parameterType == "spineResource";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -39,7 +39,8 @@ struct VariableAndItsParent {
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Find the last parent (i.e: the variables container) of a node representing a variable.
|
||||
* \brief Find the last parent (i.e: the variables container) of a node
|
||||
* representing a variable.
|
||||
*
|
||||
* Useful for completions, to know which variables can be entered in a node.
|
||||
*
|
||||
@@ -48,13 +49,12 @@ struct VariableAndItsParent {
|
||||
class GD_CORE_API ExpressionVariableParentFinder
|
||||
: public ExpressionParser2NodeWorker {
|
||||
public:
|
||||
|
||||
static VariableAndItsParent GetLastParentOfNode(
|
||||
const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers,
|
||||
gd::ExpressionNode& node) {
|
||||
gd::ExpressionVariableParentFinder typeFinder(
|
||||
platform, projectScopedContainers);
|
||||
gd::ExpressionVariableParentFinder typeFinder(platform,
|
||||
projectScopedContainers);
|
||||
node.Visit(typeFinder);
|
||||
return typeFinder.variableAndItsParent;
|
||||
}
|
||||
@@ -70,7 +70,8 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
variableNode(nullptr),
|
||||
thisIsALegacyPrescopedVariable(false),
|
||||
bailOutBecauseEmptyVariableName(false),
|
||||
legacyPrescopedVariablesContainer(nullptr){};
|
||||
legacyPrescopedVariablesContainer(nullptr),
|
||||
variableAndItsParent{} {};
|
||||
|
||||
void OnVisitSubExpressionNode(SubExpressionNode& node) override {}
|
||||
void OnVisitOperatorNode(OperatorNode& node) override {}
|
||||
@@ -135,10 +136,10 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
}
|
||||
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
|
||||
if (node.name.empty() && node.child) {
|
||||
// A variable accessor should always have a name if it has a child (i.e: another accessor).
|
||||
// While the parser may have generated an empty name,
|
||||
// flag this so we avoid finding a wrong parent (and so, run the risk of giving
|
||||
// wrong autocompletions).
|
||||
// A variable accessor should always have a name if it has a child (i.e:
|
||||
// another accessor). While the parser may have generated an empty name,
|
||||
// flag this so we avoid finding a wrong parent (and so, run the risk of
|
||||
// giving wrong autocompletions).
|
||||
bailOutBecauseEmptyVariableName = true;
|
||||
}
|
||||
childVariableNames.insert(childVariableNames.begin(), node.name);
|
||||
@@ -300,7 +301,8 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
const std::vector<gd::String>& childVariableNames,
|
||||
size_t startIndex = 0) {
|
||||
if (bailOutBecauseEmptyVariableName)
|
||||
return {}; // Do not even attempt to find the parent if we had an issue when visiting nodes.
|
||||
return {}; // Do not even attempt to find the parent if we had an issue
|
||||
// when visiting nodes.
|
||||
|
||||
const gd::Variable* currentVariable = &variable;
|
||||
|
||||
@@ -332,8 +334,8 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
}
|
||||
}
|
||||
|
||||
// Return the last parent of the chain of variables (so not the last variable
|
||||
// but the one before it).
|
||||
// Return the last parent of the chain of variables (so not the last
|
||||
// variable but the one before it).
|
||||
return {.parentVariable = currentVariable};
|
||||
}
|
||||
|
||||
@@ -341,14 +343,16 @@ class GD_CORE_API ExpressionVariableParentFinder
|
||||
const gd::VariablesContainer& variablesContainer,
|
||||
const std::vector<gd::String>& childVariableNames) {
|
||||
if (bailOutBecauseEmptyVariableName)
|
||||
return {}; // Do not even attempt to find the parent if we had an issue when visiting nodes.
|
||||
return {}; // Do not even attempt to find the parent if we had an issue
|
||||
// when visiting nodes.
|
||||
if (childVariableNames.empty())
|
||||
return {}; // There is no "parent" to the variables container itself.
|
||||
|
||||
const gd::String& firstChildName = *childVariableNames.begin();
|
||||
|
||||
const gd::Variable* variable = variablesContainer.Has(firstChildName) ?
|
||||
&variablesContainer.Get(firstChildName) : nullptr;
|
||||
const gd::Variable* variable = variablesContainer.Has(firstChildName)
|
||||
? &variablesContainer.Get(firstChildName)
|
||||
: nullptr;
|
||||
if (childVariableNames.size() == 1 || !variable)
|
||||
return {// Only one child: the parent is the variables container itself.
|
||||
.parentVariablesContainer = &variablesContainer};
|
||||
|
222
Core/GDCore/IDE/ObjectAssetSerializer.cpp
Normal file
222
Core/GDCore/IDE/ObjectAssetSerializer.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "ObjectAssetSerializer.h"
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Project/AssetResourcePathCleaner.h"
|
||||
#include "GDCore/IDE/Project/ResourcesInUseHelper.h"
|
||||
#include "GDCore/IDE/Project/ResourcesRenamer.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/CustomBehavior.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Log.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
gd::String
|
||||
ObjectAssetSerializer::GetObjectExtensionName(const gd::Object &object) {
|
||||
const gd::String &type = object.GetType();
|
||||
const auto separatorIndex =
|
||||
type.find(PlatformExtension::GetNamespaceSeparator());
|
||||
return separatorIndex != std::string::npos ? type.substr(0, separatorIndex)
|
||||
: "";
|
||||
}
|
||||
|
||||
void ObjectAssetSerializer::SerializeTo(
|
||||
gd::Project &project, const gd::Object &object,
|
||||
const gd::String &objectFullName, SerializerElement &element,
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap) {
|
||||
auto cleanObject = object.Clone();
|
||||
cleanObject->GetVariables().Clear();
|
||||
cleanObject->GetEffects().Clear();
|
||||
for (auto &&behaviorName : cleanObject->GetAllBehaviorNames()) {
|
||||
cleanObject->RemoveBehavior(behaviorName);
|
||||
}
|
||||
|
||||
gd::String extensionName = GetObjectExtensionName(*cleanObject);
|
||||
|
||||
std::map<gd::String, gd::String> resourcesNameReverseMap;
|
||||
gd::ObjectAssetSerializer::RenameObjectResourceFiles(
|
||||
project, *cleanObject, "", objectFullName, resourcesFileNameMap,
|
||||
resourcesNameReverseMap);
|
||||
|
||||
element.SetAttribute("id", "");
|
||||
element.SetAttribute("name", "");
|
||||
element.SetAttribute("license", "");
|
||||
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
|
||||
auto &extension = project.GetEventsFunctionsExtension(extensionName);
|
||||
element.SetAttribute("description", extension.GetShortDescription());
|
||||
}
|
||||
element.SetAttribute("gdevelopVersion", "");
|
||||
element.SetAttribute("version", "");
|
||||
element.SetIntAttribute("animationsCount", 1);
|
||||
element.SetIntAttribute("maxFramesCount", 1);
|
||||
// TODO Find the right object dimensions.
|
||||
element.SetIntAttribute("width", 0);
|
||||
element.SetIntAttribute("height", 0);
|
||||
SerializerElement &authorsElement = element.AddChild("authors");
|
||||
authorsElement.ConsiderAsArrayOf("author");
|
||||
SerializerElement &tagsElement = element.AddChild("tags");
|
||||
tagsElement.ConsiderAsArrayOf("tag");
|
||||
|
||||
SerializerElement &objectAssetsElement = element.AddChild("objectAssets");
|
||||
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
|
||||
SerializerElement &objectAssetElement =
|
||||
objectAssetsElement.AddChild("objectAsset");
|
||||
|
||||
cleanObject->SerializeTo(objectAssetElement.AddChild("object"));
|
||||
|
||||
SerializerElement &resourcesElement =
|
||||
objectAssetElement.AddChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
auto &resourcesManager = project.GetResourcesManager();
|
||||
gd::ResourcesInUseHelper resourcesInUse(resourcesManager);
|
||||
cleanObject->GetConfiguration().ExposeResources(resourcesInUse);
|
||||
for (auto &&newResourceName : resourcesInUse.GetAllResources()) {
|
||||
if (newResourceName.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
auto &resource = resourcesManager.GetResource(
|
||||
resourcesNameReverseMap.find(newResourceName) !=
|
||||
resourcesNameReverseMap.end()
|
||||
? resourcesNameReverseMap[newResourceName]
|
||||
: newResourceName);
|
||||
SerializerElement &resourceElement = resourcesElement.AddChild("resource");
|
||||
resource.SerializeTo(resourceElement);
|
||||
// Override name and file because the project and the asset don't use the
|
||||
// same one.
|
||||
resourceElement.SetAttribute("kind", resource.GetKind());
|
||||
resourceElement.SetAttribute("name", newResourceName);
|
||||
auto &oldFilePath = resource.GetFile();
|
||||
resourceElement.SetAttribute("file",
|
||||
resourcesFileNameMap.find(oldFilePath) !=
|
||||
resourcesFileNameMap.end()
|
||||
? resourcesFileNameMap[oldFilePath]
|
||||
: oldFilePath);
|
||||
}
|
||||
|
||||
SerializerElement &requiredExtensionsElement =
|
||||
objectAssetElement.AddChild("requiredExtensions");
|
||||
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
|
||||
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
|
||||
SerializerElement &requiredExtensionElement =
|
||||
requiredExtensionsElement.AddChild("requiredExtension");
|
||||
requiredExtensionElement.SetAttribute("extensionName", extensionName);
|
||||
requiredExtensionElement.SetAttribute("extensionVersion", "1.0.0");
|
||||
}
|
||||
|
||||
// TODO This can be removed when the asset script no longer require it.
|
||||
SerializerElement &customizationElement =
|
||||
objectAssetElement.AddChild("customization");
|
||||
customizationElement.ConsiderAsArrayOf("empty");
|
||||
}
|
||||
|
||||
void ObjectAssetSerializer::RenameObjectResourceFiles(
|
||||
gd::Project &project, gd::Object &object,
|
||||
const gd::String &destinationDirectory, const gd::String &objectFullName,
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap,
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap) {
|
||||
gd::AssetResourcePathCleaner assetResourcePathCleaner(
|
||||
project.GetResourcesManager(), resourcesFileNameMap,
|
||||
resourcesNameReverseMap);
|
||||
object.GetConfiguration().ExposeResources(assetResourcePathCleaner);
|
||||
|
||||
// Use asset store script naming conventions for sprite resource files.
|
||||
if (object.GetConfiguration().GetType() == "Sprite") {
|
||||
gd::SpriteObject &spriteConfiguration =
|
||||
dynamic_cast<gd::SpriteObject &>(object.GetConfiguration());
|
||||
std::map<gd::String, gd::String> normalizedFileNames;
|
||||
|
||||
for (std::size_t animationIndex = 0;
|
||||
animationIndex < spriteConfiguration.GetAnimationsCount();
|
||||
animationIndex++) {
|
||||
auto &animation = spriteConfiguration.GetAnimation(animationIndex);
|
||||
auto &direction = animation.GetDirection(0);
|
||||
|
||||
const gd::String &animationName =
|
||||
animation.GetName().empty()
|
||||
? gd::String::From(animationIndex)
|
||||
: animation.GetName().FindAndReplace("_", " ", true);
|
||||
|
||||
// Search frames that share the same resource.
|
||||
std::map<gd::String, std::vector<int>> frameIndexes;
|
||||
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
|
||||
frameIndex++) {
|
||||
auto &frame = direction.GetSprite(frameIndex);
|
||||
|
||||
if (frameIndexes.find(frame.GetImageName()) == frameIndexes.end()) {
|
||||
std::vector<int> emptyVector;
|
||||
frameIndexes[frame.GetImageName()] = emptyVector;
|
||||
}
|
||||
auto &indexes = frameIndexes[frame.GetImageName()];
|
||||
indexes.push_back(frameIndex);
|
||||
}
|
||||
|
||||
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
|
||||
frameIndex++) {
|
||||
auto &frame = direction.GetSprite(frameIndex);
|
||||
auto oldName = frame.GetImageName();
|
||||
|
||||
if (normalizedFileNames.find(oldName) != normalizedFileNames.end()) {
|
||||
gd::LogWarning("The resource \"" + oldName +
|
||||
"\" is shared by several animations.");
|
||||
continue;
|
||||
}
|
||||
|
||||
gd::String newName = objectFullName;
|
||||
if (spriteConfiguration.GetAnimationsCount() > 1) {
|
||||
newName += "_" + animationName;
|
||||
}
|
||||
if (direction.GetSpritesCount() > 1) {
|
||||
newName += "_";
|
||||
auto &indexes = frameIndexes[frame.GetImageName()];
|
||||
for (size_t i = 0; i < indexes.size(); i++) {
|
||||
newName += gd::String::From(indexes.at(i) + 1);
|
||||
if (i < indexes.size() - 1) {
|
||||
newName += ";";
|
||||
}
|
||||
}
|
||||
}
|
||||
gd::String extension = oldName.substr(oldName.find_last_of("."));
|
||||
newName += extension;
|
||||
|
||||
frame.SetImageName(newName);
|
||||
normalizedFileNames[oldName] = newName;
|
||||
}
|
||||
}
|
||||
for (std::map<gd::String, gd::String>::const_iterator it =
|
||||
resourcesFileNameMap.begin();
|
||||
it != resourcesFileNameMap.end(); ++it) {
|
||||
if (!it->first.empty()) {
|
||||
gd::String originFile = it->first;
|
||||
gd::String destinationFile = it->second;
|
||||
resourcesFileNameMap[originFile] = normalizedFileNames[destinationFile];
|
||||
}
|
||||
}
|
||||
auto clonedResourcesNameReverseMap = resourcesNameReverseMap;
|
||||
resourcesNameReverseMap.clear();
|
||||
for (std::map<gd::String, gd::String>::const_iterator it =
|
||||
clonedResourcesNameReverseMap.begin();
|
||||
it != clonedResourcesNameReverseMap.end(); ++it) {
|
||||
if (!it->first.empty()) {
|
||||
gd::String newResourceName = it->first;
|
||||
gd::String oldResourceName = it->second;
|
||||
resourcesNameReverseMap[normalizedFileNames[newResourceName]] =
|
||||
oldResourceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace gd
|
64
Core/GDCore/IDE/ObjectAssetSerializer.h
Normal file
64
Core/GDCore/IDE/ObjectAssetSerializer.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class Object;
|
||||
class ExtensionDependency;
|
||||
class PropertyDescriptor;
|
||||
class Project;
|
||||
class Layout;
|
||||
class ArbitraryResourceWorker;
|
||||
class InitialInstance;
|
||||
class SerializerElement;
|
||||
class EffectsContainer;
|
||||
class AbstractFileSystem;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief Serialize objects into an asset for the store.
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API ObjectAssetSerializer {
|
||||
public:
|
||||
/**
|
||||
* \brief Serialize an object into an asset.
|
||||
*
|
||||
* \param project The project that contains the object and its resources.
|
||||
* It's not actually modified.
|
||||
* \param object The object to serialize as an asset.
|
||||
* \param objectFullName The object name with spaces instead of PascalCase.
|
||||
* \param element The element where the asset is serialize.
|
||||
* \param resourcesFileNameMap The map from project resource file paths to
|
||||
* asset resource file paths.
|
||||
*/
|
||||
static void
|
||||
SerializeTo(gd::Project &project, const gd::Object &object,
|
||||
const gd::String &objectFullName, SerializerElement &element,
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap);
|
||||
|
||||
~ObjectAssetSerializer(){};
|
||||
|
||||
private:
|
||||
ObjectAssetSerializer(){};
|
||||
|
||||
static void RenameObjectResourceFiles(
|
||||
gd::Project &project, gd::Object &object,
|
||||
const gd::String &destinationDirectory, const gd::String &objectFullName,
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap,
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap);
|
||||
|
||||
static gd::String GetObjectExtensionName(const gd::Object &object);
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -52,6 +52,16 @@ void ArbitraryResourceWorker::ExposeModel3D(gd::String& resourceName){
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeAtlas(gd::String& resourceName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeSpine(gd::String& resourceName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeVideo(gd::String& videoName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
@@ -120,6 +130,7 @@ void ArbitraryResourceWorker::ExposeEmbeddeds(gd::String& resourceName) {
|
||||
|
||||
gd::String potentiallyUpdatedTargetResourceName = targetResourceName;
|
||||
ExposeResourceWithType(targetResource.GetKind(), potentiallyUpdatedTargetResourceName);
|
||||
ExposeEmbeddeds(potentiallyUpdatedTargetResourceName);
|
||||
|
||||
if (potentiallyUpdatedTargetResourceName != targetResourceName) {
|
||||
// The resource name was renamed. Also update the mapping.
|
||||
@@ -176,6 +187,14 @@ void ArbitraryResourceWorker::ExposeResourceWithType(
|
||||
ExposeVideo(resourceName);
|
||||
return;
|
||||
}
|
||||
if (resourceType == "atlas") {
|
||||
ExposeAtlas(resourceName);
|
||||
return;
|
||||
}
|
||||
if (resourceType == "spine") {
|
||||
ExposeSpine(resourceName);
|
||||
return;
|
||||
}
|
||||
gd::LogError("Unexpected resource type: " + resourceType + " for: " + resourceName);
|
||||
return;
|
||||
}
|
||||
@@ -244,6 +263,14 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
|
||||
gd::String updatedParameterValue = parameterValue;
|
||||
worker.ExposeModel3D(updatedParameterValue);
|
||||
instruction.SetParameter(parameterIndex, updatedParameterValue);
|
||||
} else if (parameterMetadata.GetType() == "atlasResource") {
|
||||
gd::String updatedParameterValue = parameterValue;
|
||||
worker.ExposeAtlas(updatedParameterValue);
|
||||
instruction.SetParameter(parameterIndex, updatedParameterValue);
|
||||
} else if (parameterMetadata.GetType() == "spineResource") {
|
||||
gd::String updatedParameterValue = parameterValue;
|
||||
worker.ExposeSpine(updatedParameterValue);
|
||||
instruction.SetParameter(parameterIndex, updatedParameterValue);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -96,6 +96,16 @@ public:
|
||||
* \brief Expose a 3D model, which is always a reference to a "model3D" resource.
|
||||
*/
|
||||
virtual void ExposeModel3D(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* \brief Expose an atlas, which is always a reference to a "atlas" resource.
|
||||
*/
|
||||
virtual void ExposeAtlas(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* \brief Expose an spine, which is always a reference to a "spine" resource.
|
||||
*/
|
||||
virtual void ExposeSpine(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* \brief Expose a video, which is always a reference to a "video" resource.
|
||||
@@ -123,14 +133,15 @@ public:
|
||||
*/
|
||||
virtual void ExposeEmbeddeds(gd::String &resourceName);
|
||||
|
||||
protected:
|
||||
gd::ResourcesManager * resourcesManager;
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Expose a resource: resources that have a file are
|
||||
* exposed as file (see ExposeFile).
|
||||
*/
|
||||
void ExposeResource(gd::Resource &resource);
|
||||
|
||||
gd::ResourcesManager * resourcesManager;
|
||||
};
|
||||
|
||||
/**
|
||||
|
74
Core/GDCore/IDE/Project/AssetResourcePathCleaner.cpp
Normal file
74
Core/GDCore/IDE/Project/AssetResourcePathCleaner.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "AssetResourcePathCleaner.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/ResourcesManager.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
void AssetResourcePathCleaner::ExposeImage(gd::String &imageName) {
|
||||
ExposeResourceAsFile(imageName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeAudio(gd::String &audioName) {
|
||||
ExposeResourceAsFile(audioName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeFont(gd::String &fontName) {
|
||||
ExposeResourceAsFile(fontName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeJson(gd::String &jsonName) {
|
||||
ExposeResourceAsFile(jsonName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeTilemap(gd::String &tilemapName) {
|
||||
ExposeResourceAsFile(tilemapName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeTileset(gd::String &tilesetName) {
|
||||
ExposeResourceAsFile(tilesetName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeVideo(gd::String &videoName) {
|
||||
ExposeResourceAsFile(videoName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeBitmapFont(gd::String &bitmapFontName) {
|
||||
ExposeResourceAsFile(bitmapFontName);
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeResourceAsFile(gd::String &resourceName) {
|
||||
|
||||
auto &resource = resourcesManager->GetResource(resourceName);
|
||||
gd::String file = resource.GetFile();
|
||||
ExposeFile(file);
|
||||
|
||||
resourcesNameReverseMap[file] = resourceName;
|
||||
resourceName = file;
|
||||
}
|
||||
|
||||
void AssetResourcePathCleaner::ExposeFile(gd::String &resourceFilePath) {
|
||||
|
||||
size_t slashPos = resourceFilePath.find_last_of("/");
|
||||
size_t antiSlashPos = resourceFilePath.find_last_of("\\");
|
||||
size_t baseNamePos =
|
||||
slashPos == String::npos
|
||||
? antiSlashPos == String::npos ? 0 : (antiSlashPos + 1)
|
||||
: antiSlashPos == String::npos ? (slashPos + 1)
|
||||
: slashPos > antiSlashPos ? (slashPos + 1)
|
||||
: (antiSlashPos + 1);
|
||||
gd::String baseName =
|
||||
baseNamePos != 0
|
||||
? resourceFilePath.substr(baseNamePos, resourceFilePath.length())
|
||||
: resourceFilePath;
|
||||
|
||||
resourcesFileNameMap[resourceFilePath] = baseName;
|
||||
resourceFilePath = baseName;
|
||||
}
|
||||
|
||||
} // namespace gd
|
65
Core/GDCore/IDE/Project/AssetResourcePathCleaner.h
Normal file
65
Core/GDCore/IDE/Project/AssetResourcePathCleaner.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
|
||||
#include "GDCore/String.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace gd {
|
||||
class AbstractFileSystem;
|
||||
class Project;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief AssetResourcePathCleaner is used when exporting an object as an asset.
|
||||
* It removes the folder from the path.
|
||||
*
|
||||
* \see ArbitraryResourceWorker
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API AssetResourcePathCleaner : public ArbitraryResourceWorker {
|
||||
public:
|
||||
AssetResourcePathCleaner(
|
||||
gd::ResourcesManager &resourcesManager,
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap_,
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap_)
|
||||
: ArbitraryResourceWorker(resourcesManager),
|
||||
resourcesFileNameMap(resourcesFileNameMap_),
|
||||
resourcesNameReverseMap(resourcesNameReverseMap_){};
|
||||
virtual ~AssetResourcePathCleaner(){};
|
||||
|
||||
void ExposeImage(gd::String &imageName) override;
|
||||
void ExposeAudio(gd::String &audioName) override;
|
||||
void ExposeFont(gd::String &fontName) override;
|
||||
void ExposeJson(gd::String &jsonName) override;
|
||||
void ExposeTilemap(gd::String &tilemapName) override;
|
||||
void ExposeTileset(gd::String &tilesetName) override;
|
||||
void ExposeVideo(gd::String &videoName) override;
|
||||
void ExposeBitmapFont(gd::String &bitmapFontName) override;
|
||||
void ExposeFile(gd::String &resource) override;
|
||||
|
||||
protected:
|
||||
void ExposeResourceAsFile(gd::String &resourceName);
|
||||
|
||||
/**
|
||||
* New file names that can be accessed by their original name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> &resourcesFileNameMap;
|
||||
|
||||
/**
|
||||
* Original resource names that can be accessed by their new name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap;
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -79,6 +79,12 @@ public:
|
||||
virtual void ExposeModel3D(gd::String& otherResourceName) override {
|
||||
MatchResourceName(otherResourceName);
|
||||
};
|
||||
virtual void ExposeAtlas(gd::String& otherResourceName) override {
|
||||
MatchResourceName(otherResourceName);
|
||||
};
|
||||
virtual void ExposeSpine(gd::String& otherResourceName) override {
|
||||
MatchResourceName(otherResourceName);
|
||||
};
|
||||
|
||||
void MatchResourceName(gd::String& otherResourceName) {
|
||||
if (otherResourceName == resourceName) matchesResourceName = true;
|
||||
|
@@ -3,9 +3,10 @@
|
||||
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef PROJECTRESOURCESCOPIER_H
|
||||
#define PROJECTRESOURCESCOPIER_H
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class Project;
|
||||
class AbstractFileSystem;
|
||||
@@ -47,6 +48,7 @@ class GD_CORE_API ProjectResourcesCopier {
|
||||
bool updateOriginalProject,
|
||||
bool preserveAbsoluteFilenames = true,
|
||||
bool preserveDirectoryStructure = true);
|
||||
|
||||
private:
|
||||
static bool CopyAllResourcesTo(gd::Project& originalProject,
|
||||
gd::Project& clonedProject,
|
||||
@@ -57,5 +59,3 @@ private:
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // PROJECTRESOURCESCOPIER_H
|
||||
|
25
Core/GDCore/IDE/Project/ResourcesInUseHelper.cpp
Normal file
25
Core/GDCore/IDE/Project/ResourcesInUseHelper.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "ResourcesInUseHelper.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
const std::vector<gd::String> ResourcesInUseHelper::resourceTypes = {
|
||||
"image", "audio", "font", "json", "tilemap",
|
||||
"tileset", "video", "bitmapFont", "model3D"};
|
||||
|
||||
const std::vector<gd::String> &ResourcesInUseHelper::GetAllResources() {
|
||||
allResources.clear();
|
||||
for (auto &&resourceType : gd::ResourcesInUseHelper::resourceTypes) {
|
||||
for (auto &&resourceName : GetAll(resourceType)) {
|
||||
allResources.push_back(resourceName);
|
||||
}
|
||||
}
|
||||
return allResources;
|
||||
}
|
||||
|
||||
} // namespace gd
|
@@ -4,9 +4,7 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#ifndef IMAGESUSEDINVENTORIZER_H
|
||||
#define IMAGESUSEDINVENTORIZER_H
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
@@ -38,6 +36,7 @@ public:
|
||||
: gd::ArbitraryResourceWorker(resourcesManager){};
|
||||
virtual ~ResourcesInUseHelper(){};
|
||||
|
||||
const std::vector<gd::String>& GetAllResources();
|
||||
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
|
||||
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
@@ -47,6 +46,8 @@ public:
|
||||
std::set<gd::String>& GetAllVideos() { return GetAll("video"); };
|
||||
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
|
||||
std::set<gd::String>& GetAll3DModels() { return GetAll("model3D"); };
|
||||
std::set<gd::String>& GetAllAtlases() { return GetAll("atlas"); };
|
||||
std::set<gd::String>& GetAllSpines() { return GetAll("spine"); };
|
||||
std::set<gd::String>& GetAll(const gd::String& resourceType) {
|
||||
if (resourceType == "image") return allImages;
|
||||
if (resourceType == "audio") return allAudios;
|
||||
@@ -57,6 +58,8 @@ public:
|
||||
if (resourceType == "video") return allVideos;
|
||||
if (resourceType == "bitmapFont") return allBitmapFonts;
|
||||
if (resourceType == "model3D") return allModel3Ds;
|
||||
if (resourceType == "atlas") return allAtlases;
|
||||
if (resourceType == "spine") return allSpines;
|
||||
|
||||
return emptyResources;
|
||||
};
|
||||
@@ -64,35 +67,42 @@ public:
|
||||
virtual void ExposeFile(gd::String& resource) override{
|
||||
/*Don't care, we just list resource names*/
|
||||
};
|
||||
virtual void ExposeImage(gd::String& imageResourceName) override {
|
||||
allImages.insert(imageResourceName);
|
||||
virtual void ExposeImage(gd::String& resourceName) override {
|
||||
allImages.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeAudio(gd::String& audioResourceName) override {
|
||||
allAudios.insert(audioResourceName);
|
||||
virtual void ExposeAudio(gd::String& resourceName) override {
|
||||
allAudios.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
allFonts.insert(fontResourceName);
|
||||
virtual void ExposeFont(gd::String& resourceName) override {
|
||||
allFonts.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeJson(gd::String& jsonResourceName) override {
|
||||
allJsons.insert(jsonResourceName);
|
||||
virtual void ExposeJson(gd::String& resourceName) override {
|
||||
allJsons.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeTilemap(gd::String& tilemapResourceName) override {
|
||||
allTilemaps.insert(tilemapResourceName);
|
||||
virtual void ExposeTilemap(gd::String& resourceName) override {
|
||||
allTilemaps.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeTileset(gd::String& tilesetResourceName) override {
|
||||
allTilesets.insert(tilesetResourceName);
|
||||
virtual void ExposeTileset(gd::String& resourceName) override {
|
||||
allTilesets.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeVideo(gd::String& videoResourceName) override {
|
||||
allVideos.insert(videoResourceName);
|
||||
virtual void ExposeVideo(gd::String& resourceName) override {
|
||||
allVideos.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
|
||||
allBitmapFonts.insert(bitmapFontResourceName);
|
||||
virtual void ExposeBitmapFont(gd::String& resourceName) override {
|
||||
allBitmapFonts.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeModel3D(gd::String& resourceName) override {
|
||||
allModel3Ds.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeAtlas(gd::String& resourceName) override {
|
||||
allAtlases.insert(resourceName);
|
||||
};
|
||||
virtual void ExposeSpine(gd::String& resourceName) override {
|
||||
allSpines.insert(resourceName);
|
||||
};
|
||||
|
||||
protected:
|
||||
std::vector<gd::String> allResources;
|
||||
std::set<gd::String> allImages;
|
||||
std::set<gd::String> allAudios;
|
||||
std::set<gd::String> allFonts;
|
||||
@@ -102,10 +112,11 @@ public:
|
||||
std::set<gd::String> allVideos;
|
||||
std::set<gd::String> allBitmapFonts;
|
||||
std::set<gd::String> allModel3Ds;
|
||||
std::set<gd::String> allAtlases;
|
||||
std::set<gd::String> allSpines;
|
||||
std::set<gd::String> emptyResources;
|
||||
|
||||
static const std::vector<gd::String> resourceTypes;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // IMAGESUSEDINVENTORIZER_H
|
||||
#endif
|
||||
|
@@ -28,7 +28,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
auto stripToFilenameOnly = [&]() {
|
||||
fs.MakeAbsolute(resourceFullFilename, baseDirectory);
|
||||
SetNewFilename(resourceFullFilename, fs.FileNameFrom(resourceFullFilename));
|
||||
resourceFilename = oldFilenames[resourceFullFilename];
|
||||
resourceFilename = newFilenames[resourceFullFilename];
|
||||
};
|
||||
|
||||
// if we do not want to preserve the folders at all,
|
||||
@@ -45,7 +45,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
gd::String relativeFilename = resourceFullFilename;
|
||||
if (fs.MakeRelative(relativeFilename, baseDirectory)) {
|
||||
SetNewFilename(resourceFullFilename, relativeFilename);
|
||||
resourceFilename = oldFilenames[resourceFullFilename];
|
||||
resourceFilename = newFilenames[resourceFullFilename];
|
||||
} else {
|
||||
// The filename cannot be made relative. Consider that it is absolute.
|
||||
// Just strip the filename to its file part
|
||||
@@ -63,7 +63,7 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
|
||||
void ResourcesMergingHelper::SetNewFilename(gd::String oldFilename,
|
||||
gd::String newFilename) {
|
||||
if (oldFilenames.find(oldFilename) != oldFilenames.end()) return;
|
||||
if (newFilenames.find(oldFilename) != newFilenames.end()) return;
|
||||
|
||||
// Extract baseName and extension from the new filename
|
||||
size_t extensionPos = newFilename.find_last_of(".");
|
||||
@@ -80,13 +80,13 @@ void ResourcesMergingHelper::SetNewFilename(gd::String oldFilename,
|
||||
gd::NewNameGenerator::Generate(
|
||||
baseName,
|
||||
[this, extension](const gd::String& newBaseName) {
|
||||
return newFilenames.find(newBaseName + extension) !=
|
||||
newFilenames.end();
|
||||
return oldFilenames.find(newBaseName + extension) !=
|
||||
oldFilenames.end();
|
||||
}) +
|
||||
extension;
|
||||
|
||||
oldFilenames[oldFilename] = finalFilename;
|
||||
newFilenames[finalFilename] = oldFilename;
|
||||
newFilenames[oldFilename] = finalFilename;
|
||||
oldFilenames[finalFilename] = oldFilename;
|
||||
}
|
||||
|
||||
void ResourcesMergingHelper::SetBaseDirectory(
|
||||
|
@@ -64,19 +64,25 @@ public:
|
||||
* the Base Directory.
|
||||
*/
|
||||
std::map<gd::String, gd::String>& GetAllResourcesOldAndNewFilename() {
|
||||
return oldFilenames;
|
||||
return newFilenames;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resources merging helper collects all resources filenames and update these
|
||||
* filenames.
|
||||
*/
|
||||
virtual void ExposeFile(gd::String& resource) override;
|
||||
void ExposeFile(gd::String& resource) override;
|
||||
|
||||
protected:
|
||||
void SetNewFilename(gd::String oldFilename, gd::String newFilename);
|
||||
|
||||
/**
|
||||
* Original file names that can be accessed by their new name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> oldFilenames;
|
||||
/**
|
||||
* New file names that can be accessed by their original name.
|
||||
*/
|
||||
std::map<gd::String, gd::String> newFilenames;
|
||||
gd::String baseDirectory;
|
||||
bool preserveDirectoriesStructure; ///< If set to true, the directory
|
||||
|
@@ -65,6 +65,12 @@ class ResourcesRenamer : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeModel3D(gd::String& resourceName) override {
|
||||
RenameIfNeeded(resourceName);
|
||||
};
|
||||
virtual void ExposeAtlas(gd::String& resourceName) override {
|
||||
RenameIfNeeded(resourceName);
|
||||
};
|
||||
virtual void ExposeSpine(gd::String& resourceName) override {
|
||||
RenameIfNeeded(resourceName);
|
||||
};
|
||||
|
||||
private:
|
||||
void RenameIfNeeded(gd::String& resourceName) {
|
||||
|
@@ -80,6 +80,12 @@ private:
|
||||
void ExposeModel3D(gd::String &resourceName) override {
|
||||
AddUsedResource(resourceName);
|
||||
};
|
||||
void ExposeAtlas(gd::String &resourceName) override {
|
||||
AddUsedResource(resourceName);
|
||||
};
|
||||
void ExposeSpine(gd::String &resourceName) override {
|
||||
AddUsedResource(resourceName);
|
||||
};
|
||||
|
||||
std::set<gd::String> resourceNames;
|
||||
};
|
||||
|
@@ -16,8 +16,8 @@
|
||||
#include "GDCore/IDE/Events/BehaviorTypeRenamer.h"
|
||||
#include "GDCore/IDE/Events/CustomObjectTypeRenamer.h"
|
||||
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
|
||||
#include "GDCore/IDE/Events/EventsRefactorer.h"
|
||||
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
|
||||
#include "GDCore/IDE/Events/EventsRefactorer.h"
|
||||
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
|
||||
#include "GDCore/IDE/Events/ExpressionsParameterMover.h"
|
||||
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
|
||||
@@ -138,7 +138,8 @@ void WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
}
|
||||
}
|
||||
|
||||
VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
|
||||
VariablesChangeset
|
||||
WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
|
||||
gd::Project &project,
|
||||
const gd::SerializerElement &oldSerializedVariablesContainer,
|
||||
const gd::VariablesContainer &newVariablesContainer) {
|
||||
@@ -149,9 +150,9 @@ VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer
|
||||
|
||||
if (oldVariablesContainer.GetPersistentUuid() !=
|
||||
newVariablesContainer.GetPersistentUuid()) {
|
||||
gd::LogWarning(
|
||||
_("Called ComputeChangesetForVariablesContainer on variables containers "
|
||||
"that are different - they can't be compared."));
|
||||
gd::LogWarning(_(
|
||||
"Called ComputeChangesetForVariablesContainer on variables containers "
|
||||
"that are different - they can't be compared."));
|
||||
return changeset;
|
||||
}
|
||||
|
||||
@@ -192,14 +193,11 @@ VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
|
||||
gd::Project &project,
|
||||
const gd::VariablesContainer &newVariablesContainer,
|
||||
const gd::VariablesChangeset& changeset) {
|
||||
gd::Project &project, const gd::VariablesContainer &newVariablesContainer,
|
||||
const gd::VariablesChangeset &changeset) {
|
||||
gd::EventsVariableReplacer eventsVariableReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
newVariablesContainer,
|
||||
changeset.oldToNewVariableNames,
|
||||
changeset.removedVariableNames);
|
||||
project.GetCurrentPlatform(), newVariablesContainer,
|
||||
changeset.oldToNewVariableNames, changeset.removedVariableNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsVariableReplacer);
|
||||
}
|
||||
@@ -743,14 +741,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
|
||||
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
|
||||
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
|
||||
{oldPropertyName, newPropertyName}};
|
||||
std::unordered_set<gd::String> removedPropertyNames;
|
||||
gd::EventsPropertyReplacer eventsPropertyReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
properties,
|
||||
oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
|
||||
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsPropertyReplacer);
|
||||
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
@@ -813,14 +811,14 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
|
||||
EventsBasedBehavior::GetSharedPropertyExpressionName(newPropertyName));
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
|
||||
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
|
||||
{oldPropertyName, newPropertyName}};
|
||||
std::unordered_set<gd::String> removedPropertyNames;
|
||||
gd::EventsPropertyReplacer eventsPropertyReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
properties,
|
||||
oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
|
||||
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsPropertyReplacer);
|
||||
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
@@ -870,14 +868,14 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
|
||||
EventsBasedObject::GetPropertyExpressionName(newPropertyName));
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
|
||||
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
|
||||
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
|
||||
{oldPropertyName, newPropertyName}};
|
||||
std::unordered_set<gd::String> removedPropertyNames;
|
||||
gd::EventsPropertyReplacer eventsPropertyReplacer(
|
||||
project.GetCurrentPlatform(),
|
||||
properties,
|
||||
oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
|
||||
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
|
||||
removedPropertyNames);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
|
||||
eventsPropertyReplacer);
|
||||
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
@@ -1351,7 +1349,6 @@ void WholeProjectRefactorer::DoRenameBehavior(
|
||||
gd::Project &project, const gd::String &oldBehaviorType,
|
||||
const gd::String &newBehaviorType,
|
||||
const gd::ProjectBrowser &projectBrowser) {
|
||||
|
||||
// Rename behavior in required behavior properties
|
||||
auto requiredBehaviorRenamer =
|
||||
gd::RequiredBehaviorRenamer(oldBehaviorType, newBehaviorType);
|
||||
@@ -1378,7 +1375,6 @@ void WholeProjectRefactorer::DoRenameBehavior(
|
||||
void WholeProjectRefactorer::DoRenameObject(
|
||||
gd::Project &project, const gd::String &oldObjectType,
|
||||
const gd::String &newObjectType, const gd::ProjectBrowser &projectBrowser) {
|
||||
|
||||
// Rename object type in objects lists.
|
||||
auto customObjectTypeRenamer =
|
||||
gd::CustomObjectTypeRenamer(oldObjectType, newObjectType);
|
||||
@@ -1398,7 +1394,8 @@ void WholeProjectRefactorer::DoRenameObject(
|
||||
void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
|
||||
gd::Project &project, gd::Layout &layout, const gd::String &objectName,
|
||||
bool isObjectGroup, bool removeEventsAndGroups) {
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
|
||||
// Remove object in the current layout
|
||||
if (removeEventsAndGroups) {
|
||||
@@ -1447,7 +1444,8 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
|
||||
if (oldName == newName || newName.empty() || oldName.empty())
|
||||
return;
|
||||
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::
|
||||
MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
|
||||
|
||||
// Rename object in the current layout
|
||||
gd::EventsRefactorer::RenameObjectInEvents(
|
||||
@@ -1536,10 +1534,19 @@ void WholeProjectRefactorer::RenameLayer(gd::Project &project,
|
||||
const gd::String &newName) {
|
||||
if (oldName == newName || newName.empty() || oldName.empty())
|
||||
return;
|
||||
|
||||
gd::ProjectElementRenamer projectElementRenamer(project.GetCurrentPlatform(),
|
||||
"layer", oldName, newName);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
layout.GetInitialInstances().MoveInstancesToLayer(oldName, newName);
|
||||
|
||||
std::vector<gd::String> externalLayoutsNames =
|
||||
GetAssociatedExternalLayouts(project, layout);
|
||||
for (gd::String name : externalLayoutsNames) {
|
||||
auto &externalLayout = project.GetExternalLayout(name);
|
||||
externalLayout.GetInitialInstances().MoveInstancesToLayer(oldName, newName);
|
||||
}
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
|
||||
@@ -1552,8 +1559,8 @@ void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "layerEffectName", oldName, newName);
|
||||
projectElementRenamer.SetLayerConstraint(layer.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
|
||||
@@ -1566,8 +1573,8 @@ void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "objectAnimationName", oldName, newName);
|
||||
projectElementRenamer.SetObjectConstraint(object.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
|
||||
@@ -1580,8 +1587,8 @@ void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "objectPointName", oldName, newName);
|
||||
projectElementRenamer.SetObjectConstraint(object.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
|
||||
@@ -1594,8 +1601,8 @@ void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
|
||||
gd::ProjectElementRenamer projectElementRenamer(
|
||||
project.GetCurrentPlatform(), "objectEffectName", oldName, newName);
|
||||
projectElementRenamer.SetObjectConstraint(object.GetName());
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
|
||||
projectElementRenamer);
|
||||
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
|
||||
project, layout, projectElementRenamer);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsBasedObject(
|
||||
@@ -1617,9 +1624,12 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsFunction(
|
||||
gd::ObjectsContainer &globalObjectsContainer,
|
||||
gd::ObjectsContainer &objectsContainer, const gd::String &objectName,
|
||||
bool isObjectGroup, bool removeEventsAndGroups) {
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
|
||||
// In practice, this is ok because we only deal with objects.
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it
|
||||
// does not have to construct one. In practice, this is ok because we only
|
||||
// deal with objects.
|
||||
auto projectScopedContainers =
|
||||
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(
|
||||
globalObjectsContainer, objectsContainer);
|
||||
|
||||
if (removeEventsAndGroups) {
|
||||
gd::EventsRefactorer::RemoveObjectInEvents(
|
||||
@@ -1655,9 +1665,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(
|
||||
gd::ObjectsContainer &globalObjectsContainer,
|
||||
gd::ObjectsContainer &objectsContainer, const gd::String &oldName,
|
||||
const gd::String &newName, bool isObjectGroup) {
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
|
||||
// In practice, this is ok because we only deal with objects.
|
||||
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
|
||||
// In theory we should pass a ProjectScopedContainers to this function so it
|
||||
// does not have to construct one. In practice, this is ok because we only
|
||||
// deal with objects.
|
||||
auto projectScopedContainers =
|
||||
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(
|
||||
globalObjectsContainer, objectsContainer);
|
||||
|
||||
gd::EventsRefactorer::RenameObjectInEvents(
|
||||
project.GetCurrentPlatform(), projectScopedContainers,
|
||||
@@ -1705,7 +1718,7 @@ void WholeProjectRefactorer::GlobalObjectOrGroupRemoved(
|
||||
if (layout.HasObjectNamed(objectName))
|
||||
continue;
|
||||
|
||||
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
|
||||
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
|
||||
removeEventsAndGroups);
|
||||
}
|
||||
}
|
||||
@@ -1753,7 +1766,8 @@ size_t WholeProjectRefactorer::GetLayoutAndExternalLayoutLayerInstancesCount(
|
||||
GetAssociatedExternalLayouts(project, layout);
|
||||
for (gd::String name : externalLayoutsNames) {
|
||||
auto &externalLayout = project.GetExternalLayout(name);
|
||||
count += externalLayout.GetInitialInstances().GetLayerInstancesCount(layerName);
|
||||
count +=
|
||||
externalLayout.GetInitialInstances().GetLayerInstancesCount(layerName);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
@@ -155,6 +155,10 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
|
||||
worker.ExposeBitmapFont(newPropertyValue);
|
||||
} else if (resourceType == "model3D") {
|
||||
worker.ExposeModel3D(newPropertyValue);
|
||||
} else if (resourceType == "atlas") {
|
||||
worker.ExposeAtlas(newPropertyValue);
|
||||
} else if (resourceType == "spine") {
|
||||
worker.ExposeSpine(newPropertyValue);
|
||||
}
|
||||
|
||||
if (newPropertyValue != oldPropertyValue) {
|
||||
|
@@ -39,6 +39,7 @@ void Layer::SetCameraCount(std::size_t n) {
|
||||
void Layer::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("name", GetName());
|
||||
element.SetAttribute("renderingType", GetRenderingType());
|
||||
element.SetAttribute("cameraType", GetCameraType());
|
||||
element.SetAttribute("visibility", GetVisibility());
|
||||
element.SetAttribute("isLocked", IsLocked());
|
||||
element.SetAttribute("isLightingLayer", IsLightingLayer());
|
||||
@@ -78,6 +79,7 @@ void Layer::SerializeTo(SerializerElement& element) const {
|
||||
void Layer::UnserializeFrom(const SerializerElement& element) {
|
||||
SetName(element.GetStringAttribute("name", "", "Name"));
|
||||
SetRenderingType(element.GetStringAttribute("renderingType", ""));
|
||||
SetCameraType(element.GetStringAttribute("cameraType", "perspective"));
|
||||
SetVisibility(element.GetBoolAttribute("visibility", true, "Visibility"));
|
||||
SetLocked(element.GetBoolAttribute("isLocked", false));
|
||||
SetLightingLayer(element.GetBoolAttribute("isLightingLayer", false));
|
||||
|
@@ -104,10 +104,17 @@ class GD_CORE_API Layer {
|
||||
const gd::String& GetName() const { return name; }
|
||||
|
||||
const gd::String& GetRenderingType() const { return renderingType; }
|
||||
|
||||
void SetRenderingType(const gd::String& renderingType_) {
|
||||
renderingType = renderingType_;
|
||||
}
|
||||
|
||||
const gd::String& GetCameraType() const { return cameraType; }
|
||||
|
||||
void SetCameraType(const gd::String& cameraType_) {
|
||||
cameraType = cameraType_;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Change if layer is displayed or not
|
||||
*/
|
||||
@@ -268,6 +275,7 @@ class GD_CORE_API Layer {
|
||||
gd::String name; ///< The name of the layer
|
||||
gd::String renderingType; ///< The rendering type: "" (empty), "2d", "3d" or
|
||||
///< "2d+3d".
|
||||
gd::String cameraType;
|
||||
bool isVisible; ///< True if the layer is visible
|
||||
bool isLocked; ///< True if the layer is locked
|
||||
bool isLightingLayer; ///< True if the layer is used to display lights and
|
||||
|
@@ -30,6 +30,9 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
|
||||
extraInformationElement.AddChild("").SetStringValue(information);
|
||||
}
|
||||
element.AddChild("hidden").SetBoolValue(hidden);
|
||||
if (deprecated) {
|
||||
element.AddChild("deprecated").SetBoolValue(deprecated);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
@@ -58,6 +61,9 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
hidden = element.HasChild("hidden")
|
||||
? element.GetChild("hidden").GetBoolValue()
|
||||
: false;
|
||||
deprecated = element.HasChild("deprecated")
|
||||
? element.GetChild("deprecated").GetBoolValue()
|
||||
: false;
|
||||
}
|
||||
|
||||
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {
|
||||
|
@@ -30,12 +30,16 @@ class GD_CORE_API PropertyDescriptor {
|
||||
* \param propertyValue The value of the property.
|
||||
*/
|
||||
PropertyDescriptor(gd::String propertyValue)
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false), measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false),
|
||||
deprecated(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
|
||||
|
||||
/**
|
||||
* \brief Empty constructor creating an empty property to be displayed.
|
||||
*/
|
||||
PropertyDescriptor() : hidden(false), measurementUnit(gd::MeasurementUnit::GetUndefined()) {};
|
||||
PropertyDescriptor()
|
||||
: hidden(false), deprecated(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()){};
|
||||
|
||||
/**
|
||||
* \brief Destructor
|
||||
@@ -142,6 +146,19 @@ class GD_CORE_API PropertyDescriptor {
|
||||
*/
|
||||
bool IsHidden() const { return hidden; }
|
||||
|
||||
/**
|
||||
* \brief Set if the property is deprecated.
|
||||
*/
|
||||
PropertyDescriptor& SetDeprecated(bool enable = true) {
|
||||
deprecated = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check if the property is deprecated.
|
||||
*/
|
||||
bool IsDeprecated() const { return deprecated; }
|
||||
|
||||
/** \name Serialization
|
||||
*/
|
||||
///@{
|
||||
@@ -179,6 +196,7 @@ class GD_CORE_API PropertyDescriptor {
|
||||
///< choices, if a property is a displayed as a combo
|
||||
///< box.
|
||||
bool hidden;
|
||||
bool deprecated;
|
||||
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
|
||||
};
|
||||
|
||||
|
@@ -93,6 +93,10 @@ std::shared_ptr<Resource> ResourcesManager::CreateResource(
|
||||
return std::make_shared<BitmapFontResource>();
|
||||
else if (kind == "model3D")
|
||||
return std::make_shared<Model3DResource>();
|
||||
else if (kind == "atlas")
|
||||
return std::make_shared<AtlasResource>();
|
||||
else if (kind == "spine")
|
||||
return std::make_shared<SpineResource>();
|
||||
|
||||
std::cout << "Bad resource created (type: " << kind << ")" << std::endl;
|
||||
return std::make_shared<Resource>();
|
||||
@@ -756,6 +760,20 @@ void Model3DResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
|
||||
void AtlasResource::SetFile(const gd::String& newFile) {
|
||||
file = NormalizePathSeparator(newFile);
|
||||
}
|
||||
|
||||
void AtlasResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetUserAdded(element.GetBoolAttribute("userAdded"));
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
void AtlasResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
|
||||
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
|
||||
|
||||
ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
|
||||
|
@@ -373,6 +373,21 @@ class GD_CORE_API JsonResource : public Resource {
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a spine json file used by a project.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
*/
|
||||
class GD_CORE_API SpineResource : public JsonResource {
|
||||
public:
|
||||
SpineResource() : JsonResource() { SetKind("spine"); };
|
||||
virtual ~SpineResource(){};
|
||||
virtual SpineResource* Clone() const override {
|
||||
return new SpineResource(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a tilemap file used by a project.
|
||||
*
|
||||
@@ -507,6 +522,32 @@ class GD_CORE_API Model3DResource : public Resource {
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe an atlas file used by a project.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
*/
|
||||
class GD_CORE_API AtlasResource : public Resource {
|
||||
public:
|
||||
AtlasResource() : Resource() { SetKind("atlas"); };
|
||||
virtual ~AtlasResource(){};
|
||||
virtual AtlasResource* Clone() const override {
|
||||
return new AtlasResource(*this);
|
||||
}
|
||||
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
virtual bool UseFile() const override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
private:
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Inventory all resources used by a project
|
||||
*
|
||||
|
@@ -489,7 +489,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
|
||||
"Effect",
|
||||
_("Apply visual effects to objects."),
|
||||
"",
|
||||
"res/actions/effect24.png", "EffectBehavior",
|
||||
"res/actions/effect_black.svg", "EffectBehavior",
|
||||
std::make_shared<gd::Behavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
.SetHidden();
|
||||
|
@@ -203,6 +203,23 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node = parser.ParseExpression("abcd[0]");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"No object, variable or property with this name found.");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 0);
|
||||
}
|
||||
{
|
||||
auto node = parser.ParseExpression("abcd[0].efg");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -214,6 +231,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node = parser.ParseExpression("abcd.efg.hij");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -762,6 +785,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node = parser.ParseExpression("abcd.efg.hij");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -1495,6 +1524,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyNonExistingSceneVariable");
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
@@ -1516,6 +1551,25 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables (2 levels, variable and child do not exist)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MyNonExistingSceneVariable.MyNonExistingChild");
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name.");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Valid object variables (1 level)") {
|
||||
{
|
||||
auto node =
|
||||
@@ -1586,6 +1640,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyNonExistingSpriteObject.MyVariable");
|
||||
|
||||
// Also check that if we try to find the last parent of node, it is not defined.
|
||||
auto lastParentOfNode = gd::ExpressionVariableParentFinder::GetLastParentOfNode(
|
||||
platform, projectScopedContainers, *node);
|
||||
REQUIRE(lastParentOfNode.parentVariable == nullptr);
|
||||
REQUIRE(lastParentOfNode.parentVariablesContainer == nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
|
148
Core/tests/ObjectAssetSerializer.cpp
Normal file
148
Core/tests/ObjectAssetSerializer.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering common features of GDevelop Core.
|
||||
*/
|
||||
#include "GDCore/IDE/ObjectAssetSerializer.h"
|
||||
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/EventsList.h"
|
||||
#include "GDCore/Events/Serialization.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/CustomObjectConfiguration.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/ObjectsContainer.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/Variable.h"
|
||||
#include "GDCore/Serialization/Serializer.h"
|
||||
#include "GDCore/Tools/SystemStats.h"
|
||||
#include "GDCore/Tools/VersionWrapper.h"
|
||||
#include "catch.hpp"
|
||||
#include <string>
|
||||
|
||||
using namespace gd;
|
||||
|
||||
TEST_CASE("ObjectAssetSerializer", "[common]") {
|
||||
|
||||
SECTION("Can serialize custom objects as assets") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
|
||||
"MyEventsBasedObject", 0);
|
||||
eventsBasedObject.SetFullName("My events based object");
|
||||
eventsBasedObject.SetDescription("An events based object for test");
|
||||
eventsBasedObject.InsertNewObject(project, "MyExtension::Sprite", "MyChild",
|
||||
0);
|
||||
|
||||
auto &resourceManager = project.GetResourcesManager();
|
||||
gd::ImageResource imageResource;
|
||||
imageResource.SetName("assets/Idle.png");
|
||||
imageResource.SetFile("assets/Idle.png");
|
||||
imageResource.SetSmooth(true);
|
||||
resourceManager.AddResource(imageResource);
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &object = layout.InsertNewObject(
|
||||
project, "MyEventsExtension::MyEventsBasedObject", "MyObject", 0);
|
||||
auto &configuration = object.GetConfiguration();
|
||||
auto *customObjectConfiguration =
|
||||
dynamic_cast<gd::CustomObjectConfiguration *>(&configuration);
|
||||
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(
|
||||
&customObjectConfiguration->GetChildObjectConfiguration("MyChild"));
|
||||
REQUIRE(spriteConfiguration != nullptr);
|
||||
{
|
||||
gd::Animation animation;
|
||||
animation.SetName("Idle");
|
||||
animation.SetDirectionsCount(1);
|
||||
auto &direction = animation.GetDirection(0);
|
||||
gd::Sprite frame;
|
||||
frame.SetImageName("assets/Idle.png");
|
||||
direction.AddSprite(frame);
|
||||
|
||||
spriteConfiguration->AddAnimation(animation);
|
||||
}
|
||||
|
||||
SerializerElement assetElement;
|
||||
std::map<gd::String, gd::String> resourcesFileNameMap;
|
||||
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
|
||||
assetElement, resourcesFileNameMap);
|
||||
|
||||
// This mapping is used to copy resource files.
|
||||
REQUIRE(resourcesFileNameMap.find("assets/Idle.png") !=
|
||||
resourcesFileNameMap.end());
|
||||
REQUIRE(resourcesFileNameMap["assets/Idle.png"] == "Idle.png");
|
||||
|
||||
// Check that the project is left untouched.
|
||||
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
|
||||
REQUIRE(resourceManager.GetResource("assets/Idle.png").GetFile() ==
|
||||
"assets/Idle.png");
|
||||
REQUIRE(!resourceManager.HasResource("Idle.png"));
|
||||
|
||||
REQUIRE(assetElement.HasChild("objectAssets"));
|
||||
auto &objectAssetsElement = assetElement.GetChild("objectAssets");
|
||||
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
|
||||
REQUIRE(objectAssetsElement.GetChildrenCount() == 1);
|
||||
auto &objectAssetElement = objectAssetsElement.GetChild(0);
|
||||
|
||||
REQUIRE(objectAssetElement.HasChild("requiredExtensions"));
|
||||
auto &requiredExtensionsElement =
|
||||
objectAssetElement.GetChild("requiredExtensions");
|
||||
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
|
||||
REQUIRE(requiredExtensionsElement.GetChildrenCount() == 1);
|
||||
auto &requiredExtensionElement = requiredExtensionsElement.GetChild(0);
|
||||
REQUIRE(requiredExtensionElement.GetStringAttribute("extensionName") ==
|
||||
"MyEventsExtension");
|
||||
|
||||
// Resources are renamed according to asset script naming conventions.
|
||||
REQUIRE(objectAssetElement.HasChild("resources"));
|
||||
auto &resourcesElement = objectAssetElement.GetChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
REQUIRE(resourcesElement.GetChildrenCount() == 1);
|
||||
{
|
||||
auto &resourceElement = resourcesElement.GetChild(0);
|
||||
REQUIRE(resourceElement.GetStringAttribute("name") == "Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("file") == "Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
|
||||
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
|
||||
}
|
||||
|
||||
// Resources used in object configuration are updated.
|
||||
REQUIRE(objectAssetElement.HasChild("object"));
|
||||
auto &objectElement = objectAssetElement.GetChild("object");
|
||||
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
|
||||
REQUIRE(objectElement.GetStringAttribute("type") ==
|
||||
"MyEventsExtension::MyEventsBasedObject");
|
||||
auto &childrenContentElement = objectElement.GetChild("childrenContent");
|
||||
|
||||
REQUIRE(childrenContentElement.HasChild("MyChild"));
|
||||
auto &childElement = childrenContentElement.GetChild("MyChild");
|
||||
REQUIRE(childElement.HasChild("animations"));
|
||||
auto &animationsElement = childElement.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
REQUIRE(animationsElement.GetChildrenCount() == 1);
|
||||
auto &animationElement = animationsElement.GetChild(0);
|
||||
|
||||
REQUIRE(animationElement.GetStringAttribute("name") == "Idle");
|
||||
auto &directionsElement = animationElement.GetChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
REQUIRE(directionsElement.GetChildrenCount() == 1);
|
||||
auto &directionElement = directionsElement.GetChild(0);
|
||||
auto &spritesElement = directionElement.GetChild("sprites");
|
||||
spritesElement.ConsiderAsArrayOf("sprite");
|
||||
REQUIRE(spritesElement.GetChildrenCount() == 1);
|
||||
auto &spriteElement = spritesElement.GetChild(0);
|
||||
REQUIRE(spriteElement.GetStringAttribute("image") == "Idle.png");
|
||||
}
|
||||
}
|
@@ -3474,6 +3474,72 @@ TEST_CASE("RenameLayer", "[common]") {
|
||||
"MyExtension::CameraCenterX(\"layerA\")");
|
||||
}
|
||||
|
||||
SECTION("Renaming a layer also moves the instances on this layer and of the associated external layouts") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
auto &layout = project.InsertNewLayout("My layout", 0);
|
||||
auto &otherLayout = project.InsertNewLayout("My other layout", 1);
|
||||
|
||||
layout.InsertNewLayer("My layer", 0);
|
||||
otherLayout.InsertNewLayer("My layer", 0);
|
||||
|
||||
auto &externalLayout =
|
||||
project.InsertNewExternalLayout("My external layout", 0);
|
||||
auto &otherExternalLayout =
|
||||
project.InsertNewExternalLayout("My other external layout", 0);
|
||||
externalLayout.SetAssociatedLayout("My layout");
|
||||
otherExternalLayout.SetAssociatedLayout("My other layout");
|
||||
|
||||
auto &initialInstances = layout.GetInitialInstances();
|
||||
auto &initialInstance1 = initialInstances.InsertNewInitialInstance();
|
||||
initialInstance1.SetLayer("My layer");
|
||||
auto &initialInstance2 = initialInstances.InsertNewInitialInstance();
|
||||
initialInstance2.SetLayer("My layer");
|
||||
auto &initialInstance3 = initialInstances.InsertNewInitialInstance();
|
||||
initialInstance3.SetLayer("");
|
||||
|
||||
auto &externalInitialInstances = externalLayout.GetInitialInstances();
|
||||
auto &externalInitialInstance1 = externalInitialInstances.InsertNewInitialInstance();
|
||||
externalInitialInstance1.SetLayer("My layer");
|
||||
auto &externalInitialInstance2 = externalInitialInstances.InsertNewInitialInstance();
|
||||
externalInitialInstance2.SetLayer("My layer");
|
||||
auto &externalInitialInstance3 = externalInitialInstances.InsertNewInitialInstance();
|
||||
externalInitialInstance3.SetLayer("");
|
||||
|
||||
auto &otherInitialInstances = otherLayout.GetInitialInstances();
|
||||
auto &otherInitialInstance1 = otherInitialInstances.InsertNewInitialInstance();
|
||||
otherInitialInstance1.SetLayer("My layer");
|
||||
|
||||
auto &otherExternalInitialInstances = otherExternalLayout.GetInitialInstances();
|
||||
auto &otherExternalInitialInstance1 = otherExternalInitialInstances.InsertNewInitialInstance();
|
||||
otherExternalInitialInstance1.SetLayer("My layer");
|
||||
|
||||
REQUIRE(initialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(initialInstance2.GetLayer() == "My layer");
|
||||
REQUIRE(initialInstance3.GetLayer() == "");
|
||||
REQUIRE(externalInitialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(externalInitialInstance2.GetLayer() == "My layer");
|
||||
REQUIRE(externalInitialInstance3.GetLayer() == "");
|
||||
REQUIRE(otherInitialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(otherExternalInitialInstance1.GetLayer() == "My layer");
|
||||
|
||||
gd::WholeProjectRefactorer::RenameLayer(project, layout, "My layer", "My new layer");
|
||||
|
||||
// Instances on the renamed layer are moved to the new layer.
|
||||
REQUIRE(initialInstance1.GetLayer() == "My new layer");
|
||||
REQUIRE(initialInstance2.GetLayer() == "My new layer");
|
||||
REQUIRE(initialInstance3.GetLayer() == "");
|
||||
// Instances on the renamed layer of external layouts are moved to the new layer.
|
||||
REQUIRE(externalInitialInstance1.GetLayer() == "My new layer");
|
||||
REQUIRE(externalInitialInstance2.GetLayer() == "My new layer");
|
||||
REQUIRE(externalInitialInstance3.GetLayer() == "");
|
||||
// Instances on the renamed layer of other layouts & external layouts are not moved.
|
||||
REQUIRE(otherInitialInstance1.GetLayer() == "My layer");
|
||||
REQUIRE(otherExternalInitialInstance1.GetLayer() == "My layer");
|
||||
}
|
||||
|
||||
SECTION("Can rename a layer when a layer parameter is empty") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
|
1
Extensions/.gitignore
vendored
Normal file
1
Extensions/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Spine/pixi-spine
|
@@ -247,7 +247,7 @@ module.exports = {
|
||||
'Model3DObject',
|
||||
_('3D Model'),
|
||||
_('An animated 3D model.'),
|
||||
'JsPlatform/Extensions/3d_box.svg',
|
||||
'JsPlatform/Extensions/3d_model.svg',
|
||||
new gd.Model3DObjectConfiguration()
|
||||
)
|
||||
.setCategoryFullName(_('General'))
|
||||
@@ -2695,7 +2695,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/3d_box.svg';
|
||||
return 'JsPlatform/Extensions/3d_model.svg';
|
||||
}
|
||||
|
||||
getOriginX() {
|
||||
|
@@ -11,7 +11,12 @@ namespace gdjs {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const layerRenderer = layer.getRenderer();
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
const fov = threeCamera ? threeCamera.fov : assumedFovIn2D;
|
||||
const fov =
|
||||
threeCamera instanceof THREE.OrthographicCamera
|
||||
? null
|
||||
: threeCamera
|
||||
? threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
return layer.getCameraZ(fov, cameraIndex);
|
||||
};
|
||||
|
||||
@@ -24,7 +29,12 @@ namespace gdjs {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const layerRenderer = layer.getRenderer();
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
const fov = threeCamera ? threeCamera.fov : assumedFovIn2D;
|
||||
const fov =
|
||||
threeCamera instanceof THREE.OrthographicCamera
|
||||
? null
|
||||
: threeCamera
|
||||
? threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
layer.setCameraZ(z, fov, cameraIndex);
|
||||
};
|
||||
|
||||
@@ -213,8 +223,10 @@ namespace gdjs {
|
||||
const layerRenderer = layer.getRenderer();
|
||||
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
if (!threeCamera) return 45;
|
||||
return threeCamera.fov;
|
||||
if (!threeCamera) return assumedFovIn2D;
|
||||
return threeCamera instanceof THREE.OrthographicCamera
|
||||
? 0
|
||||
: threeCamera.fov;
|
||||
};
|
||||
|
||||
export const setFov = (
|
||||
@@ -227,7 +239,8 @@ namespace gdjs {
|
||||
const layerRenderer = layer.getRenderer();
|
||||
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
if (!threeCamera) return;
|
||||
if (!threeCamera || threeCamera instanceof THREE.OrthographicCamera)
|
||||
return;
|
||||
|
||||
threeCamera.fov = Math.min(Math.max(angle, 0), 180);
|
||||
layerRenderer.setThreeCameraDirty(true);
|
||||
|
@@ -26,6 +26,7 @@ set(
|
||||
TextEntryObject
|
||||
TextObject
|
||||
TiledSpriteObject
|
||||
Spine
|
||||
TopDownMovementBehavior)
|
||||
|
||||
# Automatically add all listed extensions
|
||||
|
@@ -150,7 +150,8 @@ PlatformerObjectBehavior::GetProperties(
|
||||
properties["UseLegacyTrajectory"]
|
||||
.SetLabel(_("Use frame rate dependent trajectories (deprecated, it's "
|
||||
"recommended to leave this unchecked)"))
|
||||
.SetGroup(_("Deprecated options (advanced)"))
|
||||
.SetGroup(_("Deprecated options"))
|
||||
.SetDeprecated()
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
|
||||
? "true"
|
||||
: "false")
|
||||
|
@@ -62,7 +62,7 @@ namespace gdjs {
|
||||
private _yGrabOffset: any;
|
||||
private _xGrabTolerance: any;
|
||||
|
||||
_useLegacyTrajectory: boolean = true;
|
||||
_useLegacyTrajectory: boolean;
|
||||
|
||||
_canGoDownFromJumpthru: boolean = false;
|
||||
|
||||
@@ -355,13 +355,25 @@ namespace gdjs {
|
||||
|
||||
private _updateSpeed(timeDelta: float): float {
|
||||
const previousSpeed = this._currentSpeed;
|
||||
//Change the speed according to the player's input.
|
||||
// @ts-ignore
|
||||
if (this._leftKey) {
|
||||
this._currentSpeed -= this._acceleration * timeDelta;
|
||||
}
|
||||
if (this._rightKey) {
|
||||
this._currentSpeed += this._acceleration * timeDelta;
|
||||
// Change the speed according to the player's input.
|
||||
// TODO Give priority to the last key for faster reaction time.
|
||||
if (this._leftKey !== this._rightKey) {
|
||||
if (this._leftKey) {
|
||||
if (this._currentSpeed <= 0) {
|
||||
this._currentSpeed -= this._acceleration * timeDelta;
|
||||
} else {
|
||||
// Turn back at least as fast as it would stop.
|
||||
this._currentSpeed -=
|
||||
Math.max(this._acceleration, this._deceleration) * timeDelta;
|
||||
}
|
||||
} else if (this._rightKey) {
|
||||
if (this._currentSpeed >= 0) {
|
||||
this._currentSpeed += this._acceleration * timeDelta;
|
||||
} else {
|
||||
this._currentSpeed +=
|
||||
Math.max(this._acceleration, this._deceleration) * timeDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Take deceleration into account only if no key is pressed.
|
||||
|
@@ -169,6 +169,23 @@ module.exports = {
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.getUsername');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
'UserID',
|
||||
_('User ID'),
|
||||
_('Get the unique user ID of the authenticated player.'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.getUserId');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
'IsPlayerAuthenticated',
|
||||
|
@@ -171,7 +171,7 @@ namespace gdjs {
|
||||
if (!_checkedLocalStorage) {
|
||||
readAuthenticatedUserFromLocalStorage();
|
||||
}
|
||||
return _userId || null;
|
||||
return _userId || '';
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -163,9 +163,9 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
obj.AddAction("Torus",
|
||||
_("Torus"),
|
||||
_("Draw a torus on screen"),
|
||||
_("Draw at _PARAM1_;_PARAM2_ a torus with inner radius"
|
||||
"_PARAM3_ and outer radius _PARAM4_ and "
|
||||
"with start arc _PARAM5_° and end arc _PARAM6_°"
|
||||
_("Draw at _PARAM1_;_PARAM2_ a torus with "
|
||||
"inner radius: _PARAM3_, outer radius: _PARAM4_ and "
|
||||
"with start arc angle: _PARAM5_°, end angle: _PARAM6_° "
|
||||
"with _PARAM0_"),
|
||||
_("Drawing"),
|
||||
"res/actions/torus24.png",
|
||||
|
20
Extensions/Spine/CMakeLists.txt
Normal file
20
Extensions/Spine/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
cmake_policy(SET CMP0015 NEW)
|
||||
|
||||
project(SpineObject)
|
||||
gd_add_extension_includes()
|
||||
|
||||
#Defines
|
||||
###
|
||||
gd_add_extension_definitions(SpineObject)
|
||||
|
||||
#The targets
|
||||
###
|
||||
include_directories(.)
|
||||
file(GLOB source_files *.cpp *.h)
|
||||
gd_add_clang_utils(SpineObject "${source_files}")
|
||||
gd_add_extension_target(SpineObject "${source_files}")
|
||||
|
||||
#Linker files for the IDE extension
|
||||
###
|
||||
gd_extension_link_libraries(SpineObject)
|
323
Extensions/Spine/JsExtension.js
Normal file
323
Extensions/Spine/JsExtension.js
Normal file
@@ -0,0 +1,323 @@
|
||||
// @flow
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
* ℹ️ Changes in this file are watched and automatically imported if the editor
|
||||
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
|
||||
*
|
||||
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
|
||||
* ⚠️ If you make a change and the extension is not loaded, open the developer console
|
||||
* and search for any errors.
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/*::
|
||||
// Import types to allow Flow to do static type checking on this file.
|
||||
// Extensions declaration are typed using Flow (like the editor), but the files
|
||||
// for the game engine are checked with TypeScript annotations.
|
||||
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'SpineObject',
|
||||
_('Spine (experimental)'),
|
||||
_('Displays a Spine animation.'),
|
||||
'Vladyslav Pohorielov',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/objects/spine')
|
||||
.setCategory('Advanced');
|
||||
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Spine'))
|
||||
.setIcon('JsPlatform/Extensions/spine.svg');
|
||||
|
||||
const object = extension
|
||||
.addObject(
|
||||
'SpineObject',
|
||||
_('Spine (experimental)'),
|
||||
_(
|
||||
'Display and smoothly animate a 2D object with skeletal animations made with Spine. Use files exported from Spine (json, atlas and image).'
|
||||
),
|
||||
'JsPlatform/Extensions/spine.svg',
|
||||
new gd.SpineObjectConfiguration()
|
||||
)
|
||||
.addDefaultBehavior('EffectCapability::EffectBehavior')
|
||||
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
|
||||
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
|
||||
.addDefaultBehavior('FlippableCapability::FlippableBehavior')
|
||||
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
|
||||
.addDefaultBehavior('AnimatableCapability::AnimatableBehavior')
|
||||
.setIncludeFile('Extensions/Spine/spineruntimeobject.js')
|
||||
.addIncludeFile('Extensions/Spine/spineruntimeobject-pixi-renderer.js')
|
||||
.addIncludeFile('Extensions/Spine/pixi-spine/pixi-spine.js')
|
||||
.addIncludeFile('Extensions/Spine/managers/pixi-spine-atlas-manager.js')
|
||||
.addIncludeFile('Extensions/Spine/managers/pixi-spine-manager.js')
|
||||
.setCategoryFullName(_('Advanced'));
|
||||
|
||||
object
|
||||
.addExpressionAndConditionAndAction(
|
||||
'number',
|
||||
'Animation',
|
||||
_('Animation mixing duration'),
|
||||
_(
|
||||
'the duration of the smooth transition between 2 animations (in second)'
|
||||
),
|
||||
_('the animation mixing duration'),
|
||||
_('Animations and images'),
|
||||
'JsPlatform/Extensions/spine.svg'
|
||||
)
|
||||
.addParameter('object', _('Spine'), 'SpineObject')
|
||||
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
|
||||
.setFunctionName('setAnimationMixingDuration')
|
||||
.setGetter('getAnimationMixingDuration');
|
||||
|
||||
return extension;
|
||||
},
|
||||
|
||||
/**
|
||||
* You can optionally add sanity tests that will check the basic working
|
||||
* of your extension behaviors/objects by instantiating behaviors/objects
|
||||
* and setting the property to a given value.
|
||||
*
|
||||
* If you don't have any tests, you can simply return an empty array.
|
||||
*
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* Register editors for objects.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {},
|
||||
/**
|
||||
* Register renderers for instance of objects on the scene editor.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
const { PIXI, RenderedInstance, gd } = objectsRenderingService;
|
||||
|
||||
class RenderedSpineInstance extends RenderedInstance {
|
||||
_spine = null;
|
||||
_rect = new PIXI.Graphics();
|
||||
_initialWidth = null;
|
||||
_initialHeight = null;
|
||||
_animationIndex = -1;
|
||||
_spineOriginOffsetX = 0;
|
||||
_spineOriginOffsetY = 0;
|
||||
|
||||
constructor(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
) {
|
||||
super(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
// there is issue with spine selection. mouse events are not triggering during interaction.
|
||||
// create the invisible background rectangle to fill spine range.
|
||||
this._rect.alpha = 0;
|
||||
this._pixiObject = new PIXI.Container();
|
||||
this._pixiObject.addChild(this._rect);
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
|
||||
this._loadSpine();
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/spine.svg';
|
||||
}
|
||||
|
||||
update() {
|
||||
this._pixiObject.position.set(
|
||||
this._instance.getX(),
|
||||
this._instance.getY()
|
||||
);
|
||||
|
||||
this.setAnimation(this._instance.getRawDoubleProperty('animation'));
|
||||
|
||||
const width = this.getWidth();
|
||||
const height = this.getHeight();
|
||||
const { _spine: spine } = this;
|
||||
|
||||
if (spine) {
|
||||
spine.width = width;
|
||||
spine.height = height;
|
||||
const localBounds = spine.getLocalBounds(undefined, true);
|
||||
|
||||
this._spineOriginOffsetX = localBounds.x * spine.scale.x;
|
||||
this._spineOriginOffsetY = localBounds.y * spine.scale.y;
|
||||
this._rect.position.set(
|
||||
this._spineOriginOffsetX,
|
||||
this._spineOriginOffsetY
|
||||
);
|
||||
}
|
||||
|
||||
this._rect.clear();
|
||||
this._rect.beginFill(0xffffff);
|
||||
this._rect.lineStyle(1, 0xff0000);
|
||||
this._rect.drawRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns x coordinate of this spine origin offset
|
||||
*/
|
||||
getOriginX() {
|
||||
return -this._spineOriginOffsetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns y coordinate of this spine origin offset
|
||||
*/
|
||||
getOriginY() {
|
||||
return -this._spineOriginOffsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index - animation index
|
||||
*/
|
||||
setAnimation(index) {
|
||||
const { _spine: spine } = this;
|
||||
const configuration = this._getConfiguration();
|
||||
|
||||
if (
|
||||
!spine ||
|
||||
configuration.hasNoAnimations() ||
|
||||
index === this._animationIndex
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Number.isInteger(index) || index < 0) {
|
||||
index = 0;
|
||||
} else if (configuration.getAnimationsCount() <= index) {
|
||||
index = configuration.getAnimationsCount() - 1;
|
||||
}
|
||||
|
||||
this._animationIndex = index;
|
||||
const animation = configuration.getAnimation(index);
|
||||
const source = animation.getSource();
|
||||
const shouldLoop = animation.shouldLoop();
|
||||
const scale = this.getScale();
|
||||
|
||||
// reset scale to track new animation range
|
||||
// if custom size is set it will be reinitialized in update method
|
||||
spine.scale.set(1, 1);
|
||||
spine.state.setAnimation(0, source, shouldLoop);
|
||||
spine.state.tracks[0].trackTime = 0;
|
||||
spine.update(0);
|
||||
spine.autoUpdate = false;
|
||||
this._initialWidth = spine.width * this.getScale();
|
||||
this._initialHeight = spine.height * this.getScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} default width
|
||||
*/
|
||||
getDefaultWidth() {
|
||||
return this._initialWidth !== null ? this._initialWidth : 256;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} default height
|
||||
*/
|
||||
getDefaultHeight() {
|
||||
return this._initialHeight !== null ? this._initialHeight : 256;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} defined scale
|
||||
*/
|
||||
getScale() {
|
||||
return Number(this._getProperties().get('scale').getValue()) || 1;
|
||||
}
|
||||
|
||||
onRemovedFromScene() {
|
||||
super.onRemovedFromScene();
|
||||
this._pixiObject.destroy({ children: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns this spine object configuration
|
||||
*/
|
||||
_getConfiguration() {
|
||||
return gd.asSpineConfiguration(this._associatedObjectConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns this object properties container
|
||||
*/
|
||||
_getProperties() {
|
||||
return this._associatedObjectConfiguration.getProperties();
|
||||
}
|
||||
|
||||
_loadSpine() {
|
||||
const properties = this._getProperties();
|
||||
const spineResourceName = properties
|
||||
.get('spineResourceName')
|
||||
.getValue();
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.getSpineData(this._project, spineResourceName)
|
||||
.then((spineDataOrLoadingError) => {
|
||||
if (!spineDataOrLoadingError.skeleton) {
|
||||
console.error(
|
||||
'Unable to load Spine (' +
|
||||
(spineDataOrLoadingError.loadingErrorReason ||
|
||||
'Unknown reason') +
|
||||
')',
|
||||
spineDataOrLoadingError.loadingError
|
||||
);
|
||||
this._spine = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._spine = new PIXI.Spine(spineDataOrLoadingError.skeleton);
|
||||
} catch (error) {
|
||||
console.error('Exception while loading Spine.', error);
|
||||
this._spine = null;
|
||||
return;
|
||||
}
|
||||
this._pixiObject.addChild(this._spine);
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
objectsRenderingService.registerInstanceRenderer(
|
||||
'SpineObject::SpineObject',
|
||||
RenderedSpineInstance
|
||||
);
|
||||
},
|
||||
};
|
165
Extensions/Spine/SpineObjectConfiguration.cpp
Normal file
165
Extensions/Spine/SpineObjectConfiguration.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
GDevelop - Spine Extension
|
||||
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "SpineObjectConfiguration.h"
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/Project/InitialInstance.h"
|
||||
#include "GDCore/Project/MeasurementUnit.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
SpineAnimation SpineObjectConfiguration::badAnimation;
|
||||
|
||||
SpineObjectConfiguration::SpineObjectConfiguration()
|
||||
: scale(1), spineResourceName("") {};
|
||||
|
||||
bool SpineObjectConfiguration::UpdateProperty(const gd::String &propertyName, const gd::String &newValue) {
|
||||
if (propertyName == "scale") {
|
||||
scale = newValue.To<double>();
|
||||
return true;
|
||||
}
|
||||
if (propertyName == "spineResourceName") {
|
||||
spineResourceName = newValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
SpineObjectConfiguration::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> objectProperties;
|
||||
|
||||
objectProperties["scale"]
|
||||
.SetValue(gd::String::From(scale))
|
||||
.SetType("number")
|
||||
.SetLabel(_("Scale"))
|
||||
.SetGroup(_("Default size"));
|
||||
|
||||
objectProperties["spineResourceName"]
|
||||
.SetValue(spineResourceName)
|
||||
.SetType("resource")
|
||||
.AddExtraInfo("spine")
|
||||
.SetLabel(_("Spine json"));
|
||||
|
||||
return objectProperties;
|
||||
}
|
||||
|
||||
bool SpineObjectConfiguration::UpdateInitialInstanceProperty(
|
||||
gd::InitialInstance &instance, const gd::String &propertyName,
|
||||
const gd::String &newValue, gd::Project &project, gd::Layout &layout
|
||||
) {
|
||||
if (propertyName == "animation") {
|
||||
instance.SetRawDoubleProperty("animation", std::max(0, newValue.empty() ? 0 : newValue.To<int>()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
SpineObjectConfiguration::GetInitialInstanceProperties(const gd::InitialInstance &instance, gd::Project &project, gd::Layout &layout) {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
properties["animation"] =
|
||||
gd::PropertyDescriptor(gd::String::From(instance.GetRawDoubleProperty("animation")))
|
||||
.SetLabel(_("Animation"))
|
||||
.SetType("number");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::DoUnserializeFrom(gd::Project &project, const gd::SerializerElement &element) {
|
||||
auto &content = element.GetChild("content");
|
||||
|
||||
scale = content.GetDoubleAttribute("scale");
|
||||
spineResourceName = content.GetStringAttribute("spineResourceName");
|
||||
|
||||
RemoveAllAnimations();
|
||||
auto &animationsElement = content.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
for (std::size_t i = 0; i < animationsElement.GetChildrenCount(); ++i) {
|
||||
auto &animationElement = animationsElement.GetChild(i);
|
||||
SpineAnimation animation;
|
||||
animation.SetName(animationElement.GetStringAttribute("name", ""));
|
||||
animation.SetSource(animationElement.GetStringAttribute("source", ""));
|
||||
animation.SetShouldLoop(animationElement.GetBoolAttribute("loop", false));
|
||||
AddAnimation(animation);
|
||||
}
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::DoSerializeTo(gd::SerializerElement &element) const {
|
||||
auto &content = element.AddChild("content");
|
||||
content.SetAttribute("scale", scale);
|
||||
content.SetAttribute("spineResourceName", spineResourceName);
|
||||
|
||||
auto &animationsElement = content.AddChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
for (auto &animation : animations) {
|
||||
auto &animationElement = animationsElement.AddChild("animation");
|
||||
animationElement.SetAttribute("name", animation.GetName());
|
||||
animationElement.SetAttribute("source", animation.GetSource());
|
||||
animationElement.SetAttribute("loop", animation.ShouldLoop());
|
||||
}
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker &worker) {
|
||||
worker.ExposeSpine(spineResourceName);
|
||||
worker.ExposeEmbeddeds(spineResourceName);
|
||||
}
|
||||
|
||||
const SpineAnimation &
|
||||
SpineObjectConfiguration::GetAnimation(std::size_t nb) const {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
SpineAnimation &SpineObjectConfiguration::GetAnimation(std::size_t nb) {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
bool SpineObjectConfiguration::HasAnimationNamed(const gd::String &name) const {
|
||||
return !name.empty() && (find_if(animations.begin(), animations.end(),
|
||||
[&name](const SpineAnimation &animation) {
|
||||
return animation.GetName() == name;
|
||||
}) != animations.end());
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::AddAnimation(const SpineAnimation &animation) {
|
||||
animations.push_back(animation);
|
||||
}
|
||||
|
||||
bool SpineObjectConfiguration::RemoveAnimation(std::size_t nb) {
|
||||
if (nb >= GetAnimationsCount())
|
||||
return false;
|
||||
|
||||
animations.erase(animations.begin() + nb);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::SwapAnimations(std::size_t firstIndex, std::size_t secondIndex) {
|
||||
if (firstIndex < animations.size() && secondIndex < animations.size() && firstIndex != secondIndex) {
|
||||
std::swap(animations[firstIndex], animations[secondIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void SpineObjectConfiguration::MoveAnimation(std::size_t oldIndex, std::size_t newIndex) {
|
||||
if (oldIndex >= animations.size() || newIndex >= animations.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto animation = animations[oldIndex];
|
||||
animations.erase(animations.begin() + oldIndex);
|
||||
animations.insert(animations.begin() + newIndex, animation);
|
||||
}
|
162
Extensions/Spine/SpineObjectConfiguration.h
Normal file
162
Extensions/Spine/SpineObjectConfiguration.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
GDevelop - Spine Extension
|
||||
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
namespace gd {
|
||||
class InitialInstance;
|
||||
class Project;
|
||||
} // namespace gd
|
||||
|
||||
class GD_EXTENSION_API SpineAnimation {
|
||||
public:
|
||||
SpineAnimation() : shouldLoop(false) {};
|
||||
virtual ~SpineAnimation(){};
|
||||
|
||||
/**
|
||||
* \brief Return the name of the animation
|
||||
*/
|
||||
const gd::String &GetName() const { return name; }
|
||||
|
||||
/**
|
||||
* \brief Change the name of the animation
|
||||
*/
|
||||
void SetName(const gd::String &name_) { name = name_; }
|
||||
|
||||
/**
|
||||
* \brief Return the name of the animation from the spine file.
|
||||
*/
|
||||
const gd::String &GetSource() const { return source; }
|
||||
|
||||
/**
|
||||
* \brief Change the name of the animation from the spine file.
|
||||
*/
|
||||
void SetSource(const gd::String &source_) { source = source_; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the animation should loop.
|
||||
*/
|
||||
const bool ShouldLoop() const { return shouldLoop; }
|
||||
|
||||
/**
|
||||
* \brief Change whether the animation should loop or not.
|
||||
*/
|
||||
void SetShouldLoop(bool shouldLoop_) { shouldLoop = shouldLoop_; }
|
||||
|
||||
private:
|
||||
gd::String name;
|
||||
gd::String source;
|
||||
bool shouldLoop;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Spine object configuration is used for storage and for the IDE.
|
||||
*/
|
||||
class GD_EXTENSION_API SpineObjectConfiguration : public gd::ObjectConfiguration {
|
||||
public:
|
||||
SpineObjectConfiguration();
|
||||
virtual ~SpineObjectConfiguration(){};
|
||||
virtual std::unique_ptr<gd::ObjectConfiguration> Clone() const override {
|
||||
return gd::make_unique<SpineObjectConfiguration>(*this);
|
||||
}
|
||||
|
||||
virtual void ExposeResources(gd::ArbitraryResourceWorker &worker) override;
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>GetProperties() const override;
|
||||
|
||||
virtual bool UpdateProperty(const gd::String &name, const gd::String &value) override;
|
||||
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor>
|
||||
GetInitialInstanceProperties(const gd::InitialInstance &instance,
|
||||
gd::Project &project,
|
||||
gd::Layout &layout) override;
|
||||
|
||||
virtual bool UpdateInitialInstanceProperty(gd::InitialInstance &instance,
|
||||
const gd::String &name,
|
||||
const gd::String &value,
|
||||
gd::Project &project,
|
||||
gd::Layout &layout) override;
|
||||
|
||||
/** \name Animations
|
||||
* Methods related to animations management
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
*/
|
||||
const SpineAnimation &GetAnimation(std::size_t nb) const;
|
||||
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
*/
|
||||
SpineAnimation &GetAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Return the number of animations this object has.
|
||||
*/
|
||||
std::size_t GetAnimationsCount() const { return animations.size(); };
|
||||
|
||||
/**
|
||||
* \brief Return true if the animation called "name" exists.
|
||||
*/
|
||||
bool HasAnimationNamed(const gd::String& name) const;
|
||||
|
||||
/**
|
||||
* \brief Add an animation at the end of the existing ones.
|
||||
*/
|
||||
void AddAnimation(const SpineAnimation &animation);
|
||||
|
||||
/**
|
||||
* \brief Remove an animation.
|
||||
*/
|
||||
bool RemoveAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Remove all animations.
|
||||
*/
|
||||
void RemoveAllAnimations() { animations.clear(); }
|
||||
|
||||
/**
|
||||
* \brief Return true if the object hasn't any animation.
|
||||
*/
|
||||
bool HasNoAnimations() const { return animations.empty(); }
|
||||
|
||||
/**
|
||||
* \brief Swap the position of two animations
|
||||
*/
|
||||
void SwapAnimations(std::size_t firstIndex, std::size_t secondIndex);
|
||||
|
||||
/**
|
||||
* \brief Change the position of the specified animation
|
||||
*/
|
||||
void MoveAnimation(std::size_t oldIndex, std::size_t newIndex);
|
||||
|
||||
/**
|
||||
* \brief Return a read-only reference to the vector containing all the
|
||||
* animation of the object.
|
||||
*/
|
||||
const std::vector<SpineAnimation> &GetAllAnimations() const {
|
||||
return animations;
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
protected:
|
||||
virtual void DoUnserializeFrom(gd::Project &project, const gd::SerializerElement &element) override;
|
||||
virtual void DoSerializeTo(gd::SerializerElement &element) const override;
|
||||
|
||||
private:
|
||||
double scale;
|
||||
|
||||
gd::String spineResourceName;
|
||||
|
||||
std::vector<SpineAnimation> animations;
|
||||
|
||||
static SpineAnimation badAnimation;
|
||||
};
|
199
Extensions/Spine/managers/pixi-spine-atlas-manager.ts
Normal file
199
Extensions/Spine/managers/pixi-spine-atlas-manager.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
/** The callback called when a text that was requested is loaded (or an error occurred). */
|
||||
export type SpineAtlasManagerRequestCallback = (
|
||||
error: Error | null,
|
||||
content?: pixi_spine.TextureAtlas
|
||||
) => void;
|
||||
|
||||
const atlasKinds: ResourceKind[] = ['atlas'];
|
||||
|
||||
/**
|
||||
* AtlasManager loads atlas files with pixi loader, using the "atlas" resources
|
||||
* registered in the game resources and process them to Pixi TextureAtlas.
|
||||
*
|
||||
* Contrary to audio/fonts, text files are loaded asynchronously, when requested.
|
||||
* You should properly handle errors, and give the developer/player a way to know
|
||||
* that loading failed.
|
||||
*/
|
||||
export class SpineAtlasManager implements gdjs.ResourceManager {
|
||||
private _imageManager: ImageManager;
|
||||
private _resourceLoader: ResourceLoader;
|
||||
private _loadedSpineAtlases = new gdjs.ResourceCache<
|
||||
pixi_spine.TextureAtlas
|
||||
>();
|
||||
private _loadingSpineAtlases = new gdjs.ResourceCache<
|
||||
Promise<pixi_spine.TextureAtlas>
|
||||
>();
|
||||
|
||||
/**
|
||||
* @param resources The resources data of the game.
|
||||
* @param resourcesLoader The resources loader of the game.
|
||||
*/
|
||||
constructor(
|
||||
resourceLoader: gdjs.ResourceLoader,
|
||||
imageManager: ImageManager
|
||||
) {
|
||||
this._resourceLoader = resourceLoader;
|
||||
this._imageManager = imageManager;
|
||||
}
|
||||
|
||||
getResourceKinds(): ResourceKind[] {
|
||||
return atlasKinds;
|
||||
}
|
||||
|
||||
async processResource(resourceName: string): Promise<void> {
|
||||
// Do nothing because pixi-spine parses resources by itself.
|
||||
}
|
||||
|
||||
async loadResource(resourceName: string): Promise<void> {
|
||||
await this.getOrLoad(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns promisified loaded atlas resource if it is availble, loads it otherwise.
|
||||
*
|
||||
* @param resources The data of resource to load.
|
||||
*/
|
||||
getOrLoad(resourceName: string): Promise<pixi_spine.TextureAtlas> {
|
||||
const resource = this._getAtlasResource(resourceName);
|
||||
|
||||
if (!resource) {
|
||||
return Promise.reject(
|
||||
`Unable to find atlas for resource '${resourceName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
let loadingPromise = this._loadingSpineAtlases.get(resource);
|
||||
|
||||
if (!loadingPromise) {
|
||||
loadingPromise = new Promise<pixi_spine.TextureAtlas>(
|
||||
(resolve, reject) => {
|
||||
const onLoad: SpineAtlasManagerRequestCallback = (
|
||||
error,
|
||||
content
|
||||
) => {
|
||||
if (error) {
|
||||
return reject(
|
||||
`Error while preloading a spine atlas resource: ${error}`
|
||||
);
|
||||
}
|
||||
if (!content) {
|
||||
return reject(
|
||||
`Cannot reach texture atlas for resource '${resourceName}'.`
|
||||
);
|
||||
}
|
||||
|
||||
resolve(content);
|
||||
};
|
||||
|
||||
this.load(resource, onLoad);
|
||||
}
|
||||
);
|
||||
|
||||
this._loadingSpineAtlases.set(resource, loadingPromise);
|
||||
}
|
||||
|
||||
return loadingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load specified atlas resource and pass it to callback once it is loaded.
|
||||
*
|
||||
* @param resources The data of resource to load.
|
||||
* @param callback The callback to pass atlas to it once it is loaded.
|
||||
*/
|
||||
load(
|
||||
resource: ResourceData,
|
||||
callback: SpineAtlasManagerRequestCallback
|
||||
): void {
|
||||
const game = this._resourceLoader.getRuntimeGame();
|
||||
const embeddedResourcesNames = game.getEmbeddedResourcesNames(
|
||||
resource.name
|
||||
);
|
||||
|
||||
if (!embeddedResourcesNames.length)
|
||||
return callback(
|
||||
new Error(`${resource.name} do not have image metadata!`)
|
||||
);
|
||||
|
||||
const images = embeddedResourcesNames.reduce<{
|
||||
[key: string]: PIXI.Texture;
|
||||
}>((imagesMap, embeddedResourceName) => {
|
||||
const mappedResourceName = game.resolveEmbeddedResource(
|
||||
resource.name,
|
||||
embeddedResourceName
|
||||
);
|
||||
imagesMap[
|
||||
embeddedResourceName
|
||||
] = this._imageManager.getOrLoadPIXITexture(mappedResourceName);
|
||||
|
||||
return imagesMap;
|
||||
}, {});
|
||||
const onLoad = (atlas: pixi_spine.TextureAtlas) => {
|
||||
this._loadedSpineAtlases.set(resource, atlas);
|
||||
callback(null, atlas);
|
||||
};
|
||||
|
||||
PIXI.Assets.setPreferences({
|
||||
preferWorkers: false,
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(
|
||||
resource.file
|
||||
)
|
||||
? 'use-credentials'
|
||||
: 'anonymous',
|
||||
});
|
||||
PIXI.Assets.add(resource.name, resource.file, { images });
|
||||
PIXI.Assets.load<pixi_spine.TextureAtlas | string>(resource.name).then(
|
||||
(atlas) => {
|
||||
/**
|
||||
* Ideally atlas of TextureAtlas should be passed here
|
||||
* but there is known issue in case of preloaded images (see https://github.com/pixijs/spine/issues/537)
|
||||
*
|
||||
* Here covered all possible ways to make it work fine if issue is fixed in pixi-spine or after migration to spine-pixi
|
||||
*/
|
||||
if (typeof atlas === 'string') {
|
||||
new pixi_spine.TextureAtlas(
|
||||
atlas,
|
||||
(textureName, textureCb) =>
|
||||
textureCb(images[textureName].baseTexture),
|
||||
onLoad
|
||||
);
|
||||
} else {
|
||||
onLoad(atlas);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given atlas resource was loaded (preloaded or loaded with `load`).
|
||||
* @param resourceName The name of the atlas resource.
|
||||
* @returns true if the content of the atlas resource is loaded, false otherwise.
|
||||
*/
|
||||
isLoaded(resourceName: string): boolean {
|
||||
return !!this._loadedSpineAtlases.getFromName(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Pixi TextureAtlas for the given resource that is already loaded (preloaded or loaded with `load`).
|
||||
* If the resource is not loaded, `null` will be returned.
|
||||
* @param resourceName The name of the atlas resource.
|
||||
* @returns the TextureAtlas of the atlas if loaded, `null` otherwise.
|
||||
*/
|
||||
getAtlasTexture(resourceName: string): pixi_spine.TextureAtlas | null {
|
||||
return this._loadedSpineAtlases.getFromName(resourceName);
|
||||
}
|
||||
|
||||
private _getAtlasResource(resourceName: string): ResourceData | null {
|
||||
const resource = this._resourceLoader.getResource(resourceName);
|
||||
return resource && this.getResourceKinds().includes(resource.kind)
|
||||
? resource
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
120
Extensions/Spine/managers/pixi-spine-manager.ts
Normal file
120
Extensions/Spine/managers/pixi-spine-manager.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* GDevelop JS Platform
|
||||
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Spine Manager');
|
||||
|
||||
const resourceKinds: ResourceKind[] = ['spine'];
|
||||
|
||||
/**
|
||||
* SpineManager manages pixi spine skeleton data.
|
||||
*/
|
||||
export class SpineManager implements gdjs.ResourceManager {
|
||||
private _spineAtlasManager: SpineAtlasManager;
|
||||
private _resourceLoader: ResourceLoader;
|
||||
private _loadedSpines = new gdjs.ResourceCache<pixi_spine.ISkeletonData>();
|
||||
|
||||
/**
|
||||
* @param resourceDataArray The resources data of the game.
|
||||
* @param resourcesLoader The resources loader of the game.
|
||||
*/
|
||||
constructor(
|
||||
resourceLoader: gdjs.ResourceLoader,
|
||||
spineAtlasManager: SpineAtlasManager
|
||||
) {
|
||||
this._resourceLoader = resourceLoader;
|
||||
this._spineAtlasManager = spineAtlasManager;
|
||||
}
|
||||
|
||||
getResourceKinds(): ResourceKind[] {
|
||||
return resourceKinds;
|
||||
}
|
||||
|
||||
async processResource(resourceName: string): Promise<void> {
|
||||
// Do nothing because pixi-spine parses resources by itself.
|
||||
}
|
||||
|
||||
async loadResource(resourceName: string): Promise<void> {
|
||||
const resource = this._getSpineResource(resourceName);
|
||||
|
||||
if (!resource) {
|
||||
return logger.error(
|
||||
`Unable to find spine json for resource ${resourceName}.`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const game = this._resourceLoader.getRuntimeGame();
|
||||
const embeddedResourcesNames = game.getEmbeddedResourcesNames(
|
||||
resource.name
|
||||
);
|
||||
|
||||
// there should be exactly one file which is pointing to atlas
|
||||
if (embeddedResourcesNames.length !== 1) {
|
||||
return logger.error(
|
||||
`Unable to find atlas metadata for resource spine json ${resourceName}.`
|
||||
);
|
||||
}
|
||||
|
||||
const atlasResourceName = game.resolveEmbeddedResource(
|
||||
resource.name,
|
||||
embeddedResourcesNames[0]
|
||||
);
|
||||
const spineAtlas = await this._spineAtlasManager.getOrLoad(
|
||||
atlasResourceName
|
||||
);
|
||||
PIXI.Assets.setPreferences({
|
||||
preferWorkers: false,
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(
|
||||
resource.file
|
||||
)
|
||||
? 'use-credentials'
|
||||
: 'anonymous',
|
||||
});
|
||||
PIXI.Assets.add(resource.name, resource.file, { spineAtlas });
|
||||
const loadedJson = await PIXI.Assets.load(resource.name);
|
||||
|
||||
if (loadedJson.spineData) {
|
||||
this._loadedSpines.set(resource, loadedJson.spineData);
|
||||
} else {
|
||||
logger.error(
|
||||
`Loader cannot process spine resource ${resource.name} correctly.`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error while preloading spine resource ${resource.name}: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object for the given resource that is already loaded (preloaded or loaded with `loadJson`).
|
||||
* If the resource is not loaded, `null` will be returned.
|
||||
*
|
||||
* @param resourceName The name of the spine skeleton.
|
||||
* @returns the spine skeleton if loaded, `null` otherwise.
|
||||
*/
|
||||
getSpine(resourceName: string): pixi_spine.ISkeletonData | null {
|
||||
return this._loadedSpines.getFromName(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given spine skeleton was loaded.
|
||||
* @param resourceName The name of the spine skeleton.
|
||||
* @returns true if the content of the spine skeleton is loaded, false otherwise.
|
||||
*/
|
||||
isSpineLoaded(resourceName: string): boolean {
|
||||
return !!this._loadedSpines.getFromName(resourceName);
|
||||
}
|
||||
|
||||
private _getSpineResource(resourceName: string): ResourceData | null {
|
||||
const resource = this._resourceLoader.getResource(resourceName);
|
||||
return resource && this.getResourceKinds().includes(resource.kind)
|
||||
? resource
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
202
Extensions/Spine/spineruntimeobject-pixi-renderer.ts
Normal file
202
Extensions/Spine/spineruntimeobject-pixi-renderer.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
namespace gdjs {
|
||||
const isSpine = (obj: any): obj is pixi_spine.Spine =>
|
||||
obj instanceof pixi_spine.Spine;
|
||||
|
||||
export class SpineRuntimeObjectPixiRenderer {
|
||||
private _object: gdjs.SpineRuntimeObject;
|
||||
private _rendererObject: pixi_spine.Spine | PIXI.Container;
|
||||
private _isAnimationComplete = true;
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
* @param instanceContainer The container in which the object is
|
||||
*/
|
||||
constructor(
|
||||
runtimeObject: gdjs.SpineRuntimeObject,
|
||||
private instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = runtimeObject;
|
||||
this._rendererObject = this.constructRendererObject();
|
||||
if (isSpine(this._rendererObject)) {
|
||||
this._rendererObject.autoUpdate = false;
|
||||
}
|
||||
|
||||
this.updatePosition();
|
||||
this.updateAngle();
|
||||
this.updateOpacity();
|
||||
this.updateScale();
|
||||
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(this._rendererObject, runtimeObject.getZOrder());
|
||||
}
|
||||
|
||||
updateAnimation(timeDelta: float) {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return;
|
||||
}
|
||||
this._rendererObject.update(timeDelta);
|
||||
}
|
||||
|
||||
getRendererObject(): pixi_spine.Spine | PIXI.Container {
|
||||
return this._rendererObject;
|
||||
}
|
||||
|
||||
getOriginOffset(): PIXI.Point {
|
||||
if (!isSpine(this._rendererObject)) return new PIXI.Point(0, 0);
|
||||
|
||||
const localBounds = this._rendererObject.getLocalBounds(undefined, true);
|
||||
|
||||
return new PIXI.Point(
|
||||
localBounds.x * this._rendererObject.scale.x,
|
||||
localBounds.y * this._rendererObject.scale.y
|
||||
);
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
this._rendererObject.destroy();
|
||||
}
|
||||
|
||||
updateScale(): void {
|
||||
const scaleX = Math.max(
|
||||
this._object._originalScale * this._object.getScaleX(),
|
||||
0
|
||||
);
|
||||
const scaleY = Math.max(
|
||||
this._object._originalScale * this._object.getScaleY(),
|
||||
0
|
||||
);
|
||||
this._rendererObject.scale.x = this._object.isFlippedX()
|
||||
? -scaleX
|
||||
: scaleX;
|
||||
this._rendererObject.scale.y = this._object.isFlippedY()
|
||||
? -scaleY
|
||||
: scaleY;
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
this._rendererObject.position.x = this._object.x;
|
||||
this._rendererObject.position.y = this._object.y;
|
||||
}
|
||||
|
||||
updateAngle(): void {
|
||||
this._rendererObject.rotation = gdjs.toRad(this._object.angle);
|
||||
}
|
||||
|
||||
updateOpacity(): void {
|
||||
this._rendererObject.alpha = this._object.getOpacity() / 255;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._rendererObject.width;
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._rendererObject.height;
|
||||
}
|
||||
|
||||
setWidth(width: float): void {
|
||||
this._rendererObject.width = width;
|
||||
}
|
||||
|
||||
setHeight(height: float): void {
|
||||
this._rendererObject.height = height;
|
||||
}
|
||||
|
||||
getUnscaledWidth(): float {
|
||||
return Math.abs(
|
||||
(this._rendererObject.width * this._object._originalScale) /
|
||||
this._rendererObject.scale.x
|
||||
);
|
||||
}
|
||||
|
||||
getUnscaledHeight(): float {
|
||||
return Math.abs(
|
||||
(this._rendererObject.height * this._object._originalScale) /
|
||||
this._rendererObject.scale.y
|
||||
);
|
||||
}
|
||||
|
||||
setMixing(from: string, to: string, duration: number): void {
|
||||
if (!isSpine(this._rendererObject)) return;
|
||||
|
||||
this._rendererObject.stateData.setMix(from, to, duration);
|
||||
}
|
||||
|
||||
setAnimation(animation: string, loop: boolean): void {
|
||||
if (isSpine(this._rendererObject)) {
|
||||
const onCompleteListener: pixi_spine.IAnimationStateListener = {
|
||||
complete: () => {
|
||||
this._isAnimationComplete = true;
|
||||
(this._rendererObject as pixi_spine.Spine).state.removeListener(
|
||||
onCompleteListener
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
this._isAnimationComplete = false;
|
||||
this._rendererObject.state.addListener(onCompleteListener);
|
||||
this._rendererObject.state.setAnimation(0, animation, loop);
|
||||
this._rendererObject.update(0);
|
||||
}
|
||||
}
|
||||
|
||||
getAnimationDuration(sourceAnimationName: string) {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return 0;
|
||||
}
|
||||
const animation = this._rendererObject.spineData.findAnimation(
|
||||
sourceAnimationName
|
||||
);
|
||||
return animation ? animation.duration : 0;
|
||||
}
|
||||
|
||||
getAnimationElapsedTime(): number {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return 0;
|
||||
}
|
||||
const tracks = this._rendererObject.state.tracks;
|
||||
if (tracks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
// This should be fine because only 1 track is used.
|
||||
const track = tracks[0];
|
||||
// @ts-ignore TrackEntry.getAnimationTime is not exposed.
|
||||
return track.getAnimationTime();
|
||||
}
|
||||
|
||||
setAnimationElapsedTime(time: number): void {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return;
|
||||
}
|
||||
const tracks = this._rendererObject.state.tracks;
|
||||
if (tracks.length === 0) {
|
||||
return;
|
||||
}
|
||||
const track = tracks[0];
|
||||
track.trackTime = time;
|
||||
}
|
||||
|
||||
isAnimationComplete(): boolean {
|
||||
return this._isAnimationComplete;
|
||||
}
|
||||
|
||||
private constructRendererObject(): pixi_spine.Spine | PIXI.Container {
|
||||
const game = this.instanceContainer.getGame();
|
||||
const spineManager = game.getSpineManager();
|
||||
|
||||
if (
|
||||
!spineManager ||
|
||||
!spineManager.isSpineLoaded(this._object.spineResourceName)
|
||||
) {
|
||||
return new PIXI.Container();
|
||||
}
|
||||
|
||||
return new pixi_spine.Spine(
|
||||
spineManager.getSpine(this._object.spineResourceName)!
|
||||
);
|
||||
}
|
||||
}
|
||||
export const SpineRuntimeObjectRenderer = SpineRuntimeObjectPixiRenderer;
|
||||
}
|
406
Extensions/Spine/spineruntimeobject.ts
Normal file
406
Extensions/Spine/spineruntimeobject.ts
Normal file
@@ -0,0 +1,406 @@
|
||||
namespace gdjs {
|
||||
type SpineAnimation = { name: string; source: string; loop: boolean };
|
||||
|
||||
export type SpineObjectDataType = {
|
||||
content: {
|
||||
opacity: float;
|
||||
scale: float;
|
||||
timeScale: float;
|
||||
spineResourceName: string;
|
||||
animations: SpineAnimation[];
|
||||
};
|
||||
};
|
||||
export type SpineObjectData = ObjectData & SpineObjectDataType;
|
||||
|
||||
export class SpineRuntimeObject
|
||||
extends gdjs.RuntimeObject
|
||||
implements
|
||||
gdjs.Resizable,
|
||||
gdjs.Scalable,
|
||||
gdjs.Animatable,
|
||||
gdjs.OpacityHandler {
|
||||
private _opacity: float = 255;
|
||||
private _scaleX: number = 1;
|
||||
private _scaleY: number = 1;
|
||||
_originalScale: number;
|
||||
private _flippedX: boolean = false;
|
||||
private _flippedY: boolean = false;
|
||||
private _animations: SpineAnimation[];
|
||||
private _currentAnimationIndex = -1;
|
||||
private _animationSpeedScale: float = 1;
|
||||
private _animationPaused: boolean = false;
|
||||
private _isPausedFrameDirty = false;
|
||||
/** The duration in second for the smooth transition between 2 animations */
|
||||
private _animationMixingDuration: number;
|
||||
private _renderer: gdjs.SpineRuntimeObjectPixiRenderer;
|
||||
|
||||
readonly spineResourceName: string;
|
||||
|
||||
/**
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param objectData The object data used to initialize the object
|
||||
*/
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
objectData: SpineObjectData
|
||||
) {
|
||||
super(instanceContainer, objectData);
|
||||
|
||||
this._animations = objectData.content.animations;
|
||||
this._originalScale = objectData.content.scale;
|
||||
this.spineResourceName = objectData.content.spineResourceName;
|
||||
this._animationMixingDuration = 0.1;
|
||||
this._renderer = new gdjs.SpineRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer
|
||||
);
|
||||
this.setAnimationIndex(0);
|
||||
this._renderer.updateAnimation(0);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (this._animationPaused) {
|
||||
if (this._isPausedFrameDirty) {
|
||||
this._renderer.updateAnimation(0);
|
||||
this.invalidateHitboxes();
|
||||
this._isPausedFrameDirty = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const elapsedTime = this.getElapsedTime() / 1000;
|
||||
this._renderer.updateAnimation(elapsedTime * this._animationSpeedScale);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
getRendererObject(): pixi_spine.Spine | PIXI.Container {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
updateFromObjectData(
|
||||
oldObjectData: SpineObjectData,
|
||||
newObjectData: SpineObjectData
|
||||
): boolean {
|
||||
super.updateFromObjectData(oldObjectData, newObjectData);
|
||||
|
||||
if (oldObjectData.content.scale !== newObjectData.content.scale) {
|
||||
this._originalScale = newObjectData.content.scale;
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(
|
||||
initialInstanceData: InstanceData
|
||||
): void {
|
||||
const animationData = initialInstanceData.numberProperties.find(
|
||||
(data) => data.name === 'animation'
|
||||
);
|
||||
const animationIndex = animationData
|
||||
? animationData.value
|
||||
: this._currentAnimationIndex;
|
||||
|
||||
this.setAnimationIndexWithMixing(animationIndex, 0);
|
||||
|
||||
if (initialInstanceData.customSize) {
|
||||
this.setSize(initialInstanceData.width, initialInstanceData.height);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
}
|
||||
|
||||
getDrawableX(): number {
|
||||
const originOffset = this._renderer.getOriginOffset();
|
||||
|
||||
return this.getX() + originOffset.x;
|
||||
}
|
||||
|
||||
getDrawableY(): number {
|
||||
const originOffset = this._renderer.getOriginOffset();
|
||||
|
||||
return this.getY() + originOffset.y;
|
||||
}
|
||||
|
||||
onDestroyed(): void {
|
||||
super.onDestroyed();
|
||||
this._renderer.onDestroy();
|
||||
}
|
||||
|
||||
setX(x: float): void {
|
||||
super.setX(x);
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
setY(y: float): void {
|
||||
super.setY(y);
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
setAngle(angle: float): void {
|
||||
super.setAngle(angle);
|
||||
this._renderer.updateAngle();
|
||||
}
|
||||
|
||||
setOpacity(opacity: float): void {
|
||||
this._opacity = Math.max(0, Math.min(255, opacity));
|
||||
this._renderer.updateOpacity();
|
||||
}
|
||||
|
||||
getOpacity(): float {
|
||||
return this._opacity;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._renderer.getWidth();
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._renderer.getHeight();
|
||||
}
|
||||
|
||||
setWidth(newWidth: float): void {
|
||||
const unscaledWidth = this._renderer.getUnscaledWidth();
|
||||
if (unscaledWidth !== 0) {
|
||||
this.setScaleX(newWidth / unscaledWidth);
|
||||
}
|
||||
}
|
||||
|
||||
setHeight(newHeight: float): void {
|
||||
const unscaledHeight = this._renderer.getUnscaledHeight();
|
||||
if (unscaledHeight !== 0) {
|
||||
this.setScaleY(newHeight / unscaledHeight);
|
||||
}
|
||||
}
|
||||
|
||||
setSize(newWidth: number, newHeight: number): void {
|
||||
this.setWidth(newWidth);
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
setScale(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (
|
||||
newScale === Math.abs(this._scaleX) &&
|
||||
newScale === Math.abs(this._scaleY)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._scaleX = newScale * (this._flippedX ? -1 : 1);
|
||||
this._scaleY = newScale * (this._flippedY ? -1 : 1);
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
setScaleX(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleX)) {
|
||||
return;
|
||||
}
|
||||
this._scaleX = newScale * (this._flippedX ? -1 : 1);
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
setScaleY(newScale: float): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleY)) {
|
||||
return;
|
||||
}
|
||||
this._scaleY = newScale * (this._flippedY ? -1 : 1);
|
||||
this._renderer.updateScale();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*
|
||||
* @return the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*/
|
||||
getScale(): float {
|
||||
const scaleX = Math.abs(this._scaleX);
|
||||
const scaleY = Math.abs(this._scaleY);
|
||||
return scaleX === scaleY ? scaleX : Math.sqrt(scaleX * scaleY);
|
||||
}
|
||||
|
||||
getScaleY(): float {
|
||||
return Math.abs(this._scaleY);
|
||||
}
|
||||
|
||||
getScaleX(): float {
|
||||
return Math.abs(this._scaleX);
|
||||
}
|
||||
|
||||
isFlippedX(): boolean {
|
||||
return this._flippedX;
|
||||
}
|
||||
|
||||
isFlippedY(): boolean {
|
||||
return this._flippedY;
|
||||
}
|
||||
|
||||
flipX(enable: boolean) {
|
||||
if (enable !== this._flippedX) {
|
||||
this._scaleX *= -1;
|
||||
this._flippedX = enable;
|
||||
this.invalidateHitboxes();
|
||||
this._renderer.updateScale();
|
||||
}
|
||||
}
|
||||
|
||||
flipY(enable: boolean) {
|
||||
if (enable !== this._flippedY) {
|
||||
this._scaleY *= -1;
|
||||
this._flippedY = enable;
|
||||
this.invalidateHitboxes();
|
||||
this._renderer.updateScale();
|
||||
}
|
||||
}
|
||||
|
||||
setAnimationIndex(animationIndex: number): void {
|
||||
this.setAnimationIndexWithMixing(
|
||||
animationIndex,
|
||||
this._animationMixingDuration
|
||||
);
|
||||
}
|
||||
|
||||
setAnimationIndexWithMixing(
|
||||
animationIndex: number,
|
||||
mixingDuration: number
|
||||
): void {
|
||||
if (
|
||||
this._animations.length === 0 ||
|
||||
this._currentAnimationIndex === animationIndex ||
|
||||
!this.isAnimationIndex(animationIndex)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const previousAnimation = this._animations[this._currentAnimationIndex];
|
||||
const newAnimation = this._animations[animationIndex];
|
||||
this._currentAnimationIndex = animationIndex;
|
||||
|
||||
if (previousAnimation) {
|
||||
this._renderer.setMixing(
|
||||
previousAnimation.source,
|
||||
newAnimation.source,
|
||||
mixingDuration
|
||||
);
|
||||
}
|
||||
this._renderer.setAnimation(newAnimation.source, newAnimation.loop);
|
||||
this._isPausedFrameDirty = true;
|
||||
}
|
||||
|
||||
setAnimationName(animationName: string): void {
|
||||
this.setAnimationNameWithMixing(
|
||||
animationName,
|
||||
this._animationMixingDuration
|
||||
);
|
||||
}
|
||||
|
||||
setAnimationNameWithMixing(
|
||||
animationName: string,
|
||||
mixingDuration: number
|
||||
): void {
|
||||
this.setAnimationIndexWithMixing(
|
||||
this.getAnimationIndexFor(animationName),
|
||||
mixingDuration
|
||||
);
|
||||
}
|
||||
|
||||
getAnimationIndexFor(animationName: string): number {
|
||||
return this._animations.findIndex(
|
||||
(animation) => animation.name === animationName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the duration in second for the smooth transition between 2 animations.
|
||||
*/
|
||||
getAnimationMixingDuration(): number {
|
||||
return this._animationMixingDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the duration in second for the smooth transition between 2 animations.
|
||||
*/
|
||||
setAnimationMixingDuration(animationMixingDuration: number): void {
|
||||
this._animationMixingDuration = animationMixingDuration;
|
||||
}
|
||||
|
||||
getAnimationIndex(): number {
|
||||
return this._currentAnimationIndex;
|
||||
}
|
||||
|
||||
getAnimationName(): string {
|
||||
return this.isAnimationIndex(this._currentAnimationIndex)
|
||||
? this._animations[this._currentAnimationIndex].name
|
||||
: '';
|
||||
}
|
||||
|
||||
isAnimationIndex(animationIndex: number): boolean {
|
||||
return (
|
||||
Number.isInteger(animationIndex) &&
|
||||
animationIndex >= 0 &&
|
||||
animationIndex < this._animations.length
|
||||
);
|
||||
}
|
||||
|
||||
hasAnimationEnded(): boolean {
|
||||
return this._renderer.isAnimationComplete();
|
||||
}
|
||||
|
||||
isAnimationPaused() {
|
||||
return this._animationPaused;
|
||||
}
|
||||
|
||||
pauseAnimation() {
|
||||
this._animationPaused = true;
|
||||
}
|
||||
|
||||
resumeAnimation() {
|
||||
this._animationPaused = false;
|
||||
}
|
||||
|
||||
getAnimationSpeedScale() {
|
||||
return this._animationSpeedScale;
|
||||
}
|
||||
|
||||
setAnimationSpeedScale(ratio: float): void {
|
||||
this._animationSpeedScale = ratio;
|
||||
}
|
||||
|
||||
getAnimationElapsedTime(): number {
|
||||
if (this._animations.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this._renderer.getAnimationElapsedTime();
|
||||
}
|
||||
|
||||
setAnimationElapsedTime(time: number): void {
|
||||
if (this._animations.length === 0) {
|
||||
return;
|
||||
}
|
||||
this._renderer.setAnimationElapsedTime(time);
|
||||
this._isPausedFrameDirty = true;
|
||||
}
|
||||
|
||||
getAnimationDuration(): number {
|
||||
if (this._animations.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this._renderer.getAnimationDuration(
|
||||
this._animations[this._currentAnimationIndex].source
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.registerObject('SpineObject::SpineObject', gdjs.SpineRuntimeObject);
|
||||
}
|
@@ -893,7 +893,7 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectPositionXTween2');
|
||||
|
||||
// deprecated use the 3D Tween extension
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
'AddObjectPositionZTween',
|
||||
@@ -926,6 +926,38 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectPositionZTween');
|
||||
|
||||
behavior
|
||||
.addAction(
|
||||
'AddObjectPositionZTween2',
|
||||
_('Tween object Z position'),
|
||||
_(
|
||||
'Tweens an object Z position (3D objects only) from its current Z position to a new one.'
|
||||
),
|
||||
_(
|
||||
'Tween the Z position of _PARAM0_ to _PARAM4_ with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Position'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To Z'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectPositionZTween2');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1079,6 +1111,38 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectDepthTween');
|
||||
|
||||
behavior
|
||||
.addAction(
|
||||
'AddObjectDepthTween2',
|
||||
_('Tween object depth'),
|
||||
_(
|
||||
'Tweens an object depth (suitable 3D objects only) from its current depth to a new one.'
|
||||
),
|
||||
_(
|
||||
'Tween the depth of _PARAM0_ to _PARAM4_ with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Size'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To depth'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectDepthTween2');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1203,6 +1267,66 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectAngleTween2');
|
||||
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectRotationXTween',
|
||||
_('Tween object rotation on X axis'),
|
||||
_('Tweens an object rotation on X axis from its current angle to a new one.'),
|
||||
_(
|
||||
'Tween the rotation on X axis of _PARAM0_ to _PARAM4_° with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Angle'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectRotationXTween');
|
||||
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectRotationYTween',
|
||||
_('Tween object rotation on Y axis'),
|
||||
_('Tweens an object rotation on Y axis from its current angle to a new one.'),
|
||||
_(
|
||||
'Tween the rotation on Y axis of _PARAM0_ to _PARAM4_° with easing _PARAM5_ over _PARAM6_ seconds as _PARAM3_'
|
||||
),
|
||||
_('Angle'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter("behavior", _("3D capability"), "Scene3D::Base3DBehavior")
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectRotationYTween');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
@@ -1239,6 +1363,7 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectScaleTween');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectScaleTween2',
|
||||
@@ -1253,6 +1378,7 @@ module.exports = {
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.setHidden()
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
@@ -1273,6 +1399,39 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectScaleTween2');
|
||||
|
||||
behavior
|
||||
.addScopedAction(
|
||||
'AddObjectScaleTween3',
|
||||
_('Tween object scale'),
|
||||
_(
|
||||
'Tweens an object scale from its current value to a new one (note: the scale can never be 0 or less).'
|
||||
),
|
||||
_(
|
||||
'Tween the scale of _PARAM0_ to _PARAM3_ (from center: _PARAM7_) with easing _PARAM4_ over _PARAM5_ seconds as _PARAM2_'
|
||||
),
|
||||
_('Size'),
|
||||
'JsPlatform/Extensions/tween_behavior24.png',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Destroy this object when tween finishes'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.setDefaultValue('no')
|
||||
.addParameter('yesorno', _('Scale from center of object'), '', false)
|
||||
.setDefaultValue('no')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('addObjectScaleTween3');
|
||||
|
||||
// deprecated
|
||||
behavior
|
||||
.addAction(
|
||||
|
@@ -10,210 +10,256 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
};
|
||||
|
||||
/** @type {gdjs.RuntimeScene} */
|
||||
let layout;
|
||||
let runtimeScene;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
beforeEach(() => {
|
||||
layout = createScene();
|
||||
layout.getLayer('').setTimeScale(1.5);
|
||||
runtimeScene = createScene();
|
||||
runtimeScene.getLayer('').setTimeScale(1.5);
|
||||
});
|
||||
|
||||
const tween = gdjs.evtTools.tween;
|
||||
const camera = gdjs.evtTools.camera;
|
||||
|
||||
it("can get default values for tweens that don't exist", () => {
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(0);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(0);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(0);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(0);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
});
|
||||
|
||||
it('can play a tween till the end', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
|
||||
// Tween actions don't change the value directly.
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(200);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(200);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(0);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(200);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(200);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(0);
|
||||
|
||||
let oldAngle;
|
||||
let oldValue;
|
||||
let oldProgress;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
oldAngle = camera.getCameraRotation(layout, '', 0);
|
||||
oldValue = tween.getValue(layout, 'MyTween');
|
||||
oldProgress = tween.getProgress(layout, 'MyTween');
|
||||
oldAngle = camera.getCameraRotation(runtimeScene, '', 0);
|
||||
oldValue = tween.getValue(runtimeScene, 'MyTween');
|
||||
oldProgress = tween.getProgress(runtimeScene, 'MyTween');
|
||||
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be.above(oldAngle);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be.above(oldValue);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be.above(oldProgress);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be.above(
|
||||
oldAngle
|
||||
);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be.above(oldValue);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be.above(
|
||||
oldProgress
|
||||
);
|
||||
}
|
||||
// The tween reaches the end
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(600);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(600);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(1);
|
||||
|
||||
// The value is not changed after the tween is finished
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(600);
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(layout, 'MyTween')).to.be(1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(600);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(1);
|
||||
|
||||
// The value is not set to the targeted value over and over
|
||||
// after the tween is finished.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
});
|
||||
|
||||
it('can pause and resume a tween', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
|
||||
// The tween starts
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
|
||||
// Pause the tween
|
||||
tween.pauseSceneTween(layout, 'MyTween');
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
tween.pauseSceneTween(runtimeScene, 'MyTween');
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
}
|
||||
|
||||
// The value is not overridden during the pause.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
// Resume the tween
|
||||
tween.resumeSceneTween(layout, 'MyTween');
|
||||
tween.resumeSceneTween(runtimeScene, 'MyTween');
|
||||
|
||||
// Tween actions don't change the value directly.
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(440);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(440);
|
||||
});
|
||||
|
||||
it('can stop and restart a tween', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
|
||||
// Start the tween
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
|
||||
// Stop the tween
|
||||
tween.stopSceneTween(layout, 'MyTween', false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
tween.stopSceneTween(runtimeScene, 'MyTween', false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
}
|
||||
|
||||
// The value is not overridden by a stopped tween.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
// A stopped tween can't be resumed.
|
||||
tween.resumeSceneTween(layout, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
tween.resumeSceneTween(runtimeScene, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(true);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
|
||||
// Restart the tween
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 623, '', 'linear', 0.25);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
623,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(373);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(373);
|
||||
});
|
||||
|
||||
it('can remove and recreate a tween', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
|
||||
// Start the tween
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
|
||||
// Remove the tween
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
tween.removeSceneTween(layout, 'MyTween');
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
tween.removeSceneTween(runtimeScene, 'MyTween');
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(400);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(400);
|
||||
}
|
||||
|
||||
// The value is not overridden after the tween has been removed.
|
||||
camera.setCameraRotation(layout, 123, '', 0);
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
camera.setCameraRotation(runtimeScene, 123, '', 0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
// A removed tween can't be resumed.
|
||||
tween.resumeSceneTween(layout, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
tween.resumeSceneTween(runtimeScene, 'MyTween');
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(123);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
|
||||
// Recreate the tween
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 623, '', 'linear', 0.25);
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(false);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
623,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
layout.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(layout, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(layout, 'MyTween')).to.be(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
}
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(373);
|
||||
expect(tween.sceneTweenExists(layout, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(373);
|
||||
expect(tween.sceneTweenExists(runtimeScene, 'MyTween')).to.be(true);
|
||||
});
|
||||
|
||||
const checkProgress = (steps, getValueFunctions) => {
|
||||
@@ -222,7 +268,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
}
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const oldValues = getValueFunctions.map((getValue) => getValue());
|
||||
layout.renderAndStep(1000 / 60);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
for (let index = 0; index < oldValues.length; index++) {
|
||||
expect(getValueFunctions[index]()).not.to.be(oldValues[index]);
|
||||
@@ -231,10 +277,10 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
};
|
||||
|
||||
it('can tween a scene variable', () => {
|
||||
const variable = layout.getVariables().get('MyVariable');
|
||||
const variable = runtimeScene.getVariables().get('MyVariable');
|
||||
variable.setNumber(200);
|
||||
tween.tweenVariableNumber3(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
variable,
|
||||
600,
|
||||
@@ -247,7 +293,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
|
||||
it('can tween a layer value', () => {
|
||||
tween.addLayerValueTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
200,
|
||||
600,
|
||||
@@ -256,13 +302,13 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
false,
|
||||
''
|
||||
);
|
||||
checkProgress(6, () => tween.getValue(layout, 'MyTween'));
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(440);
|
||||
checkProgress(6, () => tween.getValue(runtimeScene, 'MyTween'));
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a layout value', () => {
|
||||
tween.addLayoutValueTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
200,
|
||||
600,
|
||||
@@ -270,39 +316,69 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
0.25 / 1.5,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => tween.getValue(layout, 'MyTween'));
|
||||
expect(tween.getValue(layout, 'MyTween')).to.be(440);
|
||||
checkProgress(6, () => tween.getValue(runtimeScene, 'MyTween'));
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a layer camera position', () => {
|
||||
camera.setCameraX(layout, 200, '', 0);
|
||||
camera.setCameraY(layout, 300, '', 0);
|
||||
tween.tweenCamera2(layout, 'MyTween', 600, 900, '', 'linear', 0.25);
|
||||
camera.setCameraX(runtimeScene, 200, '', 0);
|
||||
camera.setCameraY(runtimeScene, 300, '', 0);
|
||||
tween.tweenCamera2(runtimeScene, 'MyTween', 600, 900, '', 'linear', 0.25);
|
||||
checkProgress(6, [
|
||||
() => camera.getCameraX(layout, '', 0),
|
||||
() => camera.getCameraY(layout, '', 0),
|
||||
() => camera.getCameraX(runtimeScene, '', 0),
|
||||
() => camera.getCameraY(runtimeScene, '', 0),
|
||||
]);
|
||||
expect(camera.getCameraX(layout, '', 0)).to.be(440);
|
||||
expect(camera.getCameraY(layout, '', 0)).to.be(660);
|
||||
expect(camera.getCameraX(runtimeScene, '', 0)).to.be(440);
|
||||
expect(camera.getCameraY(runtimeScene, '', 0)).to.be(660);
|
||||
});
|
||||
|
||||
it('can tween a layer camera zoom', () => {
|
||||
camera.setCameraZoom(layout, 200, '', 0);
|
||||
tween.tweenCameraZoom2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
checkProgress(6, () => camera.getCameraZoom(layout, '', 0));
|
||||
camera.setCameraZoom(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraZoom2(runtimeScene, 'MyTween', 600, '', 'linear', 0.25);
|
||||
checkProgress(6, () => camera.getCameraZoom(runtimeScene, '', 0));
|
||||
// The interpolation is exponential.
|
||||
expect(camera.getCameraZoom(layout, '', 0)).to.be(386.6364089863524);
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(386.6364089863524);
|
||||
});
|
||||
|
||||
it('can tween a layer camera zoom to 0', () => {
|
||||
camera.setCameraZoom(runtimeScene, 1, '', 0);
|
||||
tween.tweenCameraZoom2(runtimeScene, 'MyTween', 0, '', 'linear', 0.25);
|
||||
// A camera zoom of 0 doesn't make sense.
|
||||
// Check that there is no NaN.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('can tween a layer camera zoom from 0', () => {
|
||||
camera.setCameraZoom(runtimeScene, 0, '', 0);
|
||||
tween.tweenCameraZoom2(runtimeScene, 'MyTween', 1, '', 'linear', 0.25);
|
||||
// A camera zoom of 0 doesn't make sense.
|
||||
// Check that there is no NaN.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(camera.getCameraZoom(runtimeScene, '', 0)).to.be(1);
|
||||
});
|
||||
|
||||
it('can tween a layer camera rotation', () => {
|
||||
camera.setCameraRotation(layout, 200, '', 0);
|
||||
tween.tweenCameraRotation2(layout, 'MyTween', 600, '', 'linear', 0.25);
|
||||
checkProgress(6, () => camera.getCameraRotation(layout, '', 0));
|
||||
expect(camera.getCameraRotation(layout, '', 0)).to.be(440);
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
'linear',
|
||||
0.25
|
||||
);
|
||||
checkProgress(6, () => camera.getCameraRotation(runtimeScene, '', 0));
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a number effect property', () => {
|
||||
const layer = layout.getLayer('');
|
||||
const layer = runtimeScene.getLayer('');
|
||||
layer.addEffect({
|
||||
effectType: 'Outline',
|
||||
name: 'MyEffect',
|
||||
@@ -311,7 +387,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
booleanParameters: {},
|
||||
});
|
||||
tween.tweenNumberEffectPropertyTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
600,
|
||||
'',
|
||||
@@ -329,7 +405,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
});
|
||||
|
||||
it('can tween a color effect property', () => {
|
||||
const layer = layout.getLayer('');
|
||||
const layer = runtimeScene.getLayer('');
|
||||
layer.addEffect({
|
||||
effectType: 'Outline',
|
||||
name: 'MyEffect',
|
||||
@@ -338,7 +414,7 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
booleanParameters: {},
|
||||
});
|
||||
tween.tweenColorEffectPropertyTween(
|
||||
layout,
|
||||
runtimeScene,
|
||||
'MyTween',
|
||||
'255;192;128',
|
||||
'',
|
||||
|
@@ -85,6 +85,32 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
const addCube = (runtimeScene) => {
|
||||
const object = new gdjs.Cube3DRuntimeObject(runtimeScene, {
|
||||
name: 'Cube',
|
||||
type: 'Scene3D::Cube3DObject',
|
||||
effects: [],
|
||||
variables: [],
|
||||
behaviors: [
|
||||
{
|
||||
type: 'Tween::TweenBehavior',
|
||||
name: behaviorName,
|
||||
},
|
||||
],
|
||||
// @ts-ignore
|
||||
content: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
depth: 64,
|
||||
},
|
||||
});
|
||||
runtimeScene.addObject(object);
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
@@ -123,6 +149,8 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
let object;
|
||||
/** @type {gdjs.SpriteRuntimeObject} */
|
||||
let sprite;
|
||||
/** @type {gdjs.Cube3DRuntimeObject} */
|
||||
let cube;
|
||||
/** @type {gdjs.TextRuntimeObject} */
|
||||
let textObject;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
@@ -130,18 +158,23 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let spriteBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let cubeBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let textObjectBehavior;
|
||||
beforeEach(() => {
|
||||
runtimeScene = createScene();
|
||||
runtimeScene.getLayer('').setTimeScale(1.5);
|
||||
object = addObject(runtimeScene);
|
||||
sprite = addSprite(runtimeScene);
|
||||
cube = addCube(runtimeScene);
|
||||
textObject = addTextObject(runtimeScene);
|
||||
//@ts-ignore
|
||||
behavior = object.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
spriteBehavior = sprite.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
cubeBehavior = cube.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
textObjectBehavior = textObject.getBehavior(behaviorName);
|
||||
});
|
||||
|
||||
@@ -216,6 +249,19 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getY()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the position on Z axis', () => {
|
||||
cube.setZ(200);
|
||||
cubeBehavior.addObjectPositionZTween(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
250 / 1.5,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getZ());
|
||||
expect(cube.getZ()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the angle', () => {
|
||||
object.setAngle(200);
|
||||
behavior.addObjectAngleTween('MyTween', 600, 'linear', 250 / 1.5, false);
|
||||
@@ -237,6 +283,19 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getHeight()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the depth', () => {
|
||||
cube.setDepth(200);
|
||||
cubeBehavior.addObjectDepthTween(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
250 / 1.5,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getDepth());
|
||||
expect(cube.getDepth()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the opacity', () => {
|
||||
sprite.setOpacity(128);
|
||||
spriteBehavior.addObjectOpacityTween(
|
||||
@@ -424,4 +483,46 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(sprite.getX()).to.be(-7580);
|
||||
expect(sprite.getY()).to.be(-11120);
|
||||
});
|
||||
|
||||
it('can tween the scales in seconds', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scales from center in seconds', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
true
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getX()).to.be(-5872.3650875632775);
|
||||
expect(sprite.getY()).to.be(-8558.547631344918);
|
||||
});
|
||||
});
|
||||
|
@@ -85,6 +85,32 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
const addCube = (runtimeScene) => {
|
||||
const object = new gdjs.Cube3DRuntimeObject(runtimeScene, {
|
||||
name: 'Cube',
|
||||
type: 'Scene3D::Cube3DObject',
|
||||
effects: [],
|
||||
variables: [],
|
||||
behaviors: [
|
||||
{
|
||||
type: 'Tween::TweenBehavior',
|
||||
name: behaviorName,
|
||||
},
|
||||
],
|
||||
// @ts-ignore
|
||||
content: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
depth: 64,
|
||||
},
|
||||
});
|
||||
runtimeScene.addObject(object);
|
||||
return object;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
*/
|
||||
@@ -123,6 +149,8 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
let object;
|
||||
/** @type {gdjs.SpriteRuntimeObject} */
|
||||
let sprite;
|
||||
/** @type {gdjs.Cube3DRuntimeObject} */
|
||||
let cube;
|
||||
/** @type {gdjs.TextRuntimeObject} */
|
||||
let textObject;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
@@ -130,18 +158,23 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let spriteBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let cubeBehavior;
|
||||
/** @type {gdjs.TweenRuntimeBehavior} */
|
||||
let textObjectBehavior;
|
||||
beforeEach(() => {
|
||||
runtimeScene = createScene();
|
||||
runtimeScene.getLayer('').setTimeScale(1.5);
|
||||
object = addObject(runtimeScene);
|
||||
sprite = addSprite(runtimeScene);
|
||||
cube = addCube(runtimeScene);
|
||||
textObject = addTextObject(runtimeScene);
|
||||
//@ts-ignore
|
||||
behavior = object.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
spriteBehavior = sprite.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
cubeBehavior = cube.getBehavior(behaviorName);
|
||||
//@ts-ignore
|
||||
textObjectBehavior = textObject.getBehavior(behaviorName);
|
||||
});
|
||||
|
||||
@@ -404,6 +437,20 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getY()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the position on Z axis', () => {
|
||||
cube.setZ(200);
|
||||
cubeBehavior.addObjectPositionZTween2(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getZ());
|
||||
expect(cube.getZ()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the angle', () => {
|
||||
object.setAngle(200);
|
||||
behavior.addObjectAngleTween2('MyTween', 600, 'linear', 0.25, false);
|
||||
@@ -411,6 +458,34 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getAngle()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the rotation X', () => {
|
||||
cube.setRotationX(200);
|
||||
cubeBehavior.addObjectRotationXTween(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getRotationX());
|
||||
expect(cube.getRotationX()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the rotation Y', () => {
|
||||
cube.setRotationY(200);
|
||||
cubeBehavior.addObjectRotationYTween(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getRotationY());
|
||||
expect(cube.getRotationY()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the width', () => {
|
||||
object.setWidth(200);
|
||||
behavior.addObjectWidthTween2('MyTween', 600, 'linear', 0.25, false);
|
||||
@@ -425,6 +500,20 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getHeight()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween the depth', () => {
|
||||
cube.setDepth(200);
|
||||
cubeBehavior.addObjectDepthTween2(
|
||||
null,
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getDepth());
|
||||
expect(cube.getDepth()).to.be(440);
|
||||
});
|
||||
|
||||
it('can tween a number effect property', () => {
|
||||
sprite.addEffect({
|
||||
effectType: 'Outline',
|
||||
@@ -540,6 +629,53 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on X axis to 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(1);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
0,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 0 directly.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(sprite.getScaleX()).to.be(0);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on X axis from 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(0);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
1,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 1 directly at the end.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getScaleX()).to.be(1);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on X axis from center', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
@@ -576,6 +712,53 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on Y axis to 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleY(1);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
0,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 0 directly.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(sprite.getScaleY()).to.be(0);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on Y axis from 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleY(0);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
1,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 1 directly at the end.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getScaleY()).to.be(1);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale on Y axis from center', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleY(200);
|
||||
@@ -623,45 +806,126 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getY()).to.be(660);
|
||||
});
|
||||
|
||||
it('can tween the scales', () => {
|
||||
it('can tween the scale', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
sprite.setScale(200);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
checkProgress(6, () => sprite.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getScale()).to.be(386.6364089863524);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale to 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScale(1);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
0,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 0 directly.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scale from 0', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScale(0);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
1,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
// The interpolation is exponential.
|
||||
// It would need an infinite speed to go away from 0.
|
||||
// This is why the scale is set to 1 directly at the end.
|
||||
for (let i = 0; i < 11; i++) {
|
||||
expect(sprite.getScale()).to.be(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(spriteBehavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(sprite.getScale()).to.be(1);
|
||||
expect(sprite.getX()).to.be(100);
|
||||
expect(sprite.getY()).to.be(400);
|
||||
});
|
||||
|
||||
it('can tween the scales from center', () => {
|
||||
sprite.setPosition(100, 400);
|
||||
sprite.setScaleX(200);
|
||||
sprite.setScaleY(300);
|
||||
spriteBehavior.addObjectScaleTween2(
|
||||
sprite.setScale(200);
|
||||
spriteBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
900,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
true
|
||||
);
|
||||
checkProgress(6, [() => sprite.getScaleX(), () => sprite.getScaleY()]);
|
||||
checkProgress(6, () => sprite.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(sprite.getScaleX()).to.be(386.6364089863524);
|
||||
expect(sprite.getScaleY()).to.be(579.9546134795287);
|
||||
expect(sprite.getScale()).to.be(386.6364089863524);
|
||||
expect(sprite.getX()).to.be(-5872.3650875632775);
|
||||
expect(sprite.getY()).to.be(-8558.547631344918);
|
||||
expect(sprite.getY()).to.be(-5572.3650875632775);
|
||||
});
|
||||
|
||||
it('can tween the scale of a cube', () => {
|
||||
cube.setPosition(100, 400);
|
||||
cube.setZ(800);
|
||||
cube.setScale(200);
|
||||
cubeBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
false
|
||||
);
|
||||
checkProgress(6, () => cube.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(cube.getScale()).to.be(386.6364089863524);
|
||||
expect(cube.getX()).to.be(100);
|
||||
expect(cube.getY()).to.be(400);
|
||||
expect(cube.getZ()).to.be(800);
|
||||
});
|
||||
|
||||
it('can tween the scales of a cube from center', () => {
|
||||
cube.setPosition(100, 400);
|
||||
cube.setZ(800);
|
||||
cube.setScale(200);
|
||||
cubeBehavior.addObjectScaleTween3(
|
||||
'MyTween',
|
||||
600,
|
||||
'linear',
|
||||
0.25,
|
||||
false,
|
||||
true
|
||||
);
|
||||
checkProgress(6, () => cube.getScale());
|
||||
// The interpolation is exponential.
|
||||
expect(cube.getScale()).to.be(386.6364089863524);
|
||||
expect(cube.getX()).to.be(-5872.3650875632775);
|
||||
expect(cube.getY()).to.be(-5572.3650875632775);
|
||||
expect(cube.getZ()).to.be(-5172.3650875632775);
|
||||
});
|
||||
});
|
||||
|
@@ -456,7 +456,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Tween an object Z position.
|
||||
* @deprecated Use the 3D Tween extension instead.
|
||||
* @deprecated Use addObjectPositionZTween2 instead.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toZ The target Z position
|
||||
* @param easing Easing function identifier
|
||||
@@ -469,14 +469,59 @@ namespace gdjs {
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectPositionZTween(
|
||||
identifier,
|
||||
toZ,
|
||||
easing,
|
||||
duration / 1000,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner.getRuntimeScene()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object Z position.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toZ The target Z position
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectPositionZTween2(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toZ: number,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectPositionZTween(
|
||||
identifier,
|
||||
toZ,
|
||||
easing,
|
||||
duration,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner
|
||||
);
|
||||
}
|
||||
|
||||
private _addObjectPositionZTween(
|
||||
identifier: string,
|
||||
toZ: number,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean,
|
||||
timeSource: gdjs.evtTools.tween.TimeSource
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner.getRuntimeScene(),
|
||||
duration / 1000,
|
||||
timeSource,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getZ(),
|
||||
@@ -558,6 +603,72 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween a 3D object rotation X.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toAngle The target angle
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectRotationXTween(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toAngle: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getRotationX(),
|
||||
toAngle,
|
||||
(value: float) => owner.setRotationX(value),
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween a 3D object rotation Y.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toAngle The target angle
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectRotationYTween(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toAngle: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getRotationY(),
|
||||
toAngle,
|
||||
(value: float) => owner.setRotationY(value),
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object scale.
|
||||
* @deprecated Use addObjectScaleTween2 instead.
|
||||
@@ -593,6 +704,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Tween an object scale.
|
||||
* @deprecated Use addObjectScaleXTween2 and addObjectScaleYTween2 instead.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toScaleX The target X-scale
|
||||
* @param toScaleY The target Y-scale
|
||||
@@ -666,6 +778,65 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object scale.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toScale The target scale
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
* @param scaleFromCenterOfObject Scale the transform from the center of the object (or point that is called center), not the top-left origin
|
||||
*/
|
||||
addObjectScaleTween3(
|
||||
identifier: string,
|
||||
toScale: number,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean,
|
||||
scaleFromCenterOfObject: boolean
|
||||
) {
|
||||
this._addObjectScaleXTween(
|
||||
identifier,
|
||||
toScale,
|
||||
easing,
|
||||
duration,
|
||||
destroyObjectWhenFinished,
|
||||
scaleFromCenterOfObject,
|
||||
this.owner,
|
||||
exponentialInterpolation
|
||||
);
|
||||
const owner = this.owner;
|
||||
if (!isScalable(owner)) return;
|
||||
|
||||
const owner3d = owner instanceof gdjs.RuntimeObject3D ? owner : null;
|
||||
|
||||
const setValue = scaleFromCenterOfObject
|
||||
? (scale: float) => {
|
||||
const oldX = owner.getCenterXInScene();
|
||||
const oldY = owner.getCenterYInScene();
|
||||
const oldZ = owner3d ? owner3d.getCenterZInScene() : 0;
|
||||
owner.setScale(scale);
|
||||
owner.setCenterXInScene(oldX);
|
||||
owner.setCenterYInScene(oldY);
|
||||
if (owner3d) {
|
||||
owner3d.setCenterZInScene(oldZ);
|
||||
}
|
||||
}
|
||||
: (scale: float) => owner.setScale(scale);
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner,
|
||||
duration,
|
||||
easing,
|
||||
exponentialInterpolation,
|
||||
owner.getScale(),
|
||||
toScale,
|
||||
setValue,
|
||||
destroyObjectWhenFinished ? () => this._deleteFromScene() : null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object X-scale.
|
||||
* @deprecated Use addObjectScaleXTween2 instead.
|
||||
@@ -1519,7 +1690,7 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Tween an object depth.
|
||||
* @deprecated Use the 3D Tween extension instead.
|
||||
* @deprecated Use addObjectDepthTween2 instead.
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toDepth The target depth
|
||||
* @param easing Easing function identifier
|
||||
@@ -1532,14 +1703,59 @@ namespace gdjs {
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectDepthTween(
|
||||
identifier,
|
||||
toDepth,
|
||||
easing,
|
||||
duration / 1000,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner.getRuntimeScene()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tween an object depth.
|
||||
* @param object3DBehavior Only used by events can be set to null
|
||||
* @param identifier Unique id to identify the tween
|
||||
* @param toDepth The target depth
|
||||
* @param easing Easing function identifier
|
||||
* @param duration Duration in seconds
|
||||
* @param destroyObjectWhenFinished Destroy this object when the tween ends
|
||||
*/
|
||||
addObjectDepthTween2(
|
||||
object3DBehavior: any,
|
||||
identifier: string,
|
||||
toDepth: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean
|
||||
) {
|
||||
this._addObjectDepthTween(
|
||||
identifier,
|
||||
toDepth,
|
||||
easing,
|
||||
duration,
|
||||
destroyObjectWhenFinished,
|
||||
this.owner
|
||||
);
|
||||
}
|
||||
|
||||
private _addObjectDepthTween(
|
||||
identifier: string,
|
||||
toDepth: float,
|
||||
easing: string,
|
||||
duration: float,
|
||||
destroyObjectWhenFinished: boolean,
|
||||
timeSource: gdjs.evtTools.tween.TimeSource
|
||||
) {
|
||||
const { owner } = this;
|
||||
if (!(owner instanceof gdjs.RuntimeObject3D)) return;
|
||||
|
||||
this._tweens.addSimpleTween(
|
||||
identifier,
|
||||
this.owner.getRuntimeScene(),
|
||||
duration / 1000,
|
||||
timeSource,
|
||||
duration,
|
||||
easing,
|
||||
linearInterpolation,
|
||||
owner.getDepth(),
|
||||
|
2
GDJS/.gitignore
vendored
2
GDJS/.gitignore
vendored
@@ -1 +1 @@
|
||||
/node_modules
|
||||
/node_modules
|
@@ -124,6 +124,8 @@ namespace gdjs {
|
||||
private _jsonManager: JsonManager;
|
||||
private _model3DManager: Model3DManager;
|
||||
private _bitmapFontManager: BitmapFontManager;
|
||||
private _spineAtlasManager: SpineAtlasManager | null = null;
|
||||
private _spineManager: SpineManager | null = null;
|
||||
|
||||
/**
|
||||
* Only used by events.
|
||||
@@ -172,6 +174,18 @@ namespace gdjs {
|
||||
);
|
||||
this._model3DManager = new gdjs.Model3DManager(this);
|
||||
|
||||
// add spine related managers only if spine extension is used
|
||||
if (gdjs.SpineAtlasManager && gdjs.SpineManager) {
|
||||
this._spineAtlasManager = new gdjs.SpineAtlasManager(
|
||||
this,
|
||||
this._imageManager
|
||||
);
|
||||
this._spineManager = new gdjs.SpineManager(
|
||||
this,
|
||||
this._spineAtlasManager
|
||||
);
|
||||
}
|
||||
|
||||
const resourceManagers: Array<ResourceManager> = [
|
||||
this._imageManager,
|
||||
this._soundManager,
|
||||
@@ -180,6 +194,11 @@ namespace gdjs {
|
||||
this._bitmapFontManager,
|
||||
this._model3DManager,
|
||||
];
|
||||
|
||||
if (this._spineAtlasManager)
|
||||
resourceManagers.push(this._spineAtlasManager);
|
||||
if (this._spineManager) resourceManagers.push(this._spineManager);
|
||||
|
||||
this._resourceManagersMap = new Map<ResourceKind, ResourceManager>();
|
||||
for (const resourceManager of resourceManagers) {
|
||||
for (const resourceKind of resourceManager.getResourceKinds()) {
|
||||
@@ -188,6 +207,13 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the runtime game instance.
|
||||
*/
|
||||
getRuntimeGame(): RuntimeGame {
|
||||
return this._runtimeGame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the resources data of the game. Useful for hot-reloading, should
|
||||
* not be used otherwise.
|
||||
@@ -576,6 +602,24 @@ namespace gdjs {
|
||||
getModel3DManager(): gdjs.Model3DManager {
|
||||
return this._model3DManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Spine manager of the game, used to load and construct spine skeletons from game
|
||||
* resources.
|
||||
* @return The Spine manager for the game
|
||||
*/
|
||||
getSpineManager(): gdjs.SpineManager | null {
|
||||
return this._spineManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Spine Atlas manager of the game, used to load atlases from game
|
||||
* resources.
|
||||
* @return The Spine Atlas manager for the game
|
||||
*/
|
||||
getSpineAtlasManager(): gdjs.SpineAtlasManager | null {
|
||||
return this._spineAtlasManager;
|
||||
}
|
||||
}
|
||||
|
||||
type PromiseError<T> = { item: T; error: Error };
|
||||
|
@@ -56,7 +56,7 @@ namespace gdjs {
|
||||
|
||||
setCameraZ(z: float, fov: float, cameraId?: integer): void {}
|
||||
|
||||
getCameraZ(fov: float = 45, cameraId?: integer): float {
|
||||
getCameraZ(fov: float | null, cameraId?: integer): float {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@@ -19,6 +19,15 @@ namespace gdjs {
|
||||
? RuntimeLayerRenderingType.TWO_D_PLUS_THREE_D
|
||||
: RuntimeLayerRenderingType.TWO_D;
|
||||
|
||||
export enum RuntimeLayerCameraType {
|
||||
PERSPECTIVE,
|
||||
ORTHOGRAPHIC,
|
||||
}
|
||||
|
||||
const getCameraTypeFromString = (renderingTypeAsString: string | undefined) =>
|
||||
renderingTypeAsString === 'orthographic'
|
||||
? RuntimeLayerCameraType.ORTHOGRAPHIC
|
||||
: RuntimeLayerCameraType.PERSPECTIVE;
|
||||
/**
|
||||
* Represents a layer of a "container", used to display objects.
|
||||
* The container can be a scene (see gdjs.Layer)
|
||||
@@ -27,6 +36,7 @@ namespace gdjs {
|
||||
export abstract class RuntimeLayer implements EffectsTarget {
|
||||
_name: string;
|
||||
_renderingType: RuntimeLayerRenderingType;
|
||||
_cameraType: RuntimeLayerCameraType;
|
||||
_timeScale: float = 1;
|
||||
_defaultZOrder: integer = 0;
|
||||
_hidden: boolean;
|
||||
@@ -59,12 +69,13 @@ namespace gdjs {
|
||||
) {
|
||||
this._name = layerData.name;
|
||||
this._renderingType = getRenderingTypeFromString(layerData.renderingType);
|
||||
this._cameraType = getCameraTypeFromString(layerData.cameraType);
|
||||
this._hidden = !layerData.visibility;
|
||||
this._initialCamera3DFieldOfView = layerData.camera3DFieldOfView || 45;
|
||||
this._initialCamera3DFarPlaneDistance =
|
||||
layerData.camera3DFarPlaneDistance || 0.1;
|
||||
this._initialCamera3DNearPlaneDistance =
|
||||
layerData.camera3DNearPlaneDistance || 2000;
|
||||
layerData.camera3DNearPlaneDistance || 0.1;
|
||||
this._initialCamera3DFarPlaneDistance =
|
||||
layerData.camera3DFarPlaneDistance || 2000;
|
||||
this._initialEffectsData = layerData.effects || [];
|
||||
this._runtimeScene = instanceContainer;
|
||||
this._effectsManager = instanceContainer.getGame().getEffectsManager();
|
||||
@@ -103,6 +114,10 @@ namespace gdjs {
|
||||
return this._renderingType;
|
||||
}
|
||||
|
||||
getCameraType(): RuntimeLayerCameraType {
|
||||
return this._cameraType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default Z order to be attributed to objects created on this layer
|
||||
* (usually from events generated code).
|
||||
@@ -246,7 +261,7 @@ namespace gdjs {
|
||||
* @param fov The field of view.
|
||||
* @param cameraId The camera number. Currently ignored.
|
||||
*/
|
||||
abstract setCameraZ(z: float, fov: float, cameraId?: integer): void;
|
||||
abstract setCameraZ(z: float, fov: float | null, cameraId?: integer): void;
|
||||
|
||||
/**
|
||||
* Get the camera center Z position.
|
||||
@@ -255,7 +270,7 @@ namespace gdjs {
|
||||
* @param cameraId The camera number. Currently ignored.
|
||||
* @return The z position of the camera
|
||||
*/
|
||||
abstract getCameraZ(fov: float, cameraId?: integer): float;
|
||||
abstract getCameraZ(fov: float | null, cameraId?: integer): float;
|
||||
|
||||
/**
|
||||
* Get the rotation of the camera, expressed in degrees.
|
||||
|
@@ -290,6 +290,16 @@ namespace gdjs {
|
||||
end: float,
|
||||
progress: float
|
||||
) => {
|
||||
if (progress === 0) {
|
||||
return start;
|
||||
}
|
||||
if (progress === 1) {
|
||||
return end;
|
||||
}
|
||||
if (start <= 0 || end <= 0) {
|
||||
// The exponential function is flattened to something like a 90° corner.
|
||||
return 0;
|
||||
}
|
||||
const startLog = Math.log(start);
|
||||
const endLog = Math.log(end);
|
||||
return Math.exp(startLog + (endLog - startLog) * progress);
|
||||
|
@@ -43,17 +43,22 @@ namespace gdjs {
|
||||
oldGameResolutionOriginX: float,
|
||||
oldGameResolutionOriginY: float
|
||||
): void {
|
||||
// Adapt position of the camera center as:
|
||||
// * Most cameras following a player/object on the scene will be updating this
|
||||
// in events anyway.
|
||||
// Adapt position of the camera center only if the camera has never moved as:
|
||||
// * When the camera follows a player/object, it will rarely be at the default position.
|
||||
// * Cameras not following a player/object are usually UIs which are intuitively
|
||||
// expected not to "move". Not adapting the center position would make the camera
|
||||
// move from its initial position (which is centered in the screen) - and anchor
|
||||
// behavior would behave counterintuitively.
|
||||
this._cameraX +=
|
||||
this._runtimeScene.getViewportOriginX() - oldGameResolutionOriginX;
|
||||
this._cameraY +=
|
||||
this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY;
|
||||
if (
|
||||
this._cameraX === oldGameResolutionOriginX &&
|
||||
this._cameraY === oldGameResolutionOriginY &&
|
||||
this._zoomFactor === 1
|
||||
) {
|
||||
this._cameraX +=
|
||||
this._runtimeScene.getViewportOriginX() - oldGameResolutionOriginX;
|
||||
this._cameraY +=
|
||||
this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY;
|
||||
}
|
||||
this._renderer.updatePosition();
|
||||
}
|
||||
|
||||
@@ -154,18 +159,20 @@ namespace gdjs {
|
||||
* @param fov The field of view.
|
||||
* @param cameraId The camera number. Currently ignored.
|
||||
*/
|
||||
setCameraZ(z: float, fov: float = 45, cameraId?: integer): void {
|
||||
const cameraFovInRadians = gdjs.toRad(fov);
|
||||
setCameraZ(z: float, fov: float | null, cameraId?: integer): void {
|
||||
if (fov) {
|
||||
const cameraFovInRadians = gdjs.toRad(fov);
|
||||
|
||||
// The zoom factor is capped to a not too big value to avoid infinity.
|
||||
// MAX_SAFE_INTEGER is an arbitrary choice. It's big but not too big.
|
||||
const zoomFactor = Math.min(
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
(0.5 * this.getHeight()) / (z * Math.tan(0.5 * cameraFovInRadians))
|
||||
);
|
||||
// The zoom factor is capped to a not too big value to avoid infinity.
|
||||
// MAX_SAFE_INTEGER is an arbitrary choice. It's big but not too big.
|
||||
const zoomFactor = Math.min(
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
(0.5 * this.getHeight()) / (z * Math.tan(0.5 * cameraFovInRadians))
|
||||
);
|
||||
|
||||
if (zoomFactor > 0) {
|
||||
this._zoomFactor = zoomFactor;
|
||||
if (zoomFactor > 0) {
|
||||
this._zoomFactor = zoomFactor;
|
||||
}
|
||||
}
|
||||
|
||||
this._cameraZ = z;
|
||||
@@ -180,8 +187,8 @@ namespace gdjs {
|
||||
* @param cameraId The camera number. Currently ignored.
|
||||
* @return The z position of the camera
|
||||
*/
|
||||
getCameraZ(fov: float = 45, cameraId?: integer): float {
|
||||
if (!this._isCameraZDirty) {
|
||||
getCameraZ(fov: float | null, cameraId?: integer): float {
|
||||
if (!this._isCameraZDirty || !fov) {
|
||||
return this._cameraZ;
|
||||
}
|
||||
|
||||
|
@@ -29,8 +29,6 @@ namespace gdjs {
|
||||
*/
|
||||
setAnimationName(newAnimationName: string): void;
|
||||
|
||||
isCurrentAnimationName(name: string): boolean;
|
||||
|
||||
/**
|
||||
* Return true if animation has ended.
|
||||
* The animation had ended if:
|
||||
@@ -117,10 +115,6 @@ namespace gdjs {
|
||||
this.object.setAnimationName(newAnimationName);
|
||||
}
|
||||
|
||||
isCurrentAnimationName(name: string): boolean {
|
||||
return this.object.isCurrentAnimationName(name);
|
||||
}
|
||||
|
||||
hasAnimationEnded(): boolean {
|
||||
return this.object.hasAnimationEnded();
|
||||
}
|
||||
|
@@ -34,7 +34,10 @@ namespace gdjs {
|
||||
// For a 3D (or 2D+3D) layer:
|
||||
private _threeGroup: THREE.Group | null = null;
|
||||
private _threeScene: THREE.Scene | null = null;
|
||||
private _threeCamera: THREE.PerspectiveCamera | null = null;
|
||||
private _threeCamera:
|
||||
| THREE.PerspectiveCamera
|
||||
| THREE.OrthographicCamera
|
||||
| null = null;
|
||||
private _threeCameraDirty: boolean = false;
|
||||
|
||||
// For a 2D+3D layer, the 2D rendering is done on the render texture
|
||||
@@ -101,13 +104,23 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
private _update3DCameraAspectAndPosition() {
|
||||
if (this._threeCamera) {
|
||||
if (!this._threeCamera) {
|
||||
return;
|
||||
}
|
||||
if (this._threeCamera instanceof THREE.OrthographicCamera) {
|
||||
const width = this._layer.getWidth();
|
||||
const height = this._layer.getHeight();
|
||||
this._threeCamera.left = -width / 2;
|
||||
this._threeCamera.right = width / 2;
|
||||
this._threeCamera.top = height / 2;
|
||||
this._threeCamera.bottom = -height / 2;
|
||||
} else {
|
||||
this._threeCamera.aspect =
|
||||
this._layer.getWidth() / this._layer.getHeight();
|
||||
this._threeCamera.updateProjectionMatrix();
|
||||
|
||||
this.updatePosition();
|
||||
}
|
||||
this._threeCamera.updateProjectionMatrix();
|
||||
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
getRendererObject(): PIXI.Container {
|
||||
@@ -118,7 +131,10 @@ namespace gdjs {
|
||||
return this._threeScene;
|
||||
}
|
||||
|
||||
getThreeCamera(): THREE.PerspectiveCamera | null {
|
||||
getThreeCamera():
|
||||
| THREE.PerspectiveCamera
|
||||
| THREE.OrthographicCamera
|
||||
| null {
|
||||
return this._threeCamera;
|
||||
}
|
||||
|
||||
@@ -164,12 +180,28 @@ namespace gdjs {
|
||||
this._threeGroup = new THREE.Group();
|
||||
this._threeScene.add(this._threeGroup);
|
||||
|
||||
this._threeCamera = new THREE.PerspectiveCamera(
|
||||
this._layer.getInitialCamera3DFieldOfView(),
|
||||
1,
|
||||
this._layer.getInitialCamera3DNearPlaneDistance(),
|
||||
this._layer.getInitialCamera3DFarPlaneDistance()
|
||||
);
|
||||
if (
|
||||
this._layer.getCameraType() ===
|
||||
gdjs.RuntimeLayerCameraType.ORTHOGRAPHIC
|
||||
) {
|
||||
const width = this._layer.getWidth();
|
||||
const height = this._layer.getHeight();
|
||||
this._threeCamera = new THREE.OrthographicCamera(
|
||||
-width / 2,
|
||||
width / 2,
|
||||
height / 2,
|
||||
-height / 2,
|
||||
this._layer.getInitialCamera3DNearPlaneDistance(),
|
||||
this._layer.getInitialCamera3DFarPlaneDistance()
|
||||
);
|
||||
} else {
|
||||
this._threeCamera = new THREE.PerspectiveCamera(
|
||||
this._layer.getInitialCamera3DFieldOfView(),
|
||||
1,
|
||||
this._layer.getInitialCamera3DNearPlaneDistance(),
|
||||
this._layer.getInitialCamera3DFarPlaneDistance()
|
||||
);
|
||||
}
|
||||
this._threeCamera.rotation.order = 'ZYX';
|
||||
|
||||
if (
|
||||
@@ -375,9 +407,14 @@ namespace gdjs {
|
||||
this._threeCamera.position.y = -this._layer.getCameraY(); // Inverted because the scene is mirrored on Y axis.
|
||||
this._threeCamera.rotation.z = angle;
|
||||
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(
|
||||
this._threeCamera.fov
|
||||
);
|
||||
if (this._threeCamera instanceof THREE.OrthographicCamera) {
|
||||
this._threeCamera.zoom = this._layer.getCameraZoom();
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(null);
|
||||
} else {
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(
|
||||
this._threeCamera.fov
|
||||
);
|
||||
}
|
||||
|
||||
if (this._threePlaneMesh) {
|
||||
// Adapt the plane size so that it covers the whole screen.
|
||||
@@ -415,6 +452,8 @@ namespace gdjs {
|
||||
}
|
||||
const width = this._layer.getWidth();
|
||||
const height = this._layer.getHeight();
|
||||
const normalizedX = (screenX / width) * 2 - 1;
|
||||
const normalizedY = -(screenY / height) * 2 + 1;
|
||||
|
||||
let vector = LayerPixiRenderer.vectorForProjections;
|
||||
if (!vector) {
|
||||
@@ -422,12 +461,31 @@ namespace gdjs {
|
||||
LayerPixiRenderer.vectorForProjections = vector;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/13055214/mouse-canvas-x-y-to-three-js-world-x-y-z
|
||||
vector.set((screenX / width) * 2 - 1, -(screenY / height) * 2 + 1, 0.5);
|
||||
vector.unproject(camera);
|
||||
vector.sub(camera.position).normalize();
|
||||
const distance = (worldZ - camera.position.z) / vector.z;
|
||||
vector.multiplyScalar(distance);
|
||||
camera.updateMatrixWorld();
|
||||
|
||||
if (camera instanceof THREE.OrthographicCamera) {
|
||||
// https://discourse.threejs.org/t/how-to-unproject-mouse2d-with-orthographic-camera/4777
|
||||
vector.set(normalizedX, normalizedY, 0);
|
||||
vector.unproject(camera);
|
||||
// The unprojected point is on the camera.
|
||||
// Find x and y for a given z along the camera direction line.
|
||||
const direction = new THREE.Vector3();
|
||||
camera.getWorldDirection(direction);
|
||||
const distance = (worldZ - vector.z) / direction.z;
|
||||
vector.x += distance * direction.x;
|
||||
vector.y += distance * direction.y;
|
||||
} else {
|
||||
// https://stackoverflow.com/questions/13055214/mouse-canvas-x-y-to-three-js-world-x-y-z
|
||||
vector.set(normalizedX, normalizedY, 0.5);
|
||||
vector.unproject(camera);
|
||||
// The unprojected point is on the frustum plane.
|
||||
// Find x and y for a given z along the line between the camera and
|
||||
// the one on the frustum.
|
||||
vector.sub(camera.position).normalize();
|
||||
const distance = (worldZ - camera.position.z) / vector.z;
|
||||
vector.x = distance * vector.x + camera.position.x;
|
||||
vector.y = distance * vector.y + camera.position.y;
|
||||
}
|
||||
|
||||
// The plane z == worldZ may not be visible on the camera.
|
||||
if (!Number.isFinite(vector.x) || !Number.isFinite(vector.y)) {
|
||||
@@ -436,8 +494,8 @@ namespace gdjs {
|
||||
return result;
|
||||
}
|
||||
|
||||
result[0] = camera.position.x + vector.x;
|
||||
result[1] = -(camera.position.y + vector.y);
|
||||
result[0] = vector.x;
|
||||
result[1] = -vector.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@@ -340,12 +340,15 @@ namespace gdjs {
|
||||
});
|
||||
|
||||
const baseTexture = texture.baseTexture;
|
||||
|
||||
baseTexture.on('loaded', () => {
|
||||
this._loadedTextures.set(resource, texture);
|
||||
applyTextureSettings(texture, resource);
|
||||
resolve();
|
||||
});
|
||||
baseTexture
|
||||
.on('loaded', () => {
|
||||
this._loadedTextures.set(resource, texture);
|
||||
applyTextureSettings(texture, resource);
|
||||
resolve();
|
||||
})
|
||||
.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// If the file has no extension, PIXI.assets.load cannot find
|
||||
|
@@ -152,6 +152,7 @@ namespace gdjs {
|
||||
getGlobalResourceNames(data),
|
||||
data.layouts
|
||||
);
|
||||
|
||||
this._effectsManager = new gdjs.EffectsManager();
|
||||
this._maxFPS = this._data.properties.maxFPS;
|
||||
this._minFPS = this._data.properties.minFPS;
|
||||
@@ -306,6 +307,24 @@ namespace gdjs {
|
||||
return this._resourcesLoader.getModel3DManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Spine manager of the game, used to load and construct spine skeletons from game
|
||||
* resources.
|
||||
* @return The Spine manager for the game
|
||||
*/
|
||||
getSpineManager(): gdjs.SpineManager | null {
|
||||
return this._resourcesLoader.getSpineManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Spine Atlas manager of the game, used to load atlases from game
|
||||
* resources.
|
||||
* @return The Spine Atlas manager for the game
|
||||
*/
|
||||
getSpineAtlasManager(): gdjs.SpineAtlasManager | null {
|
||||
return this._resourcesLoader.getSpineAtlasManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input manager of the game, storing mouse, keyboard
|
||||
* and touches states.
|
||||
@@ -1152,5 +1171,16 @@ namespace gdjs {
|
||||
? mapping[embeddedResourceName]
|
||||
: embeddedResourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of resources that are embedded to passed one.
|
||||
* @param resourceName The name of resource to find embedded resources of.
|
||||
* @returns The array of related resources names.
|
||||
*/
|
||||
getEmbeddedResourcesNames(resourceName: string): string[] {
|
||||
return this._embeddedResourcesMappings.has(resourceName)
|
||||
? Object.keys(this._embeddedResourcesMappings.get(resourceName)!)
|
||||
: [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
GDJS/Runtime/types/global-pixi-spine.d.ts
vendored
Normal file
4
GDJS/Runtime/types/global-pixi-spine.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import * as pixi_spine from 'pixi-spine';
|
||||
|
||||
export = pixi_spine;
|
||||
export as namespace pixi_spine;
|
5
GDJS/Runtime/types/project-data.d.ts
vendored
5
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -146,6 +146,7 @@ declare interface InstanceStringProperty {
|
||||
declare interface LayerData {
|
||||
name: string;
|
||||
renderingType?: '' | '2d' | '3d' | '2d+3d';
|
||||
cameraType?: 'perspective' | 'orthographic';
|
||||
visibility: boolean;
|
||||
cameras: CameraData[];
|
||||
effects: EffectData[];
|
||||
@@ -279,4 +280,6 @@ declare type ResourceKind =
|
||||
| 'tilemap'
|
||||
| 'tileset'
|
||||
| 'bitmapFont'
|
||||
| 'model3D';
|
||||
| 'model3D'
|
||||
| 'atlas'
|
||||
| 'spine';
|
||||
|
174
GDJS/package-lock.json
generated
174
GDJS/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"lebab": "^3.1.0",
|
||||
"minimist": "^1.2.5",
|
||||
"patch-package": "^6.4.7",
|
||||
"pixi-spine": "4.0.4",
|
||||
"pixi.js": "7.3.0",
|
||||
"prettier": "2.1.2",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
@@ -28,6 +29,91 @@
|
||||
"typescript": "4.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/base": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/base/-/base-4.0.3.tgz",
|
||||
"integrity": "sha512-0bunaWebaDswLFtYZ6whV+ZvgLQ7oANcvbPmIOoVpS/1pOY3Y/GAnWOFbgp3qt9Q/ntLYqNjGve6xq0IqpsTAA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi/core": "^7.0.0",
|
||||
"@pixi/display": "^7.0.0",
|
||||
"@pixi/graphics": "^7.0.0",
|
||||
"@pixi/mesh": "^7.0.0",
|
||||
"@pixi/mesh-extras": "^7.0.0",
|
||||
"@pixi/sprite": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/loader-base": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/loader-base/-/loader-base-4.0.4.tgz",
|
||||
"integrity": "sha512-Grgu+PxiUpgYWpuMRr3h5jrN3ZTnwyXfu3HuYdFb6mbJTTMub4xBPALeui+O+tw0k9RNRAr99pUzu9Rc9XTbAw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi-spine/base": "^4.0.0",
|
||||
"@pixi/assets": " ^7.0.0",
|
||||
"@pixi/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/loader-uni": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/loader-uni/-/loader-uni-4.0.3.tgz",
|
||||
"integrity": "sha512-tfhTJrnuog8ObKbbiSG1wV/nIUc3O98WfwS6lCmewaupoMIKF0ujg21MCqXUXJvljQJzU9tbURI+DWu4w9dnnA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi-spine/base": "^4.0.0",
|
||||
"@pixi-spine/loader-base": "^4.0.0",
|
||||
"@pixi-spine/runtime-3.7": "^4.0.0",
|
||||
"@pixi-spine/runtime-3.8": "^4.0.0",
|
||||
"@pixi-spine/runtime-4.1": "^4.0.0",
|
||||
"@pixi/assets": " ^7.0.0",
|
||||
"@pixi/core": "^7.0.0",
|
||||
"@pixi/display": "^7.0.0",
|
||||
"@pixi/graphics": "^7.0.0",
|
||||
"@pixi/mesh": "^7.0.0",
|
||||
"@pixi/mesh-extras": "^7.0.0",
|
||||
"@pixi/sprite": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/runtime-3.7": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-3.7/-/runtime-3.7-4.0.3.tgz",
|
||||
"integrity": "sha512-zuopKtSqjRc37wjW5xJ64j9DbiBB7rkPMFeldeWBPCbfZiCcFcwSZwZnrcgC+f4HIGo0NeviAvJGM8Hcf3AyeA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi-spine/base": "^4.0.0",
|
||||
"@pixi/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/runtime-3.8": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-3.8/-/runtime-3.8-4.0.3.tgz",
|
||||
"integrity": "sha512-lIhb4jOTon+FVYLO9AIgcB6jf9hC+RLEn8PesaDRibDocQ1htVCkEIhCIU3Mc00fuqIby7lMBsINeS/Th0q3bw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi-spine/base": "^4.0.0",
|
||||
"@pixi/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/runtime-4.0": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-4.0/-/runtime-4.0-4.0.3.tgz",
|
||||
"integrity": "sha512-2Y8qhxRkg/yH/9VylGsRVAd5W+dXVPhHTjFk0RR9wEUzTCkdZ17pE+56s2nESi2X3sYNKkz8FowfaqIvXnVGxw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi-spine/base": "^4.0.0",
|
||||
"@pixi/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi-spine/runtime-4.1": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-4.1/-/runtime-4.1-4.0.3.tgz",
|
||||
"integrity": "sha512-jK433snCQMC4FUPiDgyIcxhiatvRNSxqgs0CgHjjQ0l8GlY6gPpkkdThQ6GsFNme1SUZ4uvnWwawXFIGjW1IpQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@pixi-spine/base": "^4.0.0",
|
||||
"@pixi/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@pixi/accessibility": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.3.0.tgz",
|
||||
@@ -1599,6 +1685,30 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pixi-spine": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pixi-spine/-/pixi-spine-4.0.4.tgz",
|
||||
"integrity": "sha512-XRq1yARVoi4av7RXnd9+P37SWI9+e4/f5yTScZPJGB+sY5VcRYN6BYkBQ+y8nUKI1aJIjlms9z+pGxqikm+eFQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@pixi-spine/base": "^4.0.3",
|
||||
"@pixi-spine/loader-base": "^4.0.4",
|
||||
"@pixi-spine/loader-uni": "^4.0.3",
|
||||
"@pixi-spine/runtime-3.7": "^4.0.3",
|
||||
"@pixi-spine/runtime-3.8": "^4.0.3",
|
||||
"@pixi-spine/runtime-4.0": "^4.0.3",
|
||||
"@pixi-spine/runtime-4.1": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pixi/assets": "^7.0.0",
|
||||
"@pixi/core": "^7.0.0",
|
||||
"@pixi/display": "^7.0.0",
|
||||
"@pixi/graphics": "^7.0.0",
|
||||
"@pixi/mesh": "^7.0.0",
|
||||
"@pixi/mesh-extras": "^7.0.0",
|
||||
"@pixi/sprite": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pixi.js": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.3.0.tgz",
|
||||
@@ -1981,6 +2091,55 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@pixi-spine/base": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/base/-/base-4.0.3.tgz",
|
||||
"integrity": "sha512-0bunaWebaDswLFtYZ6whV+ZvgLQ7oANcvbPmIOoVpS/1pOY3Y/GAnWOFbgp3qt9Q/ntLYqNjGve6xq0IqpsTAA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi-spine/loader-base": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/loader-base/-/loader-base-4.0.4.tgz",
|
||||
"integrity": "sha512-Grgu+PxiUpgYWpuMRr3h5jrN3ZTnwyXfu3HuYdFb6mbJTTMub4xBPALeui+O+tw0k9RNRAr99pUzu9Rc9XTbAw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi-spine/loader-uni": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/loader-uni/-/loader-uni-4.0.3.tgz",
|
||||
"integrity": "sha512-tfhTJrnuog8ObKbbiSG1wV/nIUc3O98WfwS6lCmewaupoMIKF0ujg21MCqXUXJvljQJzU9tbURI+DWu4w9dnnA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi-spine/runtime-3.7": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-3.7/-/runtime-3.7-4.0.3.tgz",
|
||||
"integrity": "sha512-zuopKtSqjRc37wjW5xJ64j9DbiBB7rkPMFeldeWBPCbfZiCcFcwSZwZnrcgC+f4HIGo0NeviAvJGM8Hcf3AyeA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi-spine/runtime-3.8": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-3.8/-/runtime-3.8-4.0.3.tgz",
|
||||
"integrity": "sha512-lIhb4jOTon+FVYLO9AIgcB6jf9hC+RLEn8PesaDRibDocQ1htVCkEIhCIU3Mc00fuqIby7lMBsINeS/Th0q3bw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi-spine/runtime-4.0": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-4.0/-/runtime-4.0-4.0.3.tgz",
|
||||
"integrity": "sha512-2Y8qhxRkg/yH/9VylGsRVAd5W+dXVPhHTjFk0RR9wEUzTCkdZ17pE+56s2nESi2X3sYNKkz8FowfaqIvXnVGxw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi-spine/runtime-4.1": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@pixi-spine/runtime-4.1/-/runtime-4.1-4.0.3.tgz",
|
||||
"integrity": "sha512-jK433snCQMC4FUPiDgyIcxhiatvRNSxqgs0CgHjjQ0l8GlY6gPpkkdThQ6GsFNme1SUZ4uvnWwawXFIGjW1IpQ==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@pixi/accessibility": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.3.0.tgz",
|
||||
@@ -3203,6 +3362,21 @@
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true
|
||||
},
|
||||
"pixi-spine": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pixi-spine/-/pixi-spine-4.0.4.tgz",
|
||||
"integrity": "sha512-XRq1yARVoi4av7RXnd9+P37SWI9+e4/f5yTScZPJGB+sY5VcRYN6BYkBQ+y8nUKI1aJIjlms9z+pGxqikm+eFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@pixi-spine/base": "^4.0.3",
|
||||
"@pixi-spine/loader-base": "^4.0.4",
|
||||
"@pixi-spine/loader-uni": "^4.0.3",
|
||||
"@pixi-spine/runtime-3.7": "^4.0.3",
|
||||
"@pixi-spine/runtime-3.8": "^4.0.3",
|
||||
"@pixi-spine/runtime-4.0": "^4.0.3",
|
||||
"@pixi-spine/runtime-4.1": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"pixi.js": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.3.0.tgz",
|
||||
|
@@ -16,6 +16,7 @@
|
||||
"minimist": "^1.2.5",
|
||||
"patch-package": "^6.4.7",
|
||||
"pixi.js": "7.3.0",
|
||||
"pixi-spine": "4.0.4",
|
||||
"prettier": "2.1.2",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"shelljs": "^0.8.4",
|
||||
@@ -23,8 +24,19 @@
|
||||
"typedoc-plugin-reference-excluder": "^1.0.0",
|
||||
"typescript": "4.3.2"
|
||||
},
|
||||
"overrides": {
|
||||
"pixi-spine": {
|
||||
"@pixi/assets": "7.3.0",
|
||||
"@pixi/core": "7.3.0",
|
||||
"@pixi/display": "7.3.0",
|
||||
"@pixi/graphics": "7.3.0",
|
||||
"@pixi/mesh": "7.3.0",
|
||||
"@pixi/mesh-extras": "7.3.0",
|
||||
"@pixi/sprite": "7.3.0"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "patch-package",
|
||||
"postinstall": "patch-package && node scripts/install-spine.js",
|
||||
"check-types": "tsc",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "cd tests && npm run test-benchmark",
|
||||
|
36
GDJS/scripts/install-spine.js
Normal file
36
GDJS/scripts/install-spine.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const path = require('path');
|
||||
const shell = require('shelljs');
|
||||
|
||||
const readContent = (path, testErrorMessage) => {
|
||||
if (!shell.test('-f', path)) throw new Error(`${testErrorMessage} Should exist by ${path}.`);
|
||||
|
||||
const readingResult = shell.cat(path);
|
||||
|
||||
if (readingResult.stderr) throw new Error(readingResult.stderr);
|
||||
|
||||
return readingResult.toString();
|
||||
};
|
||||
|
||||
try {
|
||||
shell.echo(`Start pixi-spine.js copying...`);
|
||||
|
||||
const originalSpineDir = path.resolve('node_modules/pixi-spine');
|
||||
const originalSpinePackage = JSON.parse(readContent(path.join(originalSpineDir, 'package.json'), 'Cannot find pixi-spine package.json file.'));
|
||||
const originalSpineContent = readContent(path.join(originalSpineDir, originalSpinePackage.extensionConfig.bundle), 'Cannot find pixi-spine.js.');
|
||||
|
||||
const varSpineExport = '\nvar pixi_spine = this.PIXI.spine;\n';
|
||||
const runtimeSpineDir = '../Extensions/Spine/pixi-spine';
|
||||
|
||||
if (!shell.test('-d', runtimeSpineDir)) {
|
||||
shell.echo(`Creating directory for pixi-spine.js ${runtimeSpineDir}.`);
|
||||
shell.mkdir(runtimeSpineDir);
|
||||
}
|
||||
|
||||
const runtimeSpinePath = path.join(runtimeSpineDir, 'pixi-spine.js');
|
||||
new shell.ShellString(originalSpineContent + varSpineExport).to(runtimeSpinePath);
|
||||
|
||||
shell.echo(`✅ Properly copied pixi-spine.js from node_modules to ${runtimeSpinePath}.`);
|
||||
} catch(error) {
|
||||
shell.echo(`❌ Unable to copy pixi-spine.js from node_modules. Error is: ${error}`)
|
||||
shell.exit(1);
|
||||
}
|
@@ -23,6 +23,7 @@ const transformExcludedExtensions = ['.min.js', '.d.ts'];
|
||||
const untransformedPaths = [
|
||||
// GDJS prebuilt files:
|
||||
'GDJS/Runtime/pixi-renderers/pixi.js',
|
||||
'GDJS/Runtime/pixi-renderers/pixi-spine.js',
|
||||
'GDJS/Runtime/pixi-renderers/three.js',
|
||||
'GDJS/Runtime/pixi-renderers/ThreeAddons.js',
|
||||
'GDJS/Runtime/pixi-renderers/draco/gltf/draco_wasm_wrapper.js',
|
||||
|
101
GDJS/tests/games/capabilities/assets/spineboy.atlas
Normal file
101
GDJS/tests/games/capabilities/assets/spineboy.atlas
Normal file
@@ -0,0 +1,101 @@
|
||||
spineboy.png
|
||||
size: 1024, 256
|
||||
filter: Linear, Linear
|
||||
scale: 0.5
|
||||
crosshair
|
||||
bounds: 813, 160, 45, 45
|
||||
eye-indifferent
|
||||
bounds: 569, 2, 47, 45
|
||||
eye-surprised
|
||||
bounds: 643, 7, 47, 45
|
||||
rotate: 90
|
||||
front-bracer
|
||||
bounds: 811, 51, 29, 40
|
||||
front-fist-closed
|
||||
bounds: 807, 93, 38, 41
|
||||
front-fist-open
|
||||
bounds: 815, 210, 43, 44
|
||||
front-foot
|
||||
bounds: 706, 64, 63, 35
|
||||
rotate: 90
|
||||
front-shin
|
||||
bounds: 80, 11, 41, 92
|
||||
front-thigh
|
||||
bounds: 754, 12, 23, 56
|
||||
front-upper-arm
|
||||
bounds: 618, 5, 23, 49
|
||||
goggles
|
||||
bounds: 214, 20, 131, 83
|
||||
gun
|
||||
bounds: 347, 14, 105, 102
|
||||
rotate: 90
|
||||
head
|
||||
bounds: 80, 105, 136, 149
|
||||
hoverboard-board
|
||||
bounds: 2, 8, 246, 76
|
||||
rotate: 90
|
||||
hoverboard-thruster
|
||||
bounds: 478, 2, 30, 32
|
||||
hoverglow-small
|
||||
bounds: 218, 117, 137, 38
|
||||
rotate: 90
|
||||
mouth-grind
|
||||
bounds: 775, 80, 47, 30
|
||||
rotate: 90
|
||||
mouth-oooo
|
||||
bounds: 779, 31, 47, 30
|
||||
rotate: 90
|
||||
mouth-smile
|
||||
bounds: 783, 207, 47, 30
|
||||
rotate: 90
|
||||
muzzle-glow
|
||||
bounds: 779, 4, 25, 25
|
||||
muzzle-ring
|
||||
bounds: 451, 14, 25, 105
|
||||
muzzle01
|
||||
bounds: 664, 60, 67, 40
|
||||
rotate: 90
|
||||
muzzle02
|
||||
bounds: 580, 56, 68, 42
|
||||
rotate: 90
|
||||
muzzle03
|
||||
bounds: 478, 36, 83, 53
|
||||
rotate: 90
|
||||
muzzle04
|
||||
bounds: 533, 49, 75, 45
|
||||
rotate: 90
|
||||
muzzle05
|
||||
bounds: 624, 56, 68, 38
|
||||
rotate: 90
|
||||
neck
|
||||
bounds: 806, 8, 18, 21
|
||||
portal-bg
|
||||
bounds: 258, 121, 133, 133
|
||||
portal-flare1
|
||||
bounds: 690, 2, 56, 30
|
||||
rotate: 90
|
||||
portal-flare2
|
||||
bounds: 510, 3, 57, 31
|
||||
portal-flare3
|
||||
bounds: 722, 4, 58, 30
|
||||
rotate: 90
|
||||
portal-shade
|
||||
bounds: 393, 121, 133, 133
|
||||
portal-streaks1
|
||||
bounds: 528, 126, 126, 128
|
||||
portal-streaks2
|
||||
bounds: 656, 129, 125, 125
|
||||
rear-bracer
|
||||
bounds: 826, 13, 28, 36
|
||||
rear-foot
|
||||
bounds: 743, 70, 57, 30
|
||||
rotate: 90
|
||||
rear-shin
|
||||
bounds: 174, 14, 38, 89
|
||||
rear-thigh
|
||||
bounds: 783, 158, 28, 47
|
||||
rear-upper-arm
|
||||
bounds: 783, 136, 20, 44
|
||||
rotate: 90
|
||||
torso
|
||||
bounds: 123, 13, 49, 90
|
3672
GDJS/tests/games/capabilities/assets/spineboy.json
Normal file
3672
GDJS/tests/games/capabilities/assets/spineboy.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
GDJS/tests/games/capabilities/assets/spineboy.png
Normal file
BIN
GDJS/tests/games/capabilities/assets/spineboy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 239 KiB |
File diff suppressed because it is too large
Load Diff
@@ -41,14 +41,15 @@ module.exports = function (config) {
|
||||
'./newIDE/app/resources/GDJS/Runtime/AsyncTasksManager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/libs/rbush.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/pixi-renderers/pixi.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/pixi-renderers/pixi-spine.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/pixi-renderers/three.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/pixi-renderers/*.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/howler-sound-manager/howler.min.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/howler-sound-manager/howler-sound-manager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/fontfaceobserver-font-manager/fontfaceobserver.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/fontfaceobserver-font-manager/fontfaceobserver-font-manager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/jsonmanager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Model3DManager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/jsonmanager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/ResourceLoader.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/ResourceCache.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/timemanager.js',
|
||||
@@ -120,6 +121,10 @@ module.exports = function (config) {
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/TextInput/textinputruntimeobject-pixi-renderer.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/TextObject/textruntimeobject.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/TextObject/textruntimeobject-pixi-renderer.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/A_RuntimeObject3D.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/A_RuntimeObject3DRenderer.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/Cube3DRuntimeObject.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/3D/Cube3DRuntimeObjectPixiRenderer.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/TopDownMovementBehavior/topdownmovementruntimebehavior.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/TweenBehavior/TweenManager.js',
|
||||
'./newIDE/app/resources/GDJS/Runtime/Extensions/TweenBehavior/tweentools.js',
|
||||
|
@@ -909,6 +909,8 @@ interface Layer {
|
||||
[Const, Ref] DOMString GetName();
|
||||
void SetRenderingType([Const] DOMString renderingType);
|
||||
[Const, Ref] DOMString GetRenderingType();
|
||||
void SetCameraType([Const] DOMString cameraType);
|
||||
[Const, Ref] DOMString GetCameraType();
|
||||
void SetVisibility(boolean visible);
|
||||
boolean GetVisibility();
|
||||
void SetLocked(boolean isLocked);
|
||||
@@ -957,9 +959,11 @@ interface PropertyDescriptor {
|
||||
[Ref] PropertyDescriptor SetExtraInfo([Const, Ref] VectorString info);
|
||||
[Ref] VectorString GetExtraInfo();
|
||||
[Ref] PropertyDescriptor SetHidden(boolean enable);
|
||||
boolean IsHidden();
|
||||
[Ref] PropertyDescriptor SetDeprecated(boolean enable);
|
||||
boolean IsDeprecated();
|
||||
[Const, Ref] MeasurementUnit GetMeasurementUnit();
|
||||
[Ref] PropertyDescriptor SetMeasurementUnit([Const, Ref] MeasurementUnit measurementUnit);
|
||||
boolean IsHidden();
|
||||
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
@@ -1117,6 +1121,11 @@ interface JsonResource {
|
||||
};
|
||||
JsonResource implements Resource;
|
||||
|
||||
interface SpineResource {
|
||||
void SpineResource();
|
||||
};
|
||||
SpineResource implements JsonResource;
|
||||
|
||||
interface TilemapResource {
|
||||
void TilemapResource();
|
||||
};
|
||||
@@ -1132,6 +1141,11 @@ interface Model3DResource {
|
||||
};
|
||||
Model3DResource implements Resource;
|
||||
|
||||
interface AtlasResource {
|
||||
void AtlasResource();
|
||||
};
|
||||
AtlasResource implements Resource;
|
||||
|
||||
interface InitialInstance {
|
||||
void InitialInstance();
|
||||
|
||||
@@ -1297,6 +1311,12 @@ interface Serializer {
|
||||
[Value] SerializerElement STATIC_FromJSON([Const] DOMString json);
|
||||
};
|
||||
|
||||
interface ObjectAssetSerializer {
|
||||
void STATIC_SerializeTo([Ref] Project project, [Const, Ref] gdObject obj,
|
||||
[Const] DOMString objectFullName, [Ref] SerializerElement element,
|
||||
[Ref] MapStringString resourcesNewFileNames);
|
||||
};
|
||||
|
||||
interface InstructionsList {
|
||||
void InstructionsList();
|
||||
|
||||
@@ -3028,6 +3048,7 @@ interface ObjectsUsingResourceCollector {
|
||||
interface ResourcesInUseHelper {
|
||||
void ResourcesInUseHelper([Ref] ResourcesManager resourcesManager);
|
||||
|
||||
[Const, Ref] VectorString GetAllResources();
|
||||
[Ref] SetString GetAllImages();
|
||||
[Ref] SetString GetAllAudios();
|
||||
[Ref] SetString GetAllFonts();
|
||||
@@ -3199,6 +3220,35 @@ interface Model3DObjectConfiguration {
|
||||
};
|
||||
Model3DObjectConfiguration implements ObjectConfiguration;
|
||||
|
||||
|
||||
interface SpineAnimation {
|
||||
void SpineAnimation();
|
||||
|
||||
void SetName([Const] DOMString name);
|
||||
[Const, Ref] DOMString GetName();
|
||||
|
||||
void SetSource([Const] DOMString name);
|
||||
[Const, Ref] DOMString GetSource();
|
||||
|
||||
void SetShouldLoop(boolean shouldLoop);
|
||||
boolean ShouldLoop();
|
||||
};
|
||||
|
||||
interface SpineObjectConfiguration {
|
||||
void SpineObjectConfiguration();
|
||||
|
||||
void AddAnimation([Const, Ref] SpineAnimation animation);
|
||||
[Ref] SpineAnimation GetAnimation(unsigned long index);
|
||||
boolean HasAnimationNamed([Const] DOMString name);
|
||||
unsigned long GetAnimationsCount();
|
||||
void RemoveAnimation(unsigned long index);
|
||||
void RemoveAllAnimations();
|
||||
boolean HasNoAnimations();
|
||||
void SwapAnimations(unsigned long first, unsigned long second);
|
||||
void MoveAnimation(unsigned long oldIndex, unsigned long newIndex);
|
||||
};
|
||||
SpineObjectConfiguration implements ObjectConfiguration;
|
||||
|
||||
interface Vector2f {
|
||||
void Vector2f();
|
||||
|
||||
|
@@ -195,6 +195,10 @@ void ObjectJsImplementation::ExposeResources(gd::ArbitraryResourceWorker& worker
|
||||
worker.ExposeBitmapFont(newPropertyValue);
|
||||
} else if (resourceType == "model3D") {
|
||||
worker.ExposeModel3D(newPropertyValue);
|
||||
} else if (resourceType == "atlas") {
|
||||
worker.ExposeAtlas(newPropertyValue);
|
||||
} else if (resourceType == "spine") {
|
||||
worker.ExposeSpine(newPropertyValue);
|
||||
}
|
||||
|
||||
if (newPropertyValue != oldPropertyValue) {
|
||||
|
@@ -86,6 +86,7 @@
|
||||
#include <GDCore/Project/VariablesContainersList.h>
|
||||
#include <GDCore/Serialization/Serializer.h>
|
||||
#include <GDCore/Serialization/SerializerElement.h>
|
||||
#include <GDCore/IDE/ObjectAssetSerializer.h>
|
||||
#include <GDJS/Events/Builtin/JsCodeEvent.h>
|
||||
#include <GDJS/Events/CodeGeneration/BehaviorCodeGenerator.h>
|
||||
#include <GDJS/Events/CodeGeneration/EventsFunctionsExtensionCodeGenerator.h>
|
||||
@@ -109,6 +110,8 @@
|
||||
#include "../../Extensions/TextEntryObject/TextEntryObject.h"
|
||||
#include "../../Extensions/TextObject/TextObject.h"
|
||||
#include "../../Extensions/TiledSpriteObject/TiledSpriteObject.h"
|
||||
#include "../../Extensions/3D/Model3DObjectConfiguration.h"
|
||||
#include "../../Extensions/Spine/SpineObjectConfiguration.h"
|
||||
#include "BehaviorJsImplementation.h"
|
||||
#include "BehaviorSharedDataJsImplementation.h"
|
||||
#include "ObjectJsImplementation.h"
|
||||
@@ -539,6 +542,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
#define STATIC_GetSafeName GetSafeName
|
||||
#define STATIC_ToJSON ToJSON
|
||||
#define STATIC_FromJSON(x) FromJSON(x)
|
||||
#define STATIC_SerializeTo SerializeTo
|
||||
#define STATIC_IsObject IsObject
|
||||
#define STATIC_IsBehavior IsBehavior
|
||||
#define STATIC_IsExpression IsExpression
|
||||
@@ -590,6 +594,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
#define STATIC_ExposeProjectEvents ExposeProjectEvents
|
||||
#define STATIC_ExposeProjectObjects ExposeProjectObjects
|
||||
#define STATIC_ExposeWholeProjectResources ExposeWholeProjectResources
|
||||
#define STATIC_GetResourceTypes GetResourceTypes
|
||||
|
||||
#define STATIC_GetBehaviorMetadata GetBehaviorMetadata
|
||||
#define STATIC_GetObjectMetadata GetObjectMetadata
|
||||
@@ -754,6 +759,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
#define STATIC_ShiftSentenceParamIndexes ShiftSentenceParamIndexes
|
||||
|
||||
#define STATIC_CopyAllResourcesTo CopyAllResourcesTo
|
||||
#define STATIC_CopyObjectResourcesTo CopyObjectResourcesTo
|
||||
|
||||
#define STATIC_IsExtensionLifecycleEventsFunction \
|
||||
IsExtensionLifecycleEventsFunction
|
||||
|
@@ -150,6 +150,9 @@ var adaptNamingConventions = function (gd) {
|
||||
gd.asModel3DConfiguration = function (evt) {
|
||||
return gd.castObject(evt, gd.Model3DObjectConfiguration);
|
||||
};
|
||||
gd.asSpineConfiguration = function (evt) {
|
||||
return gd.castObject(evt, gd.SpineObjectConfiguration);
|
||||
};
|
||||
|
||||
gd.asImageResource = function (evt) {
|
||||
return gd.castObject(evt, gd.ImageResource);
|
||||
|
@@ -119,3 +119,4 @@ target_link_libraries(GD PathfindingBehavior)
|
||||
target_link_libraries(GD PhysicsBehavior)
|
||||
target_link_libraries(GD ParticleSystem)
|
||||
target_link_libraries(GD Scene3D)
|
||||
target_link_libraries(GD SpineObject)
|
||||
|
@@ -13,7 +13,7 @@ module.exports = function (grunt) {
|
||||
let cmakeBinary = 'emcmake cmake';
|
||||
let cmakeGeneratorArgs = [];
|
||||
let makeBinary = 'emmake make';
|
||||
let makeArgs = ['-j 4'];
|
||||
let makeArgs = ['-j 8'];
|
||||
|
||||
// Use more specific paths on Windows
|
||||
if (isWin) {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
const shell = require('shelljs');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const sourcePath = path.join(__dirname, '../../Binaries/embuild/GDevelop.js');
|
||||
|
@@ -199,6 +199,7 @@ type ParticleEmitterObject_RendererType = 0 | 1 | 2`
|
||||
` asObjectJsImplementation(gdObjectConfiguration): gdObjectJsImplementation;`,
|
||||
` asCustomObjectConfiguration(gdObjectConfiguration): gdCustomObjectConfiguration;`,
|
||||
` asModel3DConfiguration(gdObjectConfiguration): gdModel3DObjectConfiguration;`,
|
||||
` asSpineConfiguration(gdObjectConfiguration): gdSpineObjectConfiguration;`,
|
||||
'',
|
||||
` asImageResource(gdResource): gdImageResource;`,
|
||||
'',
|
||||
@@ -307,6 +308,12 @@ type ParticleEmitterObject_RendererType = 0 | 1 | 2`
|
||||
'declare class gdGroupEvent extends gdBaseEvent {',
|
||||
'types/gdgroupevent.js'
|
||||
);
|
||||
shell.sed(
|
||||
'-i',
|
||||
'declare class gdAbstractFileSystemJS {',
|
||||
'declare class gdAbstractFileSystemJS extends gdAbstractFileSystem {',
|
||||
'types/gdabstractfilesystemjs.js'
|
||||
);
|
||||
[
|
||||
'BaseEvent',
|
||||
'StandardEvent',
|
||||
@@ -338,13 +345,13 @@ type ParticleEmitterObject_RendererType = 0 | 1 | 2`
|
||||
shell.sed(
|
||||
'-i',
|
||||
/setKind\(kind: string\): void/,
|
||||
"setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D'): void",
|
||||
"setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D' | 'atlas' | 'spine'): void",
|
||||
'types/gdresource.js'
|
||||
);
|
||||
shell.sed(
|
||||
'-i',
|
||||
/getKind\(\): string/,
|
||||
"getKind(): 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D'",
|
||||
"getKind(): 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D' | 'atlas' | 'spine'",
|
||||
'types/gdresource.js'
|
||||
);
|
||||
|
||||
|
42
GDevelop.js/types.d.ts
vendored
42
GDevelop.js/types.d.ts
vendored
@@ -796,6 +796,8 @@ export class Layer extends EmscriptenObject {
|
||||
getName(): string;
|
||||
setRenderingType(renderingType: string): void;
|
||||
getRenderingType(): string;
|
||||
setCameraType(cameraType: string): void;
|
||||
getCameraType(): string;
|
||||
setVisibility(visible: boolean): void;
|
||||
getVisibility(): boolean;
|
||||
setLocked(isLocked: boolean): void;
|
||||
@@ -837,9 +839,11 @@ export class PropertyDescriptor extends EmscriptenObject {
|
||||
setExtraInfo(info: VectorString): PropertyDescriptor;
|
||||
getExtraInfo(): VectorString;
|
||||
setHidden(enable: boolean): PropertyDescriptor;
|
||||
isHidden(): boolean;
|
||||
setDeprecated(enable: boolean): PropertyDescriptor;
|
||||
isDeprecated(): boolean;
|
||||
getMeasurementUnit(): MeasurementUnit;
|
||||
setMeasurementUnit(measurementUnit: MeasurementUnit): PropertyDescriptor;
|
||||
isHidden(): boolean;
|
||||
serializeTo(element: SerializerElement): void;
|
||||
unserializeFrom(element: SerializerElement): void;
|
||||
serializeValuesTo(element: SerializerElement): void;
|
||||
@@ -974,6 +978,10 @@ export class JsonResource extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
}
|
||||
|
||||
export class SpineResource extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
}
|
||||
|
||||
export class TilemapResource extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
}
|
||||
@@ -982,6 +990,10 @@ export class TilesetResource extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
}
|
||||
|
||||
export class AtlasResource extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
}
|
||||
|
||||
export class InitialInstance extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
setObjectName(name: string): void;
|
||||
@@ -1114,6 +1126,10 @@ export class Serializer extends EmscriptenObject {
|
||||
static fromJSON(json: string): SerializerElement;
|
||||
}
|
||||
|
||||
export class ObjectAssetSerializer extends EmscriptenObject {
|
||||
static serializeTo(project: Project, obj: gdObject, objectFullName: string, element: SerializerElement, resourcesNewFileNames: MapStringString): void;
|
||||
}
|
||||
|
||||
export class InstructionsList extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
insert(instr: Instruction, pos: number): Instruction;
|
||||
@@ -2322,6 +2338,7 @@ export class ObjectsUsingResourceCollector extends EmscriptenObject {
|
||||
|
||||
export class ResourcesInUseHelper extends EmscriptenObject {
|
||||
constructor(resourcesManager: ResourcesManager): void;
|
||||
getAllResources(): VectorString;
|
||||
getAllImages(): SetString;
|
||||
getAllAudios(): SetString;
|
||||
getAllFonts(): SetString;
|
||||
@@ -2422,6 +2439,29 @@ export class SpriteObject extends EmscriptenObject {
|
||||
setAdaptCollisionMaskAutomatically(adaptCollisionMaskAutomatically: boolean): void;
|
||||
}
|
||||
|
||||
export class SpineAnimation extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
setName(name: string): void;
|
||||
getName(): string;
|
||||
setSource(name: string): void;
|
||||
getSource(): string;
|
||||
setShouldLoop(shouldLoop: boolean): void;
|
||||
shouldLoop(): boolean;
|
||||
}
|
||||
|
||||
export class SpineObjectConfiguration extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
addAnimation(animation: SpineAnimation): void;
|
||||
getAnimation(index: number): SpineAnimation;
|
||||
hasAnimationNamed(name: string): boolean;
|
||||
getAnimationsCount(): number;
|
||||
removeAnimation(index: number): void;
|
||||
removeAllAnimations(): void;
|
||||
hasNoAnimations(): boolean;
|
||||
swapAnimations(first: number, second: number): void;
|
||||
moveAnimation(oldIndex: number, newIndex: number): void;
|
||||
}
|
||||
|
||||
export class TextObject extends EmscriptenObject {
|
||||
constructor(): void;
|
||||
setString(string: string): void;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdAbstractFileSystemJS {
|
||||
declare class gdAbstractFileSystemJS extends gdAbstractFileSystem {
|
||||
constructor(): void;
|
||||
mkDir(dir: string): void;
|
||||
dirExists(dir: string): void;
|
||||
|
6
GDevelop.js/types/gdatlasresource.js
Normal file
6
GDevelop.js/types/gdatlasresource.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdAtlasResource extends gdResource {
|
||||
constructor(): void;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -5,6 +5,8 @@ declare class gdLayer {
|
||||
getName(): string;
|
||||
setRenderingType(renderingType: string): void;
|
||||
getRenderingType(): string;
|
||||
setCameraType(cameraType: string): void;
|
||||
getCameraType(): string;
|
||||
setVisibility(visible: boolean): void;
|
||||
getVisibility(): boolean;
|
||||
setLocked(isLocked: boolean): void;
|
||||
|
6
GDevelop.js/types/gdobjectassetserializer.js
Normal file
6
GDevelop.js/types/gdobjectassetserializer.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdObjectAssetSerializer {
|
||||
static serializeTo(project: gdProject, obj: gdObject, objectFullName: string, element: gdSerializerElement, resourcesNewFileNames: gdMapStringString): void;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -15,9 +15,11 @@ declare class gdPropertyDescriptor {
|
||||
setExtraInfo(info: gdVectorString): gdPropertyDescriptor;
|
||||
getExtraInfo(): gdVectorString;
|
||||
setHidden(enable: boolean): gdPropertyDescriptor;
|
||||
isHidden(): boolean;
|
||||
setDeprecated(enable: boolean): gdPropertyDescriptor;
|
||||
isDeprecated(): boolean;
|
||||
getMeasurementUnit(): gdMeasurementUnit;
|
||||
setMeasurementUnit(measurementUnit: gdMeasurementUnit): gdPropertyDescriptor;
|
||||
isHidden(): boolean;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(element: gdSerializerElement): void;
|
||||
serializeValuesTo(element: gdSerializerElement): void;
|
||||
|
@@ -4,8 +4,8 @@ declare class gdResource {
|
||||
clone(): gdResource;
|
||||
setName(name: string): void;
|
||||
getName(): string;
|
||||
setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D'): void;
|
||||
getKind(): 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D';
|
||||
setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D' | 'atlas' | 'spine'): void;
|
||||
getKind(): 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap' | 'tileset' | 'model3D' | 'atlas' | 'spine';
|
||||
isUserAdded(): boolean;
|
||||
setUserAdded(yes: boolean): void;
|
||||
useFile(): boolean;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdResourcesInUseHelper extends gdArbitraryResourceWorker {
|
||||
constructor(resourcesManager: gdResourcesManager): void;
|
||||
getAllResources(): gdVectorString;
|
||||
getAllImages(): gdSetString;
|
||||
getAllAudios(): gdSetString;
|
||||
getAllFonts(): gdSetString;
|
||||
|
12
GDevelop.js/types/gdspineanimation.js
Normal file
12
GDevelop.js/types/gdspineanimation.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdSpineAnimation {
|
||||
constructor(): void;
|
||||
setName(name: string): void;
|
||||
getName(): string;
|
||||
setSource(name: string): void;
|
||||
getSource(): string;
|
||||
setShouldLoop(shouldLoop: boolean): void;
|
||||
shouldLoop(): boolean;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
15
GDevelop.js/types/gdspineobjectconfiguration.js
Normal file
15
GDevelop.js/types/gdspineobjectconfiguration.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdSpineObjectConfiguration extends gdObjectConfiguration {
|
||||
constructor(): void;
|
||||
addAnimation(animation: gdSpineAnimation): void;
|
||||
getAnimation(index: number): gdSpineAnimation;
|
||||
hasAnimationNamed(name: string): boolean;
|
||||
getAnimationsCount(): number;
|
||||
removeAnimation(index: number): void;
|
||||
removeAllAnimations(): void;
|
||||
hasNoAnimations(): boolean;
|
||||
swapAnimations(first: number, second: number): void;
|
||||
moveAnimation(oldIndex: number, newIndex: number): void;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
6
GDevelop.js/types/gdspineresource.js
Normal file
6
GDevelop.js/types/gdspineresource.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdSpineResource extends gdJsonResource {
|
||||
constructor(): void;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -36,6 +36,7 @@ declare class libGDevelop {
|
||||
asObjectJsImplementation(gdObjectConfiguration): gdObjectJsImplementation;
|
||||
asCustomObjectConfiguration(gdObjectConfiguration): gdCustomObjectConfiguration;
|
||||
asModel3DConfiguration(gdObjectConfiguration): gdModel3DObjectConfiguration;
|
||||
asSpineConfiguration(gdObjectConfiguration): gdSpineObjectConfiguration;
|
||||
|
||||
asImageResource(gdResource): gdImageResource;
|
||||
|
||||
@@ -109,9 +110,11 @@ declare class libGDevelop {
|
||||
BitmapFontResource: Class<gdBitmapFontResource>;
|
||||
VideoResource: Class<gdVideoResource>;
|
||||
JsonResource: Class<gdJsonResource>;
|
||||
SpineResource: Class<gdSpineResource>;
|
||||
TilemapResource: Class<gdTilemapResource>;
|
||||
TilesetResource: Class<gdTilesetResource>;
|
||||
Model3DResource: Class<gdModel3DResource>;
|
||||
AtlasResource: Class<gdAtlasResource>;
|
||||
InitialInstance: Class<gdInitialInstance>;
|
||||
InitialInstancesContainer: Class<gdInitialInstancesContainer>;
|
||||
HighestZOrderFinder: Class<gdHighestZOrderFinder>;
|
||||
@@ -122,6 +125,7 @@ declare class libGDevelop {
|
||||
SerializerElement: Class<gdSerializerElement>;
|
||||
SharedPtrSerializerElement: Class<gdSharedPtrSerializerElement>;
|
||||
Serializer: Class<gdSerializer>;
|
||||
ObjectAssetSerializer: Class<gdObjectAssetSerializer>;
|
||||
InstructionsList: Class<gdInstructionsList>;
|
||||
Instruction: Class<gdInstruction>;
|
||||
Expression: Class<gdExpression>;
|
||||
@@ -230,6 +234,8 @@ declare class libGDevelop {
|
||||
SpriteObject: Class<gdSpriteObject>;
|
||||
Model3DAnimation: Class<gdModel3DAnimation>;
|
||||
Model3DObjectConfiguration: Class<gdModel3DObjectConfiguration>;
|
||||
SpineAnimation: Class<gdSpineAnimation>;
|
||||
SpineObjectConfiguration: Class<gdSpineObjectConfiguration>;
|
||||
Vector2f: Class<gdVector2f>;
|
||||
VectorVector2f: Class<gdVectorVector2f>;
|
||||
TextObject: Class<gdTextObject>;
|
||||
|
81
appveyor.yml
81
appveyor.yml
@@ -16,7 +16,47 @@ skip_tags: true # Don't rebuild on tags.
|
||||
init:
|
||||
- ps: Install-Product node 16
|
||||
- cmd: set NODE_OPTIONS=--max-old-space-size=8192
|
||||
cache:
|
||||
- '%APPDATA%\npm-cache' # npm cache
|
||||
- newIDE\app\node_modules -> newIDE\app\package-lock.json
|
||||
- newIDE\electron-app\node_modules -> newIDE\electron-app\package-lock.json
|
||||
- GDevelop.js\node_modules -> GDevelop.js\package-lock.json
|
||||
install:
|
||||
# Download and install SSL.com eSigner CKA.
|
||||
# See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/.
|
||||
#
|
||||
# This is necessary because of "signing to be FIPS-140 compliant". See
|
||||
# https://github.com/electron-userland/electron-builder/issues/6158
|
||||
#
|
||||
# Make sure to DISABLE "malware blocker" in SSL.com to avoid errors like:
|
||||
# Error information: "Error: SignerSign() failed." (-2146893821/0x80090003)
|
||||
- ps: >-
|
||||
# Download and Unzip eSignerCKA Setup
|
||||
|
||||
Set-StrictMode -Version 'Latest'
|
||||
|
||||
Invoke-WebRequest -OutFile eSigner_CKA_Setup.zip "https://github.com/SSLcom/eSignerCKA/releases/download/v1.0.6/SSL.COM-eSigner-CKA_1.0.6.zip"
|
||||
|
||||
Expand-Archive -Force eSigner_CKA_Setup.zip
|
||||
|
||||
Remove-Item eSigner_CKA_Setup.zip
|
||||
|
||||
Move-Item -Destination "eSigner_CKA_Installer.exe" -Path "eSigner_CKA_*\*.exe"
|
||||
|
||||
# Install it. See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "C:\projects\gdevelop\eSignerCKA"
|
||||
|
||||
./eSigner_CKA_Installer.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR="C:\projects\gdevelop\eSignerCKA" | Out-Null
|
||||
|
||||
# Disable logger.
|
||||
|
||||
# $LogConfig = Get-Content -Path C:\projects\gdevelop\eSignerCKA/log4net.config
|
||||
|
||||
# $LogConfig[0] = '<log4net threshold="OFF">'
|
||||
|
||||
# $LogConfig | Set-Content -Path C:\projects\gdevelop\eSignerCKA/log4net.config
|
||||
|
||||
# Build GDevelop.js (and run tests to ensure it works).
|
||||
# (in a subshell to avoid Emscripten polluting the Node.js and npm version for the rest of the build)
|
||||
- cmd: >-
|
||||
@@ -39,7 +79,7 @@ install:
|
||||
# setuptools will make distutils available again (but we should migrate our packages probably).
|
||||
- cmd: >-
|
||||
pip install setuptools
|
||||
|
||||
|
||||
cd newIDE\app
|
||||
|
||||
npm -v && npm install
|
||||
@@ -50,21 +90,54 @@ install:
|
||||
|
||||
cd ..\..
|
||||
|
||||
# Package the app for Windows (and sign it with the certificate set in environment variables).
|
||||
# Package the app for Windows (and sign it).
|
||||
# Don't sign the appx (it will be signed by the Microsoft Store).
|
||||
build_script:
|
||||
- ps: >-
|
||||
cd newIDE\electron-app
|
||||
|
||||
# Prepare certificate. See https://www.ssl.com/how-to/automate-ev-code-signing-with-signtool-or-certutil-esigner/?_gl=1*vuybcy*_gcl_au*MTEwODg1NDM2Mi4xNzA1ODU1NjM4#automated-code-signing
|
||||
|
||||
C:\projects\gdevelop\eSignerCKA/eSignerCKATool.exe config -mode product -user "$Env:ESIGNER_USER_NAME" -pass "$Env:ESIGNER_USER_PASSWORD" -totp "$Env:ESIGNER_USER_TOTP" -key "C:\projects\gdevelop\eSignerCKA\master.key" -r
|
||||
|
||||
C:\projects\gdevelop\eSignerCKA/eSignerCKATool.exe unload
|
||||
|
||||
C:\projects\gdevelop\eSignerCKA/eSignerCKATool.exe load
|
||||
|
||||
# Find certificate so we can tell electron-builder which one to use.
|
||||
|
||||
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
|
||||
|
||||
echo Certificate: $CodeSigningCert
|
||||
|
||||
# Use a custom signtool path because of the signtool.exe bundled withy electron-builder not working for some reason.
|
||||
# Can also be found in versioned folders like "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe".
|
||||
|
||||
$Env:SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe"
|
||||
|
||||
# Extract thumbprint and subject name of the certificate (will be passed to electron-builder).
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = $CodeSigningCert.Thumbprint
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ($CodeSigningCert.Subject -replace ", ?", "`n" | ConvertFrom-StringData).CN
|
||||
|
||||
# Build the nsis installer (signed: electron-builder will use SignTool.exe with the certificate)
|
||||
|
||||
node scripts/build.js --win nsis --publish=never
|
||||
|
||||
Remove-Item -Path Env:CSC_LINK ; Remove-Item -Path Env:CSC_KEY_PASSWORD ; node scripts/build.js --skip-app-build --win appx --publish=never
|
||||
# Build the appx (not signed).
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = ''
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ''
|
||||
|
||||
node scripts/build.js --skip-app-build --win appx --publish=never
|
||||
|
||||
cd ..\..
|
||||
|
||||
# Clean dist folder to keep only installers/binaries.
|
||||
- cmd: >-
|
||||
DEL /F/Q/S newIDE\electron-app\dist\win-unpacked
|
||||
rmdir /s /q newIDE\electron-app\dist\win-unpacked
|
||||
|
||||
# Run a few tests on Windows.
|
||||
test_script:
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import GDevelopJsInitializerDecorator from '../src/stories/GDevelopJsInitializerDecorator';
|
||||
import i18nProviderDecorator from '../src/stories/I18nProviderDecorator';
|
||||
import BrowserDropDownMenuDisablerDecorator from '../src/stories/BrowserDropDownMenuDisablerDecorator';
|
||||
import '../src/UI/icomoon-font.css'; // Styles for Icomoon font.
|
||||
import './app-level-styling.css';
|
||||
|
||||
@@ -35,5 +36,6 @@ export const parameters = {
|
||||
|
||||
export const decorators = [
|
||||
GDevelopJsInitializerDecorator,
|
||||
i18nProviderDecorator
|
||||
i18nProviderDecorator,
|
||||
BrowserDropDownMenuDisablerDecorator
|
||||
]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user