Compare commits

..

1 Commits

Author SHA1 Message Date
Florian Rival
994848bcdf Allow to easily work on the game engine using the web-app development version 2025-08-31 23:35:10 +02:00
397 changed files with 5037 additions and 18198 deletions

View File

@@ -329,6 +329,7 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
condition.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
@@ -356,11 +357,6 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(condition, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
if (instrInfos.IsObjectInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
@@ -492,16 +488,14 @@ gd::String EventsCodeGenerator::GenerateConditionsListCode(
return outputCode;
}
bool EventsCodeGenerator::CheckBehaviorParameters(
void EventsCodeGenerator::CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos) {
bool isAnyBehaviorMissing = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
gd::ParameterMetadataTools::IterateOverParameters(
instruction.GetParameters(), instrInfos.parameters,
[this, &isAnyBehaviorMissing,
&instrInfos](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
[this](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue,
const gd::String &lastObjectName) {
if (ParameterMetadata::IsBehavior(parameterMetadata.GetType())) {
const gd::String &behaviorName = parameterValue.GetPlainString();
const gd::String &actualBehaviorType =
@@ -512,25 +506,13 @@ bool EventsCodeGenerator::CheckBehaviorParameters(
if (!expectedBehaviorType.empty() &&
actualBehaviorType != expectedBehaviorType) {
const auto &objectParameterMetadata =
instrInfos.GetParameter(lastObjectIndex);
// Event functions crash if some objects in a group are missing
// the required behaviors, since they lose reference to the original
// objects. Missing behaviors are considered "fatal" only for
// ObjectList parameters, in order to minimize side effects on
// built-in functions.
if (objectParameterMetadata.GetType() == "objectList") {
isAnyBehaviorMissing = true;
}
gd::ProjectDiagnostic projectDiagnostic(
gd::ProjectDiagnostic::ErrorType::MissingBehavior, "",
actualBehaviorType, expectedBehaviorType, lastObjectName);
if (diagnosticReport)
diagnosticReport->Add(projectDiagnostic);
if (diagnosticReport) diagnosticReport->Add(projectDiagnostic);
}
}
});
return isAnyBehaviorMissing;
}
/**
@@ -570,6 +552,7 @@ gd::String EventsCodeGenerator::GenerateActionCode(
action.SetParameters(parameters);
}
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
// Verify that there are no mismatches between object type in parameters.
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType())) {
@@ -596,11 +579,6 @@ gd::String EventsCodeGenerator::GenerateActionCode(
}
}
}
bool isAnyBehaviorMissing =
gd::EventsCodeGenerator::CheckBehaviorParameters(action, instrInfos);
if (isAnyBehaviorMissing) {
return "/* Missing behavior - skipped. */";
}
// Call free function first if available
if (instrInfos.IsObjectInstruction()) {
@@ -791,7 +769,7 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
} else {
outputCode += actionCode;
}
outputCode += "}\n";
outputCode += "}";
}
return outputCode;

View File

@@ -837,7 +837,7 @@ protected:
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName);
bool CheckBehaviorParameters(
void CheckBehaviorParameters(
const gd::Instruction &instruction,
const gd::InstructionMetadata &instrInfos);

View File

@@ -52,25 +52,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
.SetHidden();
extension
.AddCondition(
"KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed. This stays true as long as "
"the key is held down. To check if a key was pressed during "
"the frame, use \"Key just pressed\" instead."),
_("_PARAM1_ key is pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("keyboardKey", _("Key to check"))
.MarkAsSimple();
extension
.AddCondition("KeyFromTextJustPressed",
_("Key just pressed"),
_("Check if a key was just pressed."),
_("_PARAM1_ key was just pressed"),
.AddCondition("KeyFromTextPressed",
_("Key pressed"),
_("Check if a key is pressed"),
_("_PARAM1_ key is pressed"),
"",
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
@@ -81,7 +66,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
extension
.AddCondition("KeyFromTextReleased",
_("Key released"),
_("Check if a key was just released."),
_("Check if a key was just released"),
_("_PARAM1_ key is released"),
"",
"res/conditions/keyboard24.png",

View File

@@ -298,19 +298,6 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
return *this;
}
/**
* Check if the behavior can be used on objects from event-based objects.
*/
bool IsRelevantForChildObjects() const { return isRelevantForChildObjects; }
/**
* Set that behavior can't be used on objects from event-based objects.
*/
BehaviorMetadata &MarkAsIrrelevantForChildObjects() {
isRelevantForChildObjects = false;
return *this;
}
QuickCustomization::Visibility GetQuickCustomizationVisibility() const {
return quickCustomizationVisibility;
}
@@ -406,7 +393,6 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
mutable std::vector<gd::String> requiredBehaviors;
bool isPrivate = false;
bool isHidden = false;
bool isRelevantForChildObjects = true;
gd::String openFullEditorLabel;
QuickCustomization::Visibility quickCustomizationVisibility = QuickCustomization::Visibility::Default;

View File

@@ -194,8 +194,7 @@ void ParameterMetadataTools::IterateOverParameters(
[&fn](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName,
size_t lastObjectIndex) {
const gd::String& lastObjectName) {
fn(parameterMetadata, parameterValue, lastObjectName);
});
}
@@ -206,10 +205,8 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName,
size_t lastObjectIndex)> fn) {
const gd::String& lastObjectName)> fn) {
gd::String lastObjectName = "";
size_t lastObjectIndex = 0;
for (std::size_t pNb = 0; pNb < parametersMetadata.GetParametersCount();
++pNb) {
const gd::ParameterMetadata &parameterMetadata =
@@ -221,17 +218,15 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
? Expression(parameterMetadata.GetDefaultValue())
: parameterValue;
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName, lastObjectIndex);
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName);
// Memorize the last object name. By convention, parameters that require
// an object (mainly, "objectvar" and "behavior") should be placed after
// the object in the list of parameters (if possible, just after).
// Search "lastObjectName" in the codebase for other place where this
// convention is enforced.
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType())) {
if (gd::ParameterMetadata::IsObject(parameterMetadata.GetType()))
lastObjectName = parameterValueOrDefault.GetPlainString();
lastObjectIndex = pNb;
}
}
}

View File

@@ -64,8 +64,7 @@ class GD_CORE_API ParameterMetadataTools {
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName,
size_t lastObjectIndex)> fn);
const gd::String& lastObjectName)> fn);
/**
* Iterate over the parameters of a FunctionCallNode.

View File

@@ -813,13 +813,6 @@ 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 =

View File

@@ -663,10 +663,6 @@ 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);

View File

@@ -29,7 +29,7 @@ bool BehaviorParametersFiller::DoVisitInstruction(gd::Instruction &instruction,
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String &lastObjectName) {
if (parameterMetadata.GetValueTypeMetadata().IsBehavior() &&
parameterValue.GetPlainString().length() == 0) {

View File

@@ -108,10 +108,12 @@ bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (gd::ParameterMetadata::IsBehavior(type)) {

View File

@@ -183,10 +183,12 @@ bool EventsParameterReplacer::DoVisitInstruction(gd::Instruction& instruction,
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
if (!gd::EventsParameterReplacer::CanContainParameter(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -217,10 +217,12 @@ bool EventsPropertyReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
if (!gd::EventsPropertyReplacer::CanContainProperty(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -334,7 +334,7 @@ private:
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String &lastObjectName) {
if (!gd::EventsObjectReplacer::CanContainObject(
parameterMetadata.GetValueTypeMetadata())) {
return;

View File

@@ -42,16 +42,18 @@ bool EventsVariableInstructionTypeSwitcher::DoVisitInstruction(gd::Instruction&
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) ||
!gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction(
instruction.GetType())) {
return;
return;
}
const auto variableName =
gd::ExpressionVariableNameFinder::GetVariableName(
@@ -70,11 +72,10 @@ bool EventsVariableInstructionTypeSwitcher::DoVisitInstruction(gd::Instruction&
.GetObjectOrGroupVariablesContainer(lastObjectName);
}
} else if (type == "variableOrProperty") {
variablesContainer =
&GetProjectScopedContainers()
.GetVariablesContainersList()
.GetVariablesContainerFromVariableOrPropertyName(
variableName);
variablesContainer =
&GetProjectScopedContainers()
.GetVariablesContainersList()
.GetVariablesContainerFromVariableOrPropertyName(variableName);
} else {
if (GetProjectScopedContainers().GetVariablesContainersList().Has(
variableName)) {

View File

@@ -448,10 +448,12 @@ bool EventsVariableReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&

View File

@@ -150,7 +150,7 @@ bool ProjectElementRenamer::DoVisitInstruction(gd::Instruction &instruction,
instruction.GetParameters(), metadata.GetParameters(),
[&](const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterValue, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
const gd::String &lastObjectName) {
if (parameterMetadata.GetType() == "layer") {
if (parameterValue.GetPlainString().length() < 2) {
// This is either the base layer or an invalid layer name.

View File

@@ -1,23 +0,0 @@
#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

View File

@@ -1,39 +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 <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

View File

@@ -76,7 +76,6 @@ void ObjectAssetSerializer::SerializeTo(
double width = 0;
double height = 0;
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
if (project.HasEventsBasedObject(object.GetType())) {
SerializerElement &variantsElement =
objectAssetElement.AddChild("variants");
@@ -88,6 +87,7 @@ void ObjectAssetSerializer::SerializeTo(
height = variant->GetAreaMaxY() - variant->GetAreaMinY();
}
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
project, object, variantsElement, alreadyUsedVariantIdentifiers);
}
@@ -114,24 +114,14 @@ void ObjectAssetSerializer::SerializeTo(
resourceElement.SetAttribute("name", resource.GetName());
}
std::unordered_set<gd::String> usedExtensionNames;
usedExtensionNames.insert(extensionName);
for (auto &usedVariantIdentifier : alreadyUsedVariantIdentifiers) {
usedExtensionNames.insert(PlatformExtension::GetExtensionFromFullObjectType(
usedVariantIdentifier));
}
SerializerElement &requiredExtensionsElement =
objectAssetElement.AddChild("requiredExtensions");
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
for (auto &usedExtensionName : usedExtensionNames) {
if (project.HasEventsFunctionsExtensionNamed(usedExtensionName)) {
auto &extension = project.GetEventsFunctionsExtension(usedExtensionName);
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", usedExtensionName);
requiredExtensionElement.SetAttribute("extensionVersion",
extension.GetVersion());
}
if (project.HasEventsFunctionsExtensionNamed(extensionName)) {
SerializerElement &requiredExtensionElement =
requiredExtensionsElement.AddChild("requiredExtension");
requiredExtensionElement.SetAttribute("extensionName", extensionName);
requiredExtensionElement.SetAttribute("extensionVersion", "1.0.0");
}
// TODO This can be removed when the asset script no longer require it.

View File

@@ -227,11 +227,12 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(), metadata.GetParameters(),
[this, &instruction](
const gd::ParameterMetadata &parameterMetadata,
const gd::Expression &parameterExpression, size_t parameterIndex,
const gd::String &lastObjectName, size_t lastObjectIndex) {
instruction.GetParameters(),
metadata.GetParameters(),
[this, &instruction](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterExpression,
size_t parameterIndex,
const gd::String& lastObjectName) {
const String& parameterValue = parameterExpression.GetPlainString();
if (parameterMetadata.GetType() == "fontResource") {
gd::String updatedParameterValue = parameterValue;

View File

@@ -7,6 +7,7 @@
#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"
@@ -25,37 +26,42 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
if (updateOriginalProject) {
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
originalProject, fs, destinationDirectory, preserveAbsoluteFilenames,
preserveDirectoryStructure);
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, originalProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
} else {
gd::Project clonedProject = originalProject;
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
clonedProject, fs, destinationDirectory, preserveAbsoluteFilenames,
preserveDirectoryStructure);
gd::ProjectResourcesCopier::CopyAllResourcesTo(
originalProject, clonedProject, fs, destinationDirectory,
preserveAbsoluteFilenames, preserveDirectoryStructure);
}
return true;
}
bool ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
gd::Project& project,
bool ProjectResourcesCopier::CopyAllResourcesTo(
gd::Project& originalProject,
gd::Project& clonedProject,
AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames,
bool preserveDirectoryStructure) {
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
// 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());
std::cout << "Copying all resources from " << projectDirectory << " to "
<< destinationDirectory << "..." << std::endl;
// Get the resources to be copied
gd::ResourcesMergingHelper resourcesMergingHelper(
project.GetResourcesManager(), fs);
clonedProject.GetResourcesManager(), fs);
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
resourcesMergingHelper.PreserveDirectoriesStructure(
preserveDirectoryStructure);
resourcesMergingHelper.PreserveAbsoluteFilenames(preserveAbsoluteFilenames);
gd::ResourceExposer::ExposeWholeProjectResources(project,
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
resourcesMergingHelper);
// Copy resources

View File

@@ -50,10 +50,12 @@ class GD_CORE_API ProjectResourcesCopier {
bool preserveDirectoryStructure = true);
private:
static bool AdaptFilePathsAndCopyAllResourcesTo(
gd::Project &project, gd::AbstractFileSystem &fs,
gd::String destinationDirectory, bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
static bool CopyAllResourcesTo(gd::Project& originalProject,
gd::Project& clonedProject,
gd::AbstractFileSystem& fs,
gd::String destinationDirectory,
bool preserveAbsoluteFilenames = true,
bool preserveDirectoryStructure = true);
};
} // namespace gd

View File

@@ -0,0 +1,17 @@
/*
* 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

View File

@@ -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/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

View File

@@ -22,14 +22,6 @@ 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).

View File

@@ -3,7 +3,8 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#ifndef RESOURCESMERGINGHELPER_H
#define RESOURCESMERGINGHELPER_H
#include <map>
#include <memory>
@@ -57,15 +58,6 @@ 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
@@ -101,13 +93,10 @@ 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

View File

@@ -6,7 +6,6 @@
#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"
@@ -28,14 +27,6 @@ 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;

View File

@@ -15,7 +15,6 @@ namespace gd {
class Project;
class Layout;
class SerializerElement;
class EventsBasedObjectVariant;
} // namespace gd
namespace gd {
@@ -28,7 +27,7 @@ namespace gd {
class SceneResourcesFinder : private gd::ArbitraryResourceWorker {
public:
/**
* @brief Find resource usages in a given scene.
* @brief Find resource usages in a given scenes.
*
* It doesn't include resources used globally.
*/
@@ -42,13 +41,6 @@ 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:

View File

@@ -332,12 +332,6 @@ 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) {

View File

@@ -13,7 +13,6 @@ class EventsFunctionsExtension;
class EventsFunction;
class EventsBasedBehavior;
class EventsBasedObject;
class EventsBasedObjectVariant;
class ArbitraryEventsWorker;
class ArbitraryEventsWorkerWithContext;
class ArbitraryEventsFunctionsWorker;
@@ -208,17 +207,6 @@ 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...)

View File

@@ -248,13 +248,12 @@ gd::String PropertyFunctionGenerator::GetStringifiedExtraInfo(
gd::String arrayString;
arrayString += "[";
bool isFirst = true;
for (const auto &choice : property.GetChoices()) {
for (const gd::String &choice : property.GetExtraInfo()) {
if (!isFirst) {
arrayString += ",";
}
isFirst = false;
// TODO Handle labels (and search "choice label")
arrayString += "\"" + choice.GetValue() + "\"";
arrayString += "\"" + choice + "\"";
}
arrayString += "]";
return arrayString;

View File

@@ -116,34 +116,6 @@ 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,

View File

@@ -9,11 +9,10 @@ namespace gd {
class Platform;
class Project;
class ArbitraryResourceWorker;
class EventsBasedObjectVariant;
class EventsFunctionsExtension;
class Effect;
class Layout;
} // namespace gd
} // namespace gd
namespace gd {
@@ -21,7 +20,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.
@@ -51,14 +50,6 @@ public:
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.
*/

View File

@@ -37,7 +37,6 @@ void EventsBasedObjectVariant::SerializeTo(SerializerElement &element) const {
layers.SerializeLayersTo(element.AddChild("layers"));
initialInstances.SerializeTo(element.AddChild("instances"));
editorSettings.SerializeTo(element.AddChild("editionSettings"));
}
void EventsBasedObjectVariant::UnserializeFrom(
@@ -67,7 +66,6 @@ void EventsBasedObjectVariant::UnserializeFrom(
layers.Reset();
}
initialInstances.UnserializeFrom(element.GetChild("instances"));
editorSettings.UnserializeFrom(element.GetChild("editionSettings"));
}
} // namespace gd

View File

@@ -5,7 +5,6 @@
*/
#pragma once
#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h"
#include "GDCore/Project/InitialInstancesContainer.h"
#include "GDCore/Project/LayersContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
@@ -200,19 +199,6 @@ public:
const gd::String &GetAssetStoreOriginalName() const {
return assetStoreOriginalName;
};
/**
*
* \brief Get the user settings for the IDE.
*/
const gd::EditorSettings& GetAssociatedEditorSettings() const {
return editorSettings;
}
/**
* \brief Get the user settings for the IDE.
*/
gd::EditorSettings& GetAssociatedEditorSettings() { return editorSettings; }
void SerializeTo(SerializerElement &element) const;
@@ -238,7 +224,6 @@ private:
* store.
*/
gd::String assetStoreOriginalName;
gd::EditorSettings editorSettings;
};
} // namespace gd

