Compare commits

...

24 Commits

Author SHA1 Message Date
Davy Hélard
8a55078119 Fix types. 2023-11-24 15:57:08 +01:00
Davy Hélard
4417d7e958 Add some comments. 2023-11-24 15:55:06 +01:00
Davy Hélard
79cd327c68 Add tests. 2023-11-24 15:49:43 +01:00
Davy Hélard
c3580fd207 Make culling more efficient 2023-11-24 12:51:26 +01:00
Florian Rival
d34f1a654f Improve navigation for extensions in the documentation (#5950) 2023-11-24 11:43:47 +01:00
D8H
5abc74b66b Remove only (#5946)
Don't show in changelog
2023-11-23 19:05:08 +01:00
Clément Pasteau
a848764318 Fix libGD.wasm not properly loaded on Electron local + build warnings (#5942)
Don't show in changelog
2023-11-23 15:55:31 +01:00
github-actions[bot]
c0c6fddcbb Update translations [skip ci] (#5937)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-11-23 09:49:51 +01:00
AlexandreS
95ac26f05d Add placeholder in menu when no recent project file (#5940) 2023-11-23 09:28:33 +01:00
D8H
1f852648ef Make games launch faster by loading resources in the background (#5572)
* Only the first scene and global objects resources (images, sounds, 3D models etc...) will be downloaded during launch of the game. This usually allows for a very fast loading time.
* Other scenes resources will continue to load in the background. It has no impact on the game performance as this is done on other threads by the browser or the engine running the game.
* Scenes are loaded in the order they are listed in the project manager.
* You can also use actions and expressions to prioritize a scene (if it's known that a level will be needed soon for example) or read the current loading progress. This allows to create lightweight scenes that can act as custom loading screens. Otherwise, the launch loading screen will be shown if a scene is still loading when launched.
* Read more about this on https://wiki.gdevelop.io/gdevelop5/all-features/resources-loading/.
2023-11-22 22:51:24 +01:00
Florian Rival
b7da4361c3 Fix some C++ warnings and improve GDevelop.js README
Don't show in changelog
2023-11-22 19:03:21 +01:00
Arthur Pacaud (arthuro555)
71b369d40e Update Emscripten version to 3.1.21 (#5636)
* Any developer working on the C++ codebase should follow again the [README](https://github.com/4ian/GDevelop/tree/master/GDevelop.js) to install latest Emscripten version and re-compile C++.

Only show in developer changelog
2023-11-22 17:19:13 +01:00
AlexandreS
4d8cf56922 Do not change homepage tab at opening if an item from the asset store is requested (#5936) 2023-11-22 16:29:44 +01:00
github-actions[bot]
1a6e0ba5a1 Update translations [skip ci] (#5918)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-11-22 14:04:18 +01:00
AlexandreS
ec1ebcbf5b Fix desktop app not opening on Mac (#5934)
Don't show in changelog
2023-11-22 11:48:07 +01:00
Arthur Pacaud (arthuro555)
4ee9ccd7a9 Allow using resources as behavior properties (#5256)
Only show in developer changelog
2023-11-22 11:17:35 +01:00
AlexandreS
1ac248bfa4 Replace parcel watcher with chokidar (#5932) 2023-11-22 09:35:29 +01:00
D8H
65b78d4db7 Fix duplication of the "create" action in the search results (#5930) 2023-11-21 18:14:35 +01:00
D8H
639d90d743 Move deprecated physics actions at the bottom of search results (#5925) 2023-11-21 18:13:38 +01:00
D8H
45d0a78656 Simplify the wording of some actions names (#5929) 2023-11-21 18:12:09 +01:00
Florian Rival
0a0811e355 Fix crash with 'Put the object around another' action when target object was not existing (#5931) 2023-11-21 17:11:37 +01:00
D8H
a8f9df3dac Use descriptions to search actions and conditions (#5928) 2023-11-21 10:36:54 +01:00
Clément Pasteau
35db56d778 Fix Debugger sometimes crashing (#5926)
* Remove pixi effects renderer from the debugger payload, which was causing it to fail.
2023-11-20 16:06:41 +01:00
D8H
27efe8e3dd Improving the grouping of some behaviors actions and conditions (#5923) 2023-11-19 16:13:19 +01:00
188 changed files with 5552 additions and 3275 deletions

View File

@@ -30,7 +30,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# GDevelop.js dependencies
- restore_cache:
@@ -107,7 +107,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
- run:
name: Install system dependencies for Electron builder
@@ -127,7 +127,8 @@ jobs:
# Build GDevelop.js (and run tests to ensure it works)
- run:
name: Build GDevelop.js
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test && cd ..
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
# GDevelop IDE dependencies (after building GDevelop.js to avoid downloading a pre-built version)
- run:
@@ -184,7 +185,7 @@ jobs:
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# GDevelop.js dependencies
- restore_cache:
@@ -200,7 +201,8 @@ jobs:
# Build GDevelop.js (and run tests to ensure it works)
- run:
name: Build GDevelop.js
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test && cd ..
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
- save_cache:
paths:

View File

@@ -14,7 +14,7 @@ tasks:
init: |
sudo apt-get update
sudo apt install cmake python-is-python3 python3-distutils -y
git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
cd GDevelop.js
npm install
source ../emsdk/emsdk_env.sh && npm run build -- --dev

View File

@@ -39,7 +39,7 @@ install:
- cd ..
# Install Emscripten (for GDevelop.js)
- git clone https://github.com/juj/emsdk.git
- cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
- cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# Install GDevelop.js dependencies
- cd GDevelop.js && npm install && cd ..
# Build GDevelop.js

View File

@@ -113,7 +113,8 @@
"memory_resource": "cpp",
"__bits": "cpp",
"__verbose_abort": "cpp",
"variant": "cpp"
"variant": "cpp",
"charconv": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

View File

@@ -164,7 +164,7 @@ void LinkEvent::UnserializeFrom(gd::Project& project,
}
bool LinkEvent::AcceptVisitor(gd::EventVisitor &eventVisitor) {
return BaseEvent::AcceptVisitor(eventVisitor) |
return BaseEvent::AcceptVisitor(eventVisitor) ||
eventVisitor.VisitLinkEvent(*this);
}

View File

@@ -491,11 +491,6 @@ class GD_CORE_API ExpressionParser2 {
std::vector<std::unique_ptr<ExpressionNode>> parameters;
gd::String lastObjectName = "";
// By convention, object is always the first parameter, and behavior the
// second one.
size_t parameterIndex =
WrittenParametersFirstIndex(objectName, behaviorName);
bool previousCharacterIsParameterSeparator = false;
while (!IsEndReached()) {
SkipAllWhitespaces();
@@ -514,7 +509,6 @@ class GD_CORE_API ExpressionParser2 {
SkipAllWhitespaces();
previousCharacterIsParameterSeparator = CheckIfChar(IsParameterSeparator);
SkipIfChar(IsParameterSeparator);
parameterIndex++;
}
ExpressionParserLocation invalidClosingParenthesisLocation;

View File

@@ -548,7 +548,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddAction(
"ChangeLayerTimeScale",
_("Change layer time scale"),
_("Layer time scale"),
_("Change the time scale applied to the objects of the layer."),
_("Set the time scale of layer _PARAM1_ to _PARAM2_"),
"",
@@ -594,7 +594,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddAction(
"SetLayerAmbientLightColor",
_("Set the ambient light color"),
_("Ambient light color"),
_("Set the ambient light color of the lighting layer in format "
"\"R;G;B\" string."),
_("Set the ambient color of the lighting layer _PARAM1_ to _PARAM2_"),

View File

@@ -44,7 +44,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"number",
"Index",
_("Animation (by number)"),
_("the number of the animation played by the object (the number from "
_("the animation played by the object using the animation number (from "
"the animations list)"),
_("the number of the animation"),
_("Animations and images"),

View File

@@ -5,6 +5,7 @@
*/
#include "AllBuiltinExtensions.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
using namespace std;
namespace gd {
@@ -57,7 +58,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
extension
.AddCondition("DoesSceneExist",
_("Does scene exist"),
_("Check if scene exists."),
_("Check if a scene exists."),
_("Scene _PARAM1_ exists"),
"",
"res/actions/texte.png",
@@ -163,6 +164,45 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
"res/actions/window.png")
.SetHelpPath("/interface/scene-editor/events")
.AddCodeOnlyParameter("currentScene", "");
extension
.AddAction("PrioritizeLoadingOfScene",
_("Preload scene"),
_("Preload a scene resources as soon as possible in background."),
_("Preload scene _PARAM1_ in background"),
"",
"res/actions/replaceScene24.png",
"res/actions/replaceScene.png")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Name of the new scene"))
.MarkAsAdvanced();
extension.AddExpressionAndCondition("number",
"SceneLoadingProgress",
_("Scene loading progress"),
_("The progress of resources loading in background for a scene (between 0 and 1)."),
_("_PARAM0_ loading progress"),
_(""),
"res/actions/replaceScene24.png")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
.UseStandardParameters("number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
extension
.AddCondition("AreSceneAssetsLoaded",
_("Scene preloaded"),
_("Check if scene resources have finished to load in background."),
_("Scene _PARAM1_ was preloaded in background"),
"",
"res/actions/replaceScene24.png",
"res/actions/replaceScene.png")
.SetHelpPath("/all-features/resources-loading")
.AddCodeOnlyParameter("currentScene", "")
.AddParameter("sceneName", _("Scene name"))
.MarkAsAdvanced();
}
} // namespace gd

View File

@@ -87,8 +87,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsWindowExtension(
extension
.AddAction(
"SetWindowSize",
_("Change the size of the game window"),
_("This action changes the size of the game window. Note that this "
_("Game window size"),
_("Changes the size of the game window. Note that this "
"will only work on platform supporting this operation: games "
"running in browsers or on mobile phones can not update their "
"window size. Game resolution can still be updated."),

View File

@@ -191,6 +191,16 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata
return *this;
}
/**
* \see gd::InstructionMetadata::SetHelpPath
*/
MultipleInstructionMetadata &SetHelpPath(const gd::String &path) {
if (expression) expression->SetHelpPath(path);
if (condition) condition->SetHelpPath(path);
if (action) action->SetHelpPath(path);
return *this;
}
/**
* \see gd::InstructionMetadata::MarkAsSimple
*/

View File

@@ -4,7 +4,6 @@
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#include "DependenciesAnalyzer.h"
#include <algorithm>
#include "GDCore/Events/Builtin/LinkEvent.h"
@@ -29,9 +28,9 @@ DependenciesAnalyzer::DependenciesAnalyzer(const gd::Project& project_,
bool DependenciesAnalyzer::Analyze() {
if (layout)
return Analyze(layout->GetEvents(), true);
return Analyze(layout->GetEvents());
else if (externalEvents)
return Analyze(externalEvents->GetEvents(), true);
return Analyze(externalEvents->GetEvents());
std::cout << "ERROR: DependenciesAnalyzer called without any layout or "
"external events.";
@@ -40,63 +39,38 @@ bool DependenciesAnalyzer::Analyze() {
DependenciesAnalyzer::~DependenciesAnalyzer() {}
bool DependenciesAnalyzer::Analyze(const gd::EventsList& events, bool isOnTopLevel) {
bool DependenciesAnalyzer::Analyze(const gd::EventsList& events) {
for (unsigned int i = 0; i < events.size(); ++i) {
const gd::LinkEvent* linkEvent = dynamic_cast<const gd::LinkEvent*>(&events[i]);
if (linkEvent) {
DependenciesAnalyzer analyzer(*this);
gd::String linked = linkEvent->GetTarget();
if (project.HasExternalEventsNamed(linked)) {
if (std::find(parentExternalEvents.begin(),
parentExternalEvents.end(),
linked) != parentExternalEvents.end())
return false; // Circular dependency!
externalEventsDependencies.insert(
linked); // There is a direct dependency
if (!isOnTopLevel) notTopLevelExternalEventsDependencies.insert(linked);
analyzer.AddParentExternalEvents(linked);
if (!analyzer.Analyze(project.GetExternalEvents(linked).GetEvents(),
isOnTopLevel))
linked) != parentExternalEvents.end()) {
// Circular dependency!
return false;
}
bool wasDependencyJustAdded = externalEventsDependencies.insert(linked).second;
if (wasDependencyJustAdded) {
parentExternalEvents.push_back(linked);
if (!Analyze(project.GetExternalEvents(linked).GetEvents()))
return false;
parentExternalEvents.pop_back();
}
} else if (project.HasLayoutNamed(linked)) {
if (std::find(parentScenes.begin(), parentScenes.end(), linked) !=
parentScenes.end())
return false; // Circular dependency!
scenesDependencies.insert(linked); // There is a direct dependency
if (!isOnTopLevel) notTopLevelScenesDependencies.insert(linked);
analyzer.AddParentScene(linked);
if (!analyzer.Analyze(project.GetLayout(linked).GetEvents(),
isOnTopLevel))
parentScenes.end()) {
// Circular dependency!
return false;
}
// Update with indirect dependencies.
scenesDependencies.insert(analyzer.GetScenesDependencies().begin(),
analyzer.GetScenesDependencies().end());
externalEventsDependencies.insert(
analyzer.GetExternalEventsDependencies().begin(),
analyzer.GetExternalEventsDependencies().end());
sourceFilesDependencies.insert(
analyzer.GetSourceFilesDependencies().begin(),
analyzer.GetSourceFilesDependencies().end());
notTopLevelScenesDependencies.insert(
analyzer.GetNotTopLevelScenesDependencies().begin(),
analyzer.GetNotTopLevelScenesDependencies().end());
notTopLevelExternalEventsDependencies.insert(
analyzer.GetNotTopLevelExternalEventsDependencies().begin(),
analyzer.GetNotTopLevelExternalEventsDependencies().end());
if (!isOnTopLevel) {
notTopLevelScenesDependencies.insert(
analyzer.GetScenesDependencies().begin(),
analyzer.GetScenesDependencies().end());
notTopLevelExternalEventsDependencies.insert(
analyzer.GetExternalEventsDependencies().begin(),
analyzer.GetExternalEventsDependencies().end());
}
bool wasDependencyJustAdded = scenesDependencies.insert(linked).second;
if (wasDependencyJustAdded) {
parentScenes.push_back(linked);
if (!Analyze(project.GetLayout(linked).GetEvents()))
return false;
parentScenes.pop_back();
}
}
}
@@ -112,45 +86,9 @@ bool DependenciesAnalyzer::Analyze(const gd::EventsList& events, bool isOnTopLev
// Analyze sub events dependencies
if (events[i].CanHaveSubEvents()) {
if (!Analyze(events[i].GetSubEvents(), false)) return false;
if (!Analyze(events[i].GetSubEvents())) return false;
}
}
return true;
}
gd::String DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene() {
if (!externalEvents) {
std::cout << "ERROR: ExternalEventsCanBeCompiledForAScene called without "
"external events set!"
<< std::endl;
return "";
}
gd::String sceneName;
for (unsigned int i = 0; i < project.GetLayoutsCount(); ++i) {
// For each layout, compute the dependencies and the dependencies which are
// not coming from a top level event.
DependenciesAnalyzer analyzer(project, project.GetLayout(i));
if (!analyzer.Analyze()) continue; // Analyze failed -> Cyclic dependencies
const std::set<gd::String>& dependencies =
analyzer.GetExternalEventsDependencies();
const std::set<gd::String>& notTopLevelDependencies =
analyzer.GetNotTopLevelExternalEventsDependencies();
// Check if the external events is a dependency, and that is is only present
// as a link on the top level.
if (dependencies.find(externalEvents->GetName()) != dependencies.end() &&
notTopLevelDependencies.find(externalEvents->GetName()) ==
notTopLevelDependencies.end()) {
if (!sceneName.empty())
return ""; // External events can be compiled only if one scene is
// including them.
else
sceneName = project.GetLayout(i).GetName();
}
}
return sceneName; // External events can be compiled and used for the scene.
}
#endif

View File

@@ -39,11 +39,6 @@ class GD_CORE_API DependenciesAnalyzer {
/**
* \brief Constructor for analyzing the dependencies of external events.
*
* You can also call then
* DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene to check if the
* external events can be compiled separately and called by a scene. \see
* DependenciesAnalyzer::ExternalEventsCanBeCompiledForAScene
*/
DependenciesAnalyzer(const gd::Project& project_,
const gd::ExternalEvents& externalEvents);
@@ -60,18 +55,6 @@ class GD_CORE_API DependenciesAnalyzer {
*/
bool Analyze();
/**
* Check if the external events (passed in the constructor) can be compiled
* and called by a single scene:<br> This is possible when the link calling
* the external events does not have any parent event and when this situation
* occurs only in a single scene and not in another.
*
* \return The name of the scene which is able to call the compiled external
* events. If empty, no scene is able to call them. (So external events have
* to be included directly by links).
*/
gd::String ExternalEventsCanBeCompiledForAScene();
/**
* \brief Return the scenes being dependencies of the scene or external events
* passed in the constructor.
@@ -96,25 +79,6 @@ class GD_CORE_API DependenciesAnalyzer {
return sourceFilesDependencies;
};
/**
* \brief Return the scenes being dependencies of the scene or external events
* passed in the constructor, but being not top level dependencies: The links
* including them are not a top level events (i.e: They have a parent event).
*/
const std::set<gd::String>& GetNotTopLevelScenesDependencies() const {
return notTopLevelScenesDependencies;
};
/**
* \brief Return the external events being dependencies of the scene or
* external events passed in the constructor, but being not top level
* dependencies: The links including them are not a top level events (i.e:
* They have a parent event).
*/
const std::set<gd::String>& GetNotTopLevelExternalEventsDependencies() const {
return notTopLevelExternalEventsDependencies;
};
private:
/**
* \brief Analyze the dependencies of the events.
@@ -124,32 +88,11 @@ class GD_CORE_API DependenciesAnalyzer {
* (they have no parents). \return false if a circular dependency exists, true
* otherwise.
*/
bool Analyze(const gd::EventsList& events, bool isOnTopLevel);
void AddParentScene(gd::String parentScene) {
parentScenes.push_back(parentScene);
};
void AddParentExternalEvents(gd::String parentExternalEvents_) {
parentExternalEvents.push_back(parentExternalEvents_);
};
/**
* Return true if all links pointing to external events called \a
* externalEventsName are only at the top level of \a events. The function
* return false as soon as it discover a link to external events which is not
* at the top level ( i.e: It has a parent event ).
*
* \warning The function assumes that there are not cyclic dependencies.
*/
bool CheckIfExternalEventsIsLinkedOnlyAtTopLevel(
const gd::String& externalEventsName,
std::vector<std::shared_ptr<gd::BaseEvent> >& events);
bool Analyze(const gd::EventsList& events);
std::set<gd::String> scenesDependencies;
std::set<gd::String> externalEventsDependencies;
std::set<gd::String> sourceFilesDependencies;
std::set<gd::String> notTopLevelScenesDependencies;
std::set<gd::String> notTopLevelExternalEventsDependencies;
std::vector<gd::String>
parentScenes; ///< Used to check for circular dependencies.
std::vector<gd::String>

View File

@@ -226,7 +226,7 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
DependenciesAnalyzer dependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);

View File

@@ -258,7 +258,7 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
DependenciesAnalyzer dependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
const gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);

View File

@@ -43,7 +43,6 @@ void ExtensionsLoader::LoadAllExtensions(const gd::String &directory,
struct dirent *lecture;
DIR *rep;
rep = opendir(directory.c_str());
int l = 0;
if (rep == NULL) {
cout << "Unable to open Extensions (" << directory << ") directory."
@@ -63,8 +62,6 @@ void ExtensionsLoader::LoadAllExtensions(const gd::String &directory,
LoadExtension(directory + "/" + lec, platform, forgiving);
librariesLoaded.push_back(directory + "/" + lec);
l++;
}
}
@@ -103,7 +100,6 @@ void ExtensionsLoader::ExtensionsLoadingDone(const gd::String &directory) {
struct dirent *lecture;
DIR *rep;
rep = opendir(directory.c_str());
int l = 0;
if (rep == NULL) {
cout << "Unable to open Extensions (" << directory << ") directory."
@@ -118,7 +114,6 @@ void ExtensionsLoader::ExtensionsLoadingDone(const gd::String &directory) {
lec.find(".xgd" + suffix, lec.length() - 4 - suffix.length()) !=
string::npos) {
librariesLoaded.push_back(directory + "/" + lec);
l++;
}
}

View File

@@ -262,13 +262,6 @@ bool ResourceWorkerInEventsWorker::DoVisitInstruction(gd::Instruction& instructi
return false;
};
void LaunchResourceWorkerOnEvents(const gd::Project& project,
gd::EventsList& events,
gd::ArbitraryResourceWorker& worker) {
gd::ResourceWorkerInEventsWorker eventsWorker(project, worker);
eventsWorker.Launch(events);
}
gd::ResourceWorkerInEventsWorker
GetResourceWorkerOnEvents(const gd::Project &project,
gd::ArbitraryResourceWorker &worker) {

View File

@@ -37,7 +37,7 @@ namespace gd {
* \see ResourcesMergingHelper
* \see gd::ResourcesInUseHelper
*
* \see gd::LaunchResourceWorkerOnEvents
* \see gd::GetResourceWorkerOnEvents
*
* \ingroup IDE
*/

View File

@@ -51,7 +51,6 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
// Copy resources
map<gd::String, gd::String>& resourcesNewFilename =
resourcesMergingHelper.GetAllResourcesOldAndNewFilename();
unsigned int i = 0;
for (map<gd::String, gd::String>::const_iterator it =
resourcesNewFilename.begin();
it != resourcesNewFilename.end();
@@ -71,8 +70,6 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
destinationFile + _("\"."));
}
}
++i;
}
return true;

View File

@@ -0,0 +1,35 @@
/*
* GDevelop JS Platform
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "SceneResourcesFinder.h"
#include "GDCore/IDE/ResourceExposer.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
std::set<gd::String> SceneResourcesFinder::FindProjectResources(gd::Project &project) {
gd::SceneResourcesFinder resourceWorker;
gd::ResourceExposer::ExposeProjectResources(project, resourceWorker);
return resourceWorker.resourceNames;
}
std::set<gd::String> SceneResourcesFinder::FindSceneResources(gd::Project &project,
gd::Layout &layout) {
gd::SceneResourcesFinder resourceWorker;
gd::ResourceExposer::ExposeLayoutResources(project, layout, resourceWorker);
return resourceWorker.resourceNames;
}
void SceneResourcesFinder::AddUsedResource(gd::String &resourceName) {
if (resourceName.empty()) {
return;
}
resourceNames.insert(resourceName);
}
} // namespace gd

View File

@@ -0,0 +1,86 @@
/*
* GDevelop JS Platform
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
#include <set>
namespace gd {
class Project;
class Layout;
class SerializerElement;
} // namespace gd
namespace gd {
/**
* \brief Find resource usages in several parts of the project.
*
* \ingroup IDE
*/
class SceneResourcesFinder : private gd::ArbitraryResourceWorker {
public:
/**
* @brief Find resource usages in a given scenes.
*
* It doesn't include resources used globally.
*/
static std::set<gd::String> FindSceneResources(gd::Project &project,
gd::Layout &layout);
/**
* @brief Find resource that are used globally in the project.
*
* It doesn't include resources used in scenes.
*/
static std::set<gd::String> FindProjectResources(gd::Project &project);
virtual ~SceneResourcesFinder(){};
private:
SceneResourcesFinder() : gd::ArbitraryResourceWorker(){};
void AddUsedResource(gd::String &resourceName);
void ExposeFile(gd::String &resourceFileName) override{
// Don't do anything: we're renaming resources, not the files they are
// pointing to.
};
void ExposeImage(gd::String &imageResourceName) override {
AddUsedResource(imageResourceName);
};
void ExposeAudio(gd::String &audioResourceName) override {
AddUsedResource(audioResourceName);
};
void ExposeFont(gd::String &fontResourceName) override {
AddUsedResource(fontResourceName);
};
void ExposeJson(gd::String &jsonResourceName) override {
AddUsedResource(jsonResourceName);
};
void ExposeTilemap(gd::String &tilemapResourceName) override {
AddUsedResource(tilemapResourceName);
};
void ExposeTileset(gd::String &tilesetResourceName) override {
AddUsedResource(tilesetResourceName);
};
void ExposeVideo(gd::String &videoResourceName) override {
AddUsedResource(videoResourceName);
};
void ExposeBitmapFont(gd::String &bitmapFontName) override {
AddUsedResource(bitmapFontName);
};
void ExposeModel3D(gd::String &resourceName) override {
AddUsedResource(resourceName);
};
std::set<gd::String> resourceNames;
};
} // namespace gd

View File

@@ -20,6 +20,7 @@
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
#include "GDCore/IDE/DependenciesAnalyzer.h"
namespace gd {
@@ -33,27 +34,8 @@ void ProjectBrowserHelper::ExposeProjectEvents(
// Add events based extensions
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
// Add (free) events functions
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
worker.Launch(eventsFunction->GetEvents());
}
// Add (behavior) events functions
for (auto &&eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
ExposeEventsBasedBehaviorEvents(project, *eventsBasedBehavior, worker);
}
// Add (object) events functions
for (auto &&eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
auto &objectEventsFunctions = eventsBasedObject->GetEventsFunctions();
for (auto &&eventsFunction : objectEventsFunctions.GetInternalVector()) {
worker.Launch(eventsFunction->GetEvents());
}
}
ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(project, eventsFunctionsExtension, worker);
}
}
@@ -69,7 +51,7 @@ void ProjectBrowserHelper::ExposeProjectEventsWithoutExtensions(
}
}
void ProjectBrowserHelper::ExposeLayoutEvents(
void ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker) {
@@ -85,7 +67,7 @@ void ProjectBrowserHelper::ExposeLayoutEvents(
}
}
void ProjectBrowserHelper::ExposeLayoutEvents(
void ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorkerWithContext &worker) {
auto projectScopedContainers =
@@ -103,6 +85,32 @@ void ProjectBrowserHelper::ExposeLayoutEvents(
}
}
void ProjectBrowserHelper::ExposeLayoutEventsAndDependencies(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker) {
// Add layouts events
worker.Launch(layout.GetEvents());
DependenciesAnalyzer dependenciesAnalyzer(project, layout);
bool hasCircularDependencies = !dependenciesAnalyzer.Analyze();
if (hasCircularDependencies) {
// The analyzer stops when it finds circular dependencies so the dependencies are not complete.
// TODO Should the analyzer still continue to avoid side effect on thing that would not be code generation related?
// Maybe a boolean parameter should be added?
return;
}
for (const gd::String& externalEventName : dependenciesAnalyzer.GetExternalEventsDependencies()) {
gd::ExternalEvents& externalEvents = project.GetExternalEvents(externalEventName);
worker.Launch(externalEvents.GetEvents());
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
gd::Layout& dependencyLayout = project.GetLayout(sceneName);
worker.Launch(dependencyLayout.GetEvents());
}
}
void ProjectBrowserHelper::ExposeProjectEvents(
gd::Project &project, gd::ArbitraryEventsWorkerWithContext &worker) {
// See also gd::Project::ExposeResources for a method that traverse the whole
@@ -130,8 +138,43 @@ void ProjectBrowserHelper::ExposeProjectEvents(
// Add events based extensions
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
// Add (free) events functions
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(project, eventsFunctionsExtension, worker);
}
}
void ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorker &worker) {
// Add (free) events functions
for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
project, eventsFunctionsExtension, *eventsFunction,
globalObjectsAndGroups, objectsAndGroups);
worker.Launch(eventsFunction->GetEvents());
}
// Add (behavior) events functions
for (auto &&eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
ExposeEventsBasedBehaviorEvents(project, *eventsBasedBehavior, worker);
}
// Add (object) events functions
for (auto &&eventsBasedObject :
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
ExposeEventsBasedObjectEvents(project, *eventsBasedObject, worker);
}
}
void ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorkerWithContext &worker) {
// Add (free) events functions
for (auto &&eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
@@ -157,7 +200,6 @@ void ProjectBrowserHelper::ExposeProjectEvents(
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
ExposeEventsBasedObjectEvents(project, *eventsBasedObject, worker);
}
}
}
void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
@@ -189,6 +231,21 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
}
}
void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
gd::Project &project, const gd::EventsBasedObject &eventsBasedObject,
gd::ArbitraryEventsWorker &worker) {
auto &objectEventsFunctions = eventsBasedObject.GetEventsFunctions();
for (auto &&eventsFunction : objectEventsFunctions.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer(
project, eventsBasedObject, *eventsFunction, globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents());
}
}
void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
gd::Project &project, const gd::EventsBasedObject &eventsBasedObject,
gd::ArbitraryEventsWorkerWithContext &worker) {
@@ -216,7 +273,7 @@ void ProjectBrowserHelper::ExposeProjectObjects(
// Layout objects
for (size_t i = 0; i < project.GetLayoutsCount(); i++) {
worker.Launch(project.GetLayout(i));
gd::ProjectBrowserHelper::ExposeLayoutObjects(project.GetLayout(i), worker);
}
// Event based objects children
@@ -232,6 +289,14 @@ void ProjectBrowserHelper::ExposeProjectObjects(
}
};
void ProjectBrowserHelper::ExposeLayoutObjects(gd::Layout &layout,
gd::ArbitraryObjectsWorker &worker) {
// In the future, layouts may have children object containers.
// Layout objects
worker.Launch(layout);
}
void ProjectBrowserHelper::ExposeProjectFunctions(
gd::Project &project, gd::ArbitraryEventsFunctionsWorker &worker) {

View File

@@ -60,18 +60,52 @@ public:
* \brief Call the specified worker on all events of a layout and
* its external events.
*/
static void ExposeLayoutEvents(gd::Project &project, gd::Layout &layout,
static void ExposeLayoutEventsAndExternalEvents(gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of a layout and
* its external events.
*/
static void ExposeLayoutEvents(gd::Project &project, gd::Layout &layout,
static void ExposeLayoutEventsAndExternalEvents(gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorkerWithContext &worker);
/**
* \brief Call the specified worker on all events of a layout and
* its dependencies according to EventLink (external events or other layout
* events).
*/
static void
ExposeLayoutEventsAndDependencies(gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of the event-based
* behavior
* extension.
*
* This should be the preferred way to traverse all the events of an events
* based extension.
*/
static void ExposeEventsFunctionsExtensionEvents(
gd::Project &project,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of the event-based
* extension.
*
* This should be the preferred way to traverse all the events of an events
* based extension.
*/
static void ExposeEventsFunctionsExtensionEvents(
gd::Project &project,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
gd::ArbitraryEventsWorkerWithContext &worker);
/**
* \brief Call the specified worker on all events of the event-based
* behavior.
*
* This should be the preferred way to traverse all the events of an events
* based behavior.
@@ -93,10 +127,22 @@ public:
/**
* \brief Call the specified worker on all events of the event-based
* behavior.
* object.
*
* This should be the preferred way to traverse all the events of an
* event-based behavior.
* event-based object.
*/
static void
ExposeEventsBasedObjectEvents(gd::Project &project,
const gd::EventsBasedObject &eventsBasedObject,
gd::ArbitraryEventsWorker &worker);
/**
* \brief Call the specified worker on all events of the event-based
* object.
*
* This should be the preferred way to traverse all the events of an
* event-based object.
*/
static void
ExposeEventsBasedObjectEvents(gd::Project &project,
@@ -112,6 +158,14 @@ public:
static void ExposeProjectObjects(gd::Project &project,
gd::ArbitraryObjectsWorker &worker);
/**
* \brief Call the specified worker on all ObjectContainers of the layout.
*
* This should be the preferred way to traverse all the objects of a layout.
*/
static void ExposeLayoutObjects(gd::Layout &layout,
gd::ArbitraryObjectsWorker &worker);
/**
* \brief Call the specified worker on all FunctionsContainers of the project
* (global, layouts...)

View File

@@ -24,6 +24,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/EffectMetadata.h"
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
namespace gd {
@@ -36,6 +37,7 @@ void ResourceExposer::ExposeWholeProjectResources(gd::Project& project, gd::Arbi
// Expose any project resources as files.
worker.ExposeResources(resourcesManager);
project.GetPlatformSpecificAssets().ExposeResources(worker);
// Expose event resources
@@ -73,6 +75,49 @@ void ResourceExposer::ExposeWholeProjectResources(gd::Project& project, gd::Arbi
worker.ExposeImage(loadingScreen.GetBackgroundImageResourceName());
}
void ResourceExposer::ExposeProjectResources(gd::Project& project, gd::ArbitraryResourceWorker& worker) {
// Expose global objects configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
objectWorker.Launch(project);
}
void ResourceExposer::ExposeLayoutResources(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryResourceWorker &worker) {
// Expose object configuration resources
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
gd::ProjectBrowserHelper::ExposeLayoutObjects(layout, objectWorker);
// Expose layer effect resources
for (std::size_t layerIndex = 0; layerIndex < layout.GetLayersCount();
layerIndex++) {
auto &layer = layout.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);
}
}
// Expose event resources
auto eventWorker = gd::GetResourceWorkerOnEvents(project, worker);
gd::ProjectBrowserHelper::ExposeLayoutEventsAndDependencies(project, layout,
eventWorker);
// Exposed extension event resources
// Note that using resources in extensions is very unlikely and probably not
// worth the effort of something smart.
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto &eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(project, eventsFunctionsExtension, eventWorker);
}
}
void ResourceExposer::ExposeEffectResources(
gd::Platform &platform, gd::Effect &effect,
gd::ArbitraryResourceWorker &worker) {
@@ -88,11 +133,13 @@ void ResourceExposer::ExposeEffectResources(
auto &resourceType = propertyDescriptor.GetExtraInfo()[0];
const gd::String &resourceName = effect.GetStringParameter(propertyName);
gd::String potentiallyUpdatedResourceName = resourceName;
worker.ExposeResourceWithType(resourceType,
potentiallyUpdatedResourceName);
if (potentiallyUpdatedResourceName != resourceName) {
effect.SetStringParameter(propertyName, potentiallyUpdatedResourceName);
if (!resourceName.empty()) {
gd::String potentiallyUpdatedResourceName = resourceName;
worker.ExposeResourceWithType(resourceType,
potentiallyUpdatedResourceName);
if (potentiallyUpdatedResourceName != resourceName) {
effect.SetStringParameter(propertyName, potentiallyUpdatedResourceName);
}
}
}
}

View File

@@ -10,6 +10,7 @@ class Platform;
class Project;
class ArbitraryResourceWorker;
class Effect;
class Layout;
} // namespace gd
namespace gd {
@@ -31,6 +32,25 @@ public:
static void ExposeWholeProjectResources(gd::Project &project,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose only the resources used globally on a project.
*
* It doesn't include resources used in layouts.
*/
static void ExposeProjectResources(gd::Project &project,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose the resources used in a given layout.
*
* It doesn't include resources used globally.
*/
static void ExposeLayoutResources(gd::Project &project, gd::Layout &layout,
gd::ArbitraryResourceWorker &worker);
/**
* @brief Expose the resources used in a given effect.
*/
static void ExposeEffectResources(gd::Platform &platform, gd::Effect &effect,
gd::ArbitraryResourceWorker &worker);
};

View File

@@ -1538,7 +1538,7 @@ void WholeProjectRefactorer::RenameLayer(gd::Project &project,
return;
gd::ProjectElementRenamer projectElementRenamer(project.GetCurrentPlatform(),
"layer", oldName, newName);
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
projectElementRenamer);
}
@@ -1552,7 +1552,7 @@ void WholeProjectRefactorer::RenameLayerEffect(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "layerEffectName", oldName, newName);
projectElementRenamer.SetLayerConstraint(layer.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
projectElementRenamer);
}
@@ -1566,7 +1566,7 @@ void WholeProjectRefactorer::RenameObjectAnimation(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "objectAnimationName", oldName, newName);
projectElementRenamer.SetObjectConstraint(object.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
projectElementRenamer);
}
@@ -1580,7 +1580,7 @@ void WholeProjectRefactorer::RenameObjectPoint(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "objectPointName", oldName, newName);
projectElementRenamer.SetObjectConstraint(object.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
projectElementRenamer);
}
@@ -1594,7 +1594,7 @@ void WholeProjectRefactorer::RenameObjectEffect(gd::Project &project,
gd::ProjectElementRenamer projectElementRenamer(
project.GetCurrentPlatform(), "objectEffectName", oldName, newName);
projectElementRenamer.SetObjectConstraint(object.GetName());
gd::ProjectBrowserHelper::ExposeLayoutEvents(project, layout,
gd::ProjectBrowserHelper::ExposeLayoutEventsAndExternalEvents(project, layout,
projectElementRenamer);
}

View File

@@ -5,6 +5,8 @@
*/
#include "CustomConfigurationHelper.h"
#include <map>
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Project.h"
@@ -13,8 +15,6 @@
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include <map>
using namespace gd;
void CustomConfigurationHelper::InitializeContent(
@@ -25,7 +25,8 @@ void CustomConfigurationHelper::InitializeContent(
auto propertyType = property->GetType();
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior") {
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
element.SetStringValue(property->GetValue());
} else if (propertyType == "Number") {
element.SetDoubleValue(property->GetValue().To<double>());
@@ -51,7 +52,8 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
if (configurationContent.HasChild(propertyName)) {
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior") {
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
newProperty.SetValue(
configurationContent.GetChild(propertyName).GetStringValue());
} else if (propertyType == "Number") {
@@ -59,8 +61,9 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
configurationContent.GetChild(propertyName).GetDoubleValue()));
} else if (propertyType == "Boolean") {
newProperty.SetValue(
configurationContent.GetChild(propertyName).GetBoolValue() ? "true"
: "false");
configurationContent.GetChild(propertyName).GetBoolValue()
? "true"
: "false");
}
} else {
// No value was serialized for this property. `newProperty`
@@ -85,7 +88,8 @@ bool CustomConfigurationHelper::UpdateProperty(
const gd::String &propertyType = property.GetType();
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior") {
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "resource") {
element.SetStringValue(newValue);
} else if (propertyType == "Number") {
element.SetDoubleValue(newValue.To<double>());

View File

@@ -534,19 +534,7 @@ void ResourcesManager::SerializeTo(SerializerElement& element) const {
if (resources[i] == std::shared_ptr<Resource>()) break;
SerializerElement& resourceElement = resourcesElement.AddChild("resource");
resourceElement.SetAttribute("kind", resources[i]->GetKind());
resourceElement.SetAttribute("name", resources[i]->GetName());
resourceElement.SetAttribute("metadata", resources[i]->GetMetadata());
const gd::String& originName = resources[i]->GetOriginName();
const gd::String& originIdentifier = resources[i]->GetOriginIdentifier();
if (!originName.empty() || !originIdentifier.empty()) {
resourceElement.AddChild("origin")
.SetAttribute("name", originName)
.SetAttribute("identifier", originIdentifier);
}
resources[i]->SerializeTo(resourceElement);
gd::ResourcesManager::SerializeResourceTo(*resources[i], resourceElement);
}
SerializerElement& resourcesFoldersElement =
@@ -556,6 +544,22 @@ void ResourcesManager::SerializeTo(SerializerElement& element) const {
folders[i].SerializeTo(resourcesFoldersElement.AddChild("folder"));
}
void ResourcesManager::SerializeResourceTo(gd::Resource &resource,
SerializerElement &resourceElement) {
resourceElement.SetAttribute("kind", resource.GetKind());
resourceElement.SetAttribute("name", resource.GetName());
resourceElement.SetAttribute("metadata", resource.GetMetadata());
const gd::String &originName = resource.GetOriginName();
const gd::String &originIdentifier = resource.GetOriginIdentifier();
if (!originName.empty() || !originIdentifier.empty()) {
resourceElement.AddChild("origin")
.SetAttribute("name", originName)
.SetAttribute("identifier", originIdentifier);
}
resource.SerializeTo(resourceElement);
}
void ImageResource::SetFile(const gd::String& newFile) {
file = NormalizePathSeparator(newFile);
}

View File

@@ -662,6 +662,11 @@ class GD_CORE_API ResourcesManager {
*/
void SerializeTo(SerializerElement& element) const;
/**
* \brief Serialize one resource.
*/
static void SerializeResourceTo(gd::Resource& resource, SerializerElement& resourceElement);
/**
* \brief Unserialize the object.
*/

File diff suppressed because it is too large Load Diff

View File

@@ -5398,12 +5398,10 @@ class Runner {
getRegistryHub().getTestCaseRegistry().getFilteredTests(
testSpec, *m_config, testCases);
int testsRunForGroup = 0;
for (std::vector<TestCase>::const_iterator it = testCases.begin(),
itEnd = testCases.end();
it != itEnd;
++it) {
testsRunForGroup++;
if (m_testsAlreadyRun.find(*it) == m_testsAlreadyRun.end()) {
if (context.aborting()) break;

View File

@@ -86,11 +86,13 @@ namespace gdjs {
export const turnCameraTowardObject = (
runtimeScene: RuntimeScene,
object: gdjs.RuntimeObject,
object: gdjs.RuntimeObject | null,
layerName: string,
cameraIndex: integer,
isStandingOnY: boolean
) => {
if (!object) return;
const layer = runtimeScene.getLayer(layerName);
const layerRenderer = layer.getRenderer();

View File

@@ -36,7 +36,7 @@ module.exports = {
extension
.addAction(
'Focus',
_('Change focus of the window'),
_('Window focus'),
_('Make the window gain or lose focus.'),
_('Focus the window: _PARAM0_'),
_('Windows, Linux, macOS'),
@@ -72,7 +72,7 @@ module.exports = {
extension
.addAction(
'Show',
_('Change visibility of the window'),
_('Window visibility'),
_('Make the window visible or invisible.'),
_('Window visible: _PARAM0_'),
_('Windows, Linux, macOS'),
@@ -624,7 +624,7 @@ module.exports = {
extension
.addAction(
'SetOpacity',
_('Set window opacity'),
_('Window opacity'),
_('Changes the window opacity.'),
_('Set the window opacity to _PARAM0_'),
_('Windows, Linux, macOS'),
@@ -645,7 +645,7 @@ module.exports = {
extension
.addAction(
'SetWindowPosition',
_('Set window position'),
_('Window position'),
_('Changes the window position.'),
_('Set the window position to _PARAM0_;_PARAM1_'),
_('Windows, Linux, macOS'),

View File

@@ -30,6 +30,7 @@ describe('gdjs.AnchorRuntimeBehavior', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
function createObject(behaviorProperties) {

View File

@@ -41,7 +41,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the additional border that the object must cross "
"before being deleted."),
_("the additional border"),
"",
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png",
"CppPlatform/Extensions/destroyoutsideicon16.png")
.AddParameter("object", _("Object"))
@@ -56,7 +56,7 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the additional border that the object must cross "
"before being deleted."),
_("the additional border"),
"",
_("Destroy outside configuration"),
"CppPlatform/Extensions/destroyoutsideicon24.png",
"CppPlatform/Extensions/destroyoutsideicon16.png")
.AddParameter("object", _("Object"))

View File

@@ -39,7 +39,7 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
_("Being dragged"),
_("Check if the object is being dragged."),
_("_PARAM0_ is being dragged"),
"",
_("Draggable"),
"CppPlatform/Extensions/draggableicon24.png",
"CppPlatform/Extensions/draggableicon16.png")
@@ -51,7 +51,7 @@ void DeclareDraggableBehaviorExtension(gd::PlatformExtension& extension) {
_("Was just dropped"),
_("Check if the object was just dropped after being dragged."),
_("_PARAM0_ was just dropped"),
"",
_("Draggable"),
"CppPlatform/Extensions/draggableicon24.png",
"CppPlatform/Extensions/draggableicon16.png")

View File

@@ -27,6 +27,7 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
var object = new gdjs.TestRuntimeObject(runtimeScene, {

View File

@@ -86,7 +86,7 @@ module.exports = {
extension
.addAction(
'AnalyticsSetUID',
_('Change user UID'),
_('User UID'),
_(
"Changes the current user's analytics identifier. " +
'This is what let Analytics differentiate user, ' +
@@ -400,7 +400,7 @@ module.exports = {
extension
.addStrExpression(
'GetAuthToken',
_('Get the user authentication token'),
_('User authentication token'),
_(
'Get the user authentication token. The token is the proof of authentication.'
),
@@ -438,8 +438,8 @@ module.exports = {
extension
.addStrExpression(
'GetUserEmail',
_('Get the user email address'),
_('Gets the user email address.'),
_('User email address'),
_('Return the user email address.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -455,8 +455,8 @@ module.exports = {
extension
.addStrExpression(
'GetAccountCreationTime',
_('Get the accounts creation time'),
_('Gets the accounts creation time.'),
_('Accounts creation time'),
_('Return the accounts creation time.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -472,8 +472,8 @@ module.exports = {
extension
.addStrExpression(
'GetLastLoginTime',
_('Get the user last login time'),
_('Gets the user last login time.'),
_('User last login time'),
_('Return the user last login time.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -489,8 +489,8 @@ module.exports = {
extension
.addStrExpression(
'GetUserDisplayName',
_('Get the user display name'),
_('Gets the user display name.'),
_('User display name'),
_('Return the user display name.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -506,8 +506,8 @@ module.exports = {
extension
.addStrExpression(
'GetPhoneNumber',
_('Get the user phone number'),
_('Gets the user phone number.'),
_('User phone number'),
_('Return the user phone number.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -523,9 +523,9 @@ module.exports = {
extension
.addStrExpression(
'GetUserUID',
_('Get the user UID'),
_('User UID'),
_(
'Gets the user Unique IDentifier. Use that to link data to an ' +
'Return the user Unique IDentifier. Use that to link data to an ' +
'user instead of the name or email.'
),
_('Authentication/User Management'),
@@ -543,8 +543,8 @@ module.exports = {
extension
.addStrExpression(
'GetTenantID',
_('Get the user tenant ID'),
_('Gets the user tenant ID. For advanced usage only.'),
_('User tenant ID'),
_('Return the user tenant ID. For advanced usage only.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -560,8 +560,8 @@ module.exports = {
extension
.addStrExpression(
'GetRefreshToken',
_('Get the user refresh token'),
_('Gets the user refresh token. For advanced usage only.'),
_('User refresh token'),
_('Return the user refresh token. For advanced usage only.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
)
@@ -577,7 +577,7 @@ module.exports = {
extension
.addStrExpression(
'GetPhotoURL',
_('Get the user profile picture URL'),
_('Profile picture URL'),
_('Gets an URL to the user profile picture.'),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png'
@@ -635,7 +635,7 @@ module.exports = {
extension
.addAction(
'SetDisplayName',
_('Set display name'),
_('Display name'),
_('Sets the user display name.'),
_("Set the user's display name to _PARAM0_"),
_('Authentication/User Management'),
@@ -655,9 +655,9 @@ module.exports = {
extension
.addAction(
'SetPhotoURL',
_('Set the user profile picture'),
_('Sets the user profile picture URL to a new one.'),
_("Set the user's profile picture URL to _PARAM0_"),
_('Profile picture'),
_('Change the user profile picture URL to a new one.'),
_("Change the user's profile picture URL to _PARAM0_"),
_('Authentication/User Management'),
'JsPlatform/Extensions/firebase.png',
'JsPlatform/Extensions/firebase.png'
@@ -676,7 +676,7 @@ module.exports = {
extension
.addAction(
'ChangeEmail',
_('Change the user email'),
_('User email'),
_(
'This action is dangerous so it requires reauthentication.\n' +
"Changes the user's email address."
@@ -716,7 +716,7 @@ module.exports = {
extension
.addAction(
'ChangeEmailProvider',
_('Change the user email (Provider)'),
_('User email (Provider)'),
_(
'This action is dangerous so it requires reauthentication.\n' +
"Changes the user's email address.\n" +
@@ -755,7 +755,7 @@ module.exports = {
extension
.addAction(
'ChangePassword',
_('Change the user password'),
_('User password'),
_(
'This action is dangerous so it requires reauthentication.\n' +
'Changes the user password.'
@@ -796,7 +796,7 @@ module.exports = {
extension
.addAction(
'ChangePasswordProvider',
_('Change the user password (Provider)'),
_('User password (Provider)'),
_(
'This action is dangerous so it requires reauthentication.\n' +
'Changes the user password.\n' +
@@ -1782,7 +1782,7 @@ module.exports = {
.addAction(
'FirestoreGetField',
_('Get a field of a document'),
_('Gets the value of a field in a firestore document.'),
_('Return the value of a field in a firestore document.'),
_(
'Load field _PARAM2_ of firestore document _PARAM1_ in collection _PARAM0_ into _PARAM3_ (store result state in _PARAM4_)'
),
@@ -2213,7 +2213,7 @@ module.exports = {
'DatabaseGetField',
_('Get a field of a variable'),
_(
'Gets the value of a field in a variable from the database and store it in a scene variable.'
'Return the value of a field in a variable from the database and store it in a scene variable.'
),
_(
'Load field _PARAM1_ of database variable _PARAM0_ into _PARAM2_ (store result state in _PARAM3_)'

View File

@@ -203,7 +203,7 @@ module.exports = {
object
.addAction(
'SetRadius',
_('Set the radius of light object'),
_('Light radius'),
_('Set the radius of light object'),
_('Set the radius of _PARAM0_ to: _PARAM1_'),
'',
@@ -218,7 +218,7 @@ module.exports = {
object
.addAction(
'SetColor',
_('Set the color of light object'),
_('Light color'),
_('Set the color of light object in format "R;G;B" string.'),
_('Set the color of _PARAM0_ to: _PARAM1_'),
'',

View File

@@ -195,8 +195,8 @@ namespace gdjs {
export const linkObjects = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
objA: gdjs.RuntimeObject,
objB: gdjs.RuntimeObject
objA: gdjs.RuntimeObject | null,
objB: gdjs.RuntimeObject | null
) {
if (objA === null || objB === null) {
return;
@@ -206,8 +206,8 @@ namespace gdjs {
export const removeLinkBetween = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
objA: gdjs.RuntimeObject,
objB: gdjs.RuntimeObject
objA: gdjs.RuntimeObject | null,
objB: gdjs.RuntimeObject | null
) {
if (objA === null || objB === null) {
return;
@@ -231,7 +231,7 @@ namespace gdjs {
export const pickObjectsLinkedTo = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
objectsLists: Hashtable<gdjs.RuntimeObject[]>,
obj: gdjs.RuntimeObject,
obj: gdjs.RuntimeObject | null,
eventsFunctionContext: EventsFunctionContext | undefined
) {
if (obj === null) {

View File

@@ -33,6 +33,7 @@ describe('gdjs.LinksManager', function () {
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
usedResources: [],
});
const manager = gdjs.LinksManager.getManager(runtimeScene);

View File

@@ -637,8 +637,8 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.SetFunctionName("GetCost");
aut.AddAction("SetImpassable",
_("Should object be impassable?"),
_("Decide if the object is an impassable obstacle"),
_("Should object be impassable"),
_("Decide if the object is an impassable obstacle."),
_("Set _PARAM0_ as an impassable obstacle: _PARAM2_"),
_("Obstacles"),
"CppPlatform/Extensions/pathfindingobstacleicon24.png",
@@ -646,12 +646,12 @@ void DeclarePathfindingBehaviorExtension(gd::PlatformExtension& extension) {
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PathfindingObstacleBehavior")
.AddParameter("yesorno", _("Impassable?"))
.AddParameter("yesorno", _("Impassable"))
.SetFunctionName("SetImpassable");
aut.AddCondition("IsImpassable",
_("Is object impassable?"),
_("Check if the obstacle is impassable"),
_("Impassable obstacle"),
_("Check if the obstacle is impassable."),
_("_PARAM0_ is impassable"),
_("Obstacles"),
"CppPlatform/Extensions/pathfindingobstacleicon24.png",

View File

@@ -34,6 +34,7 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
setFramePerSecond(runtimeScene, framePerSecond);
return runtimeScene;

View File

@@ -37,6 +37,7 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
setFramePerSecond(runtimeScene, framePerSecond);
return runtimeScene;

View File

@@ -39,6 +39,7 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / 60) * 1000;

View File

@@ -1837,7 +1837,7 @@ namespace gdjs {
addDistanceJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
length: float,
@@ -2083,7 +2083,7 @@ namespace gdjs {
addRevoluteJointBetweenTwoBodies(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
enableLimit: boolean,
@@ -2383,7 +2383,7 @@ namespace gdjs {
addPrismaticJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
axisAngle: float,
@@ -2754,7 +2754,7 @@ namespace gdjs {
addPulleyJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
groundX1: float,
@@ -3237,7 +3237,7 @@ namespace gdjs {
addWheelJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
axisAngle: float,
@@ -3522,7 +3522,7 @@ namespace gdjs {
addWeldJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
referenceAngle: float,
@@ -3673,7 +3673,7 @@ namespace gdjs {
addRopeJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
maxLength: float,
@@ -3782,7 +3782,7 @@ namespace gdjs {
addFrictionJoint(
x1: float,
y1: float,
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
x2: float,
y2: float,
maxForce: float,
@@ -3914,7 +3914,7 @@ namespace gdjs {
// Motor joint
addMotorJoint(
other: gdjs.RuntimeObject,
other: gdjs.RuntimeObject | null,
offsetX: float,
offsetY: float,
offsetAngle: float,

View File

@@ -17,326 +17,326 @@ This project is released under the MIT License.
void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("PhysicsBehavior",
_("Physics Engine (deprecated)"),
("Physics Engine (deprecated)"),
"This is the old, deprecated physics engine. Prefer to use the Physics Engine 2.0.",
"Florian Rival",
"Open source (MIT License)")
.SetCategory("Movement")
.SetExtensionHelpPath("/behaviors/physics");
extension.AddInstructionOrExpressionGroupMetadata(_("Physics Engine (deprecated)"))
.SetIcon("res/physics16.png");
extension.AddInstructionOrExpressionGroupMetadata(("Physics Engine (deprecated)"))
.SetIcon("res/physics-deprecated16.png");
{
gd::BehaviorMetadata& aut = extension.AddBehavior(
"PhysicsBehavior",
_("Physics Engine"),
_("Physics"),
_("Make objects move as if they are subject to the laws of physics. If "
("Physics Engine"),
("Physics"),
("Make objects move as if they are subject to the laws of physics. If "
"you're creating a new game, prefer Physics Engine 2.0"),
"",
"res/physics32.png",
"res/physics-deprecated32.png",
"PhysicsBehavior",
std::make_shared<PhysicsBehavior>(),
std::make_shared<ScenePhysicsDatas>());
aut.AddAction("SetStatic",
_("Make the object static"),
_("Make the object immovable."),
_("Make _PARAM0_ static"),
_("Movement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Make the object static"),
("Make the object immovable."),
("Make _PARAM0_ static"),
("Movement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetStatic");
aut.AddAction("SetDynamic",
_("Make the object dynamic"),
_("Make the object dynamic ( affected by forces and other "
("Make the object dynamic"),
("Make the object dynamic ( affected by forces and other "
"objects )."),
_("Make _PARAM0_ dynamic"),
_("Movement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Make _PARAM0_ dynamic"),
("Movement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetDynamic");
aut.AddCondition("IsDynamic",
_("The object is dynamic"),
_("Test if an object is dynamic ( affected by forces and "
("The object is dynamic"),
("Test if an object is dynamic ( affected by forces and "
"other objects )."),
_("_PARAM0_ is dynamic"),
_("Movement"),
"res/physics24.png",
"res/physics16.png")
("_PARAM0_ is dynamic"),
("Movement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.SetFunctionName("IsDynamic");
aut.AddAction("SetFixedRotation",
_("Fix rotation"),
_("Prevent the object from rotating"),
_("Fix rotation of _PARAM0_"),
_("Rotation"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Fix rotation"),
("Prevent the object from rotating"),
("Fix rotation of _PARAM0_"),
("Rotation"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetFixedRotation");
aut.AddAction(
"AddRevoluteJoint",
_("Add a hinge"),
_("Add a hinge that the object will rotate around.\nThe distance "
("Add a hinge"),
("Add a hinge that the object will rotate around.\nThe distance "
"between the hinge and the object will remain identical."),
_("Add a hinge to _PARAM0_ at _PARAM2_;_PARAM3_"),
_("Joints"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Hinge X position"))
.AddParameter("expression", _("Hinge Y position"))
("Add a hinge to _PARAM0_ at _PARAM2_;_PARAM3_"),
("Joints"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Hinge X position"))
.AddParameter("expression", ("Hinge Y position"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("AddRevoluteJoint");
aut.AddAction("AddRevoluteJointBetweenObjects",
_("Add a hinge between two objects"),
_("Add a hinge that the object will rotate around."),
_("Add a hinge between _PARAM0_ and _PARAM2_"),
_("Joints"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("objectPtr", _("Object"))
("Add a hinge between two objects"),
("Add a hinge that the object will rotate around."),
("Add a hinge between _PARAM0_ and _PARAM2_"),
("Joints"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("objectPtr", ("Object"))
.AddCodeOnlyParameter("currentScene", "")
.AddParameter(
"expression",
_("X position of the hinge, from the first object mass center"),
("X position of the hinge, from the first object mass center"),
"",
true)
.SetDefaultValue("0")
.AddParameter(
"expression",
_("Y position of the hinge, from the first object mass center"),
("Y position of the hinge, from the first object mass center"),
"",
true)
.SetDefaultValue("0")
.SetFunctionName("AddRevoluteJointBetweenObjects");
aut.AddAction("ActAddGearJointBetweenObjects",
_("Add a gear between two objects"),
_("Add a virtual gear between two objects."),
_("Add a gear between _PARAM0_ and _PARAM2_"),
_("Joints"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("objectPtr", _("Object"))
.AddParameter("expression", _("Ratio"), "", true)
("Add a gear between two objects"),
("Add a virtual gear between two objects."),
("Add a gear between _PARAM0_ and _PARAM2_"),
("Joints"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("objectPtr", ("Object"))
.AddParameter("expression", ("Ratio"), "", true)
.SetDefaultValue("1")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("AddGearJointBetweenObjects");
aut.AddAction("SetFreeRotation",
_("Make object's rotation free"),
_("Allows the object to rotate."),
_("Allow _PARAM0_ to rotate"),
_("Rotation"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Make object's rotation free"),
("Allows the object to rotate."),
("Allow _PARAM0_ to rotate"),
("Rotation"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetFreeRotation");
aut.AddCondition("IsFixedRotation",
_("Fixed rotation"),
_("Test if the object's rotation is fixed."),
_("The rotation of _PARAM0_ is fixed."),
_("Rotation"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Fixed rotation"),
("Test if the object's rotation is fixed."),
("The rotation of _PARAM0_ is fixed."),
("Rotation"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("IsFixedRotation");
aut.AddAction("SetAsBullet",
_("Treat object like a bullet."),
_("Treat the object like a bullet, so it will have better "
("Treat object like a bullet."),
("Treat the object like a bullet, so it will have better "
"collision handling."),
_("Consider _PARAM0_ as a bullet"),
_("Other"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Consider _PARAM0_ as a bullet"),
("Other"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAsBullet");
aut.AddAction("DontSetAsBullet",
_("Do not treat object like a bullet"),
_("Do not treat the object like a bullet, so it will use "
("Do not treat object like a bullet"),
("Do not treat the object like a bullet, so it will use "
"standard collision handling."),
_("Do not consider _PARAM0_ as a bullet."),
_("Other"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Do not consider _PARAM0_ as a bullet."),
("Other"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("DontSetAsBullet");
aut.AddCondition("IsBullet",
_("Object is treated like a bullet"),
_("Test if the object is treated like a bullet"),
_("_PARAM0_ is considered as a bullet"),
_("Other"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Object is treated like a bullet"),
("Test if the object is treated like a bullet"),
("_PARAM0_ is considered as a bullet"),
("Other"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("IsBullet");
aut.AddAction("ApplyImpulse",
_("Apply an impulse"),
_("Apply an impulse to the object."),
_("Apply to _PARAM0_ impulse _PARAM2_;_PARAM3_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("X component ( Newtons/Seconds )"))
.AddParameter("expression", _("Y component ( Newtons/Seconds )"))
("Apply an impulse"),
("Apply an impulse to the object."),
("Apply to _PARAM0_ impulse _PARAM2_;_PARAM3_"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("X component ( Newtons/Seconds )"))
.AddParameter("expression", ("Y component ( Newtons/Seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulse");
aut.AddAction("ApplyImpulseUsingPolarCoordinates",
_("Apply an impulse (angle)"),
_("Apply an impulse to an object, using an angle and a "
("Apply an impulse (angle)"),
("Apply an impulse to an object, using an angle and a "
"length as coordinates."),
_("Apply to _PARAM0_ impulse _PARAM3_ with angle: "
("Apply to _PARAM0_ impulse _PARAM3_ with angle: "
"_PARAM2_\302\260"), //\302\260 <=> DEGREE SIGN
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Angle"))
.AddParameter("expression", _("Impulse value ( Newton/seconds )"))
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Angle"))
.AddParameter("expression", ("Impulse value ( Newton/seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulseUsingPolarCoordinates");
aut.AddAction(
"ApplyImpulseTowardPosition",
_("Apply an impulse toward a position"),
_("Apply an impulse, directed toward a position, to the object."),
_("Apply to _PARAM0_ impulse _PARAM4_ toward position "
("Apply an impulse toward a position"),
("Apply an impulse, directed toward a position, to the object."),
("Apply to _PARAM0_ impulse _PARAM4_ toward position "
"_PARAM2_;_PARAM3_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("X position"))
.AddParameter("expression", _("Y position"))
.AddParameter("expression", _("Impulse value ( Newton/seconds )"))
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("X position"))
.AddParameter("expression", ("Y position"))
.AddParameter("expression", ("Impulse value ( Newton/seconds )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyImpulseTowardPosition");
aut.AddAction("ApplyForce",
_("Add a force"),
_("Add a force to the object"),
_("Apply to _PARAM0_ force _PARAM2_;_PARAM3_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("X component ( Newtons )"))
.AddParameter("expression", _("Y component ( Newtons )"))
("Add a force"),
("Add a force to the object"),
("Apply to _PARAM0_ force _PARAM2_;_PARAM3_"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("X component ( Newtons )"))
.AddParameter("expression", ("Y component ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForce");
aut.AddAction("ApplyForceUsingPolarCoordinates",
_("Apply a force ( angle )"),
_("Apply a force to an object, using an angle and a length "
("Apply a force ( angle )"),
("Apply a force to an object, using an angle and a length "
"as coordinates."),
_("Apply to _PARAM0_ force _PARAM3_ at angle _PARAM2_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Angle"))
.AddParameter("expression", _("Length of the force ( Newtons )"))
("Apply to _PARAM0_ force _PARAM3_ at angle _PARAM2_"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Angle"))
.AddParameter("expression", ("Length of the force ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForceUsingPolarCoordinates");
aut.AddAction(
"ApplyForceTowardPosition",
_("Apply a force toward a position"),
_("Apply a force, directed toward a position, to the object."),
_("Add to _PARAM0_ force _PARAM4_ toward position "
("Apply a force toward a position"),
("Apply a force, directed toward a position, to the object."),
("Add to _PARAM0_ force _PARAM4_ toward position "
"_PARAM2_;_PARAM3_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("X position"))
.AddParameter("expression", _("Y position"))
.AddParameter("expression", _("Length of the force ( Newtons )"))
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("X position"))
.AddParameter("expression", ("Y position"))
.AddParameter("expression", ("Length of the force ( Newtons )"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyForceTowardPosition");
aut.AddAction("ApplyTorque",
_("Add a torque (a rotation)"),
_("Add a torque (a rotation) to the object."),
_("Add to _PARAM0_ torque _PARAM2_"),
_("Rotation"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Torque value"))
("Add a torque (a rotation)"),
("Add a torque (a rotation) to the object."),
("Add to _PARAM0_ torque _PARAM2_"),
("Rotation"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Torque value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("ApplyTorque");
aut.AddAction("SetLinearVelocity",
_("Linear velocity"),
_("Modify the velocity of an object."),
_("Set linear velocity of _PARAM0_ to _PARAM2_;_PARAM3_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("X Coordinate"))
.AddParameter("expression", _("Y Coordinate"))
("Linear velocity"),
("Modify the velocity of an object."),
("Set linear velocity of _PARAM0_ to _PARAM2_;_PARAM3_"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("X Coordinate"))
.AddParameter("expression", ("Y Coordinate"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetLinearVelocity");
aut.AddCondition(
"LinearVelocityX",
_("X component"),
_("Compare the linear velocity on the X axis of the object."),
_("the linear velocity on X axis"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("X component"),
("Compare the linear velocity on the X axis of the object."),
("the linear velocity on X axis"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
@@ -344,184 +344,184 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddCondition(
"LinearVelocityY",
_("Y component"),
_("Compare the linear velocity on the Y axis of the object."),
_("the linear velocity on Y axis"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Y component"),
("Compare the linear velocity on the Y axis of the object."),
("the linear velocity on Y axis"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityY");
aut.AddCondition("LinearVelocity",
_("Linear speed"),
_("Compare the linear velocity of the object."),
_("the linear velocity"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Linear speed"),
("Compare the linear velocity of the object."),
("the linear velocity"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocity");
aut.AddAction("SetAngularVelocity",
_("Angular speed"),
_("Modify the angular velocity of the object."),
_("Set angular speed of _PARAM0_ to _PARAM2_"),
_("Rotation"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("New value"))
("Angular speed"),
("Modify the angular velocity of the object."),
("Set angular speed of _PARAM0_ to _PARAM2_"),
("Rotation"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("New value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAngularVelocity");
aut.AddCondition("AngularVelocity",
_("Angular speed"),
_("Compare the angular speed of the object."),
_("the angular speed"),
_("Rotation"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Angular speed"),
("Compare the angular speed of the object."),
("the angular speed"),
("Rotation"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularVelocity");
aut.AddCondition("LinearDamping",
_("Linear damping"),
_("Compare the linear damping of the object."),
_("the linear damping"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Linear damping"),
("Compare the linear damping of the object."),
("the linear damping"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearDamping");
aut.AddCondition("CollisionWith",
_("Collision"),
_("Test if two objects are colliding.\nAttention! Only "
("Collision"),
("Test if two objects are colliding.\nAttention! Only "
"objects specified in the first parameter will be taken "
"into account by the next actions and conditions, if "
"they are colliding with the other objects."),
_("_PARAM0_ is in collision with a _PARAM2_"),
("_PARAM0_ is in collision with a _PARAM2_"),
"",
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("objectList", _("Object"))
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("objectList", ("Object"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("CollisionWith");
aut.AddAction("SetLinearDamping",
_("Linear damping"),
_("Modify the linear damping of the object."),
_("Set linear damping of _PARAM0_ to _PARAM2_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Value"))
("Linear damping"),
("Modify the linear damping of the object."),
("Set linear damping of _PARAM0_ to _PARAM2_"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetLinearDamping");
aut.AddCondition("AngularDamping",
_("Angular damping"),
_("Test the object's angular damping"),
_("the angular damping"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Angular damping"),
("Test the object's angular damping"),
("the angular damping"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularDamping");
aut.AddAction("SetAngularDamping",
_("Angular damping"),
_("Modify the angular damping of the object."),
_("Set angular damping of _PARAM0_ to _PARAM2_"),
_("Displacement"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Value"))
("Angular damping"),
("Modify the angular damping of the object."),
("Set angular damping of _PARAM0_ to _PARAM2_"),
("Displacement"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Value"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetAngularDamping");
aut.AddAction("SetGravity",
_("Gravity"),
_("Modify the gravity"),
_("Set gravity force to _PARAM2_;_PARAM3_"),
_("Global options"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("X Coordinate"))
.AddParameter("expression", _("Y Coordinate"))
("Gravity"),
("Modify the gravity"),
("Set gravity force to _PARAM2_;_PARAM3_"),
("Global options"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("X Coordinate"))
.AddParameter("expression", ("Y Coordinate"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetGravity");
aut.AddAction("SetPolygonScaleX",
_("Change the X scale of a collision polygon"),
_("Change the X scale of the polygon. Use a value greater "
("Change the X scale of a collision polygon"),
("Change the X scale of the polygon. Use a value greater "
"than 1 to enlarge the polygon, less than 1 to reduce it."),
_("Change the X scale of the collision polygon of _PARAM0_ "
("Change the X scale of the collision polygon of _PARAM0_ "
"to _PARAM2_"),
_("Collision polygon"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Scale"))
("Collision polygon"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Scale"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetPolygonScaleX");
aut.AddAction("SetPolygonScaleY",
_("Change the Y scale of a collision polygon"),
_("Change the Y scale of the polygon. Use a value greater "
("Change the Y scale of a collision polygon"),
("Change the Y scale of the polygon. Use a value greater "
"than 1 to enlarge the polygon, less than 1 to reduce it."),
_("Change the Y scale of the collision polygon of _PARAM0_ Y "
("Change the Y scale of the collision polygon of _PARAM0_ Y "
"to _PARAM2_"),
_("Collision polygon"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
.AddParameter("expression", _("Scale"))
("Collision polygon"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddParameter("expression", ("Scale"))
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("SetPolygonScaleY");
aut.AddCondition(
"GetPolygonScaleX",
_("Collision polygon X scale"),
_("Test the value of the X scale of the collision polygon."),
_("the X scale of the collision polygon"),
_("Collision polygon"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Collision polygon X scale"),
("Test the value of the X scale of the collision polygon."),
("the X scale of the collision polygon"),
("Collision polygon"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
@@ -529,96 +529,96 @@ void DeclarePhysicsBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddCondition(
"GetPolygonScaleY",
_("Collision polygon Y scale"),
_("Test the value of the Y scale of the collision polygon."),
_("the Y scale of the collision polygon"),
_("Collision polygon"),
"res/physics24.png",
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Collision polygon Y scale"),
("Test the value of the Y scale of the collision polygon."),
("the Y scale of the collision polygon"),
("Collision polygon"),
"res/physics-deprecated24.png",
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.UseStandardRelationalOperatorParameters(
"number", gd::ParameterOptions::MakeNewOptions())
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleY");
aut.AddExpression("PolygonScaleX",
_("Collision polygon X scale"),
_("Collision polygon X scale"),
_("Collision polygon"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Collision polygon X scale"),
("Collision polygon X scale"),
("Collision polygon"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleX");
aut.AddExpression("PolygonScaleY",
_("Collision polygon Y scale"),
_("Collision polygon Y scale"),
_("Collision polygon"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Collision polygon Y scale"),
("Collision polygon Y scale"),
("Collision polygon"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetPolygonScaleY");
aut.AddExpression("LinearVelocity",
_("Linear speed"),
_("Linear speed"),
_("Displacement"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Linear speed"),
("Linear speed"),
("Displacement"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocity");
aut.AddExpression("LinearVelocityX",
_("X component"),
_("X component"),
_("Displacement"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("X component"),
("X component"),
("Displacement"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityX");
aut.AddExpression("LinearVelocityY",
_("Y component"),
_("Y component"),
_("Displacement"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Y component"),
("Y component"),
("Displacement"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearVelocityY");
aut.AddExpression("AngularVelocity",
_("Angular speed"),
_("Angular speed"),
_("Rotation"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Angular speed"),
("Angular speed"),
("Rotation"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularVelocity");
aut.AddExpression("LinearDamping",
_("Linear damping"),
_("Linear damping"),
_("Displacement"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Linear damping"),
("Linear damping"),
("Displacement"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetLinearDamping");
aut.AddExpression("AngularDamping",
_("Angular damping"),
_("Angular damping"),
_("Rotation"),
"res/physics16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PhysicsBehavior")
("Angular damping"),
("Angular damping"),
("Rotation"),
"res/physics-deprecated16.png")
.AddParameter("object", ("Object"))
.AddParameter("behavior", ("Behavior"), "PhysicsBehavior")
.AddCodeOnlyParameter("currentScene", "")
.SetFunctionName("GetAngularDamping");
}

View File

@@ -50,7 +50,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if the object is moving (whether it is on the "
"floor or in the air)."),
_("_PARAM0_ is moving"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -64,7 +64,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Check if the object is moving (whether it is on the "
"floor or in the air)."),
_("_PARAM0_ is moving"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -75,7 +75,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Is on floor"),
_("Check if the object is on a platform."),
_("_PARAM0_ is on floor"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -87,7 +87,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Is on ladder"),
_("Check if the object is on a ladder."),
_("_PARAM0_ is on ladder"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -99,7 +99,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Is jumping"),
_("Check if the object is jumping."),
_("_PARAM0_ is jumping"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -114,7 +114,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"flagged as jumping and falling at the same time: at the end of a "
"jump, the fall speed becomes higher than the jump speed."),
_("_PARAM0_ is falling"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -125,7 +125,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Is grabbing platform ledge"),
_("Check if the object is grabbing a platform ledge."),
_("_PARAM0_ is grabbing a platform ledge"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -136,7 +136,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Gravity"),
_("Compare the gravity applied on the object."),
_("the gravity"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -153,7 +153,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Gravity"),
_("Change the gravity applied on an object."),
_("the gravity"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -171,7 +171,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum falling speed"),
_("Compare the maximum falling speed of the object."),
_("the maximum falling speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -187,7 +187,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum falling speed"),
_("Change the maximum falling speed of an object."),
_("the maximum falling speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -208,7 +208,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the ladder climbing speed (in pixels per "
"second)."),
_("the ladder climbing speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -224,7 +224,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Ladder climbing speed"),
_("Change the ladder climbing speed."),
_("the ladder climbing speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -241,7 +241,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Compare the horizontal acceleration of the object."),
_("the horizontal acceleration"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -258,7 +258,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Change the horizontal acceleration of an object."),
_("the horizontal acceleration"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -276,7 +276,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Deceleration"),
_("Compare the horizontal deceleration of the object."),
_("the horizontal deceleration"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -293,7 +293,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Deceleration"),
_("Change the horizontal deceleration of an object."),
_("the horizontal deceleration"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -311,7 +311,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum horizontal speed"),
_("Compare the maximum horizontal speed of the object."),
_("the maximum horizontal speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -326,7 +326,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum horizontal speed"),
_("Change the maximum horizontal speed of an object."),
_("the maximum horizontal speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -344,7 +344,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the jump speed of the object."
"Its value is always positive."),
_("the jump speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -361,7 +361,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the jump speed of an object. "
"Its value is always positive."),
_("the jump speed"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -380,7 +380,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"This is the time during which keeping the jump button held "
"allow the initial jump speed to be maintained."),
_("the jump sustain time"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -398,7 +398,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"This is the time during which keeping the jump button held "
"allow the initial jump speed to be maintained."),
_("the jump sustain time"),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -417,7 +417,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"again this action everytime you want to allow the object to jump "
"(apart if it's on the floor)."),
_("Allow _PARAM0_ to jump again"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -432,7 +432,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"is made unable to jump while in mid air. This has no effect if "
"the object is not in the air."),
_("Forbid _PARAM0_ to air jump"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -445,7 +445,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"This action doesn't have any effect when the character is not "
"jumping."),
_("Abort the current jump of _PARAM0_"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -455,7 +455,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Can jump"),
_("Check if the object can jump."),
_("_PARAM0_ can jump"),
"",
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -467,7 +467,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate left key press"),
_("Simulate a press of the left key."),
_("Simulate pressing Left for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -479,7 +479,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate right key press"),
_("Simulate a press of the right key."),
_("Simulate pressing Right for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -491,7 +491,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate up key press"),
_("Simulate a press of the up key (used when on a ladder)."),
_("Simulate pressing Up for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -504,7 +504,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate down key press"),
_("Simulate a press of the down key (used when on a ladder)."),
_("Simulate pressing Down for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -517,7 +517,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate ladder key press"),
_("Simulate a press of the ladder key (used to grab a ladder)."),
_("Simulate pressing Ladder key for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -530,7 +530,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate release ladder key press"),
_("Simulate a press of the Release Ladder key (used to get off a ladder)."),
_("Simulate pressing Release Ladder key for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -541,7 +541,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate jump key press"),
_("Simulate a press of the jump key."),
_("Simulate pressing Jump key for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -553,7 +553,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate a press of the release platform key (used when grabbing a "
"platform ledge)."),
_("Simulate pressing Release Platform key for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -568,7 +568,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate a press of a key.\nValid keys are Left, Right, "
"Jump, Ladder, Release Ladder, Up, Down."),
_("Simulate pressing _PARAM2_ key for _PARAM0_"),
_("Controls"),
_("Platformer controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -583,7 +583,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Control pressed or simulated"),
_("A control was applied from a default control or simulated by an action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_(""),
_("Platformer state"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -598,7 +598,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("De/activate the use of default controls.\nIf deactivated, "
"use the simulated actions to move the object."),
_("Ignore default controls for _PARAM0_: _PARAM2_"),
_("Options"),
_("Platformer configuration"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -612,7 +612,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Enable (or disable) the ability of the object to grab "
"platforms when falling near to one."),
_("Allow _PARAM0_ to grab platforms: _PARAM2_"),
_("Options"),
_("Platformer configuration"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -624,7 +624,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Can grab platforms"),
_("Check if the object can grab the platforms."),
_("_PARAM0_ can grab the platforms"),
"Options",
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -637,7 +637,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the current falling speed of the object. Its "
"value is always positive."),
_("the current falling speed"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -656,7 +656,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"doesn't have any effect when the character "
"is not falling or is in the first phase of a jump."),
_("the current falling speed"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -672,7 +672,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the current jump speed of the object. Its "
"value is always positive."),
_("the current jump speed"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -691,7 +691,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"moves to the left with negative values and to the right with "
"positive ones"),
_("the current horizontal speed"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -709,7 +709,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
"moves to the left "
"with negative values and to the right with positive ones"),
_("the current horizontal speed"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png",
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
@@ -725,7 +725,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Gravity"),
_("Return the gravity applied on the object "
"(in pixels per second per second)."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -735,7 +735,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum falling speed"),
_("Return the maximum falling speed of the object "
"(in pixels per second)."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -745,7 +745,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Ladder climbing speed"),
_("Return the ladder climbing speed of the object "
"(in pixels per second)."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -755,7 +755,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Return the horizontal acceleration of the object "
"(in pixels per second per second)."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -765,7 +765,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Deceleration"),
_("Return the horizontal deceleration of the object "
"(in pixels per second per second)."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -775,7 +775,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum horizontal speed"),
_("Return the maximum horizontal speed of the object "
"(in pixels per second)."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -786,7 +786,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Jump speed"),
_("Return the jump speed of the object "
"(in pixels per second). Its value is always positive."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -797,7 +797,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Return the jump sustain time of the object (in seconds)."
"This is the time during which keeping the jump button held "
"allow the initial jump speed to be maintained."),
_("Options"),
_("Platformer configuration"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior");
@@ -806,7 +806,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Current fall speed"),
_("Return the current fall speed of the object "
"(in pixels per second). Its value is always positive."),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -817,17 +817,17 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Return the current horizontal speed of the object "
"(in pixels per second). The object moves to the left "
"with negative values and to the right with positive ones"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
.SetFunctionName("GetCurrentSpeed");
aut.AddExpression("CurrentJumpSpeed",
_("Current jump speed"),
_("Return the current jump speed of the object "
"(in pixels per second). Its value is always positive."),
_("Current jump speed"),
_(""),
_("Platformer state"),
"CppPlatform/Extensions/platformerobjecticon.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
@@ -850,7 +850,7 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
_("Change the platform type of the object: Platform, "
"Jump-Through, or Ladder."),
_("Set platform type of _PARAM0_ to _PARAM2_"),
"",
_("Platform"),
"CppPlatform/Extensions/platformicon.png",
"CppPlatform/Extensions/platformicon.png")
.AddParameter("object", _("Object"))
@@ -863,10 +863,10 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
}
extension.AddCondition("IsObjectOnGivenFloor",
_("Is object on given floor"),
_("Test if an object is on a given floor."),
_("_PARAM0_ is on floor _PARAM2_"),
"",
_("Character is on given platform"),
_("Check if a platformer character is on a given platform."),
_("_PARAM0_ is on platform _PARAM2_"),
_("Collision"),
"CppPlatform/Extensions/platformicon.png",
"CppPlatform/Extensions/platformicon.png")
.AddParameter("objectList", _("Object"), "", false)

View File

@@ -48,6 +48,7 @@ describe('gdjs.ShapePainterRuntimeObject (using a PixiJS RuntimeGame with assets
objects: [],
instances: [],
variables: [],
usedResources: [],
});
};

View File

@@ -67,9 +67,9 @@ module.exports = {
extension
.addAction(
'SetListenerPosition',
_('Set position of the listener'),
_('Sets the spatial position of the listener/player.'),
_('Set the listener position to _PARAM0_, _PARAM1_, _PARAM2_'),
_('Listener position'),
_('Change the spatial position of the listener/player.'),
_('Change the listener position to _PARAM0_, _PARAM1_, _PARAM2_'),
'',
'res/actions/son24.png',
'res/actions/son.png'

View File

@@ -178,7 +178,7 @@ module.exports = {
extension
.addAction(
'SetRichPresence',
_('Change the Steam rich presence'),
_('Steam rich presence'),
_(
"Changes an attribute of Steam's rich presence. Allows other player to see exactly what the player's currently doing in the game."
),

View File

@@ -55,6 +55,7 @@ describe('gdjs.TextInputRuntimeObject (using a PixiJS RuntimeGame with DOM eleme
objects: [],
instances: [],
variables: [],
usedResources: [],
});
};

View File

@@ -26,11 +26,12 @@ namespace gdjs {
);
};
class TextInputRuntimeObjectPixiRenderer {
class TextInputRuntimeObjectPixiRenderer implements RendererObjectInterface {
private _object: gdjs.TextInputRuntimeObject;
private _input: HTMLInputElement | HTMLTextAreaElement | null = null;
private _instanceContainer: gdjs.RuntimeInstanceContainer;
private _runtimeGame: gdjs.RuntimeGame;
private _isVisible = false;
constructor(
runtimeObject: gdjs.TextInputRuntimeObject,
@@ -113,14 +114,25 @@ namespace gdjs {
this._destroyElement();
}
//@ts-ignore
set visible(isVisible: boolean) {
this._isVisible = isVisible;
if (!this._input) return;
this._input.style.display = isVisible ? 'initial' : 'none';
}
//@ts-ignore
get visible(): boolean {
return this._isVisible;
}
updatePreRender() {
if (!this._input) return;
// Hide the input entirely if the object is hidden.
// Because this object is rendered as a DOM element (and not part of the PixiJS
// scene graph), we have to do this manually.
if (this._object.isHidden()) {
this._input.style.display = 'none';
if (!this._isVisible) {
return;
}

View File

@@ -102,7 +102,8 @@ namespace gdjs {
}
getRendererObject() {
return null;
// The renderer is not a Pixi Object but it implements visible.
return this._renderer;
}
updateFromObjectData(

View File

@@ -58,6 +58,7 @@ describe('gdjs.TileMapCollisionMaskRuntimeObject', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
setFramesPerSecond(runtimeScene, framePerSecond);
return runtimeScene;

View File

@@ -38,12 +38,11 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
std::make_shared<TopDownMovementBehavior>(),
std::make_shared<gd::BehaviorsSharedData>());
#if defined(GD_IDE_ONLY)
aut.AddAction("SimulateLeftKey",
_("Simulate left key press"),
_("Simulate a press of left key."),
_("Simulate pressing Left for _PARAM0_"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -55,7 +54,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate right key press"),
_("Simulate a press of right key."),
_("Simulate pressing Right for _PARAM0_"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -67,7 +66,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate up key press"),
_("Simulate a press of up key."),
_("Simulate pressing Up for _PARAM0_"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -79,7 +78,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate down key press"),
_("Simulate a press of down key."),
_("Simulate pressing Down for _PARAM0_"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -92,7 +91,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate control"),
_("Simulate a press of a key.\nValid keys are Left, Right, Up, Down."),
_("Simulate pressing _PARAM2_ key for _PARAM0_"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -108,7 +107,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("De/activate the use of default controls.\nIf deactivated, "
"use the simulated actions to move the object."),
_("Ignore default controls for _PARAM0_: _PARAM2_"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -121,7 +120,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Simulate stick control"),
_("Simulate a stick control."),
_("Simulate a stick control for _PARAM0_ with a _PARAM2_ angle and a _PARAM3_ force"),
_("Controls"),
_("Top-down controls"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -135,7 +134,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Control pressed or simulated"),
_("A control was applied from a default control or simulated by an action."),
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
_("Controls"),
_("Top-down state"),
"res/conditions/keyboard24.png",
"res/conditions/keyboard.png")
.AddParameter("object", _("Object"))
@@ -148,7 +147,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("StickAngle",
_("Stick angle"),
_("Return the angle of the simulated stick input (in degrees)"),
_("Controls"),
_("Top-down controls"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior");
@@ -157,7 +156,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Is moving"),
_("Check if the object is moving."),
_("_PARAM0_ is moving"),
"",
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -168,7 +167,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Change the acceleration of the object"),
_("the acceleration"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -185,7 +184,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Acceleration"),
_("Compare the acceleration of the object"),
_("the acceleration"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -201,7 +200,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Deceleration"),
_("Change the deceleration of the object"),
_("the deceleration"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -218,7 +217,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Deceleration"),
_("Compare the deceleration of the object"),
_("the deceleration"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -234,7 +233,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum speed"),
_("Change the maximum speed of the object"),
_("the max. speed"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -250,7 +249,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Maximum speed"),
_("Compare the maximum speed of the object"),
_("the max. speed"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -266,7 +265,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Speed"),
_("Compare the speed of the object"),
_("the speed"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -281,7 +280,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Angular maximum speed"),
_("Change the maximum angular speed of the object"),
_("the max. angular speed"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -298,7 +297,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Angular maximum speed"),
_("Compare the maximum angular speed of the object"),
_("the max. angular speed"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -314,7 +313,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotation offset"),
_("Change the rotation offset applied when moving the object"),
_("the rotation offset"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -332,7 +331,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotation offset"),
_("Compare the rotation offset applied when moving the object"),
_("the rotation offset"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -344,12 +343,13 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
.MarkAsAdvanced()
.SetFunctionName("GetAngleOffset");
// Deprecated
aut.AddCondition(
"Angle",
_("Angle of movement"),
_("Compare the angle of the top-down movement of the object."),
_("the angle of movement"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -367,7 +367,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Angle of movement"),
_("Compare the angle of the top-down movement of the object."),
_("Angle of movement of _PARAM0_ is _PARAM2_ ± _PARAM3_°"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -380,7 +380,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the velocity of the top-down movement of the "
"object on the X axis."),
_("the speed of movement on X axis"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -396,7 +396,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Speed on the X axis"),
_("Change the speed on the X axis of the movement"),
_("the speed on the X axis of the movement"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -412,7 +412,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Compare the velocity of the top-down movement of the "
"object on the Y axis."),
_("the speed of movement on Y axis"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -428,7 +428,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Speed on the Y axis"),
_("Change the speed on the Y axis of the movement"),
_("the speed on the Y axis of the movement"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -443,7 +443,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Diagonal movement"),
_("Allow or restrict diagonal movement"),
_("Allow diagonal moves for _PARAM0_: _PARAM2_"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -455,7 +455,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Diagonal movement"),
_("Check if the object is allowed to move diagonally"),
_("Allow diagonal moves for _PARAM0_"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -467,7 +467,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Rotate the object"),
_("Enable or disable rotation of the object"),
_("Enable rotation of _PARAM0_: _PARAM2_"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -481,7 +481,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Object rotated"),
_("Check if the object is rotated while traveling on its path."),
_("_PARAM0_ is rotated when moving"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png",
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
@@ -492,7 +492,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("Acceleration",
_("Acceleration"),
_("Acceleration of the object"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -501,7 +501,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("Deceleration",
_("Deceleration"),
_("Deceleration of the object"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -510,7 +510,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("MaxSpeed",
_("Maximum speed"),
_("Maximum speed of the object"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -519,7 +519,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("Speed",
_("Speed"),
_("Speed of the object"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -528,7 +528,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("AngularMaxSpeed",
_("Angular maximum speed"),
_("Angular maximum speed of the object"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -537,7 +537,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("AngleOffset",
_("Rotation offset"),
_("Rotation offset applied to the object"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -546,7 +546,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("Angle",
_("Angle of the movement"),
_("Angle, in degrees, of the movement"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -555,7 +555,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("XVelocity",
_("Speed on the X axis"),
_("Speed on the X axis of the movement"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -564,7 +564,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
aut.AddExpression("YVelocity",
_("Speed on the Y axis"),
_("Speed on the Y axis of the movement"),
_("Movement"),
_("Top-down state"),
"CppPlatform/Extensions/topdownmovementicon16.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -576,7 +576,7 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
_("Movement angle offset"),
_("the movement angle offset"),
_("the movement angle offset"),
_("Movement"),
_("Top-down configuration"),
"CppPlatform/Extensions/topdownmovementicon24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TopDownMovementBehavior")
@@ -584,5 +584,4 @@ void DeclareTopDownMovementBehaviorExtension(gd::PlatformExtension& extension) {
"number",
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Angle (in degrees)")));
#endif
}

View File

@@ -32,6 +32,7 @@ describe('gdjs.TopDownMovementRuntimeBehavior', function () {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
runtimeScene._timeManager.getElapsedTime = function () {
return timeDelta;

View File

@@ -203,7 +203,7 @@ module.exports = {
object
.addAction(
'SetTime',
_('Set time'),
_('Current time'),
_('Set the time of the video'),
_('the time'),
'',
@@ -224,7 +224,7 @@ module.exports = {
object
.addAction(
'SetVolume',
_('Set volume'),
_('Volume'),
_('Set the volume of the video object.'),
_('the volume'),
'',

View File

@@ -39,6 +39,15 @@ SceneExtension::SceneExtension() {
GetAllConditions()["DoesSceneExist"].SetFunctionName(
"gdjs.evtTools.runtimeScene.doesSceneExist");
GetAllActions()["PrioritizeLoadingOfScene"].SetFunctionName(
"gdjs.evtTools.runtimeScene.prioritizeLoadingOfScene");
GetAllConditions()["AreSceneAssetsLoaded"].SetFunctionName(
"gdjs.evtTools.runtimeScene.areSceneAssetsLoaded");
GetAllConditions()["SceneLoadingProgress"].SetFunctionName(
"gdjs.evtTools.runtimeScene.getSceneLoadingProgress");
GetAllExpressions()["SceneLoadingProgress"].SetFunctionName(
"gdjs.evtTools.runtimeScene.getSceneLoadingProgress");
StripUnimplementedInstructionsAndExpressions();
}

View File

@@ -15,6 +15,7 @@
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
#include "GDCore/IDE/ProjectStripper.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
@@ -129,14 +130,26 @@ 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");
// Export a WebManifest with project metadata

View File

@@ -26,6 +26,7 @@
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
#include "GDCore/IDE/ExportedDependencyResolver.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
#include "GDCore/IDE/ProjectStripper.h"
#include "GDCore/IDE/SceneNameMangler.h"
#include "GDCore/Project/EventsBasedObject.h"
@@ -187,6 +188,17 @@ bool ExporterHelper::ExportProjectForPixiPreview(
previousTime = LogTimeSpent("Events code export", previousTime);
}
auto projectUsedResources =
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
for (std::size_t layoutIndex = 0;
layoutIndex < exportedProject.GetLayoutsCount(); layoutIndex++) {
auto &layout = exportedProject.GetLayout(layoutIndex);
scenesUsedResources[layout.GetName()] =
gd::SceneResourcesFinder::FindSceneResources(exportedProject,
layout);
}
// Strip the project (*after* generating events as the events may use stripped
// things (objects groups...))
gd::ProjectStripper::StripProjectForExport(exportedProject);
@@ -234,8 +246,9 @@ bool ExporterHelper::ExportProjectForPixiPreview(
}
// Export the project
ExportProjectData(
fs, exportedProject, codeOutputDir + "/data.js", runtimeGameOptions);
ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
runtimeGameOptions, projectUsedResources,
scenesUsedResources);
includesFiles.push_back(codeOutputDir + "/data.js");
previousTime = LogTimeSpent("Project data export", previousTime);
@@ -259,14 +272,17 @@ bool ExporterHelper::ExportProjectForPixiPreview(
gd::String ExporterHelper::ExportProjectData(
gd::AbstractFileSystem &fs,
const gd::Project &project,
gd::Project &project,
gd::String filename,
const gd::SerializerElement &runtimeGameOptions) {
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);
gd::String output =
"gdjs.projectData = " + gd::Serializer::ToJSON(rootElement) + ";\n" +
"gdjs.runtimeGameOptions = " +
@@ -277,6 +293,35 @@ gd::String ExporterHelper::ExportProjectData(
return "";
}
void ExporterHelper::SerializeUsedResources(
gd::SerializerElement &rootElement,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
auto serializeUsedResources =
[](gd::SerializerElement &element,
std::set<gd::String> &usedResources) -> void {
auto &resourcesElement = element.AddChild("usedResources");
resourcesElement.ConsiderAsArrayOf("resourceReference");
for (auto &resourceName : usedResources) {
auto &resourceElement = resourcesElement.AddChild("resourceReference");
resourceElement.SetAttribute("name", resourceName);
}
};
serializeUsedResources(rootElement, projectUsedResources);
auto &layoutsElement = rootElement.GetChild("layouts");
for (std::size_t layoutIndex = 0;
layoutIndex < layoutsElement.GetChildrenCount(); layoutIndex++) {
auto &layoutElement = layoutsElement.GetChild(layoutIndex);
const auto layoutName = layoutElement.GetStringAttribute("name");
auto &layoutUsedResources = scenesUsedResources[layoutName];
serializeUsedResources(layoutElement, layoutUsedResources);
}
}
bool ExporterHelper::ExportPixiIndexFile(
const gd::Project &project,
gd::String source,
@@ -621,8 +666,12 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "inputmanager.js");
InsertUnique(includesFiles, "jsonmanager.js");
InsertUnique(includesFiles, "Model3DManager.js");
InsertUnique(includesFiles, "ResourceLoader.js");
InsertUnique(includesFiles, "ResourceCache.js");
InsertUnique(includesFiles, "timemanager.js");
InsertUnique(includesFiles, "polygon.js");
InsertUnique(includesFiles, "ObjectSleepState.js");
InsertUnique(includesFiles, "ObjectManager.js");
InsertUnique(includesFiles, "runtimeobject.js");
InsertUnique(includesFiles, "profiler.js");
InsertUnique(includesFiles, "RuntimeInstanceContainer.js");

View File

@@ -3,12 +3,13 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EXPORTER_HELPER_H
#define EXPORTER_HELPER_H
#pragma once
#include <map>
#include <set>
#include <string>
#include <vector>
#include <unordered_map>
#include "GDCore/String.h"
namespace gd {
@@ -264,11 +265,12 @@ class ExporterHelper {
* in gdjs.runtimeGameOptions \return Empty string if everything is ok,
* description of the error otherwise.
*/
static gd::String ExportProjectData(
gd::AbstractFileSystem &fs,
const gd::Project &project,
gd::String filename,
const gd::SerializerElement &runtimeGameOptions);
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,
@@ -474,7 +476,12 @@ class ExporterHelper {
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.
private:
static void SerializeUsedResources(
gd::SerializerElement &rootElement,
std::set<gd::String> &projectUsedResources,
std::unordered_map<gd::String, std::set<gd::String>> &layersUsedResources);
};
} // namespace gdjs
#endif // EXPORTER_HELPER_H

View File

@@ -5,20 +5,21 @@
*/
namespace gdjs {
const logger = new gdjs.Logger('Model3DManager');
type OnProgressCallback = (loadedCount: integer, totalCount: integer) => void;
const resourceKinds: Array<ResourceKind> = ['model3D'];
/**
* Load GLB files (using `Three.js`), using the "model3D" resources
* registered in the game resources.
*/
export class Model3DManager {
export class Model3DManager implements gdjs.ResourceManager {
/**
* Map associating a resource name to the loaded Three.js model.
*/
private _loadedThreeModels = new Map<String, THREE_ADDONS.GLTF>();
private _loadedThreeModels = new gdjs.ResourceCache<THREE_ADDONS.GLTF>();
private _downloadedArrayBuffers = new gdjs.ResourceCache<ArrayBuffer>();
_resourcesLoader: RuntimeGameResourcesLoader;
_resources: Map<string, ResourceData>;
_resourceLoader: gdjs.ResourceLoader;
_loader: THREE_ADDONS.GLTFLoader | null = null;
_dracoLoader: THREE_ADDONS.DRACOLoader | null = null;
@@ -28,15 +29,10 @@ namespace gdjs {
/**
* @param resourceDataArray The resources data of the game.
* @param resourcesLoader The resources loader of the game.
* @param resourceLoader The resources loader of the game.
*/
constructor(
resourceDataArray: ResourceData[],
resourcesLoader: RuntimeGameResourcesLoader
) {
this._resources = new Map<string, ResourceData>();
this.setResources(resourceDataArray);
this._resourcesLoader = resourcesLoader;
constructor(resourceLoader: gdjs.ResourceLoader) {
this._resourceLoader = resourceLoader;
if (typeof THREE !== 'undefined') {
this._loader = new THREE_ADDONS.GLTFLoader();
@@ -69,17 +65,34 @@ namespace gdjs {
}
}
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param resourceDataArray The resources data of the game.
*/
setResources(resourceDataArray: ResourceData[]): void {
this._resources.clear();
for (const resourceData of resourceDataArray) {
if (resourceData.kind === 'model3D') {
this._resources.set(resourceData.name, resourceData);
}
getResourceKinds(): ResourceKind[] {
return resourceKinds;
}
async processResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find texture for resource "' + resourceName + '".'
);
return;
}
const loader = this._loader;
if (!loader) {
return;
}
const data = this._downloadedArrayBuffers.get(resource);
if (!data) {
return;
}
this._downloadedArrayBuffers.delete(resource);
try {
const gltf: THREE_ADDONS.GLTF = await loader.parseAsync(data, '');
this._loadedThreeModels.set(resource, gltf);
} catch (error) {
logger.error(
"Can't fetch the 3D model file " + resource.file + ', error: ' + error
);
}
}
@@ -88,42 +101,36 @@ namespace gdjs {
*
* Note that even if a file is already loaded, it will be reloaded (useful for hot-reloading,
* as files can have been modified without the editor knowing).
*
* @param onProgress The function called after each file is loaded.
* @param onComplete The function called when all file are loaded.
*/
async loadModels(onProgress: OnProgressCallback): Promise<integer> {
const loader = this._loader;
if (this._resources.size === 0 || !loader) {
return 0;
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find texture for resource "' + resourceName + '".'
);
return;
}
const loader = this._loader;
if (!loader) {
return;
}
const url = this._resourceLoader.getFullUrl(resource.file);
try {
const response = await fetch(url, {
credentials: this._resourceLoader.checkIfCredentialsRequired(url)
? 'include'
: 'omit',
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.arrayBuffer();
this._downloadedArrayBuffers.set(resource, data);
} catch (error) {
logger.error(
"Can't fetch the 3D model file " + resource.file + ', error: ' + error
);
}
let loadedCount = 0;
await Promise.all(
[...this._resources.values()].map(async (resource) => {
const url = this._resourcesLoader.getFullUrl(resource.file);
loader.withCredentials = this._resourcesLoader.checkIfCredentialsRequired(
url
);
try {
const gltf: THREE_ADDONS.GLTF = await loader.loadAsync(
url,
(event) => {}
);
this._loadedThreeModels.set(resource.name, gltf);
} catch (error) {
logger.error(
"Can't fetch the 3D model file " +
resource.file +
', error: ' +
error
);
}
loadedCount++;
onProgress(loadedCount, this._resources.size);
})
);
return loadedCount;
}
/**
@@ -135,7 +142,9 @@ namespace gdjs {
* @returns a 3D model if it exists.
*/
getModel(resourceName: string): THREE_ADDONS.GLTF {
return this._loadedThreeModels.get(resourceName) || this._invalidModel;
return (
this._loadedThreeModels.getFromName(resourceName) || this._invalidModel
);
}
}
}

View File

@@ -0,0 +1,122 @@
namespace gdjs {
/**
* Allow to do spacial searches on objects as fast as possible.
*
* Objects are put in an R-Tree only if they didn't move recently to avoid to
* update the R-Tree too often.
*/
export class ObjectManager {
private _allInstances: Array<RuntimeObject> = [];
private _awakeInstances: Array<RuntimeObject> = [];
private _rbush: RBush<RuntimeObject>;
constructor() {
this._rbush = new RBush<RuntimeObject>();
}
_destroy(): void {
this._allInstances = [];
this._awakeInstances = [];
this._rbush.clear();
}
search(
searchArea: SearchArea,
results: Array<RuntimeObject>
): Array<RuntimeObject> {
let instances = this._allInstances;
if (instances.length >= 8) {
this._rbush.search(searchArea, results);
instances = this._awakeInstances;
}
for (const instance of instances) {
// TODO Allow to use getAABB to optimize collision conditions
const aabb = instance.getVisibilityAABB();
if (
!aabb ||
(aabb.min[0] <= searchArea.maxX &&
aabb.max[0] >= searchArea.minX &&
aabb.min[1] <= searchArea.maxY &&
aabb.max[1] >= searchArea.minY)
) {
results.push(instance);
}
}
return results;
}
private _onWakingUp(object: RuntimeObject): void {
this._rbush.remove(object._rtreeAABB);
this._awakeInstances.push(object);
}
private _onFallenAsleep(object: RuntimeObject): void {
// TODO Allow to use getAABB to optimize collision conditions
const objectAABB = object.getVisibilityAABB();
if (!objectAABB) {
return;
}
this._rbush.remove(object._rtreeAABB);
object._rtreeAABB.minX = objectAABB.min[0];
object._rtreeAABB.minY = objectAABB.min[1];
object._rtreeAABB.maxX = objectAABB.max[0];
object._rtreeAABB.maxY = objectAABB.max[1];
this._rbush.insert(object._rtreeAABB);
}
updateAwakeObjects(): void {
gdjs.ObjectSleepState.updateAwakeObjects(
this._awakeInstances,
(object) => object.getSpatialSearchSleepState(),
(object) => this._onFallenAsleep(object),
(object) => this._onWakingUp(object)
);
}
getAllInstances(): Array<RuntimeObject> {
return this._allInstances;
}
getAwakeInstances(): Array<RuntimeObject> {
return this._awakeInstances;
}
/**
* Add an object to the instances living in the container.
* @param obj The object to be added.
*/
addObject(object: gdjs.RuntimeObject): void {
this._allInstances.push(object);
this._awakeInstances.push(object);
}
/**
* Must be called whenever an object must be removed from the container.
* @param object The object to be removed.
*/
deleteObject(object: gdjs.RuntimeObject): boolean {
const objId = object.id;
let isObjectDeleted = false;
for (let i = 0, len = this._allInstances.length; i < len; ++i) {
if (this._allInstances[i].id == objId) {
this._allInstances.splice(i, 1);
isObjectDeleted = true;
break;
}
}
// TODO Maybe the state could be used but it would be more prone to errors.
let isAwake = false;
for (let i = 0, len = this._awakeInstances.length; i < len; ++i) {
if (this._awakeInstances[i].id == objId) {
this._awakeInstances.splice(i, 1);
isAwake = true;
break;
}
}
if (!isAwake) {
this._rbush.remove(object._rtreeAABB);
}
return isObjectDeleted;
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* GDevelop JS Platform
* Copyright 2023-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
export class ObjectSleepState {
private static readonly framesBeforeSleep = 60;
private _object: RuntimeObject;
private _isNeedingToBeAwake: () => boolean;
private _state: ObjectSleepState.State;
private _lastActivityFrameIndex: integer;
private _onWakingUpCallbacks: Array<(object: RuntimeObject) => void> = [];
constructor(
object: RuntimeObject,
isNeedingToBeAwake: () => boolean,
initialSleepState: ObjectSleepState.State
) {
this._object = object;
this._isNeedingToBeAwake = isNeedingToBeAwake;
this._state = initialSleepState;
this._lastActivityFrameIndex = this._object
.getRuntimeScene()
.getFrameIndex();
}
canSleep(): boolean {
return (
this._state === gdjs.ObjectSleepState.State.CanSleepThisFrame ||
this._object.getRuntimeScene().getFrameIndex() -
this._lastActivityFrameIndex >=
ObjectSleepState.framesBeforeSleep
);
}
isAwake(): boolean {
return this._state !== gdjs.ObjectSleepState.State.ASleep;
}
_forceToSleep(): void {
if (!this.isAwake()) {
return;
}
this._lastActivityFrameIndex = Number.NEGATIVE_INFINITY;
}
wakeUp() {
const object = this._object;
this._lastActivityFrameIndex = object.getRuntimeScene().getFrameIndex();
if (this.isAwake()) {
return;
}
this._state = gdjs.ObjectSleepState.State.AWake;
for (const onWakingUp of this._onWakingUpCallbacks) {
onWakingUp(object);
}
}
registerOnWakingUp(onWakingUp: (object: RuntimeObject) => void) {
this._onWakingUpCallbacks.push(onWakingUp);
}
tryToSleep(): void {
if (
this._lastActivityFrameIndex !== Number.NEGATIVE_INFINITY &&
this._isNeedingToBeAwake()
) {
this._lastActivityFrameIndex = this._object
.getRuntimeScene()
.getFrameIndex();
}
}
static updateAwakeObjects(
awakeObjects: Array<RuntimeObject>,
getSleepState: (object: RuntimeObject) => ObjectSleepState,
onFallenAsleep: (object: RuntimeObject) => void,
onWakingUp: (object: RuntimeObject) => void
) {
let writeIndex = 0;
for (let readIndex = 0; readIndex < awakeObjects.length; readIndex++) {
const object = awakeObjects[readIndex];
const sleepState = getSleepState(object);
sleepState.tryToSleep();
if (sleepState.canSleep() || !sleepState.isAwake()) {
if (sleepState.isAwake()) {
// Avoid onWakingUp to be called if some managers didn't have time
// to update their awake object list.
sleepState._onWakingUpCallbacks.length = 0;
}
sleepState._state = gdjs.ObjectSleepState.State.ASleep;
onFallenAsleep(object);
sleepState._onWakingUpCallbacks.push(onWakingUp);
} else {
awakeObjects[writeIndex] = object;
writeIndex++;
}
}
awakeObjects.length = writeIndex;
return awakeObjects;
}
}
export namespace ObjectSleepState {
export enum State {
ASleep,
CanSleepThisFrame,
AWake,
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* GDevelop JS Platform
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
/**
* A cache of resources that helps ensuring that files are only downloaded
* once.
*/
export class ResourceCache<C> {
private _nameToContent = new Map<string, C>();
private _fileToContent = new Map<string, C>();
constructor() {}
/**
* Gives a fast access to asset content when they were pre-loaded and
* on-the-fly loading is not allowed.
*/
getFromName(name: string): C | null {
return this._nameToContent.get(name) || null;
}
get(resource: ResourceData): C | null {
let existingContent = this._nameToContent.get(resource.name);
if (existingContent) {
return existingContent;
}
// When several assets use the same file, it avoids to download it again.
existingContent = this._fileToContent.get(resource.file);
if (existingContent) {
this._nameToContent.set(resource.name, existingContent);
return existingContent;
}
return null;
}
set(resource: ResourceData, content: C) {
this._nameToContent.set(resource.name, content);
this._fileToContent.set(resource.file, content);
}
delete(resource: ResourceData) {
this._nameToContent.delete(resource.name);
this._fileToContent.delete(resource.file);
}
clear() {
this._nameToContent.clear();
this._fileToContent.clear();
}
}
}

View File

@@ -0,0 +1,556 @@
/*
* GDevelop JS Platform
* Copyright 2013-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
const logger = new gdjs.Logger('ResourceLoader');
const addSearchParameterToUrl = (
url: string,
urlEncodedParameterName: string,
urlEncodedValue: string
) => {
if (url.startsWith('data:') || url.startsWith('blob:')) {
// blob/data protocol does not support search parameters, which are useless anyway.
return url;
}
const separator = url.indexOf('?') === -1 ? '?' : '&';
return url + separator + urlEncodedParameterName + '=' + urlEncodedValue;
};
const checkIfIsGDevelopCloudBucketUrl = (url: string): boolean => {
return (
url.startsWith('https://project-resources.gdevelop.io/') ||
url.startsWith('https://project-resources-dev.gdevelop.io/')
);
};
/**
* A task of pre-loading resources used by a scene.
*
* A Promise can't be used instead of this class because a Promise will start
* as soon as possible. It would flood the server with downloading requests
* and make impossible to finely tune in which order scenes are actually
* downloaded.
*/
class SceneLoadingTask {
sceneName: string;
private onProgressCallbacks: Array<(count: number, total: number) => void>;
private onFinishCallbacks: Array<() => void>;
private isFinished = false;
constructor(sceneName: string) {
this.sceneName = sceneName;
this.onProgressCallbacks = new Array<
(count: number, total: number) => void
>();
this.onFinishCallbacks = new Array<() => void>();
}
registerCallback(
onFinish: () => void,
onProgress?: (count: number, total: number) => void
) {
if (this.isFinished) {
onFinish();
return;
}
this.onFinishCallbacks.push(onFinish);
if (onProgress) {
this.onProgressCallbacks.push(onProgress);
}
}
onProgress(count: number, total: number) {
for (const onProgress of this.onProgressCallbacks) {
onProgress(count, total);
}
}
onFinish() {
this.isFinished = true;
for (const onFinish of this.onFinishCallbacks) {
onFinish();
}
}
}
/**
* Pre-load resources of any kind needed for a game or a scene.
*/
export class ResourceLoader {
_runtimeGame: RuntimeGame;
/**
* All the resource of a game by resource name.
*/
private _resources: Map<string, ResourceData>;
/**
* Resources needed for any scene. Typically, they are resources from
* global objects.
*/
private _globalResources: Array<string>;
/**
* Resources by scene names.
*/
private _sceneResources: Map<string, Array<string>>;
/**
* Keep track of which scene whose resources has already be pre-loaded.
*/
private _sceneNamesToLoad: Set<string>;
/**
* Keep track of which scene whose resources has already be loaded.
*/
private _sceneNamesToMakeReady: Set<string>;
/**
* A queue of scenes whose resources are still to be pre-loaded.
*/
private _sceneToLoadQueue: Array<SceneLoadingTask> = new Array<
SceneLoadingTask
>();
/**
* The resource managers that actually download and remember downloaded
* content.
*/
_resourceManagersMap: Map<ResourceKind, ResourceManager>;
private _imageManager: ImageManager;
private _soundManager: SoundManager;
private _fontManager: FontManager;
private _jsonManager: JsonManager;
private _model3DManager: Model3DManager;
private _bitmapFontManager: BitmapFontManager;
/**
* Only used by events.
*/
private currentLoadingSceneName: string = '';
/**
* Only used by events.
*/
private currentSceneLoadingProgress: float = 0;
/**
* @param runtimeGame The game.
* @param resourceDataArray The resources data of the game.
* @param globalResources The resources needed for any layer.
* @param layoutDataArray The resources used by each layer.
*/
constructor(
runtimeGame: RuntimeGame,
resourceDataArray: ResourceData[],
globalResources: Array<string>,
layoutDataArray: Array<LayoutData>
) {
this._runtimeGame = runtimeGame;
this._resources = new Map<string, ResourceData>();
this._globalResources = globalResources;
// These 3 attributes are filled by `setResources`.
this._sceneResources = new Map<string, Array<string>>();
this._sceneNamesToLoad = new Set<string>();
this._sceneNamesToMakeReady = new Set<string>();
this.setResources(resourceDataArray, globalResources, layoutDataArray);
this._imageManager = new gdjs.ImageManager(this);
this._soundManager = new gdjs.SoundManager(this);
this._fontManager = new gdjs.FontManager(this);
this._jsonManager = new gdjs.JsonManager(this);
this._bitmapFontManager = new gdjs.BitmapFontManager(
this,
this._imageManager
);
this._model3DManager = new gdjs.Model3DManager(this);
const resourceManagers: Array<ResourceManager> = [
this._imageManager,
this._soundManager,
this._fontManager,
this._jsonManager,
this._bitmapFontManager,
this._model3DManager,
];
this._resourceManagersMap = new Map<ResourceKind, ResourceManager>();
for (const resourceManager of resourceManagers) {
for (const resourceKind of resourceManager.getResourceKinds()) {
this._resourceManagersMap.set(resourceKind, resourceManager);
}
}
}
/**
* Update the resources data of the game. Useful for hot-reloading, should
* not be used otherwise.
*/
setResources(
resourceDataArray: ResourceData[],
globalResources: Array<string>,
layoutDataArray: Array<LayoutData>
): void {
this._globalResources = globalResources;
this._sceneResources.clear();
this._sceneNamesToLoad.clear();
this._sceneNamesToMakeReady.clear();
for (const layoutData of layoutDataArray) {
this._sceneResources.set(
layoutData.name,
layoutData.usedResources.map((resource) => resource.name)
);
this._sceneNamesToLoad.add(layoutData.name);
this._sceneNamesToMakeReady.add(layoutData.name);
}
// TODO Clearing the queue doesn't abort the running task, but it should
// not matter as resource loading is really fast in preview mode.
this._sceneToLoadQueue.length = 0;
for (let index = layoutDataArray.length - 1; index >= 0; index--) {
const layoutData = layoutDataArray[index];
this._sceneToLoadQueue.push(new SceneLoadingTask(layoutData.name));
}
this._resources.clear();
for (const resourceData of resourceDataArray) {
this._resources.set(resourceData.name, resourceData);
}
}
async loadAllResources(
onProgress: (loadingCount: integer, totalCount: integer) => void
): Promise<void> {
let loadedCount = 0;
await Promise.all(
[...this._resources.values()].map(async (resource) => {
await this._loadResource(resource);
await this._processResource(resource);
loadedCount++;
onProgress(loadedCount, this._resources.size);
})
);
this._sceneNamesToLoad.clear();
this._sceneNamesToMakeReady.clear();
}
/**
* Load the resources that are needed to launch the first scene.
*/
async loadGlobalAndFirstSceneResources(
firstSceneName: string,
onProgress: (count: number, total: number) => void
): Promise<void> {
const sceneResources = this._sceneResources.get(firstSceneName);
if (!sceneResources) {
logger.warn(
'Can\'t load resource for unknown scene: "' + firstSceneName + '".'
);
return;
}
let loadedCount = 0;
const resources = [...this._globalResources, ...sceneResources.values()];
await Promise.all(
resources.map(async (resourceName) => {
const resource = this._resources.get(resourceName);
if (!resource) {
logger.warn('Unable to find resource "' + resourceName + '".');
return;
}
await this._loadResource(resource);
await this._processResource(resource);
loadedCount++;
onProgress(loadedCount, resources.length);
})
);
this._setSceneAssetsLoaded(firstSceneName);
this._setSceneAssetsReady(firstSceneName);
}
/**
* Load each scene in order.
*
* This is done in background to try to avoid loading screens when changing
* scenes.
*/
async loadAllSceneInBackground(): Promise<void> {
while (this._sceneToLoadQueue.length > 0) {
const task = this._sceneToLoadQueue[this._sceneToLoadQueue.length - 1];
if (task === undefined) {
continue;
}
this.currentLoadingSceneName = task.sceneName;
if (!this.areSceneAssetsLoaded(task.sceneName)) {
await this._doLoadSceneResources(
task.sceneName,
async (count, total) => task.onProgress(count, total)
);
// A scene may have been moved last while awaiting resources to be
// downloaded (see _prioritizeScene).
this._sceneToLoadQueue.splice(
this._sceneToLoadQueue.findIndex((element) => element === task),
1
);
task.onFinish();
} else {
this._sceneToLoadQueue.pop();
}
}
this.currentLoadingSceneName = '';
}
private async _doLoadSceneResources(
sceneName: string,
onProgress?: (count: number, total: number) => Promise<void>
): Promise<void> {
const sceneResources = this._sceneResources.get(sceneName);
if (!sceneResources) {
logger.warn(
'Can\'t load resource for unknown scene: "' + sceneName + '".'
);
return;
}
let loadedCount = 0;
await Promise.all(
[...sceneResources.values()].map(async (resourceName) => {
const resource = this._resources.get(resourceName);
if (!resource) {
logger.warn('Unable to find resource "' + resourceName + '".');
return;
}
await this._loadResource(resource);
loadedCount++;
this.currentSceneLoadingProgress = loadedCount / this._resources.size;
onProgress && (await onProgress(loadedCount, this._resources.size));
})
);
this._setSceneAssetsLoaded(sceneName);
}
private async _loadResource(resource: ResourceData): Promise<void> {
const resourceManager = this._resourceManagersMap.get(resource.kind);
if (!resourceManager) {
logger.warn(
'Unknown resource kind: "' +
resource.kind +
'" for: "' +
resource.name +
'".'
);
return;
}
await resourceManager.loadResource(resource.name);
}
/**
* Load and process a scene that is needed right away.
*
* The renderer will show a loading screen while its done.
*/
async loadAndProcessSceneResources(
sceneName: string,
onProgress?: (count: number, total: number) => Promise<void>
): Promise<void> {
if (this.areSceneAssetsReady(sceneName)) {
return;
}
await this.loadSceneResources(sceneName, onProgress);
const sceneResources = this._sceneResources.get(sceneName);
if (!sceneResources) {
logger.warn(
'Can\'t load resource for unknown scene: "' + sceneName + '".'
);
return;
}
let parsedCount = 0;
for (const resourceName of sceneResources) {
const resource = this._resources.get(resourceName);
if (!resource) {
logger.warn('Unable to find resource "' + resourceName + '".');
continue;
}
await this._processResource(resource);
parsedCount++;
onProgress && (await onProgress(parsedCount, sceneResources.length));
}
this._setSceneAssetsReady(sceneName);
}
/**
* Load a scene resources without parsing them.
*
* When another scene resources are loading in background, it waits for
* all its resources to be loaded before loading resources of the given
* scene.
*/
async loadSceneResources(
sceneName: string,
onProgress?: (count: number, total: number) => void
): Promise<void> {
const task = this._prioritizeScene(sceneName);
return new Promise<void>((resolve, reject) => {
if (!task) {
resolve();
return;
}
task.registerCallback(() => {
resolve();
}, onProgress);
});
}
/**
* Put a given scene at the end of the queue.
*
* When the scene that is currently loading in background is done,
* this scene will be the next to be loaded.
*/
private _prioritizeScene(sceneName: string): SceneLoadingTask | null {
const taskIndex = this._sceneToLoadQueue.findIndex(
(task) => task.sceneName === sceneName
);
if (taskIndex < 0) {
// The scene is already loaded.
return null;
}
const task = this._sceneToLoadQueue[taskIndex];
this._sceneToLoadQueue.splice(taskIndex, 1);
this._sceneToLoadQueue.push(task);
return task;
}
private async _processResource(resource: ResourceData): Promise<void> {
const resourceManager = this._resourceManagersMap.get(resource.kind);
if (!resourceManager) {
logger.warn(
'Unknown resource kind: "' +
resource.kind +
'" for: "' +
resource.name +
'".'
);
return;
}
await resourceManager.processResource(resource.name);
}
getSceneLoadingProgress(sceneName: string): float {
return sceneName === this.currentLoadingSceneName
? this.currentSceneLoadingProgress
: this.areSceneAssetsLoaded(sceneName)
? 1
: 0;
}
/**
* @returns true when all the resources of the given scene are loaded
* (but maybe not parsed).
*/
areSceneAssetsLoaded(sceneName: string): boolean {
return !this._sceneNamesToLoad.has(sceneName);
}
/**
* @returns true when all the resources of the given scene are loaded and
* parsed.
*/
areSceneAssetsReady(sceneName: string): boolean {
return !this._sceneNamesToMakeReady.has(sceneName);
}
private _setSceneAssetsLoaded(sceneName: string): void {
this._sceneNamesToLoad.delete(sceneName);
}
private _setSceneAssetsReady(sceneName: string): void {
this._sceneNamesToMakeReady.delete(sceneName);
}
getResource(resourceName: string): ResourceData | null {
return this._resources.get(resourceName) || null;
}
// Helper methods used when resources are loaded from an URL.
/**
* Complete the given URL with any specific parameter required to access
* the resource (this can be for example a token needed to access the resource).
*/
getFullUrl(url: string) {
const { gdevelopResourceToken } = this._runtimeGame._options;
if (!gdevelopResourceToken) return url;
if (!checkIfIsGDevelopCloudBucketUrl(url)) return url;
return addSearchParameterToUrl(
url,
'gd_resource_token',
encodeURIComponent(gdevelopResourceToken)
);
}
/**
* Return true if the specified URL must be loaded with cookies ("credentials")
* sent to grant access to them.
*/
checkIfCredentialsRequired(url: string) {
if (this._runtimeGame._options.gdevelopResourceToken) return false;
// Any resource stored on the GDevelop Cloud buckets needs the "credentials" of the user,
// i.e: its gdevelop.io cookie, to be passed.
// Note that this is only useful during previews.
if (checkIfIsGDevelopCloudBucketUrl(url)) return true;
// For other resources, use the default way of loading resources ("anonymous" or "same-site").
return false;
}
/**
* Get the gdjs.SoundManager of the RuntimeGame.
* @return The sound manager.
*/
getSoundManager(): gdjs.HowlerSoundManager {
return this._soundManager;
}
/**
* Get the gdjs.ImageManager of the RuntimeGame.
* @return The image manager.
*/
getImageManager(): gdjs.PixiImageManager {
return this._imageManager;
}
/**
* Get the gdjs.FontManager of the RuntimeGame.
* @return The font manager.
*/
getFontManager(): gdjs.FontFaceObserverFontManager {
return this._fontManager;
}
/**
* Get the gdjs.BitmapFontManager of the RuntimeGame.
* @return The bitmap font manager.
*/
getBitmapFontManager(): gdjs.BitmapFontManager {
return this._bitmapFontManager;
}
/**
* Get the JSON manager of the game, used to load JSON from game
* resources.
* @return The json manager for the game
*/
getJsonManager(): gdjs.JsonManager {
return this._jsonManager;
}
/**
* Get the 3D model manager of the game, used to load 3D model from game
* resources.
* @return The 3D model manager for the game
*/
getModel3DManager(): gdjs.Model3DManager {
return this._model3DManager;
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* GDevelop JS Platform
* Copyright 2013-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
/**
* A resource managers that download and remember downloaded content for one
* kind of resource.
*/
export interface ResourceManager {
/**
* Load the specified resource.
*
* This method will be run during the game. It should only do light tasks
* like file downloading.
*/
loadResource(resourceName: string): Promise<void>;
/**
* Process the specified resource.
*
* This method will only be run while loading screen is shown. It can do
* heavy tasks like parsing data.
*/
processResource(resourceName: string): Promise<void>;
/**
* Return the kind of resources handled by this manager.
*/
getResourceKinds(): Array<ResourceKind>;
}
}

View File

@@ -34,7 +34,6 @@ namespace gdjs {
_layers: Hashtable<RuntimeLayer>;
_orderedLayers: RuntimeLayer[]; // TODO: should this be a single structure with _layers, to enforce its usage?
_layersCameraCoordinates: Record<string, [float, float, float, float]> = {};
// Options for the debug draw:
_debugDrawEnabled: boolean = false;
@@ -351,26 +350,6 @@ namespace gdjs {
}
}
_updateLayersCameraCoordinates(scale: float) {
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const theLayer = this._layers.items[name];
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
name
] || [0, 0, 0, 0];
this._layersCameraCoordinates[name][0] =
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][1] =
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
this._layersCameraCoordinates[name][2] =
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][3] =
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
}
}
}
/**
* Called to update effects of layers before rendering.
*/
@@ -625,6 +604,8 @@ namespace gdjs {
return;
}
onObjectChangedOfLayer(object: RuntimeObject, oldLayer: RuntimeLayer) {}
/**
* Get the layer with the given name
* @param name The name of the layer

View File

@@ -351,6 +351,7 @@ namespace gdjs {
'_renderer',
'_gameRenderer',
'_imageManager',
'_rendererEffects',
// Exclude PIXI textures:
'baseTexture',
'_baseTexture',

View File

@@ -311,77 +311,82 @@ namespace gdjs {
return Promise.all(reloadPromises);
}
_hotReloadRuntimeGame(
async _hotReloadRuntimeGame(
oldProjectData: ProjectData,
newProjectData: ProjectData,
changedRuntimeBehaviors: ChangedRuntimeBehavior[],
runtimeGame: gdjs.RuntimeGame
): Promise<void> {
return new Promise((resolve) => {
// Update project data and re-load assets (sound/image/font/json managers
// will take care of reloading only what is needed).
runtimeGame.setProjectData(newProjectData);
runtimeGame.loadAllAssets(() => {
this._hotReloadVariablesContainer(
oldProjectData.variables,
newProjectData.variables,
runtimeGame.getVariables()
);
// Reload runtime scenes
const sceneStack = runtimeGame.getSceneStack();
sceneStack._stack.forEach((runtimeScene) => {
const oldLayoutData = oldProjectData.layouts.filter(
(layoutData) => layoutData.name === runtimeScene.getName()
)[0];
const newLayoutData = newProjectData.layouts.filter(
(layoutData) => layoutData.name === runtimeScene.getName()
)[0];
if (oldLayoutData && newLayoutData) {
this._hotReloadRuntimeScene(
oldLayoutData,
newLayoutData,
changedRuntimeBehaviors,
runtimeScene
);
} else {
// A scene was removed. Not hot-reloading this.
this._logs.push({
kind: 'error',
message:
'Scene ' +
oldLayoutData.name +
' was removed. A fresh preview should be launched.',
});
}
});
// Reload changes in external layouts
newProjectData.externalLayouts.forEach((newExternalLayoutData) => {
const oldExternalLayoutData = oldProjectData.externalLayouts.filter(
(externalLayoutData) =>
externalLayoutData.name === newExternalLayoutData.name
)[0];
if (
oldExternalLayoutData &&
// Check if there are actual changes, to avoid useless work trying to
// hot-reload all the scenes.
!HotReloader.deepEqual(
oldExternalLayoutData,
newExternalLayoutData
)
) {
sceneStack._stack.forEach((runtimeScene) => {
this._hotReloadRuntimeSceneInstances(
oldExternalLayoutData.instances,
newExternalLayoutData.instances,
runtimeScene
);
});
}
});
resolve();
const sceneStack = runtimeGame.getSceneStack();
const currentScene = sceneStack.getCurrentScene();
if (!currentScene) {
// It can't actually happen.
this._logs.push({
kind: 'error',
message: "Can't hot-reload as no scene are opened.",
});
return;
}
// Update project data and re-load assets (sound/image/font/json managers
// will take care of reloading only what is needed).
runtimeGame.setProjectData(newProjectData);
await runtimeGame.loadFirstAssetsAndStartBackgroundLoading(
currentScene.getName(),
() => {}
);
this._hotReloadVariablesContainer(
oldProjectData.variables,
newProjectData.variables,
runtimeGame.getVariables()
);
// Reload runtime scenes
sceneStack._stack.forEach((runtimeScene) => {
const oldLayoutData = oldProjectData.layouts.filter(
(layoutData) => layoutData.name === runtimeScene.getName()
)[0];
const newLayoutData = newProjectData.layouts.filter(
(layoutData) => layoutData.name === runtimeScene.getName()
)[0];
if (oldLayoutData && newLayoutData) {
this._hotReloadRuntimeScene(
oldLayoutData,
newLayoutData,
changedRuntimeBehaviors,
runtimeScene
);
} else {
// A scene was removed. Not hot-reloading this.
this._logs.push({
kind: 'error',
message:
'Scene ' +
oldLayoutData.name +
' was removed. A fresh preview should be launched.',
});
}
});
// Reload changes in external layouts
newProjectData.externalLayouts.forEach((newExternalLayoutData) => {
const oldExternalLayoutData = oldProjectData.externalLayouts.filter(
(externalLayoutData) =>
externalLayoutData.name === newExternalLayoutData.name
)[0];
if (
oldExternalLayoutData &&
// Check if there are actual changes, to avoid useless work trying to
// hot-reload all the scenes.
!HotReloader.deepEqual(oldExternalLayoutData, newExternalLayoutData)
) {
sceneStack._stack.forEach((runtimeScene) => {
this._hotReloadRuntimeSceneInstances(
oldExternalLayoutData.instances,
newExternalLayoutData.instances,
runtimeScene
);
});
}
});
}

View File

@@ -380,7 +380,7 @@ namespace gdjs {
.isMouseInsideCanvas();
};
export const _cursorIsOnObject = function (
const _cursorIsOnObject = function (
obj: gdjs.RuntimeObject,
instanceContainer: gdjs.RuntimeInstanceContainer
) {
@@ -394,7 +394,7 @@ namespace gdjs {
inverted: boolean
) {
return gdjs.evtTools.object.pickObjectsIf(
gdjs.evtTools.input._cursorIsOnObject,
_cursorIsOnObject,
objectsLists,
inverted,
instanceContainer

View File

@@ -168,7 +168,7 @@ namespace gdjs {
* @deprecated Use `JSON.stringify(variable.toJSObject())` instead.
*/
export const objectVariableStructureToJSON = function (
object: gdjs.RuntimeObject,
object: gdjs.RuntimeObject | null,
variable: gdjs.Variable
): string {
return JSON.stringify(variable.toJSObject());
@@ -205,7 +205,7 @@ namespace gdjs {
*/
export const jsonToObjectVariableStructure = function (
jsonStr: string,
object: gdjs.RuntimeObject,
object: gdjs.RuntimeObject | null,
variable: gdjs.Variable
) {
variable.fromJSON(jsonStr);

View File

@@ -326,6 +326,36 @@ namespace gdjs {
): boolean => {
return runtimeScene.getGame().hasScene(sceneName);
};
/**
* Preload a scene assets as soon as possible in background.
*/
export const prioritizeLoadingOfScene = (
runtimeScene: gdjs.RuntimeScene,
sceneName: string
): void => {
runtimeScene.getGame().prioritizeLoadingOfScene(sceneName);
};
/**
* @return The progress of assets loading in background for a scene (between 0 and 1).
*/
export const getSceneLoadingProgress = (
runtimeScene: gdjs.RuntimeScene,
sceneName: string
): float => {
return runtimeScene.getGame().getSceneLoadingProgress(sceneName);
};
/**
* Check if scene assets have finished to load in background.
*/
export const areSceneAssetsLoaded = (
runtimeScene: gdjs.RuntimeScene,
sceneName: string
): boolean => {
return runtimeScene.getGame().areSceneAssetsLoaded(sceneName);
};
}
}
}

View File

@@ -6,46 +6,29 @@
namespace gdjs {
const logger = new gdjs.Logger('Font manager');
const resourceKinds: Array<ResourceKind> = ['font'];
/**
* FontFaceObserverFontManager loads fonts (using `FontFace` or `fontfaceobserver` library)
* from the game resources (see `loadFonts`), and allow to access to
* the font families of the loaded fonts during the game (see `getFontFamily`).
*/
export class FontFaceObserverFontManager {
_resourcesLoader: RuntimeGameResourcesLoader;
_resources: Map<string, ResourceData>;
export class FontFaceObserverFontManager implements gdjs.ResourceManager {
_resourceLoader: gdjs.ResourceLoader;
// Associate font resource names to the loaded font family
_loadedFontFamily: { [key: string]: string } = {};
// Associate font resource names to the resources, for faster access
_loadedFonts: { [key: string]: ResourceData } = {};
_filenameToFontFamily: { [key: string]: string } = {};
_loadedFontFamily = new gdjs.ResourceCache<string>();
_loadedFontFamilySet = new Set<string>();
/**
* @param resources The resources data of the game.
* @param resourcesLoader The resources loader of the game.
* @param resourceLoader The resources loader of the game.
*/
constructor(
resourceDataArray: ResourceData[],
resourcesLoader: RuntimeGameResourcesLoader
) {
this._resources = new Map<string, ResourceData>();
this.setResources(resourceDataArray);
this._resourcesLoader = resourcesLoader;
constructor(resourceLoader: gdjs.ResourceLoader) {
this._resourceLoader = resourceLoader;
}
// Cache the result of transforming a filename to a font family - useful to avoid duplicates.
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param resources The resources data of the game.
*/
setResources(resourceDataArray: ResourceData[]): void {
this._resources.clear();
for (const resourceData of resourceDataArray) {
if (resourceData.kind === 'font') {
this._resources.set(resourceData.name, resourceData);
}
}
getResourceKinds(): ResourceKind[] {
return resourceKinds;
}
/**
@@ -58,10 +41,7 @@ namespace gdjs {
* or "Arial" if not loaded.
*/
getFontFamily(resourceName: string): string {
if (this._loadedFontFamily[resourceName]) {
return this._loadedFontFamily[resourceName];
}
return 'Arial';
return this._loadedFontFamily.getFromName(resourceName) || 'Arial';
}
/**
@@ -76,10 +56,8 @@ namespace gdjs {
* @returns The file of the font resource.
*/
getFontFile(resourceName: string): string {
if (this._loadedFonts[resourceName]) {
return this._loadedFonts[resourceName].file || '';
}
return resourceName;
const resource = this._resourceLoader.getResource(resourceName);
return resource ? resource.file || '' : resourceName;
}
/**
@@ -92,26 +70,20 @@ namespace gdjs {
* @param filename The filename of the font.
* @returns The font family to be used for this font resource.
*/
_getFontFamilyFromFilename(filename: string): string {
if (this._filenameToFontFamily[filename]) {
return this._filenameToFontFamily[filename];
}
_getFontFamilyFromFilename(resource: ResourceData): string {
// Replaces all non-alphanumeric characters with dashes to ensure no issues when
// referring to this font family (see https://github.com/4ian/GDevelop/issues/1521).
let baseSlugifiedName =
'gdjs_font_' + filename.toLowerCase().replace(/[^\w]/gi, '-');
'gdjs_font_' + resource.file.toLowerCase().replace(/[^\w]/gi, '-');
// Ensure the generated font family is unique.
const slugifiedName = baseSlugifiedName;
let uniqueSuffix = 2;
while (!!this._filenameToFontFamily[baseSlugifiedName]) {
while (this._loadedFontFamilySet.has(baseSlugifiedName)) {
baseSlugifiedName = baseSlugifiedName + '-' + uniqueSuffix;
uniqueSuffix++;
}
// Cache the result to avoid collision with a similar slugified name for another filename.
return (this._filenameToFontFamily[filename] = slugifiedName);
return slugifiedName;
}
/**
@@ -131,8 +103,8 @@ namespace gdjs {
// @ts-ignore
if (typeof FontFace !== 'undefined') {
// Load the given font using CSS Font Loading API.
return fetch(this._resourcesLoader.getFullUrl(src), {
credentials: this._resourcesLoader.checkIfCredentialsRequired(src)
return fetch(this._resourceLoader.getFullUrl(src), {
credentials: this._resourceLoader.checkIfCredentialsRequired(src)
? // Any resource stored on the GDevelop Cloud buckets needs the "credentials" of the user,
// i.e: its gdevelop.io cookie, to be passed.
'include'
@@ -184,59 +156,46 @@ namespace gdjs {
}
}
async processResource(resourceName: string): Promise<void> {
// Do nothing because fonts are light enough to be parsed in background.
}
/**
* Load the specified resources, so that fonts are loaded and can then be
* used by using the font family returned by getFontFamily.
* @param onProgress Callback called each time a new file is loaded.
*/
async loadFonts(
onProgress: (loadedCount: integer, totalCount: integer) => void
): Promise<integer> {
// Construct the list of files to be loaded.
// For one loaded file, it can have one or more resources
// that use it.
const filesResources: { [key: string]: ResourceData[] } = {};
for (const res of this._resources.values()) {
if (res.file) {
if (!!this._loadedFonts[res.name]) {
continue;
}
filesResources[res.file] = filesResources[res.file]
? filesResources[res.file].concat(res)
: [res];
}
}
const totalCount = Object.keys(filesResources).length;
if (totalCount === 0) {
return 0;
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn('Unable to find font for resource "' + resourceName + '".');
return;
}
let loadingCount = 0;
await Promise.all(
Object.keys(filesResources).map(async (file) => {
const fontFamily = this._getFontFamilyFromFilename(file);
const fontResources = filesResources[file];
try {
await this._loadFont(fontFamily, file);
} catch (error) {
logger.error(
'Error loading font resource "' +
fontResources[0].name +
'" (file: ' +
file +
'): ' +
(error.message || 'Unknown error')
);
}
fontResources.forEach((resource) => {
this._loadedFontFamily[resource.name] = fontFamily;
this._loadedFonts[resource.name] = resource;
});
loadingCount++;
onProgress(loadingCount, totalCount);
})
);
return totalCount;
if (this._loadedFontFamily.get(resource)) {
return;
}
const file = resource.file;
if (!file) {
return;
}
const fontFamily = this._getFontFamilyFromFilename(resource);
// Cache the result to avoid collision with a similar slugified name for another filename.
this._loadedFontFamily.set(resource, fontFamily);
this._loadedFontFamilySet.add(fontFamily);
try {
await this._loadFont(fontFamily, file);
} catch (error) {
logger.error(
'Error loading font resource "' +
resource.name +
'" (file: ' +
file +
'): ' +
(error.message || 'Unknown error')
);
}
}
}

View File

@@ -7,6 +7,8 @@
namespace gdjs {
const logger = new gdjs.Logger('Audio manager');
const resourceKinds: Array<ResourceKind> = ['audio'];
const HowlParameters: HowlOptions = {
preload: true,
onplayerror: (_, error) =>
@@ -364,9 +366,8 @@ namespace gdjs {
* of all sounds being played.
*/
export class HowlerSoundManager {
_loadedMusics: Record<string, Howl> = {};
_loadedSounds: Record<string, Howl> = {};
_resources: Map<string, ResourceData>;
_loadedMusics = new gdjs.ResourceCache<Howl>();
_loadedSounds = new gdjs.ResourceCache<Howl>();
_availableResources: Record<string, ResourceData> = {};
_globalVolume: float = 100;
_sounds: Record<integer, HowlerSound> = {};
@@ -378,19 +379,14 @@ namespace gdjs {
_pausedSounds: HowlerSound[] = [];
_paused: boolean = false;
_resourcesLoader: RuntimeGameResourcesLoader;
_resourceLoader: gdjs.ResourceLoader;
/**
* @param resources The resources data of the game.
* @param resourcesLoader The resources loader of the game.
* @param resourceLoader The resources loader of the game.
*/
constructor(
resourceDataArray: ResourceData[],
resourcesLoader: RuntimeGameResourcesLoader
) {
this._resources = new Map<string, ResourceData>();
this.setResources(resourceDataArray);
this._resourcesLoader = resourcesLoader;
constructor(resourceLoader: gdjs.ResourceLoader) {
this._resourceLoader = resourceLoader;
const that = this;
document.addEventListener('deviceready', function () {
@@ -437,18 +433,8 @@ namespace gdjs {
});
}
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param resources The resources data of the game.
*/
setResources(resourceDataArray: ResourceData[]): void {
this._resources.clear();
for (const resourceData of resourceDataArray) {
if (resourceData.kind === 'audio') {
this._resources.set(resourceData.name, resourceData);
}
}
getResourceKinds(): ResourceKind[] {
return resourceKinds;
}
/**
@@ -472,17 +458,19 @@ namespace gdjs {
* file is associated to the given name, then the name will be considered as a
* filename and will be returned.
*
* @return The associated filename
* @return The associated resource
*/
private _getFileFromSoundName(soundName: string): string {
if (
this._availableResources.hasOwnProperty(soundName) &&
this._availableResources[soundName].file
) {
return this._availableResources[soundName].file;
}
return soundName;
}
private _getAudioResource = (resourceName: string): ResourceData => {
const resource = this._resourceLoader.getResource(resourceName);
return resource && this.getResourceKinds().includes(resource.kind)
? resource
: ({
file: resourceName,
kind: 'audio',
metadata: '',
name: resourceName,
} as ResourceData);
};
/**
* Store the sound in the specified array, put it at the first index that
@@ -524,18 +512,20 @@ namespace gdjs {
loop: boolean,
rate: float
): HowlerSound {
const soundFile = this._getFileFromSoundName(soundName);
const cacheContainer = isMusic ? this._loadedMusics : this._loadedSounds;
const resource = this._getAudioResource(soundName);
if (!cacheContainer.hasOwnProperty(soundFile)) {
cacheContainer[soundFile] = new Howl(
let howl = cacheContainer.get(resource);
if (!howl) {
const fileName = resource ? resource.file : soundName;
howl = new Howl(
Object.assign(
{
src: [this._resourcesLoader.getFullUrl(soundFile)],
src: [this._resourceLoader.getFullUrl(fileName)],
html5: isMusic,
xhr: {
withCredentials: this._resourcesLoader.checkIfCredentialsRequired(
soundFile
withCredentials: this._resourceLoader.checkIfCredentialsRequired(
fileName
),
},
// Cache the sound with no volume. This avoids a bug where it plays at full volume
@@ -545,14 +535,10 @@ namespace gdjs {
HowlParameters
)
);
cacheContainer.set(resource, howl);
}
return new gdjs.HowlerSound(
cacheContainer[soundFile],
volume,
loop,
rate
);
return new gdjs.HowlerSound(howl, volume, loop, rate);
}
/**
@@ -561,27 +547,32 @@ namespace gdjs {
* @param isMusic True if a music, false if a sound.
*/
loadAudio(soundName: string, isMusic: boolean) {
const soundFile = this._getFileFromSoundName(soundName);
const cacheContainer = isMusic ? this._loadedMusics : this._loadedSounds;
const resource = this._getAudioResource(soundName);
// Do not reload if it is already loaded.
if (cacheContainer.hasOwnProperty(soundFile)) return;
if (cacheContainer.get(resource)) {
return;
}
cacheContainer[soundFile] = new Howl(
Object.assign(
{
src: [this._resourcesLoader.getFullUrl(soundFile)],
html5: isMusic,
xhr: {
withCredentials: this._resourcesLoader.checkIfCredentialsRequired(
soundFile
),
cacheContainer.set(
resource,
new Howl(
Object.assign(
{
src: [this._resourceLoader.getFullUrl(resource.file)],
html5: isMusic,
xhr: {
withCredentials: this._resourceLoader.checkIfCredentialsRequired(
resource.file
),
},
// Cache the sound with no volume. This avoids a bug where it plays at full volume
// for a split second before setting its correct volume.
volume: 0,
},
// Cache the sound with no volume. This avoids a bug where it plays at full volume
// for a split second before setting its correct volume.
volume: 0,
},
HowlParameters
HowlParameters
)
)
);
}
@@ -592,15 +583,17 @@ namespace gdjs {
* @param isMusic True if a music, false if a sound.
*/
unloadAudio(soundName: string, isMusic: boolean) {
const soundFile = this._getFileFromSoundName(soundName);
const cacheContainer = isMusic ? this._loadedMusics : this._loadedSounds;
const resource = this._getAudioResource(soundName);
if (!cacheContainer[soundFile]) return;
const howl = cacheContainer.get(resource);
if (!howl) {
return;
}
// Make sure any sound using the howl is deleted so
// that the howl can be garbage collected
// and no weird "zombies" using the unloaded howl can exist.
const howl = cacheContainer[soundFile];
function clearContainer(howlerSoundContainer: HowlerSound[]) {
for (let i in howlerSoundContainer) {
if (
@@ -620,8 +613,8 @@ namespace gdjs {
clearContainer(Object.values(this._sounds));
clearContainer(this._pausedSounds);
cacheContainer[soundFile].unload();
delete cacheContainer[soundFile];
howl.unload();
cacheContainer.delete(resource);
}
/**
@@ -638,8 +631,8 @@ namespace gdjs {
this._sounds = {};
this._musics = {};
this._pausedSounds.length = 0;
this._loadedMusics = {};
this._loadedSounds = {};
this._loadedMusics.clear();
this._loadedSounds.clear();
}
playSound(soundName: string, loop: boolean, volume: float, pitch: float) {
@@ -763,31 +756,24 @@ namespace gdjs {
this._pausedSounds.length = 0;
}
async preloadAudio(
onProgress: (loadedCount: integer, totalCount: integer) => void,
resources?: ResourceData[]
): Promise<integer> {
// Construct the list of files to be loaded.
// For one loaded file, it can have one or more resources
// that use it.
const files = {};
for (const res of resources || this._resources.values()) {
if (res.file) {
if (!!this._availableResources[res.name]) {
continue;
}
async processResource(resourceName: string): Promise<void> {
// Do nothing because sounds are light enough to be parsed in background.
}
this._availableResources[res.name] = res;
files[res.file] = (files[res.file] || []).concat(res);
}
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find audio for resource "' + resourceName + '".'
);
return;
}
if (resource.file) {
if (this._availableResources[resource.name]) {
return;
}
const filesToLoad = Object.keys(files);
const totalCount = filesToLoad.length;
if (totalCount === 0) {
// Nothing to load.
return 0;
this._availableResources[resource.name] = resource;
}
const preloadAudioFile = (
@@ -798,12 +784,12 @@ namespace gdjs {
const container = isMusic ? this._loadedMusics : this._loadedSounds;
container[file] = new Howl(
Object.assign({}, HowlParameters, {
src: [this._resourcesLoader.getFullUrl(file)],
src: [this._resourceLoader.getFullUrl(file)],
onload: resolve,
onloaderror: (soundId: number, error?: string) => reject(error),
html5: isMusic,
xhr: {
withCredentials: this._resourcesLoader.checkIfCredentialsRequired(
withCredentials: this._resourceLoader.checkIfCredentialsRequired(
file
),
},
@@ -815,58 +801,49 @@ namespace gdjs {
});
};
let loadedCount: integer = 0;
await Promise.all(
filesToLoad.map(async (file) => {
const fileData = files[file][0];
const file = resource.file;
if (resource.preloadAsMusic) {
try {
await preloadAudioFile(file, /* isMusic= */ true);
} catch (error) {
logger.warn(
'There was an error while preloading an audio file: ' + error
);
}
}
if (fileData.preloadAsMusic) {
try {
await preloadAudioFile(file, /* isMusic= */ true);
} catch (error) {
logger.warn(
'There was an error while preloading an audio file: ' + error
);
}
}
if (fileData.preloadAsSound) {
try {
await preloadAudioFile(file, /* isMusic= */ false);
} catch (error) {
logger.warn(
'There was an error while preloading an audio file: ' + error
);
}
} else if (fileData.preloadInCache) {
// preloading as sound already does a XHR request, hence "else if"
try {
await new Promise((resolve, reject) => {
const sound = new XMLHttpRequest();
sound.withCredentials = this._resourcesLoader.checkIfCredentialsRequired(
file
);
sound.addEventListener('load', resolve);
sound.addEventListener('error', (_) =>
reject('XHR error: ' + file)
);
sound.addEventListener('abort', (_) =>
reject('XHR abort: ' + file)
);
sound.open('GET', this._resourcesLoader.getFullUrl(file));
sound.send();
});
} catch (error) {
logger.warn(
'There was an error while preloading an audio file: ' + error
);
}
}
loadedCount++;
onProgress(loadedCount, totalCount);
})
);
return totalCount;
if (resource.preloadAsSound) {
try {
await preloadAudioFile(file, /* isMusic= */ false);
} catch (error) {
logger.warn(
'There was an error while preloading an audio file: ' + error
);
}
} else if (resource.preloadInCache) {
// preloading as sound already does a XHR request, hence "else if"
try {
await new Promise((resolve, reject) => {
const sound = new XMLHttpRequest();
sound.withCredentials = this._resourceLoader.checkIfCredentialsRequired(
file
);
sound.addEventListener('load', resolve);
sound.addEventListener('error', (_) =>
reject('XHR error: ' + file)
);
sound.addEventListener('abort', (_) =>
reject('XHR abort: ' + file)
);
sound.open('GET', this._resourceLoader.getFullUrl(file));
sound.send();
});
} catch (error) {
logger.warn(
'There was an error while preloading an audio file: ' + error
);
}
}
}
}

View File

@@ -12,6 +12,7 @@ namespace gdjs {
content: Object | null
) => void;
const resourceKinds: Array<ResourceKind> = ['json', 'tilemap', 'tileset'];
/**
* JsonManager loads json files (using `XMLHttpRequest`), using the "json" resources
* registered in the game resources.
@@ -20,42 +21,22 @@ namespace gdjs {
* You should properly handle errors, and give the developer/player a way to know
* that loading failed.
*/
export class JsonManager {
_resourcesLoader: RuntimeGameResourcesLoader;
_resources: Map<string, ResourceData>;
export class JsonManager implements gdjs.ResourceManager {
_resourceLoader: ResourceLoader;
_loadedJsons: { [key: string]: Object } = {};
_callbacks: { [key: string]: Array<JsonManagerRequestCallback> } = {};
_loadedJsons = new gdjs.ResourceCache<Object>();
_callbacks = new gdjs.ResourceCache<Array<JsonManagerRequestCallback>>();
/**
* @param resourceDataArray The resources data of the game.
* @param resourcesLoader The resources loader of the game.
* @param resourceLoader The resources loader of the game.
*/
constructor(
resourceDataArray: ResourceData[],
resourcesLoader: RuntimeGameResourcesLoader
) {
this._resources = new Map<string, ResourceData>();
this.setResources(resourceDataArray);
this._resourcesLoader = resourcesLoader;
constructor(resourceLoader: gdjs.ResourceLoader) {
this._resourceLoader = resourceLoader;
}
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param resourceDataArray The resources data of the game.
*/
setResources(resourceDataArray: ResourceData[]): void {
this._resources.clear();
for (const resourceData of resourceDataArray) {
if (
resourceData.kind === 'json' ||
resourceData.kind === 'tilemap' ||
resourceData.kind === 'tileset'
) {
this._resources.set(resourceData.name, resourceData);
}
}
getResourceKinds(): ResourceKind[] {
return resourceKinds;
}
/**
@@ -63,32 +44,25 @@ namespace gdjs {
*
* Note that even if a JSON is already loaded, it will be reloaded (useful for hot-reloading,
* as JSON files can have been modified without the editor knowing).
*
* @param onProgress The function called after each json is loaded.
*/
async preloadJsons(
onProgress: (loadedCount: integer, totalCount: integer) => void
): Promise<integer> {
const preloadedResources = [...this._resources.values()].filter(
(resource) => !resource.disablePreload
);
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn('Unable to find json for resource "' + resourceName + '".');
return;
}
if (resource.disablePreload) {
return;
}
let loadedCount = 0;
await Promise.all(
preloadedResources.map(async (resource) => {
try {
await this.loadJsonAsync(resource.name);
} catch (error) {
logger.error(
`Error while preloading json resource ${resource.name}:`,
error
);
}
loadedCount++;
onProgress(loadedCount, this._resources.size);
})
);
return loadedCount;
try {
await this.loadJsonAsync(resource.name);
} catch (error) {
logger.error(
`Error while preloading json resource ${resource.name}:`,
error
);
}
}
loadJsonAsync(resourceName: string): Promise<Object | null> {
@@ -103,6 +77,17 @@ namespace gdjs {
});
}
private _getJsonResource = (resourceName: string): ResourceData | null => {
const resource = this._resourceLoader.getResource(resourceName);
return resource && this.getResourceKinds().includes(resource.kind)
? resource
: null;
};
async processResource(resourceName: string): Promise<void> {
// Do nothing because json are light enough to be parsed in background.
}
/**
* Request the json file from the given resource name.
* This method is asynchronous. When loaded, the `callback` is called with the error
@@ -112,7 +97,7 @@ namespace gdjs {
* @param callback The callback function called when json is loaded (or an error occurred).
*/
loadJson(resourceName: string, callback: JsonManagerRequestCallback): void {
const resource = this._resources.get(resourceName);
const resource = this._getJsonResource(resourceName);
if (!resource) {
callback(
new Error(
@@ -126,29 +111,30 @@ namespace gdjs {
}
// Don't fetch again an object that is already in memory
if (this._loadedJsons[resourceName]) {
callback(null, this._loadedJsons[resourceName]);
if (this._loadedJsons.get(resource)) {
callback(null, this._loadedJsons.get(resource));
return;
}
// Don't fetch again an object that is already being fetched.
{
const callbacks = this._callbacks[resourceName];
const callbacks = this._callbacks.get(resource);
if (callbacks) {
callbacks.push(callback);
return;
} else {
this._callbacks[resourceName] = [callback];
this._callbacks.set(resource, [callback]);
}
}
const that = this;
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.withCredentials = this._resourcesLoader.checkIfCredentialsRequired(
xhr.withCredentials = this._resourceLoader.checkIfCredentialsRequired(
resource.file
);
xhr.open('GET', this._resourcesLoader.getFullUrl(resource.file));
xhr.open('GET', this._resourceLoader.getFullUrl(resource.file));
xhr.onload = function () {
const callbacks = that._callbacks[resourceName];
const callbacks = that._callbacks.get(resource);
if (!callbacks) {
return;
}
@@ -161,36 +147,36 @@ namespace gdjs {
null
);
}
delete that._callbacks[resourceName];
that._callbacks.delete(resource);
return;
}
// Cache the result
that._loadedJsons[resourceName] = xhr.response;
that._loadedJsons.set(resource, xhr.response);
for (const callback of callbacks) {
callback(null, xhr.response);
}
delete that._callbacks[resourceName];
that._callbacks.delete(resource);
};
xhr.onerror = function () {
const callbacks = that._callbacks[resourceName];
const callbacks = that._callbacks.get(resource);
if (!callbacks) {
return;
}
for (const callback of callbacks) {
callback(new Error('Network error'), null);
}
delete that._callbacks[resourceName];
that._callbacks.delete(resource);
};
xhr.onabort = function () {
const callbacks = that._callbacks[resourceName];
const callbacks = that._callbacks.get(resource);
if (!callbacks) {
return;
}
for (const callback of callbacks) {
callback(new Error('Request aborted'), null);
}
delete that._callbacks[resourceName];
that._callbacks.delete(resource);
};
xhr.send();
}
@@ -201,7 +187,7 @@ namespace gdjs {
* @returns true if the content of the json resource is loaded. false otherwise.
*/
isJsonLoaded(resourceName: string): boolean {
return !!this._loadedJsons[resourceName];
return !!this._loadedJsons.getFromName(resourceName);
}
/**
@@ -212,7 +198,7 @@ namespace gdjs {
* @returns the content of the json resource, if loaded. `null` otherwise.
*/
getLoadedJson(resourceName: string): Object | null {
return this._loadedJsons[resourceName] || null;
return this._loadedJsons.getFromName(resourceName) || null;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@ namespace gdjs {
class LoadingScreenPixiRenderer {
_pixiRenderer: PIXI.Renderer | null;
_loadingScreenData: LoadingScreenData;
_isFirstLayout: boolean;
_loadingScreenContainer: PIXI.Container;
_backgroundSprite: PIXI.Sprite | null = null;
@@ -40,9 +41,11 @@ namespace gdjs {
constructor(
runtimeGamePixiRenderer: gdjs.RuntimeGamePixiRenderer,
imageManager: gdjs.PixiImageManager,
loadingScreenData: LoadingScreenData
loadingScreenData: LoadingScreenData,
isFirstScene: boolean
) {
this._loadingScreenData = loadingScreenData;
this._isFirstLayout = isFirstScene;
this._loadingScreenContainer = new PIXI.Container();
this._pixiRenderer = runtimeGamePixiRenderer.getPIXIRenderer();
if (!this._pixiRenderer) {
@@ -55,7 +58,10 @@ namespace gdjs {
const backgroundTexture = imageManager.getOrLoadPIXITexture(
loadingScreenData.backgroundImageResourceName
);
if (backgroundTexture !== imageManager.getInvalidPIXITexture()) {
if (
backgroundTexture !== imageManager.getInvalidPIXITexture() &&
isFirstScene
) {
this._backgroundSprite = PIXI.Sprite.from(backgroundTexture);
this._backgroundSprite.alpha = 0;
this._backgroundSprite.anchor.x = 0.5;
@@ -63,7 +69,7 @@ namespace gdjs {
this._loadingScreenContainer.addChild(this._backgroundSprite);
}
if (loadingScreenData.showGDevelopSplash) {
if (loadingScreenData.showGDevelopSplash && isFirstScene) {
this._gdevelopLogoSprite = PIXI.Sprite.from(gdjs.gdevelopLogo);
this._gdevelopLogoSprite.alpha = 0;
this._gdevelopLogoSprite.anchor.x = 0.5;
@@ -137,6 +143,22 @@ namespace gdjs {
requestAnimationFrame(() => this._render(performance.now()));
}
this._renderIfNeeded(timeInMs);
}
renderIfNeeded(): boolean {
return this._renderIfNeeded(performance.now());
}
private _renderIfNeeded(timeInMs: float): boolean {
if (timeInMs - this._lastFrameTimeInMs < 1000 / 60) {
return false;
}
if (!this._pixiRenderer) {
return false;
}
const deltaTimeInMs = this._lastFrameTimeInMs
? timeInMs - this._lastFrameTimeInMs
: 0;
@@ -226,12 +248,14 @@ namespace gdjs {
}
this._pixiRenderer.render(this._loadingScreenContainer);
return true;
}
unload(): Promise<void> {
const totalElapsedTime = (performance.now() - this._startTimeInMs) / 1000;
const remainingTime =
this._loadingScreenData.minDuration - totalElapsedTime;
(this._isFirstLayout ? this._loadingScreenData.minDuration : 0) -
totalElapsedTime;
this.setPercent(100);
// Ensure we have shown the loading screen for at least minDuration.

View File

@@ -33,13 +33,14 @@ namespace gdjs {
return PIXI.BitmapFont.available[bitmapFontInstallKey];
};
const resourceKinds: Array<ResourceKind> = ['bitmapFont'];
/**
* PixiBitmapFontManager loads fnt/xml files (using `fetch`), from the "bitmapFont" resources of the game.
*
* It installs the "BitmapFont" with PixiJS to be used with PIXI.BitmapText.
*/
export class PixiBitmapFontManager {
_resources: Map<string, ResourceData>;
export class PixiBitmapFontManager implements gdjs.ResourceManager {
private _imageManager: gdjs.PixiImageManager;
/** Pixi.BitmapFont used, indexed by their BitmapFont name. */
@@ -52,26 +53,27 @@ namespace gdjs {
private _pixiBitmapFontsToUninstall: string[] = [];
/** Loaded fonts data, indexed by resource name. */
private _loadedFontsData: Record<string, any> = {};
private _loadedFontsData = new gdjs.ResourceCache<any>();
private _defaultSlugFontName: string | null = null;
_resourcesLoader: RuntimeGameResourcesLoader;
_resourceLoader: gdjs.ResourceLoader;
/**
* @param resourceDataArray The resources data of the game.
* @param resourcesLoader The resources loader of the game.
* @param resourceLoader The resources loader of the game.
* @param imageManager The image manager to be used to get textures used by fonts.
*/
constructor(
resourceDataArray: ResourceData[],
resourcesLoader: RuntimeGameResourcesLoader,
resourceLoader: gdjs.ResourceLoader,
imageManager: gdjs.PixiImageManager
) {
this._resources = new Map<string, ResourceData>();
this.setResources(resourceDataArray);
this._imageManager = imageManager;
this._resourcesLoader = resourcesLoader;
this._resourceLoader = resourceLoader;
}
getResourceKinds(): ResourceKind[] {
return resourceKinds;
}
/**
@@ -108,19 +110,6 @@ namespace gdjs {
return defaultBitmapFont;
}
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
* @param resourceDataArray The resources data of the game.
*/
setResources(resourceDataArray: ResourceData[]): void {
this._resources.clear();
for (const resourceData of resourceDataArray) {
if (resourceData.kind === 'bitmapFont') {
this._resources.set(resourceData.name, resourceData);
}
}
}
/**
* Called to specify that the bitmap font with the specified key is used by an object
* (i.e: this is reference counting).
@@ -219,7 +208,9 @@ namespace gdjs {
// The Bitmap Font is not loaded, load it in memory.
// First get the font data:
const fontData = this._loadedFontsData[bitmapFontResourceName];
const fontData = this._loadedFontsData.getFromName(
bitmapFontResourceName
);
if (!fontData) {
logger.warn(
'Could not find Bitmap Font for resource named "' +
@@ -253,49 +244,50 @@ namespace gdjs {
}
}
async processResource(resourceName: string): Promise<void> {
// Do nothing because fonts are light enough to be parsed in background.
}
/**
* Load the "bitmapFont" resources of the game, so that they are ready
* to be used when `obtainBitmapFont` is called.
*/
async loadBitmapFontData(
onProgress: (count: integer, total: integer) => void
): Promise<integer> {
const preloadedResources = [...this._resources.values()].filter(
(resource) => !resource.disablePreload
);
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find bitmap font for resource "' + resourceName + '".'
);
return;
}
if (this._loadedFontsData.get(resource)) {
return;
}
let loadedCount = 0;
await Promise.all(
preloadedResources.map(async (bitmapFontResource) => {
try {
const response = await fetch(
this._resourcesLoader.getFullUrl(bitmapFontResource.file),
{
credentials: this._resourcesLoader.checkIfCredentialsRequired(
bitmapFontResource.file
)
? // Any resource stored on the GDevelop Cloud buckets needs the "credentials" of the user,
// i.e: its gdevelop.io cookie, to be passed.
'include'
: // For other resources, use "same-origin" as done by default by fetch.
'same-origin',
}
);
const fontData = await response.text();
this._loadedFontsData[bitmapFontResource.name] = fontData;
} catch (error) {
logger.error(
"Can't fetch the bitmap font file " +
bitmapFontResource.file +
', error: ' +
error
);
try {
const response = await fetch(
this._resourceLoader.getFullUrl(resource.file),
{
credentials: this._resourceLoader.checkIfCredentialsRequired(
resource.file
)
? // Any resource stored on the GDevelop Cloud buckets needs the "credentials" of the user,
// i.e: its gdevelop.io cookie, to be passed.
'include'
: // For other resources, use "same-origin" as done by default by fetch.
'same-origin',
}
loadedCount++;
onProgress(loadedCount, preloadedResources.length);
})
);
return loadedCount;
);
const fontData = await response.text();
this._loadedFontsData.set(resource, fontData);
} catch (error) {
logger.error(
"Can't fetch the bitmap font file " +
resource.file +
', error: ' +
error
);
}
}
}

View File

@@ -34,21 +34,12 @@ namespace gdjs {
}
};
const findResourceWithNameAndKind = (
resources: Map<string, ResourceData>,
resourceName: string,
kind: ResourceKind
): ResourceData | null => {
const resource = resources.get(resourceName);
return resource && resource.kind === kind ? resource : null;
};
const resourceKinds: Array<ResourceKind> = ['image', 'video'];
/**
* PixiImageManager loads and stores textures that can be used by the Pixi.js renderers.
*/
export class PixiImageManager {
_resources: Map<string, ResourceData>;
export class PixiImageManager implements gdjs.ResourceManager {
/**
* The invalid texture is a 8x8 PNG file filled with magenta (#ff00ff), to be
* easily spotted if rendered on screen.
@@ -58,7 +49,7 @@ namespace gdjs {
/**
* Map associating a resource name to the loaded PixiJS texture.
*/
private _loadedTextures: Hashtable<PIXI.Texture<PIXI.Resource>>;
private _loadedTextures = new gdjs.ResourceCache<PIXI.Texture>();
/**
* Map associating a resource name to the loaded Three.js texture.
@@ -66,39 +57,23 @@ namespace gdjs {
private _loadedThreeTextures: Hashtable<THREE.Texture>;
private _loadedThreeMaterials: Hashtable<THREE.Material>;
private _resourcesLoader: RuntimeGameResourcesLoader;
private _resourceLoader: gdjs.ResourceLoader;
/**
* @param resources The resources data of the game.
* @param resourcesLoader The resources loader of the game.
* @param resourceLoader The resources loader of the game.
*/
constructor(
resourceDataArray: ResourceData[],
resourcesLoader: RuntimeGameResourcesLoader
) {
this._resources = new Map<string, ResourceData>();
this.setResources(resourceDataArray);
this._resourcesLoader = resourcesLoader;
constructor(resourceLoader: gdjs.ResourceLoader) {
this._resourceLoader = resourceLoader;
this._invalidTexture = PIXI.Texture.from(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVQoU2P8z/D/PwMewDgyFAAApMMX8Zi0uXAAAAAASUVORK5CYIIA'
);
this._loadedTextures = new Hashtable();
this._loadedThreeTextures = new Hashtable();
this._loadedThreeMaterials = new Hashtable();
}
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param resources The resources data of the game.
*/
setResources(resourceDataArray: ResourceData[]): void {
this._resources.clear();
for (const resourceData of resourceDataArray) {
if (resourceData.kind === 'image' || resourceData.kind === 'video') {
this._resources.set(resourceData.name, resourceData);
}
}
getResourceKinds(): ResourceKind[] {
return resourceKinds;
}
/**
@@ -108,19 +83,28 @@ namespace gdjs {
* @returns The requested texture, or a placeholder if not found.
*/
getPIXITexture(resourceName: string): PIXI.Texture {
if (this._loadedTextures.containsKey(resourceName)) {
const texture = this._loadedTextures.get(resourceName);
if (texture.valid) {
return texture;
} else {
logger.error(
'Texture for ' +
resourceName +
' is not valid anymore (or never was).'
);
}
const resource = this._getImageResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find texture for resource "' + resourceName + '".'
);
return this._invalidTexture;
}
return this._invalidTexture;
const existingTexture = this._loadedTextures.get(resource);
if (!existingTexture) {
return this._invalidTexture;
}
if (!existingTexture.valid) {
logger.error(
'Texture for ' +
resourceName +
' is not valid anymore (or never was).'
);
return this._invalidTexture;
}
return existingTexture;
}
/**
@@ -132,10 +116,18 @@ namespace gdjs {
* @returns The requested texture, or a placeholder if not valid.
*/
getOrLoadPIXITexture(resourceName: string): PIXI.Texture {
if (this._loadedTextures.containsKey(resourceName)) {
const texture = this._loadedTextures.get(resourceName);
if (texture.valid) {
return texture;
const resource = this._getImageResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find texture for resource "' + resourceName + '".'
);
return this._invalidTexture;
}
const existingTexture = this._loadedTextures.get(resource);
if (existingTexture) {
if (existingTexture.valid) {
return existingTexture;
} else {
logger.error(
'Texture for ' +
@@ -146,29 +138,15 @@ namespace gdjs {
}
}
// Texture is not loaded, load it now from the resources list.
const resource = findResourceWithNameAndKind(
this._resources,
resourceName,
'image'
);
if (!resource) {
logger.warn(
'Unable to find texture for resource "' + resourceName + '".'
);
return this._invalidTexture;
}
logger.log('Loading texture for resource "' + resourceName + '"...');
const file = resource.file;
const url = this._resourcesLoader.getFullUrl(file);
const url = this._resourceLoader.getFullUrl(file);
const texture = PIXI.Texture.from(url, {
resourceOptions: {
// Note that using `false`
// to not having `crossorigin` at all would NOT work because the browser would taint the
// loaded resource so that it can't be read/used in a canvas (it's only working for display `<img>` on screen).
crossorigin: this._resourcesLoader.checkIfCredentialsRequired(file)
crossorigin: this._resourceLoader.checkIfCredentialsRequired(file)
? 'use-credentials'
: 'anonymous',
},
@@ -185,7 +163,7 @@ namespace gdjs {
}
applyTextureSettings(texture, resource);
this._loadedTextures.put(resourceName, texture);
this._loadedTextures.set(resource, texture);
return texture;
}
@@ -197,13 +175,15 @@ namespace gdjs {
*/
getThreeTexture(resourceName: string): THREE.Texture {
const loadedThreeTexture = this._loadedThreeTextures.get(resourceName);
if (loadedThreeTexture) return loadedThreeTexture;
if (loadedThreeTexture) {
return loadedThreeTexture;
}
// 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.
const pixiTexture = this.getPIXITexture(resourceName);
const pixiRenderer = this._resourcesLoader._runtimeGame
const pixiRenderer = this._resourceLoader._runtimeGame
.getRenderer()
.getPIXIRenderer();
if (!pixiRenderer) throw new Error('No PIXI renderer was found.');
@@ -224,11 +204,7 @@ namespace gdjs {
threeTexture.colorSpace = THREE.SRGBColorSpace;
threeTexture.needsUpdate = true;
const resource = findResourceWithNameAndKind(
this._resources,
resourceName,
'image'
);
const resource = this._getImageResource(resourceName);
applyThreeTextureSettings(threeTexture, resource);
this._loadedThreeTextures.put(resourceName, threeTexture);
@@ -278,12 +254,31 @@ namespace gdjs {
* @param resourceName The name of the resource to get.
*/
getPIXIVideoTexture(resourceName: string) {
if (this._loadedTextures.containsKey(resourceName)) {
return this._loadedTextures.get(resourceName);
if (resourceName === '') {
return this._invalidTexture;
}
return this._invalidTexture;
const resource = this._getImageResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find video texture for resource "' + resourceName + '".'
);
return this._invalidTexture;
}
const texture = this._loadedTextures.get(resource);
if (!texture) {
return this._invalidTexture;
}
return texture;
}
private _getImageResource = (resourceName: string): ResourceData | null => {
const resource = this._resourceLoader.getResource(resourceName);
return resource && this.getResourceKinds().includes(resource.kind)
? resource
: null;
};
/**
* Return a PIXI texture which can be used as a placeholder when no
* suitable texture can be found.
@@ -292,87 +287,95 @@ namespace gdjs {
return this._invalidTexture;
}
/**
* Load the specified resources, so that textures are loaded and can then be
* used by calling `getPIXITexture`.
*/
async loadResource(resourceName: string): Promise<void> {
const resource = this._resourceLoader.getResource(resourceName);
if (!resource) {
logger.warn(
'Unable to find texture for resource "' + resourceName + '".'
);
return;
}
await this._loadTexture(resource);
}
async processResource(resourceName: string): Promise<void> {
// Do nothing because images are light enough to be parsed in background.
}
/**
* Load the specified resources, so that textures are loaded and can then be
* used by calling `getPIXITexture`.
* @param onProgress Callback called each time a new file is loaded.
*/
async loadTextures(
onProgress: (loadingCount: integer, totalCount: integer) => void
): Promise<integer> {
let loadedCount = 0;
await Promise.all(
[...this._resources.values()].map(async (resource) => {
try {
if (resource.kind === 'video') {
// For videos, we want to preload them so they are available as soon as we want to use them.
// We cannot use Pixi.assets.load() as it does not allow passing options (autoplay) to the resource loader.
// Pixi.Texture.from() does not return a promise, so we need to ensure we look at the 'loaded' event of the baseTexture,
// to continue, otherwise if we try to play the video too soon (at the beginning of scene for instance),
// it will fail.
await new Promise<void>((resolve, reject) => {
const texture = PIXI.Texture.from(
this._resourcesLoader.getFullUrl(resource.file),
{
resourceOptions: {
crossorigin: this._resourcesLoader.checkIfCredentialsRequired(
resource.file
)
? 'use-credentials'
: 'anonymous',
autoPlay: false,
},
}
).on('error', (error) => {
reject(error);
});
async _loadTexture(resource: ResourceData): Promise<void> {
if (this._loadedTextures.get(resource)) {
return;
}
try {
if (resource.kind === 'video') {
// For videos, we want to preload them so they are available as soon as we want to use them.
// We cannot use Pixi.assets.load() as it does not allow passing options (autoplay) to the resource loader.
// Pixi.Texture.from() does not return a promise, so we need to ensure we look at the 'loaded' event of the baseTexture,
// to continue, otherwise if we try to play the video too soon (at the beginning of scene for instance),
// it will fail.
await new Promise<void>((resolve, reject) => {
const texture = PIXI.Texture.from(
this._resourceLoader.getFullUrl(resource.file),
{
resourceOptions: {
crossorigin: this._resourceLoader.checkIfCredentialsRequired(
resource.file
)
? 'use-credentials'
: 'anonymous',
autoPlay: false,
},
}
).on('error', (error) => {
reject(error);
});
const baseTexture = texture.baseTexture;
const baseTexture = texture.baseTexture;
baseTexture
.on('loaded', () => {
this._loadedTextures.put(resource.name, texture);
applyTextureSettings(texture, resource);
resolve();
})
.on('error', (error) => {
reject(error);
});
});
} else {
// If the file has no extension, PIXI.assets.load cannot find
// an adequate load parser and does not load the file although
// we would like to force it to load (we are confident it's an image).
// TODO: When PIXI v8+ is used, PIXI.Assets.load can be used because
// loadParser can be forced in PIXI.Assets.load
// (see https://github.com/pixijs/pixijs/blob/71ed56c569ebc6b53da19e3c49258a0a84892101/packages/assets/src/loader/Loader.ts#L68)
const loadedTexture = PIXI.Texture.from(
this._resourcesLoader.getFullUrl(resource.file),
{
resourceOptions: {
autoLoad: false,
crossorigin: this._resourcesLoader.checkIfCredentialsRequired(
resource.file
)
? 'use-credentials'
: 'anonymous',
},
}
);
await loadedTexture.baseTexture.resource.load();
this._loadedTextures.put(resource.name, loadedTexture);
// TODO What if 2 assets share the same file with different settings?
applyTextureSettings(loadedTexture, resource);
baseTexture.on('loaded', () => {
this._loadedTextures.set(resource, texture);
applyTextureSettings(texture, resource);
resolve();
});
});
} else {
// If the file has no extension, PIXI.assets.load cannot find
// an adequate load parser and does not load the file although
// we would like to force it to load (we are confident it's an image).
// TODO: When PIXI v8+ is used, PIXI.Assets.load can be used because
// loadParser can be forced in PIXI.Assets.load
// (see https://github.com/pixijs/pixijs/blob/71ed56c569ebc6b53da19e3c49258a0a84892101/packages/assets/src/loader/Loader.ts#L68)
const loadedTexture = PIXI.Texture.from(
this._resourceLoader.getFullUrl(resource.file),
{
resourceOptions: {
autoLoad: false,
crossorigin: this._resourceLoader.checkIfCredentialsRequired(
resource.file
)
? 'use-credentials'
: 'anonymous',
},
}
} catch (error) {
logFileLoadingError(resource.file, error);
}
loadedCount++;
onProgress(loadedCount, this._resources.size);
})
);
return loadedCount;
);
await loadedTexture.baseTexture.resource.load();
this._loadedTextures.set(resource, loadedTexture);
// TODO What if 2 assets share the same file with different settings?
applyTextureSettings(loadedTexture, resource);
}
} catch (error) {
logFileLoadingError(resource.file, error);
}
}
}

View File

@@ -184,15 +184,18 @@ namespace gdjs {
return this._sprite.texture.frame.height;
}
static getAnimationFrame(imageManager, imageName) {
static getAnimationFrame(
imageManager: gdjs.PixiImageManager,
imageName: string
) {
return imageManager.getPIXITexture(imageName);
}
static getAnimationFrameWidth(pixiTexture) {
static getAnimationFrameWidth(pixiTexture: PIXI.Texture) {
return pixiTexture.width;
}
static getAnimationFrameHeight(pixiTexture) {
static getAnimationFrameHeight(pixiTexture: PIXI.Texture) {
return pixiTexture.height;
}
}

View File

@@ -6,6 +6,9 @@
namespace gdjs {
const logger = new gdjs.Logger('Game manager');
const sleep = (ms: float) =>
new Promise((resolve) => setTimeout(resolve, ms));
/** Identify a script file, with its content hash (useful for hot-reloading). */
export type RuntimeGameOptionsScriptFile = {
/** The path for this script file. */
@@ -14,6 +17,9 @@ namespace gdjs {
hash: number;
};
const getGlobalResourceNames = (projectData: ProjectData): Array<string> =>
projectData.usedResources.map((resource) => resource.name);
/** Options given to the game at startup. */
export type RuntimeGameOptions = {
/** if true, force fullscreen. */
@@ -63,86 +69,15 @@ namespace gdjs {
environment?: 'dev';
};
const addSearchParameterToUrl = (
url: string,
urlEncodedParameterName: string,
urlEncodedValue: string
) => {
if (url.startsWith('data:') || url.startsWith('blob:')) {
// blob/data protocol does not support search parameters, which are useless anyway.
return url;
}
const separator = url.indexOf('?') === -1 ? '?' : '&';
return url + separator + urlEncodedParameterName + '=' + urlEncodedValue;
};
const checkIfIsGDevelopCloudBucketUrl = (url: string): boolean => {
return (
url.startsWith('https://project-resources.gdevelop.io/') ||
url.startsWith('https://project-resources-dev.gdevelop.io/')
);
};
/**
* Gives helper methods used when resources are loaded from an URL.
*/
export class RuntimeGameResourcesLoader {
_runtimeGame: RuntimeGame;
constructor(runtimeGame: RuntimeGame) {
this._runtimeGame = runtimeGame;
}
/**
* Complete the given URL with any specific parameter required to access
* the resource (this can be for example a token needed to access the resource).
*/
getFullUrl(url: string) {
const { gdevelopResourceToken } = this._runtimeGame._options;
if (!gdevelopResourceToken) return url;
if (!checkIfIsGDevelopCloudBucketUrl(url)) return url;
return addSearchParameterToUrl(
url,
'gd_resource_token',
encodeURIComponent(gdevelopResourceToken)
);
}
/**
* Return true if the specified URL must be loaded with cookies ("credentials")
* sent to grant access to them.
*/
checkIfCredentialsRequired(url: string) {
if (this._runtimeGame._options.gdevelopResourceToken) return false;
// Any resource stored on the GDevelop Cloud buckets needs the "credentials" of the user,
// i.e: its gdevelop.io cookie, to be passed.
// Note that this is only useful during previews.
if (checkIfIsGDevelopCloudBucketUrl(url)) return true;
// For other resources, use the default way of loading resources ("anonymous" or "same-site").
return false;
}
}
/**
* Represents a game being played.
*/
export class RuntimeGame {
_resourcesLoader: RuntimeGameResourcesLoader;
_resourcesLoader: gdjs.ResourceLoader;
_variables: VariablesContainer;
_data: ProjectData;
_eventsBasedObjectDatas: Map<String, EventsBasedObjectData>;
_imageManager: ImageManager;
_soundManager: SoundManager;
_fontManager: FontManager;
_jsonManager: JsonManager;
_model3DManager: Model3DManager;
_effectsManager: EffectsManager;
_bitmapFontManager: BitmapFontManager;
_maxFPS: integer;
_minFPS: integer;
_gameResolutionWidth: integer;
@@ -210,33 +145,12 @@ namespace gdjs {
this._options = options || {};
this._variables = new gdjs.VariablesContainer(data.variables);
this._data = data;
this._resourcesLoader = new gdjs.RuntimeGameResourcesLoader(this);
const resources = this._data.resources.resources;
this._imageManager = new gdjs.ImageManager(
resources,
this._resourcesLoader
);
this._soundManager = new gdjs.SoundManager(
resources,
this._resourcesLoader
);
this._fontManager = new gdjs.FontManager(
resources,
this._resourcesLoader
);
this._jsonManager = new gdjs.JsonManager(
resources,
this._resourcesLoader
);
this._bitmapFontManager = new gdjs.BitmapFontManager(
resources,
this._resourcesLoader,
this._imageManager
);
this._model3DManager = new gdjs.Model3DManager(
resources,
this._resourcesLoader
this._resourcesLoader = new gdjs.ResourceLoader(
this,
data.resources.resources,
getGlobalResourceNames(data),
data.layouts
);
this._effectsManager = new gdjs.EffectsManager();
this._maxFPS = this._data.properties.maxFPS;
@@ -315,12 +229,11 @@ namespace gdjs {
*/
setProjectData(projectData: ProjectData): void {
this._data = projectData;
this._imageManager.setResources(this._data.resources.resources);
this._soundManager.setResources(this._data.resources.resources);
this._fontManager.setResources(this._data.resources.resources);
this._jsonManager.setResources(this._data.resources.resources);
this._bitmapFontManager.setResources(this._data.resources.resources);
this._model3DManager.setResources(this._data.resources.resources);
this._resourcesLoader.setResources(
projectData.resources.resources,
getGlobalResourceNames(projectData),
projectData.layouts
);
}
/**
@@ -348,7 +261,7 @@ namespace gdjs {
* @return The sound manager.
*/
getSoundManager(): gdjs.HowlerSoundManager {
return this._soundManager;
return this._resourcesLoader.getSoundManager();
}
/**
@@ -356,7 +269,7 @@ namespace gdjs {
* @return The image manager.
*/
getImageManager(): gdjs.PixiImageManager {
return this._imageManager;
return this._resourcesLoader.getImageManager();
}
/**
@@ -364,7 +277,7 @@ namespace gdjs {
* @return The font manager.
*/
getFontManager(): gdjs.FontFaceObserverFontManager {
return this._fontManager;
return this._resourcesLoader.getFontManager();
}
/**
@@ -372,8 +285,25 @@ namespace gdjs {
* @return The bitmap font manager.
*/
getBitmapFontManager(): gdjs.BitmapFontManager {
// @ts-ignore
return this._bitmapFontManager;
return this._resourcesLoader.getBitmapFontManager();
}
/**
* Get the JSON manager of the game, used to load JSON from game
* resources.
* @return The json manager for the game
*/
getJsonManager(): gdjs.JsonManager {
return this._resourcesLoader.getJsonManager();
}
/**
* Get the 3D model manager of the game, used to load 3D model from game
* resources.
* @return The 3D model manager for the game
*/
getModel3DManager(): gdjs.Model3DManager {
return this._resourcesLoader.getModel3DManager();
}
/**
@@ -385,24 +315,6 @@ namespace gdjs {
return this._inputManager;
}
/**
* Get the JSON manager of the game, used to load JSON from game
* resources.
* @return The json manager for the game
*/
getJsonManager(): gdjs.JsonManager {
return this._jsonManager;
}
/**
* Get the 3D model manager of the game, used to load 3D model from game
* resources.
* @return The 3D model manager for the game
*/
getModel3DManager(): gdjs.Model3DManager {
return this._model3DManager;
}
/**
* Get the effects manager of the game, which allows to manage
* effects on runtime objects or runtime layers.
@@ -681,54 +593,151 @@ namespace gdjs {
}
/**
* Load all assets, displaying progress in renderer.
* Preload a scene assets as soon as possible in background.
*/
prioritizeLoadingOfScene(sceneName: string) {
// Don't await the scene assets to be loaded.
this._resourcesLoader.loadSceneResources(sceneName);
}
/**
* @return The progress of assets loading in background for a scene
* (between 0 and 1).
*/
getSceneLoadingProgress(sceneName: string): number {
return this._resourcesLoader.getSceneLoadingProgress(sceneName);
}
/**
* @returns true when all the resources of the given scene are loaded
* (but maybe not parsed).
*/
areSceneAssetsLoaded(sceneName: string): boolean {
return this._resourcesLoader.areSceneAssetsLoaded(sceneName);
}
/**
* @returns true when all the resources of the given scene are loaded and
* parsed.
*/
areSceneAssetsReady(sceneName: string): boolean {
return this._resourcesLoader.areSceneAssetsReady(sceneName);
}
/**
* Load all assets needed to display the 1st scene, displaying progress in
* renderer.
*/
loadAllAssets(
callback: () => void,
progressCallback?: (progress: float) => void
) {
this.loadAllAssetsAsync(progressCallback).then(callback);
this.loadFirstAssetsAndStartBackgroundLoading(
this._getFirstSceneName(),
progressCallback
).then(callback);
}
/**
* Load all assets, displaying progress in renderer.
* Load all assets needed to display the 1st scene, displaying progress in
* renderer.
*
* When a game is hot-reload, this method can be called with the current
* scene.
*/
loadAllAssetsAsync = async (
async loadFirstAssetsAndStartBackgroundLoading(
firstSceneName: string,
progressCallback?: (progress: float) => void
) => {
): Promise<void> {
try {
const loadingScreen = new gdjs.LoadingScreenRenderer(
this.getRenderer(),
this._imageManager,
this._data.properties.loadingScreen
await this._loadAssetsWithLoadingScreen(
/* isFirstScene = */ true,
async (onProgress) => {
// TODO Is a setting needed?
if (false) {
await this._resourcesLoader.loadAllResources(onProgress);
} else {
await this._resourcesLoader.loadGlobalAndFirstSceneResources(
firstSceneName,
onProgress
);
// Don't await as it must not block the first scene from starting.
this._resourcesLoader.loadAllSceneInBackground();
}
},
progressCallback
);
const allAssetsTotal = this._data.resources.resources.length;
let loadedAssets = 0;
const onProgress = (count: integer, total: integer) => {
const percent = Math.floor(
(100 * (loadedAssets + count)) / allAssetsTotal
);
loadingScreen.setPercent(percent);
if (progressCallback) {
progressCallback(percent);
}
};
loadedAssets += await this._imageManager.loadTextures(onProgress);
loadedAssets += await this._soundManager.preloadAudio(onProgress);
loadedAssets += await this._fontManager.loadFonts(onProgress);
loadedAssets += await this._jsonManager.preloadJsons(onProgress);
loadedAssets += await this._model3DManager.loadModels(onProgress);
await this._bitmapFontManager.loadBitmapFontData(onProgress);
await loadingScreen.unload();
// TODO This is probably not necessary in case of hot reload.
await gdjs.getAllAsynchronouslyLoadingLibraryPromise();
} catch (e) {
if (this._debuggerClient) this._debuggerClient.onUncaughtException(e);
throw e;
}
};
}
/**
* Load all assets for a given scene, displaying progress in renderer.
*/
async loadSceneAssets(
sceneName: string,
progressCallback?: (progress: float) => void
): Promise<void> {
await this._loadAssetsWithLoadingScreen(
/* isFirstLayout = */ false,
async (onProgress) => {
await this._resourcesLoader.loadAndProcessSceneResources(
sceneName,
onProgress
);
},
progressCallback
);
}
/**
* Load assets, displaying progress in renderer.
*/
private async _loadAssetsWithLoadingScreen(
isFirstScene: boolean,
loadAssets: (
onProgress: (count: integer, total: integer) => Promise<void>
) => Promise<void>,
progressCallback?: (progress: float) => void
): Promise<void> {
this.pause(true);
const loadingScreen = new gdjs.LoadingScreenRenderer(
this.getRenderer(),
this._resourcesLoader.getImageManager(),
this._data.properties.loadingScreen,
isFirstScene
);
const onProgress = async (count: integer, total: integer) => {
const percent = Math.floor((100 * count) / total);
loadingScreen.setPercent(percent);
if (progressCallback) {
progressCallback(percent);
}
const hasRendered = loadingScreen.renderIfNeeded();
if (hasRendered) {
// Give a chance to draw calls from the renderer to be handled.
await sleep(1);
}
};
await loadAssets(onProgress);
await loadingScreen.unload();
this.pause(false);
}
private _getFirstSceneName(): string {
const firstSceneName = this._data.firstLayout;
return this.hasScene(firstSceneName)
? firstSceneName
: // @ts-ignore - no risk of null object.
this.getSceneData().name;
}
/**
* Start the game loop, to be called once assets are loaded.
@@ -742,12 +751,8 @@ namespace gdjs {
this._forceGameResolutionUpdate();
// Load the first scene
const firstSceneName = this._data.firstLayout;
this._sceneStack.push(
this.hasScene(firstSceneName)
? firstSceneName
: // @ts-ignore - no risk of null object.
this.getSceneData().name,
this._getFirstSceneName(),
this._injectExternalLayout
);
this._watermark.displayAtStartup();

View File

@@ -147,6 +147,8 @@ namespace gdjs {
return true;
};
type RuntimeObjectCallback = (object: gdjs.RuntimeObject) => void;
/**
* RuntimeObject represents an object being used on a RuntimeScene.
*
@@ -164,9 +166,12 @@ namespace gdjs {
layer: string = '';
protected _nameId: integer;
protected _livingOnScene: boolean = true;
protected _spatialSearchSleepState: ObjectSleepState;
readonly id: integer;
private destroyCallbacks = new Set<() => void>();
// HitboxChanges happen a lot, an Array is faster to iterate.
private hitBoxChangedCallbacks: Array<RuntimeObjectCallback> = [];
_runtimeScene: gdjs.RuntimeInstanceContainer;
/**
@@ -181,12 +186,16 @@ namespace gdjs {
* not "thread safe" or "re-entrant algorithm" safe.
*/
pick: boolean = false;
pickingId: integer = 0;
//Hit boxes:
protected _defaultHitBoxes: gdjs.Polygon[] = [];
protected hitBoxes: gdjs.Polygon[];
protected hitBoxesDirty: boolean = true;
// TODO use a different AABB for collision mask and rendered image.
protected aabb: AABB = { min: [0, 0], max: [0, 0] };
_rtreeAABB: SearchedItem<RuntimeObject>;
protected _isIncludedInParentCollisionMask = true;
//Variables:
@@ -229,10 +238,11 @@ namespace gdjs {
instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: ObjectData & any
) {
const scene = instanceContainer.getScene();
this.name = objectData.name || '';
this.type = objectData.type || '';
this._nameId = RuntimeObject.getNameIdentifier(this.name);
this.id = instanceContainer.getScene().createNewUniqueId();
this.id = scene.createNewUniqueId();
this._runtimeScene = instanceContainer;
this._defaultHitBoxes.push(gdjs.Polygon.createRectangle(0, 0));
this.hitBoxes = this._defaultHitBoxes;
@@ -241,8 +251,20 @@ namespace gdjs {
);
this._totalForce = new gdjs.Force(0, 0, 0);
this._behaviorsTable = new Hashtable();
this._rtreeAABB = {
source: this,
minX: 0,
minY: 0,
maxX: 0,
maxY: 0,
};
this._spatialSearchSleepState = new gdjs.ObjectSleepState(
this,
() => !this.getVisibilityAABB(),
gdjs.ObjectSleepState.State.CanSleepThisFrame
);
for (let i = 0; i < objectData.effects.length; ++i) {
this._runtimeScene
scene
.getGame()
.getEffectsManager()
.initializeEffect(objectData.effects[i], this._rendererEffects, this);
@@ -439,6 +461,14 @@ namespace gdjs {
return false;
}
getSpatialSearchSleepState(): ObjectSleepState {
return this._spatialSearchSleepState;
}
isAlive(): boolean {
return this._livingOnScene;
}
/**
* Remove an object from a scene.
*
@@ -486,6 +516,31 @@ namespace gdjs {
onDestroyed(): void {}
registerHitboxChangedCallback(callback: RuntimeObjectCallback) {
if (this.hitBoxChangedCallbacks.includes(callback)) {
return;
}
this.hitBoxChangedCallbacks.push(callback);
}
/**
* Send a signal that the object hitboxes are no longer up to date.
*
* The signal is propagated to parents so
* {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified
* directly.
*/
invalidateHitboxes(): void {
// TODO EBO Check that no community extension set hitBoxesDirty to true
// directly.
this.hitBoxesDirty = true;
this._spatialSearchSleepState.wakeUp();
this._runtimeScene.onChildrenLocationChanged();
for (const callback of this.hitBoxChangedCallbacks) {
callback(this);
}
}
/**
* Called whenever the scene owning the object is paused.
* This should *not* impact objects, but some may need to inform their renderer.
@@ -570,20 +625,6 @@ namespace gdjs {
this.invalidateHitboxes();
}
/**
* Send a signal that the object hitboxes are no longer up to date.
*
* The signal is propagated to parents so
* {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified
* directly.
*/
invalidateHitboxes(): void {
// TODO EBO Check that no community extension set hitBoxesDirty to true
// directly.
this.hitBoxesDirty = true;
this._runtimeScene.onChildrenLocationChanged();
}
/**
* Get the X position of the object.
*
@@ -758,6 +799,7 @@ namespace gdjs {
oldLayer.getRenderer().remove3DRendererObject(rendererObject3D);
newLayer.getRenderer().add3DRendererObject(rendererObject3D);
}
this._runtimeScene.onObjectChangedOfLayer(this, oldLayer);
}
/**
@@ -1410,7 +1452,7 @@ namespace gdjs {
* @param multiplier Set the force multiplier
*/
addForceTowardObject(
object: gdjs.RuntimeObject,
object: gdjs.RuntimeObject | null,
len: float,
multiplier: integer
): void {
@@ -2276,10 +2318,12 @@ namespace gdjs {
* @param angleInDegrees The angle between the object and the target, in degrees.
*/
putAroundObject(
obj: gdjs.RuntimeObject,
obj: gdjs.RuntimeObject | null,
distance: float,
angleInDegrees: float
): void {
if (!obj) return;
this.putAround(
obj.getDrawableX() + obj.getCenterX(),
obj.getDrawableY() + obj.getCenterY(),

View File

@@ -6,6 +6,7 @@
namespace gdjs {
const logger = new gdjs.Logger('RuntimeScene');
const setupWarningLogger = new gdjs.Logger('RuntimeScene (setup warnings)');
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
/**
* A scene being played, containing instances of objects rendered on screen.
@@ -45,6 +46,17 @@ namespace gdjs {
_cachedGameResolutionWidth: integer;
_cachedGameResolutionHeight: integer;
private _frameIndex: integer = 0;
_layersCameraCoordinates: Record<string, SearchArea> = {};
private _layerObjectManagers = new Map<string, ObjectManager>();
/**
* Objects that were rendered for the last frame.
*
* They keep to be hide back without iterating every objects from the scene.
*/
private _objectsInsideCamera: Record<string, Array<RuntimeObject>> = {};
/**
* @param runtimeGame The game associated to this scene.
*/
@@ -81,6 +93,43 @@ namespace gdjs {
this._orderedLayers.push(layer);
}
addObject(object: gdjs.RuntimeObject): void {
super.addObject(object);
this._addObjectToLayerObjectManager(object);
}
onObjectChangedOfLayer(object: RuntimeObject, oldLayer: RuntimeLayer) {
this._removeObjectFromLayerObjectManager(object, oldLayer.getName());
this._addObjectToLayerObjectManager(object);
}
private _addObjectToLayerObjectManager(object: gdjs.RuntimeObject): void {
const layerName = object.getLayer();
let objectManager = this._layerObjectManagers.get(layerName);
if (!objectManager) {
objectManager = new gdjs.ObjectManager();
this._layerObjectManagers.set(layerName, objectManager);
}
objectManager.addObject(object);
}
markObjectForDeletion(object: gdjs.RuntimeObject): void {
super.markObjectForDeletion(object);
const layerName = object.getLayer();
this._removeObjectFromLayerObjectManager(object, layerName);
}
private _removeObjectFromLayerObjectManager(
object: gdjs.RuntimeObject,
layerName: string
): void {
let objectManager = this._layerObjectManagers.get(layerName);
if (!objectManager) {
return;
}
objectManager.deleteObject(object);
}
/**
* Should be called when the canvas where the scene is rendered has been resized.
* See gdjs.RuntimeGame.startGameLoop in particular.
@@ -427,6 +476,7 @@ namespace gdjs {
if (this._profiler) {
this._profiler.endFrame();
}
this._frameIndex++;
return !!this.getRequestedChange();
}
@@ -437,6 +487,26 @@ namespace gdjs {
this._renderer.render();
}
_updateLayersCameraCoordinates(scale: float) {
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const theLayer = this._layers.items[name];
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
name
] || { minX: 0, minY: 0, maxX: 0, maxY: 0 };
this._layersCameraCoordinates[name].minX =
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name].minY =
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
this._layersCameraCoordinates[name].maxX =
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name].maxY =
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
}
}
}
/**
* Called to update visibility of the renderers of objects
* rendered on the scene ("culling"), update effects (of visible objects)
@@ -446,50 +516,68 @@ namespace gdjs {
* object is too far from the camera of its layer ("culling").
*/
_updateObjectsPreRender() {
// Check awake objects only once every 64 frames.
if ((this._frameIndex & 63) === 0) {
for (const objectManager of this._layerObjectManagers.values()) {
objectManager.updateAwakeObjects();
}
}
if (this._timeManager.isFirstFrame()) {
super._updateObjectsPreRender();
return;
} else {
// After first frame, optimise rendering by setting only objects
// near camera as visible.
// TODO: For compatibility, pass a scale of `2`,
// meaning that size of cameras will be multiplied by 2 and so objects
// will be hidden if they are outside of this *larger* camera area.
// This is useful for:
// - objects not properly reporting their visibility AABB,
// (so we have a "safety margin") but these objects should be fixed
// instead.
// - objects having effects rendering outside of their visibility AABB.
// TODO (3D) culling - add support for 3D object culling?
this._updateLayersCameraCoordinates(2);
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const object = allInstancesList[i];
const rendererObject = object.getRendererObject();
if (rendererObject) {
if (object.isHidden()) {
rendererObject.visible = false;
} else {
const cameraCoords = this._layersCameraCoordinates[
object.getLayer()
];
if (!cameraCoords) {
continue;
}
const aabb = object.getVisibilityAABB();
rendererObject.visible =
// If no AABB is returned, the object should always be visible
!aabb ||
// If an AABB is there, it must be at least partially inside
// the camera bounds.
!(
aabb.min[0] > cameraCoords[2] ||
aabb.min[1] > cameraCoords[3] ||
aabb.max[0] < cameraCoords[0] ||
aabb.max[1] < cameraCoords[1]
);
}
rendererObject.visible = false;
}
}
}
// After first frame, optimise rendering by setting only objects
// near camera as visible.
// TODO: For compatibility, pass a scale of `2`,
// meaning that size of cameras will be multiplied by 2 and so objects
// will be hidden if they are outside of this *larger* camera area.
// This is useful for:
// - objects not properly reporting their visibility AABB,
// (so we have a "safety margin") but these objects should be fixed
// instead.
// - objects having effects rendering outside of their visibility AABB.
// TODO (3D) culling - add support for 3D object culling?
this._updateLayersCameraCoordinates(2);
// Reset objects that were visible last frame.
for (const layerName in this._objectsInsideCamera) {
for (const object of this._objectsInsideCamera[layerName]) {
const rendererObject = object.getRendererObject();
if (rendererObject) {
rendererObject.visible = false;
}
}
}
for (const layerName in this._layers.items) {
const cameraAABB = this._layersCameraCoordinates[layerName];
let objectsInsideCamera = this._objectsInsideCamera[layerName];
if (objectsInsideCamera === undefined) {
objectsInsideCamera = [];
this._objectsInsideCamera[layerName] = objectsInsideCamera;
}
if (!cameraAABB) {
continue;
}
const layerObjectManager = this._layerObjectManagers.get(layerName);
if (!layerObjectManager) {
continue;
}
// Find objects that are visible this frame.
objectsInsideCamera.length = 0;
layerObjectManager.search(cameraAABB, objectsInsideCamera);
for (const object of objectsInsideCamera) {
const rendererObject = object.getRendererObject();
if (rendererObject) {
rendererObject.visible = !object.isHidden();
// Update effects, only for visible objects.
if (rendererObject.visible) {
@@ -734,6 +822,10 @@ namespace gdjs {
sceneJustResumed(): boolean {
return this._isJustResumed;
}
getFrameIndex(): integer {
return this._frameIndex;
}
}
//The flags to describe the change request by a scene:

View File

@@ -8,6 +8,7 @@ namespace gdjs {
_runtimeGame: gdjs.RuntimeGame;
_stack: gdjs.RuntimeScene[] = [];
_wasFirstSceneLoaded: boolean = false;
_isNextLayoutLoading: boolean = false;
/**
* @param runtimeGame The runtime game that is using the scene stack
@@ -31,7 +32,7 @@ namespace gdjs {
}
step(elapsedTime: float): boolean {
if (this._stack.length === 0) {
if (this._isNextLayoutLoading || this._stack.length === 0) {
return false;
}
const currentScene = this._stack[this._stack.length - 1];
@@ -91,13 +92,34 @@ namespace gdjs {
* Pause the scene currently being played and start the new scene that is specified.
* If `externalLayoutName` is set, also instantiate the objects from this external layout.
*/
push(newSceneName: string, externalLayoutName?: string): gdjs.RuntimeScene {
push(
newSceneName: string,
externalLayoutName?: string
): gdjs.RuntimeScene | null {
// Tell the scene it's being paused
const currentScene = this._stack[this._stack.length - 1];
if (currentScene) {
currentScene.onPause();
}
// Avoid a risk of displaying an intermediate loading screen
// during 1 frame.
if (this._runtimeGame.areSceneAssetsReady(newSceneName)) {
return this._loadNewScene(newSceneName, externalLayoutName);
}
this._isNextLayoutLoading = true;
this._runtimeGame.loadSceneAssets(newSceneName).then(() => {
this._loadNewScene(newSceneName);
this._isNextLayoutLoading = false;
});
return null;
}
private _loadNewScene(
newSceneName: string,
externalLayoutName?: string
): gdjs.RuntimeScene {
// Load the new one
const newScene = new gdjs.RuntimeScene(this._runtimeGame);
newScene.loadFromScene(this._runtimeGame.getSceneData(newSceneName));
@@ -127,7 +149,7 @@ namespace gdjs {
* Start the specified scene, replacing the one currently being played.
* If `clear` is set to true, all running scenes are also removed from the stack of scenes.
*/
replace(newSceneName: string, clear?: boolean): gdjs.RuntimeScene {
replace(newSceneName: string, clear?: boolean): gdjs.RuntimeScene | null {
if (!!clear) {
// Unload all the scenes
while (this._stack.length !== 0) {

View File

@@ -1379,7 +1379,7 @@ namespace gdjs {
* @param scene The scene containing the object
* @deprecated
*/
turnTowardObject(obj: gdjs.RuntimeObject, scene: gdjs.RuntimeScene) {
turnTowardObject(obj: gdjs.RuntimeObject | null, scene: gdjs.RuntimeScene) {
if (obj === null) {
return;
}

View File

@@ -12,6 +12,7 @@ declare interface ProjectData {
gdVersion: GdVersionData;
properties: ProjectPropertiesData;
resources: ResourcesData;
usedResources: ResourceReference[];
objects: ObjectData[];
variables: RootVariableData[];
layouts: LayoutData[];
@@ -83,6 +84,7 @@ declare interface LayoutData {
objects: ObjectData[];
layers: LayerData[];
behaviorsSharedData: BehaviorSharedData[];
usedResources: ResourceReference[];
}
declare interface EventsFunctionsExtensionData {
@@ -264,6 +266,10 @@ declare interface ResourceData {
preloadInCache?: boolean;
}
declare interface ResourceReference {
name: string;
}
declare type ResourceKind =
| 'audio'
| 'image'

19
GDJS/Runtime/types/rbush.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
type SearchedItem<T> = {
source: T;
minX: float;
minY: float;
maxX: float;
maxY: float;
};
declare class RBush<T> {
constructor(maxEntries?: number);
search(bbox: SearchArea, result?: Array<T>): Array<T>;
insert(item: SearchedItem<T>): RBush<T>;
clear(): RBush<T>;
remove(
item: SearchedItem<T>,
equalsFn?: (item: SearchedItem<T>, otherItem: SearchedItem<T>) => boolean
): RBush<T>;
}

View File

@@ -49,8 +49,12 @@ module.exports = function (config) {
'./newIDE/app/resources/GDJS/Runtime/fontfaceobserver-font-manager/fontfaceobserver-font-manager.js',
'./newIDE/app/resources/GDJS/Runtime/jsonmanager.js',
'./newIDE/app/resources/GDJS/Runtime/Model3DManager.js',
'./newIDE/app/resources/GDJS/Runtime/ResourceLoader.js',
'./newIDE/app/resources/GDJS/Runtime/ResourceCache.js',
'./newIDE/app/resources/GDJS/Runtime/timemanager.js',
'./newIDE/app/resources/GDJS/Runtime/polygon.js',
'./newIDE/app/resources/GDJS/Runtime/ObjectSleepState.js',
'./newIDE/app/resources/GDJS/Runtime/ObjectManager.js',
'./newIDE/app/resources/GDJS/Runtime/runtimeobject.js',
'./newIDE/app/resources/GDJS/Runtime/RuntimeInstanceContainer.js',
'./newIDE/app/resources/GDJS/Runtime/runtimescene.js',

View File

@@ -64,6 +64,7 @@ gdjs.getPixiRuntimeGame = (settings) => {
externalLayouts: [],
resources: (settings && settings.resources) || { resources: [] },
eventsFunctionsExtensions: [],
usedResources: [],
});
return runtimeGame;

View File

@@ -63,7 +63,23 @@ gdjs.getPixiRuntimeGameWithAssets = () => {
revision: 0,
},
objects: [],
layouts: [],
layouts: [
{
r: 0,
v: 0,
b: 0,
mangledName: '',
name: '',
objects: [],
layers: [],
instances: [],
behaviorsSharedData: [],
stopSoundsOnStartup: false,
title: '',
variables: [],
usedResources: [],
},
],
externalLayouts: [],
resources: {
resources: [
@@ -76,6 +92,7 @@ gdjs.getPixiRuntimeGameWithAssets = () => {
},
],
},
usedResources: [{ name: 'base/tests-utils/assets/64x64.jpg' }],
// Used in CustomRuntimeObjects.js
eventsFunctionsExtensions: [
{

View File

@@ -0,0 +1,40 @@
/*
* GDevelop JS Platform
* Copyright 2013-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
// implements gdjs.ResourceManager
gdjs.MockedResourceManager = class MockedResourceManager {
loadResourceCallbacks = new Map();
loadResource(resourceName) {
const that = this;
return new Promise((resolve, reject) => {
that.loadResourceCallbacks.set(resourceName, resolve);
});
}
async processResource(resourceName) {}
/**
* @param {string} resourceName
* @returns {boolean}
*/
isResourceDownloadPending(resourceName) {
return this.loadResourceCallbacks.has(resourceName);
}
/**
* @param {string} resourceName
*/
markPendingResourcesAsLoaded(resourceName) {
const loadResourceCallback = this.loadResourceCallbacks.get(resourceName);
loadResourceCallback();
this.loadResourceCallbacks.delete(resourceName);
}
getResourceKinds() {
return ['fake-heavy-resource'];
}
}

View File

@@ -3,12 +3,15 @@
*/
gdjs.TestRuntimeScene = class TestRuntimeScene extends gdjs.RuntimeScene {
/**
* @param {gdjs.RuntimeGame} runtimeGame
* @param {gdjs.RuntimeGame} runtimeGame
*/
constructor(runtimeGame) {
constructor(runtimeGame, layerNames = ['']) {
super(runtimeGame);
this.addLayer({ name: '', cameras: [], effects: [] });
for (const layerName of layerNames) {
this.addLayer({ name: layerName, cameras: [], effects: [] });
}
}
/**

View File

@@ -20,7 +20,32 @@
/** @type {?float} */
_customCenterY = null;
constructor(runtimeScene, objectData) {
constructor(runtimeScene, objectData = {
name: 'MySprite',
type: '',
behaviors: [],
effects: [],
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: { x: 0, y: 0 },
centerPoint: { x: 0, y: 0 },
points: [
{ name: 'Center', x: 0, y: 0 },
{ name: 'Origin', x: 0, y: 0 },
],
hasCustomCollisionMask: false,
},
],
},
],
},
],
}) {
// *ALWAYS* call the base gdjs.RuntimeObject constructor.
super(runtimeScene, objectData);
@@ -50,10 +75,6 @@
this.invalidateHitboxes();
}
getRendererObject() {
return null;
}
getWidth() {
return this._customWidth;
}

View File

@@ -106,6 +106,7 @@ describe('gdjs.EffectsManager', () => {
behaviorsSharedData: [],
objects: [],
instances: [],
usedResources: [],
});
const runtimeLayer = runtimeScene.getLayer('');

View File

@@ -0,0 +1,105 @@
// @ts-check
describe('gdjs.RuntimeScene culling tests', () => {
it('should hide objects moving outside of the screen', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game, ['', 'MyLayer']);
scene.getLayer('MyLayer').setCameraX(8000);
const object = new gdjs.TestSpriteRuntimeObject(scene);
object.setUnscaledWidthAndHeight(100, 100);
object.setCustomWidthAndHeight(100, 100);
scene.addObject(object);
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(true);
object.setY(8000);
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(false);
});
it('should show objects moving back in the screen', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game, ['']);
const object = new gdjs.TestSpriteRuntimeObject(scene);
object.setUnscaledWidthAndHeight(100, 100);
object.setCustomWidthAndHeight(100, 100);
scene.addObject(object);
object.setY(8000);
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(false);
object.setY(200);
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(true);
});
it('should handle objects changing of layer', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game, ['']);
const object = new gdjs.TestSpriteRuntimeObject(scene);
object.setUnscaledWidthAndHeight(100, 100);
object.setCustomWidthAndHeight(100, 100);
scene.addObject(object);
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(true);
object.setLayer('MyLayer');
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(false);
object.setLayer('');
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(true);
});
// This is important to avoid games with big levels to be CPU limited during
// the 2 first seconds.
it('should allow objects to sleep at the end of the 1st frame', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game, ['']);
const object = new gdjs.TestSpriteRuntimeObject(scene);
object.setUnscaledWidthAndHeight(100, 100);
object.setCustomWidthAndHeight(100, 100);
scene.addObject(object);
scene.renderAndStep(1000 / 60);
expect(object.getRendererObject().visible).to.be(true);
expect(object.getSpatialSearchSleepState().isAwake()).to.be(false);
object.setY(200);
expect(object.getSpatialSearchSleepState().isAwake()).to.be(true);
});
it('should put objects to sleep when they don\'t move for 2 seconds', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game, ['']);
const object = new gdjs.TestSpriteRuntimeObject(scene);
object.setUnscaledWidthAndHeight(100, 100);
object.setCustomWidthAndHeight(100, 100);
scene.addObject(object);
scene.renderAndStep(1000 / 60);
object.setY(200);
expect(object.getSpatialSearchSleepState().isAwake()).to.be(true);
// Objects can sleep every 64 frames if they haven't moved for 1 second.
for (let index = 0; index < 60 + 64; index++) {
scene.renderAndStep(1000 / 60);
}
expect(object.getSpatialSearchSleepState().isAwake()).to.be(false);
expect(object.getRendererObject().visible).to.be(true);
});
});

View File

@@ -46,6 +46,7 @@ describe('gdjs.RuntimeScene integration tests', function () {
},
],
instances: [],
usedResources: [],
});
const object = runtimeScene.createObject('Object1');

View File

@@ -3,47 +3,62 @@
/**
* Tests for gdjs.SceneStack.
*/
describe('gdjs.SceneStack', function () {
const runtimeGame = gdjs.getPixiRuntimeGame({
layouts: [
{
r: 0,
v: 0,
b: 0,
mangledName: 'Scene2',
name: 'Scene 1',
objects: [],
layers: [],
instances: [],
behaviorsSharedData: [],
stopSoundsOnStartup: false,
title: '',
variables: [],
},
{
r: 0,
v: 0,
b: 0,
mangledName: 'Scene2',
name: 'Scene 2',
objects: [],
layers: [],
instances: [],
behaviorsSharedData: [],
stopSoundsOnStartup: false,
title: '',
variables: [],
},
],
});
var sceneStack = runtimeGame._sceneStack;
describe('gdjs.SceneStack', () => {
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
const createSene = (name, usedResources) => {
return {
r: 0,
v: 0,
b: 0,
mangledName: name,
name,
objects: [],
layers: [],
instances: [],
behaviorsSharedData: [],
stopSoundsOnStartup: false,
title: '',
variables: [],
usedResources,
};
};
const gameSettings = {
layouts: [
createSene('Scene 1', []),
createSene('Scene 2', [{ name: 'base/tests-utils/assets/64x64.jpg' }]),
],
resources: {
resources: [
{
kind: 'image',
name: 'base/tests-utils/assets/64x64.jpg',
metadata: '',
file: 'base/tests-utils/assets/64x64.jpg',
userAdded: true,
},
],
},
};
it('should support pushing, replacing and popping scenes', async () => {
//@ts-ignore
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettings);
let sceneStack = runtimeGame._sceneStack;
// Async asset loading is not tested here.
await runtimeGame._resourcesLoader.loadAllResources(() => {});
it('should support pushing, replacing and popping scenes', function () {
// Set up some scene callbacks.
/** @type gdjs.RuntimeScene | null */
let firstLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastUnloadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastPausedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastResumedScene = null;
const onFirstRuntimeSceneLoaded = (runtimeScene) => {
@@ -74,22 +89,22 @@ describe('gdjs.SceneStack', function () {
expect(firstLoadedScene).to.be(null);
expect(sceneStack.wasFirstSceneLoaded()).to.be(false);
var scene1 = sceneStack.push('Scene 1');
let scene1 = sceneStack.push('Scene 1');
expect(lastLoadedScene).to.be(scene1);
expect(firstLoadedScene).to.be(scene1);
expect(sceneStack.wasFirstSceneLoaded()).to.be(true);
var scene2 = sceneStack.push('Scene 2');
let scene2 = sceneStack.push('Scene 2');
expect(lastPausedScene).to.be(scene1);
expect(lastLoadedScene).to.be(scene2);
expect(firstLoadedScene).to.be(scene1); // Not changed
var scene3 = sceneStack.push('Scene 1');
let scene3 = sceneStack.push('Scene 1');
expect(lastPausedScene).to.be(scene2);
expect(lastLoadedScene).to.be(scene3);
expect(firstLoadedScene).to.be(scene1); // Not changed
var scene4 = sceneStack.replace('Scene 1');
let scene4 = sceneStack.replace('Scene 1');
expect(lastPausedScene).to.be(scene2); // Not changed
expect(lastUnloadedScene).to.be(scene3);
expect(lastLoadedScene).to.be(scene4);
@@ -107,7 +122,7 @@ describe('gdjs.SceneStack', function () {
expect(lastPausedScene).to.be(scene2); // Not changed
expect(firstLoadedScene).to.be(scene1); // Not changed
var scene5 = sceneStack.replace('Scene 2', true);
let scene5 = sceneStack.replace('Scene 2', true);
expect(lastLoadedScene).to.be(scene5);
expect(lastUnloadedScene).to.be(scene1);
expect(lastPausedScene).to.be(scene2); // Not changed
@@ -130,4 +145,390 @@ describe('gdjs.SceneStack', function () {
gdjs._unregisterCallback(onRuntimeScenePaused);
gdjs._unregisterCallback(onRuntimeSceneResumed);
});
const gameSettingsWithHeavyResource = {
layouts: [
createSene('Scene 1', [{ name: 'fake-heavy-resource1.png' }]),
createSene('Scene 2', [{ name: 'fake-heavy-resource2.png' }]),
createSene('Scene 3', [{ name: 'fake-heavy-resource3.png' }]),
createSene('Scene 4', [{ name: 'fake-heavy-resource4.png' }]),
],
resources: {
resources: [
{
kind: 'fake-heavy-resource',
name: 'fake-heavy-resource1.png',
metadata: '',
file: 'fake-heavy-resource1.png',
userAdded: true,
},
{
kind: 'fake-heavy-resource',
name: 'fake-heavy-resource2.png',
metadata: '',
file: 'fake-heavy-resource2.png',
userAdded: true,
},
{
kind: 'fake-heavy-resource',
name: 'fake-heavy-resource3.png',
metadata: '',
file: 'fake-heavy-resource3.png',
userAdded: true,
},
{
kind: 'fake-heavy-resource',
name: 'fake-heavy-resource4.png',
metadata: '',
file: 'fake-heavy-resource4.png',
userAdded: true,
},
],
},
};
it('can start a layout when all its assets are already downloaded', async () => {
const mockedResourceManager = new gdjs.MockedResourceManager();
//@ts-ignore
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithHeavyResource);
runtimeGame._resourcesLoader._resourceManagersMap.set(
//@ts-ignore
'fake-heavy-resource',
mockedResourceManager
);
let sceneStack = runtimeGame._sceneStack;
// Set up some scene callbacks.
/** @type gdjs.RuntimeScene | null */
let firstLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastPausedScene = null;
const onFirstRuntimeSceneLoaded = (runtimeScene) => {
firstLoadedScene = runtimeScene;
};
const onRuntimeSceneLoaded = (runtimeScene) => {
lastLoadedScene = runtimeScene;
};
const onRuntimeScenePaused = (runtimeScene) => {
lastPausedScene = runtimeScene;
};
gdjs.registerFirstRuntimeSceneLoadedCallback(onFirstRuntimeSceneLoaded);
gdjs.registerRuntimeSceneLoadedCallback(onRuntimeSceneLoaded);
gdjs.registerRuntimeScenePausedCallback(onRuntimeScenePaused);
// The test do not await because test test will unblock
// `loadFirstAssetsAsync` with `markPendingResourcesAsLoaded`.
runtimeGame.loadFirstAssetsAndStartBackgroundLoading('Scene 1');
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource1.png'
)
).to.be(true);
// No layout has loaded.
expect(lastLoadedScene).to.be(null);
expect(firstLoadedScene).to.be(null);
expect(sceneStack.wasFirstSceneLoaded()).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 1')).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
// Assets of the 1st layout are downloaded before the layout is pushed.
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource1.png'
);
await delay(10);
sceneStack.push('Scene 1');
// The 1st layout is loaded
expect(lastLoadedScene).not.to.be(null);
expect(firstLoadedScene).not.to.be(null);
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(firstLoadedScene.getName()).to.be('Scene 1');
expect(sceneStack.wasFirstSceneLoaded()).to.be(true);
expect(runtimeGame.areSceneAssetsLoaded('Scene 1')).to.be(true);
expect(runtimeGame.areSceneAssetsReady('Scene 1')).to.be(true);
expect(runtimeGame.areSceneAssetsLoaded('Scene 2')).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
// "Scene 2" is loading in background.
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource2.png'
)
).to.be(true);
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
// Finish to load "Scene 2" assets.
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource2.png'
);
await delay(10);
expect(runtimeGame.areSceneAssetsLoaded('Scene 2')).to.be(true);
// The player triggers "Scene 2" to start.
sceneStack.push('Scene 2');
// "Scene 2" is loaded for the 1st time, assets are processed
// asynchronously.
await delay(10);
expect(lastPausedScene).not.to.be(null);
//@ts-ignore
expect(lastPausedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 2');
// The player triggers "Scene 1" to start.
sceneStack.push('Scene 1');
// "Scene 1" has already been shown the scene change is done synchronously.
expect(lastPausedScene).not.to.be(null);
//@ts-ignore
expect(lastPausedScene.getName()).to.be('Scene 2');
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 1');
// Remove all the global callbacks
gdjs._unregisterCallback(onFirstRuntimeSceneLoaded);
gdjs._unregisterCallback(onRuntimeSceneLoaded);
gdjs._unregisterCallback(onRuntimeScenePaused);
});
it('can start a layout while assets loading and wait them to finish', async () => {
const mockedResourceManager = new gdjs.MockedResourceManager();
//@ts-ignore
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithHeavyResource);
runtimeGame._resourcesLoader._resourceManagersMap.set(
//@ts-ignore
'fake-heavy-resource',
mockedResourceManager
);
let sceneStack = runtimeGame._sceneStack;
// Set up some scene callbacks.
/** @type gdjs.RuntimeScene | null */
let firstLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastPausedScene = null;
const onFirstRuntimeSceneLoaded = (runtimeScene) => {
firstLoadedScene = runtimeScene;
};
const onRuntimeSceneLoaded = (runtimeScene) => {
lastLoadedScene = runtimeScene;
};
const onRuntimeScenePaused = (runtimeScene) => {
lastPausedScene = runtimeScene;
};
gdjs.registerFirstRuntimeSceneLoadedCallback(onFirstRuntimeSceneLoaded);
gdjs.registerRuntimeSceneLoadedCallback(onRuntimeSceneLoaded);
gdjs.registerRuntimeScenePausedCallback(onRuntimeScenePaused);
// The test do not await because test test will unblock
// `loadFirstAssetsAsync` with `markPendingResourcesAsLoaded`.
runtimeGame.loadFirstAssetsAndStartBackgroundLoading('Scene 1');
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource1.png'
)
).to.be(true);
// No layout has loaded.
expect(lastLoadedScene).to.be(null);
expect(firstLoadedScene).to.be(null);
expect(sceneStack.wasFirstSceneLoaded()).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 1')).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
// Assets of the 1st layout are downloaded before the layout is pushed.
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource1.png'
);
await delay(10);
sceneStack.push('Scene 1');
// The 1st layout is loaded
expect(lastLoadedScene).not.to.be(null);
expect(firstLoadedScene).not.to.be(null);
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(firstLoadedScene.getName()).to.be('Scene 1');
expect(sceneStack.wasFirstSceneLoaded()).to.be(true);
expect(runtimeGame.areSceneAssetsReady('Scene 1')).to.be(true);
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
// The 2nd layout is loading in background.
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource2.png'
)
).to.be(true);
// The player triggers "Scene 2" to load.
let scene2 = sceneStack.push('Scene 2');
expect(scene2).to.be(null);
await delay(10);
// The 2nd layout is not loaded because its assets are still being downloaded.
//@ts-ignore
expect(lastPausedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 1');
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource2.png'
);
await delay(10);
// The 2nd layout is now loaded.
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(true);
expect(lastPausedScene).not.to.be(null);
//@ts-ignore
expect(lastPausedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 2');
// Remove all the global callbacks
gdjs._unregisterCallback(onFirstRuntimeSceneLoaded);
gdjs._unregisterCallback(onRuntimeSceneLoaded);
gdjs._unregisterCallback(onRuntimeScenePaused);
});
it('can start a layout which assets loading didn\'t stated yet and wait them to finish', async () => {
const mockedResourceManager = new gdjs.MockedResourceManager();
//@ts-ignore
const runtimeGame = gdjs.getPixiRuntimeGame(gameSettingsWithHeavyResource);
runtimeGame._resourcesLoader._resourceManagersMap.set(
//@ts-ignore
'fake-heavy-resource',
mockedResourceManager
);
let sceneStack = runtimeGame._sceneStack;
// Set up some scene callbacks.
/** @type gdjs.RuntimeScene | null */
let firstLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastLoadedScene = null;
/** @type gdjs.RuntimeScene | null */
let lastPausedScene = null;
const onFirstRuntimeSceneLoaded = (runtimeScene) => {
firstLoadedScene = runtimeScene;
};
const onRuntimeSceneLoaded = (runtimeScene) => {
lastLoadedScene = runtimeScene;
};
const onRuntimeScenePaused = (runtimeScene) => {
lastPausedScene = runtimeScene;
};
gdjs.registerFirstRuntimeSceneLoadedCallback(onFirstRuntimeSceneLoaded);
gdjs.registerRuntimeSceneLoadedCallback(onRuntimeSceneLoaded);
gdjs.registerRuntimeScenePausedCallback(onRuntimeScenePaused);
// The test do not await because test test will unblock
// `loadFirstAssetsAsync` with `markPendingResourcesAsLoaded`.
runtimeGame.loadFirstAssetsAndStartBackgroundLoading('Scene 1');
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource1.png'
)
).to.be(true);
// No layout has loaded.
expect(lastLoadedScene).to.be(null);
expect(firstLoadedScene).to.be(null);
expect(sceneStack.wasFirstSceneLoaded()).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 1')).to.be(false);
expect(runtimeGame.areSceneAssetsReady('Scene 2')).to.be(false);
// Assets of the 1st layout are downloaded before the layout is pushed.
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource1.png'
);
await delay(10);
sceneStack.push('Scene 1');
// The 1st layout is loaded
expect(lastLoadedScene).not.to.be(null);
expect(firstLoadedScene).not.to.be(null);
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(firstLoadedScene.getName()).to.be('Scene 1');
expect(sceneStack.wasFirstSceneLoaded()).to.be(true);
expect(runtimeGame.areSceneAssetsLoaded('Scene 1')).to.be(true);
expect(runtimeGame.areSceneAssetsLoaded('Scene 2')).to.be(false);
// "Scene 2" is loaded on background but is blocked because
// 'fake-heavy-resource2.png' take a lot of time to load.
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource2.png'
)
).to.be(true);
expect(runtimeGame.areSceneAssetsLoaded('Scene 2')).to.be(false);
// The player triggers "Scene 4" to load.
let scene4 = sceneStack.push('Scene 4');
expect(scene4).to.be(null);
await delay(10);
// "Scene 4" loading doesn't start yet as assets are currently downloading
// for "Scene 2".
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource4.png'
)
).to.be(false);
expect(runtimeGame.areSceneAssetsLoaded('Scene 2')).to.be(false);
expect(runtimeGame.areSceneAssetsLoaded('Scene 4')).to.be(false);
// Finish to download "Scene 2" assets.
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource2.png'
);
await delay(10);
// "Scene 4" assets are now downloading.
expect(
mockedResourceManager.isResourceDownloadPending(
'fake-heavy-resource4.png'
)
).to.be(true);
// "Scene 4" is not loaded because its assets are still being downloading.
//@ts-ignore
expect(lastPausedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 1');
expect(runtimeGame.areSceneAssetsLoaded('Scene 2')).to.be(true);
expect(runtimeGame.areSceneAssetsLoaded('Scene 4')).to.be(false);
mockedResourceManager.markPendingResourcesAsLoaded(
'fake-heavy-resource4.png'
);
await delay(10);
// "Scene 4" is now loaded.
expect(runtimeGame.areSceneAssetsReady('Scene 4')).to.be(true);
expect(lastPausedScene).not.to.be(null);
//@ts-ignore
expect(lastPausedScene.getName()).to.be('Scene 1');
//@ts-ignore
expect(lastLoadedScene.getName()).to.be('Scene 4');
// Remove all the global callbacks
gdjs._unregisterCallback(onFirstRuntimeSceneLoaded);
gdjs._unregisterCallback(onRuntimeSceneLoaded);
gdjs._unregisterCallback(onRuntimeScenePaused);
});
});

View File

@@ -12,8 +12,17 @@ if(NOT EMSCRIPTEN)
endif()
# Compilation flags (https://emscripten.org/docs/tools_reference/emcc.html):
add_compile_options(-O2) # Optimizations during compilation
#add_compile_options(-g --profiling) # Uncomment for debugging + profiling support
add_compile_options(-O3 -flto) # Optimizations during compilation
# add_compile_options(-fwasm-exceptions) # Enable exceptions
if(NOT DISABLE_EMSCRIPTEN_LINK_OPTIMIZATIONS)
add_compile_options(-flto) # The compiler needs to know if there will be link time optimisations
endif()
# Compiler debugging options
#
# add_compile_options(-fsanitize=address) # Uncomment to auto-detect occurences of memory bugs (memory leak, use after free, overflows, ...) - also enable linking below!
# add_compile_options(-fsanitize=undefined) # Uncomment to auto-detect occurences of undefined behavior - also enable linking below!
# add_compile_options(-g) # Uncomment for debugging support
# add_compile_options(--profiling) # Uncomment for profiling support
# Common directories:
@@ -64,7 +73,7 @@ if(DISABLE_EMSCRIPTEN_LINK_OPTIMIZATIONS)
message(STATUS "Disabling optimization at link time for (slightly) faster build")
target_link_libraries(GD "-O0")
else()
target_link_libraries(GD "-O2")
target_link_libraries(GD "-O3 -flto")
endif()
target_link_libraries(GD "--post-js ${GD_base_dir}/GDevelop.js/Bindings/glue.js")
target_link_libraries(GD "--post-js ${GD_base_dir}/GDevelop.js/Bindings/postjs.js")
@@ -73,7 +82,17 @@ target_link_libraries(GD "-s EXPORT_NAME=\"initializeGDevelopJs\"") # Global fun
target_link_libraries(GD "-s TOTAL_MEMORY=48MB") # Get some initial memory size that is a bit bigger than the default.
target_link_libraries(GD "-s ALLOW_MEMORY_GROWTH=1")
target_link_libraries(GD "-s ERROR_ON_UNDEFINED_SYMBOLS=0")
target_link_libraries(GD "-s \"EXTRA_EXPORTED_RUNTIME_METHODS=['addOnPreMain', 'calledRun', 'UTF8ToString']\"")
target_link_libraries(GD "-s \"EXPORTED_FUNCTIONS=['_free']\"")
# Linker debugging options
#
# target_link_libraries(GD "-s DEMANGLE_SUPPORT=1") # Demangle stack traces
# target_link_libraries(GD "-s ASSERTIONS=1") # Basic runtime memory allocation checks (necessary for wasm exceptions stack traces)
# target_link_libraries(GD "-s ASSERTIONS=2 -s SAFE_HEAP=1") # Uncomment to do runtime checks for memory allocations and access errors
# target_link_libraries(GD "-fsanitize=address") # Uncomment to auto-detect occurences of memory bugs (memory leak, use after free, overflows, ...) - also enable compiling above!
# target_link_libraries(GD "-fsanitize=undefined") # Uncomment to auto-detect occurences of undefined behavior - also enable compiling above!
# target_link_libraries(--cpuprofiler) # Uncomment for interactive performance profiling
# target_link_libraries(--memoryprofiler) # Uncomment for interactive memory profiling
# Even if we're building an "executable", prefix it by lib as it's used as a library.
set_target_properties(GD PROPERTIES PREFIX "lib")

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