Experimental in-game editor (#7974)
Co-authored-by: Davy Hélard <davy.helard@gmail.com> Don't show in changelog
@@ -40,6 +40,31 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
|
||||
"res/actions/scale24_black.png");
|
||||
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-MoveIcon")
|
||||
.SetFilePath("InGameEditor/MoveIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-RotateIcon")
|
||||
.SetFilePath("InGameEditor/RotateIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-ResizeIcon")
|
||||
.SetFilePath("InGameEditor/ResizeIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-FocusIcon")
|
||||
.SetFilePath("InGameEditor/FocusIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-FreeCameraIcon")
|
||||
.SetFilePath("InGameEditor/FreeCameraIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
extension.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-OrbitCameraIcon")
|
||||
.SetFilePath("InGameEditor/OrbitCameraIcon.svg")
|
||||
.SetKind("internal-in-game-editor-only-svg");
|
||||
|
||||
gd::ObjectMetadata& obj = extension.AddObject<gd::ObjectConfiguration>(
|
||||
"", _("Base object"), _("Base object"), "res/objeticon24.png");
|
||||
|
||||
|
||||
@@ -311,6 +311,24 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the behavior should be run by the editor when it is attached
|
||||
* to a child-object.
|
||||
*
|
||||
* When variants are edited behaviors attached to their direct child-objects
|
||||
* are not executed.
|
||||
*/
|
||||
bool IsActivatedByDefaultInEditor() const { return isActivatedByDefaultInEditor; }
|
||||
|
||||
/**
|
||||
* Set that behavior should be run by the editor when it is attached to a
|
||||
* child-object.
|
||||
*/
|
||||
BehaviorMetadata &MarkAsActivatedByDefaultInEditor() {
|
||||
isActivatedByDefaultInEditor = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
|
||||
return quickCustomizationVisibility;
|
||||
}
|
||||
@@ -407,6 +425,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
bool isPrivate = false;
|
||||
bool isHidden = false;
|
||||
bool isRelevantForChildObjects = true;
|
||||
bool isActivatedByDefaultInEditor = false;
|
||||
gd::String openFullEditorLabel;
|
||||
QuickCustomization::Visibility quickCustomizationVisibility = QuickCustomization::Visibility::Default;
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
|
||||
namespace gd {
|
||||
/**
|
||||
* \brief Describe a resource to be used in the in-game editor.
|
||||
*/
|
||||
class GD_CORE_API InGameEditorResourceMetadata {
|
||||
public:
|
||||
InGameEditorResourceMetadata() {};
|
||||
|
||||
InGameEditorResourceMetadata& SetResourceName(const gd::String& resourceName_) {
|
||||
resourceName = resourceName_;
|
||||
return *this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the file path relative to the Runtime folder.
|
||||
* In-game editor resources are not copied during a preview.
|
||||
* Instead, they are included in the exported project with an absolute path.
|
||||
*/
|
||||
InGameEditorResourceMetadata& SetFilePath(const gd::String& relativeFilePath_) {
|
||||
relativeFilePath = relativeFilePath_;
|
||||
return *this;
|
||||
};
|
||||
|
||||
InGameEditorResourceMetadata& SetKind(const gd::String& kind_) {
|
||||
kind = kind_;
|
||||
return *this;
|
||||
};
|
||||
|
||||
const gd::String& GetResourceName() const { return resourceName; };
|
||||
const gd::String& GetFilePath() const { return relativeFilePath; };
|
||||
const gd::String& GetKind() const { return kind; };
|
||||
|
||||
private:
|
||||
gd::String resourceName;
|
||||
gd::String relativeFilePath;
|
||||
gd::String kind;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/InGameEditorResourceMetadata.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
#include "GDCore/String.h"
|
||||
@@ -305,7 +306,6 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
*/
|
||||
std::map<gd::String, gd::ExpressionMetadata>& GetAllStrExpressions() override { return strExpressionsInfos; };
|
||||
|
||||
|
||||
/**
|
||||
* Check if the behavior is private - it can't be used outside of its
|
||||
* extension.
|
||||
@@ -358,6 +358,19 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
return openFullEditorLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Declare a new resource to be used in the in-game editor.
|
||||
*/
|
||||
InGameEditorResourceMetadata& AddInGameEditorResource() {
|
||||
InGameEditorResourceMetadata newInGameEditorResource;
|
||||
inGameEditorResources.push_back(newInGameEditorResource);
|
||||
return inGameEditorResources.back();
|
||||
}
|
||||
|
||||
const std::vector<gd::InGameEditorResourceMetadata>& GetInGameEditorResources() const {
|
||||
return inGameEditorResources;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::InstructionMetadata> conditionsInfos;
|
||||
std::map<gd::String, gd::InstructionMetadata> actionsInfos;
|
||||
std::map<gd::String, gd::ExpressionMetadata> expressionsInfos;
|
||||
@@ -381,6 +394,7 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
bool hidden = false;
|
||||
bool isRenderedIn3D = false;
|
||||
gd::String openFullEditorLabel;
|
||||
std::vector<gd::InGameEditorResourceMetadata> inGameEditorResources;
|
||||
|
||||
std::shared_ptr<gd::ObjectConfiguration>
|
||||
blueprintObject; ///< The "blueprint" object to be copied when a new
|
||||
|
||||
@@ -813,6 +813,13 @@ gd::String PlatformExtension::GetObjectFullType(const gd::String& extensionName,
|
||||
return extensionName + separator + objectName;
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetVariantFullType(const gd::String& extensionName,
|
||||
const gd::String& objectName,
|
||||
const gd::String& variantName) {
|
||||
const auto& separator = GetNamespaceSeparator();
|
||||
return extensionName + separator + objectName + separator + variantName;
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetExtensionFromFullObjectType(
|
||||
const gd::String& type) {
|
||||
const auto separatorIndex =
|
||||
|
||||
@@ -361,6 +361,16 @@ class GD_CORE_API PlatformExtension {
|
||||
* generator.
|
||||
*/
|
||||
void StripUnimplementedInstructionsAndExpressions();
|
||||
|
||||
|
||||
/**
|
||||
* \brief Declare a new resource to be used in the in-game editor.
|
||||
*/
|
||||
InGameEditorResourceMetadata& AddInGameEditorResource() {
|
||||
InGameEditorResourceMetadata newInGameEditorResource;
|
||||
inGameEditorResources.push_back(newInGameEditorResource);
|
||||
return inGameEditorResources.back();
|
||||
}
|
||||
///@}
|
||||
|
||||
/** \name Extension accessors
|
||||
@@ -632,6 +642,10 @@ class GD_CORE_API PlatformExtension {
|
||||
GetAllInstructionOrExpressionGroupMetadata() const {
|
||||
return instructionOrExpressionGroupMetadata;
|
||||
}
|
||||
|
||||
const std::vector<gd::InGameEditorResourceMetadata>& GetInGameEditorResources() const {
|
||||
return inGameEditorResources;
|
||||
}
|
||||
///@}
|
||||
|
||||
/**
|
||||
@@ -663,6 +677,10 @@ class GD_CORE_API PlatformExtension {
|
||||
static gd::String GetObjectFullType(const gd::String& extensionName,
|
||||
const gd::String& objectName);
|
||||
|
||||
static gd::String GetVariantFullType(const gd::String& extensionName,
|
||||
const gd::String& objectName,
|
||||
const gd::String& variantName);
|
||||
|
||||
static gd::String GetExtensionFromFullObjectType(const gd::String& type);
|
||||
|
||||
static gd::String GetObjectNameFromFullObjectType(const gd::String& type);
|
||||
@@ -704,6 +722,7 @@ class GD_CORE_API PlatformExtension {
|
||||
std::map<gd::String, gd::PropertyDescriptor> extensionPropertiesMetadata;
|
||||
std::map<gd::String, InstructionOrExpressionGroupMetadata>
|
||||
instructionOrExpressionGroupMetadata;
|
||||
std::vector<gd::InGameEditorResourceMetadata> inGameEditorResources;
|
||||
|
||||
ObjectMetadata badObjectMetadata;
|
||||
BehaviorMetadata badBehaviorMetadata;
|
||||
|
||||
@@ -20,4 +20,13 @@ void BehaviorDefaultFlagClearer::DoVisitBehavior(gd::Behavior& behavior) {
|
||||
|
||||
BehaviorDefaultFlagClearer::~BehaviorDefaultFlagClearer() {}
|
||||
|
||||
void BehaviorDefaultFlagClearer::SerializeObjectWithCleanDefaultBehaviorFlags(
|
||||
const gd::Object &object, SerializerElement &serializerElement) {
|
||||
gd::Object clonedObject(object);
|
||||
for (const auto &behaviorName : clonedObject.GetAllBehaviorNames()) {
|
||||
clonedObject.GetBehavior(behaviorName).SetDefaultBehavior(false);
|
||||
}
|
||||
clonedObject.SerializeTo(serializerElement);
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
namespace gd {
|
||||
class Object;
|
||||
class Behavior;
|
||||
class SerializerElement;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
@@ -26,7 +27,10 @@ class GD_CORE_API BehaviorDefaultFlagClearer : public ArbitraryObjectsWorker {
|
||||
BehaviorDefaultFlagClearer() {};
|
||||
virtual ~BehaviorDefaultFlagClearer();
|
||||
|
||||
private:
|
||||
static void SerializeObjectWithCleanDefaultBehaviorFlags(
|
||||
const gd::Object &object, SerializerElement &serializerElement);
|
||||
|
||||
private:
|
||||
void DoVisitObject(gd::Object& object) override;
|
||||
void DoVisitBehavior(gd::Behavior& behavior) override;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/IDE/ProjectBrowserHelper.h"
|
||||
#include "GDCore/IDE/WholeProjectRefactorer.h"
|
||||
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
|
||||
@@ -19,14 +20,24 @@ void UsedExtensionsResult::AddUsedExtension(const gd::PlatformExtension& extensi
|
||||
usedSourceFiles.insert(usedSourceFiles.end(),
|
||||
extension.GetAllSourceFiles().begin(),
|
||||
extension.GetAllSourceFiles().end());
|
||||
|
||||
for (auto &&inGameEditorResource : extension.GetInGameEditorResources()) {
|
||||
AddUsedInGameEditorResource(inGameEditorResource);
|
||||
}
|
||||
}
|
||||
|
||||
void UsedExtensionsResult::AddUsedBuiltinExtension(const gd::String& extensionName) {
|
||||
usedExtensions.insert(extensionName);
|
||||
void UsedExtensionsResult::AddUsedBuiltinExtension(const gd::Project& project, const gd::String& extensionName) {
|
||||
if (project.GetCurrentPlatform().IsExtensionLoaded(extensionName)) {
|
||||
auto extension = project.GetCurrentPlatform().GetExtension(extensionName);
|
||||
if (extension) {
|
||||
AddUsedExtension(*extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const UsedExtensionsResult UsedExtensionsFinder::ScanProject(gd::Project& project) {
|
||||
UsedExtensionsFinder worker(project);
|
||||
worker.result.AddUsedBuiltinExtension(project, "BuiltinObject");
|
||||
gd::ProjectBrowserHelper::ExposeProjectObjects(project, worker);
|
||||
gd::ProjectBrowserHelper::ExposeProjectEvents(project, worker);
|
||||
return worker.result;
|
||||
@@ -44,6 +55,9 @@ void UsedExtensionsFinder::DoVisitObject(gd::Object &object) {
|
||||
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
|
||||
result.AddUsedIncludeFiles(includeFile);
|
||||
}
|
||||
for (auto &&inGameEditorResource : metadata.GetMetadata().GetInGameEditorResources()) {
|
||||
result.AddUsedInGameEditorResource(inGameEditorResource);
|
||||
}
|
||||
};
|
||||
|
||||
// Behaviors scanner
|
||||
@@ -89,7 +103,7 @@ bool UsedExtensionsFinder::DoVisitInstruction(gd::Instruction& instruction,
|
||||
rootType = "number";
|
||||
parameterValue.GetRootNode()->Visit(*this);
|
||||
} else if (gd::ParameterMetadata::IsExpression("variable", parameterType))
|
||||
result.AddUsedBuiltinExtension("BuiltinVariables");
|
||||
result.AddUsedBuiltinExtension(project, "BuiltinVariables");
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -122,7 +136,7 @@ void UsedExtensionsFinder::OnVisitUnaryOperatorNode(UnaryOperatorNode& node) {
|
||||
|
||||
// Add variable extension and visit sub-expressions on variable nodes
|
||||
void UsedExtensionsFinder::OnVisitVariableNode(VariableNode& node) {
|
||||
result.AddUsedBuiltinExtension("BuiltinVariables");
|
||||
result.AddUsedBuiltinExtension(project, "BuiltinVariables");
|
||||
|
||||
auto type = gd::ExpressionTypeFinder::GetType(
|
||||
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
|
||||
@@ -155,13 +169,13 @@ void UsedExtensionsFinder::OnVisitVariableNode(VariableNode& node) {
|
||||
|
||||
void UsedExtensionsFinder::OnVisitVariableAccessorNode(
|
||||
VariableAccessorNode& node) {
|
||||
result.AddUsedBuiltinExtension("BuiltinVariables");
|
||||
result.AddUsedBuiltinExtension(project, "BuiltinVariables");
|
||||
if (node.child) node.child->Visit(*this);
|
||||
};
|
||||
|
||||
void UsedExtensionsFinder::OnVisitVariableBracketAccessorNode(
|
||||
VariableBracketAccessorNode& node) {
|
||||
result.AddUsedBuiltinExtension("BuiltinVariables");
|
||||
result.AddUsedBuiltinExtension(project, "BuiltinVariables");
|
||||
node.expression->Visit(*this);
|
||||
if (node.child) node.child->Visit(*this);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/SourceFileMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/InGameEditorResourceMetadata.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
|
||||
@@ -50,6 +51,10 @@ public:
|
||||
return usedSourceFiles;
|
||||
}
|
||||
|
||||
const std::vector<gd::InGameEditorResourceMetadata>& GetUsedInGameEditorResources() const {
|
||||
return usedInGameEditorResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return true when at least 1 object uses the 3D renderer.
|
||||
*/
|
||||
@@ -58,9 +63,12 @@ public:
|
||||
}
|
||||
|
||||
void AddUsedExtension(const gd::PlatformExtension& extension);
|
||||
void AddUsedBuiltinExtension(const gd::String& extensionName);
|
||||
void AddUsedBuiltinExtension(const gd::Project& project, const gd::String& extensionName);
|
||||
void AddUsedIncludeFiles(const gd::String& includeFile) { usedIncludeFiles.insert(includeFile); }
|
||||
void AddUsedRequiredFiles(const gd::String& requiredFile) { usedRequiredFiles.insert(requiredFile); }
|
||||
void AddUsedInGameEditorResource(const gd::InGameEditorResourceMetadata& inGameEditorResource) {
|
||||
usedInGameEditorResources.push_back(inGameEditorResource);
|
||||
}
|
||||
|
||||
void MarkAsHaving3DObjects() {
|
||||
has3DObjects = true;
|
||||
@@ -71,6 +79,7 @@ private:
|
||||
std::set<gd::String> usedIncludeFiles;
|
||||
std::set<gd::String> usedRequiredFiles;
|
||||
std::vector<gd::SourceFileMetadata> usedSourceFiles;
|
||||
std::vector<gd::InGameEditorResourceMetadata> usedInGameEditorResources;
|
||||
bool has3DObjects = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "UsedObjectTypeFinder.h"
|
||||
|
||||
#include "GDCore/Events/Instruction.h"
|
||||
#include "GDCore/IDE/ProjectBrowserHelper.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
bool UsedObjectTypeFinder::ScanProject(gd::Project &project,
|
||||
const gd::String &objectType) {
|
||||
UsedObjectTypeFinder worker(project, objectType);
|
||||
gd::ProjectBrowserHelper::ExposeProjectObjects(project, worker);
|
||||
return worker.hasFoundObjectType;
|
||||
};
|
||||
|
||||
void UsedObjectTypeFinder::DoVisitObject(gd::Object &object) {
|
||||
if (!hasFoundObjectType && object.GetType() == objectType) {
|
||||
hasFoundObjectType = true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <set>
|
||||
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/SourceFileMetadata.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class Project;
|
||||
class Object;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
class GD_CORE_API UsedObjectTypeFinder : public ArbitraryObjectsWorker {
|
||||
public:
|
||||
static bool ScanProject(gd::Project &project, const gd::String &objectType);
|
||||
|
||||
private:
|
||||
UsedObjectTypeFinder(gd::Project &project_, const gd::String &objectType_)
|
||||
: project(project_), objectType(objectType_){};
|
||||
gd::Project &project;
|
||||
const gd::String &objectType;
|
||||
bool hasFoundObjectType = false;
|
||||
|
||||
// Object Visitor
|
||||
void DoVisitObject(gd::Object &object) override;
|
||||
};
|
||||
|
||||
}; // namespace gd
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <map>
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ResourcesAbsolutePathChecker.h"
|
||||
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
@@ -26,42 +25,37 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
bool preserveAbsoluteFilenames,
|
||||
bool preserveDirectoryStructure) {
|
||||
if (updateOriginalProject) {
|
||||
gd::ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
originalProject, originalProject, fs, destinationDirectory,
|
||||
preserveAbsoluteFilenames, preserveDirectoryStructure);
|
||||
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
|
||||
originalProject, fs, destinationDirectory, preserveAbsoluteFilenames,
|
||||
preserveDirectoryStructure);
|
||||
} else {
|
||||
gd::Project clonedProject = originalProject;
|
||||
gd::ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
originalProject, clonedProject, fs, destinationDirectory,
|
||||
preserveAbsoluteFilenames, preserveDirectoryStructure);
|
||||
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
|
||||
clonedProject, fs, destinationDirectory, preserveAbsoluteFilenames,
|
||||
preserveDirectoryStructure);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
gd::Project& originalProject,
|
||||
gd::Project& clonedProject,
|
||||
bool ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
|
||||
gd::Project& project,
|
||||
AbstractFileSystem& fs,
|
||||
gd::String destinationDirectory,
|
||||
bool preserveAbsoluteFilenames,
|
||||
bool preserveDirectoryStructure) {
|
||||
|
||||
// Check if there are some resources with absolute filenames
|
||||
gd::ResourcesAbsolutePathChecker absolutePathChecker(originalProject.GetResourcesManager(), fs);
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, absolutePathChecker);
|
||||
|
||||
auto projectDirectory = fs.DirNameFrom(originalProject.GetProjectFile());
|
||||
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
|
||||
std::cout << "Copying all resources from " << projectDirectory << " to "
|
||||
<< destinationDirectory << "..." << std::endl;
|
||||
|
||||
// Get the resources to be copied
|
||||
gd::ResourcesMergingHelper resourcesMergingHelper(
|
||||
clonedProject.GetResourcesManager(), fs);
|
||||
project.GetResourcesManager(), fs);
|
||||
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
|
||||
resourcesMergingHelper.PreserveDirectoriesStructure(
|
||||
preserveDirectoryStructure);
|
||||
resourcesMergingHelper.PreserveAbsoluteFilenames(preserveAbsoluteFilenames);
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(project,
|
||||
resourcesMergingHelper);
|
||||
|
||||
// Copy resources
|
||||
|
||||
@@ -50,12 +50,10 @@ class GD_CORE_API ProjectResourcesCopier {
|
||||
bool preserveDirectoryStructure = true);
|
||||
|
||||
private:
|
||||
static bool CopyAllResourcesTo(gd::Project& originalProject,
|
||||
gd::Project& clonedProject,
|
||||
gd::AbstractFileSystem& fs,
|
||||
gd::String destinationDirectory,
|
||||
bool preserveAbsoluteFilenames = true,
|
||||
bool preserveDirectoryStructure = true);
|
||||
static bool AdaptFilePathsAndCopyAllResourcesTo(
|
||||
gd::Project &project, gd::AbstractFileSystem &fs,
|
||||
gd::String destinationDirectory, bool preserveAbsoluteFilenames = true,
|
||||
bool preserveDirectoryStructure = true);
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "ResourcesAbsolutePathChecker.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
void ResourcesAbsolutePathChecker::ExposeFile(gd::String& resourceFilename) {
|
||||
if (fs.IsAbsolute(resourceFilename)) hasAbsoluteFilenames = true;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief Helper used to check if a project has at least a resource with an
|
||||
* absolute filename.
|
||||
*
|
||||
* \see ArbitraryResourceWorker
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API ResourcesAbsolutePathChecker
|
||||
: public ArbitraryResourceWorker {
|
||||
public:
|
||||
ResourcesAbsolutePathChecker(gd::ResourcesManager &resourcesManager,
|
||||
AbstractFileSystem &fileSystem)
|
||||
: ArbitraryResourceWorker(resourcesManager), hasAbsoluteFilenames(false),
|
||||
fs(fileSystem){};
|
||||
virtual ~ResourcesAbsolutePathChecker(){};
|
||||
|
||||
/**
|
||||
* Return true if there is at least a resource with an absolute filename.
|
||||
*/
|
||||
bool HasResourceWithAbsoluteFilenames() const {
|
||||
return hasAbsoluteFilenames;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if there is a resource with an absolute path
|
||||
*/
|
||||
virtual void ExposeFile(gd::String& resource);
|
||||
|
||||
private:
|
||||
bool hasAbsoluteFilenames;
|
||||
AbstractFileSystem& fs;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
@@ -22,6 +22,14 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
resourceFullFilename = gd::AbstractFileSystem::NormalizeSeparator(
|
||||
resourceFullFilename); // Protect against \ on Linux.
|
||||
|
||||
if (shouldUseOriginalAbsoluteFilenames) {
|
||||
// There is no need to fill `newFilenames` and `oldFilenames` since the file
|
||||
// location stays the same.
|
||||
fs.MakeAbsolute(resourceFullFilename, baseDirectory);
|
||||
resourceFilename = resourceFullFilename;
|
||||
return;
|
||||
}
|
||||
|
||||
// In the case of absolute filenames that we don't want to preserve, or
|
||||
// in the case of copying files without preserving relative folders, the new
|
||||
// names will be generated from the filename alone (with collision protection).
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef RESOURCESMERGINGHELPER_H
|
||||
#define RESOURCESMERGINGHELPER_H
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -58,6 +57,15 @@ public:
|
||||
preserveAbsoluteFilenames = preserveAbsoluteFilenames_;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Set if the absolute filenames of original files must be used for
|
||||
* any resource.
|
||||
*/
|
||||
void SetShouldUseOriginalAbsoluteFilenames(
|
||||
bool shouldUseOriginalAbsoluteFilenames_ = true) {
|
||||
shouldUseOriginalAbsoluteFilenames = shouldUseOriginalAbsoluteFilenames_;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Return a map containing the resources old absolute filename as key,
|
||||
* and the resources new filenames as value. The new filenames are relative to
|
||||
@@ -93,10 +101,13 @@ public:
|
||||
///< absolute (C:\MyFile.png will not be
|
||||
///< transformed into a relative filename
|
||||
///< (MyFile.png).
|
||||
/**
|
||||
* Set to true if the absolute filenames of original files must be used for
|
||||
* any resource.
|
||||
*/
|
||||
bool shouldUseOriginalAbsoluteFilenames = false;
|
||||
gd::AbstractFileSystem&
|
||||
fs; ///< The gd::AbstractFileSystem used to manipulate files.
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // RESOURCESMERGINGHELPER_H
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "SceneResourcesFinder.h"
|
||||
|
||||
#include "GDCore/IDE/ResourceExposer.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
@@ -27,6 +28,14 @@ std::set<gd::String> SceneResourcesFinder::FindSceneResources(gd::Project &proje
|
||||
return resourceWorker.resourceNames;
|
||||
}
|
||||
|
||||
std::set<gd::String> SceneResourcesFinder::FindEventsBasedObjectVariantResources(gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &variant) {
|
||||
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
|
||||
|
||||
gd::ResourceExposer::ExposeEventsBasedObjectVariantResources(project, variant, resourceWorker);
|
||||
return resourceWorker.resourceNames;
|
||||
}
|
||||
|
||||
void SceneResourcesFinder::AddUsedResource(gd::String &resourceName) {
|
||||
if (resourceName.empty()) {
|
||||
return;
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace gd {
|
||||
class Project;
|
||||
class Layout;
|
||||
class SerializerElement;
|
||||
class EventsBasedObjectVariant;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
@@ -27,7 +28,7 @@ namespace gd {
|
||||
class SceneResourcesFinder : private gd::ArbitraryResourceWorker {
|
||||
public:
|
||||
/**
|
||||
* @brief Find resource usages in a given scenes.
|
||||
* @brief Find resource usages in a given scene.
|
||||
*
|
||||
* It doesn't include resources used globally.
|
||||
*/
|
||||
@@ -41,6 +42,13 @@ public:
|
||||
*/
|
||||
static std::set<gd::String> FindProjectResources(gd::Project &project);
|
||||
|
||||
/**
|
||||
* @brief Find resource usages in a given events-based object variant.
|
||||
*/
|
||||
static std::set<gd::String>
|
||||
FindEventsBasedObjectVariantResources(gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &variant);
|
||||
|
||||
virtual ~SceneResourcesFinder(){};
|
||||
|
||||
private:
|
||||
|
||||
@@ -332,6 +332,12 @@ void ProjectBrowserHelper::ExposeLayoutObjects(gd::Layout &layout,
|
||||
worker.Launch(layout.GetObjects());
|
||||
}
|
||||
|
||||
void ProjectBrowserHelper::ExposeEventsBasedObjectVariantObjects(
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryObjectsWorker &worker) {
|
||||
worker.Launch(eventsBasedObjectVariant.GetObjects());
|
||||
}
|
||||
|
||||
void ProjectBrowserHelper::ExposeProjectFunctions(
|
||||
gd::Project &project, gd::ArbitraryEventsFunctionsWorker &worker) {
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class EventsFunctionsExtension;
|
||||
class EventsFunction;
|
||||
class EventsBasedBehavior;
|
||||
class EventsBasedObject;
|
||||
class EventsBasedObjectVariant;
|
||||
class ArbitraryEventsWorker;
|
||||
class ArbitraryEventsWorkerWithContext;
|
||||
class ArbitraryEventsFunctionsWorker;
|
||||
@@ -207,6 +208,17 @@ public:
|
||||
static void ExposeLayoutObjects(gd::Layout &layout,
|
||||
gd::ArbitraryObjectsWorker &worker);
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all ObjectContainers of the
|
||||
* events-based object variant.
|
||||
*
|
||||
* This should be the preferred way to traverse all the objects of an
|
||||
* events-based object variant.
|
||||
*/
|
||||
static void ExposeEventsBasedObjectVariantObjects(
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryObjectsWorker &worker);
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all FunctionsContainers of the project
|
||||
* (global, layouts...)
|
||||
|
||||
@@ -116,6 +116,34 @@ void ResourceExposer::ExposeLayoutResources(
|
||||
project, layout, eventWorker);
|
||||
}
|
||||
|
||||
void ResourceExposer::ExposeEventsBasedObjectVariantResources(
|
||||
gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryResourceWorker &worker) {
|
||||
// Expose object configuration resources
|
||||
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
|
||||
gd::ProjectBrowserHelper::ExposeEventsBasedObjectVariantObjects(
|
||||
eventsBasedObjectVariant, objectWorker);
|
||||
|
||||
// Expose layer effect resources
|
||||
auto &layers = eventsBasedObjectVariant.GetLayers();
|
||||
for (std::size_t layerIndex = 0; layerIndex < layers.GetLayersCount();
|
||||
layerIndex++) {
|
||||
auto &layer = layers.GetLayer(layerIndex);
|
||||
|
||||
auto &effects = layer.GetEffects();
|
||||
for (size_t effectIndex = 0; effectIndex < effects.GetEffectsCount();
|
||||
effectIndex++) {
|
||||
auto &effect = effects.GetEffect(effectIndex);
|
||||
gd::ResourceExposer::ExposeEffectResources(project.GetCurrentPlatform(),
|
||||
effect, worker);
|
||||
}
|
||||
}
|
||||
// We don't check the events because it would cost too much to do it for every
|
||||
// variant. Resource usage in events-based object events and their
|
||||
// dependencies should be rare.
|
||||
}
|
||||
|
||||
void ResourceExposer::ExposeEffectResources(
|
||||
gd::Platform &platform,
|
||||
gd::Effect &effect,
|
||||
|
||||
@@ -9,10 +9,11 @@ namespace gd {
|
||||
class Platform;
|
||||
class Project;
|
||||
class ArbitraryResourceWorker;
|
||||
class EventsBasedObjectVariant;
|
||||
class EventsFunctionsExtension;
|
||||
class Effect;
|
||||
class Layout;
|
||||
} // namespace gd
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
@@ -20,7 +21,7 @@ namespace gd {
|
||||
* \brief
|
||||
*/
|
||||
class GD_CORE_API ResourceExposer {
|
||||
public:
|
||||
public:
|
||||
/**
|
||||
* \brief Called ( e.g. during compilation ) so as to inventory internal
|
||||
* resources, sometimes update their filename or any other work or resources.
|
||||
@@ -50,6 +51,14 @@ class GD_CORE_API ResourceExposer {
|
||||
gd::Layout &layout,
|
||||
gd::ArbitraryResourceWorker &worker);
|
||||
|
||||
/**
|
||||
* @brief Expose the resources used in a given events-based object variant.
|
||||
*/
|
||||
static void ExposeEventsBasedObjectVariantResources(
|
||||
gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryResourceWorker &worker);
|
||||
|
||||
/**
|
||||
* @brief Expose the resources used in a given effect.
|
||||
*/
|
||||
|
||||
@@ -60,6 +60,18 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
} else {
|
||||
SetHasCustomDepth(false);
|
||||
}
|
||||
if (element.HasChild("defaultWidth") ||
|
||||
element.HasAttribute("defaultWidth")) {
|
||||
defaultWidth = element.GetDoubleAttribute("defaultWidth");
|
||||
}
|
||||
if (element.HasChild("defaultHeight") ||
|
||||
element.HasAttribute("defaultHeight")) {
|
||||
defaultHeight = element.GetDoubleAttribute("defaultHeight");
|
||||
}
|
||||
if (element.HasChild("defaultDepth") ||
|
||||
element.HasAttribute("defaultDepth")) {
|
||||
defaultDepth = element.GetDoubleAttribute("defaultDepth");
|
||||
}
|
||||
SetZOrder(element.GetIntAttribute("zOrder", 0, "plan"));
|
||||
SetOpacity(element.GetIntAttribute("opacity", 255));
|
||||
SetLayer(element.GetStringAttribute("layer"));
|
||||
@@ -74,45 +86,51 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
if (persistentUuid.empty()) ResetPersistentUuid();
|
||||
|
||||
numberProperties.clear();
|
||||
const SerializerElement& numberPropertiesElement =
|
||||
element.GetChild("numberProperties", 0, "floatInfos");
|
||||
numberPropertiesElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < numberPropertiesElement.GetChildrenCount(); ++j) {
|
||||
gd::String name =
|
||||
numberPropertiesElement.GetChild(j).GetStringAttribute("name");
|
||||
double value =
|
||||
numberPropertiesElement.GetChild(j).GetDoubleAttribute("value");
|
||||
if (element.HasChild("numberProperties", "floatInfos")) {
|
||||
const SerializerElement& numberPropertiesElement =
|
||||
element.GetChild("numberProperties", 0, "floatInfos");
|
||||
numberPropertiesElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < numberPropertiesElement.GetChildrenCount(); ++j) {
|
||||
gd::String name =
|
||||
numberPropertiesElement.GetChild(j).GetStringAttribute("name");
|
||||
double value =
|
||||
numberPropertiesElement.GetChild(j).GetDoubleAttribute("value");
|
||||
|
||||
// Compatibility with GD <= 5.1.164
|
||||
if (name == "z") {
|
||||
SetZ(value);
|
||||
} else if (name == "rotationX") {
|
||||
SetRotationX(value);
|
||||
} else if (name == "rotationY") {
|
||||
SetRotationY(value);
|
||||
} else if (name == "depth") {
|
||||
SetHasCustomDepth(true);
|
||||
SetCustomDepth(value);
|
||||
}
|
||||
// end of compatibility code
|
||||
else {
|
||||
numberProperties[name] = value;
|
||||
// Compatibility with GD <= 5.1.164
|
||||
if (name == "z") {
|
||||
SetZ(value);
|
||||
} else if (name == "rotationX") {
|
||||
SetRotationX(value);
|
||||
} else if (name == "rotationY") {
|
||||
SetRotationY(value);
|
||||
} else if (name == "depth") {
|
||||
SetHasCustomDepth(true);
|
||||
SetCustomDepth(value);
|
||||
}
|
||||
// end of compatibility code
|
||||
else {
|
||||
numberProperties[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stringProperties.clear();
|
||||
const SerializerElement& stringPropElement =
|
||||
element.GetChild("stringProperties", 0, "stringInfos");
|
||||
stringPropElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < stringPropElement.GetChildrenCount(); ++j) {
|
||||
gd::String name = stringPropElement.GetChild(j).GetStringAttribute("name");
|
||||
gd::String value =
|
||||
stringPropElement.GetChild(j).GetStringAttribute("value");
|
||||
stringProperties[name] = value;
|
||||
if (element.HasChild("stringProperties", "stringInfos")) {
|
||||
const SerializerElement& stringPropElement =
|
||||
element.GetChild("stringProperties", 0, "stringInfos");
|
||||
stringPropElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < stringPropElement.GetChildrenCount(); ++j) {
|
||||
gd::String name = stringPropElement.GetChild(j).GetStringAttribute("name");
|
||||
gd::String value =
|
||||
stringPropElement.GetChild(j).GetStringAttribute("value");
|
||||
stringProperties[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
GetVariables().UnserializeFrom(
|
||||
element.GetChild("initialVariables", 0, "InitialVariables"));
|
||||
if (element.HasChild("initialVariables", "InitialVariables")) {
|
||||
GetVariables().UnserializeFrom(
|
||||
element.GetChild("initialVariables", 0, "InitialVariables"));
|
||||
}
|
||||
}
|
||||
|
||||
void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
@@ -133,6 +151,8 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("width", GetCustomWidth());
|
||||
element.SetAttribute("height", GetCustomHeight());
|
||||
if (HasCustomDepth()) element.SetAttribute("depth", GetCustomDepth());
|
||||
// defaultWidth, defaultHeight and defaultDepth are not serialized
|
||||
// because they are evaluated by InGameEditor.
|
||||
if (IsLocked()) element.SetAttribute("locked", IsLocked());
|
||||
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
|
||||
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());
|
||||
|
||||
@@ -219,6 +219,13 @@ class GD_CORE_API InitialInstance {
|
||||
double GetCustomDepth() const { return depth; }
|
||||
void SetCustomDepth(double depth_) { depth = depth_; }
|
||||
|
||||
double GetDefaultWidth() const { return defaultWidth; }
|
||||
double GetDefaultHeight() const { return defaultHeight; }
|
||||
double GetDefaultDepth() const { return defaultDepth; }
|
||||
void SetDefaultWidth(double width_) { defaultWidth = width_; }
|
||||
void SetDefaultHeight(double height_) { defaultHeight = height_; }
|
||||
void SetDefaultDepth(double depth_) { defaultDepth = depth_; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the instance is locked and cannot be moved in the
|
||||
* IDE.
|
||||
@@ -366,7 +373,11 @@ class GD_CORE_API InitialInstance {
|
||||
*/
|
||||
InitialInstance& ResetPersistentUuid();
|
||||
|
||||
const gd::String& GetPersistentUuid() const { return persistentUuid; }
|
||||
/**
|
||||
* \brief Reset the persistent UUID used to recognize
|
||||
* the same initial instance between serialization.
|
||||
*/
|
||||
const gd::String& GetPersistentUuid() const { return persistentUuid; }
|
||||
///@}
|
||||
|
||||
private:
|
||||
@@ -395,6 +406,9 @@ class GD_CORE_API InitialInstance {
|
||||
double width; ///< Instance custom width
|
||||
double height; ///< Instance custom height
|
||||
double depth; ///< Instance custom depth
|
||||
double defaultWidth = 0; ///< Instance default width as reported by InGameEditor
|
||||
double defaultHeight = 0; ///< Instance default height as reported by InGameEditor
|
||||
double defaultDepth = 0; ///< Instance default depth as reported by InGameEditor
|
||||
gd::VariablesContainer initialVariables; ///< Instance specific variables
|
||||
bool locked; ///< True if the instance is locked
|
||||
bool sealed; ///< True if the instance is sealed
|
||||
|
||||
@@ -23,6 +23,7 @@ Layer::Layer()
|
||||
camera3DNearPlaneDistance(3),
|
||||
camera3DFarPlaneDistance(10000),
|
||||
camera3DFieldOfView(45),
|
||||
camera2DPlaneMaxDrawingDistance(5000),
|
||||
ambientLightColorR(200),
|
||||
ambientLightColorG(200),
|
||||
ambientLightColorB(200) {}
|
||||
@@ -56,6 +57,8 @@ void Layer::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("camera3DFarPlaneDistance",
|
||||
GetCamera3DFarPlaneDistance());
|
||||
element.SetAttribute("camera3DFieldOfView", GetCamera3DFieldOfView());
|
||||
element.SetAttribute("camera2DPlaneMaxDrawingDistance",
|
||||
GetCamera2DPlaneMaxDrawingDistance());
|
||||
|
||||
SerializerElement& camerasElement = element.AddChild("cameras");
|
||||
camerasElement.ConsiderAsArrayOf("camera");
|
||||
@@ -99,6 +102,8 @@ void Layer::UnserializeFrom(const SerializerElement& element) {
|
||||
"camera3DFarPlaneDistance", 10000, "threeDFarPlaneDistance"));
|
||||
SetCamera3DFieldOfView(element.GetDoubleAttribute(
|
||||
"camera3DFieldOfView", 45, "threeDFieldOfView"));
|
||||
SetCamera2DPlaneMaxDrawingDistance(element.GetDoubleAttribute(
|
||||
"camera2DPlaneMaxDrawingDistance", 5000));
|
||||
|
||||
cameras.clear();
|
||||
SerializerElement& camerasElement = element.GetChild("cameras");
|
||||
|
||||
@@ -182,6 +182,8 @@ class GD_CORE_API Layer {
|
||||
}
|
||||
double GetCamera3DFieldOfView() const { return camera3DFieldOfView; }
|
||||
void SetCamera3DFieldOfView(double angle) { camera3DFieldOfView = angle; }
|
||||
double GetCamera2DPlaneMaxDrawingDistance() const { return camera2DPlaneMaxDrawingDistance; }
|
||||
void SetCamera2DPlaneMaxDrawingDistance(double distance) { camera2DPlaneMaxDrawingDistance = distance; }
|
||||
///@}
|
||||
|
||||
/** \name Cameras
|
||||
@@ -292,6 +294,7 @@ class GD_CORE_API Layer {
|
||||
double camera3DNearPlaneDistance; ///< 3D camera frustum near plan distance
|
||||
double camera3DFarPlaneDistance; ///< 3D camera frustum far plan distance
|
||||
double camera3DFieldOfView; ///< 3D camera field of view (fov) in degrees
|
||||
double camera2DPlaneMaxDrawingDistance; ///< Max drawing distance of the 2D plane when in the 3D world
|
||||
unsigned int ambientLightColorR; ///< Ambient light color Red component
|
||||
unsigned int ambientLightColorG; ///< Ambient light color Green component
|
||||
unsigned int ambientLightColorB; ///< Ambient light color Blue component
|
||||
|
||||
@@ -236,6 +236,8 @@ void Object::SerializeTo(SerializerElement& element) const {
|
||||
const gd::Behavior& behavior = GetBehavior(allBehaviors[i]);
|
||||
// Default behaviors are added at the object creation according to
|
||||
// metadata. They don't need to be serialized.
|
||||
// During the export, all behaviors are set as not default by
|
||||
// `BehaviorDefaultFlagClearer` because the Runtime needs all the behaviors.
|
||||
if (behavior.IsDefaultBehavior()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -730,6 +730,8 @@ void Project::UnserializeFrom(const SerializerElement& element) {
|
||||
SetPackageName(propElement.GetStringAttribute("packageName"));
|
||||
SetTemplateSlug(propElement.GetStringAttribute("templateSlug"));
|
||||
SetOrientation(propElement.GetStringAttribute("orientation", "default"));
|
||||
SetEffectsHiddenInEditor(
|
||||
propElement.GetBoolAttribute("areEffectsHiddenInEditor", false));
|
||||
SetFolderProject(propElement.GetBoolAttribute("folderProject"));
|
||||
SetLastCompilationDirectory(propElement
|
||||
.GetChild("latestCompilationDirectory",
|
||||
@@ -1109,6 +1111,10 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
propElement.SetAttribute("packageName", packageName);
|
||||
propElement.SetAttribute("templateSlug", templateSlug);
|
||||
propElement.SetAttribute("orientation", orientation);
|
||||
if (areEffectsHiddenInEditor) {
|
||||
propElement.SetBoolAttribute("areEffectsHiddenInEditor",
|
||||
areEffectsHiddenInEditor);
|
||||
}
|
||||
platformSpecificAssets.SerializeTo(
|
||||
propElement.AddChild("platformSpecificAssets"));
|
||||
loadingScreen.SerializeTo(propElement.AddChild("loadingScreen"));
|
||||
@@ -1150,6 +1156,8 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
// end of compatibility code
|
||||
|
||||
extensionProperties.SerializeTo(propElement.AddChild("extensionProperties"));
|
||||
|
||||
playableDevicesElement.AddChild("").SetStringValue("mobile");
|
||||
|
||||
SerializerElement& platformsElement = propElement.AddChild("platforms");
|
||||
platformsElement.ConsiderAsArrayOf("platform");
|
||||
@@ -1319,6 +1327,8 @@ void Project::Init(const gd::Project& game) {
|
||||
|
||||
sceneResourcesPreloading = game.sceneResourcesPreloading;
|
||||
sceneResourcesUnloading = game.sceneResourcesUnloading;
|
||||
|
||||
areEffectsHiddenInEditor = game.areEffectsHiddenInEditor;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -506,6 +506,20 @@ class GD_CORE_API Project {
|
||||
*/
|
||||
void SetCurrentPlatform(const gd::String& platformName);
|
||||
|
||||
/**
|
||||
* Check if the effects are shown.
|
||||
*/
|
||||
bool AreEffectsHiddenInEditor() const { return areEffectsHiddenInEditor; }
|
||||
|
||||
/**
|
||||
* Define the project as playable on a mobile.
|
||||
* \param enable True When false effects are not shown and a default light is
|
||||
* used for 3D layers.
|
||||
*/
|
||||
void SetEffectsHiddenInEditor(bool enable = true) {
|
||||
areEffectsHiddenInEditor = enable;
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Factory method
|
||||
@@ -1165,6 +1179,9 @@ class GD_CORE_API Project {
|
||||
mutable unsigned int gdBuildVersion =
|
||||
0; ///< The GD build version used the last
|
||||
///< time the project was saved.
|
||||
bool areEffectsHiddenInEditor =
|
||||
false; ///< When false effects are not shown and a default light is used
|
||||
///< for 3D layers.
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -70,7 +70,9 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
currentValue = element.GetChild("value").GetStringValue();
|
||||
type = element.GetChild("type").GetStringValue();
|
||||
if (type == "Number") {
|
||||
gd::String unitName = element.GetChild("unit").GetStringValue();
|
||||
gd::String unitName = element.HasChild("unit")
|
||||
? element.GetChild("unit").GetStringValue()
|
||||
: "";
|
||||
measurementUnit =
|
||||
gd::MeasurementUnit::HasDefaultMeasurementUnitNamed(unitName)
|
||||
? measurementUnit =
|
||||
|
||||
@@ -99,6 +99,8 @@ std::shared_ptr<Resource> ResourcesManager::CreateResource(
|
||||
return std::make_shared<SpineResource>();
|
||||
else if (kind == "javascript")
|
||||
return std::make_shared<JavaScriptResource>();
|
||||
else if (kind == "internal-in-game-editor-only-svg")
|
||||
return std::make_shared<InternalInGameEditorOnlySvgResource>();
|
||||
|
||||
std::cout << "Bad resource created (type: " << kind << ")" << std::endl;
|
||||
return std::make_shared<Resource>();
|
||||
@@ -783,6 +785,20 @@ void JavaScriptResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
|
||||
void InternalInGameEditorOnlySvgResource::SetFile(const gd::String& newFile) {
|
||||
file = NormalizePathSeparator(newFile);
|
||||
}
|
||||
|
||||
void InternalInGameEditorOnlySvgResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetUserAdded(element.GetBoolAttribute("userAdded"));
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
void InternalInGameEditorOnlySvgResource::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) {
|
||||
|
||||
@@ -548,7 +548,7 @@ class GD_CORE_API AtlasResource : public Resource {
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a video file used by a project.
|
||||
* \brief Describe a JavaScript file used by a project.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
@@ -573,6 +573,33 @@ class GD_CORE_API JavaScriptResource : public Resource {
|
||||
gd::String file;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Describe a SVG file used by a project.
|
||||
* Currently only used in the in-game editor.
|
||||
*
|
||||
* \see Resource
|
||||
* \ingroup ResourcesManagement
|
||||
*/
|
||||
class GD_CORE_API InternalInGameEditorOnlySvgResource : public Resource {
|
||||
public:
|
||||
InternalInGameEditorOnlySvgResource() : Resource() { SetKind("internal-in-game-editor-only-svg"); };
|
||||
virtual ~InternalInGameEditorOnlySvgResource(){};
|
||||
virtual InternalInGameEditorOnlySvgResource* Clone() const override {
|
||||
return new InternalInGameEditorOnlySvgResource(*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
|
||||
*
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
#include "GDCore/Tools/Log.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
SerializerElement SerializerElement::nullElement;
|
||||
@@ -56,7 +59,16 @@ SerializerElement& SerializerElement::SetAttribute(const gd::String& name,
|
||||
// support code using attributes. Make sure that any
|
||||
// existing child with this name is removed (otherwise it
|
||||
// would erase the attribute at serialization).
|
||||
attributes[name].SetDouble(value);
|
||||
|
||||
if (std::isnan(value)) {
|
||||
gd::LogError("Attribute \"" + name +
|
||||
"\" was set to NaN - this is not allowed (would not be "
|
||||
"serialized correctly to JSON). Defaulting to 0.");
|
||||
attributes[name].SetDouble(0);
|
||||
} else {
|
||||
attributes[name].SetDouble(value);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,15 +159,9 @@ namespace gdjs {
|
||||
if (initialInstanceData.depth !== undefined) {
|
||||
this.setDepth(initialInstanceData.depth);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
if (initialInstanceData.flippedZ) {
|
||||
this.flipZ(initialInstanceData.flippedZ);
|
||||
}
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
this.flipZ(!!initialInstanceData.flippedZ);
|
||||
}
|
||||
|
||||
setX(x: float): void {
|
||||
@@ -334,6 +328,18 @@ namespace gdjs {
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._originalWidth;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._originalHeight;
|
||||
}
|
||||
|
||||
getOriginalDepth(): float {
|
||||
return this._originalDepth;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._width;
|
||||
}
|
||||
@@ -380,31 +386,6 @@ namespace gdjs {
|
||||
this.getRenderer().updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the object for a scale of 1.
|
||||
*
|
||||
* It can't be 0.
|
||||
*/
|
||||
_getOriginalWidth(): float {
|
||||
return this._originalWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the object for a scale of 1.
|
||||
*
|
||||
* It can't be 0.
|
||||
*/
|
||||
_getOriginalHeight(): float {
|
||||
return this._originalHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object size on the Z axis (called "depth") when the scale equals 1.
|
||||
*/
|
||||
_getOriginalDepth(): float {
|
||||
return this._originalDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width of the object for a scale of 1.
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace gdjs {
|
||||
this._object = runtimeObject;
|
||||
this._threeObject3D = threeObject3D;
|
||||
this._threeObject3D.rotation.order = 'ZYX';
|
||||
//@ts-ignore
|
||||
this._threeObject3D.gdjsRuntimeObject = runtimeObject;
|
||||
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
|
||||
@@ -115,6 +115,12 @@ namespace gdjs {
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number;
|
||||
|
||||
/**
|
||||
* Return the depth of the object before any custom size is applied.
|
||||
* @return The depth of the object
|
||||
*/
|
||||
getOriginalDepth(): float;
|
||||
}
|
||||
|
||||
export interface Object3DDataContent {
|
||||
@@ -131,7 +137,11 @@ namespace gdjs {
|
||||
export namespace Base3DHandler {
|
||||
export const is3D = (
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
|
||||
): object is gdjs.RuntimeObject &
|
||||
gdjs.Base3DHandler &
|
||||
gdjs.Resizable &
|
||||
gdjs.Scalable &
|
||||
gdjs.Flippable => {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return object.getZ && object.setZ;
|
||||
};
|
||||
@@ -243,6 +253,10 @@ namespace gdjs {
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.object.getUnrotatedAABBMaxZ();
|
||||
}
|
||||
|
||||
getOriginalDepth(): float {
|
||||
return this.object.getOriginalDepth();
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);
|
||||
|
||||
@@ -78,15 +78,9 @@ namespace gdjs {
|
||||
if (initialInstanceData.depth !== undefined) {
|
||||
this.setDepth(initialInstanceData.depth);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
if (initialInstanceData.flippedZ) {
|
||||
this.flipZ(initialInstanceData.flippedZ);
|
||||
}
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
this.flipZ(!!initialInstanceData.flippedZ);
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
@@ -325,6 +319,10 @@ namespace gdjs {
|
||||
return this._maxZ - this._minZ;
|
||||
}
|
||||
|
||||
getOriginalDepth(): float {
|
||||
return this._instanceContainer._getInitialInnerAreaDepth();
|
||||
}
|
||||
|
||||
override _updateUntransformedHitBoxes(): void {
|
||||
super._updateUntransformedHitBoxes();
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace gdjs {
|
||||
|
||||
this._threeGroup = new THREE.Group();
|
||||
this._threeGroup.rotation.order = 'ZYX';
|
||||
//@ts-ignore
|
||||
this._threeGroup.gdjsRuntimeObject = object;
|
||||
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
|
||||
@@ -127,13 +127,12 @@ namespace gdjs {
|
||||
this._materialType = this._convertMaterialType(
|
||||
objectData.content.materialType
|
||||
);
|
||||
this._crossfadeDuration = objectData.content.crossfadeDuration || 0;
|
||||
|
||||
this.setIsCastingShadow(objectData.content.isCastingShadow);
|
||||
this.setIsReceivingShadow(objectData.content.isReceivingShadow);
|
||||
this.onModelChanged(objectData);
|
||||
|
||||
this._crossfadeDuration = objectData.content.crossfadeDuration || 0;
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
@@ -143,12 +142,13 @@ namespace gdjs {
|
||||
* - After the renderer was instantiated
|
||||
* - After reloading the model
|
||||
*/
|
||||
private onModelChanged(objectData) {
|
||||
private onModelChanged(objectData: Model3DObjectData) {
|
||||
this._updateModel(objectData);
|
||||
if (this._animations.length > 0) {
|
||||
this._renderer.playAnimation(
|
||||
this._animations[0].source,
|
||||
this._animations[0].loop
|
||||
this._animations[0].loop,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,7 @@ namespace gdjs {
|
||||
this._centerPoint = getPointForLocation(
|
||||
newObjectData.content.centerLocation
|
||||
);
|
||||
this._updateModel(newObjectData);
|
||||
this.onModelChanged(newObjectData);
|
||||
}
|
||||
if (
|
||||
oldObjectData.content.originLocation !==
|
||||
@@ -221,6 +221,23 @@ namespace gdjs {
|
||||
) {
|
||||
this.setIsReceivingShadow(newObjectData.content.isReceivingShadow);
|
||||
}
|
||||
if (this.getInstanceContainer().getGame().isInGameEdition()) {
|
||||
const oldDefaultAnimationSource =
|
||||
this._animations.length > 0 ? this._animations[0].source : null;
|
||||
this._animations = newObjectData.content.animations;
|
||||
const newDefaultAnimationSource =
|
||||
this._animations.length > 0 ? this._animations[0].source : null;
|
||||
if (
|
||||
newDefaultAnimationSource &&
|
||||
oldDefaultAnimationSource !== newDefaultAnimationSource
|
||||
) {
|
||||
this._renderer.playAnimation(
|
||||
newDefaultAnimationSource,
|
||||
this._animations[0].loop,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,11 @@ namespace gdjs {
|
||||
this._action.paused = false;
|
||||
}
|
||||
|
||||
playAnimation(animationName: string, shouldLoop: boolean) {
|
||||
playAnimation(
|
||||
animationName: string,
|
||||
shouldLoop: boolean,
|
||||
ignoreCrossFade: boolean = false
|
||||
) {
|
||||
const clip = THREE.AnimationClip.findByName(
|
||||
this._originalModel.animations,
|
||||
animationName
|
||||
@@ -396,7 +400,11 @@ namespace gdjs {
|
||||
this._action.timeScale =
|
||||
this._model3DRuntimeObject.getAnimationSpeedScale();
|
||||
|
||||
if (previousAction && previousAction !== this._action) {
|
||||
if (
|
||||
previousAction &&
|
||||
previousAction !== this._action &&
|
||||
!ignoreCrossFade
|
||||
) {
|
||||
this._action.crossFadeFrom(
|
||||
previousAction,
|
||||
this._model3DRuntimeObject._crossfadeDuration,
|
||||
|
||||
@@ -31,5 +31,6 @@ void DeclareAnchorBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"AnchorBehavior",
|
||||
std::make_shared<AnchorBehavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
.MarkAsActivatedByDefaultInEditor()
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,19 @@ describe('gdjs.AnchorRuntimeBehavior', () => {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -221,9 +221,11 @@ namespace gdjs {
|
||||
this.setWrappingWidth(initialInstanceData.width);
|
||||
this.setWrapping(true);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
override onDestroyed(): void {
|
||||
|
||||
@@ -29,6 +29,19 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -478,6 +478,9 @@ namespace gdjs {
|
||||
|
||||
return new gdjs.PromiseTask(
|
||||
(async () => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
const scoreSavingState = (_scoreSavingStateByLeaderboard[
|
||||
leaderboardId
|
||||
] =
|
||||
@@ -516,6 +519,9 @@ namespace gdjs {
|
||||
) =>
|
||||
new gdjs.PromiseTask(
|
||||
(async () => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
const playerId = gdjs.playerAuthentication.getUserId();
|
||||
const playerToken = gdjs.playerAuthentication.getUserToken();
|
||||
if (!playerId || !playerToken) {
|
||||
@@ -843,6 +849,9 @@ namespace gdjs {
|
||||
leaderboardId: string,
|
||||
displayLoader: boolean
|
||||
) {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
// First ensure we're not trying to display multiple times the same leaderboard (in which case
|
||||
// we "de-duplicate" the request to display it).
|
||||
if (leaderboardId === _requestedLeaderboardId) {
|
||||
|
||||
|
After Width: | Height: | Size: 716 B |
@@ -175,6 +175,12 @@ module.exports = {
|
||||
.setCategoryFullName(_('Visual effect'))
|
||||
.addDefaultBehavior('EffectCapability::EffectBehavior');
|
||||
|
||||
object
|
||||
.addInGameEditorResource()
|
||||
.setResourceName('InGameEditor-LightIcon')
|
||||
.setFilePath('Extensions/Lighting/InGameEditor/LightIcon.png')
|
||||
.setKind('image');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
'SetRadius',
|
||||
@@ -338,11 +344,11 @@ module.exports = {
|
||||
}
|
||||
|
||||
getOriginX() {
|
||||
return this._radius;
|
||||
return (this._radius / this.getDefaultWidth()) * this.getWidth();
|
||||
}
|
||||
|
||||
getOriginY() {
|
||||
return this._radius;
|
||||
return (this._radius / this.getDefaultHeight()) * this.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace gdjs {
|
||||
_debugMode: boolean = false;
|
||||
_debugLight: PIXI.Container | null = null;
|
||||
_debugGraphics: PIXI.Graphics | null = null;
|
||||
_lightIconSprite: PIXI.Sprite | null = null;
|
||||
|
||||
/**
|
||||
* A polygon updated when vertices of the light are computed
|
||||
@@ -61,15 +62,53 @@ namespace gdjs {
|
||||
|
||||
this.updateDebugMode();
|
||||
|
||||
const game = this._object.getInstanceContainer().getGame();
|
||||
if (game.isInGameEdition()) {
|
||||
const texture = game
|
||||
.getImageManager()
|
||||
.getPIXITexture('InGameEditor-LightIcon');
|
||||
this._lightIconSprite = new PIXI.Sprite(texture);
|
||||
this._lightIconSprite.anchor.x = 0.5;
|
||||
this._lightIconSprite.anchor.y = 0.5;
|
||||
|
||||
this._debugGraphics = new PIXI.Graphics();
|
||||
|
||||
this._debugLight = new PIXI.Container();
|
||||
this._debugLight.addChild(this._debugGraphics);
|
||||
this._debugLight.addChild(this._lightIconSprite);
|
||||
// Force a 1st rendering of the circle.
|
||||
this._radius = 0;
|
||||
}
|
||||
|
||||
// Objects will be added in lighting layer, this is just to maintain consistency.
|
||||
if (this._light) {
|
||||
const rendererObject = this.getRendererObject();
|
||||
if (rendererObject) {
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
.getRenderer()
|
||||
.addRendererObject(
|
||||
this.getRendererObject(),
|
||||
runtimeObject.getZOrder()
|
||||
);
|
||||
.addRendererObject(rendererObject, runtimeObject.getZOrder());
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this._lightIconSprite) {
|
||||
this._lightIconSprite.removeFromParent();
|
||||
this._lightIconSprite.destroy();
|
||||
this._lightIconSprite = null;
|
||||
}
|
||||
if (this._debugGraphics) {
|
||||
this._debugGraphics.removeFromParent();
|
||||
this._debugGraphics.destroy();
|
||||
this._debugGraphics = null;
|
||||
}
|
||||
if (this._texture) {
|
||||
this._texture.destroy();
|
||||
this._texture = null;
|
||||
}
|
||||
if (this._light) {
|
||||
this._light.removeFromParent();
|
||||
this._light.destroy();
|
||||
this._light = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +162,40 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
ensureUpToDate() {
|
||||
if (this._object.getInstanceContainer().getGame().isInGameEdition()) {
|
||||
if (!this._debugLight) {
|
||||
return;
|
||||
}
|
||||
this._debugLight.x = this._object.getX();
|
||||
this._debugLight.y = this._object.getY();
|
||||
if (
|
||||
this._radius === this._object.getRadius() &&
|
||||
this._color[0] === this._object._color[0] &&
|
||||
this._color[1] === this._object._color[1] &&
|
||||
this._color[2] === this._object._color[2]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (this._debugGraphics) {
|
||||
this._radius = this._object.getRadius();
|
||||
this._color[0] = this._object._color[0];
|
||||
this._color[1] = this._object._color[1];
|
||||
this._color[2] = this._object._color[2];
|
||||
const radiusBorderWidth = 2;
|
||||
this._debugGraphics.clear();
|
||||
this._debugGraphics.lineStyle(
|
||||
radiusBorderWidth,
|
||||
gdjs.rgbToHexNumber(this._color[0], this._color[1], this._color[2]),
|
||||
0.8
|
||||
);
|
||||
this._debugGraphics.drawCircle(
|
||||
0,
|
||||
0,
|
||||
Math.max(1, this._radius - radiusBorderWidth)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this._object.isHidden()) {
|
||||
return;
|
||||
}
|
||||
@@ -133,6 +206,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateMesh(): void {
|
||||
if (this._object.getInstanceContainer().getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (!PIXI.utils.isWebGLSupported()) {
|
||||
logger.warn(
|
||||
'This device does not support webgl, which is required for Lighting Extension.'
|
||||
|
||||
@@ -61,11 +61,11 @@ namespace gdjs {
|
||||
return [(hexNumber >> 16) & 255, (hexNumber >> 8) & 255, hexNumber & 255];
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
override getRendererObject() {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
updateFromObjectData(
|
||||
override updateFromObjectData(
|
||||
oldObjectData: LightObjectData,
|
||||
newObjectData: LightObjectData
|
||||
): boolean {
|
||||
@@ -87,7 +87,7 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
override getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): LightNetworkSyncData {
|
||||
return {
|
||||
@@ -97,7 +97,7 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
override updateFromNetworkSyncData(
|
||||
networkSyncData: LightNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
@@ -111,10 +111,15 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
updatePreRender(): void {
|
||||
override updatePreRender(): void {
|
||||
this._renderer.ensureUpToDate();
|
||||
}
|
||||
|
||||
override onDestroyed(): void {
|
||||
super.onDestroyed();
|
||||
this._renderer.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the radius of the light object.
|
||||
* @returns radius of the light object.
|
||||
@@ -131,19 +136,11 @@ namespace gdjs {
|
||||
this._renderer.updateRadius();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the light object.
|
||||
* @returns height of light object.
|
||||
*/
|
||||
getHeight(): float {
|
||||
override getHeight(): float {
|
||||
return 2 * this._radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the light object.
|
||||
* @returns width of light object.
|
||||
*/
|
||||
getWidth(): float {
|
||||
override getWidth(): float {
|
||||
return 2 * this._radius;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,19 @@ describe('gdjs.LinksManager', function () {
|
||||
stopSoundsOnStartup: false,
|
||||
title: '',
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -1841,6 +1841,9 @@ namespace gdjs {
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (isQuickJoiningTooFast()) {
|
||||
return;
|
||||
}
|
||||
@@ -1860,6 +1863,9 @@ namespace gdjs {
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (isQuickJoiningTooFast()) {
|
||||
return;
|
||||
}
|
||||
@@ -1893,6 +1899,9 @@ namespace gdjs {
|
||||
export const openLobbiesWindow = async (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isLobbiesWindowOpen(runtimeScene) ||
|
||||
gdjs.playerAuthentication.isAuthenticationWindowOpen()
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace gdjs {
|
||||
|
||||
_renderer: gdjs.PanelSpriteRuntimeObjectRenderer;
|
||||
|
||||
_objectData: PanelSpriteObjectData;
|
||||
|
||||
/**
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param panelSpriteObjectData The initial properties of the object
|
||||
@@ -62,6 +64,7 @@ namespace gdjs {
|
||||
panelSpriteObjectData: PanelSpriteObjectData
|
||||
) {
|
||||
super(instanceContainer, panelSpriteObjectData);
|
||||
this._objectData = panelSpriteObjectData;
|
||||
this._rBorder = panelSpriteObjectData.rightMargin;
|
||||
this._lBorder = panelSpriteObjectData.leftMargin;
|
||||
this._tBorder = panelSpriteObjectData.topMargin;
|
||||
@@ -84,6 +87,7 @@ namespace gdjs {
|
||||
oldObjectData: PanelSpriteObjectData,
|
||||
newObjectData: PanelSpriteObjectData
|
||||
): boolean {
|
||||
this._objectData = newObjectData;
|
||||
if (oldObjectData.width !== newObjectData.width) {
|
||||
this.setWidth(newObjectData.width);
|
||||
}
|
||||
@@ -166,9 +170,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,6 +253,14 @@ namespace gdjs {
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._objectData.width;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._objectData.height;
|
||||
}
|
||||
|
||||
setOpacity(opacity: float): void {
|
||||
if (opacity < 0) {
|
||||
opacity = 0;
|
||||
|
||||
@@ -18,12 +18,15 @@ namespace gdjs {
|
||||
renderer: PIXI.Container;
|
||||
emitter: PIXI.particles.Emitter;
|
||||
started: boolean = false;
|
||||
helperGraphics: PIXI.Graphics | null = null;
|
||||
runtimeObject: gdjs.ParticleEmitterObject;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
runtimeObject: gdjs.RuntimeObject,
|
||||
runtimeObject: gdjs.ParticleEmitterObject,
|
||||
objectData: any
|
||||
) {
|
||||
this.runtimeObject = runtimeObject;
|
||||
const pixiRenderer = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
@@ -218,10 +221,52 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
update(delta: float): void {
|
||||
const wasEmitting = this.emitter.emit;
|
||||
this.emitter.update(delta);
|
||||
if (!this.started && wasEmitting) {
|
||||
this.started = true;
|
||||
if (
|
||||
!this.runtimeObject.getInstanceContainer().getGame().isInGameEdition()
|
||||
) {
|
||||
const wasEmitting = this.emitter.emit;
|
||||
this.emitter.update(delta);
|
||||
if (!this.started && wasEmitting) {
|
||||
this.started = true;
|
||||
}
|
||||
}
|
||||
if (this.helperGraphics) {
|
||||
this.helperGraphics.clear();
|
||||
this.helperGraphics.position.x = this.runtimeObject.getX();
|
||||
this.helperGraphics.position.y = this.runtimeObject.getY();
|
||||
|
||||
const emitterAngle = gdjs.toRad(this.runtimeObject.getAngle());
|
||||
const sprayConeAngle = gdjs.toRad(
|
||||
this.runtimeObject.getConeSprayAngle()
|
||||
);
|
||||
const line1Angle = emitterAngle - sprayConeAngle / 2;
|
||||
const line2Angle = emitterAngle + sprayConeAngle / 2;
|
||||
const length = 64;
|
||||
|
||||
this.helperGraphics.beginFill(0, 0);
|
||||
this.helperGraphics.lineStyle(
|
||||
3,
|
||||
this.runtimeObject.getParticleColorEnd(),
|
||||
1
|
||||
);
|
||||
this.helperGraphics.moveTo(0, 0);
|
||||
this.helperGraphics.lineTo(
|
||||
Math.cos(line1Angle) * length,
|
||||
Math.sin(line1Angle) * length
|
||||
);
|
||||
this.helperGraphics.moveTo(0, 0);
|
||||
this.helperGraphics.lineTo(
|
||||
Math.cos(line2Angle) * length,
|
||||
Math.sin(line2Angle) * length
|
||||
);
|
||||
this.helperGraphics.endFill();
|
||||
|
||||
this.helperGraphics.lineStyle(0, 0x000000, 1);
|
||||
this.helperGraphics.beginFill(
|
||||
this.runtimeObject.getParticleColorStart()
|
||||
);
|
||||
this.helperGraphics.drawCircle(0, 0, 8);
|
||||
this.helperGraphics.endFill();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +488,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
private static readonly frequencyMinimumValue = 0.0001;
|
||||
|
||||
setHelperVisible(visible: boolean) {
|
||||
if (visible && !this.helperGraphics) {
|
||||
this.helperGraphics = new PIXI.Graphics();
|
||||
this.renderer.addChild(this.helperGraphics);
|
||||
} else if (!visible && this.helperGraphics) {
|
||||
this.helperGraphics.removeFromParent();
|
||||
this.helperGraphics.destroy();
|
||||
this.helperGraphics = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore - Register the class to let the engine use it.
|
||||
|
||||
@@ -9,46 +9,46 @@ namespace gdjs {
|
||||
/**
|
||||
* @deprecated Data not used
|
||||
*/
|
||||
emitterAngleA: number;
|
||||
emitterForceMin: number;
|
||||
emitterAngleA: float;
|
||||
emitterForceMin: float;
|
||||
/**
|
||||
* Cone spray angle (degrees)
|
||||
*/
|
||||
emitterAngleB: number;
|
||||
zoneRadius: number;
|
||||
emitterForceMax: number;
|
||||
particleLifeTimeMax: number;
|
||||
particleLifeTimeMin: number;
|
||||
particleGravityY: number;
|
||||
particleGravityX: number;
|
||||
emitterAngleB: float;
|
||||
zoneRadius: float;
|
||||
emitterForceMax: float;
|
||||
particleLifeTimeMax: float;
|
||||
particleLifeTimeMin: float;
|
||||
particleGravityY: float;
|
||||
particleGravityX: float;
|
||||
particleColor2: string;
|
||||
particleColor1: string;
|
||||
particleSize2: number;
|
||||
particleSize1: number;
|
||||
particleSize2: float;
|
||||
particleSize1: float;
|
||||
/**
|
||||
* Particle max rotation speed (degrees/second)
|
||||
*/
|
||||
particleAngle2: number;
|
||||
particleAngle2: float;
|
||||
/**
|
||||
* Particle min rotation speed (degrees/second)
|
||||
*/
|
||||
particleAngle1: number;
|
||||
particleAlpha1: number;
|
||||
particleAngle1: float;
|
||||
particleAlpha1: float;
|
||||
rendererType: string;
|
||||
particleAlpha2: number;
|
||||
rendererParam2: number;
|
||||
rendererParam1: number;
|
||||
particleSizeRandomness1: number;
|
||||
particleSizeRandomness2: number;
|
||||
maxParticleNb: number;
|
||||
particleSizeRandomness1: float;
|
||||
particleSizeRandomness2: float;
|
||||
maxParticleNb: integer;
|
||||
additive: boolean;
|
||||
/** Resource name for image in particle */
|
||||
textureParticleName: string;
|
||||
tank: number;
|
||||
flow: number;
|
||||
tank: integer;
|
||||
flow: float;
|
||||
/** Destroy the object when there is no particles? */
|
||||
destroyWhenNoParticles: boolean;
|
||||
jumpForwardInTimeOnCreation: number;
|
||||
jumpForwardInTimeOnCreation: float;
|
||||
};
|
||||
|
||||
export type ParticleEmitterObjectData = ObjectData &
|
||||
@@ -107,33 +107,33 @@ namespace gdjs {
|
||||
/**
|
||||
* @deprecated Data not used
|
||||
*/
|
||||
angleA: number;
|
||||
angleB: number;
|
||||
forceMin: number;
|
||||
angleA: float;
|
||||
angleB: float;
|
||||
forceMin: float;
|
||||
forceMax: float;
|
||||
zoneRadius: number;
|
||||
lifeTimeMin: number;
|
||||
zoneRadius: float;
|
||||
lifeTimeMin: float;
|
||||
lifeTimeMax: float;
|
||||
gravityX: number;
|
||||
gravityY: number;
|
||||
color1: number;
|
||||
color2: number;
|
||||
size1: number;
|
||||
size2: number;
|
||||
gravityX: float;
|
||||
gravityY: float;
|
||||
color1: integer;
|
||||
color2: integer;
|
||||
size1: float;
|
||||
size2: float;
|
||||
alpha1: number;
|
||||
alpha2: number;
|
||||
rendererType: string;
|
||||
rendererParam1: number;
|
||||
rendererParam2: number;
|
||||
texture: string;
|
||||
flow: number;
|
||||
tank: number;
|
||||
flow: float;
|
||||
tank: integer;
|
||||
destroyWhenNoParticles: boolean;
|
||||
particleRotationMinSpeed: number;
|
||||
particleRotationMaxSpeed: number;
|
||||
maxParticlesCount: number;
|
||||
particleRotationMinSpeed: float;
|
||||
particleRotationMaxSpeed: float;
|
||||
maxParticlesCount: integer;
|
||||
additiveRendering: boolean;
|
||||
jumpForwardInTimeOnCreation: number;
|
||||
jumpForwardInTimeOnCreation: float;
|
||||
_jumpForwardInTimeCompleted: boolean = false;
|
||||
_posDirty: boolean = true;
|
||||
_angleDirty: boolean = true;
|
||||
@@ -160,6 +160,9 @@ namespace gdjs {
|
||||
// @ts-ignore
|
||||
_renderer: gdjs.ParticleEmitterObjectRenderer;
|
||||
|
||||
width: float = 32;
|
||||
height: float = 32;
|
||||
|
||||
/**
|
||||
* @param instanceContainer the container the object belongs to
|
||||
* @param particleObjectData The initial properties of the object
|
||||
@@ -174,6 +177,9 @@ namespace gdjs {
|
||||
this,
|
||||
particleObjectData
|
||||
);
|
||||
if (instanceContainer.getGame().isInGameEdition()) {
|
||||
this._renderer.setHelperVisible(true);
|
||||
}
|
||||
this.angleA = particleObjectData.emitterAngleA;
|
||||
this.angleB = particleObjectData.emitterAngleB;
|
||||
this.forceMin = particleObjectData.emitterForceMin;
|
||||
@@ -212,32 +218,32 @@ namespace gdjs {
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
setX(x: number): void {
|
||||
override setX(x: float): void {
|
||||
if (this.x !== x) {
|
||||
this._posDirty = true;
|
||||
}
|
||||
super.setX(x);
|
||||
}
|
||||
|
||||
setY(y: number): void {
|
||||
override setY(y: float): void {
|
||||
if (this.y !== y) {
|
||||
this._posDirty = true;
|
||||
}
|
||||
super.setY(y);
|
||||
}
|
||||
|
||||
setAngle(angle): void {
|
||||
override setAngle(angle: float): void {
|
||||
if (this.angle !== angle) {
|
||||
this._angleDirty = true;
|
||||
}
|
||||
super.setAngle(angle);
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
override getRendererObject() {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
updateFromObjectData(
|
||||
override updateFromObjectData(
|
||||
oldObjectData: ParticleEmitterObjectData,
|
||||
newObjectData: ParticleEmitterObjectData
|
||||
): boolean {
|
||||
@@ -370,7 +376,7 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
override getNetworkSyncData(
|
||||
syncOptions: GetNetworkSyncDataOptions
|
||||
): ParticleEmitterObjectNetworkSyncData {
|
||||
return {
|
||||
@@ -400,7 +406,7 @@ namespace gdjs {
|
||||
};
|
||||
}
|
||||
|
||||
updateFromNetworkSyncData(
|
||||
override updateFromNetworkSyncData(
|
||||
syncData: ParticleEmitterObjectNetworkSyncData,
|
||||
options: UpdateFromNetworkSyncDataOptions
|
||||
): void {
|
||||
@@ -486,7 +492,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
if (this._posDirty) {
|
||||
this._renderer.setPosition(this.getX(), this.getY());
|
||||
}
|
||||
@@ -574,12 +580,44 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
onDestroyed(): void {
|
||||
override onDestroyed(): void {
|
||||
this._renderer.destroy();
|
||||
super.onDestroyed();
|
||||
}
|
||||
|
||||
getEmitterForceMin(): number {
|
||||
override getOriginalWidth(): float {
|
||||
return 32;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return 32;
|
||||
}
|
||||
|
||||
override setWidth(width: float): void {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
override setHeight(height: float): void {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
override getWidth(): float {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
override getHeight(): float {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
override getDrawableX(): float {
|
||||
return this.getX() - this.getCenterX();
|
||||
}
|
||||
|
||||
override getDrawableY(): float {
|
||||
return this.getY() - this.getCenterY();
|
||||
}
|
||||
|
||||
getEmitterForceMin(): float {
|
||||
return this.forceMin;
|
||||
}
|
||||
|
||||
@@ -593,7 +631,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getEmitterForceMax(): number {
|
||||
getEmitterForceMax(): float {
|
||||
return this.forceMax;
|
||||
}
|
||||
|
||||
@@ -607,36 +645,36 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
setParticleRotationMinSpeed(speed: number): void {
|
||||
setParticleRotationMinSpeed(speed: float): void {
|
||||
if (this.particleRotationMinSpeed !== speed) {
|
||||
this._particleRotationSpeedDirty = true;
|
||||
this.particleRotationMinSpeed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
getParticleRotationMinSpeed(): number {
|
||||
getParticleRotationMinSpeed(): float {
|
||||
return this.particleRotationMinSpeed;
|
||||
}
|
||||
|
||||
setParticleRotationMaxSpeed(speed: number): void {
|
||||
setParticleRotationMaxSpeed(speed: float): void {
|
||||
if (this.particleRotationMaxSpeed !== speed) {
|
||||
this._particleRotationSpeedDirty = true;
|
||||
this.particleRotationMaxSpeed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
getParticleRotationMaxSpeed(): number {
|
||||
getParticleRotationMaxSpeed(): float {
|
||||
return this.particleRotationMaxSpeed;
|
||||
}
|
||||
|
||||
setMaxParticlesCount(count: number): void {
|
||||
setMaxParticlesCount(count: integer): void {
|
||||
if (this.maxParticlesCount !== count) {
|
||||
this._maxParticlesCountDirty = true;
|
||||
this.maxParticlesCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
getMaxParticlesCount(): number {
|
||||
getMaxParticlesCount(): integer {
|
||||
return this.maxParticlesCount;
|
||||
}
|
||||
|
||||
@@ -802,6 +840,14 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getParticleColorStart(): integer {
|
||||
return this.color1;
|
||||
}
|
||||
|
||||
getParticleColorEnd(): integer {
|
||||
return this.color2;
|
||||
}
|
||||
|
||||
getParticleRed1(): number {
|
||||
return gdjs.hexNumberToRGBArray(this.color1)[0];
|
||||
}
|
||||
@@ -904,7 +950,7 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
setParticleColor1AsNumber(color: number): void {
|
||||
setParticleColor1AsNumber(color: integer): void {
|
||||
this.color1 = color;
|
||||
this._colorDirty = true;
|
||||
}
|
||||
@@ -915,7 +961,7 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
setParticleColor2AsNumber(color: number): void {
|
||||
setParticleColor2AsNumber(color: integer): void {
|
||||
this.color2 = color;
|
||||
this._colorDirty = true;
|
||||
}
|
||||
@@ -995,26 +1041,26 @@ namespace gdjs {
|
||||
this._renderer.recreate();
|
||||
}
|
||||
|
||||
getFlow(): number {
|
||||
getFlow(): float {
|
||||
return this.flow;
|
||||
}
|
||||
|
||||
setFlow(flow: number): void {
|
||||
setFlow(flow: float): void {
|
||||
if (this.flow !== flow) {
|
||||
this.flow = flow;
|
||||
this._flowDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
getParticleCount(): number {
|
||||
getParticleCount(): integer {
|
||||
return this._renderer.getParticleCount();
|
||||
}
|
||||
|
||||
getTank(): number {
|
||||
getTank(): integer {
|
||||
return this.tank;
|
||||
}
|
||||
|
||||
setTank(tank: number): void {
|
||||
setTank(tank: integer): void {
|
||||
this.tank = tank;
|
||||
this._tankDirty = true;
|
||||
}
|
||||
@@ -1035,7 +1081,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
jumpEmitterForwardInTime(timeSkipped: number): void {
|
||||
jumpEmitterForwardInTime(timeSkipped: float): void {
|
||||
this._renderer.update(timeSkipped);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -39,6 +39,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -41,6 +41,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -623,10 +623,46 @@ Zv();a.b2Manifold.e_faceA=$v();a.b2Manifold.e_faceB=aw();a.b2_staticBody=bw();a.
|
||||
})();
|
||||
|
||||
gdjs.registerAsynchronouslyLoadingLibraryPromise(initializeBox2D({locateFile: function(path, prefix) {
|
||||
return location.protocol === 'file:' ?
|
||||
// This is needed to run on Electron.
|
||||
prefix + "Extensions/Physics2Behavior/" + path :
|
||||
prefix + path;
|
||||
// Path should always be "Box2D_v2.3.1_min.wasm.wasm" (and if it's not, we should probably hardcode it).
|
||||
if (path !== 'Box2D_v2.3.1_min.wasm.wasm') {
|
||||
console.warn("'path' argument sent to locateFile in Box2D_v2.3.1_min.wasm.js is not the expected string 'Box2D_v2.3.1_min.wasm.wasm'. Loading may fail.")
|
||||
}
|
||||
|
||||
// Prefix is typically:
|
||||
// Games ("exported", standalone game):
|
||||
// - Web game: "https://games.gdevelop-app.com/[...]/Extensions/Physics2Behavior/"
|
||||
// - Cordova Android: "https://localhost/Extensions/Physics2Behavior/".
|
||||
// - Cordova iOS: "ionic://localhost/Extensions/Physics2Behavior/".
|
||||
// - Electron macOS: "/private/var/[...]/Contents/Resources/app.asar/app/" (notice the missing folder).
|
||||
// - Electron Windows: "C:\Users\[...]\AppData\Local\[...]\resources\app.asar\app/" (notice the missing folder).
|
||||
// Preview (in the editor):
|
||||
// - Web app preview (dev editor): "http://localhost:5002/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Web app preview (production editor): "https://resources.gdevelop-app.com/[...]/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Electron app preview (dev editor): "/var/[...]/preview/" (notice the missing folder).
|
||||
// - Electron app preview (production editor): "/var/[...]/preview/" (notice the missing folder).
|
||||
// In-game editor:
|
||||
// - Web app (dev editor): "http://localhost:5002/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Web app (production editor): "https://resources.gdevelop-app.com/[...]/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Electron app (dev editor): "file:///var/[...]/in-game-editor-preview/Extensions/Physics2Behavior/"
|
||||
// - Electron app (production editor): "file:///var/[...]/in-game-editor-preview/Extensions/Physics2Behavior/"
|
||||
|
||||
// If the prefix is a full URL, it's a full URL to the folder containing this JS file.
|
||||
// Sill consider the case where the folder could have been missing.
|
||||
let url;
|
||||
if (prefix.startsWith('http:') || prefix.startsWith('https:')) {
|
||||
url = prefix.endsWith('Extensions/Physics2Behavior/') ?
|
||||
prefix + path :
|
||||
prefix + 'Extensions/Physics2Behavior/' + path;
|
||||
} else {
|
||||
// Electron or Cordova iOS will fall in this case.
|
||||
// We can't use this simple solution for http/https because
|
||||
// on the web-app, the runtime is not necessarily hosted
|
||||
// on the same domain as where the game generated files are served (so "prefix" is needed).
|
||||
url = "Extensions/Physics2Behavior/" + path;
|
||||
}
|
||||
|
||||
console.info(`Box2D wasm file is being loaded from path "${path}" with prefix "${prefix}". Resolved URL: "${url}".`);
|
||||
return url;
|
||||
}}).then(box2d => {
|
||||
window.Box2D = box2d;
|
||||
}));
|
||||
|
||||
@@ -647,6 +647,9 @@ namespace gdjs {
|
||||
export const displayAuthenticationBanner = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (_authenticationBanner) {
|
||||
// Banner already displayed, ensure it's visible.
|
||||
_authenticationBanner.style.opacity = '1';
|
||||
@@ -1042,6 +1045,10 @@ namespace gdjs {
|
||||
): gdjs.PromiseTask<{ status: 'logged' | 'errored' | 'dismissed' }> =>
|
||||
new gdjs.PromiseTask(
|
||||
new Promise((resolve) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
resolve({ status: 'dismissed' });
|
||||
}
|
||||
|
||||
// Create the authentication container for the player to wait.
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
|
||||
@@ -37,6 +37,11 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
|
||||
.AddDefaultBehavior("FlippableCapability::FlippableBehavior");
|
||||
|
||||
obj.AddInGameEditorResource()
|
||||
.SetResourceName("InGameEditor-ShapePainterIcon")
|
||||
.SetFilePath("Extensions/PrimitiveDrawing/InGameEditor/ShapePainterIcon.png")
|
||||
.SetKind("image");
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.AddAction(
|
||||
"Rectangle",
|
||||
|
||||
|
After Width: | Height: | Size: 394 B |
@@ -20,6 +20,8 @@ namespace gdjs {
|
||||
|
||||
_antialiasingFilter: null | PIXI.Filter = null;
|
||||
|
||||
_placeholder: PIXI.Sprite | null = null;
|
||||
|
||||
private static readonly _positionForTransformation: PIXI.IPointData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -405,6 +407,24 @@ namespace gdjs {
|
||||
|
||||
updatePreRender(): void {
|
||||
this.updatePositionIfNeeded();
|
||||
|
||||
const game = this._object.getRuntimeScene().getGame();
|
||||
if (
|
||||
game.isInGameEdition() &&
|
||||
this._graphics.geometry.graphicsData.length === 0
|
||||
) {
|
||||
if (!this._placeholder) {
|
||||
const texture = game
|
||||
.getImageManager()
|
||||
.getPIXITexture('InGameEditor-ShapePainterIcon');
|
||||
this._placeholder = new PIXI.Sprite(texture);
|
||||
this._graphics.addChild(this._placeholder);
|
||||
}
|
||||
} else if (this._placeholder) {
|
||||
this._placeholder.removeFromParent();
|
||||
this._placeholder.destroy();
|
||||
this._placeholder = null;
|
||||
}
|
||||
}
|
||||
|
||||
updatePositionX(): void {
|
||||
@@ -613,6 +633,11 @@ namespace gdjs {
|
||||
|
||||
destroy(): void {
|
||||
this._graphics.destroy();
|
||||
if (this._placeholder) {
|
||||
this._placeholder.removeFromParent();
|
||||
this._placeholder.destroy();
|
||||
this._placeholder = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -278,12 +278,8 @@ namespace gdjs {
|
||||
* @param initialInstanceData The extra parameters
|
||||
*/
|
||||
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
|
||||
@@ -50,6 +50,19 @@ describe('gdjs.ShapePainterRuntimeObject (using a PixiJS RuntimeGame with assets
|
||||
instances: [],
|
||||
variables: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -71,6 +71,17 @@ describe('SaveState', () => {
|
||||
instances: instances || [],
|
||||
variables: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 0,
|
||||
gridHeight: 0,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 0,
|
||||
snap: false,
|
||||
},
|
||||
});
|
||||
|
||||
describe('Save State Basics', () => {
|
||||
|
||||
@@ -203,13 +203,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
|
||||
const loadedSpineAtlas = this._loadedSpineAtlases.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (loadedSpineAtlas) {
|
||||
loadedSpineAtlas.dispose();
|
||||
this._loadedSpineAtlases.delete(resourceData);
|
||||
}
|
||||
|
||||
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
|
||||
const loadingSpineAtlas = this._loadingSpineAtlases.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (loadingSpineAtlas) {
|
||||
loadingSpineAtlas.then((atl) => atl.dispose());
|
||||
this._loadingSpineAtlases.delete(resourceData);
|
||||
|
||||
@@ -218,15 +218,13 @@ namespace gdjs {
|
||||
this.setSize(initialInstanceData.width, initialInstanceData.height);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
getDrawableX(): number {
|
||||
|
||||
@@ -61,6 +61,19 @@ describe('gdjs.TextInputRuntimeObject (using a PixiJS RuntimeGame with DOM eleme
|
||||
instances: [],
|
||||
variables: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -141,7 +141,9 @@ namespace gdjs {
|
||||
);
|
||||
this._borderOpacity = objectData.content.borderOpacity;
|
||||
this._borderWidth = objectData.content.borderWidth;
|
||||
this._disabled = objectData.content.disabled;
|
||||
this._disabled = instanceContainer.getGame().isInGameEdition()
|
||||
? true
|
||||
: objectData.content.disabled;
|
||||
this._readOnly = objectData.content.readOnly;
|
||||
this._spellCheck =
|
||||
objectData.content.spellCheck !== undefined
|
||||
@@ -334,9 +336,11 @@ namespace gdjs {
|
||||
this.setHeight(initialInstanceData.height);
|
||||
this._renderer.updatePadding();
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
onScenePaused(runtimeScene: gdjs.RuntimeScene): void {
|
||||
@@ -566,6 +570,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setDisabled(value: boolean) {
|
||||
if (this.getInstanceContainer().getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
this._disabled = value;
|
||||
this._renderer.updateDisabled();
|
||||
}
|
||||
|
||||
@@ -340,7 +340,9 @@ namespace gdjs {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
override updatePreRender(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
this._renderer.ensureUpToDate();
|
||||
}
|
||||
|
||||
@@ -358,9 +360,11 @@ namespace gdjs {
|
||||
} else {
|
||||
this.setWrapping(false);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -240,9 +240,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
|
||||
// 4. Update position (calculations based on renderer's dimensions).
|
||||
this._renderer.updatePosition();
|
||||
@@ -453,6 +455,14 @@ namespace gdjs {
|
||||
return this._renderer.getHeight();
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this.getTileMapWidth();
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this.getTileMapHeight();
|
||||
}
|
||||
|
||||
getScaleX(): float {
|
||||
return this._renderer.getScaleX();
|
||||
}
|
||||
|
||||
@@ -57,6 +57,19 @@ describe('gdjs.TileMapCollisionMaskRuntimeObject', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -197,9 +197,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
updateTileMap(): void {
|
||||
@@ -343,6 +345,14 @@ namespace gdjs {
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this.getTileMapWidth();
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this.getTileMapHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace gdjs {
|
||||
|
||||
_renderer: gdjs.TiledSpriteRuntimeObjectRenderer;
|
||||
|
||||
_objectData: TiledSpriteObjectData;
|
||||
|
||||
/**
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param tiledSpriteObjectData The initial properties of the object
|
||||
@@ -51,6 +53,7 @@ namespace gdjs {
|
||||
tiledSpriteObjectData: TiledSpriteObjectData
|
||||
) {
|
||||
super(instanceContainer, tiledSpriteObjectData);
|
||||
this._objectData = tiledSpriteObjectData;
|
||||
this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer,
|
||||
@@ -66,6 +69,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromObjectData(oldObjectData, newObjectData): boolean {
|
||||
this._objectData = newObjectData;
|
||||
if (oldObjectData.texture !== newObjectData.texture) {
|
||||
this.setTexture(newObjectData.texture, this.getRuntimeScene());
|
||||
}
|
||||
@@ -129,9 +133,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,6 +229,14 @@ namespace gdjs {
|
||||
this.setHeight(height);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._objectData.width;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._objectData.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the offset on the X-axis when displaying the image of the Tiled Sprite object.
|
||||
* @param xOffset The new offset on the X-axis.
|
||||
|
||||
@@ -34,6 +34,19 @@ describe('gdjs.TopDownMovementRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
||||
@@ -151,9 +151,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
onDestroyed(): void {
|
||||
|
||||
@@ -13,11 +13,15 @@
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Events/CodeGeneration/DiagnosticReport.h"
|
||||
#include "GDCore/Extensions/Metadata/InGameEditorResourceMetadata.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
|
||||
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
|
||||
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
|
||||
#include "GDCore/IDE/ProjectStripper.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/ExternalLayout.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
@@ -47,7 +51,7 @@ Exporter::~Exporter() {}
|
||||
bool Exporter::ExportProjectForPixiPreview(
|
||||
const PreviewExportOptions &options) {
|
||||
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
|
||||
return helper.ExportProjectForPixiPreview(options);
|
||||
return helper.ExportProjectForPixiPreview(options, includesFiles);
|
||||
}
|
||||
|
||||
bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
@@ -80,7 +84,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
|
||||
// Prepare the export directory
|
||||
fs.MkDir(exportDir);
|
||||
std::vector<gd::String> includesFiles;
|
||||
includesFiles.clear();
|
||||
std::vector<gd::String> resourcesFiles;
|
||||
|
||||
// Export the resources (before generating events as some resources
|
||||
@@ -98,6 +102,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
helper.AddLibsInclude(
|
||||
/*pixiRenderers=*/true,
|
||||
usedExtensionsResult.Has3DObjects(),
|
||||
/*isInGameEditor=*/false,
|
||||
/*includeWebsocketDebuggerClient=*/false,
|
||||
/*includeWindowMessageDebuggerClient=*/false,
|
||||
/*includeMinimalDebuggerClient=*/false,
|
||||
@@ -120,7 +125,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
helper.ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export events
|
||||
if (!helper.ExportEventsCode(exportedProject,
|
||||
if (!helper.ExportScenesEventsCode(exportedProject,
|
||||
codeOutputDir,
|
||||
includesFiles,
|
||||
wholeProjectDiagnosticReport,
|
||||
@@ -130,29 +135,11 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto projectUsedResources =
|
||||
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
|
||||
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
|
||||
for (std::size_t layoutIndex = 0;
|
||||
layoutIndex < exportedProject.GetLayoutsCount();
|
||||
layoutIndex++) {
|
||||
auto &layout = exportedProject.GetLayout(layoutIndex);
|
||||
scenesUsedResources[layout.GetName()] =
|
||||
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use
|
||||
// stripped things like objects groups...)...
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
|
||||
//...and export it
|
||||
gd::SerializerElement noRuntimeGameOptions;
|
||||
helper.ExportProjectData(fs,
|
||||
exportedProject,
|
||||
codeOutputDir + "/data.js",
|
||||
noRuntimeGameOptions,
|
||||
projectUsedResources,
|
||||
scenesUsedResources);
|
||||
std::vector<gd::InGameEditorResourceMetadata> noInGameEditorResources;
|
||||
helper.ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
|
||||
noRuntimeGameOptions, false, noInGameEditorResources);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
|
||||
@@ -215,4 +202,18 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Exporter::SerializeProjectData(const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &projectDataElement) {
|
||||
std::vector<gd::InGameEditorResourceMetadata> noInGameEditorResources;
|
||||
ExporterHelper::SerializeProjectData(fs, project, options, projectDataElement, noInGameEditorResources);
|
||||
}
|
||||
|
||||
void Exporter::SerializeRuntimeGameOptions(
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &runtimeGameOptionsElement) {
|
||||
ExporterHelper::SerializeRuntimeGameOptions(
|
||||
fs, gdjsRoot, options, includesFiles, runtimeGameOptionsElement);
|
||||
}
|
||||
|
||||
} // namespace gdjs
|
||||
|
||||
@@ -16,6 +16,7 @@ class Project;
|
||||
class Layout;
|
||||
class ExternalLayout;
|
||||
class AbstractFileSystem;
|
||||
class SerializerElement;
|
||||
} // namespace gd
|
||||
namespace gdjs {
|
||||
struct PreviewExportOptions;
|
||||
@@ -64,7 +65,33 @@ class Exporter {
|
||||
codeOutputDir = codeOutputDir_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Serialize a project without its events to JSON
|
||||
*
|
||||
* \param project The project to be exported
|
||||
* \param options The content of the extra configuration
|
||||
* \param projectDataElement The element where the project data is serialized
|
||||
*/
|
||||
void SerializeProjectData(const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &projectDataElement);
|
||||
|
||||
/**
|
||||
* \brief Serialize the content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions to JSON
|
||||
*
|
||||
* \warning `ExportProjectForPixiPreview` must be called first to serialize
|
||||
* the list of scripts files.
|
||||
*
|
||||
* \param options The content of the extra configuration
|
||||
* \param runtimeGameOptionsElement The element where the game options are
|
||||
* serialized
|
||||
*/
|
||||
void
|
||||
SerializeRuntimeGameOptions(const PreviewExportOptions &options,
|
||||
gd::SerializerElement &runtimeGameOptionsElement);
|
||||
|
||||
private:
|
||||
gd::AbstractFileSystem&
|
||||
fs; ///< The abstract file system to be used for exportation.
|
||||
gd::String lastError; ///< The last error that occurred.
|
||||
@@ -72,6 +99,8 @@ class Exporter {
|
||||
gdjsRoot; ///< The root directory of GDJS, used to copy runtime files.
|
||||
gd::String codeOutputDir; ///< The directory where JS code is outputted. Will
|
||||
///< be then copied to the final output directory.
|
||||
std::vector<gd::String>
|
||||
includesFiles; ///< The list of scripts files - useful for hot-reloading
|
||||
};
|
||||
|
||||
} // namespace gdjs
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "GDCore/Events/CodeGeneration/EffectsCodeGenerator.h"
|
||||
#include "GDCore/Extensions/Metadata/DependencyMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/InGameEditorResourceMetadata.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
@@ -28,10 +29,13 @@
|
||||
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
|
||||
#include "GDCore/IDE/ExportedDependencyResolver.h"
|
||||
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
|
||||
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
|
||||
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
|
||||
#include "GDCore/IDE/ProjectStripper.h"
|
||||
#include "GDCore/IDE/ResourceExposer.h"
|
||||
#include "GDCore/IDE/SceneNameMangler.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/ExternalLayout.h"
|
||||
@@ -58,7 +62,6 @@ double GetTimeSpent(double previousTime) { return GetTimeNow() - previousTime; }
|
||||
double LogTimeSpent(const gd::String &name, double previousTime) {
|
||||
gd::LogStatus(name + " took " + gd::String::From(GetTimeSpent(previousTime)) +
|
||||
"ms");
|
||||
std::cout << std::endl;
|
||||
return GetTimeNow();
|
||||
}
|
||||
} // namespace
|
||||
@@ -104,128 +107,325 @@ ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
|
||||
: fs(fileSystem), gdjsRoot(gdjsRoot_), codeOutputDir(codeOutputDir_) {};
|
||||
|
||||
bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
const PreviewExportOptions &options) {
|
||||
const PreviewExportOptions &options,
|
||||
std::vector<gd::String> &includesFiles) {
|
||||
|
||||
if (options.isInGameEdition && !options.shouldReloadProjectData &&
|
||||
!options.shouldReloadLibraries && !options.shouldGenerateScenesEventsCode &&
|
||||
!options.shouldClearExportFolder) {
|
||||
gd::LogStatus("Skip project export entirely");
|
||||
return "";
|
||||
}
|
||||
|
||||
double previousTime = GetTimeNow();
|
||||
fs.MkDir(options.exportPath);
|
||||
fs.ClearDir(options.exportPath);
|
||||
std::vector<gd::String> includesFiles;
|
||||
if (options.shouldClearExportFolder) {
|
||||
fs.ClearDir(options.exportPath);
|
||||
}
|
||||
includesFiles.clear();
|
||||
std::vector<gd::String> resourcesFiles;
|
||||
|
||||
std::vector<gd::InGameEditorResourceMetadata> inGameEditorResources;
|
||||
|
||||
// TODO Try to remove side effects to avoid the copy
|
||||
// that destroys the AST in cache.
|
||||
gd::Project exportedProject = options.project;
|
||||
const gd::Project &immutableProject = exportedProject;
|
||||
const gd::Project &immutableProject = options.project;
|
||||
previousTime = LogTimeSpent("Project cloning", previousTime);
|
||||
|
||||
if (options.fullLoadingScreen) {
|
||||
// Use project properties fallback to set empty properties
|
||||
if (exportedProject.GetAuthorIds().empty() &&
|
||||
!options.fallbackAuthorId.empty()) {
|
||||
exportedProject.GetAuthorIds().push_back(options.fallbackAuthorId);
|
||||
}
|
||||
if (exportedProject.GetAuthorUsernames().empty() &&
|
||||
!options.fallbackAuthorUsername.empty()) {
|
||||
exportedProject.GetAuthorUsernames().push_back(
|
||||
options.fallbackAuthorUsername);
|
||||
if (options.isInGameEdition) {
|
||||
if (options.shouldReloadProjectData ||
|
||||
options.shouldGenerateScenesEventsCode ||
|
||||
options.shouldClearExportFolder) {
|
||||
auto projectDirectory = fs.DirNameFrom(exportedProject.GetProjectFile());
|
||||
gd::ResourcesMergingHelper resourcesMergingHelper(
|
||||
exportedProject.GetResourcesManager(), fs);
|
||||
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
|
||||
resourcesMergingHelper.SetShouldUseOriginalAbsoluteFilenames();
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(exportedProject,
|
||||
resourcesMergingHelper);
|
||||
|
||||
previousTime = LogTimeSpent("Resource path resolving", previousTime);
|
||||
}
|
||||
gd::LogStatus("Resource export is skipped");
|
||||
} else {
|
||||
// Most of the time, we skip the logo and minimum duration so that
|
||||
// the preview start as soon as possible.
|
||||
exportedProject.GetLoadingScreen()
|
||||
.ShowGDevelopLogoDuringLoadingScreen(false)
|
||||
.SetMinDuration(0);
|
||||
exportedProject.GetWatermark().ShowGDevelopWatermark(false);
|
||||
// Export resources (*before* generating events as some resources filenames
|
||||
// may be updated)
|
||||
ExportResources(fs, exportedProject, options.exportPath);
|
||||
|
||||
previousTime = LogTimeSpent("Resource export", previousTime);
|
||||
}
|
||||
|
||||
// Export resources (*before* generating events as some resources filenames
|
||||
// may be updated)
|
||||
ExportResources(fs, exportedProject, options.exportPath);
|
||||
|
||||
previousTime = LogTimeSpent("Resource export", previousTime);
|
||||
|
||||
// Compatibility with GD <= 5.0-beta56
|
||||
// Stay compatible with text objects declaring their font as just a filename
|
||||
// without a font resource - by manually adding these resources.
|
||||
AddDeprecatedFontFilesToFontResources(
|
||||
fs, exportedProject.GetResourcesManager(), options.exportPath);
|
||||
// end of compatibility code
|
||||
|
||||
auto usedExtensionsResult =
|
||||
gd::UsedExtensionsFinder::ScanProject(exportedProject);
|
||||
|
||||
// Export engine libraries
|
||||
AddLibsInclude(/*pixiRenderers=*/true,
|
||||
usedExtensionsResult.Has3DObjects(),
|
||||
/*includeWebsocketDebuggerClient=*/
|
||||
!options.websocketDebuggerServerAddress.empty(),
|
||||
/*includeWindowMessageDebuggerClient=*/
|
||||
options.useWindowMessageDebuggerClient,
|
||||
/*includeMinimalDebuggerClient=*/
|
||||
options.useMinimalDebuggerClient,
|
||||
/*includeCaptureManager=*/
|
||||
!options.captureOptions.IsEmpty(),
|
||||
/*includeInAppTutorialMessage*/
|
||||
!options.inAppTutorialMessageInPreview.empty(),
|
||||
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
|
||||
// Export files for free function, object and behaviors
|
||||
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
for (const auto &requiredFile : usedExtensionsResult.GetUsedRequiredFiles()) {
|
||||
InsertUnique(resourcesFiles, requiredFile);
|
||||
if (options.shouldReloadProjectData ||
|
||||
options.shouldGenerateScenesEventsCode ||
|
||||
options.shouldClearExportFolder) {
|
||||
// Compatibility with GD <= 5.0-beta56
|
||||
// Stay compatible with text objects declaring their font as just a filename
|
||||
// without a font resource - by manually adding these resources.
|
||||
AddDeprecatedFontFilesToFontResources(
|
||||
fs, exportedProject.GetResourcesManager(), options.exportPath);
|
||||
// end of compatibility code
|
||||
}
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to
|
||||
// the engine)
|
||||
ExportEffectIncludes(exportedProject, includesFiles);
|
||||
std::vector<gd::SourceFileMetadata> noUsedSourceFiles;
|
||||
std::vector<gd::SourceFileMetadata> &usedSourceFiles = noUsedSourceFiles;
|
||||
if (options.shouldReloadLibraries || options.shouldClearExportFolder) {
|
||||
auto usedExtensionsResult =
|
||||
gd::UsedExtensionsFinder::ScanProject(exportedProject);
|
||||
usedSourceFiles = usedExtensionsResult.GetUsedSourceFiles();
|
||||
|
||||
previousTime = LogTimeSpent("Include files export", previousTime);
|
||||
// Export engine libraries
|
||||
AddLibsInclude(/*pixiRenderers=*/true,
|
||||
/*pixiInThreeRenderers=*/
|
||||
usedExtensionsResult.Has3DObjects(),
|
||||
/*isInGameEdition=*/
|
||||
options.isInGameEdition,
|
||||
/*includeWebsocketDebuggerClient=*/
|
||||
!options.websocketDebuggerServerAddress.empty(),
|
||||
/*includeWindowMessageDebuggerClient=*/
|
||||
options.useWindowMessageDebuggerClient,
|
||||
/*includeMinimalDebuggerClient=*/
|
||||
options.useMinimalDebuggerClient,
|
||||
/*includeCaptureManager=*/
|
||||
!options.captureOptions.IsEmpty(),
|
||||
/*includeInAppTutorialMessage*/
|
||||
!options.inAppTutorialMessageInPreview.empty(),
|
||||
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
|
||||
if (!options.projectDataOnlyExport) {
|
||||
// Export files for free function, object and behaviors
|
||||
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
for (const auto &requiredFile : usedExtensionsResult.GetUsedRequiredFiles()) {
|
||||
InsertUnique(resourcesFiles, requiredFile);
|
||||
}
|
||||
|
||||
if (options.isInGameEdition) {
|
||||
// List the in-game editor resources used by the project, so they can
|
||||
// be later included in the exported project resources.
|
||||
for (const auto &inGameEditorResource : usedExtensionsResult.GetUsedInGameEditorResources()) {
|
||||
inGameEditorResources.push_back(inGameEditorResource);
|
||||
|
||||
// Always use absolute paths for in-game editor resources.
|
||||
// There are not copied and instead directly refer to the file in the Runtime folder.
|
||||
gd::String resourceFile = inGameEditorResource.GetFilePath();
|
||||
if (!fs.IsAbsolute(resourceFile)) {
|
||||
fs.MakeAbsolute(resourceFile, gdjsRoot + "/Runtime");
|
||||
}
|
||||
inGameEditorResources.back().SetFilePath(resourceFile);
|
||||
}
|
||||
|
||||
// TODO Scan the objects and events of event-based objects
|
||||
// (it could be an alternative method ScanProjectAndEventsBasedObjects in
|
||||
// UsedExtensionsFinder).
|
||||
// This is already done by UsedExtensionsFinder, but maybe it shouldn't.
|
||||
|
||||
// Export all event-based objects because they can be edited even if they
|
||||
// are not used yet.
|
||||
for (std::size_t e = 0;
|
||||
e < exportedProject.GetEventsFunctionsExtensionsCount(); e++) {
|
||||
auto &eventsFunctionsExtension =
|
||||
exportedProject.GetEventsFunctionsExtension(e);
|
||||
|
||||
for (auto &&eventsBasedObjectUniquePtr :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects()
|
||||
.GetInternalVector()) {
|
||||
auto eventsBasedObject = eventsBasedObjectUniquePtr.get();
|
||||
|
||||
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
|
||||
exportedProject.GetCurrentPlatform(),
|
||||
gd::PlatformExtension::GetObjectFullType(
|
||||
eventsFunctionsExtension.GetName(),
|
||||
eventsBasedObject->GetName()));
|
||||
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
for (auto &behaviorType :
|
||||
metadata.GetMetadata().GetDefaultBehaviors()) {
|
||||
auto behaviorMetadata =
|
||||
gd::MetadataProvider::GetExtensionAndBehaviorMetadata(
|
||||
exportedProject.GetCurrentPlatform(), behaviorType);
|
||||
for (auto &&includeFile :
|
||||
behaviorMetadata.GetMetadata().includeFiles) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to
|
||||
// the engine)
|
||||
ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
previousTime = LogTimeSpent("Include files export", previousTime);
|
||||
}
|
||||
else {
|
||||
gd::LogStatus("Include files export is skipped");
|
||||
}
|
||||
|
||||
if (options.shouldGenerateScenesEventsCode) {
|
||||
gd::WholeProjectDiagnosticReport &wholeProjectDiagnosticReport =
|
||||
options.project.GetWholeProjectDiagnosticReport();
|
||||
wholeProjectDiagnosticReport.Clear();
|
||||
|
||||
// Generate events code
|
||||
if (!ExportEventsCode(immutableProject,
|
||||
if (!ExportScenesEventsCode(immutableProject,
|
||||
codeOutputDir,
|
||||
includesFiles,
|
||||
wholeProjectDiagnosticReport,
|
||||
true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
previousTime = LogTimeSpent("Events code export", previousTime);
|
||||
}
|
||||
|
||||
auto projectUsedResources =
|
||||
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
|
||||
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
|
||||
for (std::size_t layoutIndex = 0;
|
||||
layoutIndex < exportedProject.GetLayoutsCount();
|
||||
layoutIndex++) {
|
||||
auto &layout = exportedProject.GetLayout(layoutIndex);
|
||||
scenesUsedResources[layout.GetName()] =
|
||||
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
|
||||
else {
|
||||
gd::LogStatus("Events code export is skipped");
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use stripped
|
||||
// things (objects groups...))
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
exportedProject.SetFirstLayout(options.layoutName);
|
||||
if (options.shouldReloadProjectData || options.shouldClearExportFolder) {
|
||||
|
||||
previousTime = LogTimeSpent("Data stripping", previousTime);
|
||||
if (options.fullLoadingScreen) {
|
||||
// Use project properties fallback to set empty properties
|
||||
if (exportedProject.GetAuthorIds().empty() &&
|
||||
!options.fallbackAuthorId.empty()) {
|
||||
exportedProject.GetAuthorIds().push_back(options.fallbackAuthorId);
|
||||
}
|
||||
if (exportedProject.GetAuthorUsernames().empty() &&
|
||||
!options.fallbackAuthorUsername.empty()) {
|
||||
exportedProject.GetAuthorUsernames().push_back(
|
||||
options.fallbackAuthorUsername);
|
||||
}
|
||||
} else {
|
||||
// Most of the time, we skip the logo and minimum duration so that
|
||||
// the preview start as soon as possible.
|
||||
exportedProject.GetLoadingScreen()
|
||||
.ShowGDevelopLogoDuringLoadingScreen(false)
|
||||
.SetMinDuration(0);
|
||||
exportedProject.GetWatermark().ShowGDevelopWatermark(false);
|
||||
}
|
||||
|
||||
gd::SerializerElement runtimeGameOptions;
|
||||
ExporterHelper::SerializeRuntimeGameOptions(fs, gdjsRoot, options,
|
||||
includesFiles, runtimeGameOptions);
|
||||
ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
|
||||
runtimeGameOptions, options.isInGameEdition,
|
||||
inGameEditorResources);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
previousTime = LogTimeSpent("Project data export", previousTime);
|
||||
}
|
||||
else {
|
||||
gd::LogStatus("Project data export is skipped");
|
||||
}
|
||||
|
||||
if (options.shouldReloadLibraries || options.shouldClearExportFolder) {
|
||||
// Copy all the dependencies and their source maps
|
||||
ExportIncludesAndLibs(includesFiles, options.exportPath, true);
|
||||
ExportIncludesAndLibs(resourcesFiles, options.exportPath, true);
|
||||
|
||||
// TODO Build a full includesFiles list without actually doing export or
|
||||
// generation.
|
||||
if (options.shouldGenerateScenesEventsCode || options.shouldClearExportFolder) {
|
||||
// Create the index file
|
||||
if (!ExportIndexFile(exportedProject, gdjsRoot + "/Runtime/index.html",
|
||||
options.exportPath, includesFiles, usedSourceFiles,
|
||||
options.nonRuntimeScriptsCacheBurst,
|
||||
"gdjs.runtimeGameOptions")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
previousTime = LogTimeSpent("Include and libs export", previousTime);
|
||||
} else {
|
||||
gd::LogStatus("Include and libs export is skipped");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::ExportProjectData(
|
||||
gd::AbstractFileSystem &fs, gd::Project &project, gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions, bool isInGameEdition,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources) {
|
||||
fs.MkDir(fs.DirNameFrom(filename));
|
||||
|
||||
gd::SerializerElement projectDataElement;
|
||||
ExporterHelper::StriptAndSerializeProjectData(project, projectDataElement,
|
||||
isInGameEdition,
|
||||
inGameEditorResources);
|
||||
|
||||
// Save the project to JSON
|
||||
gd::String output =
|
||||
"gdjs.projectData = " + gd::Serializer::ToJSON(projectDataElement) +
|
||||
";\ngdjs.runtimeGameOptions = " + gd::Serializer::ToJSON(runtimeGameOptions) +
|
||||
";\n";
|
||||
|
||||
if (!fs.WriteToFile(filename, output))
|
||||
return "Unable to write " + filename;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void ExporterHelper::SerializeRuntimeGameOptions(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const PreviewExportOptions &options, std::vector<gd::String> &includesFiles,
|
||||
gd::SerializerElement &runtimeGameOptions) {
|
||||
// Create the setup options passed to the gdjs.RuntimeGame
|
||||
gd::SerializerElement runtimeGameOptions;
|
||||
runtimeGameOptions.AddChild("isPreview").SetBoolValue(true);
|
||||
if (!options.externalLayoutName.empty()) {
|
||||
runtimeGameOptions.AddChild("injectExternalLayout")
|
||||
.SetValue(options.externalLayoutName);
|
||||
|
||||
auto &initialRuntimeGameStatus =
|
||||
runtimeGameOptions.AddChild("initialRuntimeGameStatus");
|
||||
initialRuntimeGameStatus.AddChild("sceneName")
|
||||
.SetStringValue(options.layoutName);
|
||||
if (options.isInGameEdition) {
|
||||
initialRuntimeGameStatus.AddChild("isInGameEdition").SetBoolValue(true);
|
||||
initialRuntimeGameStatus.AddChild("editorId").SetValue(options.editorId);
|
||||
if (!options.editorCamera3DCameraMode.empty()) {
|
||||
auto &editorCamera3D =
|
||||
initialRuntimeGameStatus.AddChild("editorCamera3D");
|
||||
editorCamera3D.AddChild("cameraMode").SetStringValue(
|
||||
options.editorCamera3DCameraMode);
|
||||
editorCamera3D.AddChild("positionX")
|
||||
.SetDoubleValue(options.editorCamera3DPositionX);
|
||||
editorCamera3D.AddChild("positionY")
|
||||
.SetDoubleValue(options.editorCamera3DPositionY);
|
||||
editorCamera3D.AddChild("positionZ")
|
||||
.SetDoubleValue(options.editorCamera3DPositionZ);
|
||||
editorCamera3D.AddChild("rotationAngle")
|
||||
.SetDoubleValue(options.editorCamera3DRotationAngle);
|
||||
editorCamera3D.AddChild("elevationAngle")
|
||||
.SetDoubleValue(options.editorCamera3DElevationAngle);
|
||||
editorCamera3D.AddChild("distance")
|
||||
.SetDoubleValue(options.editorCamera3DDistance);
|
||||
}
|
||||
}
|
||||
runtimeGameOptions.AddChild("projectDataOnlyExport")
|
||||
.SetBoolValue(options.projectDataOnlyExport);
|
||||
if (!options.externalLayoutName.empty()) {
|
||||
initialRuntimeGameStatus.AddChild("injectedExternalLayoutName")
|
||||
.SetValue(options.externalLayoutName);
|
||||
|
||||
if (options.isInGameEdition) {
|
||||
initialRuntimeGameStatus.AddChild("skipCreatingInstancesFromScene")
|
||||
.SetBoolValue(true);
|
||||
}
|
||||
}
|
||||
if (!options.eventsBasedObjectType.empty()) {
|
||||
initialRuntimeGameStatus.AddChild("eventsBasedObjectType")
|
||||
.SetValue(options.eventsBasedObjectType);
|
||||
initialRuntimeGameStatus.AddChild("eventsBasedObjectVariantName")
|
||||
.SetValue(options.eventsBasedObjectVariantName);
|
||||
}
|
||||
|
||||
if (!options.inGameEditorSettingsJson.empty()) {
|
||||
runtimeGameOptions.AddChild("inGameEditorSettings") =
|
||||
gd::Serializer::FromJSON(options.inGameEditorSettingsJson);
|
||||
}
|
||||
|
||||
runtimeGameOptions.AddChild("shouldReloadLibraries")
|
||||
.SetBoolValue(options.shouldReloadLibraries);
|
||||
runtimeGameOptions.AddChild("shouldGenerateScenesEventsCode")
|
||||
.SetBoolValue(options.shouldGenerateScenesEventsCode);
|
||||
|
||||
runtimeGameOptions.AddChild("nativeMobileApp")
|
||||
.SetBoolValue(options.nativeMobileApp);
|
||||
runtimeGameOptions.AddChild("websocketDebuggerServerAddress")
|
||||
@@ -297,71 +497,133 @@ bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
|
||||
for (const auto &includeFile : includesFiles) {
|
||||
auto hashIt = options.includeFileHashes.find(includeFile);
|
||||
gd::String scriptSrc = GetExportedIncludeFilename(includeFile);
|
||||
gd::String scriptSrc = GetExportedIncludeFilename(fs, gdjsRoot, includeFile);
|
||||
scriptFilesElement.AddChild("scriptFile")
|
||||
.SetStringAttribute("path", scriptSrc)
|
||||
.SetIntAttribute(
|
||||
"hash",
|
||||
hashIt != options.includeFileHashes.end() ? hashIt->second : 0);
|
||||
}
|
||||
|
||||
// Export the project
|
||||
ExportProjectData(fs,
|
||||
exportedProject,
|
||||
codeOutputDir + "/data.js",
|
||||
runtimeGameOptions,
|
||||
projectUsedResources,
|
||||
scenesUsedResources);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
previousTime = LogTimeSpent("Project data export", previousTime);
|
||||
|
||||
// Copy all the dependencies and their source maps
|
||||
ExportIncludesAndLibs(includesFiles, options.exportPath, true);
|
||||
ExportIncludesAndLibs(resourcesFiles, options.exportPath, true);
|
||||
|
||||
// Create the index file
|
||||
if (!ExportIndexFile(exportedProject,
|
||||
gdjsRoot + "/Runtime/index.html",
|
||||
options.exportPath,
|
||||
includesFiles,
|
||||
usedExtensionsResult.GetUsedSourceFiles(),
|
||||
options.nonRuntimeScriptsCacheBurst,
|
||||
"gdjs.runtimeGameOptions"))
|
||||
return false;
|
||||
|
||||
previousTime = LogTimeSpent("Include and libs export", previousTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::ExportProjectData(
|
||||
gd::AbstractFileSystem &fs,
|
||||
void ExporterHelper::AddInGameEditorResources(
|
||||
gd::Project &project,
|
||||
gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
|
||||
fs.MkDir(fs.DirNameFrom(filename));
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources) {
|
||||
for (const auto &inGameEditorResource : inGameEditorResources) {
|
||||
project.GetResourcesManager().AddResource(
|
||||
inGameEditorResource.GetResourceName(),
|
||||
inGameEditorResource.GetFilePath(),
|
||||
inGameEditorResource.GetKind());
|
||||
projectUsedResources.insert(inGameEditorResource.GetResourceName());
|
||||
}
|
||||
}
|
||||
|
||||
void ExporterHelper::SerializeProjectData(gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &rootElement,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources) {
|
||||
gd::Project clonedProject = project;
|
||||
|
||||
// Replace all resource file paths with the one used in exported projects.
|
||||
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
|
||||
gd::ResourcesMergingHelper resourcesMergingHelper(
|
||||
clonedProject.GetResourcesManager(), fs);
|
||||
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
|
||||
if (options.isInGameEdition) {
|
||||
resourcesMergingHelper.SetShouldUseOriginalAbsoluteFilenames();
|
||||
} else {
|
||||
resourcesMergingHelper.PreserveDirectoriesStructure(false);
|
||||
resourcesMergingHelper.PreserveAbsoluteFilenames(false);
|
||||
}
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
|
||||
resourcesMergingHelper);
|
||||
|
||||
ExporterHelper::StriptAndSerializeProjectData(clonedProject, rootElement,
|
||||
options.isInGameEdition,
|
||||
inGameEditorResources);
|
||||
}
|
||||
|
||||
void ExporterHelper::StriptAndSerializeProjectData(
|
||||
gd::Project &project, gd::SerializerElement &rootElement,
|
||||
bool isInGameEdition,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources) {
|
||||
auto projectUsedResources =
|
||||
gd::SceneResourcesFinder::FindProjectResources(project);
|
||||
|
||||
if (isInGameEdition) {
|
||||
// All used in-game editor resources must be always loaded and available.
|
||||
ExporterHelper::AddInGameEditorResources(
|
||||
project, projectUsedResources, inGameEditorResources);
|
||||
}
|
||||
|
||||
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
|
||||
for (std::size_t layoutIndex = 0;
|
||||
layoutIndex < project.GetLayoutsCount(); layoutIndex++) {
|
||||
auto &layout = project.GetLayout(layoutIndex);
|
||||
scenesUsedResources[layout.GetName()] =
|
||||
gd::SceneResourcesFinder::FindSceneResources(project, layout);
|
||||
}
|
||||
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
eventsBasedObjectVariantsUsedResources;
|
||||
for (std::size_t extensionIndex = 0;
|
||||
extensionIndex < project.GetEventsFunctionsExtensionsCount();
|
||||
extensionIndex++) {
|
||||
auto &eventsFunctionsExtension =
|
||||
project.GetEventsFunctionsExtension(extensionIndex);
|
||||
for (auto &&eventsBasedObject :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
|
||||
|
||||
auto eventsBasedObjectType = gd::PlatformExtension::GetObjectFullType(
|
||||
eventsFunctionsExtension.GetName(), eventsBasedObject->GetName());
|
||||
eventsBasedObjectVariantsUsedResources[eventsBasedObjectType] =
|
||||
gd::SceneResourcesFinder::FindEventsBasedObjectVariantResources(
|
||||
project, eventsBasedObject->GetDefaultVariant());
|
||||
|
||||
for (auto &&eventsBasedObjectVariant :
|
||||
eventsBasedObject->GetVariants().GetInternalVector()) {
|
||||
|
||||
auto variantType = gd::PlatformExtension::GetVariantFullType(
|
||||
eventsFunctionsExtension.GetName(), eventsBasedObject->GetName(),
|
||||
eventsBasedObjectVariant->GetName());
|
||||
eventsBasedObjectVariantsUsedResources[variantType] =
|
||||
gd::SceneResourcesFinder::FindEventsBasedObjectVariantResources(
|
||||
project, *eventsBasedObjectVariant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use stripped
|
||||
// things (objects groups...))
|
||||
gd::ProjectStripper::StripProjectForExport(project);
|
||||
|
||||
// Save the project to JSON
|
||||
gd::SerializerElement rootElement;
|
||||
project.SerializeTo(rootElement);
|
||||
SerializeUsedResources(
|
||||
rootElement, projectUsedResources, scenesUsedResources);
|
||||
gd::String output =
|
||||
"gdjs.projectData = " + gd::Serializer::ToJSON(rootElement) + ";\n" +
|
||||
"gdjs.runtimeGameOptions = " +
|
||||
gd::Serializer::ToJSON(runtimeGameOptions) + ";\n";
|
||||
|
||||
if (!fs.WriteToFile(filename, output)) return "Unable to write " + filename;
|
||||
|
||||
return "";
|
||||
SerializeUsedResources(rootElement, projectUsedResources, scenesUsedResources,
|
||||
eventsBasedObjectVariantsUsedResources);
|
||||
if (isInGameEdition) {
|
||||
auto &behaviorsElement = rootElement.AddChild("activatedByDefaultInEditorBehaviors");
|
||||
behaviorsElement.ConsiderAsArrayOf("resourceReference");
|
||||
auto &platform = project.GetCurrentPlatform();
|
||||
for (auto &extension : platform.GetAllPlatformExtensions()) {
|
||||
for (auto &behaviorType : extension->GetBehaviorsTypes()) {
|
||||
auto &behaviorMetadata = extension->GetBehaviorMetadata(behaviorType);
|
||||
if (behaviorMetadata.IsActivatedByDefaultInEditor()) {
|
||||
behaviorsElement.AddChild("resourceReference")
|
||||
.SetStringValue(behaviorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExporterHelper::SerializeUsedResources(
|
||||
gd::SerializerElement &rootElement,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
|
||||
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&eventsBasedObjectVariantsUsedResources) {
|
||||
auto serializeUsedResources =
|
||||
[](gd::SerializerElement &element,
|
||||
std::set<gd::String> &usedResources) -> void {
|
||||
@@ -385,6 +647,41 @@ void ExporterHelper::SerializeUsedResources(
|
||||
auto &layoutUsedResources = scenesUsedResources[layoutName];
|
||||
serializeUsedResources(layoutElement, layoutUsedResources);
|
||||
}
|
||||
|
||||
auto &extensionsElement = rootElement.GetChild("eventsFunctionsExtensions");
|
||||
for (std::size_t extensionIndex = 0;
|
||||
extensionIndex < extensionsElement.GetChildrenCount();
|
||||
extensionIndex++) {
|
||||
auto &extensionElement = extensionsElement.GetChild(extensionIndex);
|
||||
const auto extensionName = extensionElement.GetStringAttribute("name");
|
||||
|
||||
auto &objectsElement = extensionElement.GetChild("eventsBasedObjects");
|
||||
|
||||
for (std::size_t objectIndex = 0;
|
||||
objectIndex < objectsElement.GetChildrenCount(); objectIndex++) {
|
||||
auto &objectElement = objectsElement.GetChild(objectIndex);
|
||||
const auto objectName = objectElement.GetStringAttribute("name");
|
||||
|
||||
auto eventsBasedObjectType =
|
||||
gd::PlatformExtension::GetObjectFullType(extensionName, objectName);
|
||||
auto &objectUsedResources =
|
||||
eventsBasedObjectVariantsUsedResources[eventsBasedObjectType];
|
||||
serializeUsedResources(objectElement, objectUsedResources);
|
||||
|
||||
auto &variantsElement = objectElement.GetChild("variants");
|
||||
for (std::size_t variantIndex = 0;
|
||||
variantIndex < variantsElement.GetChildrenCount(); variantIndex++) {
|
||||
auto &variantElement = variantsElement.GetChild(variantIndex);
|
||||
const auto variantName = variantElement.GetStringAttribute("name");
|
||||
|
||||
auto variantType = gd::PlatformExtension::GetVariantFullType(
|
||||
extensionName, objectName, variantName);
|
||||
auto &variantUsedResources =
|
||||
eventsBasedObjectVariantsUsedResources[variantType];
|
||||
serializeUsedResources(variantElement, variantUsedResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ExporterHelper::ExportIndexFile(
|
||||
@@ -775,7 +1072,7 @@ bool ExporterHelper::CompleteIndexFile(
|
||||
gd::String codeFilesIncludes;
|
||||
for (auto &include : includesFiles) {
|
||||
gd::String scriptSrc =
|
||||
GetExportedIncludeFilename(include, nonRuntimeScriptsCacheBurst);
|
||||
GetExportedIncludeFilename(fs, gdjsRoot, include, nonRuntimeScriptsCacheBurst);
|
||||
|
||||
// Sanity check if the file exists - if not skip it to avoid
|
||||
// including it in the list of scripts.
|
||||
@@ -801,6 +1098,7 @@ bool ExporterHelper::CompleteIndexFile(
|
||||
|
||||
void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
bool pixiInThreeRenderers,
|
||||
bool isInGameEdition,
|
||||
bool includeWebsocketDebuggerClient,
|
||||
bool includeWindowMessageDebuggerClient,
|
||||
bool includeMinimalDebuggerClient,
|
||||
@@ -890,14 +1188,21 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "debugger-client/minimal-debugger-client.js");
|
||||
}
|
||||
|
||||
if (pixiInThreeRenderers) {
|
||||
if (pixiInThreeRenderers || isInGameEdition) {
|
||||
InsertUnique(includesFiles, "pixi-renderers/three.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/ThreeAddons.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/draco/gltf/draco_decoder.wasm");
|
||||
InsertUnique(includesFiles,
|
||||
"pixi-renderers/draco/gltf/draco_wasm_wrapper.js");
|
||||
// Extensions in JS may use it.
|
||||
InsertUnique(includesFiles, "Extensions/3D/Scene3DTools.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3D.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3DRenderer.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/CustomRuntimeObject3D.js");
|
||||
InsertUnique(includesFiles,
|
||||
"Extensions/3D/CustomRuntimeObject3DRenderer.js");
|
||||
}
|
||||
if (pixiRenderers) {
|
||||
if (pixiRenderers || isInGameEdition) {
|
||||
InsertUnique(includesFiles, "pixi-renderers/pixi.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/pixi-filters-tools.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/runtimegame-pixi-renderer.js");
|
||||
@@ -921,12 +1226,11 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
includesFiles,
|
||||
"fontfaceobserver-font-manager/fontfaceobserver-font-manager.js");
|
||||
}
|
||||
if (pixiInThreeRenderers) {
|
||||
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3D.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3DRenderer.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/CustomRuntimeObject3D.js");
|
||||
InsertUnique(includesFiles,
|
||||
"Extensions/3D/CustomRuntimeObject3DRenderer.js");
|
||||
if (isInGameEdition) {
|
||||
// `InGameEditor` uses the `is3D` function.
|
||||
InsertUnique(includesFiles, "Extensions/3D/Base3DBehavior.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/HemisphereLight.js");
|
||||
InsertUnique(includesFiles, "InGameEditor/InGameEditor.js");
|
||||
}
|
||||
if (includeCaptureManager) {
|
||||
InsertUnique(includesFiles, "capturemanager.js");
|
||||
@@ -959,7 +1263,7 @@ bool ExporterHelper::ExportEffectIncludes(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExporterHelper::ExportEventsCode(
|
||||
bool ExporterHelper::ExportScenesEventsCode(
|
||||
const gd::Project &project,
|
||||
gd::String outputDir,
|
||||
std::vector<gd::String> &includesFiles,
|
||||
@@ -995,6 +1299,7 @@ bool ExporterHelper::ExportEventsCode(
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::GetExportedIncludeFilename(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst) {
|
||||
auto addSearchParameterToUrl = [](const gd::String &url,
|
||||
const gd::String &urlEncodedParameterName,
|
||||
|
||||
@@ -24,6 +24,7 @@ class SourceFileMetadata;
|
||||
class WholeProjectDiagnosticReport;
|
||||
class CaptureOptions;
|
||||
class Screenshot;
|
||||
class InGameEditorResourceMetadata;
|
||||
} // namespace gd
|
||||
|
||||
namespace gdjs {
|
||||
@@ -42,9 +43,9 @@ struct PreviewExportOptions {
|
||||
useWindowMessageDebuggerClient(false),
|
||||
useMinimalDebuggerClient(false),
|
||||
nativeMobileApp(false),
|
||||
projectDataOnlyExport(false),
|
||||
fullLoadingScreen(false),
|
||||
isDevelopmentEnvironment(false),
|
||||
isInGameEdition(false),
|
||||
nonRuntimeScriptsCacheBurst(0),
|
||||
inAppTutorialMessageInPreview(""),
|
||||
inAppTutorialMessagePositionInPreview(""),
|
||||
@@ -145,6 +146,26 @@ struct PreviewExportOptions {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the (optional) events-based object to be instantiated in the scene
|
||||
* at the beginning of the previewed game.
|
||||
*/
|
||||
PreviewExportOptions &SetEventsBasedObjectType(
|
||||
const gd::String &eventsBasedObjectType_) {
|
||||
eventsBasedObjectType = eventsBasedObjectType_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the (optional) events-based object variant to be instantiated in the scene
|
||||
* at the beginning of the previewed game.
|
||||
*/
|
||||
PreviewExportOptions &SetEventsBasedObjectVariantName(
|
||||
const gd::String &eventsBasedObjectVariantName_) {
|
||||
eventsBasedObjectVariantName = eventsBasedObjectVariantName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the hash associated to an include file. Useful for the preview
|
||||
* hot-reload, to know if a file changed.
|
||||
@@ -156,11 +177,34 @@ struct PreviewExportOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export should only export the project data, not
|
||||
* exporting events code.
|
||||
* \brief Set if the exported folder should be cleared befor the export.
|
||||
*/
|
||||
PreviewExportOptions &SetProjectDataOnlyExport(bool enable) {
|
||||
projectDataOnlyExport = enable;
|
||||
PreviewExportOptions &SetShouldClearExportFolder(bool enable) {
|
||||
shouldClearExportFolder = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the `ProjectData` must be reloaded.
|
||||
*/
|
||||
PreviewExportOptions &SetShouldReloadProjectData(bool enable) {
|
||||
shouldReloadProjectData = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if GDJS libraries must be reloaded.
|
||||
*/
|
||||
PreviewExportOptions &SetShouldReloadLibraries(bool enable) {
|
||||
shouldReloadLibraries = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export should export events code.
|
||||
*/
|
||||
PreviewExportOptions &SetShouldGenerateScenesEventsCode(bool enable) {
|
||||
shouldGenerateScenesEventsCode = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -182,6 +226,48 @@ struct PreviewExportOptions {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export is made for being edited in the editor.
|
||||
*/
|
||||
PreviewExportOptions &SetIsInGameEdition(bool enable) {
|
||||
isInGameEdition = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the JSON string representation of the in-game editor settings.
|
||||
*/
|
||||
PreviewExportOptions &SetInGameEditorSettingsJson(const gd::String &inGameEditorSettingsJson_) {
|
||||
inGameEditorSettingsJson = inGameEditorSettingsJson_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the in-game editor identifier.
|
||||
*/
|
||||
PreviewExportOptions &SetEditorId(const gd::String &editorId_) {
|
||||
editorId = editorId_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the camera state to use in the in-game editor.
|
||||
*/
|
||||
PreviewExportOptions &
|
||||
SetEditorCameraState3D(const gd::String &cameraMode, double positionX,
|
||||
double positionY, double positionZ,
|
||||
double rotationAngle, double elevationAngle,
|
||||
double distance) {
|
||||
editorCamera3DCameraMode = cameraMode;
|
||||
editorCamera3DPositionX = positionX;
|
||||
editorCamera3DPositionY = positionY;
|
||||
editorCamera3DPositionZ = positionZ;
|
||||
editorCamera3DRotationAngle = rotationAngle;
|
||||
editorCamera3DElevationAngle = elevationAngle;
|
||||
editorCamera3DDistance = distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief If set to a non zero value, the exported script URLs will have an
|
||||
* extra search parameter added (with the given value) to ensure browser cache
|
||||
@@ -294,6 +380,8 @@ struct PreviewExportOptions {
|
||||
bool useMinimalDebuggerClient;
|
||||
gd::String layoutName;
|
||||
gd::String externalLayoutName;
|
||||
gd::String eventsBasedObjectType;
|
||||
gd::String eventsBasedObjectVariantName;
|
||||
gd::String fallbackAuthorUsername;
|
||||
gd::String fallbackAuthorId;
|
||||
gd::String playerId;
|
||||
@@ -303,9 +391,22 @@ struct PreviewExportOptions {
|
||||
gd::String inAppTutorialMessagePositionInPreview;
|
||||
bool nativeMobileApp;
|
||||
std::map<gd::String, int> includeFileHashes;
|
||||
bool projectDataOnlyExport;
|
||||
bool shouldClearExportFolder = true;
|
||||
bool shouldReloadProjectData = true;
|
||||
bool shouldReloadLibraries = true;
|
||||
bool shouldGenerateScenesEventsCode = true;
|
||||
bool fullLoadingScreen;
|
||||
bool isDevelopmentEnvironment;
|
||||
bool isInGameEdition;
|
||||
gd::String editorId;
|
||||
gd::String editorCamera3DCameraMode;
|
||||
gd::String inGameEditorSettingsJson;
|
||||
double editorCamera3DPositionX = 0;
|
||||
double editorCamera3DPositionY = 0;
|
||||
double editorCamera3DPositionZ = 0;
|
||||
double editorCamera3DRotationAngle = 0;
|
||||
double editorCamera3DElevationAngle = 0;
|
||||
double editorCamera3DDistance = 0;
|
||||
unsigned int nonRuntimeScriptsCacheBurst;
|
||||
gd::String electronRemoteRequirePath;
|
||||
gd::String gdevelopResourceToken;
|
||||
@@ -379,23 +480,54 @@ class ExporterHelper {
|
||||
const gd::String &GetLastError() const { return lastError; };
|
||||
|
||||
/**
|
||||
* \brief Export a project to JSON
|
||||
* \brief Export a project without its events and options to 2 JS variables
|
||||
*
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param project The project to be exported.
|
||||
* \param filename The filename where export the project
|
||||
* \param runtimeGameOptions The content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions \return Empty string if everything is ok,
|
||||
* in gdjs.runtimeGameOptions
|
||||
*
|
||||
* \return Empty string if everything is ok,
|
||||
* description of the error otherwise.
|
||||
*/
|
||||
static gd::String ExportProjectData(
|
||||
gd::AbstractFileSystem &fs,
|
||||
gd::Project &project,
|
||||
gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&layersUsedResources);
|
||||
gd::AbstractFileSystem &fs, gd::Project &project, gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions, bool isInGameEdition,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources);
|
||||
|
||||
/**
|
||||
* \brief Serialize a project without its events to JSON
|
||||
*
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param project The project to be exported.
|
||||
* \param options The content of the extra configuration
|
||||
* \param projectDataElement The element where the project data is serialized
|
||||
* \param inGameEditorResources The list of in-game editor resources to be used.
|
||||
*/
|
||||
static void SerializeProjectData(gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &projectDataElement,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources);
|
||||
|
||||
/**
|
||||
* \brief Serialize the content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions to JSON
|
||||
*
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
|
||||
* \param options The content of the extra configuration
|
||||
* \param includesFiles The list of scripts files - useful for hot-reloading
|
||||
* \param runtimeGameOptionsElement The element where the game options are
|
||||
* serialized
|
||||
*/
|
||||
static void
|
||||
SerializeRuntimeGameOptions(gd::AbstractFileSystem &fs,
|
||||
const gd::String &gdjsRoot,
|
||||
const PreviewExportOptions &options,
|
||||
std::vector<gd::String> &includesFiles,
|
||||
gd::SerializerElement &runtimeGameOptionsElement);
|
||||
|
||||
/**
|
||||
* \brief Copy all the resources of the project to to the export directory,
|
||||
@@ -416,6 +548,7 @@ class ExporterHelper {
|
||||
*/
|
||||
void AddLibsInclude(bool pixiRenderers,
|
||||
bool pixiInThreeRenderers,
|
||||
bool isInGameEdition,
|
||||
bool includeWebsocketDebuggerClient,
|
||||
bool includeWindowMessageDebuggerClient,
|
||||
bool includeMinimalDebuggerClient,
|
||||
@@ -453,7 +586,7 @@ class ExporterHelper {
|
||||
* includesFiles A reference to a vector that will be filled with JS files to
|
||||
* be exported along with the project. ( including "codeX.js" files ).
|
||||
*/
|
||||
bool ExportEventsCode(
|
||||
bool ExportScenesEventsCode(
|
||||
const gd::Project &project,
|
||||
gd::String outputDir,
|
||||
std::vector<gd::String> &includesFiles,
|
||||
@@ -578,14 +711,20 @@ class ExporterHelper {
|
||||
* a browser pointing to the preview.
|
||||
*
|
||||
* \param options The options to generate the preview.
|
||||
* \param includesFiles The list of scripts files - useful for hot-reloading
|
||||
*/
|
||||
bool ExportProjectForPixiPreview(const PreviewExportOptions &options);
|
||||
bool ExportProjectForPixiPreview(const PreviewExportOptions &options,
|
||||
std::vector<gd::String> &includesFiles);
|
||||
|
||||
/**
|
||||
* \brief Given an include file, returns the name of the file to reference
|
||||
* in the exported game.
|
||||
*
|
||||
* \param fs The abstract file system to use
|
||||
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
|
||||
*/
|
||||
gd::String GetExportedIncludeFilename(
|
||||
static gd::String GetExportedIncludeFilename(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst = 0);
|
||||
|
||||
/**
|
||||
@@ -612,11 +751,30 @@ class ExporterHelper {
|
||||
///< be then copied to the final output directory.
|
||||
|
||||
private:
|
||||
static void SerializeUsedResources(
|
||||
gd::SerializerElement &rootElement,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&layersUsedResources);
|
||||
static void
|
||||
SerializeUsedResources(gd::SerializerElement &rootElement,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&layersUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&eventsBasedObjectVariantsUsedResources);
|
||||
|
||||
/**
|
||||
* \brief Strip a project and serialize it to JSON.
|
||||
*/
|
||||
static void StriptAndSerializeProjectData(gd::Project &project,
|
||||
gd::SerializerElement &rootElement,
|
||||
bool isInGameEdition,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources);
|
||||
|
||||
/**
|
||||
* \brief Add additional resources that are used by the in-game editor to the
|
||||
* project.
|
||||
*/
|
||||
static void
|
||||
AddInGameEditorResources(gd::Project &project,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
const std::vector<gd::InGameEditorResourceMetadata> &inGameEditorResources);
|
||||
};
|
||||
|
||||
} // namespace gdjs
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace gdjs {
|
||||
export type CustomObjectConfiguration = ObjectConfiguration & {
|
||||
animatable?: SpriteAnimationData[];
|
||||
variant: string;
|
||||
childrenContent: { [objectName: string]: ObjectConfiguration & any };
|
||||
childrenContent?: { [objectName: string]: ObjectConfiguration & any };
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
@@ -118,37 +118,19 @@ namespace gdjs {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventsBasedObjectData.defaultVariant) {
|
||||
eventsBasedObjectData.defaultVariant = {
|
||||
...eventsBasedObjectData,
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
eventsBasedObjectData.defaultVariant.instances.length == 0;
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
if (
|
||||
customObjectData.variant &&
|
||||
!isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
) {
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
variantIndex < eventsBasedObjectData.variants.length;
|
||||
variantIndex++
|
||||
) {
|
||||
const variantData = eventsBasedObjectData.variants[variantIndex];
|
||||
if (variantData.name === customObjectData.variant) {
|
||||
usedVariantData = variantData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const usedVariantData: EventsBasedObjectVariantData | null =
|
||||
this.getRuntimeScene()
|
||||
.getGame()
|
||||
.getEventsBasedObjectVariantData(
|
||||
customObjectData.type,
|
||||
customObjectData.variant
|
||||
);
|
||||
if (!usedVariantData) {
|
||||
// This can't actually happen.
|
||||
logger.error(
|
||||
`Unknown variant "${customObjectData.variant}" for object "${customObjectData.type}".`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._isInnerAreaFollowingParentSize =
|
||||
@@ -178,8 +160,7 @@ namespace gdjs {
|
||||
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
|
||||
super.reinitialize(objectData);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(objectData);
|
||||
this._reinitializeContentFromObjectData(objectData);
|
||||
|
||||
// When changing the variant, the instance is like a new instance.
|
||||
// We call again `onCreated` at the end, like done by the constructor
|
||||
@@ -187,6 +168,14 @@ namespace gdjs {
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
private _reinitializeContentFromObjectData(
|
||||
objectData: ObjectData & CustomObjectConfiguration
|
||||
) {
|
||||
this._reinitializeRenderer();
|
||||
this._instanceContainer._unloadContent();
|
||||
this._initializeFromObjectData(objectData);
|
||||
}
|
||||
|
||||
override updateFromObjectData(
|
||||
oldObjectData: ObjectData & CustomObjectConfiguration,
|
||||
newObjectData: ObjectData & CustomObjectConfiguration
|
||||
@@ -214,8 +203,7 @@ namespace gdjs {
|
||||
this._instanceContainer._initialInnerArea.max[1] !==
|
||||
this._innerArea.max[1]);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(newObjectData);
|
||||
this._reinitializeContentFromObjectData(newObjectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
this.onCreated();
|
||||
@@ -311,15 +299,13 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
override onDeletedFromScene(): void {
|
||||
@@ -658,6 +644,20 @@ namespace gdjs {
|
||||
return this._unrotatedAABB.max[1];
|
||||
}
|
||||
|
||||
getOriginalWidth(): float {
|
||||
return (
|
||||
this._instanceContainer.getInitialUnrotatedViewportMaxX() -
|
||||
this._instanceContainer.getInitialUnrotatedViewportMinX()
|
||||
);
|
||||
}
|
||||
|
||||
getOriginalHeight(): float {
|
||||
return (
|
||||
this._instanceContainer.getInitialUnrotatedViewportMaxY() -
|
||||
this._instanceContainer.getInitialUnrotatedViewportMinY()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the internal width of the object according to its children.
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace gdjs {
|
||||
_parent: gdjs.RuntimeInstanceContainer;
|
||||
/** The object that is built with the instances of this container. */
|
||||
_customObject: gdjs.CustomRuntimeObject;
|
||||
// TODO Remove this attribute
|
||||
_isLoaded: boolean = false;
|
||||
/**
|
||||
* The default size defined by users in the custom object initial instances editor.
|
||||
@@ -39,22 +40,35 @@ namespace gdjs {
|
||||
parent: gdjs.RuntimeInstanceContainer,
|
||||
customObject: gdjs.CustomRuntimeObject
|
||||
) {
|
||||
super();
|
||||
super(parent.getGame());
|
||||
this._parent = parent;
|
||||
this._customObject = customObject;
|
||||
this._runtimeScene = parent.getScene();
|
||||
this._debuggerRenderer = new gdjs.DebuggerRenderer(this);
|
||||
}
|
||||
|
||||
// TODO `_layers` and `_orderedLayers` should not be used directly.
|
||||
|
||||
addLayer(layerData: LayerData) {
|
||||
if (this._layers.containsKey(layerData.name)) {
|
||||
return;
|
||||
}
|
||||
// This code is duplicated with `RuntimeScene.addLayer` because it avoids
|
||||
// to expose a method to build a layer.
|
||||
const layer = new gdjs.RuntimeCustomObjectLayer(layerData, this);
|
||||
this._layers.put(layerData.name, layer);
|
||||
this._orderedLayers.push(layer);
|
||||
}
|
||||
|
||||
_unloadContent() {
|
||||
this.onDeletedFromScene(this._parent);
|
||||
// At this point, layer renderers are already removed by
|
||||
// `CustomRuntimeObject._reinitializeRenderer`.
|
||||
// It's not great to do this here, but it allows to keep it private.
|
||||
this._layers.clear();
|
||||
this._orderedLayers.length = 0;
|
||||
}
|
||||
|
||||
createObject(objectName: string): gdjs.RuntimeObject | null {
|
||||
const result = super.createObject(objectName);
|
||||
this._customObject.onChildrenLocationChanged();
|
||||
@@ -63,21 +77,14 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Load the container from the given initial configuration.
|
||||
* @param customObjectData An object containing the container data.
|
||||
* @param customObjectData An object containing the parent object data.
|
||||
* @param eventsBasedObjectVariantData An object containing the container data.
|
||||
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
|
||||
*/
|
||||
loadFrom(
|
||||
customObjectData: ObjectData & CustomObjectConfiguration,
|
||||
eventsBasedObjectVariantData: EventsBasedObjectVariantData
|
||||
) {
|
||||
if (this._isLoaded) {
|
||||
this.onDeletedFromScene(this._parent);
|
||||
}
|
||||
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
this._setOriginalInnerArea(eventsBasedObjectVariantData);
|
||||
|
||||
// Registering objects
|
||||
@@ -87,19 +94,21 @@ namespace gdjs {
|
||||
++i
|
||||
) {
|
||||
const childObjectData = eventsBasedObjectVariantData.objects[i];
|
||||
// The children configuration override only applies to the default variant.
|
||||
if (
|
||||
customObjectData.childrenContent &&
|
||||
(!eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
|
||||
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
|
||||
customObjectData,
|
||||
eventsBasedObjectVariantData
|
||||
)
|
||||
) {
|
||||
this.registerObject({
|
||||
...childObjectData,
|
||||
// The custom object overrides its events-based object configuration.
|
||||
// The custom object overrides its variant configuration with
|
||||
// a legacy children configuration.
|
||||
...customObjectData.childrenContent[childObjectData.name],
|
||||
});
|
||||
} else {
|
||||
// The custom object follows its events-based object configuration.
|
||||
// The custom object follows its variant configuration.
|
||||
this.registerObject(childObjectData);
|
||||
}
|
||||
}
|
||||
@@ -154,6 +163,28 @@ namespace gdjs {
|
||||
this._isLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the custom object has a children configuration overriding that
|
||||
* should be used instead of the variant's objects configurations.
|
||||
* @param customObjectData An object containing the parent object data.
|
||||
* @param eventsBasedObjectVariantData An object containing the container data.
|
||||
* @returns
|
||||
*/
|
||||
static hasChildrenConfigurationOverriding(
|
||||
customObjectData: CustomObjectConfiguration,
|
||||
eventsBasedObjectVariantData: EventsBasedObjectVariantData
|
||||
): boolean {
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
// The children configuration override only applies to the default variant.
|
||||
return customObjectData.childrenContent
|
||||
? !eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize `_initialInnerArea` if it doesn't exist.
|
||||
* `_initialInnerArea` is shared by every instance to save memory.
|
||||
@@ -161,7 +192,10 @@ namespace gdjs {
|
||||
private _setOriginalInnerArea(
|
||||
eventsBasedObjectData: EventsBasedObjectVariantData
|
||||
) {
|
||||
if (eventsBasedObjectData.instances.length > 0) {
|
||||
if (
|
||||
eventsBasedObjectData.instances.length > 0 ||
|
||||
this.getGame().isInGameEdition()
|
||||
) {
|
||||
if (!eventsBasedObjectData._initialInnerArea) {
|
||||
eventsBasedObjectData._initialInnerArea = {
|
||||
min: [
|
||||
@@ -341,6 +375,12 @@ namespace gdjs {
|
||||
return this._initialInnerArea ? this._initialInnerArea.max[1] : 0;
|
||||
}
|
||||
|
||||
_getInitialInnerAreaDepth(): float {
|
||||
return this._initialInnerArea
|
||||
? this._initialInnerArea.max[2] - this._initialInnerArea.min[2]
|
||||
: 0;
|
||||
}
|
||||
|
||||
getViewportWidth(): float {
|
||||
return this._customObject.getUnscaledWidth();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.4502 17C12.8643 17.0001 13.2002 17.3359 13.2002 17.75V21.25C13.2002 21.6641 12.8643 21.9999 12.4502 22C12.036 22 11.7002 21.6642 11.7002 21.25V17.75C11.7002 17.3358 12.036 17 12.4502 17Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2998 8.75C14.0947 8.75 15.5498 10.2051 15.5498 12C15.5498 13.7949 14.0947 15.25 12.2998 15.25C10.505 15.2499 9.0498 13.7948 9.0498 12C9.0498 10.2052 10.505 8.75013 12.2998 8.75ZM12.2998 10.25C11.3334 10.2501 10.5498 11.0336 10.5498 12C10.5498 12.9664 11.3334 13.7499 12.2998 13.75C13.2663 13.75 14.0498 12.9665 14.0498 12C14.0498 11.0335 13.2663 10.25 12.2998 10.25Z" fill="white"/>
|
||||
<path d="M6.25 11C6.66421 11 7 11.3358 7 11.75C7 12.1642 6.66421 12.5 6.25 12.5H2.75C2.33579 12.5 2 12.1642 2 11.75C2 11.3358 2.33579 11 2.75 11H6.25Z" fill="white"/>
|
||||
<path d="M21.25 11C21.6642 11 22 11.3358 22 11.75C22 12.1642 21.6642 12.5 21.25 12.5H17.75C17.3358 12.5 17 12.1642 17 11.75C17 11.3358 17.3358 11 17.75 11H21.25Z" fill="white"/>
|
||||
<path d="M12.4502 2C12.8643 2.00013 13.2002 2.33587 13.2002 2.75V6.25C13.2002 6.66413 12.8643 6.99987 12.4502 7C12.036 7 11.7002 6.66421 11.7002 6.25V2.75C11.7002 2.33579 12.036 2 12.4502 2Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2660_307)">
|
||||
<path d="M13.4696 20.4698C13.7625 20.177 14.2373 20.177 14.5302 20.4698C14.823 20.7627 14.823 21.2374 14.5302 21.5303L12.5302 23.5303C12.2555 23.8049 11.8208 23.8224 11.5262 23.5821L11.4696 23.5303L9.46961 21.5303C9.17681 21.2374 9.17674 20.7627 9.46961 20.4698C9.76247 20.177 10.2373 20.177 10.5302 20.4698L11.9999 21.9395L13.4696 20.4698Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.9999 7.75007C13.8962 7.75007 14.6337 8.42427 14.7362 9.29304L15.9413 8.69147L16.0985 8.62507C16.8905 8.35275 17.7495 8.93791 17.7499 9.80866V14.5655C17.7499 15.5639 16.6372 16.1593 15.8065 15.6055L14.7089 14.8741C14.5375 15.6608 13.838 16.2501 12.9999 16.2501H7.99988C7.03344 16.25 6.24988 15.4665 6.24988 14.5001V9.50007C6.24995 8.53367 7.03348 7.75013 7.99988 7.75007H12.9999ZM7.99988 9.25007C7.86191 9.25013 7.74995 9.36209 7.74988 9.50007V14.5001C7.74988 14.6381 7.86186 14.75 7.99988 14.7501H12.9999C13.138 14.7501 13.2499 14.6381 13.2499 14.5001V9.50007C13.2498 9.36205 13.1379 9.25007 12.9999 9.25007H7.99988ZM14.7499 10.963V13.0977L16.2499 14.0977V10.213L14.7499 10.963Z" fill="white"/>
|
||||
<path d="M2.46961 9.46979C2.76245 9.17698 3.23726 9.17706 3.53015 9.46979C3.82304 9.76268 3.82302 10.2374 3.53015 10.5303L2.06043 12.0001L3.53015 13.4698C3.82304 13.7627 3.82302 14.2374 3.53015 14.5303C3.23726 14.8232 2.7625 14.8232 2.46961 14.5303L0.469606 12.5303L0.417848 12.4737C0.177679 12.1791 0.195053 11.7443 0.469606 11.4698L2.46961 9.46979Z" fill="white"/>
|
||||
<path d="M20.4696 9.46979C20.7625 9.17698 21.2373 9.17706 21.5302 9.46979L23.5302 11.4698C23.8047 11.7444 23.8222 12.1791 23.5819 12.4737L23.5302 12.5303L21.5302 14.5303C21.2373 14.8232 20.7625 14.8232 20.4696 14.5303C20.1769 14.2374 20.1768 13.7626 20.4696 13.4698L21.9393 12.0001L20.4696 10.5303C20.1769 10.2374 20.1768 9.76264 20.4696 9.46979Z" fill="white"/>
|
||||
<path d="M11.5262 0.418037C11.8208 0.17777 12.2555 0.195283 12.5302 0.469794L14.5302 2.46979C14.823 2.76268 14.823 3.23745 14.5302 3.53034C14.2373 3.82323 13.7625 3.82323 13.4696 3.53034L11.9999 2.06061L10.5302 3.53034C10.2373 3.82323 9.7625 3.82323 9.46961 3.53034C9.1768 3.23744 9.17674 2.76266 9.46961 2.46979L11.4696 0.469794L11.5262 0.418037Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2660_307">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.4696 18.4698C14.7624 18.177 15.2372 18.1771 15.5302 18.4698C15.823 18.7627 15.823 19.2374 15.5302 19.5303L12.5302 22.5303L12.4735 22.5821C12.1789 22.8224 11.7442 22.8049 11.4696 22.5303L8.46961 19.5303C8.17687 19.2374 8.17679 18.7626 8.46961 18.4698C8.76245 18.177 9.23725 18.1771 9.53015 18.4698L11.9999 20.9395L14.4696 18.4698Z" fill="white"/>
|
||||
<path d="M4.46961 8.46979C4.76245 8.17698 5.23726 8.17706 5.53015 8.46979C5.82304 8.76268 5.82301 9.23745 5.53015 9.53034L3.06043 12.0001L5.53015 14.4698C5.82304 14.7627 5.82301 15.2374 5.53015 15.5303C5.23726 15.8232 4.7625 15.8232 4.46961 15.5303L1.46961 12.5303L1.41785 12.4737C1.17768 12.1791 1.19505 11.7443 1.46961 11.4698L4.46961 8.46979Z" fill="white"/>
|
||||
<path d="M18.4696 8.46979C18.7625 8.17693 19.2373 8.177 19.5302 8.46979L22.5302 11.4698L22.5819 11.5264C22.8222 11.821 22.8048 12.2557 22.5302 12.5303L19.5302 15.5303C19.2373 15.8232 18.7625 15.8232 18.4696 15.5303C18.1768 15.2375 18.1768 14.7627 18.4696 14.4698L20.9393 12.0001L18.4696 9.53034C18.1768 9.23745 18.1768 8.76266 18.4696 8.46979Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2499 9.25007C13.9067 9.25007 15.2498 10.5933 15.2499 12.2501C15.2499 13.9069 13.9067 15.2501 12.2499 15.2501C10.5931 15.25 9.24988 13.9069 9.24988 12.2501C9.24994 10.5933 10.5931 9.25013 12.2499 9.25007ZM12.2499 10.7501C11.4215 10.7501 10.7499 11.4217 10.7499 12.2501C10.7499 13.0785 11.4215 13.75 12.2499 13.7501C13.0783 13.7501 13.7499 13.0785 13.7499 12.2501C13.7498 11.4217 13.0783 10.7501 12.2499 10.7501Z" fill="white"/>
|
||||
<path d="M11.5262 1.41804C11.8208 1.17777 12.2555 1.19528 12.5302 1.46979L15.5302 4.46979C15.823 4.76268 15.823 5.23745 15.5302 5.53034C15.2373 5.82323 14.7625 5.82323 14.4696 5.53034L11.9999 3.06061L9.53015 5.53034C9.23726 5.82323 8.7625 5.82323 8.46961 5.53034C8.1768 5.23744 8.17674 4.76266 8.46961 4.46979L11.4696 1.46979L11.5262 1.41804Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2660_308)">
|
||||
<path d="M12.8945 22.2119C13.3072 22.1763 13.6714 22.4819 13.707 22.8945C13.7425 23.3069 13.4368 23.6702 13.0244 23.7061C12.6868 23.7352 12.3448 23.75 12 23.75C11.6552 23.75 11.3132 23.7352 10.9756 23.7061C10.5632 23.6702 10.2575 23.3069 10.293 22.8945C10.3286 22.4819 10.6928 22.1763 11.1055 22.2119C11.4001 22.2374 11.6984 22.25 12 22.25C12.3016 22.25 12.5999 22.2374 12.8945 22.2119Z" fill="white"/>
|
||||
<path d="M5.07617 20.5811C5.31405 20.242 5.78199 20.1596 6.12109 20.3975C6.60729 20.7385 7.12423 21.0384 7.66699 21.292C8.04228 21.4673 8.2046 21.9138 8.0293 22.2891C7.85399 22.6643 7.40751 22.8267 7.03223 22.6514C6.40964 22.3605 5.81688 22.0168 5.25977 21.626C4.92073 21.3881 4.83847 20.9201 5.07617 20.5811Z" fill="white"/>
|
||||
<path d="M17.8789 20.3975C18.218 20.1596 18.686 20.242 18.9238 20.5811C19.1615 20.9201 19.0793 21.3881 18.7402 21.626C18.1831 22.0168 17.5904 22.3605 16.9678 22.6514C16.5925 22.8267 16.146 22.6643 15.9707 22.2891C15.7954 21.9138 15.9577 21.4673 16.333 21.292C16.8758 21.0384 17.3927 20.7385 17.8789 20.3975Z" fill="white"/>
|
||||
<path d="M1.71094 15.9707C2.08623 15.7954 2.53271 15.9577 2.70801 16.333C2.96157 16.8758 3.26149 17.3927 3.60254 17.8789C3.84041 18.218 3.75804 18.686 3.41895 18.9238C3.07986 19.1615 2.61185 19.0793 2.37402 18.7402C1.98325 18.1831 1.63947 17.5904 1.34863 16.9678C1.17334 16.5925 1.33567 16.146 1.71094 15.9707Z" fill="white"/>
|
||||
<path d="M21.292 16.333C21.4673 15.9577 21.9138 15.7954 22.2891 15.9707C22.6643 16.146 22.8267 16.5925 22.6514 16.9678C22.3605 17.5904 22.0168 18.1831 21.626 18.7402C21.3881 19.0793 20.9201 19.1615 20.5811 18.9238C20.242 18.686 20.1596 18.218 20.3975 17.8789C20.7385 17.3927 21.0384 16.8758 21.292 16.333Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 7.7002C13.8965 7.7002 14.6339 8.37421 14.7363 9.24316L15.9414 8.6416L16.0986 8.5752C16.8907 8.30286 17.7497 8.88798 17.75 9.75879V14.5156C17.7498 15.5138 16.6372 16.1092 15.8066 15.5557L14.709 14.8232C14.5379 15.6103 13.8383 16.2002 13 16.2002H8C7.03358 16.2002 6.25013 15.4166 6.25 14.4502V9.4502C6.25 8.4837 7.0335 7.7002 8 7.7002H13ZM8 9.2002C7.86193 9.2002 7.75 9.31212 7.75 9.4502V14.4502C7.75013 14.5882 7.86201 14.7002 8 14.7002H13C13.138 14.7002 13.2499 14.5882 13.25 14.4502V9.4502C13.25 9.32952 13.1645 9.22854 13.0508 9.20508L13 9.2002H8ZM14.75 10.9131V13.0479L16.25 14.0479V10.1631L14.75 10.9131Z" fill="white"/>
|
||||
<path d="M0.293945 10.9756C0.329776 10.5632 0.693076 10.2575 1.10547 10.293C1.51814 10.3286 1.82374 10.6928 1.78809 11.1055C1.76264 11.4001 1.75 11.6984 1.75 12C1.75 12.3016 1.76264 12.5999 1.78809 12.8945C1.82374 13.3072 1.51814 13.6714 1.10547 13.707C0.693075 13.7425 0.329775 13.4368 0.293945 13.0244C0.264771 12.6868 0.25 12.3448 0.25 12C0.25 11.6552 0.264771 11.3132 0.293945 10.9756Z" fill="white"/>
|
||||
<path d="M22.8945 10.293C23.3069 10.2575 23.6702 10.5632 23.7061 10.9756C23.7352 11.3132 23.75 11.6552 23.75 12C23.75 12.3448 23.7352 12.6868 23.7061 13.0244C23.6702 13.4368 23.3069 13.7425 22.8945 13.707C22.4819 13.6714 22.1763 13.3072 22.2119 12.8945C22.2374 12.5999 22.25 12.3016 22.25 12C22.25 11.6984 22.2374 11.4001 22.2119 11.1055C22.1763 10.6928 22.4819 10.3286 22.8945 10.293Z" fill="white"/>
|
||||
<path d="M2.37402 5.25977C2.61185 4.92073 3.07986 4.83847 3.41895 5.07617C3.75804 5.31405 3.84041 5.78199 3.60254 6.12109C3.26149 6.60729 2.96157 7.12423 2.70801 7.66699C2.53271 8.04228 2.08623 8.2046 1.71094 8.0293C1.33567 7.85399 1.17334 7.40751 1.34863 7.03223C1.63947 6.40964 1.98325 5.81688 2.37402 5.25977Z" fill="white"/>
|
||||
<path d="M20.5811 5.07617C20.9201 4.83847 21.3881 4.92073 21.626 5.25977C22.0168 5.81688 22.3605 6.40964 22.6514 7.03223C22.8267 7.40751 22.6643 7.85399 22.2891 8.0293C21.9138 8.2046 21.4673 8.04228 21.292 7.66699C21.0384 7.12423 20.7385 6.60729 20.3975 6.12109C20.1596 5.78199 20.242 5.31405 20.5811 5.07617Z" fill="white"/>
|
||||
<path d="M7.03223 1.34863C7.40751 1.17334 7.85399 1.33567 8.0293 1.71094C8.2046 2.08623 8.04228 2.53271 7.66699 2.70801C7.12423 2.96157 6.60729 3.26149 6.12109 3.60254C5.78199 3.84041 5.31405 3.75804 5.07617 3.41895C4.83847 3.07986 4.92073 2.61185 5.25977 2.37402C5.81688 1.98325 6.40964 1.63947 7.03223 1.34863Z" fill="white"/>
|
||||
<path d="M15.9707 1.71094C16.146 1.33567 16.5925 1.17334 16.9678 1.34863C17.5904 1.63947 18.1831 1.98325 18.7402 2.37402C19.0793 2.61185 19.1615 3.07986 18.9238 3.41895C18.686 3.75804 18.218 3.84041 17.8789 3.60254C17.3927 3.26149 16.8758 2.96157 16.333 2.70801C15.9577 2.53271 15.7954 2.08623 15.9707 1.71094Z" fill="white"/>
|
||||
<path d="M12 0.25C12.3448 0.25 12.6868 0.264771 13.0244 0.293945C13.4368 0.329776 13.7425 0.693076 13.707 1.10547C13.6714 1.51814 13.3072 1.82374 12.8945 1.78809C12.5999 1.76264 12.3016 1.75 12 1.75C11.6984 1.75 11.4001 1.76264 11.1055 1.78809C10.6928 1.82374 10.3286 1.51814 10.293 1.10547C10.2575 0.693075 10.5632 0.329775 10.9756 0.293945C11.3132 0.264771 11.6552 0.25 12 0.25Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2660_308">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.46973 15.4697C7.76262 15.1768 8.23738 15.1768 8.53027 15.4697C8.82317 15.7626 8.82317 16.2374 8.53027 16.5303L4.81055 20.25H8C8.41421 20.25 8.75 20.5858 8.75 21C8.75 21.4142 8.41421 21.75 8 21.75H3C2.58579 21.75 2.25 21.4142 2.25 21V16C2.25 15.5858 2.58579 15.25 3 15.25C3.41421 15.25 3.75 15.5858 3.75 16V19.1895L7.46973 15.4697Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 9.25C14.2949 9.25 15.75 10.7051 15.75 12.5C15.75 14.2949 14.2949 15.75 12.5 15.75C10.7051 15.75 9.25 14.2949 9.25 12.5C9.25 10.7051 10.7051 9.25 12.5 9.25ZM12.5 10.75C11.5335 10.75 10.75 11.5335 10.75 12.5C10.75 13.4665 11.5335 14.25 12.5 14.25C13.4665 14.25 14.25 13.4665 14.25 12.5C14.25 11.5335 13.4665 10.75 12.5 10.75Z" fill="white"/>
|
||||
<path d="M21 2.25C21.4142 2.25 21.75 2.58579 21.75 3V8C21.75 8.41421 21.4142 8.75 21 8.75C20.5858 8.75 20.25 8.41421 20.25 8V4.81055L16.5303 8.53027C16.2374 8.82317 15.7626 8.82317 15.4697 8.53027C15.1768 8.23738 15.1768 7.76262 15.4697 7.46973L19.1895 3.75H16C15.5858 3.75 15.25 3.41421 15.25 3C15.25 2.58579 15.5858 2.25 16 2.25H21Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.37598 1.58403C9.60576 1.23944 10.0714 1.14627 10.416 1.37602L13.416 3.37602C13.6029 3.50059 13.7238 3.70239 13.7461 3.92583C13.7682 4.1494 13.6891 4.37144 13.5303 4.53032L10.5303 7.53032C10.2374 7.82321 9.76262 7.82321 9.46973 7.53032C9.17684 7.23742 9.17684 6.76266 9.46973 6.46977L11.1436 4.79399C6.98969 5.22242 3.75006 8.73295 3.75 13C3.75 17.5564 7.44365 21.25 12 21.25C16.5564 21.25 20.25 17.5564 20.25 13C20.2501 12.5859 20.5858 12.25 21 12.25C21.4142 12.25 21.7499 12.5859 21.75 13C21.75 18.3849 17.3848 22.75 12 22.75C6.61523 22.75 2.25 18.3849 2.25 13C2.25006 8.06895 5.91102 3.99458 10.6631 3.34282L9.58398 2.62407C9.23934 2.3943 9.14621 1.92868 9.37598 1.58403Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10.25C13.7949 10.25 15.2499 11.7052 15.25 13.5C15.25 15.295 13.7949 16.75 12 16.75C10.2051 16.75 8.75 15.295 8.75 13.5C8.75007 11.7052 10.2051 10.25 12 10.25ZM12 11.75C11.0335 11.75 10.2501 12.5336 10.25 13.5C10.25 14.4665 11.0335 15.25 12 15.25C12.9665 15.25 13.75 14.4665 13.75 13.5C13.7499 12.5336 12.9665 11.75 12 11.75Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -164,14 +164,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const loadedThreeModel = this._loadedThreeModels.get(resourceData);
|
||||
const loadedThreeModel = this._loadedThreeModels.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (loadedThreeModel) {
|
||||
loadedThreeModel.scene.clear();
|
||||
this._loadedThreeModels.delete(resourceData);
|
||||
}
|
||||
|
||||
const downloadedArrayBuffer =
|
||||
this._downloadedArrayBuffers.get(resourceData);
|
||||
const downloadedArrayBuffer = this._downloadedArrayBuffers.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (downloadedArrayBuffer) {
|
||||
this._downloadedArrayBuffers.delete(resourceData);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,28 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
class InternalInGameEditorOnlySvgManager implements gdjs.ResourceManager {
|
||||
async loadResource(resourceName: string): Promise<void> {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
async processResource(resourceName: string): Promise<void> {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
getResourceKinds(): Array<ResourceKind> {
|
||||
return ['internal-in-game-editor-only-svg'];
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-load resources of any kind needed for a game or a scene.
|
||||
*/
|
||||
@@ -124,6 +146,7 @@ namespace gdjs {
|
||||
private _bitmapFontManager: BitmapFontManager;
|
||||
private _spineAtlasManager: SpineAtlasManager | null = null;
|
||||
private _spineManager: SpineManager | null = null;
|
||||
private _svgManager: InternalInGameEditorOnlySvgManager;
|
||||
|
||||
/**
|
||||
* The name of the scene for which resources are currently being loaded.
|
||||
@@ -169,6 +192,7 @@ namespace gdjs {
|
||||
this._imageManager
|
||||
);
|
||||
this._model3DManager = new gdjs.Model3DManager(this);
|
||||
this._svgManager = new InternalInGameEditorOnlySvgManager();
|
||||
|
||||
// add spine related managers only if spine extension is used
|
||||
if (gdjs.SpineAtlasManager && gdjs.SpineManager) {
|
||||
@@ -189,6 +213,7 @@ namespace gdjs {
|
||||
this._jsonManager,
|
||||
this._bitmapFontManager,
|
||||
this._model3DManager,
|
||||
this._svgManager,
|
||||
];
|
||||
|
||||
if (this._spineAtlasManager)
|
||||
@@ -282,6 +307,27 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
async loadResources(
|
||||
resourceNames: Array<string>,
|
||||
onProgress: (loadingCount: integer, totalCount: integer) => void
|
||||
): Promise<void> {
|
||||
let loadedCount = 0;
|
||||
await processAndRetryIfNeededWithPromisePool(
|
||||
resourceNames,
|
||||
maxForegroundConcurrency,
|
||||
maxAttempt,
|
||||
async (resourceName) => {
|
||||
const resource = this._resources.get(resourceName);
|
||||
if (resource) {
|
||||
await this._loadResource(resource);
|
||||
await this._processResource(resource);
|
||||
}
|
||||
loadedCount++;
|
||||
onProgress(loadedCount, this._resources.size);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the resources that are needed to launch the first scene.
|
||||
*/
|
||||
@@ -551,6 +597,23 @@ namespace gdjs {
|
||||
// TODO: mark the scene as unloaded so it's not automatically loaded again eagerly.
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when hot-reloading resources.
|
||||
*/
|
||||
unloadAllResources(): void {
|
||||
debugLogger.log(`Unloading of all resources was requested.`);
|
||||
for (const resource of this._resources.values()) {
|
||||
const resourceManager = this._resourceManagersMap.get(resource.kind);
|
||||
if (resourceManager) {
|
||||
resourceManager.unloadResource(resource);
|
||||
}
|
||||
}
|
||||
for (const sceneLoadingState of this._sceneLoadingStates.values()) {
|
||||
sceneLoadingState.status = 'not-loaded';
|
||||
}
|
||||
debugLogger.log(`Unloading of all resources finished.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a given scene at the end of the queue.
|
||||
*
|
||||
@@ -652,6 +715,9 @@ namespace gdjs {
|
||||
* the resource (this can be for example a token needed to access the resource).
|
||||
*/
|
||||
getFullUrl(url: string) {
|
||||
if (this._runtimeGame.isInGameEdition()) {
|
||||
url = addSearchParameterToUrl(url, 'cache', '' + Date.now());
|
||||
}
|
||||
const { gdevelopResourceToken } = this._runtimeGame._options;
|
||||
if (!gdevelopResourceToken) return url;
|
||||
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('RuntimeInstanceContainer');
|
||||
|
||||
const unknownObjectData = {
|
||||
name: '',
|
||||
type: '',
|
||||
variables: [],
|
||||
behaviors: [],
|
||||
effects: [],
|
||||
content: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
depth: 32,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A container of object instances rendered on screen.
|
||||
*/
|
||||
@@ -42,7 +55,10 @@ namespace gdjs {
|
||||
_debugDrawShowPointsNames: boolean = false;
|
||||
_debugDrawShowCustomPoints: boolean = false;
|
||||
|
||||
constructor() {
|
||||
/**
|
||||
* @param runtimeGame The game associated to this scene.
|
||||
*/
|
||||
constructor(runtimeGame: gdjs.RuntimeGame) {
|
||||
this._initialBehaviorSharedData = new Hashtable();
|
||||
this._instances = new Hashtable();
|
||||
this._instancesCache = new Hashtable();
|
||||
@@ -50,6 +66,10 @@ namespace gdjs {
|
||||
this._objectsCtor = new Hashtable();
|
||||
this._layers = new Hashtable();
|
||||
this._orderedLayers = [];
|
||||
if (runtimeGame.isInGameEdition()) {
|
||||
// Register an UnknownRuntimeObject to use when the object doesn't exist.
|
||||
this.registerObject(unknownObjectData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -579,6 +599,14 @@ namespace gdjs {
|
||||
this._cacheOrClearRemovedInstances();
|
||||
}
|
||||
|
||||
_updateObjectsForInGameEditor() {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const obj = allInstancesList[i];
|
||||
obj.update(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call each behavior stepPostEvents method.
|
||||
*/
|
||||
@@ -616,7 +644,7 @@ namespace gdjs {
|
||||
getObjects(name: string): gdjs.RuntimeObject[] {
|
||||
if (!this._instances.containsKey(name)) {
|
||||
logger.info(
|
||||
'RuntimeScene.getObjects: No instances called "' +
|
||||
'RuntimeInstanceContainer.getObjects: No instances called "' +
|
||||
name +
|
||||
'"! Adding it.'
|
||||
);
|
||||
@@ -636,8 +664,13 @@ namespace gdjs {
|
||||
!this._objectsCtor.containsKey(objectName) ||
|
||||
!this._objects.containsKey(objectName)
|
||||
) {
|
||||
// There is no such object in this container.
|
||||
return null;
|
||||
if (this.getGame().isInGameEdition()) {
|
||||
// Fallback on the UnknownRuntimeObject.
|
||||
objectName = '';
|
||||
} else {
|
||||
// There is no such object in this container.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const objectData = this._objects.get(objectName);
|
||||
|
||||
@@ -55,13 +55,14 @@ namespace gdjs {
|
||||
_timeScale: float = 1;
|
||||
_defaultZOrder: integer = 0;
|
||||
_hidden: boolean;
|
||||
_initialEffectsData: Array<EffectData>;
|
||||
_initialLayerData: LayerData;
|
||||
|
||||
// TODO EBO Don't store scene layer related data in layers used by custom objects.
|
||||
// (both these 3D settings and the lighting layer properties below).
|
||||
_initialCamera3DFieldOfView: float;
|
||||
_initialCamera3DFarPlaneDistance: float;
|
||||
_initialCamera3DNearPlaneDistance: float;
|
||||
_initialCamera2DPlaneMaxDrawingDistance: float;
|
||||
|
||||
_runtimeScene: gdjs.RuntimeInstanceContainer;
|
||||
_effectsManager: gdjs.EffectsManager;
|
||||
@@ -94,7 +95,9 @@ namespace gdjs {
|
||||
layerData.camera3DNearPlaneDistance || 0.1;
|
||||
this._initialCamera3DFarPlaneDistance =
|
||||
layerData.camera3DFarPlaneDistance || 2000;
|
||||
this._initialEffectsData = layerData.effects || [];
|
||||
this._initialCamera2DPlaneMaxDrawingDistance =
|
||||
layerData.camera2DPlaneMaxDrawingDistance || 5000;
|
||||
this._initialLayerData = layerData;
|
||||
this._runtimeScene = instanceContainer;
|
||||
this._effectsManager = instanceContainer.getGame().getEffectsManager();
|
||||
this._isLightingLayer = layerData.isLightingLayer;
|
||||
@@ -491,6 +494,9 @@ namespace gdjs {
|
||||
getInitialCamera3DFarPlaneDistance(): float {
|
||||
return this._initialCamera3DFarPlaneDistance;
|
||||
}
|
||||
getInitialCamera2DPlaneMaxDrawingDistance(): float {
|
||||
return this._initialCamera2DPlaneMaxDrawingDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial effects data for the layer. Only to
|
||||
@@ -498,7 +504,7 @@ namespace gdjs {
|
||||
* @deprecated
|
||||
*/
|
||||
getInitialEffectsData(): EffectData[] {
|
||||
return this._initialEffectsData;
|
||||
return this._initialLayerData.effects || [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -696,7 +702,9 @@ namespace gdjs {
|
||||
* @return true if it is a lighting layer, false otherwise.
|
||||
*/
|
||||
isLightingLayer(): boolean {
|
||||
return this._isLightingLayer;
|
||||
return (
|
||||
this._isLightingLayer && !this._runtimeScene.getGame().isInGameEdition()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace gdjs {
|
||||
/** A minimal utility to define DOM elements. */
|
||||
/**
|
||||
* A minimal utility to define DOM elements.
|
||||
* Also copied in InGameEditor.tsx.
|
||||
*/
|
||||
function h<K extends keyof HTMLElementTagNameMap>(
|
||||
tag: K,
|
||||
attrs: {
|
||||
|
||||
@@ -107,8 +107,12 @@ namespace gdjs {
|
||||
exception: Error,
|
||||
runtimeGame: gdjs.RuntimeGame
|
||||
) => {
|
||||
const sceneNames = runtimeGame.getSceneStack().getAllSceneNames();
|
||||
const currentScene = runtimeGame.getSceneStack().getCurrentScene();
|
||||
const currentScene = runtimeGame.isInGameEdition()
|
||||
? runtimeGame.getInGameEditor()?.getCurrentScene()
|
||||
: runtimeGame.getSceneStack().getCurrentScene();
|
||||
const sceneNames = runtimeGame.isInGameEdition()
|
||||
? [currentScene?.getName()]
|
||||
: runtimeGame.getSceneStack().getAllSceneNames();
|
||||
return {
|
||||
type: 'javascript-uncaught-exception',
|
||||
exception,
|
||||
@@ -116,6 +120,7 @@ namespace gdjs {
|
||||
playerId: runtimeGame.getPlayerId(),
|
||||
sessionId: runtimeGame.getSessionId(),
|
||||
isPreview: runtimeGame.isPreview(),
|
||||
isInGameEdition: runtimeGame.isInGameEdition(),
|
||||
gdevelop: {
|
||||
previewContext: runtimeGame.getAdditionalOptions().previewContext,
|
||||
isNativeMobileApp: runtimeGame.getAdditionalOptions().nativeMobileApp,
|
||||
@@ -233,42 +238,261 @@ namespace gdjs {
|
||||
protected handleCommand(data: any) {
|
||||
const that = this;
|
||||
const runtimeGame = this._runtimegame;
|
||||
const inGameEditor = runtimeGame.getInGameEditor();
|
||||
if (!data || !data.command) {
|
||||
// Not a command that's meant to be handled by the debugger, return silently to
|
||||
// avoid polluting the console.
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.command === 'play') {
|
||||
runtimeGame.pause(false);
|
||||
} else if (data.command === 'pause') {
|
||||
runtimeGame.pause(true);
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'refresh') {
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'set') {
|
||||
that.set(data.path, data.newValue);
|
||||
} else if (data.command === 'call') {
|
||||
that.call(data.path, data.args);
|
||||
} else if (data.command === 'profiler.start') {
|
||||
runtimeGame.startCurrentSceneProfiler(function (stoppedProfiler) {
|
||||
that.sendProfilerOutput(
|
||||
stoppedProfiler.getFramesAverageMeasures(),
|
||||
stoppedProfiler.getStats()
|
||||
try {
|
||||
if (data.command === 'play') {
|
||||
runtimeGame.pause(false);
|
||||
} else if (data.command === 'pause') {
|
||||
runtimeGame.pause(true);
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'refresh') {
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'getStatus') {
|
||||
that.sendRuntimeGameStatus();
|
||||
} else if (data.command === 'set') {
|
||||
that.set(data.path, data.newValue);
|
||||
} else if (data.command === 'call') {
|
||||
that.call(data.path, data.args);
|
||||
} else if (data.command === 'profiler.start') {
|
||||
runtimeGame.startCurrentSceneProfiler(function (stoppedProfiler) {
|
||||
that.sendProfilerOutput(
|
||||
stoppedProfiler.getFramesAverageMeasures(),
|
||||
stoppedProfiler.getStats()
|
||||
);
|
||||
that.sendProfilerStopped();
|
||||
});
|
||||
that.sendProfilerStarted();
|
||||
} else if (data.command === 'profiler.stop') {
|
||||
runtimeGame.stopCurrentSceneProfiler();
|
||||
} else if (data.command === 'hotReload') {
|
||||
const runtimeGameOptions: RuntimeGameOptions =
|
||||
data.payload.runtimeGameOptions;
|
||||
if (
|
||||
(runtimeGameOptions.initialRuntimeGameStatus?.isInGameEdition ||
|
||||
false) === runtimeGame.isInGameEdition()
|
||||
) {
|
||||
this._hasLoggedUncaughtException = false;
|
||||
that._hotReloader
|
||||
.hotReload({
|
||||
projectData: data.payload.projectData,
|
||||
runtimeGameOptions,
|
||||
shouldReloadResources:
|
||||
data.payload.shouldReloadResources || false,
|
||||
})
|
||||
.then((logs) => {
|
||||
that.sendHotReloaderLogs(logs);
|
||||
});
|
||||
}
|
||||
} else if (data.command === 'hotReloadObjects') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
that._hotReloader.hotReloadRuntimeSceneObjects(
|
||||
data.payload.updatedObjects,
|
||||
editedInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'hotReloadLayers') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
inGameEditor.onLayersDataChange(
|
||||
data.payload.layers,
|
||||
data.payload.areEffectsHidden
|
||||
);
|
||||
that._hotReloader.hotReloadRuntimeSceneLayers(
|
||||
data.payload.layers,
|
||||
editedInstanceContainer
|
||||
);
|
||||
// Apply `areEffectsHidden` to all the layers of the project data.
|
||||
// It avoids inconsistency when switching scene later on.
|
||||
// We do it after `hotReloadRuntimeSceneLayers` because it relies
|
||||
// on the differences with old project data.
|
||||
inGameEditor.setEffectsHiddenInEditor(
|
||||
data.payload.areEffectsHidden
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'setBackgroundColor') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
const backgroundColor = data.payload.backgroundColor;
|
||||
if (
|
||||
backgroundColor &&
|
||||
editedInstanceContainer instanceof gdjs.RuntimeScene
|
||||
) {
|
||||
const sceneData = runtimeGame.getSceneData(
|
||||
editedInstanceContainer.getScene().getName()
|
||||
);
|
||||
if (sceneData) {
|
||||
editedInstanceContainer._backgroundColor =
|
||||
gdjs.rgbToHexNumber(
|
||||
backgroundColor[0],
|
||||
backgroundColor[1],
|
||||
backgroundColor[2]
|
||||
);
|
||||
sceneData.r = backgroundColor[0];
|
||||
sceneData.v = backgroundColor[1];
|
||||
sceneData.b = backgroundColor[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'hotReloadAllInstances') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
that._hotReloader.hotReloadRuntimeInstances(
|
||||
inGameEditor.getEditedInstanceDataList(),
|
||||
data.payload.instances,
|
||||
editedInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'switchForInGameEdition') {
|
||||
if (!this._runtimegame.isInGameEdition()) return;
|
||||
|
||||
const sceneName = data.sceneName || null;
|
||||
const eventsBasedObjectType = data.eventsBasedObjectType || null;
|
||||
if (!sceneName && !eventsBasedObjectType) {
|
||||
logger.warn(
|
||||
'No scene name specified, switchForInGameEdition aborted'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (inGameEditor) {
|
||||
const wasPaused = this._runtimegame.isPaused();
|
||||
this._runtimegame.pause(true);
|
||||
inGameEditor.switchToSceneOrVariant(
|
||||
data.editorId || null,
|
||||
sceneName,
|
||||
data.externalLayoutName || null,
|
||||
eventsBasedObjectType,
|
||||
data.eventsBasedObjectVariantName || null,
|
||||
data.editorCamera3D || null
|
||||
);
|
||||
this._runtimegame.pause(wasPaused);
|
||||
}
|
||||
} else if (data.command === 'setVisibleStatus') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setVisibleStatus(data.visible);
|
||||
}
|
||||
} else if (data.command === 'updateInstances') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.reloadInstances(data.payload.instances);
|
||||
}
|
||||
} else if (data.command === 'addInstances') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.addInstances(data.payload.instances);
|
||||
inGameEditor.setSelectedObjects(
|
||||
data.payload.instances.map((instance) => instance.persistentUuid)
|
||||
);
|
||||
if (data.payload.moveUnderCursor) {
|
||||
inGameEditor.moveSelectionUnderCursor();
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'deleteSelection') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.deleteSelection();
|
||||
}
|
||||
} else if (data.command === 'dragNewInstance') {
|
||||
const gameCoords = runtimeGame
|
||||
.getRenderer()
|
||||
.convertPageToGameCoords(data.x, data.y);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseMove(gameCoords[0], gameCoords[1]);
|
||||
|
||||
if (inGameEditor)
|
||||
inGameEditor.dragNewInstance({
|
||||
name: data.name,
|
||||
dropped: data.dropped,
|
||||
isAltPressed: data.isAltPressed,
|
||||
});
|
||||
} else if (data.command === 'cancelDragNewInstance') {
|
||||
if (inGameEditor) inGameEditor.cancelDragNewInstance();
|
||||
} else if (data.command === 'setInGameEditorSettings') {
|
||||
if (inGameEditor && data.payload?.inGameEditorSettings) {
|
||||
inGameEditor.setInGameEditorSettings(
|
||||
data.payload.inGameEditorSettings
|
||||
);
|
||||
}
|
||||
} else if (data.command === 'setInstancesEditorSettings') {
|
||||
if (inGameEditor)
|
||||
inGameEditor.updateInstancesEditorSettings(
|
||||
data.payload.instancesEditorSettings
|
||||
);
|
||||
} else if (data.command === 'zoomToInitialPosition') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomToInitialPosition(data.payload.visibleScreenArea);
|
||||
}
|
||||
} else if (data.command === 'zoomToFitContent') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomToFitContent(data.payload.visibleScreenArea);
|
||||
}
|
||||
} else if (data.command === 'setSelectedLayer') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setSelectedLayerName(data.payload.layerName);
|
||||
}
|
||||
} else if (data.command === 'zoomToFitSelection') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomToFitSelection(data.payload.visibleScreenArea);
|
||||
}
|
||||
} else if (data.command === 'zoomBy') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomBy(data.payload.zoomFactor);
|
||||
}
|
||||
} else if (data.command === 'setZoom') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setZoom(data.payload.zoom);
|
||||
}
|
||||
} else if (data.command === 'setSelectedInstances') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setSelectedObjects(data.payload.instanceUuids);
|
||||
}
|
||||
} else if (data.command === 'centerViewOnLastSelectedInstance') {
|
||||
if (inGameEditor) {
|
||||
// TODO: use data.payload.visibleScreenArea
|
||||
inGameEditor.centerViewOnLastSelectedInstance();
|
||||
}
|
||||
} else if (data.command === 'updateInnerArea') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.updateInnerArea(
|
||||
data.payload.areaMinX,
|
||||
data.payload.areaMinY,
|
||||
data.payload.areaMinZ,
|
||||
data.payload.areaMaxX,
|
||||
data.payload.areaMaxY,
|
||||
data.payload.areaMaxZ
|
||||
);
|
||||
}
|
||||
} else if (data.command === 'getSelectionAABB') {
|
||||
if (inGameEditor) {
|
||||
this.sendSelectionAABB(data.messageId);
|
||||
}
|
||||
} else if (data.command === 'hardReload') {
|
||||
// This usually means that the preview was modified so much that an entire reload
|
||||
// is needed, or that the runtime itself could have been modified.
|
||||
this.launchHardReload();
|
||||
} else {
|
||||
logger.info(
|
||||
'Unknown command "' + data.command + '" received by the debugger.'
|
||||
);
|
||||
that.sendProfilerStopped();
|
||||
});
|
||||
that.sendProfilerStarted();
|
||||
} else if (data.command === 'profiler.stop') {
|
||||
runtimeGame.stopCurrentSceneProfiler();
|
||||
} else if (data.command === 'hotReload') {
|
||||
that._hotReloader.hotReload().then((logs) => {
|
||||
that.sendHotReloaderLogs(logs);
|
||||
});
|
||||
} else {
|
||||
logger.info(
|
||||
'Unknown command "' + data.command + '" received by the debugger.'
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.onUncaughtException(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,9 +554,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
onUncaughtException(exception: Error): void {
|
||||
logger.error('Uncaught exception: ' + exception);
|
||||
logger.error('Uncaught exception: ', exception, exception.stack);
|
||||
|
||||
this._inGameDebugger.setUncaughtException(exception);
|
||||
const runtimeGame = this._runtimegame;
|
||||
if (!runtimeGame.isInGameEdition()) {
|
||||
this._inGameDebugger.setUncaughtException(exception);
|
||||
}
|
||||
|
||||
if (!this._hasLoggedUncaughtException) {
|
||||
// Only log an uncaught exception once, to avoid spamming the debugger server
|
||||
@@ -435,6 +662,20 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
sendRuntimeGameStatus(): void {
|
||||
const currentScene = this._runtimegame.getSceneStack().getCurrentScene();
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'status',
|
||||
payload: {
|
||||
isPaused: this._runtimegame.isPaused(),
|
||||
isInGameEdition: this._runtimegame.isInGameEdition(),
|
||||
sceneName: currentScene ? currentScene.getName() : null,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all the relevant data from the {@link RuntimeGame} instance and send it to the server.
|
||||
*/
|
||||
@@ -515,7 +756,10 @@ namespace gdjs {
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'hotReloader.logs',
|
||||
payload: logs,
|
||||
payload: {
|
||||
isInGameEdition: this._runtimegame.isInGameEdition(),
|
||||
logs,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -544,26 +788,173 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called when the game is paused.
|
||||
*/
|
||||
sendGamePaused(): void {
|
||||
sendInstanceChanges(changes: {
|
||||
isSendingBackSelectionForDefaultSize: boolean;
|
||||
updatedInstances: Array<InstanceData>;
|
||||
addedInstances: Array<InstanceData>;
|
||||
selectedInstances: Array<InstancePersistentUuidData>;
|
||||
removedInstances: Array<InstancePersistentUuidData>;
|
||||
objectNameToEdit: string | null;
|
||||
}): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'game.paused',
|
||||
payload: null,
|
||||
command: 'updateInstances',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: changes,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called when the game is resumed.
|
||||
*/
|
||||
sendGameResumed(): void {
|
||||
sendOpenContextMenu(cursorX: float, cursorY: float): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'game.resumed',
|
||||
payload: null,
|
||||
command: 'openContextMenu',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: { cursorX, cursorY },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendCameraState(cameraState: EditorCameraState): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'setCameraState',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: cameraState,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendUndo(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'undo',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendRedo(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'redo',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendCopy(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'copy',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendPaste(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'paste',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendCut(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'cut',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendKeyboardShortcut(keyEventLike: {
|
||||
keyCode: number;
|
||||
metaKey: boolean;
|
||||
ctrlKey: boolean;
|
||||
altKey: boolean;
|
||||
shiftKey: boolean;
|
||||
}): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'handleKeyboardShortcutFromInGameEditor',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: keyEventLike,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendSelectionAABB(messageId: number): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
const selectionAABB = inGameEditor.getSelectionAABB();
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'selectionAABB',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
messageId,
|
||||
payload: selectionAABB
|
||||
? {
|
||||
minX: selectionAABB.min[0],
|
||||
minY: selectionAABB.min[1],
|
||||
minZ: selectionAABB.min[2],
|
||||
maxX: selectionAABB.max[0],
|
||||
maxY: selectionAABB.max[1],
|
||||
maxZ: selectionAABB.max[2],
|
||||
}
|
||||
: {
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
minZ: 0,
|
||||
maxX: 0,
|
||||
maxY: 0,
|
||||
maxZ: 0,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -587,5 +978,43 @@ namespace gdjs {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
launchHardReload(): void {
|
||||
try {
|
||||
const reloadUrl = new URL(location.href);
|
||||
|
||||
// Construct the initial status to be restored.
|
||||
const initialRuntimeGameStatus =
|
||||
this._runtimegame.getAdditionalOptions().initialRuntimeGameStatus;
|
||||
// We use empty strings to avoid `null` to become `"null"`.
|
||||
const runtimeGameStatus: RuntimeGameStatus = {
|
||||
editorId: initialRuntimeGameStatus?.editorId || '',
|
||||
isPaused: this._runtimegame.isPaused(),
|
||||
isInGameEdition: this._runtimegame.isInGameEdition(),
|
||||
sceneName: initialRuntimeGameStatus?.sceneName || '',
|
||||
injectedExternalLayoutName:
|
||||
initialRuntimeGameStatus?.injectedExternalLayoutName || '',
|
||||
skipCreatingInstancesFromScene:
|
||||
initialRuntimeGameStatus?.skipCreatingInstancesFromScene || false,
|
||||
eventsBasedObjectType:
|
||||
initialRuntimeGameStatus?.eventsBasedObjectType || '',
|
||||
eventsBasedObjectVariantName:
|
||||
initialRuntimeGameStatus?.eventsBasedObjectVariantName || '',
|
||||
editorCamera3D: this._runtimegame.getInGameEditor()?.getCameraState(),
|
||||
};
|
||||
|
||||
reloadUrl.searchParams.set(
|
||||
'runtimeGameStatus',
|
||||
JSON.stringify(runtimeGameStatus)
|
||||
);
|
||||
location.replace(reloadUrl);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'Could not reload the game with the new initial status',
|
||||
error
|
||||
);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,18 +144,30 @@ namespace gdjs {
|
||||
});
|
||||
}
|
||||
|
||||
hotReload(): Promise<HotReloaderLog[]> {
|
||||
async hotReload({
|
||||
shouldReloadResources,
|
||||
projectData: newProjectData,
|
||||
runtimeGameOptions: newRuntimeGameOptions,
|
||||
}: {
|
||||
shouldReloadResources: boolean;
|
||||
projectData: ProjectData;
|
||||
runtimeGameOptions: RuntimeGameOptions;
|
||||
}): Promise<HotReloaderLog[]> {
|
||||
logger.info('Hot reload started');
|
||||
const wasPaused = this._runtimeGame.isPaused();
|
||||
this._runtimeGame.pause(true);
|
||||
this._logs = [];
|
||||
|
||||
// Save old data of the project, to be used to compute
|
||||
// the difference between the old and new project data:
|
||||
|
||||
const oldProjectData: ProjectData = gdjs.projectData;
|
||||
gdjs.projectData = newProjectData;
|
||||
|
||||
const oldScriptFiles = gdjs.runtimeGameOptions
|
||||
.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
const oldRuntimeGameOptions = gdjs.runtimeGameOptions;
|
||||
gdjs.runtimeGameOptions = newRuntimeGameOptions;
|
||||
|
||||
const oldScriptFiles =
|
||||
oldRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
|
||||
oldScriptFiles.forEach((scriptFile) => {
|
||||
this._alreadyLoadedScriptFiles[scriptFile.path] = true;
|
||||
@@ -167,76 +179,103 @@ namespace gdjs {
|
||||
gdjs.behaviorsTypes.items[behaviorTypeName];
|
||||
}
|
||||
|
||||
// Reload projectData and runtimeGameOptions stored by convention in data.js:
|
||||
return this._reloadScript('data.js').then(() => {
|
||||
const newProjectData: ProjectData = gdjs.projectData;
|
||||
if (gdjs.inAppTutorialMessage) {
|
||||
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
|
||||
this._runtimeGame,
|
||||
newRuntimeGameOptions.inAppTutorialMessageInPreview,
|
||||
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
|
||||
);
|
||||
}
|
||||
|
||||
const newRuntimeGameOptions: RuntimeGameOptions =
|
||||
gdjs.runtimeGameOptions;
|
||||
const newScriptFiles =
|
||||
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
const shouldGenerateScenesEventsCode =
|
||||
!!newRuntimeGameOptions.shouldGenerateScenesEventsCode;
|
||||
const shouldReloadLibraries =
|
||||
!!newRuntimeGameOptions.shouldReloadLibraries;
|
||||
|
||||
if (gdjs.inAppTutorialMessage) {
|
||||
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
|
||||
this._runtimeGame,
|
||||
newRuntimeGameOptions.inAppTutorialMessageInPreview,
|
||||
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
|
||||
// Reload the changed scripts, which will have the side effects of re-running
|
||||
// the new scripts, potentially replacing the code of the free functions from
|
||||
// extensions (which is fine) and registering updated behaviors (which will
|
||||
// need to be re-instantiated in runtime objects).
|
||||
try {
|
||||
if (shouldReloadLibraries) {
|
||||
await this.reloadScriptFiles(
|
||||
newProjectData,
|
||||
oldScriptFiles,
|
||||
newScriptFiles,
|
||||
shouldGenerateScenesEventsCode
|
||||
);
|
||||
}
|
||||
|
||||
const newScriptFiles =
|
||||
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
const projectDataOnlyExport =
|
||||
!!newRuntimeGameOptions.projectDataOnlyExport;
|
||||
|
||||
// Reload the changed scripts, which will have the side effects of re-running
|
||||
// the new scripts, potentially replacing the code of the free functions from
|
||||
// extensions (which is fine) and registering updated behaviors (which will
|
||||
// need to be re-instantiated in runtime objects).
|
||||
return this.reloadScriptFiles(
|
||||
newProjectData,
|
||||
oldScriptFiles,
|
||||
newScriptFiles,
|
||||
projectDataOnlyExport
|
||||
)
|
||||
.then(() => {
|
||||
const changedRuntimeBehaviors =
|
||||
this._computeChangedRuntimeBehaviors(
|
||||
oldBehaviorConstructors,
|
||||
gdjs.behaviorsTypes.items
|
||||
);
|
||||
return this._hotReloadRuntimeGame(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
changedRuntimeBehaviors,
|
||||
this._runtimeGame
|
||||
const newRuntimeGameStatus =
|
||||
newRuntimeGameOptions.initialRuntimeGameStatus;
|
||||
if (
|
||||
newRuntimeGameStatus &&
|
||||
newRuntimeGameStatus.editorId &&
|
||||
newRuntimeGameStatus.isInGameEdition
|
||||
) {
|
||||
if (shouldReloadResources) {
|
||||
// Unloading all resources will force them to be loaded again,
|
||||
// which is sufficient for ensuring they are up-to-date as
|
||||
// resources will be loaded with a 'cache bursting' parameter.
|
||||
this._runtimeGame._resourcesLoader.unloadAllResources();
|
||||
}
|
||||
// The editor don't need to hot-reload the current scene because the
|
||||
// editor always stays in the initial state.
|
||||
this._runtimeGame.setProjectData(newProjectData);
|
||||
await this._runtimeGame.loadFirstAssetsAndStartBackgroundLoading(
|
||||
newRuntimeGameStatus.sceneName || newProjectData.firstLayout,
|
||||
() => {}
|
||||
);
|
||||
const inGameEditor = this._runtimeGame.getInGameEditor();
|
||||
if (inGameEditor) {
|
||||
inGameEditor.onProjectDataChange(newProjectData);
|
||||
await inGameEditor.switchToSceneOrVariant(
|
||||
newRuntimeGameStatus.editorId || null,
|
||||
newRuntimeGameStatus.sceneName,
|
||||
newRuntimeGameStatus.injectedExternalLayoutName,
|
||||
newRuntimeGameStatus.eventsBasedObjectType,
|
||||
newRuntimeGameStatus.eventsBasedObjectVariantName,
|
||||
newRuntimeGameStatus.editorCamera3D || null
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorTarget = error.target;
|
||||
if (errorTarget instanceof HTMLScriptElement) {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message: 'Unable to reload script: ' + errorTarget.src,
|
||||
});
|
||||
} else {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message:
|
||||
'Unexpected error happened while hot-reloading: ' +
|
||||
error.message +
|
||||
'\n' +
|
||||
error.stack,
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
logger.info(
|
||||
'Hot reload finished with logs:',
|
||||
this._logs.map((log) => '\n' + log.kind + ': ' + log.message)
|
||||
);
|
||||
this._runtimeGame.pause(false);
|
||||
return this._logs;
|
||||
}
|
||||
} else {
|
||||
const changedRuntimeBehaviors = this._computeChangedRuntimeBehaviors(
|
||||
oldBehaviorConstructors,
|
||||
gdjs.behaviorsTypes.items
|
||||
);
|
||||
await this._hotReloadRuntimeGame(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
changedRuntimeBehaviors,
|
||||
this._runtimeGame
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorTarget = error.target;
|
||||
if (errorTarget instanceof HTMLScriptElement) {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message: 'Unable to reload script: ' + errorTarget.src,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message:
|
||||
'Unexpected error happened while hot-reloading: ' +
|
||||
error.message +
|
||||
'\n' +
|
||||
error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'Hot reload finished with logs:',
|
||||
this._logs.map((log) => '\n' + log.kind + ': ' + log.message)
|
||||
);
|
||||
this._runtimeGame.pause(wasPaused);
|
||||
return this._logs;
|
||||
}
|
||||
|
||||
_computeChangedRuntimeBehaviors(
|
||||
@@ -281,12 +320,12 @@ namespace gdjs {
|
||||
newProjectData: ProjectData,
|
||||
oldScriptFiles: RuntimeGameOptionsScriptFile[],
|
||||
newScriptFiles: RuntimeGameOptionsScriptFile[],
|
||||
projectDataOnlyExport: boolean
|
||||
shouldGenerateScenesEventsCode: boolean
|
||||
): Promise<void[]> {
|
||||
const reloadPromises: Array<Promise<void>> = [];
|
||||
|
||||
// Reload events, only if they were exported.
|
||||
if (!projectDataOnlyExport) {
|
||||
if (shouldGenerateScenesEventsCode) {
|
||||
newProjectData.layouts.forEach((_layoutData, index) => {
|
||||
reloadPromises.push(this._reloadScript('code' + index + '.js'));
|
||||
});
|
||||
@@ -326,7 +365,7 @@ namespace gdjs {
|
||||
)[0];
|
||||
|
||||
// A file may be removed because of a partial preview.
|
||||
if (!newScriptFile && !projectDataOnlyExport) {
|
||||
if (!newScriptFile && !shouldGenerateScenesEventsCode) {
|
||||
this._logs.push({
|
||||
kind: 'warning',
|
||||
message: 'Script file ' + oldScriptFile.path + ' was removed.',
|
||||
@@ -694,6 +733,16 @@ namespace gdjs {
|
||||
runtimeScene.setEventsGeneratedCodeFunction(newLayoutData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the children object data into every custom object data.
|
||||
*
|
||||
* At the runtime, this is done at the object instantiation.
|
||||
* For hot-reloading, it's done before hands to optimize.
|
||||
*
|
||||
* @param projectData The project data
|
||||
* @param objectDatas The object datas to modify
|
||||
* @returns
|
||||
*/
|
||||
static resolveCustomObjectConfigurations(
|
||||
projectData: ProjectData,
|
||||
objectDatas: ObjectData[]
|
||||
@@ -717,27 +766,43 @@ namespace gdjs {
|
||||
if (!eventsBasedObjectData) {
|
||||
return objectData;
|
||||
}
|
||||
|
||||
const customObjectConfiguration = objectData as ObjectData &
|
||||
CustomObjectConfiguration;
|
||||
const eventsBasedObjectVariantData =
|
||||
gdjs.RuntimeGame._getEventsBasedObjectVariantData(
|
||||
eventsBasedObjectData,
|
||||
customObjectConfiguration.variant
|
||||
);
|
||||
|
||||
// Apply the legacy children configuration overriding if any.
|
||||
const mergedChildObjectDataList =
|
||||
customObjectConfiguration.childrenContent
|
||||
? eventsBasedObjectData.objects.map((objectData) => ({
|
||||
...objectData,
|
||||
...customObjectConfiguration.childrenContent[objectData.name],
|
||||
}))
|
||||
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
|
||||
customObjectConfiguration,
|
||||
eventsBasedObjectVariantData
|
||||
)
|
||||
? eventsBasedObjectData.objects.map((objectData) =>
|
||||
customObjectConfiguration.childrenContent
|
||||
? {
|
||||
...objectData,
|
||||
...customObjectConfiguration.childrenContent[
|
||||
objectData.name
|
||||
],
|
||||
}
|
||||
: objectData
|
||||
)
|
||||
: eventsBasedObjectData.objects;
|
||||
|
||||
const mergedObjectConfiguration = {
|
||||
...eventsBasedObjectData,
|
||||
...objectData,
|
||||
// ObjectData doesn't have an `objects` attribute.
|
||||
// ObjectData doesn't have an `objects` nor `instances` attribute.
|
||||
// This is a small optimization to avoid to create an
|
||||
// InstanceContainerData for each instance to hot-reload their inner
|
||||
// scene (see `_hotReloadRuntimeInstanceContainer` call from
|
||||
// `_hotReloadRuntimeSceneInstances`).
|
||||
...eventsBasedObjectData,
|
||||
...eventsBasedObjectVariantData,
|
||||
objects: mergedChildObjectDataList,
|
||||
// It must be the last one to ensure the object name won't be overridden.
|
||||
...objectData,
|
||||
};
|
||||
return mergedObjectConfiguration;
|
||||
});
|
||||
@@ -751,6 +816,12 @@ namespace gdjs {
|
||||
changedRuntimeBehaviors: ChangedRuntimeBehavior[],
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
if (!oldLayoutData.objects || !newLayoutData.objects) {
|
||||
// It can happen when `hotReloadRuntimeInstances` is executed.
|
||||
// `hotReloadRuntimeInstances` doesn't resolve the custom objects
|
||||
// because it can only modify the 1st level of instances.
|
||||
return;
|
||||
}
|
||||
const oldObjectDataList = HotReloader.resolveCustomObjectConfigurations(
|
||||
oldProjectData,
|
||||
oldLayoutData.objects
|
||||
@@ -921,16 +992,62 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
hotReloadRuntimeSceneObjects(
|
||||
updatedObjects: Array<ObjectData>,
|
||||
// runtimeInstanceContainer gives an access as a map.
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
const oldObjects: Array<ObjectData | null> = updatedObjects.map(
|
||||
(objectData) =>
|
||||
runtimeInstanceContainer._objects.get(objectData.name) || null
|
||||
);
|
||||
|
||||
const projectData: ProjectData = this._runtimeGame._data;
|
||||
const newObjectDataList = HotReloader.resolveCustomObjectConfigurations(
|
||||
projectData,
|
||||
updatedObjects
|
||||
);
|
||||
|
||||
this._hotReloadRuntimeSceneObjects(
|
||||
oldObjects,
|
||||
newObjectDataList,
|
||||
runtimeInstanceContainer
|
||||
);
|
||||
// Update the GameData
|
||||
for (let index = 0; index < updatedObjects.length; index++) {
|
||||
const oldObjectData = oldObjects[index];
|
||||
// When the object is new, the hot-reload call `registerObject`
|
||||
// so `_objects` is already updated.
|
||||
if (oldObjectData) {
|
||||
// In gdjs.CustomRuntimeObjectInstanceContainer.loadFrom, object can
|
||||
// be registered with a different instance from the ProjectData. This
|
||||
// is only done for children of a custom object with a children overriding.
|
||||
// In the case of the editor, the fake custom object used for editing
|
||||
// variants has no children overriding (see
|
||||
// gdjs.RuntimeGame._createSceneWithCustomObject).
|
||||
// Thus, the oldObjectData is always the one from the ProjectData.
|
||||
HotReloader.assignOrDelete(oldObjectData, updatedObjects[index]);
|
||||
} else {
|
||||
console.warn(
|
||||
`Can't update object data for "${updatedObjects[index].name}" because it doesn't exist.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hotReloadRuntimeSceneObjects(
|
||||
oldObjects: ObjectData[],
|
||||
oldObjects: Array<ObjectData | null>,
|
||||
newObjects: ObjectData[],
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
oldObjects.forEach((oldObjectData) => {
|
||||
if (!oldObjectData) {
|
||||
return;
|
||||
}
|
||||
const name = oldObjectData.name;
|
||||
const newObjectData = newObjects.filter(
|
||||
const newObjectData = newObjects.find(
|
||||
(objectData) => objectData.name === name
|
||||
)[0];
|
||||
);
|
||||
|
||||
// Note: if an object is renamed in the editor, it will be considered as removed,
|
||||
// and the new object name as a new object to register.
|
||||
@@ -952,9 +1069,9 @@ namespace gdjs {
|
||||
});
|
||||
newObjects.forEach((newObjectData) => {
|
||||
const name = newObjectData.name;
|
||||
const oldObjectData = oldObjects.filter(
|
||||
(layerData) => layerData.name === name
|
||||
)[0];
|
||||
const oldObjectData = oldObjects.find(
|
||||
(layerData) => layerData && layerData.name === name
|
||||
);
|
||||
if (
|
||||
(!oldObjectData || oldObjectData.type !== newObjectData.type) &&
|
||||
!runtimeInstanceContainer.isObjectRegistered(name)
|
||||
@@ -1192,6 +1309,31 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
hotReloadRuntimeSceneLayers(
|
||||
newLayers: LayerData[],
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
const layerNames = [];
|
||||
runtimeInstanceContainer.getAllLayerNames(layerNames);
|
||||
const oldLayers = layerNames.map((layerName) =>
|
||||
runtimeInstanceContainer.hasLayer(layerName)
|
||||
? runtimeInstanceContainer.getLayer(layerName)._initialLayerData
|
||||
: null
|
||||
);
|
||||
this._hotReloadRuntimeSceneLayers(
|
||||
oldLayers.filter(Boolean) as LayerData[],
|
||||
newLayers,
|
||||
runtimeInstanceContainer
|
||||
);
|
||||
// Update the GameData
|
||||
for (let index = 0; index < newLayers.length; index++) {
|
||||
const oldLayer = oldLayers[index];
|
||||
if (oldLayer) {
|
||||
HotReloader.assignOrDelete(oldLayer, newLayers[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hotReloadRuntimeSceneLayers(
|
||||
oldLayers: LayerData[],
|
||||
newLayers: LayerData[],
|
||||
@@ -1273,6 +1415,8 @@ namespace gdjs {
|
||||
newLayer.effects,
|
||||
runtimeLayer
|
||||
);
|
||||
|
||||
runtimeLayer._initialLayerData = newLayer;
|
||||
}
|
||||
|
||||
_hotReloadRuntimeLayerEffects(
|
||||
@@ -1357,6 +1501,28 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
hotReloadRuntimeInstances(
|
||||
oldInstances: InstanceData[],
|
||||
newInstances: InstanceData[],
|
||||
runtimeInstanceContainer: RuntimeInstanceContainer
|
||||
): void {
|
||||
const projectData: ProjectData = gdjs.projectData;
|
||||
const objects: Array<ObjectData> = [];
|
||||
runtimeInstanceContainer._objects.values(objects);
|
||||
projectData.layouts;
|
||||
this._hotReloadRuntimeSceneInstances(
|
||||
projectData,
|
||||
projectData,
|
||||
[],
|
||||
objects,
|
||||
objects,
|
||||
oldInstances,
|
||||
newInstances,
|
||||
runtimeInstanceContainer
|
||||
);
|
||||
gdjs.copyArray(newInstances, oldInstances);
|
||||
}
|
||||
|
||||
_hotReloadRuntimeSceneInstances(
|
||||
oldProjectData: ProjectData,
|
||||
newProjectData: ProjectData,
|
||||
@@ -1423,6 +1589,9 @@ namespace gdjs {
|
||||
);
|
||||
} else {
|
||||
// Reload objects that were created at runtime.
|
||||
// This is a subset of what is done by `_hotReloadRuntimeInstance`.
|
||||
// Since the instance doesn't exist in the editor, it's properties
|
||||
// can't be updated, only the object changes are applied.
|
||||
|
||||
// Update variables
|
||||
this._hotReloadVariablesContainer(
|
||||
@@ -1431,6 +1600,7 @@ namespace gdjs {
|
||||
runtimeObject.getVariables()
|
||||
);
|
||||
|
||||
// Update the content of custom object
|
||||
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
|
||||
const childrenInstanceContainer =
|
||||
runtimeObject.getChildrenContainer();
|
||||
@@ -1443,15 +1613,18 @@ namespace gdjs {
|
||||
CustomObjectConfiguration &
|
||||
InstanceContainerData;
|
||||
|
||||
// Reload the content of custom objects that were created at runtime.
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
|
||||
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
|
||||
// Reload the content of custom objects that were created at runtime.
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1513,22 +1686,16 @@ namespace gdjs {
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (gdjs.Base3DHandler && gdjs.Base3DHandler.is3D(runtimeObject)) {
|
||||
if (oldInstance.z !== newInstance.z && newInstance.z !== undefined) {
|
||||
runtimeObject.setZ(newInstance.z);
|
||||
if (oldInstance.z !== newInstance.z) {
|
||||
runtimeObject.setZ(newInstance.z || 0);
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (
|
||||
oldInstance.rotationX !== newInstance.rotationX &&
|
||||
newInstance.rotationX !== undefined
|
||||
) {
|
||||
runtimeObject.setRotationX(newInstance.rotationX);
|
||||
if (oldInstance.rotationX !== newInstance.rotationX) {
|
||||
runtimeObject.setRotationX(newInstance.rotationX || 0);
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (
|
||||
oldInstance.rotationY !== newInstance.rotationY &&
|
||||
newInstance.rotationY !== undefined
|
||||
) {
|
||||
runtimeObject.setRotationY(newInstance.rotationY);
|
||||
if (oldInstance.rotationY !== newInstance.rotationY) {
|
||||
runtimeObject.setRotationY(newInstance.rotationY || 0);
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
@@ -1583,8 +1750,6 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
|
||||
const childrenInstanceContainer = runtimeObject.getChildrenContainer();
|
||||
|
||||
// The `objects` attribute is already resolved by `resolveCustomObjectConfigurations()`.
|
||||
const oldCustomObjectData = oldObjectData as ObjectData &
|
||||
CustomObjectConfiguration &
|
||||
@@ -1593,14 +1758,19 @@ namespace gdjs {
|
||||
CustomObjectConfiguration &
|
||||
InstanceContainerData;
|
||||
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
|
||||
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
|
||||
const childrenInstanceContainer =
|
||||
runtimeObject.getChildrenContainer();
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update variables
|
||||
@@ -1727,5 +1897,23 @@ namespace gdjs {
|
||||
// true if both NaN, false otherwise
|
||||
return a !== a && b !== b;
|
||||
}
|
||||
|
||||
static assignOrDelete(
|
||||
target: any,
|
||||
source: any,
|
||||
ignoreKeys: string[] = []
|
||||
): void {
|
||||
Object.assign(target, source);
|
||||
for (const key in target) {
|
||||
if (ignoreKeys.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
||||
if (source[key] === undefined) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,19 @@ namespace gdjs {
|
||||
};
|
||||
this._ws.onclose = function close() {
|
||||
logger.info('Debugger connection closed');
|
||||
|
||||
if (that._runtimegame.isInGameEdition()) {
|
||||
// Sometimes, for example if the editor is launched for a long time and the device goes to sleep,
|
||||
// the WebSocket connection between the editor and the game is closed. When we are in in-game edition,
|
||||
// we can't afford to lose the connection because it means the editor is unusable.
|
||||
// In this case, we hard reload the game to re-establish a new connection.
|
||||
setTimeout(() => {
|
||||
logger.info(
|
||||
'Debugger connection closed while in in-game edition - this is suspicious so hard reloading to re-establish a new connection.'
|
||||
);
|
||||
that.launchHardReload();
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
this._ws.onerror = function errored(error) {
|
||||
logger.warn('Debugger client error:', error);
|
||||
|
||||
@@ -11,7 +11,13 @@ namespace gdjs {
|
||||
constructor(runtimeGame: RuntimeGame) {
|
||||
super(runtimeGame);
|
||||
|
||||
// Opener is either the `opener` for popups, or the `parent` if the game
|
||||
// is running as an iframe (notably: in-game edition).
|
||||
this._opener = window.opener || null;
|
||||
if (!this._opener && window.parent !== window) {
|
||||
this._opener = window.parent;
|
||||
}
|
||||
|
||||
if (!this._opener) {
|
||||
logger.info("`window.opener` not existing, the debugger won't work.");
|
||||
return;
|
||||
|
||||