View File

@@ -60,18 +60,6 @@ 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"));
@@ -86,51 +74,45 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
if (persistentUuid.empty()) ResetPersistentUuid();
numberProperties.clear();
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");
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();
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;
}
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("initialVariables", "InitialVariables")) {
GetVariables().UnserializeFrom(
element.GetChild("initialVariables", 0, "InitialVariables"));
}
GetVariables().UnserializeFrom(
element.GetChild("initialVariables", 0, "InitialVariables"));
}
void InitialInstance::SerializeTo(SerializerElement& element) const {
@@ -151,8 +133,6 @@ 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());

View File

@@ -219,13 +219,6 @@ 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.
@@ -373,11 +366,7 @@ class GD_CORE_API InitialInstance {
*/
InitialInstance& ResetPersistentUuid();
/**
* \brief Reset the persistent UUID used to recognize
* the same initial instance between serialization.
*/
const gd::String& GetPersistentUuid() const { return persistentUuid; }
const gd::String& GetPersistentUuid() const { return persistentUuid; }
///@}
private:
@@ -406,9 +395,6 @@ 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

View File

@@ -23,7 +23,6 @@ Layer::Layer()
camera3DNearPlaneDistance(3),
camera3DFarPlaneDistance(10000),
camera3DFieldOfView(45),
camera2DPlaneMaxDrawingDistance(5000),
ambientLightColorR(200),
ambientLightColorG(200),
ambientLightColorB(200) {}
@@ -57,8 +56,6 @@ 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");
@@ -102,8 +99,6 @@ 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");

View File

@@ -182,8 +182,6 @@ 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
@@ -294,7 +292,6 @@ 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

View File

@@ -730,8 +730,6 @@ 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",
@@ -1111,10 +1109,6 @@ 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"));
@@ -1156,8 +1150,6 @@ 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");
@@ -1327,8 +1319,6 @@ void Project::Init(const gd::Project& game) {
sceneResourcesPreloading = game.sceneResourcesPreloading;
sceneResourcesUnloading = game.sceneResourcesUnloading;
areEffectsHiddenInEditor = game.areEffectsHiddenInEditor;
}
} // namespace gd

View File

@@ -506,20 +506,6 @@ 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
@@ -1179,9 +1165,6 @@ 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

View File

@@ -34,20 +34,6 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
}
}
if (!choices.empty()
// Compatibility with GD <= 5.5.239
|| !extraInformation.empty()
// end of compatibility code
) {
SerializerElement &choicesElement = element.AddChild("choices");
choicesElement.ConsiderAsArrayOf("choice");
for (const auto &choice : choices) {
auto &choiceElement = choicesElement.AddChild("Choice");
choiceElement.SetStringAttribute("value", choice.GetValue());
choiceElement.SetStringAttribute("label", choice.GetLabel());
}
}
if (hidden) {
element.AddChild("hidden").SetBoolValue(hidden);
}
@@ -70,9 +56,7 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
currentValue = element.GetChild("value").GetStringValue();
type = element.GetChild("type").GetStringValue();
if (type == "Number") {
gd::String unitName = element.HasChild("unit")
? element.GetChild("unit").GetStringValue()
: "";
gd::String unitName = element.GetChild("unit").GetStringValue();
measurementUnit =
gd::MeasurementUnit::HasDefaultMeasurementUnitNamed(unitName)
? measurementUnit =
@@ -96,26 +80,6 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
extraInformationElement.GetChild(i).GetStringValue());
}
if (element.HasChild("choices")) {
choices.clear();
const SerializerElement &choicesElement = element.GetChild("choices");
choicesElement.ConsiderAsArrayOf("choice");
for (std::size_t i = 0; i < choicesElement.GetChildrenCount(); ++i) {
auto &choiceElement = choicesElement.GetChild(i);
AddChoice(choiceElement.GetStringAttribute("value"),
choiceElement.GetStringAttribute("label"));
}
}
// Compatibility with GD <= 5.5.239
else if (type == "Choice") {
choices.clear();
for (auto &choiceValue : extraInformation) {
AddChoice(choiceValue, choiceValue);
}
extraInformation.clear();
}
// end of compatibility code
hidden = element.HasChild("hidden")
? element.GetChild("hidden").GetBoolValue()
: false;

View File

@@ -116,11 +116,6 @@ class GD_CORE_API PropertyDescriptor {
return *this;
}
PropertyDescriptor& ClearChoices() {
choices.clear();
return *this;
}
PropertyDescriptor& AddChoice(const gd::String& value,
const gd::String& label) {
choices.push_back(PropertyDescriptorChoice(value, label));

View File

@@ -139,8 +139,8 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
.SetLabel("Dot shape")
.SetDescription("The shape is used for collision.")
.SetGroup("Movement");
property.AddChoice("DotShape", "Dot shape");
property.AddChoice("BoundingDisk", "Bounding disk");
property.GetExtraInfo().push_back("Dot shape");
property.GetExtraInfo().push_back("Bounding disk");
gd::PropertyFunctionGenerator::GenerateBehaviorGetterAndSetter(
project, extension, behavior, property, false);
@@ -157,7 +157,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
gd::EventsFunction::ExpressionAndCondition);
REQUIRE(getter.GetExpressionType().GetName() == "stringWithSelector");
REQUIRE(getter.GetExpressionType().GetExtraInfo() ==
"[\"DotShape\",\"BoundingDisk\"]");
"[\"Dot shape\",\"Bounding disk\"]");
}
SECTION("Can generate functions for a boolean property in a behavior") {
@@ -386,8 +386,8 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
.SetLabel("Dot shape")
.SetDescription("The shape is used for collision.")
.SetGroup("Movement");
property.AddChoice("DotShape", "Dot shape");
property.AddChoice("BoundingDisk", "Bounding disk");
property.GetExtraInfo().push_back("Dot shape");
property.GetExtraInfo().push_back("Bounding disk");
gd::PropertyFunctionGenerator::GenerateObjectGetterAndSetter(
project, extension, object, property);
@@ -404,7 +404,7 @@ TEST_CASE("PropertyFunctionGenerator", "[common]") {
gd::EventsFunction::ExpressionAndCondition);
REQUIRE(getter.GetExpressionType().GetName() == "stringWithSelector");
REQUIRE(getter.GetExpressionType().GetExtraInfo() ==
"[\"DotShape\",\"BoundingDisk\"]");
"[\"Dot shape\",\"Bounding disk\"]");
}
SECTION("Can generate functions for a boolean property in an object") {

View File

@@ -147,9 +147,15 @@ namespace gdjs {
if (initialInstanceData.depth !== undefined) {
this.setDepth(initialInstanceData.depth);
}
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
this.flipZ(!!initialInstanceData.flippedZ);
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
if (initialInstanceData.flippedZ) {
this.flipZ(initialInstanceData.flippedZ);
}
}
setX(x: float): void {
@@ -316,18 +322,6 @@ 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;
}
@@ -374,6 +368,31 @@ 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.
*/

View File

@@ -11,8 +11,6 @@ namespace gdjs {
this._object = runtimeObject;
this._threeObject3D = threeObject3D;
this._threeObject3D.rotation.order = 'ZYX';
//@ts-ignore
this._threeObject3D.gdjsRuntimeObject = runtimeObject;
instanceContainer
.getLayer('')

View File

@@ -115,12 +115,6 @@ 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 {
@@ -137,11 +131,7 @@ namespace gdjs {
export namespace Base3DHandler {
export const is3D = (
object: gdjs.RuntimeObject
): object is gdjs.RuntimeObject &
gdjs.Base3DHandler &
gdjs.Resizable &
gdjs.Scalable &
gdjs.Flippable => {
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
//@ts-ignore We are checking if the methods are present.
return object.getZ && object.setZ;
};
@@ -253,10 +243,6 @@ namespace gdjs {
getUnrotatedAABBMaxZ(): number {
return this.object.getUnrotatedAABBMaxZ();
}
getOriginalDepth(): float {
return this.object.getOriginalDepth();
}
}
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);

View File

@@ -74,9 +74,15 @@ namespace gdjs {
if (initialInstanceData.depth !== undefined) {
this.setDepth(initialInstanceData.depth);
}
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
this.flipZ(!!initialInstanceData.flippedZ);
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
if (initialInstanceData.flippedZ) {
this.flipZ(initialInstanceData.flippedZ);
}
}
getNetworkSyncData(): CustomObject3DNetworkSyncDataType {
@@ -309,10 +315,6 @@ namespace gdjs {
return this._maxZ - this._minZ;
}
getOriginalDepth(): float {
return this._instanceContainer._getInitialInnerAreaDepth();
}
override _updateUntransformedHitBoxes(): void {
super._updateUntransformedHitBoxes();

View File

@@ -2063,48 +2063,6 @@ module.exports = {
.setType('number')
.setGroup(_('Orientation'));
}
{
const effect = extension
.addEffect('Skybox')
.setFullName(_('Skybox'))
.setDescription(
_('Display a background on a cube surrounding the scene.')
)
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/Skybox.js');
const properties = effect.getProperties();
properties
.getOrCreate('rightFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Right face (X+)'));
properties
.getOrCreate('leftFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Left face (X-)'));
properties
.getOrCreate('bottomFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Bottom face (Y+)'));
properties
.getOrCreate('topFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Top face (Y-)'));
properties
.getOrCreate('frontFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Front face (Z+)'));
properties
.getOrCreate('backFaceResourceName')
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Back face (Z-)'));
}
{
const effect = extension
.addEffect('HueAndSaturation')

View File

@@ -278,9 +278,9 @@ namespace gdjs {
rotationX,
rotationY,
rotationZ,
this.getOriginalWidth(),
this.getOriginalHeight(),
this.getOriginalDepth(),
this._getOriginalWidth(),
this._getOriginalHeight(),
this._getOriginalDepth(),
keepAspectRatio
);
}

View File

@@ -1,102 +0,0 @@
namespace gdjs {
interface SkyboxFilterNetworkSyncData {}
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::Skybox',
new (class implements gdjs.PixiFiltersTools.FilterCreator {
makeFilter(
target: EffectsTarget,
effectData: EffectData
): gdjs.PixiFiltersTools.Filter {
if (typeof THREE === 'undefined') {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
_cubeTexture: THREE.CubeTexture;
_oldBackground:
| THREE.CubeTexture
| THREE.Texture
| THREE.Color
| null = null;
_isEnabled: boolean = false;
constructor() {
this._cubeTexture = target
.getRuntimeScene()
.getGame()
.getImageManager()
.getThreeCubeTexture(
effectData.stringParameters.rightFaceResourceName,
effectData.stringParameters.leftFaceResourceName,
effectData.stringParameters.topFaceResourceName,
effectData.stringParameters.bottomFaceResourceName,
effectData.stringParameters.frontFaceResourceName,
effectData.stringParameters.backFaceResourceName
);
}
isEnabled(target: EffectsTarget): boolean {
return this._isEnabled;
}
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
if (this._isEnabled === enabled) {
return true;
}
if (enabled) {
return this.applyEffect(target);
} else {
return this.removeEffect(target);
}
}
applyEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
// TODO Add a background stack in LayerPixiRenderer to allow
// filters to stack them.
this._oldBackground = scene.background;
scene.background = this._cubeTexture;
if (!scene.environment) {
scene.environment = this._cubeTexture;
}
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
const scene = target.get3DRendererObject() as
| THREE.Scene
| null
| undefined;
if (!scene) {
return false;
}
scene.background = this._oldBackground;
scene.environment = null;
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {}
getDoubleParameter(parameterName: string): number {
return 0;
}
updateStringParameter(parameterName: string, value: string): void {}
updateColorParameter(parameterName: string, value: number): void {}
getColorParameter(parameterName: string): number {
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
getNetworkSyncData(): SkyboxFilterNetworkSyncData {
return {};
}
updateFromNetworkSyncData(
syncData: SkyboxFilterNetworkSyncData
): void {}
})();
}
})()
);
}

View File

@@ -473,14 +473,7 @@ namespace gdjs {
this._parentOldMaxY = instanceContainer.getUnrotatedViewportMaxY();
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Custom objects can be resized during the events step.
// The anchor constraints must be applied on child-objects after the parent events.
const isChildObject = instanceContainer !== instanceContainer.getScene();
if (isChildObject) {
this.doStepPreEvents(instanceContainer);
}
}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
private _convertCoords(
instanceContainer: gdjs.RuntimeInstanceContainer,

View File

@@ -218,11 +218,9 @@ namespace gdjs {
this.setWrappingWidth(initialInstanceData.width);
this.setWrapping(true);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
override onDestroyed(): void {

View File

@@ -388,9 +388,6 @@ namespace gdjs {
return new gdjs.PromiseTask(
(async () => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
const scoreSavingState = (_scoreSavingStateByLeaderboard[
leaderboardId
] =
@@ -426,9 +423,6 @@ namespace gdjs {
) =>
new gdjs.PromiseTask(
(async () => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!playerId || !playerToken) {
@@ -753,9 +747,6 @@ 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) {

View File

@@ -1841,9 +1841,6 @@ namespace gdjs {
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (isQuickJoiningTooFast()) {
return;
}
@@ -1863,9 +1860,6 @@ namespace gdjs {
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (isQuickJoiningTooFast()) {
return;
}
@@ -1899,9 +1893,6 @@ namespace gdjs {
export const openLobbiesWindow = async (
runtimeScene: gdjs.RuntimeScene
) => {
if (runtimeScene.getGame().isInGameEdition()) {
return;
}
if (
isLobbiesWindowOpen(runtimeScene) ||
gdjs.playerAuthentication.isAuthenticationWindowOpen()

View File

@@ -53,8 +53,6 @@ namespace gdjs {
_renderer: gdjs.PanelSpriteRuntimeObjectRenderer;
_objectData: PanelSpriteObjectData;
/**
* @param instanceContainer The container the object belongs to.
* @param panelSpriteObjectData The initial properties of the object
@@ -64,7 +62,6 @@ namespace gdjs {
panelSpriteObjectData: PanelSpriteObjectData
) {
super(instanceContainer, panelSpriteObjectData);
this._objectData = panelSpriteObjectData;
this._rBorder = panelSpriteObjectData.rightMargin;
this._lBorder = panelSpriteObjectData.leftMargin;
this._tBorder = panelSpriteObjectData.topMargin;
@@ -87,7 +84,6 @@ namespace gdjs {
oldObjectData: PanelSpriteObjectData,
newObjectData: PanelSpriteObjectData
): boolean {
this._objectData = newObjectData;
if (oldObjectData.width !== newObjectData.width) {
this.setWidth(newObjectData.width);
}
@@ -167,11 +163,9 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
/**
@@ -250,14 +244,6 @@ 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;

View File

@@ -531,7 +531,6 @@ module.exports = {
physics2Behavior,
sharedData
)
.markAsIrrelevantForChildObjects()
.setIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
.addIncludeFile('Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.js')
.addRequiredFile('Extensions/Physics2Behavior/Box2D_v2.3.1_min.wasm.wasm')

View File

@@ -679,7 +679,6 @@ module.exports = {
behavior,
sharedData
)
.markAsIrrelevantForChildObjects()
.addIncludeFile(
'Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.js'
)

View File

@@ -37,8 +37,7 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
"res/physics-deprecated32.png",
"PhysicsBehavior",
std::make_shared<PhysicsBehavior>(),
std::make_shared<ScenePhysicsDatas>())
.MarkAsIrrelevantForChildObjects();
std::make_shared<ScenePhysicsDatas>());
aut.AddAction("SetStatic",
("Make the object static"),

View File

@@ -647,9 +647,6 @@ 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';
@@ -1045,10 +1042,6 @@ 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()

View File

@@ -196,8 +196,12 @@ namespace gdjs {
* @param initialInstanceData The extra parameters
*/
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
}
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {

View File

@@ -203,17 +203,13 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedSpineAtlas = this._loadedSpineAtlases.getFromName(
resourceData.name
);
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
if (loadedSpineAtlas) {
loadedSpineAtlas.dispose();
this._loadedSpineAtlases.delete(resourceData);
}
const loadingSpineAtlas = this._loadingSpineAtlases.getFromName(
resourceData.name
);
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
if (loadingSpineAtlas) {
loadingSpineAtlas.then((atl) => atl.dispose());
this._loadingSpineAtlases.delete(resourceData);

View File

@@ -199,13 +199,15 @@ namespace gdjs {
this.setSize(initialInstanceData.width, initialInstanceData.height);
this.invalidateHitboxes();
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
}
getDrawableX(): number {

View File

@@ -141,9 +141,7 @@ namespace gdjs {
);
this._borderOpacity = objectData.content.borderOpacity;
this._borderWidth = objectData.content.borderWidth;
this._disabled = instanceContainer.getGame().isInGameEdition()
? true
: objectData.content.disabled;
this._disabled = objectData.content.disabled;
this._readOnly = objectData.content.readOnly;
this._spellCheck =
objectData.content.spellCheck !== undefined
@@ -331,11 +329,9 @@ namespace gdjs {
this.setHeight(initialInstanceData.height);
this._renderer.updatePadding();
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
onScenePaused(runtimeScene: gdjs.RuntimeScene): void {
@@ -565,9 +561,6 @@ namespace gdjs {
}
setDisabled(value: boolean) {
if (this.getInstanceContainer().getGame().isInGameEdition()) {
return;
}
this._disabled = value;
this._renderer.updateDisabled();
}

View File

@@ -334,9 +334,7 @@ namespace gdjs {
return this._renderer.getRendererObject();
}
override updatePreRender(
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.ensureUpToDate();
}
@@ -354,11 +352,9 @@ namespace gdjs {
} else {
this.setWrapping(false);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
/**

View File

@@ -205,11 +205,9 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
// 4. Update position (calculations based on renderer's dimensions).
this._renderer.updatePosition();
@@ -417,14 +415,6 @@ namespace gdjs {
return this._renderer.getHeight();
}
override getOriginalWidth(): float {
return this._renderer.getTileMapWidth();
}
override getOriginalHeight(): float {
return this._renderer.getTileMapHeight();
}
getScaleX(): float {
return this._renderer.getScaleX();
}

View File

@@ -193,11 +193,9 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
private _updateTileMap(): void {
@@ -339,14 +337,6 @@ namespace gdjs {
this.setHeight(newHeight);
}
override getOriginalWidth(): float {
return this._renderer.getTileMapWidth();
}
override getOriginalHeight(): float {
return this._renderer.getTileMapHeight();
}
/**
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
*

View File

@@ -42,8 +42,6 @@ namespace gdjs {
_renderer: gdjs.TiledSpriteRuntimeObjectRenderer;
_objectData: TiledSpriteObjectData;
/**
* @param instanceContainer The container the object belongs to.
* @param tiledSpriteObjectData The initial properties of the object
@@ -53,7 +51,6 @@ namespace gdjs {
tiledSpriteObjectData: TiledSpriteObjectData
) {
super(instanceContainer, tiledSpriteObjectData);
this._objectData = tiledSpriteObjectData;
this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer(
this,
instanceContainer,
@@ -69,7 +66,6 @@ namespace gdjs {
}
updateFromObjectData(oldObjectData, newObjectData): boolean {
this._objectData = newObjectData;
if (oldObjectData.texture !== newObjectData.texture) {
this.setTexture(newObjectData.texture, this.getRuntimeScene());
}
@@ -130,11 +126,9 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
/**
@@ -226,14 +220,6 @@ 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.

View File

@@ -146,11 +146,9 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
}
onDestroyed(): void {

View File

@@ -145,19 +145,16 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
gd::DiagnosticReport diagnosticReport;
codeGenerator.SetDiagnosticReport(&diagnosticReport);
// Generate the code setting up the context of the function.
gd::String fullPreludeCode = "let scopeInstanceContainer = null;\n" +
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
"runtimeScene.getOnceTriggers()");
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator, codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParametersForEvents(
eventsFunctionsExtension.GetEventsFunctions()),
0, true),
fullPreludeCode, eventsFunction.GetEvents(), "",
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunctionsExtension, eventsFunction,
"runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(), "",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
// TODO: the editor should pass the diagnostic report and display it to the
@@ -212,7 +209,6 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._runtimeScene;\n" +
"let scopeInstanceContainer = null;\n" +
// By convention of Behavior Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
@@ -298,7 +294,6 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._instanceContainer;\n" +
"let scopeInstanceContainer = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
@@ -629,11 +624,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// the cost of creating/storing it for each events function might not
// be worth it.
" if (objectsList) {\n" +
" const object = parentEventsFunctionContext && "
// Check if `objectName` is not a child object and is passed by
// parameter from a parent instance container.
"!(scopeInstanceContainer && "
"scopeInstanceContainer.isObjectRegistered(objectName)) ?\n" +
" const object = parentEventsFunctionContext ?\n" +
" "
"parentEventsFunctionContext.createObject(objectsList.firstKey()) "
":\n" +
@@ -643,7 +634,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
" objectsList.get(objectsList.firstKey()).push(object);\n" +
" "
"eventsFunctionContext._objectArraysMap[objectName].push(object);\n" +
" }\n" + " return object;\n" + " }\n" +
" }\n" + " return object;" + " }\n" +
// Unknown object, don't create anything:
" return null;\n" +
" },\n"
@@ -655,11 +646,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
"eventsFunctionContext._objectsMap[objectName];\n" +
" let count = 0;\n" + " if (objectsList) {\n" +
" for(const objectName in objectsList.items)\n" +
" count += parentEventsFunctionContext && "
// Check if `objectName` is not a child object and is passed by
// parameter from a parent instance container.
"!(scopeInstanceContainer && "
"scopeInstanceContainer.isObjectRegistered(objectName)) ?\n" +
" count += parentEventsFunctionContext ?\n" +
"parentEventsFunctionContext.getInstancesCountOnScene(objectName) "
":\n" +
" runtimeScene.getInstancesCountOnScene(objectName);\n" +

View File

@@ -18,8 +18,6 @@ KeyboardExtension::KeyboardExtension() {
"gdjs.evtTools.input.wasKeyReleased");
GetAllConditions()["KeyFromTextPressed"].SetFunctionName(
"gdjs.evtTools.input.isKeyPressed");
GetAllConditions()["KeyFromTextJustPressed"].SetFunctionName(
"gdjs.evtTools.input.wasKeyJustPressed");
GetAllConditions()["KeyFromTextReleased"].SetFunctionName(
"gdjs.evtTools.input.wasKeyReleased");
GetAllConditions()["AnyKeyPressed"].SetFunctionName(

View File

@@ -18,9 +18,6 @@
#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"
@@ -50,7 +47,7 @@ Exporter::~Exporter() {}
bool Exporter::ExportProjectForPixiPreview(
const PreviewExportOptions &options) {
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
return helper.ExportProjectForPixiPreview(options, includesFiles);
return helper.ExportProjectForPixiPreview(options);
}
bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
@@ -83,7 +80,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
// Prepare the export directory
fs.MkDir(exportDir);
includesFiles.clear();
std::vector<gd::String> includesFiles;
std::vector<gd::String> resourcesFiles;
// Export the resources (before generating events as some resources
@@ -101,7 +98,6 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
helper.AddLibsInclude(
/*pixiRenderers=*/true,
usedExtensionsResult.Has3DObjects(),
/*isInGameEditor=*/false,
/*includeWebsocketDebuggerClient=*/false,
/*includeWindowMessageDebuggerClient=*/false,
/*includeMinimalDebuggerClient=*/false,
@@ -124,7 +120,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
helper.ExportEffectIncludes(exportedProject, includesFiles);
// Export events
if (!helper.ExportScenesEventsCode(exportedProject,
if (!helper.ExportEventsCode(exportedProject,
codeOutputDir,
includesFiles,
wholeProjectDiagnosticReport,
@@ -134,10 +130,29 @@ 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);
helper.ExportProjectData(fs,
exportedProject,
codeOutputDir + "/data.js",
noRuntimeGameOptions,
projectUsedResources,
scenesUsedResources);
includesFiles.push_back(codeOutputDir + "/data.js");
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
@@ -200,18 +215,4 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
return true;
}
void Exporter::SerializeProjectData(const gd::Project &project,
const PreviewExportOptions &options,
gd::SerializerElement &projectDataElement) {
ExporterHelper::SerializeProjectData(fs, project, options,
projectDataElement);
}
void Exporter::SerializeRuntimeGameOptions(
const PreviewExportOptions &options,
gd::SerializerElement &runtimeGameOptionsElement) {
ExporterHelper::SerializeRuntimeGameOptions(
fs, gdjsRoot, options, includesFiles, runtimeGameOptionsElement);
}
} // namespace gdjs

View File

@@ -16,7 +16,6 @@ class Project;
class Layout;
class ExternalLayout;
class AbstractFileSystem;
class SerializerElement;
} // namespace gd
namespace gdjs {
struct PreviewExportOptions;
@@ -65,33 +64,7 @@ class Exporter {
codeOutputDir = codeOutputDir_;
}
/**
* \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:
private:
gd::AbstractFileSystem&
fs; ///< The abstract file system to be used for exportation.
gd::String lastError; ///< The last error that occurred.
@@ -99,8 +72,6 @@ private:
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

View File

@@ -28,13 +28,10 @@
#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"
@@ -61,6 +58,7 @@ 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
@@ -106,297 +104,128 @@ ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
: fs(fileSystem), gdjsRoot(gdjsRoot_), codeOutputDir(codeOutputDir_) {};
bool ExporterHelper::ExportProjectForPixiPreview(
const PreviewExportOptions &options,
std::vector<gd::String> &includesFiles) {
if (options.isInGameEdition && !options.shouldReloadProjectData &&
!options.shouldReloadLibraries && !options.shouldGenerateScenesEventsCode) {
gd::LogStatus("Skip project export entirely");
return "";
}
const PreviewExportOptions &options) {
double previousTime = GetTimeNow();
fs.MkDir(options.exportPath);
if (options.shouldClearExportFolder) {
fs.ClearDir(options.exportPath);
}
includesFiles.clear();
fs.ClearDir(options.exportPath);
std::vector<gd::String> includesFiles;
std::vector<gd::String> resourcesFiles;
// 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 = options.project;
previousTime = LogTimeSpent("Project cloning", previousTime);
const gd::Project &immutableProject = exportedProject;
if (options.isInGameEdition) {
if (options.shouldReloadProjectData || options.shouldGenerateScenesEventsCode) {
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);
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);
}
gd::LogStatus("Resource export is skipped");
} else {
// Export resources (*before* generating events as some resources filenames
// may be updated)
ExportResources(fs, exportedProject, options.exportPath);
previousTime = LogTimeSpent("Resource export", previousTime);
// 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);
}
if (options.shouldReloadProjectData || options.shouldGenerateScenesEventsCode) {
// 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 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);
}
std::vector<gd::SourceFileMetadata> noUsedSourceFiles;
std::vector<gd::SourceFileMetadata> &usedSourceFiles = noUsedSourceFiles;
if (options.shouldReloadLibraries) {
auto usedExtensionsResult =
gd::UsedExtensionsFinder::ScanProject(exportedProject);
usedSourceFiles = usedExtensionsResult.GetUsedSourceFiles();
// Export effects (after engine libraries as they auto-register themselves to
// the engine)
ExportEffectIncludes(exportedProject, includesFiles);
// 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);
previousTime = LogTimeSpent("Include files export", previousTime);
// 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) {
// 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) {
if (!options.projectDataOnlyExport) {
gd::WholeProjectDiagnosticReport &wholeProjectDiagnosticReport =
options.project.GetWholeProjectDiagnosticReport();
wholeProjectDiagnosticReport.Clear();
// Generate events code
if (!ExportScenesEventsCode(immutableProject,
if (!ExportEventsCode(immutableProject,
codeOutputDir,
includesFiles,
wholeProjectDiagnosticReport,
true)) {
return false;
}
previousTime = LogTimeSpent("Events code export", previousTime);
}
else {
gd::LogStatus("Events code export is skipped");
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);
}
if (options.shouldReloadProjectData) {
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);
}
// Strip the project (*after* generating events as the events may use stripped
// things (objects groups...))
gd::ProjectStripper::StripProjectForExport(exportedProject);
exportedProject.SetFirstLayout(options.layoutName);
gd::SerializerElement runtimeGameOptions;
ExporterHelper::SerializeRuntimeGameOptions(fs, gdjsRoot, options,
includesFiles, runtimeGameOptions);
ExportProjectData(fs,
exportedProject,
codeOutputDir + "/data.js",
runtimeGameOptions);
includesFiles.push_back(codeOutputDir + "/data.js");
previousTime = LogTimeSpent("Data stripping", previousTime);
previousTime = LogTimeSpent("Project data export", previousTime);
}
else {
gd::LogStatus("Project data export is skipped");
}
if (options.shouldReloadLibraries) {
// 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) {
// 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) {
fs.MkDir(fs.DirNameFrom(filename));
gd::SerializerElement projectDataElement;
ExporterHelper::StriptAndSerializeProjectData(project, projectDataElement);
// 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);
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);
}
}
if (!options.externalLayoutName.empty()) {
initialRuntimeGameStatus.AddChild("injectedExternalLayoutName")
runtimeGameOptions.AddChild("injectExternalLayout")
.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);
}
runtimeGameOptions.AddChild("shouldReloadLibraries")
.SetBoolValue(options.shouldReloadLibraries);
runtimeGameOptions.AddChild("shouldGenerateScenesEventsCode")
.SetBoolValue(options.shouldGenerateScenesEventsCode);
runtimeGameOptions.AddChild("projectDataOnlyExport")
.SetBoolValue(options.projectDataOnlyExport);
runtimeGameOptions.AddChild("nativeMobileApp")
.SetBoolValue(options.nativeMobileApp);
runtimeGameOptions.AddChild("websocketDebuggerServerAddress")
@@ -468,93 +297,71 @@ void ExporterHelper::SerializeRuntimeGameOptions(
for (const auto &includeFile : includesFiles) {
auto hashIt = options.includeFileHashes.find(includeFile);
gd::String scriptSrc = GetExportedIncludeFilename(fs, gdjsRoot, includeFile);
gd::String scriptSrc = GetExportedIncludeFilename(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;
}
void ExporterHelper::SerializeProjectData(gd::AbstractFileSystem &fs,
const gd::Project &project,
const PreviewExportOptions &options,
gd::SerializerElement &rootElement) {
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);
}
void ExporterHelper::StriptAndSerializeProjectData(
gd::Project &project, gd::SerializerElement &rootElement) {
auto projectUsedResources =
gd::SceneResourcesFinder::FindProjectResources(project);
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);
gd::String ExporterHelper::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>> &scenesUsedResources) {
fs.MkDir(fs.DirNameFrom(filename));
// Save the project to JSON
gd::SerializerElement rootElement;
project.SerializeTo(rootElement);
SerializeUsedResources(rootElement, projectUsedResources, scenesUsedResources,
eventsBasedObjectVariantsUsedResources);
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 "";
}
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>>
&eventsBasedObjectVariantsUsedResources) {
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
auto serializeUsedResources =
[](gd::SerializerElement &element,
std::set<gd::String> &usedResources) -> void {
@@ -578,41 +385,6 @@ 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(
@@ -1003,7 +775,7 @@ bool ExporterHelper::CompleteIndexFile(
gd::String codeFilesIncludes;
for (auto &include : includesFiles) {
gd::String scriptSrc =
GetExportedIncludeFilename(fs, gdjsRoot, include, nonRuntimeScriptsCacheBurst);
GetExportedIncludeFilename(include, nonRuntimeScriptsCacheBurst);
// Sanity check if the file exists - if not skip it to avoid
// including it in the list of scripts.
@@ -1029,7 +801,6 @@ bool ExporterHelper::CompleteIndexFile(
void ExporterHelper::AddLibsInclude(bool pixiRenderers,
bool pixiInThreeRenderers,
bool isInGameEdition,
bool includeWebsocketDebuggerClient,
bool includeWindowMessageDebuggerClient,
bool includeMinimalDebuggerClient,
@@ -1106,7 +877,6 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "debugger-client/hot-reloader.js");
InsertUnique(includesFiles, "debugger-client/abstract-debugger-client.js");
InsertUnique(includesFiles, "debugger-client/InGameDebugger.js");
InsertUnique(includesFiles, "InGameEditor/InGameEditor.js");
}
if (includeWebsocketDebuggerClient) {
InsertUnique(includesFiles, "debugger-client/websocket-debugger-client.js");
@@ -1119,16 +889,14 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "debugger-client/minimal-debugger-client.js");
}
if (pixiInThreeRenderers || isInGameEdition) {
if (pixiInThreeRenderers) {
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");
}
if (pixiRenderers || isInGameEdition) {
if (pixiRenderers) {
InsertUnique(includesFiles, "pixi-renderers/pixi.js");
InsertUnique(includesFiles, "pixi-renderers/pixi-filters-tools.js");
InsertUnique(includesFiles, "pixi-renderers/runtimegame-pixi-renderer.js");
@@ -1152,12 +920,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
includesFiles,
"fontfaceobserver-font-manager/fontfaceobserver-font-manager.js");
}
if (isInGameEdition) {
// `InGameEditor` uses the `is3D` function.
InsertUnique(includesFiles, "Extensions/3D/Base3DBehavior.js");
InsertUnique(includesFiles, "Extensions/3D/HemisphereLight.js");
}
if (pixiInThreeRenderers || isInGameEdition) {
if (pixiInThreeRenderers) {
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3D.js");
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3DRenderer.js");
InsertUnique(includesFiles, "Extensions/3D/CustomRuntimeObject3D.js");
@@ -1195,7 +958,7 @@ bool ExporterHelper::ExportEffectIncludes(
return true;
}
bool ExporterHelper::ExportScenesEventsCode(
bool ExporterHelper::ExportEventsCode(
const gd::Project &project,
gd::String outputDir,
std::vector<gd::String> &includesFiles,
@@ -1231,7 +994,6 @@ bool ExporterHelper::ExportScenesEventsCode(
}
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,

View File

@@ -42,9 +42,9 @@ struct PreviewExportOptions {
useWindowMessageDebuggerClient(false),
useMinimalDebuggerClient(false),
nativeMobileApp(false),
projectDataOnlyExport(false),
fullLoadingScreen(false),
isDevelopmentEnvironment(false),
isInGameEdition(false),
nonRuntimeScriptsCacheBurst(0),
inAppTutorialMessageInPreview(""),
inAppTutorialMessagePositionInPreview(""),
@@ -145,26 +145,6 @@ 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.
@@ -176,34 +156,11 @@ struct PreviewExportOptions {
}
/**
* \brief Set if the exported folder should be cleared befor the export.
* \brief Set if the export should only export the project data, not
* exporting events code.
*/
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;
PreviewExportOptions &SetProjectDataOnlyExport(bool enable) {
projectDataOnlyExport = enable;
return *this;
}
@@ -225,40 +182,6 @@ 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 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
@@ -371,8 +294,6 @@ 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;
@@ -382,21 +303,9 @@ struct PreviewExportOptions {
gd::String inAppTutorialMessagePositionInPreview;
bool nativeMobileApp;
std::map<gd::String, int> includeFileHashes;
bool shouldClearExportFolder = true;
bool shouldReloadProjectData = true;
bool shouldReloadLibraries = true;
bool shouldGenerateScenesEventsCode = true;
bool projectDataOnlyExport;
bool fullLoadingScreen;
bool isDevelopmentEnvironment;
bool isInGameEdition;
gd::String editorId;
gd::String editorCamera3DCameraMode;
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;
@@ -470,50 +379,23 @@ class ExporterHelper {
const gd::String &GetLastError() const { return lastError; };
/**
* \brief Export a project without its events and options to 2 JS variables
* \brief Export a project to JSON
*
* \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);
/**
* \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 projectDataElement The element where the project data is serialized
*/
static void SerializeProjectData(gd::AbstractFileSystem &fs,
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
*
* \param fs The abstract file system to use to write the file
* \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);
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);
/**
* \brief Copy all the resources of the project to to the export directory,
@@ -534,7 +416,6 @@ class ExporterHelper {
*/
void AddLibsInclude(bool pixiRenderers,
bool pixiInThreeRenderers,
bool isInGameEdition,
bool includeWebsocketDebuggerClient,
bool includeWindowMessageDebuggerClient,
bool includeMinimalDebuggerClient,
@@ -572,7 +453,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 ExportScenesEventsCode(
bool ExportEventsCode(
const gd::Project &project,
gd::String outputDir,
std::vector<gd::String> &includesFiles,
@@ -697,17 +578,14 @@ 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,
std::vector<gd::String> &includesFiles);
bool ExportProjectForPixiPreview(const PreviewExportOptions &options);
/**
* \brief Given an include file, returns the name of the file to reference
* in the exported game.
*/
static gd::String GetExportedIncludeFilename(
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
gd::String GetExportedIncludeFilename(
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst = 0);
/**
@@ -734,22 +612,11 @@ 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,
std::unordered_map<gd::String, std::set<gd::String>>
&eventsBasedObjectVariantsUsedResources);
/**
* \brief Stript a project and serialize it to JSON
*
* \param project The project to be exported.
*/
static void
StriptAndSerializeProjectData(gd::Project &project,
gd::SerializerElement &rootElement);
static void SerializeUsedResources(
gd::SerializerElement &rootElement,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>>
&layersUsedResources);
};
} // namespace gdjs

View File

@@ -5,7 +5,7 @@
<name>GDJS_PROJECTNAME</name>
<content src="index.html" />
<plugin name="cordova-plugin-whitelist" version="1" />
<plugin name="cordova-plugin-screen-orientation" version="3.0.4" />
<plugin name="cordova-plugin-screen-orientation" version="3.0.2" />
<access origin="*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
@@ -67,4 +67,4 @@
<!-- Keep cordova-plugin-ionic-webview plugin last as it has a deployment-target to 11, which
affects the installation of other plugins.-->
<plugin name="cordova-plugin-ionic-webview" version="5.0.1" />
</widget>
</widget>

View File

@@ -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;
};
@@ -110,19 +110,37 @@ namespace gdjs {
);
return;
}
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;
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;
}
}
}
this._isInnerAreaFollowingParentSize =
@@ -152,7 +170,8 @@ namespace gdjs {
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
super.reinitialize(objectData);
this._reinitializeContentFromObjectData(objectData);
this._reinitializeRenderer();
this._initializeFromObjectData(objectData);
// When changing the variant, the instance is like a new instance.
// We call again `onCreated` at the end, like done by the constructor
@@ -160,14 +179,6 @@ 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
@@ -195,7 +206,8 @@ namespace gdjs {
this._instanceContainer._initialInnerArea.max[1] !==
this._innerArea.max[1]);
this._reinitializeContentFromObjectData(newObjectData);
this._reinitializeRenderer();
this._initializeFromObjectData(newObjectData);
// The generated code calls the onCreated super implementation at the end.
this.onCreated();
@@ -249,13 +261,15 @@ namespace gdjs {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
this.setOpacity(
initialInstanceData.opacity === undefined
? 255
: initialInstanceData.opacity
);
this.flipX(!!initialInstanceData.flippedX);
this.flipY(!!initialInstanceData.flippedY);
if (initialInstanceData.opacity !== undefined) {
this.setOpacity(initialInstanceData.opacity);
}
if (initialInstanceData.flippedX) {
this.flipX(initialInstanceData.flippedX);
}
if (initialInstanceData.flippedY) {
this.flipY(initialInstanceData.flippedY);
}
}
override onDeletedFromScene(): void {
@@ -286,13 +300,8 @@ namespace gdjs {
if (profiler) {
profiler.end(this.type);
}
}
override stepBehaviorsPostEvents(
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
super.stepBehaviorsPostEvents(instanceContainer);
this._instanceContainer._stepBehaviorsPostEvents();
this._instanceContainer._updateObjectsPostEvents();
}
/**
@@ -594,20 +603,6 @@ 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.
*/

View File

@@ -16,7 +16,6 @@ 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.
@@ -47,28 +46,15 @@ namespace gdjs {
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();
@@ -77,14 +63,21 @@ namespace gdjs {
/**
* Load the container from the given initial configuration.
* @param customObjectData An object containing the parent object data.
* @param eventsBasedObjectVariantData An object containing the container data.
* @param customObjectData 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
@@ -94,21 +87,19 @@ namespace gdjs {
++i
) {
const childObjectData = eventsBasedObjectVariantData.objects[i];
// The children configuration override only applies to the default variant.
if (
customObjectData.childrenContent &&
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
customObjectData,
eventsBasedObjectVariantData
)
(!eventsBasedObjectVariantData.name ||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
) {
this.registerObject({
...childObjectData,
// The custom object overrides its variant configuration with
// a legacy children configuration.
// The custom object overrides its events-based object configuration.
...customObjectData.childrenContent[childObjectData.name],
});
} else {
// The custom object follows its variant configuration.
// The custom object follows its events-based object configuration.
this.registerObject(childObjectData);
}
}
@@ -163,28 +154,6 @@ 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.
@@ -192,10 +161,7 @@ namespace gdjs {
private _setOriginalInnerArea(
eventsBasedObjectData: EventsBasedObjectVariantData
) {
if (
eventsBasedObjectData.instances.length > 0 ||
this.getGame().isInGameEdition()
) {
if (eventsBasedObjectData.instances.length > 0) {
if (!eventsBasedObjectData._initialInnerArea) {
eventsBasedObjectData._initialInnerArea = {
min: [
@@ -375,12 +341,6 @@ 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();
}

File diff suppressed because it is too large Load Diff

View File

@@ -164,17 +164,14 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedThreeModel = this._loadedThreeModels.getFromName(
resourceData.name
);
const loadedThreeModel = this._loadedThreeModels.get(resourceData);
if (loadedThreeModel) {
loadedThreeModel.scene.clear();
this._loadedThreeModels.delete(resourceData);
}
const downloadedArrayBuffer = this._downloadedArrayBuffers.getFromName(
resourceData.name
);
const downloadedArrayBuffer =
this._downloadedArrayBuffers.get(resourceData);
if (downloadedArrayBuffer) {
this._downloadedArrayBuffers.delete(resourceData);
}

View File

@@ -282,27 +282,6 @@ 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.
*/
@@ -572,23 +551,6 @@ 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.
*
@@ -690,9 +652,6 @@ 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;

View File

@@ -575,18 +575,10 @@ 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.
* Update the objects (update positions, time management...)
*/
_stepBehaviorsPostEvents() {
_updateObjectsPostEvents() {
this._cacheOrClearRemovedInstances();
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
@@ -620,7 +612,7 @@ namespace gdjs {
getObjects(name: string): gdjs.RuntimeObject[] | undefined {
if (!this._instances.containsKey(name)) {
logger.info(
'RuntimeInstanceContainer.getObjects: No instances called "' +
'RuntimeScene.getObjects: No instances called "' +
name +
'"! Adding it.'
);

View File

@@ -55,14 +55,13 @@ namespace gdjs {
_timeScale: float = 1;
_defaultZOrder: integer = 0;
_hidden: boolean;
_initialLayerData: LayerData;
_initialEffectsData: Array<EffectData>;
// 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;
@@ -95,9 +94,7 @@ namespace gdjs {
layerData.camera3DNearPlaneDistance || 0.1;
this._initialCamera3DFarPlaneDistance =
layerData.camera3DFarPlaneDistance || 2000;
this._initialCamera2DPlaneMaxDrawingDistance =
layerData.camera2DPlaneMaxDrawingDistance || 5000;
this._initialLayerData = layerData;
this._initialEffectsData = layerData.effects || [];
this._runtimeScene = instanceContainer;
this._effectsManager = instanceContainer.getGame().getEffectsManager();
this._isLightingLayer = layerData.isLightingLayer;
@@ -399,9 +396,6 @@ namespace gdjs {
getInitialCamera3DFarPlaneDistance(): float {
return this._initialCamera3DFarPlaneDistance;
}
getInitialCamera2DPlaneMaxDrawingDistance(): float {
return this._initialCamera2DPlaneMaxDrawingDistance;
}
/**
* Return the initial effects data for the layer. Only to
@@ -409,7 +403,7 @@ namespace gdjs {
* @deprecated
*/
getInitialEffectsData(): EffectData[] {
return this._initialLayerData.effects || [];
return this._initialEffectsData;
}
/**

View File

@@ -107,12 +107,8 @@ namespace gdjs {
exception: Error,
runtimeGame: gdjs.RuntimeGame
) => {
const currentScene = runtimeGame.isInGameEdition()
? runtimeGame.getInGameEditor()?.getCurrentScene()
: runtimeGame.getSceneStack().getCurrentScene();
const sceneNames = runtimeGame.isInGameEdition()
? [currentScene?.getName()]
: runtimeGame.getSceneStack().getAllSceneNames();
const sceneNames = runtimeGame.getSceneStack().getAllSceneNames();
const currentScene = runtimeGame.getSceneStack().getCurrentScene();
return {
type: 'javascript-uncaught-exception',
exception,
@@ -120,7 +116,6 @@ namespace gdjs {
playerId: runtimeGame.getPlayerId(),
sessionId: runtimeGame.getSessionId(),
isPreview: runtimeGame.isPreview(),
isInGameEdition: runtimeGame.isInGameEdition(),
gdevelop: {
previewContext: runtimeGame.getAdditionalOptions().previewContext,
isNativeMobileApp: runtimeGame.getAdditionalOptions().nativeMobileApp,
@@ -238,242 +233,42 @@ 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;
}
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') {
this._hasLoggedUncaughtException = false;
that._hotReloader
.hotReload({
projectData: data.payload.projectData,
runtimeGameOptions: data.payload.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
);
runtimeGame._data.areEffectsHiddenInEditor =
data.payload.areEffectsHidden;
that._hotReloader.hotReloadRuntimeSceneLayers(
data.payload.layers,
editedInstanceContainer
);
}
}
} 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,
});
} else if (data.command === 'cancelDragNewInstance') {
if (inGameEditor) inGameEditor.cancelDragNewInstance();
} 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.'
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()
);
}
} catch (error) {
this.onUncaughtException(error);
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.'
);
}
}
@@ -535,12 +330,9 @@ namespace gdjs {
}
onUncaughtException(exception: Error): void {
logger.error('Uncaught exception: ', exception, exception.stack);
logger.error('Uncaught exception: ' + exception);
const runtimeGame = this._runtimegame;
if (!runtimeGame.isInGameEdition()) {
this._inGameDebugger.setUncaughtException(exception);
}
this._inGameDebugger.setUncaughtException(exception);
if (!this._hasLoggedUncaughtException) {
// Only log an uncaught exception once, to avoid spamming the debugger server
@@ -643,20 +435,6 @@ 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.
*/
@@ -737,10 +515,7 @@ namespace gdjs {
this._sendMessage(
circularSafeStringify({
command: 'hotReloader.logs',
payload: {
isInGameEdition: this._runtimegame.isInGameEdition(),
logs,
},
payload: logs,
})
);
}
@@ -769,152 +544,26 @@ namespace gdjs {
);
}
sendInstanceChanges(changes: {
isSendingBackSelectionForDefaultSize: boolean;
updatedInstances: Array<InstanceData>;
addedInstances: Array<InstanceData>;
selectedInstances: Array<InstancePersistentUuidData>;
removedInstances: Array<InstancePersistentUuidData>;
}): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
/**
* Callback called when the game is paused.
*/
sendGamePaused(): void {
this._sendMessage(
circularSafeStringify({
command: 'updateInstances',
editorId: inGameEditor.getEditorId(),
payload: changes,
command: 'game.paused',
payload: null,
})
);
}
sendOpenContextMenu(cursorX: float, cursorY: float): void {
const inGameEditor = this._runtimegame.getInGameEditor();
if (!inGameEditor) {
return;
}
/**
* Callback called when the game is resumed.
*/
sendGameResumed(): void {
this._sendMessage(
circularSafeStringify({
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: {},
})
);
}
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,
},
command: 'game.resumed',
payload: null,
})
);
}
@@ -938,43 +587,5 @@ 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();
}
}
}
}

View File

@@ -144,30 +144,18 @@ namespace gdjs {
});
}
async hotReload({
shouldReloadResources,
projectData: newProjectData,
runtimeGameOptions: newRuntimeGameOptions,
}: {
shouldReloadResources: boolean;
projectData: ProjectData;
runtimeGameOptions: RuntimeGameOptions;
}): Promise<HotReloaderLog[]> {
hotReload(): 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 oldRuntimeGameOptions = gdjs.runtimeGameOptions;
gdjs.runtimeGameOptions = newRuntimeGameOptions;
const oldScriptFiles =
oldRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
const oldScriptFiles = gdjs.runtimeGameOptions
.scriptFiles as RuntimeGameOptionsScriptFile[];
oldScriptFiles.forEach((scriptFile) => {
this._alreadyLoadedScriptFiles[scriptFile.path] = true;
@@ -179,102 +167,76 @@ namespace gdjs {
gdjs.behaviorsTypes.items[behaviorTypeName];
}
if (gdjs.inAppTutorialMessage) {
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
this._runtimeGame,
newRuntimeGameOptions.inAppTutorialMessageInPreview,
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
);
}
// Reload projectData and runtimeGameOptions stored by convention in data.js:
return this._reloadScript('data.js').then(() => {
const newProjectData: ProjectData = gdjs.projectData;
const newScriptFiles =
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
const shouldGenerateScenesEventsCode =
!!newRuntimeGameOptions.shouldGenerateScenesEventsCode;
const shouldReloadLibraries =
!!newRuntimeGameOptions.shouldReloadLibraries;
const newRuntimeGameOptions: RuntimeGameOptions =
gdjs.runtimeGameOptions;
// 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
if (gdjs.inAppTutorialMessage) {
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
this._runtimeGame,
newRuntimeGameOptions.inAppTutorialMessageInPreview,
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
);
}
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) {
await inGameEditor.switchToSceneOrVariant(
newRuntimeGameStatus.editorId || null,
newRuntimeGameStatus.sceneName,
newRuntimeGameStatus.injectedExternalLayoutName,
newRuntimeGameStatus.eventsBasedObjectType,
newRuntimeGameStatus.eventsBasedObjectVariantName,
newRuntimeGameStatus.editorCamera3D || null
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
);
}
} 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,
})
.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 {
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(
@@ -319,12 +281,12 @@ namespace gdjs {
newProjectData: ProjectData,
oldScriptFiles: RuntimeGameOptionsScriptFile[],
newScriptFiles: RuntimeGameOptionsScriptFile[],
shouldGenerateScenesEventsCode: boolean
projectDataOnlyExport: boolean
): Promise<void[]> {
const reloadPromises: Array<Promise<void>> = [];
// Reload events, only if they were exported.
if (shouldGenerateScenesEventsCode) {
if (!projectDataOnlyExport) {
newProjectData.layouts.forEach((_layoutData, index) => {
reloadPromises.push(this._reloadScript('code' + index + '.js'));
});
@@ -364,7 +326,7 @@ namespace gdjs {
)[0];
// A file may be removed because of a partial preview.
if (!newScriptFile && !shouldGenerateScenesEventsCode) {
if (!newScriptFile && !projectDataOnlyExport) {
this._logs.push({
kind: 'warning',
message: 'Script file ' + oldScriptFile.path + ' was removed.',
@@ -732,16 +694,6 @@ 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[]
@@ -765,43 +717,27 @@ 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 =
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
customObjectConfiguration,
eventsBasedObjectVariantData
)
? eventsBasedObjectData.objects.map((objectData) =>
customObjectConfiguration.childrenContent
? {
...objectData,
...customObjectConfiguration.childrenContent[
objectData.name
],
}
: objectData
)
customObjectConfiguration.childrenContent
? eventsBasedObjectData.objects.map((objectData) => ({
...objectData,
...customObjectConfiguration.childrenContent[objectData.name],
}))
: eventsBasedObjectData.objects;
const mergedObjectConfiguration = {
// ObjectData doesn't have an `objects` nor `instances` attribute.
...eventsBasedObjectData,
...objectData,
// ObjectData doesn't have an `objects` 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;
});
@@ -815,12 +751,6 @@ 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
@@ -991,62 +921,16 @@ 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: Array<ObjectData | null>,
oldObjects: ObjectData[],
newObjects: ObjectData[],
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
): void {
oldObjects.forEach((oldObjectData) => {
if (!oldObjectData) {
return;
}
const name = oldObjectData.name;
const newObjectData = newObjects.find(
const newObjectData = newObjects.filter(
(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.
@@ -1068,9 +952,9 @@ namespace gdjs {
});
newObjects.forEach((newObjectData) => {
const name = newObjectData.name;
const oldObjectData = oldObjects.find(
(layerData) => layerData && layerData.name === name
);
const oldObjectData = oldObjects.filter(
(layerData) => layerData.name === name
)[0];
if (
(!oldObjectData || oldObjectData.type !== newObjectData.type) &&
!runtimeInstanceContainer.isObjectRegistered(name)
@@ -1308,31 +1192,6 @@ 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[],
@@ -1414,8 +1273,6 @@ namespace gdjs {
newLayer.effects,
runtimeLayer
);
runtimeLayer._initialLayerData = newLayer;
}
_hotReloadRuntimeLayerEffects(
@@ -1500,28 +1357,6 @@ 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,
@@ -1588,9 +1423,6 @@ 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(
@@ -1599,7 +1431,6 @@ namespace gdjs {
runtimeObject.getVariables()
);
// Update the content of custom object
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
const childrenInstanceContainer =
runtimeObject.getChildrenContainer();
@@ -1612,18 +1443,15 @@ namespace gdjs {
CustomObjectConfiguration &
InstanceContainerData;
// 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
);
}
// Reload the content of custom objects that were created at runtime.
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
}
}
}
@@ -1685,16 +1513,22 @@ namespace gdjs {
somethingChanged = true;
}
if (gdjs.Base3DHandler && gdjs.Base3DHandler.is3D(runtimeObject)) {
if (oldInstance.z !== newInstance.z) {
runtimeObject.setZ(newInstance.z || 0);
if (oldInstance.z !== newInstance.z && newInstance.z !== undefined) {
runtimeObject.setZ(newInstance.z);
somethingChanged = true;
}
if (oldInstance.rotationX !== newInstance.rotationX) {
runtimeObject.setRotationX(newInstance.rotationX || 0);
if (
oldInstance.rotationX !== newInstance.rotationX &&
newInstance.rotationX !== undefined
) {
runtimeObject.setRotationX(newInstance.rotationX);
somethingChanged = true;
}
if (oldInstance.rotationY !== newInstance.rotationY) {
runtimeObject.setRotationY(newInstance.rotationY || 0);
if (
oldInstance.rotationY !== newInstance.rotationY &&
newInstance.rotationY !== undefined
) {
runtimeObject.setRotationY(newInstance.rotationY);
somethingChanged = true;
}
}
@@ -1749,6 +1583,8 @@ 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 &
@@ -1757,19 +1593,14 @@ namespace gdjs {
CustomObjectConfiguration &
InstanceContainerData;
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
const childrenInstanceContainer =
runtimeObject.getChildrenContainer();
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
}
this._hotReloadRuntimeInstanceContainer(
oldProjectData,
newProjectData,
oldCustomObjectData,
newCustomObjectData,
changedRuntimeBehaviors,
childrenInstanceContainer
);
}
// Update variables
@@ -1896,23 +1727,5 @@ 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];
}
}
}
}
}
}

View File

@@ -56,19 +56,6 @@ 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);

View File

@@ -11,13 +11,7 @@ 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;

View File

@@ -153,7 +153,7 @@ namespace gdjs {
};
/**
* Return true if the specified key is pressed (i.e: just pressed or held down).
* Return true if the specified key is pressed
*
*/
export const isKeyPressed = function (
@@ -170,22 +170,8 @@ namespace gdjs {
};
/**
* Return true if the specified key was just pressed (i.e: it started being pressed
* during this frame).
*/
export const wasKeyJustPressed = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
key: string
) {
return instanceContainer
.getGame()
.getInputManager()
.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode[key]);
};
/**
* Return true if the specified key was just released (i.e: it stopped being pressed
* during this frame).
* Return true if the specified key was just released
*
*/
export const wasKeyReleased = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -201,7 +187,7 @@ namespace gdjs {
};
/**
* Return the name of the last key pressed in the game.
* Return the name of the last key pressed in the game
*/
export const lastPressedKey = function (
instanceContainer: gdjs.RuntimeInstanceContainer

View File

@@ -207,7 +207,7 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const resource = this._loadedFontFamily.getFromName(resourceData.name);
const resource = this._loadedFontFamily.get(resourceData);
if (resource) {
this._loadedFontFamily.delete(resourceData);
}

View File

@@ -941,12 +941,12 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const musicRes = this._loadedMusics.getFromName(resourceData.name);
const musicRes = this._loadedMusics.get(resourceData);
if (musicRes) {
this.unloadAudio(resourceData.name, true);
}
const soundRes = this._loadedSounds.getFromName(resourceData.name);
const soundRes = this._loadedSounds.get(resourceData);
if (soundRes) {
this.unloadAudio(resourceData.name, false);
}

View File

@@ -23,38 +23,33 @@ namespace gdjs {
* variants and should default to their left variant values
* if location is not specified.
*/
private static _DEFAULT_LEFT_VARIANT_KEYS: integer[] = [16, 17, 18, 91];
private _pressedKeys: Hashtable<boolean>;
private _justPressedKeys: Hashtable<boolean>;
private _releasedKeys: Hashtable<boolean>;
private _lastPressedKey: float = 0;
private _pressedMouseButtons: Array<boolean>;
private _releasedMouseButtons: Array<boolean>;
static _DEFAULT_LEFT_VARIANT_KEYS: integer[] = [16, 17, 18, 91];
_pressedKeys: Hashtable<boolean>;
_releasedKeys: Hashtable<boolean>;
_lastPressedKey: float = 0;
_pressedMouseButtons: Array<boolean>;
_releasedMouseButtons: Array<boolean>;
/**
* The cursor X position (moved by mouse and touch events).
*/
private _cursorX: float = 0;
_cursorX: float = 0;
/**
* The cursor Y position (moved by mouse and touch events).
*/
private _cursorY: float = 0;
_cursorY: float = 0;
/**
* The mouse X position (only moved by mouse events).
*/
private _mouseX: float = 0;
_mouseX: float = 0;
/**
* The mouse Y position (only moved by mouse events).
*/
private _mouseY: float = 0;
private _isMouseInsideCanvas: boolean = true;
private _wheelDeltaX: float = 0;
private _wheelDeltaY: float = 0;
private _wheelDeltaZ: float = 0;
_mouseY: float = 0;
_isMouseInsideCanvas: boolean = true;
_mouseWheelDelta: float = 0;
// TODO Remove _touches when there is no longer SpritePanelButton 1.2.0
// extension in the wild.
// @ts-ignore
private _touches = {
_touches = {
firstKey: (): string | number | null => {
for (const key in this._mouseOrTouches.items) {
// Exclude mouse key.
@@ -65,27 +60,25 @@ namespace gdjs {
return null;
},
};
private _mouseOrTouches: Hashtable<Touch>;
_mouseOrTouches: Hashtable<Touch>;
//Identifiers of the touches that started during/before the frame.
private _startedTouches: Array<integer> = [];
_startedTouches: Array<integer> = [];
//Identifiers of the touches that ended during/before the frame.
private _endedTouches: Array<integer> = [];
private _touchSimulateMouse: boolean = true;
_endedTouches: Array<integer> = [];
_touchSimulateMouse: boolean = true;
/**
* @deprecated
*/
private _lastStartedTouchIndex = 0;
_lastStartedTouchIndex = 0;
/**
* @deprecated
*/
private _lastEndedTouchIndex = 0;
_lastEndedTouchIndex = 0;
constructor() {
this._pressedKeys = new Hashtable();
this._justPressedKeys = new Hashtable();
this._releasedKeys = new Hashtable();
this._pressedMouseButtons = new Array(5);
this._releasedMouseButtons = new Array(5);
@@ -101,7 +94,7 @@ namespace gdjs {
* @param keyCode The raw key code
* @param location The location
*/
static getLocationAwareKeyCode(
_getLocationAwareKeyCode(
keyCode: number,
location: number | null | undefined
): integer {
@@ -126,12 +119,11 @@ namespace gdjs {
* @param location The location of the event.
*/
onKeyPressed(keyCode: number, location?: number): void {
const locationAwareKeyCode = InputManager.getLocationAwareKeyCode(
const locationAwareKeyCode = this._getLocationAwareKeyCode(
keyCode,
location
);
this._pressedKeys.put(locationAwareKeyCode, true);
this._justPressedKeys.put(locationAwareKeyCode, true);
this._lastPressedKey = locationAwareKeyCode;
}
@@ -143,39 +135,14 @@ namespace gdjs {
* @param location The location of the event.
*/
onKeyReleased(keyCode: number, location?: number): void {
const locationAwareKeyCode = InputManager.getLocationAwareKeyCode(
const locationAwareKeyCode = this._getLocationAwareKeyCode(
keyCode,
location
);
this._pressedKeys.put(locationAwareKeyCode, false);
this._justPressedKeys.put(locationAwareKeyCode, false);
this._releasedKeys.put(locationAwareKeyCode, true);
}
/**
* Release all keys that are currently pressed.
* Note: if you want to discard pressed keys without considering them as
* released, check `clearAllPressedKeys` instead.
*/
releaseAllPressedKeys(): void {
for (const locationAwareKeyCode in this._pressedKeys.items) {
this._pressedKeys.put(locationAwareKeyCode, false);
this._justPressedKeys.put(locationAwareKeyCode, false);
this._releasedKeys.put(locationAwareKeyCode, true);
}
}
/**
* Clears all stored pressed keys without making the keys go through
* the release state.
* Note: prefer to use `releaseAllPressedKeys` instead, as it corresponds
* to a normal key release.
*/
clearAllPressedKeys(): void {
this._pressedKeys.clear();
this._justPressedKeys.clear();
}
/**
* Return the location-aware code of the last key that was pressed.
* @return The location-aware code of the last key pressed.
@@ -185,21 +152,14 @@ namespace gdjs {
}
/**
* Return true if the key corresponding to the location-aware keyCode is pressed
* (either it was just pressed or is still held down).
* Return true if the key corresponding to the location-aware keyCode is pressed.
* @param locationAwareKeyCode The location-aware key code to be tested.
*/
isKeyPressed(locationAwareKeyCode: number): boolean {
return !!this._pressedKeys.get(locationAwareKeyCode);
}
/**
* Return true if the key corresponding to the location-aware keyCode
* was just pressed during the last frame.
* @param locationAwareKeyCode The location-aware key code to be tested.
*/
wasKeyJustPressed(locationAwareKeyCode: number): boolean {
return !!this._justPressedKeys.get(locationAwareKeyCode);
return (
this._pressedKeys.containsKey(locationAwareKeyCode) &&
this._pressedKeys.get(locationAwareKeyCode)
);
}
/**
@@ -207,7 +167,10 @@ namespace gdjs {
* @param locationAwareKeyCode The location-aware key code to be tested.
*/
wasKeyReleased(locationAwareKeyCode: number) {
return !!this._releasedKeys.get(locationAwareKeyCode);
return (
this._releasedKeys.containsKey(locationAwareKeyCode) &&
this._releasedKeys.get(locationAwareKeyCode)
);
}
/**
@@ -340,19 +303,6 @@ namespace gdjs {
}
}
/**
* Return true if any mouse button is pressed.
* @return true if any mouse button is pressed.
*/
anyMouseButtonPressed(): boolean {
for (const buttonCode in this._pressedMouseButtons) {
if (this._pressedMouseButtons[buttonCode]) {
return true;
}
}
return false;
}
_setMouseButtonPressed(buttonCode: number): void {
this._pressedMouseButtons[buttonCode] = true;
this._releasedMouseButtons[buttonCode] = false;
@@ -398,37 +348,17 @@ namespace gdjs {
/**
* Should be called whenever the mouse wheel is used
* @param wheelDeltaY The mouse wheel delta
* @param wheelDelta The mouse wheel delta
*/
onMouseWheel(
wheelDeltaY: number,
wheelDeltaX: number,
wheelDeltaZ: number
): void {
this._wheelDeltaY = wheelDeltaY;
if (wheelDeltaX !== undefined) this._wheelDeltaX = wheelDeltaX;
if (wheelDeltaZ !== undefined) this._wheelDeltaZ = wheelDeltaZ;
onMouseWheel(wheelDelta: number): void {
this._mouseWheelDelta = wheelDelta;
}
/**
* Return the mouse wheel delta on Y axis.
* Return the mouse wheel delta
*/
getMouseWheelDelta(): float {
return this._wheelDeltaY;
}
/**
* Return the mouse wheel delta on X axis.
*/
getMouseWheelDeltaX(): float {
return this._wheelDeltaX;
}
/**
* Return the mouse wheel delta on Z axis.
*/
getMouseWheelDeltaZ(): float {
return this._wheelDeltaZ;
return this._mouseWheelDelta;
}
/**
@@ -614,11 +544,8 @@ namespace gdjs {
this._startedTouches.length = 0;
this._endedTouches.length = 0;
this._releasedKeys.clear();
this._justPressedKeys.clear();
this._releasedMouseButtons.length = 0;
this._wheelDeltaX = 0;
this._wheelDeltaY = 0;
this._wheelDeltaZ = 0;
this._mouseWheelDelta = 0;
this._lastStartedTouchIndex = 0;
this._lastEndedTouchIndex = 0;
}
@@ -637,6 +564,14 @@ namespace gdjs {
return this.getMouseWheelDelta() < 0;
}
/**
* Clears all stored pressed keys without making the keys go through
* the release state.
*/
clearAllPressedKeys(): void {
this._pressedKeys.clear();
}
static _allTouchIds: Array<integer> = [];
}
}

View File

@@ -210,12 +210,12 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedJson = this._loadedJsons.getFromName(resourceData.name);
const loadedJson = this._loadedJsons.get(resourceData);
if (loadedJson) {
this._loadedJsons.delete(resourceData);
}
const callback = this._callbacks.getFromName(resourceData.name);
const callback = this._callbacks.get(resourceData);
if (callback) {
this._callbacks.delete(resourceData);
}

File diff suppressed because one or more lines are too long

View File

@@ -7,178 +7,6 @@
namespace gdjs {
const logger = new gdjs.Logger('LayerPixiRenderer');
const FRUSTUM_EDGES: Array<[number, number]> = [
// near plane edges
[0, 1],
[1, 2],
[2, 3],
[3, 0],
// far plane edges
[4, 5],
[5, 6],
[6, 7],
[7, 4],
// near↔far connections
[0, 4],
[1, 5],
[2, 6],
[3, 7],
];
/** Normalized Device Coordinates corners for near (-1) and far (+1) planes (Three.js NDC: z=-1 near, z=+1 far). */
const NDC_CORNERS: Array<Array<float>> = [
// near
[-1, -1, -1],
[+1, -1, -1],
[+1, +1, -1],
[-1, +1, -1],
// far
[-1, -1, +1],
[+1, -1, +1],
[+1, +1, +1],
[-1, +1, +1],
];
/** Sort convex polygon vertices around centroid to get consistent winding. */
const sortConvexPolygon = (points: THREE.Vector3[]): THREE.Vector3[] => {
if (points.length <= 2) return points;
const cx = points.reduce((s, p) => s + p.x, 0) / points.length;
const cy = points.reduce((s, p) => s + p.y, 0) / points.length;
return points
.map((p) => ({ p, a: Math.atan2(p.y - cy, p.x - cx) }))
.sort((u, v) => u.a - v.a)
.map((u) => u.p);
};
/**
* Intersect a frustum edge segment [a,b] with plane Z=0.
* Returns point or null if no intersection on the segment.
*/
const intersectSegmentWithZ0 = (
a: THREE.Vector3,
b: THREE.Vector3,
eps = 1e-9
): THREE.Vector3 | null => {
const az = a.z,
bz = b.z;
const dz = bz - az;
// If both z on same side and not on plane, no crossing.
if (Math.abs(dz) < eps) {
// Segment is (almost) parallel to plane.
if (Math.abs(az) < eps && Math.abs(bz) < eps) {
// Entire segment lies on plane: return endpoints (handled by caller via dedup).
// Here we return null and let caller add endpoints if needed.
return null;
}
return null;
}
// Solve a.z + t*(b.z - a.z) = 0 ⇒ t = -a.z / (b.z - a.z)
const t = -az / dz;
if (t < -eps || t > 1 + eps) {
// Intersection beyond the segment bounds.
return null;
}
const p = new THREE.Vector3(
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
0
);
return p;
};
/** Remove near-duplicate points. */
const dedupPoints = (
points: THREE.Vector3[],
eps = 1e-6
): THREE.Vector3[] => {
const out: THREE.Vector3[] = [];
for (const p of points) {
const exists = out.some(
(q) => Math.abs(p.x - q.x) < eps && Math.abs(p.y - q.y) < eps
);
if (!exists) out.push(p);
}
return out;
};
/**
* Compute the convex polygon of the camera frustum clipped by plane Z=0.
* Returns ordered vertices (world coords, z=0). Empty array if no intersection.
*/
const clipFrustumAgainstZ0 = (camera: THREE.Camera): THREE.Vector3[] => {
camera.updateMatrixWorld(true);
// Get the 8 corners of the camera frustum in world coordinates.
const corners = NDC_CORNERS.map((ndc) =>
new THREE.Vector3(ndc[0], ndc[1], ndc[2]).unproject(camera)
);
if (corners.length !== 8) return [];
const hits: THREE.Vector3[] = [];
// 1) Add vertices that already lie on the plane (z≈0).
for (const v of corners) {
if (Math.abs(v.z) < 1e-9) {
hits.push(new THREE.Vector3(v.x, v.y, 0));
}
}
// 2) Intersect each frustum edge with plane Z=0.
for (const [i, j] of FRUSTUM_EDGES) {
const a = corners[i],
b = corners[j];
const p = intersectSegmentWithZ0(a, b);
if (p) hits.push(p);
}
// Deduplicate and order.
const unique = dedupPoints(hits);
if (unique.length < 3) return [];
return sortConvexPolygon(unique);
};
/**
* Intersect the ray going through a normalized device coordinate (nx, ny)
* with the plane Z=0. Returns the hit point in THREE world coords (z=0)
* or null if the ray doesn't intersect the plane in front of the camera.
*/
const projectNDCToZ0 = (
camera: THREE.Camera,
nx: number,
ny: number
): THREE.Vector3 | null => {
if (!camera) return null;
camera.updateMatrixWorld(true);
const origin = new THREE.Vector3();
const dir = new THREE.Vector3();
const p = new THREE.Vector3(nx, ny, 0.5);
if (camera instanceof THREE.OrthographicCamera) {
// For ortho, unproject a point on the camera plane, and use forward dir.
p.z = 0; // on the camera plane
p.unproject(camera); // gives a point on the camera plane in world coords
origin.copy(p);
camera.getWorldDirection(dir);
} else {
// Perspective: unproject a point on the frustum plane, build a ray.
p.unproject(camera);
origin.copy(camera.position);
dir.copy(p).sub(origin).normalize();
}
const dz = dir.z;
if (Math.abs(dz) < 1e-8) return null; // parallel
const t = -origin.z / dz;
if (t <= 0) return null; // behind the camera => not visible
return origin.addScaledVector(dir, t).setZ(0);
};
/**
* The renderer for a gdjs.Layer using Pixi.js.
*/
@@ -219,7 +47,6 @@ namespace gdjs {
private _threePlaneGeometry: THREE.PlaneGeometry | null = null;
private _threePlaneMaterial: THREE.ShaderMaterial | null = null;
private _threePlaneMesh: THREE.Mesh | null = null;
private _threePlaneMeshDebugOutline: THREE.LineSegments | null = null;
/**
* Pixi doesn't sort children with zIndex == 0.
@@ -272,9 +99,6 @@ namespace gdjs {
// The layer is now fully initialized. Adapt the 3D camera position
// (which we could not do before in `_setup3DRendering`).
this._update3DCameraAspectAndPosition();
// Uncomment to show the outline of the 2D rendering plane.
// this.show2DRenderingPlaneDebugOutline(true);
}
onGameResolutionResized() {
@@ -310,10 +134,6 @@ namespace gdjs {
return this._threeScene;
}
getThreeGroup(): THREE.Group | null {
return this._threeGroup;
}
getThreeCamera():
| THREE.PerspectiveCamera
| THREE.OrthographicCamera
@@ -465,10 +285,6 @@ namespace gdjs {
'Tried to setup PixiJS plane for 2D rendering in 3D for a layer that is already set up.'
);
this.set2DPlaneMaxDrawingDistance(
this._layer.getInitialCamera2DPlaneMaxDrawingDistance()
);
// If we have both 2D and 3D objects to be rendered, create a render texture that PixiJS will use
// to render, and that will be projected on a plane by Three.js
this._createPixiRenderTexture(pixiRenderer);
@@ -572,298 +388,30 @@ namespace gdjs {
}
/**
* Enable or disable the drawing of an outline of the 2D rendering plane.
* Useful to visually see where the 2D rendering is done in the 3D world.
* Update the position of the PIXI container. To be called after each change
* made to position, zoom or rotation of the camera.
*/
show2DRenderingPlaneDebugOutline(enable: boolean) {
if (!this._threePlaneMesh) return;
if (enable && !this._threePlaneMeshDebugOutline) {
// Add rectangle outline around the plane.
const edges = new THREE.EdgesGeometry(this._threePlaneGeometry);
const lineMaterial = new THREE.LineBasicMaterial({
color: 0xff0000,
});
this._threePlaneMeshDebugOutline = new THREE.LineSegments(
edges,
lineMaterial
);
// Attach the outline to the plane so it follows position/scale/rotation.
this._threePlaneMesh.add(this._threePlaneMeshDebugOutline);
}
if (!enable && this._threePlaneMeshDebugOutline) {
this._threePlaneMesh.remove(this._threePlaneMeshDebugOutline);
this._threePlaneMeshDebugOutline = null;
}
}
/** Maximum size of the 2D plane, in pixels. */
private _2DPlaneMaxDrawingDistance: number = 5000;
/** Tilt degrees below which the 2D plane is not clamped. */
private _2DPlaneClampFreeTiltDeg: number = 0.1;
/** Tilt degrees below which the 2D plane is fully clamped. */
private _2DPlaneClampHardTiltDeg: number = 6;
private _2DPlaneClampRampPower: number = 1.5; // 1 = linear, >1 = smoother
/**
* Set the maximum "drawing distance", in pixels, of the 2D when in the 3D world.
* This corresponds to the "height" of the 2D plane.
* Used when the 3D camera is tilted on the X or Y axis (instead of looking down the Z axis,
* as it's done by default for 2D games).
* This is useful to avoid the 2D plane being too big when the camera is tilted.
*/
set2DPlaneMaxDrawingDistance(h: number) {
this._2DPlaneMaxDrawingDistance = Math.max(0, h);
}
/**
* Set the tilt degrees below which the 2D plane is not clamped.
*/
set2DPlaneClampFreeTiltDegrees(d: number) {
this._2DPlaneClampFreeTiltDeg = Math.max(0, d);
}
/**
* Set the tilt degrees below which the 2D plane is clamped (see `set2DPlaneMaxDrawingDistance`).
*/
set2DPlaneClampHardTiltDegrees(d: number) {
this._2DPlaneClampHardTiltDeg = Math.max(0, d);
}
/**
* Set the ramp power of the 2D plane clamping (see `set2DPlaneMaxDrawingDistance`). Used
* for smoother transition between clamped and unclamped.
*/
set2DPlaneClampRampPower(p: number) {
this._2DPlaneClampRampPower = Math.max(0.1, p);
}
/**
* Get the size of the 2D plane, in the world coordinates.
*/
private _get2DPlaneSize(): [number, number] {
if (!this._threeCamera) return [0, 0];
// Compute the intersection of the frustrum of the camera on the Z=0 plane.
// In theory, that's where the entire 2D rendering should be displayed.
const poly = clipFrustumAgainstZ0(this._threeCamera);
if (poly.length === 0) {
// No intersection at all: Z=0 not in view.
return [0, 0];
}
// Compute the axis-aligned bounds on Z=0 (world units) of the polygon,
// so we can compute the size of the plane doing the 2D rendering.
let minX = Infinity,
maxX = -Infinity,
minY = Infinity,
maxY = -Infinity;
for (const p of poly) {
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.y < minY) minY = p.y;
if (p.y > maxY) maxY = p.y;
}
let boxW = Math.max(1e-8, maxX - minX);
let boxH = Math.max(1e-8, maxY - minY);
// Keep 2D layer aspect ratio (so texture isn't stretched).
const targetAspect = this._layer.getWidth() / this._layer.getHeight();
const boxAspect = boxW / boxH;
if (boxAspect < targetAspect) {
boxW = targetAspect * boxH;
} else {
boxH = boxW / targetAspect;
}
// Decide if we should cap based on camera tilt (X/Y) ---
const forward = new THREE.Vector3();
this._threeCamera.getWorldDirection(forward);
// |forward.z| ≈ 1 -> no tilt (look mostly perpendicular to Z=0).
// |forward.z| ≈ 0 -> grazing the horizon (strong tilt).
const freeCos = Math.cos(
THREE.MathUtils.degToRad(this._2DPlaneClampFreeTiltDeg)
);
const hardCos = Math.cos(
THREE.MathUtils.degToRad(this._2DPlaneClampHardTiltDeg)
);
const tiltCos = Math.abs(forward.z);
// Map tiltCos ∈ [hardCos, freeCos] to w ∈ [1, 0]
let w = 0;
if (tiltCos <= hardCos)
w = 1; // fully clamped
else if (tiltCos >= freeCos)
w = 0; // no clamp
else w = (freeCos - tiltCos) / (freeCos - hardCos);
// Ease it
w = Math.pow(w, this._2DPlaneClampRampPower);
// Interpolate Infinity→base via 1/w (bounded):
const BIG = 1e12; // “practically infinite”
const denom = Math.max(w, 1e-6);
const effectiveMaxH = Math.min(
BIG,
this._2DPlaneMaxDrawingDistance / denom
);
// Apply the max height.
if (effectiveMaxH < BIG) {
const clampedH = Math.max(1e-8, Math.min(boxH, effectiveMaxH));
if (clampedH !== boxH) {
boxH = clampedH;
boxW = targetAspect * boxH; // keep aspect
}
}
return [boxW, boxH];
}
private _get2DPlanePosition(boxH: number): [number, number] {
if (!this._threeCamera) return [0, 0];
// Choose the plane position (anchor to bottom of screen, heading-invariant) ---
const bottomLeft = projectNDCToZ0(this._threeCamera, -1, -1);
const bottomRight = projectNDCToZ0(this._threeCamera, +1, -1);
let cx: number, cy: number;
if (bottomLeft && bottomRight) {
// Midpoint of the bottom-of-screen segment on Z=0:
const mx = 0.5 * (bottomLeft.x + bottomRight.x);
const my = 0.5 * (bottomLeft.y + bottomRight.y);
// Tangent along the bottom line (unit):
let dx = bottomRight.x - bottomLeft.x;
let dy = bottomRight.y - bottomLeft.y;
const len = Math.hypot(dx, dy) || 1;
dx /= len;
dy /= len;
// Inward normal n = +90° rotation of d in XY plane:
// d = (dx, dy) -> n = (-dy, dx)
let nx = -dy;
let ny = dx;
// Ensure n points "into the screen":
const midIn = projectNDCToZ0(this._threeCamera, 0, -0.5);
if (midIn) {
const vx = midIn.x - mx;
const vy = midIn.y - my;
if (vx * nx + vy * ny < 0) {
nx = -nx;
ny = -ny;
}
}
// Place the plane so its bottom edge lies on the bottom-of-screen line:
cx = mx + nx * (boxH * 0.5);
cy = my + ny * (boxH * 0.5);
} else {
// Fallback to the camera center projected on Z=0 if bottom line not visible:
const centerRay = projectNDCToZ0(this._threeCamera, 0, 0);
if (centerRay) {
cx = centerRay.x;
cy = centerRay.y;
} else {
// Fallback to the camera position if the center ray is not visible:
cx = this._threeCamera.position.x;
cy = this._threeCamera.position.y;
}
}
return [cx, cy];
}
updatePosition(): void {
// Update the 3D camera position and rotation.
if (this._threeCamera) {
const angle = -gdjs.toRad(this._layer.getCameraRotation());
this._threeCamera.position.x = this._layer.getCameraX();
this._threeCamera.position.y = -this._layer.getCameraY(); // scene is mirrored on Y
this._threeCamera.rotation.z = angle;
if (this._threeCamera instanceof THREE.OrthographicCamera) {
this._threeCamera.zoom = this._layer.getCameraZoom();
this._threeCamera.updateProjectionMatrix();
this._threeCamera.position.z = this._layer.getCameraZ(null);
} else {
this._threeCamera.position.z = this._layer.getCameraZ(
this._threeCamera.fov
);
}
}
let effectivePixiZoom = 1;
const angle = -gdjs.toRad(this._layer.getCameraRotation());
const angleCosValue = Math.cos(angle);
const angleSinValue = Math.sin(angle);
const zoomFactor = this._layer.getCameraZoom();
this._pixiContainer.rotation = angle;
this._pixiContainer.scale.x = zoomFactor;
this._pixiContainer.scale.y = zoomFactor;
const cosValue = Math.cos(angle);
const sinValue = Math.sin(angle);
const centerX =
this._layer.getCameraX() * zoomFactor * cosValue -
this._layer.getCameraY() * zoomFactor * sinValue;
const centerY =
this._layer.getCameraX() * zoomFactor * sinValue +
this._layer.getCameraY() * zoomFactor * cosValue;
this._pixiContainer.position.x = this._layer.getWidth() / 2 - centerX;
this._pixiContainer.position.y = this._layer.getHeight() / 2 - centerY;
// Update the 2D plane in the 3D world position, size and rotation,
// and update the 2D Pixi container position, size and rotation.
if (this._threeCamera && this._threePlaneMesh) {
const [boxW, boxH] = this._get2DPlaneSize();
if (boxW === 0 || boxH === 0) {
// No size means the 2D plane is not visible.
this._threePlaneMesh.visible = false;
} else {
this._threePlaneMesh.visible = true;
const [cx, cy] = this._get2DPlanePosition(boxH);
// Update the 2D plane size, position and rotation (so 2D remains upright).
// Plane size (geometry is 1×1).
this._threePlaneMesh.scale.set(boxW, boxH, 1);
this._threePlaneMesh.position.set(cx, -cy, 0);
this._threePlaneMesh.rotation.set(0, 0, -angle);
// Update the 2D Pixi container size and rotation to match the "zoom" (which comes from the 2D plane size)
// rotation and position.
effectivePixiZoom = this._layer.getWidth() / boxW; // == height/boxH
this._pixiContainer.scale.set(effectivePixiZoom, effectivePixiZoom);
this._pixiContainer.rotation = angle;
const followX = cx;
const followY = -cy;
const centerX2d =
followX * effectivePixiZoom * angleCosValue -
followY * effectivePixiZoom * angleSinValue;
const centerY2d =
followX * effectivePixiZoom * angleSinValue +
followY * effectivePixiZoom * angleCosValue;
this._pixiContainer.position.x =
this._layer.getWidth() / 2 - centerX2d;
this._pixiContainer.position.y =
this._layer.getHeight() / 2 - centerY2d;
}
}
// 2D only (no 3D rendering and so no 2D plane in the 3D world):
// Update the 2D Pixi container position, size and rotation.
if (!this._threeCamera || !this._threePlaneMesh) {
effectivePixiZoom = this._layer.getCameraZoom();
this._pixiContainer.rotation = angle;
this._pixiContainer.scale.x = effectivePixiZoom;
this._pixiContainer.scale.y = effectivePixiZoom;
const centerX =
this._layer.getCameraX() * effectivePixiZoom * angleCosValue -
this._layer.getCameraY() * effectivePixiZoom * angleSinValue;
const centerY =
this._layer.getCameraX() * effectivePixiZoom * angleSinValue +
this._layer.getCameraY() * effectivePixiZoom * angleCosValue;
this._pixiContainer.position.x = this._layer.getWidth() / 2 - centerX;
this._pixiContainer.position.y = this._layer.getHeight() / 2 - centerY;
}
// Pixel rounding for the Pixi rendering (be it for 2D only
// or for the 2D rendering shown in the 2D plane in the 3D world).
if (
this._layer.getRuntimeScene().getGame().getPixelsRounding() &&
(angleCosValue === 0 || angleSinValue === 0) &&
Number.isInteger(effectivePixiZoom)
(cosValue === 0 || sinValue === 0) &&
Number.isInteger(zoomFactor)
) {
// Camera rounding is important for pixel perfect games.
// Otherwise, the camera position fractional part is added to
@@ -919,12 +467,39 @@ namespace gdjs {
);
}
}
if (this._threeCamera) {
// TODO (3D) - improvement: handle camera rounding like down for PixiJS?
this._threeCamera.position.x = this._layer.getCameraX();
this._threeCamera.position.y = -this._layer.getCameraY(); // Inverted because the scene is mirrored on Y axis.
this._threeCamera.rotation.z = angle;
if (this._threeCamera instanceof THREE.OrthographicCamera) {
this._threeCamera.zoom = this._layer.getCameraZoom();
this._threeCamera.updateProjectionMatrix();
this._threeCamera.position.z = this._layer.getCameraZ(null);
} else {
this._threeCamera.position.z = this._layer.getCameraZ(
this._threeCamera.fov
);
}
if (this._threePlaneMesh) {
// Adapt the plane size so that it covers the whole screen.
this._threePlaneMesh.scale.x = this._layer.getWidth() / zoomFactor;
this._threePlaneMesh.scale.y = this._layer.getHeight() / zoomFactor;
// Adapt the plane position so that it's always displayed on the whole screen.
this._threePlaneMesh.position.x = this._threeCamera.position.x;
this._threePlaneMesh.position.y = -this._threeCamera.position.y; // Inverted because the scene is mirrored on Y axis.
this._threePlaneMesh.rotation.z = -angle;
}
}
}
updateResolution() {
if (this._threeEffectComposer) {
const game = this._layer.getRuntimeScene().getGame();
this._threeEffectComposer.setPixelRatio(window.devicePixelRatio);
this._threeEffectComposer.setSize(
game.getGameResolutionWidth(),
game.getGameResolutionHeight()

View File

@@ -309,7 +309,7 @@ namespace gdjs {
}
unloadResource(resourceData: ResourceData): void {
const loadedFont = this._loadedFontsData.getFromName(resourceData.name);
const loadedFont = this._loadedFontsData.get(resourceData);
if (loadedFont) {
this._loadedFontsData.delete(resourceData);
}

View File

@@ -56,11 +56,6 @@ namespace gdjs {
*/
private _loadedThreeTextures: Hashtable<THREE.Texture>;
private _loadedThreeMaterials = new ThreeMaterialCache();
private _loadedThreeCubeTextures = new Map<string, THREE.CubeTexture>();
private _loadedThreeCubeTextureKeysByResourceName = new ArrayMap<
string,
string
>();
private _diskTextures = new Map<float, PIXI.Texture>();
private _rectangleTextures = new Map<string, PIXI.Texture>();
@@ -103,10 +98,6 @@ namespace gdjs {
if (!existingTexture) {
return this._invalidTexture;
}
if (existingTexture.destroyed) {
logger.error('Texture for ' + resourceName + ' is not valid anymore.');
return this._invalidTexture;
}
if (!existingTexture.valid) {
logger.error(
'Texture for ' +
@@ -190,25 +181,7 @@ namespace gdjs {
if (loadedThreeTexture) {
return loadedThreeTexture;
}
const image = this._getImageSource(resourceName);
const threeTexture = new THREE.Texture(image);
threeTexture.magFilter = THREE.LinearFilter;
threeTexture.minFilter = THREE.LinearFilter;
threeTexture.wrapS = THREE.RepeatWrapping;
threeTexture.wrapT = THREE.RepeatWrapping;
threeTexture.colorSpace = THREE.SRGBColorSpace;
threeTexture.needsUpdate = true;
const resource = this._getImageResource(resourceName);
applyThreeTextureSettings(threeTexture, resource);
this._loadedThreeTextures.put(resourceName, threeTexture);
return threeTexture;
}
private _getImageSource(resourceName: string): HTMLImageElement {
// Texture is not loaded, load it now from the PixiJS texture.
// TODO (3D) - optimization: don't load the PixiJS Texture if not used by PixiJS.
// TODO (3D) - optimization: Ideally we could even share the same WebGL texture.
@@ -225,86 +198,21 @@ namespace gdjs {
`Can't load texture for resource "${resourceName}" as it's not an image.`
);
}
return image;
}
/**
* Return the three.js texture associated to the specified resource name.
* Returns a placeholder texture if not found.
* @param xPositiveResourceName The name of the resource
* @returns The requested cube texture, or a placeholder if not found.
*/
getThreeCubeTexture(
xPositiveResourceName: string,
xNegativeResourceName: string,
yPositiveResourceName: string,
yNegativeResourceName: string,
zPositiveResourceName: string,
zNegativeResourceName: string
): THREE.CubeTexture {
const key =
xPositiveResourceName +
'|' +
xNegativeResourceName +
'|' +
yPositiveResourceName +
'|' +
yNegativeResourceName +
'|' +
zPositiveResourceName +
'|' +
zNegativeResourceName;
const loadedThreeTexture = this._loadedThreeCubeTextures.get(key);
if (loadedThreeTexture) {
return loadedThreeTexture;
}
const threeTexture = new THREE.Texture(image);
threeTexture.magFilter = THREE.LinearFilter;
threeTexture.minFilter = THREE.LinearFilter;
threeTexture.wrapS = THREE.RepeatWrapping;
threeTexture.wrapT = THREE.RepeatWrapping;
threeTexture.colorSpace = THREE.SRGBColorSpace;
threeTexture.needsUpdate = true;
const cubeTexture = new THREE.CubeTexture();
// Faces on X axis need to be swapped.
cubeTexture.images[0] = this._getImageSource(xNegativeResourceName);
cubeTexture.images[1] = this._getImageSource(xPositiveResourceName);
// Faces on Y keep the same order.
cubeTexture.images[2] = this._getImageSource(yPositiveResourceName);
cubeTexture.images[3] = this._getImageSource(yNegativeResourceName);
// Faces on Z keep the same order.
cubeTexture.images[4] = this._getImageSource(zPositiveResourceName);
cubeTexture.images[5] = this._getImageSource(zNegativeResourceName);
// The images also need to be mirrored horizontally by users.
const resource = this._getImageResource(resourceName);
cubeTexture.magFilter = THREE.LinearFilter;
cubeTexture.minFilter = THREE.LinearFilter;
cubeTexture.colorSpace = THREE.SRGBColorSpace;
cubeTexture.needsUpdate = true;
applyThreeTextureSettings(threeTexture, resource);
this._loadedThreeTextures.put(resourceName, threeTexture);
const resource = this._getImageResource(xPositiveResourceName);
applyThreeTextureSettings(cubeTexture, resource);
this._loadedThreeCubeTextures.set(key, cubeTexture);
this._loadedThreeCubeTextureKeysByResourceName.add(
xPositiveResourceName,
key
);
this._loadedThreeCubeTextureKeysByResourceName.add(
xNegativeResourceName,
key
);
this._loadedThreeCubeTextureKeysByResourceName.add(
yPositiveResourceName,
key
);
this._loadedThreeCubeTextureKeysByResourceName.add(
yNegativeResourceName,
key
);
this._loadedThreeCubeTextureKeysByResourceName.add(
zPositiveResourceName,
key
);
this._loadedThreeCubeTextureKeysByResourceName.add(
zNegativeResourceName,
key
);
return cubeTexture;
return threeTexture;
}
/**
@@ -574,11 +482,6 @@ namespace gdjs {
for (const threeTexture of threeTextures) {
threeTexture.dispose();
}
for (const cubeTexture of this._loadedThreeCubeTextures.values()) {
cubeTexture.dispose();
}
this._loadedThreeCubeTextures.clear();
this._loadedThreeCubeTextureKeysByResourceName.clear();
this._loadedThreeMaterials.disposeAll();
@@ -625,51 +528,12 @@ namespace gdjs {
}
this._loadedThreeMaterials.dispose(resourceName);
const cubeTextureKeys =
this._loadedThreeCubeTextureKeysByResourceName.getValuesFor(
resourceName
);
if (cubeTextureKeys) {
for (const cubeTextureKey of cubeTextureKeys) {
const cubeTexture = this._loadedThreeCubeTextures.get(cubeTextureKey);
if (cubeTexture) {
cubeTexture.dispose();
this._loadedThreeCubeTextures.delete(cubeTextureKey);
}
}
}
}
}
class ArrayMap<K, V> {
map = new Map<K, Array<V>>();
getValuesFor(key: K): Array<V> | undefined {
return this.map.get(key);
}
add(key: K, value: V): void {
let values = this.map.get(key);
if (!values) {
values = [];
this.map.set(key, values);
}
values.push(value);
}
deleteValuesFor(key: K): void {
this.map.delete(key);
}
clear(): void {
this.map.clear();
}
}
class ThreeMaterialCache {
private _flaggedMaterials = new Map<string, THREE.Material>();
private _materialFlaggedKeys = new ArrayMap<string, string>();
private _materialFlaggedKeys = new Map<string, Array<string>>();
/**
* Return the three.js material associated to the specified resource name
@@ -720,7 +584,12 @@ namespace gdjs {
forceBasicMaterial ? 1 : 0
}|${vertexColors ? 1 : 0}`;
this._flaggedMaterials.set(cacheKey, material);
this._materialFlaggedKeys.add(resourceName, cacheKey);
let flaggedKeys = this._materialFlaggedKeys.get(resourceName);
if (!flaggedKeys) {
flaggedKeys = [];
this._materialFlaggedKeys.set(resourceName, flaggedKeys);
}
flaggedKeys.push(cacheKey);
}
/**
@@ -729,7 +598,7 @@ namespace gdjs {
* @param resourceName The name of the resource
*/
dispose(resourceName: string): void {
const flaggedKeys = this._materialFlaggedKeys.getValuesFor(resourceName);
const flaggedKeys = this._materialFlaggedKeys.get(resourceName);
if (flaggedKeys) {
for (const flaggedKey of flaggedKeys) {
const threeMaterial = this._flaggedMaterials.get(flaggedKey);
@@ -739,7 +608,7 @@ namespace gdjs {
this._flaggedMaterials.delete(flaggedKey);
}
}
this._materialFlaggedKeys.deleteValuesFor(resourceName);
this._materialFlaggedKeys.delete(resourceName);
}
/**

View File

@@ -101,7 +101,6 @@ namespace gdjs {
this._threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
this._threeRenderer.useLegacyLights = true;
this._threeRenderer.autoClear = false;
this._threeRenderer.pixelRatio = window.devicePixelRatio;
this._threeRenderer.setSize(
this._game.getGameResolutionWidth(),
this._game.getGameResolutionHeight()
@@ -658,14 +657,6 @@ namespace gdjs {
e.preventDefault();
}
if (e.repeat) {
// If `repeat` is true, this is not the first press of the key.
// We only communicate the changes of states ("first" key down, key up)
// to the manager, which then tracks the state of the key:
// pressed, just pressed or released.
return;
}
manager.onKeyPressed(e.keyCode, e.location);
};
document.onkeyup = function (e) {
@@ -762,7 +753,7 @@ namespace gdjs {
};
// @ts-ignore
canvas.onwheel = function (event) {
manager.onMouseWheel(-event.deltaY, event.deltaX, event.deltaZ);
manager.onMouseWheel(-event.deltaY);
};
// Touches:
@@ -785,7 +776,6 @@ namespace gdjs {
touch.pageY
);
manager.onTouchMove(touch.identifier, pos[0], pos[1]);
manager.onTouchMove(touch.identifier, pos[0], pos[1]);
// This works because touch events are sent
// when they continue outside of the canvas.
if (manager.isSimulatingMouseWithTouch()) {

View File

@@ -18,7 +18,6 @@ namespace gdjs {
rendered2DLayersCount: 0,
rendered3DLayersCount: 0,
};
private _backgroundColor: THREE.Color | null = null;
constructor(
runtimeScene: gdjs.RuntimeScene,
@@ -115,10 +114,9 @@ namespace gdjs {
const runtimeLayerRenderingType = runtimeLayer.getRenderingType();
const layerHas3DObjectsToRender = runtimeLayerRenderer.has3DObjects();
if (
!this._runtimeScene.getGame().isInGameEdition() &&
(runtimeLayerRenderingType ===
runtimeLayerRenderingType ===
gdjs.RuntimeLayerRenderingType.TWO_D ||
!layerHas3DObjectsToRender)
!layerHas3DObjectsToRender
) {
// Render a layer with 2D rendering (PixiJS) only if layer is configured as is
// or if there is no 3D object to render.
@@ -211,23 +209,15 @@ namespace gdjs {
);
threeRenderer.resetState();
if (this._runtimeScene.getClearCanvas()) threeRenderer.clear();
if (!this._backgroundColor) {
this._backgroundColor = new THREE.Color();
}
this._backgroundColor.set(
threeScene.background = new THREE.Color(
this._runtimeScene.getBackgroundColor()
);
if (!threeScene.background) {
threeScene.background = this._backgroundColor;
}
isFirstRender = false;
} else {
// It's important to set the background to null, as maybe the first rendered
// layer has changed and so the Three.js scene background must be reset.
if (threeScene.background === this._backgroundColor) {
threeScene.background = null;
}
threeScene.background = null;
}
// Clear the depth as each layer is independent and display on top of the previous one,

View File

@@ -41,71 +41,18 @@ namespace gdjs {
return supportedCompressionMethods;
};
/**
* The desired status of the game, used for previews or in-game edition.
* Either stored in the options generated by the preview or in the URL
* in case of a hard reload.
*/
export type RuntimeGameStatus = {
isPaused: boolean;
isInGameEdition: boolean;
sceneName: string | null;
injectedExternalLayoutName: string | null;
skipCreatingInstancesFromScene: boolean;
eventsBasedObjectType: string | null;
eventsBasedObjectVariantName: string | null;
editorId: string | null;
editorCamera3D?: EditorCameraState;
};
/**
* Read the desired status of the game from the URL. Only useful for previews
* when hard reloaded.
*/
const readRuntimeGameStatusFromUrl = (): RuntimeGameStatus | null => {
try {
const url = new URL(location.href);
const runtimeGameStatus = url.searchParams.get('runtimeGameStatus');
if (!runtimeGameStatus) return null;
const parsedRuntimeGameStatus = JSON.parse(runtimeGameStatus);
return {
isPaused: !!parsedRuntimeGameStatus.isPaused,
isInGameEdition: !!parsedRuntimeGameStatus.isInGameEdition,
sceneName: '' + parsedRuntimeGameStatus.sceneName,
injectedExternalLayoutName:
'' + parsedRuntimeGameStatus.injectedExternalLayoutName,
skipCreatingInstancesFromScene:
!!parsedRuntimeGameStatus.skipCreatingInstancesFromScene,
eventsBasedObjectType: parsedRuntimeGameStatus.eventsBasedObjectType,
eventsBasedObjectVariantName:
parsedRuntimeGameStatus.eventsBasedObjectVariantName,
editorId: parsedRuntimeGameStatus.editorId,
editorCamera3D: parsedRuntimeGameStatus.editorCamera3D,
};
} catch (e) {
return null;
}
};
/** Options given to the game at startup. */
export type RuntimeGameOptions = {
/** if true, force fullscreen. */
forceFullscreen?: boolean;
/** if true, game is run as a preview launched from an editor. */
isPreview?: boolean;
/** if set, the status of the game to be restored. */
initialRuntimeGameStatus?: RuntimeGameStatus;
/** The name of the external layout to create in the scene at position 0;0. */
injectExternalLayout?: string;
/** Script files, used for hot-reloading. */
scriptFiles?: Array<RuntimeGameOptionsScriptFile>;
/** if true, export is a partial preview without reloading libraries. */
shouldReloadLibraries?: boolean;
/** if true, export is a partial preview without generating events. */
shouldGenerateScenesEventsCode?: boolean;
/** if true, export is a partial preview without events. */
projectDataOnlyExport?: boolean;
/** if true, preview is launched from GDevelop native mobile app. */
nativeMobileApp?: boolean;
/** The address of the debugger server, to reach out using WebSocket. */
@@ -192,13 +139,7 @@ namespace gdjs {
_gameResolutionHeight: integer;
_originalWidth: float;
_originalHeight: float;
_resizeMode:
| ''
| 'scaleOuter'
| 'adaptWidth'
| 'adaptHeight'
| 'native'
| string;
_resizeMode: 'adaptWidth' | 'adaptHeight' | string;
_adaptGameResolutionAtRuntime: boolean;
_scaleMode: 'linear' | 'nearest';
_pixelsRounding: boolean;
@@ -230,8 +171,12 @@ namespace gdjs {
_hasJustResumed: boolean = false;
//Inputs :
private _inputManager: InputManager;
_inputManager: InputManager;
/**
* Allow to specify an external layout to insert in the first scene.
*/
_injectExternalLayout: any;
_options: RuntimeGameOptions;
/**
@@ -249,7 +194,6 @@ namespace gdjs {
_sessionMetricsInitialized: boolean = false;
_disableMetrics: boolean = false;
_isPreview: boolean;
_isInGameEdition: boolean;
/**
* The capture manager, used to manage captures (screenshots, videos, etc...).
@@ -259,27 +203,12 @@ namespace gdjs {
/** True if the RuntimeGame has been disposed and should not be used anymore. */
_wasDisposed: boolean = false;
_inGameEditor: InGameEditor | null;
/**
* @param data The object (usually stored in data.json) containing the full project data
* @param options The game options
*/
constructor(data: ProjectData, options?: RuntimeGameOptions) {
this._options = options || {};
this._isPreview = this._options.isPreview || false;
if (this._isPreview) {
// Check if we need to restore the state from the URL, which is used
// when a preview is hard reloaded (search for `hardReload`).
const runtimeGameStatusFromUrl = readRuntimeGameStatusFromUrl();
if (runtimeGameStatusFromUrl) {
this._options.initialRuntimeGameStatus = runtimeGameStatusFromUrl;
}
}
this._isInGameEdition =
this._options.initialRuntimeGameStatus?.isInGameEdition || false;
this._variables = new gdjs.VariablesContainer(data.variables);
this._variablesByExtensionName = new Map<
string,
@@ -308,12 +237,7 @@ namespace gdjs {
getGlobalResourceNames(data),
data.layouts
);
this._inGameEditor = this._isInGameEdition
? new gdjs.InGameEditor(this, data)
: null;
this._debuggerClient = gdjs.DebuggerClient
? new gdjs.DebuggerClient(this)
: null;
this._effectsManager = new gdjs.EffectsManager();
this._maxFPS = this._data.properties.maxFPS;
this._minFPS = this._data.properties.minFPS;
@@ -341,12 +265,17 @@ namespace gdjs {
);
this._sceneStack = new gdjs.SceneStack(this);
this._inputManager = new gdjs.InputManager();
this._injectExternalLayout = this._options.injectExternalLayout || '';
this._debuggerClient = gdjs.DebuggerClient
? new gdjs.DebuggerClient(this)
: null;
this._captureManager = gdjs.CaptureManager
? new gdjs.CaptureManager(
this._renderer,
this._options.captureOptions || {}
)
: null;
this._isPreview = this._options.isPreview || false;
this._sessionId = null;
this._playerId = null;
@@ -382,9 +311,6 @@ namespace gdjs {
* @param projectData The object (usually stored in data.json) containing the full project data
*/
setProjectData(projectData: ProjectData): void {
if (this._inGameEditor) {
this._inGameEditor.onProjectDataChange(projectData);
}
this._data = projectData;
this._updateSceneAndExtensionsData();
this._resourcesLoader.setResources(
@@ -559,55 +485,6 @@ namespace gdjs {
return eventsBasedObjectData;
}
getEventsBasedObjectVariantData(
type: string,
variantName: string
): EventsBasedObjectVariantData | null {
const eventsBasedObjectData = this.getEventsBasedObjectData(type);
if (!eventsBasedObjectData) {
return null;
}
return gdjs.RuntimeGame._getEventsBasedObjectVariantData(
eventsBasedObjectData,
variantName
);
}
static _getEventsBasedObjectVariantData(
eventsBasedObjectData: EventsBasedObjectData,
variantName: string
): EventsBasedObjectVariantData {
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;
if (isForcedToOverrideEventsBasedObjectChildrenConfiguration) {
return eventsBasedObjectData.defaultVariant;
}
let usedVariantData: EventsBasedObjectVariantData =
eventsBasedObjectData.defaultVariant;
for (
let variantIndex = 0;
variantIndex < eventsBasedObjectData.variants.length;
variantIndex++
) {
const variantData = eventsBasedObjectData.variants[variantIndex];
if (variantData.name === variantName) {
usedVariantData = variantData;
}
}
return usedVariantData;
}
/**
* Get the data associated to a scene.
*
@@ -646,22 +523,6 @@ namespace gdjs {
return false;
}
/**
* Get the data associated to a scene.
*
* @param name The name of the scene.
* @return The data associated to the scene or null if not found.
*/
getSceneData(sceneName: string): LayoutData | null {
for (let i = 0, len = this._data.layouts.length; i < len; ++i) {
const sceneData = this._data.layouts[i];
if (sceneData.name == sceneName) {
return sceneData;
}
}
return null;
}
/**
* Get the data associated to an external layout.
*
@@ -733,7 +594,7 @@ namespace gdjs {
this._gameResolutionWidth = width;
this._gameResolutionHeight = height;
if (this._adaptGameResolutionAtRuntime || this._isInGameEdition) {
if (this._adaptGameResolutionAtRuntime) {
if (
gdjs.RuntimeGameRenderer &&
gdjs.RuntimeGameRenderer.getWindowInnerWidth &&
@@ -745,10 +606,7 @@ namespace gdjs {
gdjs.RuntimeGameRenderer.getWindowInnerHeight();
// Enlarge either the width or the eight to fill the inner window space.
if (this._isInGameEdition) {
this._gameResolutionWidth = windowInnerWidth;
this._gameResolutionHeight = windowInnerHeight;
} else if (this._resizeMode === 'adaptWidth') {
if (this._resizeMode === 'adaptWidth') {
this._gameResolutionWidth =
(this._gameResolutionHeight * windowInnerWidth) /
windowInnerHeight;
@@ -877,9 +735,9 @@ namespace gdjs {
if (this._paused === enable) return;
this._paused = enable;
if (this._inGameEditor) this._inGameEditor.activate(enable);
if (this._debuggerClient) {
this._debuggerClient.sendRuntimeGameStatus();
if (this._paused) this._debuggerClient.sendGamePaused();
else this._debuggerClient.sendGameResumed();
}
}
@@ -1056,16 +914,11 @@ namespace gdjs {
await loadAssets(onProgress);
await loadingScreen.unload();
if (!this._isInGameEdition) {
this.pause(false);
}
this.pause(false);
}
private _getFirstSceneName(): string {
const firstSceneName =
this._options.initialRuntimeGameStatus?.sceneName ||
this._data.firstLayout;
const firstSceneName = this._data.firstLayout;
return this.hasScene(firstSceneName)
? firstSceneName
: // There is always at least a scene
@@ -1085,41 +938,10 @@ namespace gdjs {
this._forceGameResolutionUpdate();
// Load the first scene
const sceneName = this._getFirstSceneName();
const externalLayoutName =
this._options.initialRuntimeGameStatus?.injectedExternalLayoutName ||
null;
if (this._inGameEditor) {
const eventsBasedObjectType =
this._options.initialRuntimeGameStatus?.eventsBasedObjectType ||
null;
const eventsBasedObjectVariantName =
this._options.initialRuntimeGameStatus
?.eventsBasedObjectVariantName || null;
const editorId =
this._options.initialRuntimeGameStatus?.editorId || null;
const editorCamera3D =
this._options.initialRuntimeGameStatus?.editorCamera3D || null;
this._inGameEditor.switchToSceneOrVariant(
editorId,
sceneName,
externalLayoutName,
eventsBasedObjectType,
eventsBasedObjectVariantName,
editorCamera3D
);
} else {
if (sceneName) {
this.getSceneStack().replace({
sceneName,
externalLayoutName:
externalLayoutName === null ? undefined : externalLayoutName,
skipCreatingInstancesFromScene: !!externalLayoutName,
clear: true,
});
}
}
this._sceneStack.push(
this._getFirstSceneName(),
this._injectExternalLayout
);
this._watermark.displayAtStartup();
//Uncomment to profile the first x frames of the game.
@@ -1145,33 +967,15 @@ namespace gdjs {
}
// The standard game loop
let lastFrameSceneName: string | null = null;
let accumulatedElapsedTime = 0;
this._hasJustResumed = false;
this._renderer.startGameLoop((lastCallElapsedTime) => {
try {
// Watch the scene name to automatically update debugger when a scene is changed.
if (this._debuggerClient) {
const currentScene = (
this._inGameEditor || this.getSceneStack()
).getCurrentScene();
if (
currentScene &&
currentScene.getName() !== lastFrameSceneName
) {
lastFrameSceneName = currentScene.getName();
this._debuggerClient.sendRuntimeGameStatus();
}
if (this._paused) {
return true;
}
// If the game is edited, update the target framerate according to interactions.
// Do it now (before frame skip), so that if a user interaction happens
// we don't wait for a frame to pass at the current, probably very slow framerate.
if (this._paused && this._inGameEditor) {
this._inGameEditor.updateTargetFramerate(lastCallElapsedTime);
}
// Skip the frame if we rendering frames too fast.
// Skip the frame if we rendering frames too fast
accumulatedElapsedTime += lastCallElapsedTime;
if (
this._maxFPS > 0 &&
@@ -1188,36 +992,17 @@ namespace gdjs {
// Manage resize events.
if (this._notifyScenesForGameResolutionResize) {
if (this._inGameEditor) {
this._inGameEditor.onGameResolutionResized();
} else {
this._sceneStack.onGameResolutionResized();
}
this._sceneStack.onGameResolutionResized();
this._notifyScenesForGameResolutionResize = false;
}
// Render and possibly step the game.
if (this._paused) {
if (this._inGameEditor) {
// The game is paused for edition: the in-game editor runs and render
// the scene.
this._inGameEditor.updateAndRender();
} else {
// The game is paused (for debugging): the rendering of the scene is done,
// but the game logic is not executed (no full "step").
this._sceneStack.renderWithoutStep();
}
} else {
// The game is not paused (and so, not edited): both the rendering
// and game logic (a full "step") is executed.
if (!this._sceneStack.step(elapsedTime)) {
return false; // Return if game asked to be stopped.
}
// Render and step the scene.
if (this._sceneStack.step(elapsedTime)) {
this.getInputManager().onFrameEnded();
this._hasJustResumed = false;
return true;
}
this.getInputManager().onFrameEnded();
return true;
return false;
} catch (e) {
if (this._debuggerClient)
this._debuggerClient.onUncaughtException(e);
@@ -1538,37 +1323,6 @@ namespace gdjs {
return this._isPreview;
}
/**
* Check if the game loop is paused, for debugging/edition purposes.
* @returns true if the current game is paused
*/
isPaused(): boolean {
return this._paused;
}
/**
* Check if the game should display in-game edition tools or not.
* @returns true if the current game is being edited.
*/
isInGameEdition(): boolean {
return this._isInGameEdition;
}
/**
* Return in-game editor.
*/
getInGameEditor(): InGameEditor | null {
return this._inGameEditor;
}
/**
* Set the maximum FPS of the game.
* @param maximumFps The maximum FPS.
*/
setMaximumFps(maximumFps: integer) {
this._maxFPS = maximumFps;
}
/**
* Check if the game should call GDevelop development APIs or not.
*

View File

@@ -1380,22 +1380,6 @@ namespace gdjs {
return this.hidden;
}
/**
* Return the width of the object before any custom size is applied.
* @return The width of the object
*/
getOriginalWidth(): float {
return this.getWidth();
}
/**
* Return the width of the object before any custom size is applied.
* @return The width of the object
*/
getOriginalHeight(): float {
return this.getHeight();
}
/**
* Set the width of the object, if applicable.
* @param width The new width in pixels.

Some files were not shown because too many files have changed in this diff Show More