mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
397 Commits
d474c2a47e
...
shape-pain
Author | SHA1 | Date | |
---|---|---|---|
![]() |
62ed7a4998 | ||
![]() |
dc5dab7086 | ||
![]() |
dad7546099 | ||
![]() |
0a55eb46d3 | ||
![]() |
2678d86783 | ||
![]() |
b31618568a | ||
![]() |
b3b4681c51 | ||
![]() |
b1b7643efc | ||
![]() |
d43479cbe5 | ||
![]() |
76d43e1695 | ||
![]() |
c3ee9d9891 | ||
![]() |
313f7857e2 | ||
![]() |
3ac2aec229 | ||
![]() |
92e8dfdf04 | ||
![]() |
80726da56d | ||
![]() |
3098fb034f | ||
![]() |
9d0f172663 | ||
![]() |
e0d2f16dc1 | ||
![]() |
0578c45b0f | ||
![]() |
a8866a7bd6 | ||
![]() |
ec62e33530 | ||
![]() |
e1f12837f1 | ||
![]() |
732f3c14b9 | ||
![]() |
7fdad45192 | ||
![]() |
94590d3713 | ||
![]() |
de310512a3 | ||
![]() |
a9edd2f21a | ||
![]() |
0cc03bd4b5 | ||
![]() |
cdbcaf59d9 | ||
![]() |
4e9b09e426 | ||
![]() |
3d843a0170 | ||
![]() |
ee2f7fb8dc | ||
![]() |
a780601230 | ||
![]() |
cc42923e16 | ||
![]() |
0e8a223b24 | ||
![]() |
c2f17f9348 | ||
![]() |
0ba59d5d7f | ||
![]() |
6878a4cd75 | ||
![]() |
33eed58c62 | ||
![]() |
f516eff739 | ||
![]() |
baefc272f6 | ||
![]() |
2badc72dfb | ||
![]() |
5e54f02061 | ||
![]() |
827bb2d08d | ||
![]() |
0fda0baa48 | ||
![]() |
e655cc0661 | ||
![]() |
225d1b37ab | ||
![]() |
5a0ab9dffa | ||
![]() |
9300604d56 | ||
![]() |
02fcf1dbc2 | ||
![]() |
a70b2e2c2c | ||
![]() |
3e04b5a82f | ||
![]() |
cbcf0b7b70 | ||
![]() |
5d1fe83655 | ||
![]() |
7cf70a6fd7 | ||
![]() |
24c2dbc340 | ||
![]() |
290901849c | ||
![]() |
59721cba7e | ||
![]() |
53cd78bb45 | ||
![]() |
49f8ce9385 | ||
![]() |
de2a9725f9 | ||
![]() |
b2ec3b5387 | ||
![]() |
1873b5e592 | ||
![]() |
e47e35c090 | ||
![]() |
ae572683f1 | ||
![]() |
f8e387230f | ||
![]() |
9451e5969f | ||
![]() |
ad7ddf09a3 | ||
![]() |
8e70930d8d | ||
![]() |
ab3bda24dc | ||
![]() |
d33a7331b3 | ||
![]() |
4d3793815f | ||
![]() |
e1835d1144 | ||
![]() |
72068460e1 | ||
![]() |
5da76ae655 | ||
![]() |
b5b25ad710 | ||
![]() |
2279f069af | ||
![]() |
f0a68db0d4 | ||
![]() |
15ed28ad8d | ||
![]() |
4291d5597a | ||
![]() |
7af3fa5f2f | ||
![]() |
5ce9591f68 | ||
![]() |
d5929010a7 | ||
![]() |
30f2f5256b | ||
![]() |
806d59fb88 | ||
![]() |
e5f18ae2d8 | ||
![]() |
15a7fd1f85 | ||
![]() |
3678a0dd45 | ||
![]() |
197bd913b8 | ||
![]() |
baef911d61 | ||
![]() |
771e16e779 | ||
![]() |
a2cf8e694b | ||
![]() |
cb151ea30e | ||
![]() |
7d1ebc8963 | ||
![]() |
510a699c03 | ||
![]() |
08892b68d4 | ||
![]() |
e079fa4108 | ||
![]() |
5a26e883b8 | ||
![]() |
6bf5b389b5 | ||
![]() |
2837a2906a | ||
![]() |
8c63fae2f2 | ||
![]() |
f837c22290 | ||
![]() |
465bfa4deb | ||
![]() |
a047ecdf9c | ||
![]() |
04c28de00b | ||
![]() |
a7df6de044 | ||
![]() |
81c3199d00 | ||
![]() |
dbe93a4bfd | ||
![]() |
e0edbe8d57 | ||
![]() |
c25d51805f | ||
![]() |
ab695370bb | ||
![]() |
15ee216e33 | ||
![]() |
3fac0522c9 | ||
![]() |
8d502d7c5c | ||
![]() |
12d18c45bc | ||
![]() |
d255ab458b | ||
![]() |
fbfa0315de | ||
![]() |
9575705d29 | ||
![]() |
1223eaa348 | ||
![]() |
21ea077768 | ||
![]() |
11895decd9 | ||
![]() |
c0e1e9fac6 | ||
![]() |
bd9c631e1b | ||
![]() |
4a6e8ef664 | ||
![]() |
e25345000d | ||
![]() |
111d37fc15 | ||
![]() |
4a83c9eb59 | ||
![]() |
177cb2c519 | ||
![]() |
b5d69dee4c | ||
![]() |
fbdaebe575 | ||
![]() |
2a2fd75ca3 | ||
![]() |
af7563b4b7 | ||
![]() |
1861c3be41 | ||
![]() |
895dc625eb | ||
![]() |
52aea76677 | ||
![]() |
4f87191176 | ||
![]() |
43d4e2e8cc | ||
![]() |
cbfaa13978 | ||
![]() |
0aba5cf551 | ||
![]() |
a3ef07c163 | ||
![]() |
a8ede5eee7 | ||
![]() |
ce965ca31c | ||
![]() |
039fbb8b1b | ||
![]() |
80c1e67146 | ||
![]() |
2591cabdd0 | ||
![]() |
45620d6bf4 | ||
![]() |
89a59cdd35 | ||
![]() |
17b819a423 | ||
![]() |
afb4e3c1c6 | ||
![]() |
e5f8fe1bf8 | ||
![]() |
30de5c6fa9 | ||
![]() |
79a29cf7d5 | ||
![]() |
98a1323bf5 | ||
![]() |
0fe6585897 | ||
![]() |
33dd605c57 | ||
![]() |
bab0c227f8 | ||
![]() |
5ad2be4a8a | ||
![]() |
39e18678f5 | ||
![]() |
f54b13a9f5 | ||
![]() |
703adc090a | ||
![]() |
81f0047dab | ||
![]() |
ef70add27b | ||
![]() |
d45932cc04 | ||
![]() |
081a97a5fc | ||
![]() |
2b3cedb441 | ||
![]() |
76426117ff | ||
![]() |
e8720780eb | ||
![]() |
e5ed642121 | ||
![]() |
a6602292b7 | ||
![]() |
859d8e08a0 | ||
![]() |
8128adfd7b | ||
![]() |
f9317dd17f | ||
![]() |
32a169014a | ||
![]() |
37d3fd99eb | ||
![]() |
e0973a8231 | ||
![]() |
876ce0d0a5 | ||
![]() |
9f614ce7e0 | ||
![]() |
63de997e60 | ||
![]() |
d2aa49fd1c | ||
![]() |
d355c16bdf | ||
![]() |
8c7f5d1ea8 | ||
![]() |
08635de08e | ||
![]() |
4dc8676848 | ||
![]() |
164a230cbc | ||
![]() |
f3dc551284 | ||
![]() |
ec674e85d6 | ||
![]() |
32f47900cd | ||
![]() |
320774c8d8 | ||
![]() |
1594f44a72 | ||
![]() |
726d3a8816 | ||
![]() |
097f13db42 | ||
![]() |
15585c2007 | ||
![]() |
ca0a2c3215 | ||
![]() |
d2820bdf2a | ||
![]() |
947e5eb9a3 | ||
![]() |
5901e34f6d | ||
![]() |
1c942d2f9d | ||
![]() |
b419bf8f35 | ||
![]() |
7edfa1284a | ||
![]() |
32e4006b2c | ||
![]() |
e773a1dbfc | ||
![]() |
3942214ba1 | ||
![]() |
461fc36401 | ||
![]() |
fa9198174e | ||
![]() |
96460d92d3 | ||
![]() |
e0d376c15b | ||
![]() |
46e0301dd0 | ||
![]() |
14aa26c651 | ||
![]() |
ab59dfaa86 | ||
![]() |
4841f46d95 | ||
![]() |
faccc6a6f2 | ||
![]() |
f336e76a86 | ||
![]() |
811604d15a | ||
![]() |
dd005941e9 | ||
![]() |
311381332f | ||
![]() |
50173c4127 | ||
![]() |
e030adbb72 | ||
![]() |
3762ccf639 | ||
![]() |
d2758400e0 | ||
![]() |
1551ec440e | ||
![]() |
bd493e39e4 | ||
![]() |
2fb663b63f | ||
![]() |
3878f35107 | ||
![]() |
e055c0edbf | ||
![]() |
a02afc4c68 | ||
![]() |
30130df6fb | ||
![]() |
400cd2e22c | ||
![]() |
dc25a0e87f | ||
![]() |
d21abd97a7 | ||
![]() |
bd38eaf924 | ||
![]() |
360e98b3ee | ||
![]() |
e03908da83 | ||
![]() |
64e7afea7c | ||
![]() |
13077f7ce8 | ||
![]() |
ef68f4e7f6 | ||
![]() |
eaeef23bc6 | ||
![]() |
5a3b12a257 | ||
![]() |
794b74a90c | ||
![]() |
ac4a500fff | ||
![]() |
13a5939550 | ||
![]() |
e560f0f08b | ||
![]() |
3894720a93 | ||
![]() |
2a89b95510 | ||
![]() |
e8e4bf07df | ||
![]() |
4d3b98f78b | ||
![]() |
42ee73b046 | ||
![]() |
0a5df21c1b | ||
![]() |
b0ea0c262b | ||
![]() |
ffa9c45692 | ||
![]() |
15f6f16d22 | ||
![]() |
c3240b6767 | ||
![]() |
2f7fe905b8 | ||
![]() |
0de1d42d73 | ||
![]() |
c4c8931c73 | ||
![]() |
1c9eb654ce | ||
![]() |
ee5b55cfbb | ||
![]() |
7e121526ca | ||
![]() |
79dfec1feb | ||
![]() |
d30a278b97 | ||
![]() |
5ae9e80987 | ||
![]() |
245fb1b3ab | ||
![]() |
bedf8a48a2 | ||
![]() |
3c77c9e2d8 | ||
![]() |
92ba2c3111 | ||
![]() |
3ea754bba2 | ||
![]() |
a3429fb687 | ||
![]() |
476a107c46 | ||
![]() |
f98d28bee2 | ||
![]() |
df5ee8732a | ||
![]() |
bd500f5a90 | ||
![]() |
536518f4bb | ||
![]() |
7e11936fee | ||
![]() |
b0da0508e1 | ||
![]() |
48cc5a27f6 | ||
![]() |
d470513f84 | ||
![]() |
baaeb4317f | ||
![]() |
40156c5e74 | ||
![]() |
da0a47b487 | ||
![]() |
ee12b0b372 | ||
![]() |
31f7ec3a17 | ||
![]() |
3ec7617b14 | ||
![]() |
69e9eebd8e | ||
![]() |
268af00ce8 | ||
![]() |
ad748e4ed5 | ||
![]() |
f933736f1f | ||
![]() |
89861de544 | ||
![]() |
d503fff76b | ||
![]() |
e8dc9e3b20 | ||
![]() |
e40cdfca95 | ||
![]() |
e357fca9c0 | ||
![]() |
6b4f022157 | ||
![]() |
29c276d061 | ||
![]() |
b28eec1071 | ||
![]() |
c3c774579d | ||
![]() |
193977d6d2 | ||
![]() |
931bb10edb | ||
![]() |
408b9c151c | ||
![]() |
bd96a63087 | ||
![]() |
dd5eb3b96f | ||
![]() |
cd46428b81 | ||
![]() |
29926f057e | ||
![]() |
5444896b76 | ||
![]() |
4adb7295bd | ||
![]() |
2edd127d68 | ||
![]() |
eb45ed8b9a | ||
![]() |
3a81c76741 | ||
![]() |
14e2a6236a | ||
![]() |
1ea065dec6 | ||
![]() |
db42bbef62 | ||
![]() |
bd0b610a90 | ||
![]() |
f46213ce6e | ||
![]() |
0909e1d5fe | ||
![]() |
95c15b2edb | ||
![]() |
e48115336d | ||
![]() |
daa957fa7b | ||
![]() |
3d5a997633 | ||
![]() |
ca15d0316d | ||
![]() |
96a9c28e44 | ||
![]() |
006f8b1900 | ||
![]() |
d576b3ede8 | ||
![]() |
195664ee09 | ||
![]() |
bf85daf354 | ||
![]() |
d3192734a1 | ||
![]() |
16f5ecdd0b | ||
![]() |
b17fa8e9ae | ||
![]() |
88bcd797e9 | ||
![]() |
577d605b87 | ||
![]() |
a5e29a7f14 | ||
![]() |
7f27526c7c | ||
![]() |
f3adb4e876 | ||
![]() |
c571a65a32 | ||
![]() |
55f38231a9 | ||
![]() |
6a2e37d147 | ||
![]() |
f6ec9634ce | ||
![]() |
eedb030619 | ||
![]() |
c828c2cace | ||
![]() |
b4188893a0 | ||
![]() |
8ea26ec148 | ||
![]() |
bf73e430f8 | ||
![]() |
343f6a572d | ||
![]() |
66134e95ce | ||
![]() |
1ffb03ccd5 | ||
![]() |
998bf76e72 | ||
![]() |
47553bc020 | ||
![]() |
c84b2410b9 | ||
![]() |
fbbac73e94 | ||
![]() |
c2f0f9b9a3 | ||
![]() |
4d02ae092b | ||
![]() |
7db2849404 | ||
![]() |
d5a3b55e98 | ||
![]() |
79206fbfee | ||
![]() |
2089e29664 | ||
![]() |
38c5425d0c | ||
![]() |
b0d45e80de | ||
![]() |
2ac5beee0c | ||
![]() |
c99c73fa96 | ||
![]() |
f796228d06 | ||
![]() |
ca3836b3c7 | ||
![]() |
918f8ec989 | ||
![]() |
f44d9682ee | ||
![]() |
ce6800164d | ||
![]() |
4afb2de178 | ||
![]() |
9865fd3663 | ||
![]() |
5501527974 | ||
![]() |
3e4826bafc | ||
![]() |
d914f9f5be | ||
![]() |
ab8c90dd41 | ||
![]() |
8882841a8d | ||
![]() |
3d357950f9 | ||
![]() |
ef5d2651c0 | ||
![]() |
1dd4bb9b7a | ||
![]() |
682e6f6b03 | ||
![]() |
b468c28ae8 | ||
![]() |
9fc9452a08 | ||
![]() |
3b176b7152 | ||
![]() |
f1d1a9b66b | ||
![]() |
c0d2f491a8 | ||
![]() |
aeecce2ea8 | ||
![]() |
647ee149ff | ||
![]() |
22313e148a | ||
![]() |
31f2d7ce2e | ||
![]() |
4a0efec6c2 | ||
![]() |
5f555df5c1 | ||
![]() |
53a611a1b9 | ||
![]() |
14511b23af | ||
![]() |
fa2371274d | ||
![]() |
0aea8dfa0f | ||
![]() |
81ca18098d | ||
![]() |
b6e44a022f | ||
![]() |
1a8eee2477 | ||
![]() |
d0ef92da03 | ||
![]() |
9c98cb3b3b | ||
![]() |
3681542056 | ||
![]() |
7c0bf135d7 | ||
![]() |
9a31dd046c | ||
![]() |
74401a1f9c | ||
![]() |
cedc6ea3e9 |
@@ -813,6 +813,13 @@ gd::String PlatformExtension::GetObjectFullType(const gd::String& extensionName,
|
||||
return extensionName + separator + objectName;
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetVariantFullType(const gd::String& extensionName,
|
||||
const gd::String& objectName,
|
||||
const gd::String& variantName) {
|
||||
const auto& separator = GetNamespaceSeparator();
|
||||
return extensionName + separator + objectName + separator + variantName;
|
||||
}
|
||||
|
||||
gd::String PlatformExtension::GetExtensionFromFullObjectType(
|
||||
const gd::String& type) {
|
||||
const auto separatorIndex =
|
||||
|
@@ -663,6 +663,10 @@ class GD_CORE_API PlatformExtension {
|
||||
static gd::String GetObjectFullType(const gd::String& extensionName,
|
||||
const gd::String& objectName);
|
||||
|
||||
static gd::String GetVariantFullType(const gd::String& extensionName,
|
||||
const gd::String& objectName,
|
||||
const gd::String& variantName);
|
||||
|
||||
static gd::String GetExtensionFromFullObjectType(const gd::String& type);
|
||||
|
||||
static gd::String GetObjectNameFromFullObjectType(const gd::String& type);
|
||||
|
23
Core/GDCore/IDE/Events/UsedObjectTypeFinder.cpp
Normal file
23
Core/GDCore/IDE/Events/UsedObjectTypeFinder.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "UsedObjectTypeFinder.h"
|
||||
|
||||
#include "GDCore/Events/Instruction.h"
|
||||
#include "GDCore/IDE/ProjectBrowserHelper.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
bool UsedObjectTypeFinder::ScanProject(gd::Project &project,
|
||||
const gd::String &objectType) {
|
||||
UsedObjectTypeFinder worker(project, objectType);
|
||||
gd::ProjectBrowserHelper::ExposeProjectObjects(project, worker);
|
||||
return worker.hasFoundObjectType;
|
||||
};
|
||||
|
||||
void UsedObjectTypeFinder::DoVisitObject(gd::Object &object) {
|
||||
if (!hasFoundObjectType && object.GetType() == objectType) {
|
||||
hasFoundObjectType = true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gd
|
39
Core/GDCore/IDE/Events/UsedObjectTypeFinder.h
Normal file
39
Core/GDCore/IDE/Events/UsedObjectTypeFinder.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <set>
|
||||
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/SourceFileMetadata.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
class Project;
|
||||
class Object;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
class GD_CORE_API UsedObjectTypeFinder : public ArbitraryObjectsWorker {
|
||||
public:
|
||||
static bool ScanProject(gd::Project &project, const gd::String &objectType);
|
||||
|
||||
private:
|
||||
UsedObjectTypeFinder(gd::Project &project_, const gd::String &objectType_)
|
||||
: project(project_), objectType(objectType_){};
|
||||
gd::Project &project;
|
||||
const gd::String &objectType;
|
||||
bool hasFoundObjectType = false;
|
||||
|
||||
// Object Visitor
|
||||
void DoVisitObject(gd::Object &object) override;
|
||||
};
|
||||
|
||||
}; // namespace gd
|
@@ -7,7 +7,6 @@
|
||||
#include <map>
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ResourcesAbsolutePathChecker.h"
|
||||
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
@@ -26,42 +25,37 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
bool preserveAbsoluteFilenames,
|
||||
bool preserveDirectoryStructure) {
|
||||
if (updateOriginalProject) {
|
||||
gd::ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
originalProject, originalProject, fs, destinationDirectory,
|
||||
preserveAbsoluteFilenames, preserveDirectoryStructure);
|
||||
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
|
||||
originalProject, fs, destinationDirectory, preserveAbsoluteFilenames,
|
||||
preserveDirectoryStructure);
|
||||
} else {
|
||||
gd::Project clonedProject = originalProject;
|
||||
gd::ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
originalProject, clonedProject, fs, destinationDirectory,
|
||||
preserveAbsoluteFilenames, preserveDirectoryStructure);
|
||||
gd::ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
|
||||
clonedProject, fs, destinationDirectory, preserveAbsoluteFilenames,
|
||||
preserveDirectoryStructure);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProjectResourcesCopier::CopyAllResourcesTo(
|
||||
gd::Project& originalProject,
|
||||
gd::Project& clonedProject,
|
||||
bool ProjectResourcesCopier::AdaptFilePathsAndCopyAllResourcesTo(
|
||||
gd::Project& project,
|
||||
AbstractFileSystem& fs,
|
||||
gd::String destinationDirectory,
|
||||
bool preserveAbsoluteFilenames,
|
||||
bool preserveDirectoryStructure) {
|
||||
|
||||
// Check if there are some resources with absolute filenames
|
||||
gd::ResourcesAbsolutePathChecker absolutePathChecker(originalProject.GetResourcesManager(), fs);
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(originalProject, absolutePathChecker);
|
||||
|
||||
auto projectDirectory = fs.DirNameFrom(originalProject.GetProjectFile());
|
||||
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
|
||||
std::cout << "Copying all resources from " << projectDirectory << " to "
|
||||
<< destinationDirectory << "..." << std::endl;
|
||||
|
||||
// Get the resources to be copied
|
||||
gd::ResourcesMergingHelper resourcesMergingHelper(
|
||||
clonedProject.GetResourcesManager(), fs);
|
||||
project.GetResourcesManager(), fs);
|
||||
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
|
||||
resourcesMergingHelper.PreserveDirectoriesStructure(
|
||||
preserveDirectoryStructure);
|
||||
resourcesMergingHelper.PreserveAbsoluteFilenames(preserveAbsoluteFilenames);
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(project,
|
||||
resourcesMergingHelper);
|
||||
|
||||
// Copy resources
|
||||
|
@@ -50,12 +50,10 @@ class GD_CORE_API ProjectResourcesCopier {
|
||||
bool preserveDirectoryStructure = true);
|
||||
|
||||
private:
|
||||
static bool CopyAllResourcesTo(gd::Project& originalProject,
|
||||
gd::Project& clonedProject,
|
||||
gd::AbstractFileSystem& fs,
|
||||
gd::String destinationDirectory,
|
||||
bool preserveAbsoluteFilenames = true,
|
||||
bool preserveDirectoryStructure = true);
|
||||
static bool AdaptFilePathsAndCopyAllResourcesTo(
|
||||
gd::Project &project, gd::AbstractFileSystem &fs,
|
||||
gd::String destinationDirectory, bool preserveAbsoluteFilenames = true,
|
||||
bool preserveDirectoryStructure = true);
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "ResourcesAbsolutePathChecker.h"
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
void ResourcesAbsolutePathChecker::ExposeFile(gd::String& resourceFilename) {
|
||||
if (fs.IsAbsolute(resourceFilename)) hasAbsoluteFilenames = true;
|
||||
}
|
||||
|
||||
} // namespace gd
|
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/IDE/AbstractFileSystem.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief Helper used to check if a project has at least a resource with an
|
||||
* absolute filename.
|
||||
*
|
||||
* \see ArbitraryResourceWorker
|
||||
*
|
||||
* \ingroup IDE
|
||||
*/
|
||||
class GD_CORE_API ResourcesAbsolutePathChecker
|
||||
: public ArbitraryResourceWorker {
|
||||
public:
|
||||
ResourcesAbsolutePathChecker(gd::ResourcesManager &resourcesManager,
|
||||
AbstractFileSystem &fileSystem)
|
||||
: ArbitraryResourceWorker(resourcesManager), hasAbsoluteFilenames(false),
|
||||
fs(fileSystem){};
|
||||
virtual ~ResourcesAbsolutePathChecker(){};
|
||||
|
||||
/**
|
||||
* Return true if there is at least a resource with an absolute filename.
|
||||
*/
|
||||
bool HasResourceWithAbsoluteFilenames() const {
|
||||
return hasAbsoluteFilenames;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if there is a resource with an absolute path
|
||||
*/
|
||||
virtual void ExposeFile(gd::String& resource);
|
||||
|
||||
private:
|
||||
bool hasAbsoluteFilenames;
|
||||
AbstractFileSystem& fs;
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -22,6 +22,14 @@ void ResourcesMergingHelper::ExposeFile(gd::String& resourceFilename) {
|
||||
resourceFullFilename = gd::AbstractFileSystem::NormalizeSeparator(
|
||||
resourceFullFilename); // Protect against \ on Linux.
|
||||
|
||||
if (shouldUseOriginalAbsoluteFilenames) {
|
||||
// There is no need to fill `newFilenames` and `oldFilenames` since the file
|
||||
// location stays the same.
|
||||
fs.MakeAbsolute(resourceFullFilename, baseDirectory);
|
||||
resourceFilename = resourceFullFilename;
|
||||
return;
|
||||
}
|
||||
|
||||
// In the case of absolute filenames that we don't want to preserve, or
|
||||
// in the case of copying files without preserving relative folders, the new
|
||||
// names will be generated from the filename alone (with collision protection).
|
||||
|
@@ -3,8 +3,7 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef RESOURCESMERGINGHELPER_H
|
||||
#define RESOURCESMERGINGHELPER_H
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -58,6 +57,15 @@ public:
|
||||
preserveAbsoluteFilenames = preserveAbsoluteFilenames_;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Set if the absolute filenames of original files must be used for
|
||||
* any resource.
|
||||
*/
|
||||
void SetShouldUseOriginalAbsoluteFilenames(
|
||||
bool shouldUseOriginalAbsoluteFilenames_ = true) {
|
||||
shouldUseOriginalAbsoluteFilenames = shouldUseOriginalAbsoluteFilenames_;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Return a map containing the resources old absolute filename as key,
|
||||
* and the resources new filenames as value. The new filenames are relative to
|
||||
@@ -93,10 +101,13 @@ public:
|
||||
///< absolute (C:\MyFile.png will not be
|
||||
///< transformed into a relative filename
|
||||
///< (MyFile.png).
|
||||
/**
|
||||
* Set to true if the absolute filenames of original files must be used for
|
||||
* any resource.
|
||||
*/
|
||||
bool shouldUseOriginalAbsoluteFilenames = false;
|
||||
gd::AbstractFileSystem&
|
||||
fs; ///< The gd::AbstractFileSystem used to manipulate files.
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif // RESOURCESMERGINGHELPER_H
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include "SceneResourcesFinder.h"
|
||||
|
||||
#include "GDCore/IDE/ResourceExposer.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
@@ -27,6 +28,14 @@ std::set<gd::String> SceneResourcesFinder::FindSceneResources(gd::Project &proje
|
||||
return resourceWorker.resourceNames;
|
||||
}
|
||||
|
||||
std::set<gd::String> SceneResourcesFinder::FindEventsBasedObjectVariantResources(gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &variant) {
|
||||
gd::SceneResourcesFinder resourceWorker(project.GetResourcesManager());
|
||||
|
||||
gd::ResourceExposer::ExposeEventsBasedObjectVariantResources(project, variant, resourceWorker);
|
||||
return resourceWorker.resourceNames;
|
||||
}
|
||||
|
||||
void SceneResourcesFinder::AddUsedResource(gd::String &resourceName) {
|
||||
if (resourceName.empty()) {
|
||||
return;
|
||||
|
@@ -15,6 +15,7 @@ namespace gd {
|
||||
class Project;
|
||||
class Layout;
|
||||
class SerializerElement;
|
||||
class EventsBasedObjectVariant;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
@@ -27,7 +28,7 @@ namespace gd {
|
||||
class SceneResourcesFinder : private gd::ArbitraryResourceWorker {
|
||||
public:
|
||||
/**
|
||||
* @brief Find resource usages in a given scenes.
|
||||
* @brief Find resource usages in a given scene.
|
||||
*
|
||||
* It doesn't include resources used globally.
|
||||
*/
|
||||
@@ -41,6 +42,13 @@ public:
|
||||
*/
|
||||
static std::set<gd::String> FindProjectResources(gd::Project &project);
|
||||
|
||||
/**
|
||||
* @brief Find resource usages in a given events-based object variant.
|
||||
*/
|
||||
static std::set<gd::String>
|
||||
FindEventsBasedObjectVariantResources(gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &variant);
|
||||
|
||||
virtual ~SceneResourcesFinder(){};
|
||||
|
||||
private:
|
||||
|
@@ -332,6 +332,12 @@ void ProjectBrowserHelper::ExposeLayoutObjects(gd::Layout &layout,
|
||||
worker.Launch(layout.GetObjects());
|
||||
}
|
||||
|
||||
void ProjectBrowserHelper::ExposeEventsBasedObjectVariantObjects(
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryObjectsWorker &worker) {
|
||||
worker.Launch(eventsBasedObjectVariant.GetObjects());
|
||||
}
|
||||
|
||||
void ProjectBrowserHelper::ExposeProjectFunctions(
|
||||
gd::Project &project, gd::ArbitraryEventsFunctionsWorker &worker) {
|
||||
|
||||
|
@@ -13,6 +13,7 @@ class EventsFunctionsExtension;
|
||||
class EventsFunction;
|
||||
class EventsBasedBehavior;
|
||||
class EventsBasedObject;
|
||||
class EventsBasedObjectVariant;
|
||||
class ArbitraryEventsWorker;
|
||||
class ArbitraryEventsWorkerWithContext;
|
||||
class ArbitraryEventsFunctionsWorker;
|
||||
@@ -207,6 +208,17 @@ public:
|
||||
static void ExposeLayoutObjects(gd::Layout &layout,
|
||||
gd::ArbitraryObjectsWorker &worker);
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all ObjectContainers of the
|
||||
* events-based object variant.
|
||||
*
|
||||
* This should be the preferred way to traverse all the objects of an
|
||||
* events-based object variant.
|
||||
*/
|
||||
static void ExposeEventsBasedObjectVariantObjects(
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryObjectsWorker &worker);
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all FunctionsContainers of the project
|
||||
* (global, layouts...)
|
||||
|
@@ -116,6 +116,34 @@ void ResourceExposer::ExposeLayoutResources(
|
||||
project, layout, eventWorker);
|
||||
}
|
||||
|
||||
void ResourceExposer::ExposeEventsBasedObjectVariantResources(
|
||||
gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryResourceWorker &worker) {
|
||||
// Expose object configuration resources
|
||||
auto objectWorker = gd::GetResourceWorkerOnObjects(project, worker);
|
||||
gd::ProjectBrowserHelper::ExposeEventsBasedObjectVariantObjects(
|
||||
eventsBasedObjectVariant, objectWorker);
|
||||
|
||||
// Expose layer effect resources
|
||||
auto &layers = eventsBasedObjectVariant.GetLayers();
|
||||
for (std::size_t layerIndex = 0; layerIndex < layers.GetLayersCount();
|
||||
layerIndex++) {
|
||||
auto &layer = layers.GetLayer(layerIndex);
|
||||
|
||||
auto &effects = layer.GetEffects();
|
||||
for (size_t effectIndex = 0; effectIndex < effects.GetEffectsCount();
|
||||
effectIndex++) {
|
||||
auto &effect = effects.GetEffect(effectIndex);
|
||||
gd::ResourceExposer::ExposeEffectResources(project.GetCurrentPlatform(),
|
||||
effect, worker);
|
||||
}
|
||||
}
|
||||
// We don't check the events because it would cost too much to do it for every
|
||||
// variant. Resource usage in events-based object events and their
|
||||
// dependencies should be rare.
|
||||
}
|
||||
|
||||
void ResourceExposer::ExposeEffectResources(
|
||||
gd::Platform &platform,
|
||||
gd::Effect &effect,
|
||||
|
@@ -9,10 +9,11 @@ namespace gd {
|
||||
class Platform;
|
||||
class Project;
|
||||
class ArbitraryResourceWorker;
|
||||
class EventsBasedObjectVariant;
|
||||
class EventsFunctionsExtension;
|
||||
class Effect;
|
||||
class Layout;
|
||||
} // namespace gd
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
@@ -20,7 +21,7 @@ namespace gd {
|
||||
* \brief
|
||||
*/
|
||||
class GD_CORE_API ResourceExposer {
|
||||
public:
|
||||
public:
|
||||
/**
|
||||
* \brief Called ( e.g. during compilation ) so as to inventory internal
|
||||
* resources, sometimes update their filename or any other work or resources.
|
||||
@@ -50,6 +51,14 @@ class GD_CORE_API ResourceExposer {
|
||||
gd::Layout &layout,
|
||||
gd::ArbitraryResourceWorker &worker);
|
||||
|
||||
/**
|
||||
* @brief Expose the resources used in a given events-based object variant.
|
||||
*/
|
||||
static void ExposeEventsBasedObjectVariantResources(
|
||||
gd::Project &project,
|
||||
gd::EventsBasedObjectVariant &eventsBasedObjectVariant,
|
||||
gd::ArbitraryResourceWorker &worker);
|
||||
|
||||
/**
|
||||
* @brief Expose the resources used in a given effect.
|
||||
*/
|
||||
|
@@ -60,6 +60,18 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
} else {
|
||||
SetHasCustomDepth(false);
|
||||
}
|
||||
if (element.HasChild("defaultWidth") ||
|
||||
element.HasAttribute("defaultWidth")) {
|
||||
defaultWidth = element.GetDoubleAttribute("defaultWidth");
|
||||
}
|
||||
if (element.HasChild("defaultHeight") ||
|
||||
element.HasAttribute("defaultHeight")) {
|
||||
defaultHeight = element.GetDoubleAttribute("defaultHeight");
|
||||
}
|
||||
if (element.HasChild("defaultDepth") ||
|
||||
element.HasAttribute("defaultDepth")) {
|
||||
defaultDepth = element.GetDoubleAttribute("defaultDepth");
|
||||
}
|
||||
SetZOrder(element.GetIntAttribute("zOrder", 0, "plan"));
|
||||
SetOpacity(element.GetIntAttribute("opacity", 255));
|
||||
SetLayer(element.GetStringAttribute("layer"));
|
||||
@@ -74,45 +86,51 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
if (persistentUuid.empty()) ResetPersistentUuid();
|
||||
|
||||
numberProperties.clear();
|
||||
const SerializerElement& numberPropertiesElement =
|
||||
element.GetChild("numberProperties", 0, "floatInfos");
|
||||
numberPropertiesElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < numberPropertiesElement.GetChildrenCount(); ++j) {
|
||||
gd::String name =
|
||||
numberPropertiesElement.GetChild(j).GetStringAttribute("name");
|
||||
double value =
|
||||
numberPropertiesElement.GetChild(j).GetDoubleAttribute("value");
|
||||
if (element.HasChild("numberProperties", "floatInfos")) {
|
||||
const SerializerElement& numberPropertiesElement =
|
||||
element.GetChild("numberProperties", 0, "floatInfos");
|
||||
numberPropertiesElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < numberPropertiesElement.GetChildrenCount(); ++j) {
|
||||
gd::String name =
|
||||
numberPropertiesElement.GetChild(j).GetStringAttribute("name");
|
||||
double value =
|
||||
numberPropertiesElement.GetChild(j).GetDoubleAttribute("value");
|
||||
|
||||
// Compatibility with GD <= 5.1.164
|
||||
if (name == "z") {
|
||||
SetZ(value);
|
||||
} else if (name == "rotationX") {
|
||||
SetRotationX(value);
|
||||
} else if (name == "rotationY") {
|
||||
SetRotationY(value);
|
||||
} else if (name == "depth") {
|
||||
SetHasCustomDepth(true);
|
||||
SetCustomDepth(value);
|
||||
}
|
||||
// end of compatibility code
|
||||
else {
|
||||
numberProperties[name] = value;
|
||||
// Compatibility with GD <= 5.1.164
|
||||
if (name == "z") {
|
||||
SetZ(value);
|
||||
} else if (name == "rotationX") {
|
||||
SetRotationX(value);
|
||||
} else if (name == "rotationY") {
|
||||
SetRotationY(value);
|
||||
} else if (name == "depth") {
|
||||
SetHasCustomDepth(true);
|
||||
SetCustomDepth(value);
|
||||
}
|
||||
// end of compatibility code
|
||||
else {
|
||||
numberProperties[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stringProperties.clear();
|
||||
const SerializerElement& stringPropElement =
|
||||
element.GetChild("stringProperties", 0, "stringInfos");
|
||||
stringPropElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < stringPropElement.GetChildrenCount(); ++j) {
|
||||
gd::String name = stringPropElement.GetChild(j).GetStringAttribute("name");
|
||||
gd::String value =
|
||||
stringPropElement.GetChild(j).GetStringAttribute("value");
|
||||
stringProperties[name] = value;
|
||||
if (element.HasChild("stringProperties", "stringInfos")) {
|
||||
const SerializerElement& stringPropElement =
|
||||
element.GetChild("stringProperties", 0, "stringInfos");
|
||||
stringPropElement.ConsiderAsArrayOf("property", "Info");
|
||||
for (std::size_t j = 0; j < stringPropElement.GetChildrenCount(); ++j) {
|
||||
gd::String name = stringPropElement.GetChild(j).GetStringAttribute("name");
|
||||
gd::String value =
|
||||
stringPropElement.GetChild(j).GetStringAttribute("value");
|
||||
stringProperties[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
GetVariables().UnserializeFrom(
|
||||
element.GetChild("initialVariables", 0, "InitialVariables"));
|
||||
if (element.HasChild("initialVariables", "InitialVariables")) {
|
||||
GetVariables().UnserializeFrom(
|
||||
element.GetChild("initialVariables", 0, "InitialVariables"));
|
||||
}
|
||||
}
|
||||
|
||||
void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
@@ -133,6 +151,8 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("width", GetCustomWidth());
|
||||
element.SetAttribute("height", GetCustomHeight());
|
||||
if (HasCustomDepth()) element.SetAttribute("depth", GetCustomDepth());
|
||||
// defaultWidth, defaultHeight and defaultDepth are not serialized
|
||||
// because they are evaluated by InGameEditor.
|
||||
if (IsLocked()) element.SetAttribute("locked", IsLocked());
|
||||
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
|
||||
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());
|
||||
|
@@ -219,6 +219,13 @@ class GD_CORE_API InitialInstance {
|
||||
double GetCustomDepth() const { return depth; }
|
||||
void SetCustomDepth(double depth_) { depth = depth_; }
|
||||
|
||||
double GetDefaultWidth() const { return defaultWidth; }
|
||||
double GetDefaultHeight() const { return defaultHeight; }
|
||||
double GetDefaultDepth() const { return defaultDepth; }
|
||||
void SetDefaultWidth(double width_) { defaultWidth = width_; }
|
||||
void SetDefaultHeight(double height_) { defaultHeight = height_; }
|
||||
void SetDefaultDepth(double depth_) { defaultDepth = depth_; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the instance is locked and cannot be moved in the
|
||||
* IDE.
|
||||
@@ -366,7 +373,11 @@ class GD_CORE_API InitialInstance {
|
||||
*/
|
||||
InitialInstance& ResetPersistentUuid();
|
||||
|
||||
const gd::String& GetPersistentUuid() const { return persistentUuid; }
|
||||
/**
|
||||
* \brief Reset the persistent UUID used to recognize
|
||||
* the same initial instance between serialization.
|
||||
*/
|
||||
const gd::String& GetPersistentUuid() const { return persistentUuid; }
|
||||
///@}
|
||||
|
||||
private:
|
||||
@@ -395,6 +406,9 @@ class GD_CORE_API InitialInstance {
|
||||
double width; ///< Instance custom width
|
||||
double height; ///< Instance custom height
|
||||
double depth; ///< Instance custom depth
|
||||
double defaultWidth = 0; ///< Instance default width as reported by InGameEditor
|
||||
double defaultHeight = 0; ///< Instance default height as reported by InGameEditor
|
||||
double defaultDepth = 0; ///< Instance default depth as reported by InGameEditor
|
||||
gd::VariablesContainer initialVariables; ///< Instance specific variables
|
||||
bool locked; ///< True if the instance is locked
|
||||
bool sealed; ///< True if the instance is sealed
|
||||
|
@@ -23,6 +23,7 @@ Layer::Layer()
|
||||
camera3DNearPlaneDistance(3),
|
||||
camera3DFarPlaneDistance(10000),
|
||||
camera3DFieldOfView(45),
|
||||
camera2DPlaneMaxDrawingDistance(5000),
|
||||
ambientLightColorR(200),
|
||||
ambientLightColorG(200),
|
||||
ambientLightColorB(200) {}
|
||||
@@ -56,6 +57,8 @@ void Layer::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("camera3DFarPlaneDistance",
|
||||
GetCamera3DFarPlaneDistance());
|
||||
element.SetAttribute("camera3DFieldOfView", GetCamera3DFieldOfView());
|
||||
element.SetAttribute("camera2DPlaneMaxDrawingDistance",
|
||||
GetCamera2DPlaneMaxDrawingDistance());
|
||||
|
||||
SerializerElement& camerasElement = element.AddChild("cameras");
|
||||
camerasElement.ConsiderAsArrayOf("camera");
|
||||
@@ -99,6 +102,8 @@ void Layer::UnserializeFrom(const SerializerElement& element) {
|
||||
"camera3DFarPlaneDistance", 10000, "threeDFarPlaneDistance"));
|
||||
SetCamera3DFieldOfView(element.GetDoubleAttribute(
|
||||
"camera3DFieldOfView", 45, "threeDFieldOfView"));
|
||||
SetCamera2DPlaneMaxDrawingDistance(element.GetDoubleAttribute(
|
||||
"camera2DPlaneMaxDrawingDistance", 5000));
|
||||
|
||||
cameras.clear();
|
||||
SerializerElement& camerasElement = element.GetChild("cameras");
|
||||
|
@@ -182,6 +182,8 @@ class GD_CORE_API Layer {
|
||||
}
|
||||
double GetCamera3DFieldOfView() const { return camera3DFieldOfView; }
|
||||
void SetCamera3DFieldOfView(double angle) { camera3DFieldOfView = angle; }
|
||||
double GetCamera2DPlaneMaxDrawingDistance() const { return camera2DPlaneMaxDrawingDistance; }
|
||||
void SetCamera2DPlaneMaxDrawingDistance(double distance) { camera2DPlaneMaxDrawingDistance = distance; }
|
||||
///@}
|
||||
|
||||
/** \name Cameras
|
||||
@@ -292,6 +294,7 @@ class GD_CORE_API Layer {
|
||||
double camera3DNearPlaneDistance; ///< 3D camera frustum near plan distance
|
||||
double camera3DFarPlaneDistance; ///< 3D camera frustum far plan distance
|
||||
double camera3DFieldOfView; ///< 3D camera field of view (fov) in degrees
|
||||
double camera2DPlaneMaxDrawingDistance; ///< Max drawing distance of the 2D plane when in the 3D world
|
||||
unsigned int ambientLightColorR; ///< Ambient light color Red component
|
||||
unsigned int ambientLightColorG; ///< Ambient light color Green component
|
||||
unsigned int ambientLightColorB; ///< Ambient light color Blue component
|
||||
|
@@ -730,6 +730,8 @@ void Project::UnserializeFrom(const SerializerElement& element) {
|
||||
SetPackageName(propElement.GetStringAttribute("packageName"));
|
||||
SetTemplateSlug(propElement.GetStringAttribute("templateSlug"));
|
||||
SetOrientation(propElement.GetStringAttribute("orientation", "default"));
|
||||
SetEffectsHiddenInEditor(
|
||||
propElement.GetBoolAttribute("areEffectsHiddenInEditor", false));
|
||||
SetFolderProject(propElement.GetBoolAttribute("folderProject"));
|
||||
SetLastCompilationDirectory(propElement
|
||||
.GetChild("latestCompilationDirectory",
|
||||
@@ -1109,6 +1111,10 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
propElement.SetAttribute("packageName", packageName);
|
||||
propElement.SetAttribute("templateSlug", templateSlug);
|
||||
propElement.SetAttribute("orientation", orientation);
|
||||
if (areEffectsHiddenInEditor) {
|
||||
propElement.SetBoolAttribute("areEffectsHiddenInEditor",
|
||||
areEffectsHiddenInEditor);
|
||||
}
|
||||
platformSpecificAssets.SerializeTo(
|
||||
propElement.AddChild("platformSpecificAssets"));
|
||||
loadingScreen.SerializeTo(propElement.AddChild("loadingScreen"));
|
||||
@@ -1150,6 +1156,8 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
// end of compatibility code
|
||||
|
||||
extensionProperties.SerializeTo(propElement.AddChild("extensionProperties"));
|
||||
|
||||
playableDevicesElement.AddChild("").SetStringValue("mobile");
|
||||
|
||||
SerializerElement& platformsElement = propElement.AddChild("platforms");
|
||||
platformsElement.ConsiderAsArrayOf("platform");
|
||||
@@ -1319,6 +1327,8 @@ void Project::Init(const gd::Project& game) {
|
||||
|
||||
sceneResourcesPreloading = game.sceneResourcesPreloading;
|
||||
sceneResourcesUnloading = game.sceneResourcesUnloading;
|
||||
|
||||
areEffectsHiddenInEditor = game.areEffectsHiddenInEditor;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -506,6 +506,20 @@ class GD_CORE_API Project {
|
||||
*/
|
||||
void SetCurrentPlatform(const gd::String& platformName);
|
||||
|
||||
/**
|
||||
* Check if the effects are shown.
|
||||
*/
|
||||
bool AreEffectsHiddenInEditor() const { return areEffectsHiddenInEditor; }
|
||||
|
||||
/**
|
||||
* Define the project as playable on a mobile.
|
||||
* \param enable True When false effects are not shown and a default light is
|
||||
* used for 3D layers.
|
||||
*/
|
||||
void SetEffectsHiddenInEditor(bool enable = true) {
|
||||
areEffectsHiddenInEditor = enable;
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Factory method
|
||||
@@ -1165,6 +1179,9 @@ class GD_CORE_API Project {
|
||||
mutable unsigned int gdBuildVersion =
|
||||
0; ///< The GD build version used the last
|
||||
///< time the project was saved.
|
||||
bool areEffectsHiddenInEditor =
|
||||
false; ///< When false effects are not shown and a default light is used
|
||||
///< for 3D layers.
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -70,7 +70,9 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
currentValue = element.GetChild("value").GetStringValue();
|
||||
type = element.GetChild("type").GetStringValue();
|
||||
if (type == "Number") {
|
||||
gd::String unitName = element.GetChild("unit").GetStringValue();
|
||||
gd::String unitName = element.HasChild("unit")
|
||||
? element.GetChild("unit").GetStringValue()
|
||||
: "";
|
||||
measurementUnit =
|
||||
gd::MeasurementUnit::HasDefaultMeasurementUnitNamed(unitName)
|
||||
? measurementUnit =
|
||||
|
@@ -159,15 +159,9 @@ namespace gdjs {
|
||||
if (initialInstanceData.depth !== undefined) {
|
||||
this.setDepth(initialInstanceData.depth);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
if (initialInstanceData.flippedZ) {
|
||||
this.flipZ(initialInstanceData.flippedZ);
|
||||
}
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
this.flipZ(!!initialInstanceData.flippedZ);
|
||||
}
|
||||
|
||||
setX(x: float): void {
|
||||
@@ -334,6 +328,18 @@ namespace gdjs {
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._originalWidth;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._originalHeight;
|
||||
}
|
||||
|
||||
getOriginalDepth(): float {
|
||||
return this._originalDepth;
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._width;
|
||||
}
|
||||
@@ -380,31 +386,6 @@ namespace gdjs {
|
||||
this.getRenderer().updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the object for a scale of 1.
|
||||
*
|
||||
* It can't be 0.
|
||||
*/
|
||||
_getOriginalWidth(): float {
|
||||
return this._originalWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the object for a scale of 1.
|
||||
*
|
||||
* It can't be 0.
|
||||
*/
|
||||
_getOriginalHeight(): float {
|
||||
return this._originalHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object size on the Z axis (called "depth") when the scale equals 1.
|
||||
*/
|
||||
_getOriginalDepth(): float {
|
||||
return this._originalDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width of the object for a scale of 1.
|
||||
*/
|
||||
|
@@ -11,6 +11,8 @@ namespace gdjs {
|
||||
this._object = runtimeObject;
|
||||
this._threeObject3D = threeObject3D;
|
||||
this._threeObject3D.rotation.order = 'ZYX';
|
||||
//@ts-ignore
|
||||
this._threeObject3D.gdjsRuntimeObject = runtimeObject;
|
||||
|
||||
instanceContainer
|
||||
.getLayer('')
|
||||
|
@@ -115,6 +115,12 @@ namespace gdjs {
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number;
|
||||
|
||||
/**
|
||||
* Return the depth of the object before any custom size is applied.
|
||||
* @return The depth of the object
|
||||
*/
|
||||
getOriginalDepth(): float;
|
||||
}
|
||||
|
||||
export interface Object3DDataContent {
|
||||
@@ -131,7 +137,11 @@ namespace gdjs {
|
||||
export namespace Base3DHandler {
|
||||
export const is3D = (
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
|
||||
): object is gdjs.RuntimeObject &
|
||||
gdjs.Base3DHandler &
|
||||
gdjs.Resizable &
|
||||
gdjs.Scalable &
|
||||
gdjs.Flippable => {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return object.getZ && object.setZ;
|
||||
};
|
||||
@@ -243,6 +253,10 @@ namespace gdjs {
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.object.getUnrotatedAABBMaxZ();
|
||||
}
|
||||
|
||||
getOriginalDepth(): float {
|
||||
return this.object.getOriginalDepth();
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);
|
||||
|
@@ -78,15 +78,9 @@ namespace gdjs {
|
||||
if (initialInstanceData.depth !== undefined) {
|
||||
this.setDepth(initialInstanceData.depth);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
if (initialInstanceData.flippedZ) {
|
||||
this.flipZ(initialInstanceData.flippedZ);
|
||||
}
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
this.flipZ(!!initialInstanceData.flippedZ);
|
||||
}
|
||||
|
||||
getNetworkSyncData(
|
||||
@@ -325,6 +319,10 @@ namespace gdjs {
|
||||
return this._maxZ - this._minZ;
|
||||
}
|
||||
|
||||
getOriginalDepth(): float {
|
||||
return this._instanceContainer._getInitialInnerAreaDepth();
|
||||
}
|
||||
|
||||
override _updateUntransformedHitBoxes(): void {
|
||||
super._updateUntransformedHitBoxes();
|
||||
|
||||
|
@@ -23,6 +23,8 @@ namespace gdjs {
|
||||
|
||||
this._threeGroup = new THREE.Group();
|
||||
this._threeGroup.rotation.order = 'ZYX';
|
||||
//@ts-ignore
|
||||
this._threeGroup.gdjsRuntimeObject = object;
|
||||
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
|
@@ -40,6 +40,19 @@ describe('gdjs.AnchorRuntimeBehavior', () => {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -221,9 +221,11 @@ namespace gdjs {
|
||||
this.setWrappingWidth(initialInstanceData.width);
|
||||
this.setWrapping(true);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
override onDestroyed(): void {
|
||||
|
@@ -29,6 +29,19 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -388,6 +388,9 @@ namespace gdjs {
|
||||
|
||||
return new gdjs.PromiseTask(
|
||||
(async () => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
const scoreSavingState = (_scoreSavingStateByLeaderboard[
|
||||
leaderboardId
|
||||
] =
|
||||
@@ -423,6 +426,9 @@ namespace gdjs {
|
||||
) =>
|
||||
new gdjs.PromiseTask(
|
||||
(async () => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
const playerId = gdjs.playerAuthentication.getUserId();
|
||||
const playerToken = gdjs.playerAuthentication.getUserToken();
|
||||
if (!playerId || !playerToken) {
|
||||
@@ -747,6 +753,9 @@ namespace gdjs {
|
||||
leaderboardId: string,
|
||||
displayLoader: boolean
|
||||
) {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
// First ensure we're not trying to display multiple times the same leaderboard (in which case
|
||||
// we "de-duplicate" the request to display it).
|
||||
if (leaderboardId === _requestedLeaderboardId) {
|
||||
|
@@ -35,6 +35,19 @@ describe('gdjs.LinksManager', function () {
|
||||
stopSoundsOnStartup: false,
|
||||
title: '',
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -1841,6 +1841,9 @@ namespace gdjs {
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (isQuickJoiningTooFast()) {
|
||||
return;
|
||||
}
|
||||
@@ -1860,6 +1863,9 @@ namespace gdjs {
|
||||
displayLoader: boolean,
|
||||
openLobbiesPageIfFailure: boolean
|
||||
) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (isQuickJoiningTooFast()) {
|
||||
return;
|
||||
}
|
||||
@@ -1893,6 +1899,9 @@ namespace gdjs {
|
||||
export const openLobbiesWindow = async (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isLobbiesWindowOpen(runtimeScene) ||
|
||||
gdjs.playerAuthentication.isAuthenticationWindowOpen()
|
||||
|
@@ -53,6 +53,8 @@ namespace gdjs {
|
||||
|
||||
_renderer: gdjs.PanelSpriteRuntimeObjectRenderer;
|
||||
|
||||
_objectData: PanelSpriteObjectData;
|
||||
|
||||
/**
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param panelSpriteObjectData The initial properties of the object
|
||||
@@ -62,6 +64,7 @@ namespace gdjs {
|
||||
panelSpriteObjectData: PanelSpriteObjectData
|
||||
) {
|
||||
super(instanceContainer, panelSpriteObjectData);
|
||||
this._objectData = panelSpriteObjectData;
|
||||
this._rBorder = panelSpriteObjectData.rightMargin;
|
||||
this._lBorder = panelSpriteObjectData.leftMargin;
|
||||
this._tBorder = panelSpriteObjectData.topMargin;
|
||||
@@ -84,6 +87,7 @@ namespace gdjs {
|
||||
oldObjectData: PanelSpriteObjectData,
|
||||
newObjectData: PanelSpriteObjectData
|
||||
): boolean {
|
||||
this._objectData = newObjectData;
|
||||
if (oldObjectData.width !== newObjectData.width) {
|
||||
this.setWidth(newObjectData.width);
|
||||
}
|
||||
@@ -166,9 +170,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,6 +253,14 @@ namespace gdjs {
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._objectData.width;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._objectData.height;
|
||||
}
|
||||
|
||||
setOpacity(opacity: float): void {
|
||||
if (opacity < 0) {
|
||||
opacity = 0;
|
||||
|
@@ -18,12 +18,15 @@ namespace gdjs {
|
||||
renderer: PIXI.Container;
|
||||
emitter: PIXI.particles.Emitter;
|
||||
started: boolean = false;
|
||||
helperGraphics: PIXI.Graphics | null = null;
|
||||
runtimeObject: gdjs.ParticleEmitterObject;
|
||||
|
||||
constructor(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer,
|
||||
runtimeObject: gdjs.RuntimeObject,
|
||||
runtimeObject: gdjs.ParticleEmitterObject,
|
||||
objectData: any
|
||||
) {
|
||||
this.runtimeObject = runtimeObject;
|
||||
const pixiRenderer = instanceContainer
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
@@ -223,6 +226,44 @@ namespace gdjs {
|
||||
if (!this.started && wasEmitting) {
|
||||
this.started = true;
|
||||
}
|
||||
if (this.helperGraphics) {
|
||||
this.helperGraphics.clear();
|
||||
this.helperGraphics.position.x = this.runtimeObject.getX();
|
||||
this.helperGraphics.position.y = this.runtimeObject.getY();
|
||||
|
||||
const emitterAngle = gdjs.toRad(this.runtimeObject.getAngle());
|
||||
const sprayConeAngle = gdjs.toRad(
|
||||
this.runtimeObject.getConeSprayAngle()
|
||||
);
|
||||
const line1Angle = emitterAngle - sprayConeAngle / 2;
|
||||
const line2Angle = emitterAngle + sprayConeAngle / 2;
|
||||
const length = 64;
|
||||
|
||||
this.helperGraphics.beginFill(0, 0);
|
||||
this.helperGraphics.lineStyle(
|
||||
3,
|
||||
this.runtimeObject.getParticleColorEnd(),
|
||||
1
|
||||
);
|
||||
this.helperGraphics.moveTo(0, 0);
|
||||
this.helperGraphics.lineTo(
|
||||
Math.cos(line1Angle) * length,
|
||||
Math.sin(line1Angle) * length
|
||||
);
|
||||
this.helperGraphics.moveTo(0, 0);
|
||||
this.helperGraphics.lineTo(
|
||||
Math.cos(line2Angle) * length,
|
||||
Math.sin(line2Angle) * length
|
||||
);
|
||||
this.helperGraphics.endFill();
|
||||
|
||||
this.helperGraphics.lineStyle(0, 0x000000, 1);
|
||||
this.helperGraphics.beginFill(
|
||||
this.runtimeObject.getParticleColorStart()
|
||||
);
|
||||
this.helperGraphics.drawCircle(0, 0, 8);
|
||||
this.helperGraphics.endFill();
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number): void {
|
||||
@@ -443,6 +484,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
private static readonly frequencyMinimumValue = 0.0001;
|
||||
|
||||
setHelperVisible(visible: boolean) {
|
||||
if (visible && !this.helperGraphics) {
|
||||
this.helperGraphics = new PIXI.Graphics();
|
||||
this.renderer.addChild(this.helperGraphics);
|
||||
} else if (!visible && this.helperGraphics) {
|
||||
this.helperGraphics.removeFromParent();
|
||||
this.helperGraphics.destroy();
|
||||
this.helperGraphics = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore - Register the class to let the engine use it.
|
||||
|
@@ -174,6 +174,10 @@ namespace gdjs {
|
||||
this,
|
||||
particleObjectData
|
||||
);
|
||||
if (instanceContainer.getGame().isInGameEdition()) {
|
||||
// TODO Disable the particles rendering
|
||||
this._renderer.setHelperVisible(true);
|
||||
}
|
||||
this.angleA = particleObjectData.emitterAngleA;
|
||||
this.angleB = particleObjectData.emitterAngleB;
|
||||
this.forceMin = particleObjectData.emitterForceMin;
|
||||
@@ -802,6 +806,14 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
getParticleColorStart(): number {
|
||||
return this.color1;
|
||||
}
|
||||
|
||||
getParticleColorEnd(): number {
|
||||
return this.color2;
|
||||
}
|
||||
|
||||
getParticleRed1(): number {
|
||||
return gdjs.hexNumberToRGBArray(this.color1)[0];
|
||||
}
|
||||
|
@@ -36,6 +36,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -39,6 +39,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -41,6 +41,19 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -623,10 +623,46 @@ Zv();a.b2Manifold.e_faceA=$v();a.b2Manifold.e_faceB=aw();a.b2_staticBody=bw();a.
|
||||
})();
|
||||
|
||||
gdjs.registerAsynchronouslyLoadingLibraryPromise(initializeBox2D({locateFile: function(path, prefix) {
|
||||
return location.protocol === 'file:' ?
|
||||
// This is needed to run on Electron.
|
||||
prefix + "Extensions/Physics2Behavior/" + path :
|
||||
prefix + path;
|
||||
// Path should always be "Box2D_v2.3.1_min.wasm.wasm" (and if it's not, we should probably hardcode it).
|
||||
if (path !== 'Box2D_v2.3.1_min.wasm.wasm') {
|
||||
console.warn("'path' argument sent to locateFile in Box2D_v2.3.1_min.wasm.js is not the expected string 'Box2D_v2.3.1_min.wasm.wasm'. Loading may fail.")
|
||||
}
|
||||
|
||||
// Prefix is typically:
|
||||
// Games ("exported", standalone game):
|
||||
// - Web game: "https://games.gdevelop-app.com/[...]/Extensions/Physics2Behavior/"
|
||||
// - Cordova Android: "https://localhost/Extensions/Physics2Behavior/".
|
||||
// - Cordova iOS: "ionic://localhost/Extensions/Physics2Behavior/".
|
||||
// - Electron macOS: "/private/var/[...]/Contents/Resources/app.asar/app/" (notice the missing folder).
|
||||
// - Electron Windows: "C:\Users\[...]\AppData\Local\[...]\resources\app.asar\app/" (notice the missing folder).
|
||||
// Preview (in the editor):
|
||||
// - Web app preview (dev editor): "http://localhost:5002/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Web app preview (production editor): "https://resources.gdevelop-app.com/[...]/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Electron app preview (dev editor): "/var/[...]/preview/" (notice the missing folder).
|
||||
// - Electron app preview (production editor): "/var/[...]/preview/" (notice the missing folder).
|
||||
// In-game editor:
|
||||
// - Web app (dev editor): "http://localhost:5002/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Web app (production editor): "https://resources.gdevelop-app.com/[...]/Runtime/Extensions/Physics2Behavior/"
|
||||
// - Electron app (dev editor): "file:///var/[...]/in-game-editor-preview/Extensions/Physics2Behavior/"
|
||||
// - Electron app (production editor): "file:///var/[...]/in-game-editor-preview/Extensions/Physics2Behavior/"
|
||||
|
||||
// If the prefix is a full URL, it's a full URL to the folder containing this JS file.
|
||||
// Sill consider the case where the folder could have been missing.
|
||||
let url;
|
||||
if (prefix.startsWith('http:') || prefix.startsWith('https:')) {
|
||||
url = prefix.endsWith('Extensions/Physics2Behavior/') ?
|
||||
prefix + path :
|
||||
prefix + 'Extensions/Physics2Behavior/' + path;
|
||||
} else {
|
||||
// Electron or Cordova iOS will fall in this case.
|
||||
// We can't use this simple solution for http/https because
|
||||
// on the web-app, the runtime is not necessarily hosted
|
||||
// on the same domain as where the game generated files are served (so "prefix" is needed).
|
||||
url = "Extensions/Physics2Behavior/" + path;
|
||||
}
|
||||
|
||||
console.info(`Box2D wasm file is being loaded from path "${path}" with prefix "${prefix}". Resolved URL: "${url}".`);
|
||||
return url;
|
||||
}}).then(box2d => {
|
||||
window.Box2D = box2d;
|
||||
}));
|
||||
|
@@ -647,6 +647,9 @@ namespace gdjs {
|
||||
export const displayAuthenticationBanner = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
if (_authenticationBanner) {
|
||||
// Banner already displayed, ensure it's visible.
|
||||
_authenticationBanner.style.opacity = '1';
|
||||
@@ -1042,6 +1045,10 @@ namespace gdjs {
|
||||
): gdjs.PromiseTask<{ status: 'logged' | 'errored' | 'dismissed' }> =>
|
||||
new gdjs.PromiseTask(
|
||||
new Promise((resolve) => {
|
||||
if (runtimeScene.getGame().isInGameEdition()) {
|
||||
resolve({ status: 'dismissed' });
|
||||
}
|
||||
|
||||
// Create the authentication container for the player to wait.
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
|
@@ -20,6 +20,8 @@ namespace gdjs {
|
||||
|
||||
_antialiasingFilter: null | PIXI.Filter = null;
|
||||
|
||||
_placeholder: PIXI.Sprite | null = null;
|
||||
|
||||
private static readonly _positionForTransformation: PIXI.IPointData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -405,6 +407,25 @@ namespace gdjs {
|
||||
|
||||
updatePreRender(): void {
|
||||
this.updatePositionIfNeeded();
|
||||
|
||||
const game = this._object.getRuntimeScene().getGame();
|
||||
if (
|
||||
game.isInGameEdition() &&
|
||||
this._graphics.geometry.graphicsData.length === 0
|
||||
) {
|
||||
if (!this._placeholder) {
|
||||
console.log(game.getGameData().resources.resources);
|
||||
const texture = game
|
||||
.getImageManager()
|
||||
.getPIXITexture('InGameEditor-ShapePainterIcon');
|
||||
this._placeholder = new PIXI.Sprite(texture);
|
||||
}
|
||||
this._graphics.addChild(this._placeholder);
|
||||
} else if (this._placeholder) {
|
||||
this._placeholder.removeFromParent();
|
||||
this._placeholder.destroy();
|
||||
this._placeholder = null;
|
||||
}
|
||||
}
|
||||
|
||||
updatePositionX(): void {
|
||||
|
@@ -278,12 +278,8 @@ namespace gdjs {
|
||||
* @param initialInstanceData The extra parameters
|
||||
*/
|
||||
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
|
||||
|
@@ -50,6 +50,19 @@ describe('gdjs.ShapePainterRuntimeObject (using a PixiJS RuntimeGame with assets
|
||||
instances: [],
|
||||
variables: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -203,13 +203,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const loadedSpineAtlas = this._loadedSpineAtlases.get(resourceData);
|
||||
const loadedSpineAtlas = this._loadedSpineAtlases.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (loadedSpineAtlas) {
|
||||
loadedSpineAtlas.dispose();
|
||||
this._loadedSpineAtlases.delete(resourceData);
|
||||
}
|
||||
|
||||
const loadingSpineAtlas = this._loadingSpineAtlases.get(resourceData);
|
||||
const loadingSpineAtlas = this._loadingSpineAtlases.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (loadingSpineAtlas) {
|
||||
loadingSpineAtlas.then((atl) => atl.dispose());
|
||||
this._loadingSpineAtlases.delete(resourceData);
|
||||
|
@@ -218,15 +218,13 @@ namespace gdjs {
|
||||
this.setSize(initialInstanceData.width, initialInstanceData.height);
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
getDrawableX(): number {
|
||||
|
@@ -61,6 +61,19 @@ describe('gdjs.TextInputRuntimeObject (using a PixiJS RuntimeGame with DOM eleme
|
||||
instances: [],
|
||||
variables: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -141,7 +141,9 @@ namespace gdjs {
|
||||
);
|
||||
this._borderOpacity = objectData.content.borderOpacity;
|
||||
this._borderWidth = objectData.content.borderWidth;
|
||||
this._disabled = objectData.content.disabled;
|
||||
this._disabled = instanceContainer.getGame().isInGameEdition()
|
||||
? true
|
||||
: objectData.content.disabled;
|
||||
this._readOnly = objectData.content.readOnly;
|
||||
this._spellCheck =
|
||||
objectData.content.spellCheck !== undefined
|
||||
@@ -334,9 +336,11 @@ namespace gdjs {
|
||||
this.setHeight(initialInstanceData.height);
|
||||
this._renderer.updatePadding();
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
onScenePaused(runtimeScene: gdjs.RuntimeScene): void {
|
||||
@@ -566,6 +570,9 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setDisabled(value: boolean) {
|
||||
if (this.getInstanceContainer().getGame().isInGameEdition()) {
|
||||
return;
|
||||
}
|
||||
this._disabled = value;
|
||||
this._renderer.updateDisabled();
|
||||
}
|
||||
|
@@ -340,7 +340,9 @@ namespace gdjs {
|
||||
return this._renderer.getRendererObject();
|
||||
}
|
||||
|
||||
override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
override updatePreRender(
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
this._renderer.ensureUpToDate();
|
||||
}
|
||||
|
||||
@@ -358,9 +360,11 @@ namespace gdjs {
|
||||
} else {
|
||||
this.setWrapping(false);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -236,9 +236,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
|
||||
// 4. Update position (calculations based on renderer's dimensions).
|
||||
this._renderer.updatePosition();
|
||||
@@ -449,6 +451,14 @@ namespace gdjs {
|
||||
return this._renderer.getHeight();
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this.getTileMapWidth();
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this.getTileMapHeight();
|
||||
}
|
||||
|
||||
getScaleX(): float {
|
||||
return this._renderer.getScaleX();
|
||||
}
|
||||
|
@@ -57,6 +57,19 @@ describe('gdjs.TileMapCollisionMaskRuntimeObject', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -197,9 +197,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
private _updateTileMap(): void {
|
||||
@@ -343,6 +345,14 @@ namespace gdjs {
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this.getTileMapWidth();
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this.getTileMapHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric mean of the X and Y scale in case they are different).
|
||||
*
|
||||
|
@@ -42,6 +42,8 @@ namespace gdjs {
|
||||
|
||||
_renderer: gdjs.TiledSpriteRuntimeObjectRenderer;
|
||||
|
||||
_objectData: TiledSpriteObjectData;
|
||||
|
||||
/**
|
||||
* @param instanceContainer The container the object belongs to.
|
||||
* @param tiledSpriteObjectData The initial properties of the object
|
||||
@@ -51,6 +53,7 @@ namespace gdjs {
|
||||
tiledSpriteObjectData: TiledSpriteObjectData
|
||||
) {
|
||||
super(instanceContainer, tiledSpriteObjectData);
|
||||
this._objectData = tiledSpriteObjectData;
|
||||
this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer(
|
||||
this,
|
||||
instanceContainer,
|
||||
@@ -66,6 +69,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateFromObjectData(oldObjectData, newObjectData): boolean {
|
||||
this._objectData = newObjectData;
|
||||
if (oldObjectData.texture !== newObjectData.texture) {
|
||||
this.setTexture(newObjectData.texture, this.getRuntimeScene());
|
||||
}
|
||||
@@ -129,9 +133,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,6 +229,14 @@ namespace gdjs {
|
||||
this.setHeight(height);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._objectData.width;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._objectData.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the offset on the X-axis when displaying the image of the Tiled Sprite object.
|
||||
* @param xOffset The new offset on the X-axis.
|
||||
|
@@ -34,6 +34,19 @@ describe('gdjs.TopDownMovementRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
|
@@ -151,9 +151,11 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
}
|
||||
|
||||
onDestroyed(): void {
|
||||
|
@@ -18,6 +18,9 @@
|
||||
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
|
||||
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
|
||||
#include "GDCore/IDE/ProjectStripper.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/ExternalLayout.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
@@ -47,7 +50,7 @@ Exporter::~Exporter() {}
|
||||
bool Exporter::ExportProjectForPixiPreview(
|
||||
const PreviewExportOptions &options) {
|
||||
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
|
||||
return helper.ExportProjectForPixiPreview(options);
|
||||
return helper.ExportProjectForPixiPreview(options, includesFiles);
|
||||
}
|
||||
|
||||
bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
@@ -80,7 +83,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
|
||||
// Prepare the export directory
|
||||
fs.MkDir(exportDir);
|
||||
std::vector<gd::String> includesFiles;
|
||||
includesFiles.clear();
|
||||
std::vector<gd::String> resourcesFiles;
|
||||
|
||||
// Export the resources (before generating events as some resources
|
||||
@@ -98,6 +101,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
helper.AddLibsInclude(
|
||||
/*pixiRenderers=*/true,
|
||||
usedExtensionsResult.Has3DObjects(),
|
||||
/*isInGameEditor=*/false,
|
||||
/*includeWebsocketDebuggerClient=*/false,
|
||||
/*includeWindowMessageDebuggerClient=*/false,
|
||||
/*includeMinimalDebuggerClient=*/false,
|
||||
@@ -120,7 +124,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
helper.ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
// Export events
|
||||
if (!helper.ExportEventsCode(exportedProject,
|
||||
if (!helper.ExportScenesEventsCode(exportedProject,
|
||||
codeOutputDir,
|
||||
includesFiles,
|
||||
wholeProjectDiagnosticReport,
|
||||
@@ -130,29 +134,11 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto projectUsedResources =
|
||||
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
|
||||
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
|
||||
for (std::size_t layoutIndex = 0;
|
||||
layoutIndex < exportedProject.GetLayoutsCount();
|
||||
layoutIndex++) {
|
||||
auto &layout = exportedProject.GetLayout(layoutIndex);
|
||||
scenesUsedResources[layout.GetName()] =
|
||||
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use
|
||||
// stripped things like objects groups...)...
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
|
||||
//...and export it
|
||||
gd::SerializerElement noRuntimeGameOptions;
|
||||
helper.ExportProjectData(fs,
|
||||
exportedProject,
|
||||
codeOutputDir + "/data.js",
|
||||
helper.ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
|
||||
noRuntimeGameOptions,
|
||||
projectUsedResources,
|
||||
scenesUsedResources);
|
||||
/*isInGameEdition=*/false);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
|
||||
@@ -215,4 +201,17 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Exporter::SerializeProjectData(const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &projectDataElement) {
|
||||
ExporterHelper::SerializeProjectData(fs, project, options, projectDataElement);
|
||||
}
|
||||
|
||||
void Exporter::SerializeRuntimeGameOptions(
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &runtimeGameOptionsElement) {
|
||||
ExporterHelper::SerializeRuntimeGameOptions(
|
||||
fs, gdjsRoot, options, includesFiles, runtimeGameOptionsElement);
|
||||
}
|
||||
|
||||
} // namespace gdjs
|
||||
|
@@ -16,6 +16,7 @@ class Project;
|
||||
class Layout;
|
||||
class ExternalLayout;
|
||||
class AbstractFileSystem;
|
||||
class SerializerElement;
|
||||
} // namespace gd
|
||||
namespace gdjs {
|
||||
struct PreviewExportOptions;
|
||||
@@ -64,7 +65,33 @@ class Exporter {
|
||||
codeOutputDir = codeOutputDir_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Serialize a project without its events to JSON
|
||||
*
|
||||
* \param project The project to be exported
|
||||
* \param options The content of the extra configuration
|
||||
* \param projectDataElement The element where the project data is serialized
|
||||
*/
|
||||
void SerializeProjectData(const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &projectDataElement);
|
||||
|
||||
/**
|
||||
* \brief Serialize the content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions to JSON
|
||||
*
|
||||
* \warning `ExportProjectForPixiPreview` must be called first to serialize
|
||||
* the list of scripts files.
|
||||
*
|
||||
* \param options The content of the extra configuration
|
||||
* \param runtimeGameOptionsElement The element where the game options are
|
||||
* serialized
|
||||
*/
|
||||
void
|
||||
SerializeRuntimeGameOptions(const PreviewExportOptions &options,
|
||||
gd::SerializerElement &runtimeGameOptionsElement);
|
||||
|
||||
private:
|
||||
gd::AbstractFileSystem&
|
||||
fs; ///< The abstract file system to be used for exportation.
|
||||
gd::String lastError; ///< The last error that occurred.
|
||||
@@ -72,6 +99,8 @@ class Exporter {
|
||||
gdjsRoot; ///< The root directory of GDJS, used to copy runtime files.
|
||||
gd::String codeOutputDir; ///< The directory where JS code is outputted. Will
|
||||
///< be then copied to the final output directory.
|
||||
std::vector<gd::String>
|
||||
includesFiles; ///< The list of scripts files - useful for hot-reloading
|
||||
};
|
||||
|
||||
} // namespace gdjs
|
||||
|
@@ -28,10 +28,13 @@
|
||||
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
|
||||
#include "GDCore/IDE/ExportedDependencyResolver.h"
|
||||
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
|
||||
#include "GDCore/IDE/Project/ResourcesMergingHelper.h"
|
||||
#include "GDCore/IDE/Project/SceneResourcesFinder.h"
|
||||
#include "GDCore/IDE/ProjectStripper.h"
|
||||
#include "GDCore/IDE/ResourceExposer.h"
|
||||
#include "GDCore/IDE/SceneNameMangler.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
#include "GDCore/Project/ExternalLayout.h"
|
||||
@@ -58,7 +61,6 @@ double GetTimeSpent(double previousTime) { return GetTimeNow() - previousTime; }
|
||||
double LogTimeSpent(const gd::String &name, double previousTime) {
|
||||
gd::LogStatus(name + " took " + gd::String::From(GetTimeSpent(previousTime)) +
|
||||
"ms");
|
||||
std::cout << std::endl;
|
||||
return GetTimeNow();
|
||||
}
|
||||
} // namespace
|
||||
@@ -104,128 +106,298 @@ ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
|
||||
: fs(fileSystem), gdjsRoot(gdjsRoot_), codeOutputDir(codeOutputDir_) {};
|
||||
|
||||
bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
const PreviewExportOptions &options) {
|
||||
const PreviewExportOptions &options,
|
||||
std::vector<gd::String> &includesFiles) {
|
||||
|
||||
if (options.isInGameEdition && !options.shouldReloadProjectData &&
|
||||
!options.shouldReloadLibraries && !options.shouldGenerateScenesEventsCode) {
|
||||
gd::LogStatus("Skip project export entirely");
|
||||
return "";
|
||||
}
|
||||
|
||||
double previousTime = GetTimeNow();
|
||||
fs.MkDir(options.exportPath);
|
||||
fs.ClearDir(options.exportPath);
|
||||
std::vector<gd::String> includesFiles;
|
||||
if (options.shouldClearExportFolder) {
|
||||
fs.ClearDir(options.exportPath);
|
||||
}
|
||||
includesFiles.clear();
|
||||
std::vector<gd::String> resourcesFiles;
|
||||
|
||||
// TODO Try to remove side effects to avoid the copy
|
||||
// that destroys the AST in cache.
|
||||
gd::Project exportedProject = options.project;
|
||||
const gd::Project &immutableProject = exportedProject;
|
||||
const gd::Project &immutableProject = options.project;
|
||||
previousTime = LogTimeSpent("Project cloning", previousTime);
|
||||
|
||||
if (options.fullLoadingScreen) {
|
||||
// Use project properties fallback to set empty properties
|
||||
if (exportedProject.GetAuthorIds().empty() &&
|
||||
!options.fallbackAuthorId.empty()) {
|
||||
exportedProject.GetAuthorIds().push_back(options.fallbackAuthorId);
|
||||
}
|
||||
if (exportedProject.GetAuthorUsernames().empty() &&
|
||||
!options.fallbackAuthorUsername.empty()) {
|
||||
exportedProject.GetAuthorUsernames().push_back(
|
||||
options.fallbackAuthorUsername);
|
||||
if (options.isInGameEdition) {
|
||||
if (options.shouldReloadProjectData || options.shouldGenerateScenesEventsCode) {
|
||||
auto projectDirectory = fs.DirNameFrom(exportedProject.GetProjectFile());
|
||||
gd::ResourcesMergingHelper resourcesMergingHelper(
|
||||
exportedProject.GetResourcesManager(), fs);
|
||||
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
|
||||
resourcesMergingHelper.SetShouldUseOriginalAbsoluteFilenames();
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(exportedProject,
|
||||
resourcesMergingHelper);
|
||||
|
||||
previousTime = LogTimeSpent("Resource path resolving", previousTime);
|
||||
}
|
||||
gd::LogStatus("Resource export is skipped");
|
||||
} else {
|
||||
// Most of the time, we skip the logo and minimum duration so that
|
||||
// the preview start as soon as possible.
|
||||
exportedProject.GetLoadingScreen()
|
||||
.ShowGDevelopLogoDuringLoadingScreen(false)
|
||||
.SetMinDuration(0);
|
||||
exportedProject.GetWatermark().ShowGDevelopWatermark(false);
|
||||
// Export resources (*before* generating events as some resources filenames
|
||||
// may be updated)
|
||||
ExportResources(fs, exportedProject, options.exportPath);
|
||||
|
||||
previousTime = LogTimeSpent("Resource export", previousTime);
|
||||
}
|
||||
|
||||
// Export resources (*before* generating events as some resources filenames
|
||||
// may be updated)
|
||||
ExportResources(fs, exportedProject, options.exportPath);
|
||||
|
||||
previousTime = LogTimeSpent("Resource export", previousTime);
|
||||
|
||||
// Compatibility with GD <= 5.0-beta56
|
||||
// Stay compatible with text objects declaring their font as just a filename
|
||||
// without a font resource - by manually adding these resources.
|
||||
AddDeprecatedFontFilesToFontResources(
|
||||
fs, exportedProject.GetResourcesManager(), options.exportPath);
|
||||
// end of compatibility code
|
||||
|
||||
auto usedExtensionsResult =
|
||||
gd::UsedExtensionsFinder::ScanProject(exportedProject);
|
||||
|
||||
// Export engine libraries
|
||||
AddLibsInclude(/*pixiRenderers=*/true,
|
||||
usedExtensionsResult.Has3DObjects(),
|
||||
/*includeWebsocketDebuggerClient=*/
|
||||
!options.websocketDebuggerServerAddress.empty(),
|
||||
/*includeWindowMessageDebuggerClient=*/
|
||||
options.useWindowMessageDebuggerClient,
|
||||
/*includeMinimalDebuggerClient=*/
|
||||
options.useMinimalDebuggerClient,
|
||||
/*includeCaptureManager=*/
|
||||
!options.captureOptions.IsEmpty(),
|
||||
/*includeInAppTutorialMessage*/
|
||||
!options.inAppTutorialMessageInPreview.empty(),
|
||||
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
|
||||
// Export files for free function, object and behaviors
|
||||
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
for (const auto &requiredFile : usedExtensionsResult.GetUsedRequiredFiles()) {
|
||||
InsertUnique(resourcesFiles, requiredFile);
|
||||
if (options.shouldReloadProjectData || options.shouldGenerateScenesEventsCode) {
|
||||
// Compatibility with GD <= 5.0-beta56
|
||||
// Stay compatible with text objects declaring their font as just a filename
|
||||
// without a font resource - by manually adding these resources.
|
||||
AddDeprecatedFontFilesToFontResources(
|
||||
fs, exportedProject.GetResourcesManager(), options.exportPath);
|
||||
// end of compatibility code
|
||||
}
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to
|
||||
// the engine)
|
||||
ExportEffectIncludes(exportedProject, includesFiles);
|
||||
std::vector<gd::SourceFileMetadata> noUsedSourceFiles;
|
||||
std::vector<gd::SourceFileMetadata> &usedSourceFiles = noUsedSourceFiles;
|
||||
if (options.shouldReloadLibraries) {
|
||||
auto usedExtensionsResult =
|
||||
gd::UsedExtensionsFinder::ScanProject(exportedProject);
|
||||
usedSourceFiles = usedExtensionsResult.GetUsedSourceFiles();
|
||||
|
||||
previousTime = LogTimeSpent("Include files export", previousTime);
|
||||
// Export engine libraries
|
||||
AddLibsInclude(/*pixiRenderers=*/true,
|
||||
/*pixiInThreeRenderers=*/
|
||||
usedExtensionsResult.Has3DObjects(),
|
||||
/*isInGameEdition=*/
|
||||
options.isInGameEdition,
|
||||
/*includeWebsocketDebuggerClient=*/
|
||||
!options.websocketDebuggerServerAddress.empty(),
|
||||
/*includeWindowMessageDebuggerClient=*/
|
||||
options.useWindowMessageDebuggerClient,
|
||||
/*includeMinimalDebuggerClient=*/
|
||||
options.useMinimalDebuggerClient,
|
||||
/*includeCaptureManager=*/
|
||||
!options.captureOptions.IsEmpty(),
|
||||
/*includeInAppTutorialMessage*/
|
||||
!options.inAppTutorialMessageInPreview.empty(),
|
||||
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
|
||||
if (!options.projectDataOnlyExport) {
|
||||
// Export files for free function, object and behaviors
|
||||
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
for (const auto &requiredFile : usedExtensionsResult.GetUsedRequiredFiles()) {
|
||||
InsertUnique(resourcesFiles, requiredFile);
|
||||
}
|
||||
if (options.isInGameEdition) {
|
||||
// TODO Scan the objects and events of event-based objects
|
||||
// (it could be an alternative method ScanProjectAndEventsBasedObjects in
|
||||
// UsedExtensionsFinder).
|
||||
// This is already done by UsedExtensionsFinder, but maybe it shouldn't.
|
||||
|
||||
// Export all event-based objects because they can be edited even if they
|
||||
// are not used yet.
|
||||
for (std::size_t e = 0;
|
||||
e < exportedProject.GetEventsFunctionsExtensionsCount(); e++) {
|
||||
auto &eventsFunctionsExtension =
|
||||
exportedProject.GetEventsFunctionsExtension(e);
|
||||
|
||||
for (auto &&eventsBasedObjectUniquePtr :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects()
|
||||
.GetInternalVector()) {
|
||||
auto eventsBasedObject = eventsBasedObjectUniquePtr.get();
|
||||
|
||||
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
|
||||
exportedProject.GetCurrentPlatform(),
|
||||
gd::PlatformExtension::GetObjectFullType(
|
||||
eventsFunctionsExtension.GetName(),
|
||||
eventsBasedObject->GetName()));
|
||||
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
for (auto &behaviorType :
|
||||
metadata.GetMetadata().GetDefaultBehaviors()) {
|
||||
auto behaviorMetadata =
|
||||
gd::MetadataProvider::GetExtensionAndBehaviorMetadata(
|
||||
exportedProject.GetCurrentPlatform(), behaviorType);
|
||||
for (auto &&includeFile :
|
||||
behaviorMetadata.GetMetadata().includeFiles) {
|
||||
InsertUnique(includesFiles, includeFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export effects (after engine libraries as they auto-register themselves to
|
||||
// the engine)
|
||||
ExportEffectIncludes(exportedProject, includesFiles);
|
||||
|
||||
previousTime = LogTimeSpent("Include files export", previousTime);
|
||||
}
|
||||
else {
|
||||
gd::LogStatus("Include files export is skipped");
|
||||
}
|
||||
|
||||
if (options.shouldGenerateScenesEventsCode) {
|
||||
gd::WholeProjectDiagnosticReport &wholeProjectDiagnosticReport =
|
||||
options.project.GetWholeProjectDiagnosticReport();
|
||||
wholeProjectDiagnosticReport.Clear();
|
||||
|
||||
// Generate events code
|
||||
if (!ExportEventsCode(immutableProject,
|
||||
if (!ExportScenesEventsCode(immutableProject,
|
||||
codeOutputDir,
|
||||
includesFiles,
|
||||
wholeProjectDiagnosticReport,
|
||||
true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
previousTime = LogTimeSpent("Events code export", previousTime);
|
||||
}
|
||||
|
||||
auto projectUsedResources =
|
||||
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
|
||||
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
|
||||
for (std::size_t layoutIndex = 0;
|
||||
layoutIndex < exportedProject.GetLayoutsCount();
|
||||
layoutIndex++) {
|
||||
auto &layout = exportedProject.GetLayout(layoutIndex);
|
||||
scenesUsedResources[layout.GetName()] =
|
||||
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
|
||||
else {
|
||||
gd::LogStatus("Events code export is skipped");
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use stripped
|
||||
// things (objects groups...))
|
||||
gd::ProjectStripper::StripProjectForExport(exportedProject);
|
||||
exportedProject.SetFirstLayout(options.layoutName);
|
||||
if (options.shouldReloadProjectData) {
|
||||
|
||||
if (options.fullLoadingScreen) {
|
||||
// Use project properties fallback to set empty properties
|
||||
if (exportedProject.GetAuthorIds().empty() &&
|
||||
!options.fallbackAuthorId.empty()) {
|
||||
exportedProject.GetAuthorIds().push_back(options.fallbackAuthorId);
|
||||
}
|
||||
if (exportedProject.GetAuthorUsernames().empty() &&
|
||||
!options.fallbackAuthorUsername.empty()) {
|
||||
exportedProject.GetAuthorUsernames().push_back(
|
||||
options.fallbackAuthorUsername);
|
||||
}
|
||||
} else {
|
||||
// Most of the time, we skip the logo and minimum duration so that
|
||||
// the preview start as soon as possible.
|
||||
exportedProject.GetLoadingScreen()
|
||||
.ShowGDevelopLogoDuringLoadingScreen(false)
|
||||
.SetMinDuration(0);
|
||||
exportedProject.GetWatermark().ShowGDevelopWatermark(false);
|
||||
}
|
||||
|
||||
previousTime = LogTimeSpent("Data stripping", previousTime);
|
||||
gd::SerializerElement runtimeGameOptions;
|
||||
ExporterHelper::SerializeRuntimeGameOptions(fs, gdjsRoot, options,
|
||||
includesFiles, runtimeGameOptions);
|
||||
ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
|
||||
runtimeGameOptions, options.isInGameEdition);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
previousTime = LogTimeSpent("Project data export", previousTime);
|
||||
}
|
||||
else {
|
||||
gd::LogStatus("Project data export is skipped");
|
||||
}
|
||||
|
||||
if (options.shouldReloadLibraries) {
|
||||
if (options.isInGameEdition) {
|
||||
InsertUnique(resourcesFiles, "InGameEditor/Resources/primitivedrawingicon.png");
|
||||
}
|
||||
// Copy all the dependencies and their source maps
|
||||
ExportIncludesAndLibs(includesFiles, options.exportPath, true);
|
||||
ExportIncludesAndLibs(resourcesFiles, options.exportPath, true);
|
||||
|
||||
// TODO Build a full includesFiles list without actually doing export or
|
||||
// generation.
|
||||
if (options.shouldGenerateScenesEventsCode) {
|
||||
// Create the index file
|
||||
if (!ExportIndexFile(exportedProject, gdjsRoot + "/Runtime/index.html",
|
||||
options.exportPath, includesFiles, usedSourceFiles,
|
||||
options.nonRuntimeScriptsCacheBurst,
|
||||
"gdjs.runtimeGameOptions")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
previousTime = LogTimeSpent("Include and libs export", previousTime);
|
||||
} else {
|
||||
gd::LogStatus("Include and libs export is skipped");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::ExportProjectData(
|
||||
gd::AbstractFileSystem &fs, gd::Project &project, gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions, bool isInGameEdition) {
|
||||
fs.MkDir(fs.DirNameFrom(filename));
|
||||
|
||||
gd::SerializerElement projectDataElement;
|
||||
ExporterHelper::StriptAndSerializeProjectData(
|
||||
project, projectDataElement, isInGameEdition);
|
||||
|
||||
// Save the project to JSON
|
||||
gd::String output =
|
||||
"gdjs.projectData = " + gd::Serializer::ToJSON(projectDataElement) +
|
||||
";\ngdjs.runtimeGameOptions = " + gd::Serializer::ToJSON(runtimeGameOptions) +
|
||||
";\n";
|
||||
|
||||
if (!fs.WriteToFile(filename, output))
|
||||
return "Unable to write " + filename;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void ExporterHelper::SerializeRuntimeGameOptions(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const PreviewExportOptions &options, std::vector<gd::String> &includesFiles,
|
||||
gd::SerializerElement &runtimeGameOptions) {
|
||||
// Create the setup options passed to the gdjs.RuntimeGame
|
||||
gd::SerializerElement runtimeGameOptions;
|
||||
runtimeGameOptions.AddChild("isPreview").SetBoolValue(true);
|
||||
if (!options.externalLayoutName.empty()) {
|
||||
runtimeGameOptions.AddChild("injectExternalLayout")
|
||||
.SetValue(options.externalLayoutName);
|
||||
|
||||
auto &initialRuntimeGameStatus =
|
||||
runtimeGameOptions.AddChild("initialRuntimeGameStatus");
|
||||
initialRuntimeGameStatus.AddChild("sceneName")
|
||||
.SetStringValue(options.layoutName);
|
||||
if (options.isInGameEdition) {
|
||||
initialRuntimeGameStatus.AddChild("isInGameEdition").SetBoolValue(true);
|
||||
initialRuntimeGameStatus.AddChild("editorId").SetValue(options.editorId);
|
||||
if (!options.editorCamera3DCameraMode.empty()) {
|
||||
auto &editorCamera3D =
|
||||
initialRuntimeGameStatus.AddChild("editorCamera3D");
|
||||
editorCamera3D.AddChild("cameraMode").SetStringValue(
|
||||
options.editorCamera3DCameraMode);
|
||||
editorCamera3D.AddChild("positionX")
|
||||
.SetDoubleValue(options.editorCamera3DPositionX);
|
||||
editorCamera3D.AddChild("positionY")
|
||||
.SetDoubleValue(options.editorCamera3DPositionY);
|
||||
editorCamera3D.AddChild("positionZ")
|
||||
.SetDoubleValue(options.editorCamera3DPositionZ);
|
||||
editorCamera3D.AddChild("rotationAngle")
|
||||
.SetDoubleValue(options.editorCamera3DRotationAngle);
|
||||
editorCamera3D.AddChild("elevationAngle")
|
||||
.SetDoubleValue(options.editorCamera3DElevationAngle);
|
||||
editorCamera3D.AddChild("distance")
|
||||
.SetDoubleValue(options.editorCamera3DDistance);
|
||||
}
|
||||
}
|
||||
runtimeGameOptions.AddChild("projectDataOnlyExport")
|
||||
.SetBoolValue(options.projectDataOnlyExport);
|
||||
if (!options.externalLayoutName.empty()) {
|
||||
initialRuntimeGameStatus.AddChild("injectedExternalLayoutName")
|
||||
.SetValue(options.externalLayoutName);
|
||||
|
||||
if (options.isInGameEdition) {
|
||||
initialRuntimeGameStatus.AddChild("skipCreatingInstancesFromScene")
|
||||
.SetBoolValue(true);
|
||||
}
|
||||
}
|
||||
if (!options.eventsBasedObjectType.empty()) {
|
||||
initialRuntimeGameStatus.AddChild("eventsBasedObjectType")
|
||||
.SetValue(options.eventsBasedObjectType);
|
||||
initialRuntimeGameStatus.AddChild("eventsBasedObjectVariantName")
|
||||
.SetValue(options.eventsBasedObjectVariantName);
|
||||
}
|
||||
|
||||
runtimeGameOptions.AddChild("shouldReloadLibraries")
|
||||
.SetBoolValue(options.shouldReloadLibraries);
|
||||
runtimeGameOptions.AddChild("shouldGenerateScenesEventsCode")
|
||||
.SetBoolValue(options.shouldGenerateScenesEventsCode);
|
||||
|
||||
runtimeGameOptions.AddChild("nativeMobileApp")
|
||||
.SetBoolValue(options.nativeMobileApp);
|
||||
runtimeGameOptions.AddChild("websocketDebuggerServerAddress")
|
||||
@@ -297,71 +469,106 @@ bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
|
||||
for (const auto &includeFile : includesFiles) {
|
||||
auto hashIt = options.includeFileHashes.find(includeFile);
|
||||
gd::String scriptSrc = GetExportedIncludeFilename(includeFile);
|
||||
gd::String scriptSrc = GetExportedIncludeFilename(fs, gdjsRoot, includeFile);
|
||||
scriptFilesElement.AddChild("scriptFile")
|
||||
.SetStringAttribute("path", scriptSrc)
|
||||
.SetIntAttribute(
|
||||
"hash",
|
||||
hashIt != options.includeFileHashes.end() ? hashIt->second : 0);
|
||||
}
|
||||
|
||||
// Export the project
|
||||
ExportProjectData(fs,
|
||||
exportedProject,
|
||||
codeOutputDir + "/data.js",
|
||||
runtimeGameOptions,
|
||||
projectUsedResources,
|
||||
scenesUsedResources);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
previousTime = LogTimeSpent("Project data export", previousTime);
|
||||
|
||||
// Copy all the dependencies and their source maps
|
||||
ExportIncludesAndLibs(includesFiles, options.exportPath, true);
|
||||
ExportIncludesAndLibs(resourcesFiles, options.exportPath, true);
|
||||
|
||||
// Create the index file
|
||||
if (!ExportIndexFile(exportedProject,
|
||||
gdjsRoot + "/Runtime/index.html",
|
||||
options.exportPath,
|
||||
includesFiles,
|
||||
usedExtensionsResult.GetUsedSourceFiles(),
|
||||
options.nonRuntimeScriptsCacheBurst,
|
||||
"gdjs.runtimeGameOptions"))
|
||||
return false;
|
||||
|
||||
previousTime = LogTimeSpent("Include and libs export", previousTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::ExportProjectData(
|
||||
gd::AbstractFileSystem &fs,
|
||||
gd::Project &project,
|
||||
gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
|
||||
fs.MkDir(fs.DirNameFrom(filename));
|
||||
void ExporterHelper::AddInGameEditorResources(
|
||||
gd::Project &project, std::set<gd::String> &projectUsedResources) {
|
||||
project.GetResourcesManager().AddResource(
|
||||
"InGameEditor-ShapePainterIcon",
|
||||
"InGameEditor/Resources/primitivedrawingicon.png", "image");
|
||||
projectUsedResources.insert("InGameEditor-ShapePainterIcon");
|
||||
}
|
||||
|
||||
void ExporterHelper::SerializeProjectData(gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &rootElement) {
|
||||
gd::Project clonedProject = project;
|
||||
|
||||
// Replace all resource file paths with the one used in exported projects.
|
||||
auto projectDirectory = fs.DirNameFrom(project.GetProjectFile());
|
||||
gd::ResourcesMergingHelper resourcesMergingHelper(
|
||||
clonedProject.GetResourcesManager(), fs);
|
||||
resourcesMergingHelper.SetBaseDirectory(projectDirectory);
|
||||
if (options.isInGameEdition) {
|
||||
resourcesMergingHelper.SetShouldUseOriginalAbsoluteFilenames();
|
||||
} else {
|
||||
resourcesMergingHelper.PreserveDirectoriesStructure(false);
|
||||
resourcesMergingHelper.PreserveAbsoluteFilenames(false);
|
||||
}
|
||||
gd::ResourceExposer::ExposeWholeProjectResources(clonedProject,
|
||||
resourcesMergingHelper);
|
||||
|
||||
ExporterHelper::StriptAndSerializeProjectData(
|
||||
clonedProject, rootElement, options.isInGameEdition);
|
||||
}
|
||||
|
||||
void ExporterHelper::StriptAndSerializeProjectData(
|
||||
gd::Project &project, gd::SerializerElement &rootElement,
|
||||
bool isInGameEdition) {
|
||||
auto projectUsedResources =
|
||||
gd::SceneResourcesFinder::FindProjectResources(project);
|
||||
if (isInGameEdition) {
|
||||
ExporterHelper::AddInGameEditorResources(project, projectUsedResources);
|
||||
}
|
||||
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
|
||||
for (std::size_t layoutIndex = 0;
|
||||
layoutIndex < project.GetLayoutsCount(); layoutIndex++) {
|
||||
auto &layout = project.GetLayout(layoutIndex);
|
||||
scenesUsedResources[layout.GetName()] =
|
||||
gd::SceneResourcesFinder::FindSceneResources(project, layout);
|
||||
}
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
eventsBasedObjectVariantsUsedResources;
|
||||
for (std::size_t extensionIndex = 0;
|
||||
extensionIndex < project.GetEventsFunctionsExtensionsCount();
|
||||
extensionIndex++) {
|
||||
auto &eventsFunctionsExtension =
|
||||
project.GetEventsFunctionsExtension(extensionIndex);
|
||||
for (auto &&eventsBasedObject :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
|
||||
|
||||
auto eventsBasedObjectType = gd::PlatformExtension::GetObjectFullType(
|
||||
eventsFunctionsExtension.GetName(), eventsBasedObject->GetName());
|
||||
eventsBasedObjectVariantsUsedResources[eventsBasedObjectType] =
|
||||
gd::SceneResourcesFinder::FindEventsBasedObjectVariantResources(
|
||||
project, eventsBasedObject->GetDefaultVariant());
|
||||
|
||||
for (auto &&eventsBasedObjectVariant :
|
||||
eventsBasedObject->GetVariants().GetInternalVector()) {
|
||||
|
||||
auto variantType = gd::PlatformExtension::GetVariantFullType(
|
||||
eventsFunctionsExtension.GetName(), eventsBasedObject->GetName(),
|
||||
eventsBasedObjectVariant->GetName());
|
||||
eventsBasedObjectVariantsUsedResources[variantType] =
|
||||
gd::SceneResourcesFinder::FindEventsBasedObjectVariantResources(
|
||||
project, *eventsBasedObjectVariant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the project (*after* generating events as the events may use stripped
|
||||
// things (objects groups...))
|
||||
gd::ProjectStripper::StripProjectForExport(project);
|
||||
|
||||
// Save the project to JSON
|
||||
gd::SerializerElement rootElement;
|
||||
project.SerializeTo(rootElement);
|
||||
SerializeUsedResources(
|
||||
rootElement, projectUsedResources, scenesUsedResources);
|
||||
gd::String output =
|
||||
"gdjs.projectData = " + gd::Serializer::ToJSON(rootElement) + ";\n" +
|
||||
"gdjs.runtimeGameOptions = " +
|
||||
gd::Serializer::ToJSON(runtimeGameOptions) + ";\n";
|
||||
|
||||
if (!fs.WriteToFile(filename, output)) return "Unable to write " + filename;
|
||||
|
||||
return "";
|
||||
SerializeUsedResources(rootElement, projectUsedResources, scenesUsedResources,
|
||||
eventsBasedObjectVariantsUsedResources);
|
||||
}
|
||||
|
||||
void ExporterHelper::SerializeUsedResources(
|
||||
gd::SerializerElement &rootElement,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources) {
|
||||
std::unordered_map<gd::String, std::set<gd::String>> &scenesUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&eventsBasedObjectVariantsUsedResources) {
|
||||
auto serializeUsedResources =
|
||||
[](gd::SerializerElement &element,
|
||||
std::set<gd::String> &usedResources) -> void {
|
||||
@@ -385,6 +592,41 @@ void ExporterHelper::SerializeUsedResources(
|
||||
auto &layoutUsedResources = scenesUsedResources[layoutName];
|
||||
serializeUsedResources(layoutElement, layoutUsedResources);
|
||||
}
|
||||
|
||||
auto &extensionsElement = rootElement.GetChild("eventsFunctionsExtensions");
|
||||
for (std::size_t extensionIndex = 0;
|
||||
extensionIndex < extensionsElement.GetChildrenCount();
|
||||
extensionIndex++) {
|
||||
auto &extensionElement = extensionsElement.GetChild(extensionIndex);
|
||||
const auto extensionName = extensionElement.GetStringAttribute("name");
|
||||
|
||||
auto &objectsElement = extensionElement.GetChild("eventsBasedObjects");
|
||||
|
||||
for (std::size_t objectIndex = 0;
|
||||
objectIndex < objectsElement.GetChildrenCount(); objectIndex++) {
|
||||
auto &objectElement = objectsElement.GetChild(objectIndex);
|
||||
const auto objectName = objectElement.GetStringAttribute("name");
|
||||
|
||||
auto eventsBasedObjectType =
|
||||
gd::PlatformExtension::GetObjectFullType(extensionName, objectName);
|
||||
auto &objectUsedResources =
|
||||
eventsBasedObjectVariantsUsedResources[eventsBasedObjectType];
|
||||
serializeUsedResources(objectElement, objectUsedResources);
|
||||
|
||||
auto &variantsElement = objectElement.GetChild("variants");
|
||||
for (std::size_t variantIndex = 0;
|
||||
variantIndex < variantsElement.GetChildrenCount(); variantIndex++) {
|
||||
auto &variantElement = variantsElement.GetChild(variantIndex);
|
||||
const auto variantName = variantElement.GetStringAttribute("name");
|
||||
|
||||
auto variantType = gd::PlatformExtension::GetVariantFullType(
|
||||
extensionName, objectName, variantName);
|
||||
auto &variantUsedResources =
|
||||
eventsBasedObjectVariantsUsedResources[variantType];
|
||||
serializeUsedResources(variantElement, variantUsedResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ExporterHelper::ExportIndexFile(
|
||||
@@ -775,7 +1017,7 @@ bool ExporterHelper::CompleteIndexFile(
|
||||
gd::String codeFilesIncludes;
|
||||
for (auto &include : includesFiles) {
|
||||
gd::String scriptSrc =
|
||||
GetExportedIncludeFilename(include, nonRuntimeScriptsCacheBurst);
|
||||
GetExportedIncludeFilename(fs, gdjsRoot, include, nonRuntimeScriptsCacheBurst);
|
||||
|
||||
// Sanity check if the file exists - if not skip it to avoid
|
||||
// including it in the list of scripts.
|
||||
@@ -801,6 +1043,7 @@ bool ExporterHelper::CompleteIndexFile(
|
||||
|
||||
void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
bool pixiInThreeRenderers,
|
||||
bool isInGameEdition,
|
||||
bool includeWebsocketDebuggerClient,
|
||||
bool includeWindowMessageDebuggerClient,
|
||||
bool includeMinimalDebuggerClient,
|
||||
@@ -878,6 +1121,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "debugger-client/hot-reloader.js");
|
||||
InsertUnique(includesFiles, "debugger-client/abstract-debugger-client.js");
|
||||
InsertUnique(includesFiles, "debugger-client/InGameDebugger.js");
|
||||
InsertUnique(includesFiles, "InGameEditor/InGameEditor.js");
|
||||
}
|
||||
if (includeWebsocketDebuggerClient) {
|
||||
InsertUnique(includesFiles, "debugger-client/websocket-debugger-client.js");
|
||||
@@ -890,14 +1134,16 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "debugger-client/minimal-debugger-client.js");
|
||||
}
|
||||
|
||||
if (pixiInThreeRenderers) {
|
||||
if (pixiInThreeRenderers || isInGameEdition) {
|
||||
InsertUnique(includesFiles, "pixi-renderers/three.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/ThreeAddons.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/draco/gltf/draco_decoder.wasm");
|
||||
InsertUnique(includesFiles,
|
||||
"pixi-renderers/draco/gltf/draco_wasm_wrapper.js");
|
||||
// Extensions in JS may use it.
|
||||
InsertUnique(includesFiles, "Extensions/3D/Scene3DTools.js");
|
||||
}
|
||||
if (pixiRenderers) {
|
||||
if (pixiRenderers || isInGameEdition) {
|
||||
InsertUnique(includesFiles, "pixi-renderers/pixi.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/pixi-filters-tools.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/runtimegame-pixi-renderer.js");
|
||||
@@ -921,7 +1167,12 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
includesFiles,
|
||||
"fontfaceobserver-font-manager/fontfaceobserver-font-manager.js");
|
||||
}
|
||||
if (pixiInThreeRenderers) {
|
||||
if (isInGameEdition) {
|
||||
// `InGameEditor` uses the `is3D` function.
|
||||
InsertUnique(includesFiles, "Extensions/3D/Base3DBehavior.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/HemisphereLight.js");
|
||||
}
|
||||
if (pixiInThreeRenderers || isInGameEdition) {
|
||||
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3D.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/A_RuntimeObject3DRenderer.js");
|
||||
InsertUnique(includesFiles, "Extensions/3D/CustomRuntimeObject3D.js");
|
||||
@@ -959,7 +1210,7 @@ bool ExporterHelper::ExportEffectIncludes(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExporterHelper::ExportEventsCode(
|
||||
bool ExporterHelper::ExportScenesEventsCode(
|
||||
const gd::Project &project,
|
||||
gd::String outputDir,
|
||||
std::vector<gd::String> &includesFiles,
|
||||
@@ -995,6 +1246,7 @@ bool ExporterHelper::ExportEventsCode(
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::GetExportedIncludeFilename(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst) {
|
||||
auto addSearchParameterToUrl = [](const gd::String &url,
|
||||
const gd::String &urlEncodedParameterName,
|
||||
|
@@ -42,9 +42,9 @@ struct PreviewExportOptions {
|
||||
useWindowMessageDebuggerClient(false),
|
||||
useMinimalDebuggerClient(false),
|
||||
nativeMobileApp(false),
|
||||
projectDataOnlyExport(false),
|
||||
fullLoadingScreen(false),
|
||||
isDevelopmentEnvironment(false),
|
||||
isInGameEdition(false),
|
||||
nonRuntimeScriptsCacheBurst(0),
|
||||
inAppTutorialMessageInPreview(""),
|
||||
inAppTutorialMessagePositionInPreview(""),
|
||||
@@ -145,6 +145,26 @@ struct PreviewExportOptions {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the (optional) events-based object to be instantiated in the scene
|
||||
* at the beginning of the previewed game.
|
||||
*/
|
||||
PreviewExportOptions &SetEventsBasedObjectType(
|
||||
const gd::String &eventsBasedObjectType_) {
|
||||
eventsBasedObjectType = eventsBasedObjectType_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the (optional) events-based object variant to be instantiated in the scene
|
||||
* at the beginning of the previewed game.
|
||||
*/
|
||||
PreviewExportOptions &SetEventsBasedObjectVariantName(
|
||||
const gd::String &eventsBasedObjectVariantName_) {
|
||||
eventsBasedObjectVariantName = eventsBasedObjectVariantName_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the hash associated to an include file. Useful for the preview
|
||||
* hot-reload, to know if a file changed.
|
||||
@@ -156,11 +176,34 @@ struct PreviewExportOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export should only export the project data, not
|
||||
* exporting events code.
|
||||
* \brief Set if the exported folder should be cleared befor the export.
|
||||
*/
|
||||
PreviewExportOptions &SetProjectDataOnlyExport(bool enable) {
|
||||
projectDataOnlyExport = enable;
|
||||
PreviewExportOptions &SetShouldClearExportFolder(bool enable) {
|
||||
shouldClearExportFolder = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the `ProjectData` must be reloaded.
|
||||
*/
|
||||
PreviewExportOptions &SetShouldReloadProjectData(bool enable) {
|
||||
shouldReloadProjectData = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if GDJS libraries must be reloaded.
|
||||
*/
|
||||
PreviewExportOptions &SetShouldReloadLibraries(bool enable) {
|
||||
shouldReloadLibraries = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export should export events code.
|
||||
*/
|
||||
PreviewExportOptions &SetShouldGenerateScenesEventsCode(bool enable) {
|
||||
shouldGenerateScenesEventsCode = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -182,6 +225,40 @@ struct PreviewExportOptions {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set if the export is made for being edited in the editor.
|
||||
*/
|
||||
PreviewExportOptions &SetIsInGameEdition(bool enable) {
|
||||
isInGameEdition = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the in-game editor identifier.
|
||||
*/
|
||||
PreviewExportOptions &SetEditorId(const gd::String &editorId_) {
|
||||
editorId = editorId_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the camera state to use in the in-game editor.
|
||||
*/
|
||||
PreviewExportOptions &
|
||||
SetEditorCameraState3D(const gd::String &cameraMode, double positionX,
|
||||
double positionY, double positionZ,
|
||||
double rotationAngle, double elevationAngle,
|
||||
double distance) {
|
||||
editorCamera3DCameraMode = cameraMode;
|
||||
editorCamera3DPositionX = positionX;
|
||||
editorCamera3DPositionY = positionY;
|
||||
editorCamera3DPositionZ = positionZ;
|
||||
editorCamera3DRotationAngle = rotationAngle;
|
||||
editorCamera3DElevationAngle = elevationAngle;
|
||||
editorCamera3DDistance = distance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief If set to a non zero value, the exported script URLs will have an
|
||||
* extra search parameter added (with the given value) to ensure browser cache
|
||||
@@ -294,6 +371,8 @@ struct PreviewExportOptions {
|
||||
bool useMinimalDebuggerClient;
|
||||
gd::String layoutName;
|
||||
gd::String externalLayoutName;
|
||||
gd::String eventsBasedObjectType;
|
||||
gd::String eventsBasedObjectVariantName;
|
||||
gd::String fallbackAuthorUsername;
|
||||
gd::String fallbackAuthorId;
|
||||
gd::String playerId;
|
||||
@@ -303,9 +382,21 @@ struct PreviewExportOptions {
|
||||
gd::String inAppTutorialMessagePositionInPreview;
|
||||
bool nativeMobileApp;
|
||||
std::map<gd::String, int> includeFileHashes;
|
||||
bool projectDataOnlyExport;
|
||||
bool shouldClearExportFolder = true;
|
||||
bool shouldReloadProjectData = true;
|
||||
bool shouldReloadLibraries = true;
|
||||
bool shouldGenerateScenesEventsCode = true;
|
||||
bool fullLoadingScreen;
|
||||
bool isDevelopmentEnvironment;
|
||||
bool isInGameEdition;
|
||||
gd::String editorId;
|
||||
gd::String editorCamera3DCameraMode;
|
||||
double editorCamera3DPositionX = 0;
|
||||
double editorCamera3DPositionY = 0;
|
||||
double editorCamera3DPositionZ = 0;
|
||||
double editorCamera3DRotationAngle = 0;
|
||||
double editorCamera3DElevationAngle = 0;
|
||||
double editorCamera3DDistance = 0;
|
||||
unsigned int nonRuntimeScriptsCacheBurst;
|
||||
gd::String electronRemoteRequirePath;
|
||||
gd::String gdevelopResourceToken;
|
||||
@@ -379,23 +470,51 @@ class ExporterHelper {
|
||||
const gd::String &GetLastError() const { return lastError; };
|
||||
|
||||
/**
|
||||
* \brief Export a project to JSON
|
||||
* \brief Export a project without its events and options to 2 JS variables
|
||||
*
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param project The project to be exported.
|
||||
* \param filename The filename where export the project
|
||||
* \param runtimeGameOptions The content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions \return Empty string if everything is ok,
|
||||
* in gdjs.runtimeGameOptions
|
||||
*
|
||||
* \return Empty string if everything is ok,
|
||||
* description of the error otherwise.
|
||||
*/
|
||||
static gd::String ExportProjectData(
|
||||
gd::AbstractFileSystem &fs,
|
||||
gd::Project &project,
|
||||
gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&layersUsedResources);
|
||||
gd::AbstractFileSystem &fs, gd::Project &project, gd::String filename,
|
||||
const gd::SerializerElement &runtimeGameOptions, bool isInGameEdition);
|
||||
|
||||
/**
|
||||
* \brief Serialize a project without its events to JSON
|
||||
*
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param project The project to be exported.
|
||||
* \param options The content of the extra configuration
|
||||
* \param projectDataElement The element where the project data is serialized
|
||||
*/
|
||||
static void SerializeProjectData(gd::AbstractFileSystem &fs,
|
||||
const gd::Project &project,
|
||||
const PreviewExportOptions &options,
|
||||
gd::SerializerElement &projectDataElement);
|
||||
|
||||
/**
|
||||
* \brief Serialize the content of the extra configuration to store
|
||||
* in gdjs.runtimeGameOptions to JSON
|
||||
*
|
||||
* \param fs The abstract file system to use to write the file
|
||||
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
|
||||
* \param options The content of the extra configuration
|
||||
* \param includesFiles The list of scripts files - useful for hot-reloading
|
||||
* \param runtimeGameOptionsElement The element where the game options are
|
||||
* serialized
|
||||
*/
|
||||
static void
|
||||
SerializeRuntimeGameOptions(gd::AbstractFileSystem &fs,
|
||||
const gd::String &gdjsRoot,
|
||||
const PreviewExportOptions &options,
|
||||
std::vector<gd::String> &includesFiles,
|
||||
gd::SerializerElement &runtimeGameOptionsElement);
|
||||
|
||||
/**
|
||||
* \brief Copy all the resources of the project to to the export directory,
|
||||
@@ -416,6 +535,7 @@ class ExporterHelper {
|
||||
*/
|
||||
void AddLibsInclude(bool pixiRenderers,
|
||||
bool pixiInThreeRenderers,
|
||||
bool isInGameEdition,
|
||||
bool includeWebsocketDebuggerClient,
|
||||
bool includeWindowMessageDebuggerClient,
|
||||
bool includeMinimalDebuggerClient,
|
||||
@@ -453,7 +573,7 @@ class ExporterHelper {
|
||||
* includesFiles A reference to a vector that will be filled with JS files to
|
||||
* be exported along with the project. ( including "codeX.js" files ).
|
||||
*/
|
||||
bool ExportEventsCode(
|
||||
bool ExportScenesEventsCode(
|
||||
const gd::Project &project,
|
||||
gd::String outputDir,
|
||||
std::vector<gd::String> &includesFiles,
|
||||
@@ -578,14 +698,20 @@ class ExporterHelper {
|
||||
* a browser pointing to the preview.
|
||||
*
|
||||
* \param options The options to generate the preview.
|
||||
* \param includesFiles The list of scripts files - useful for hot-reloading
|
||||
*/
|
||||
bool ExportProjectForPixiPreview(const PreviewExportOptions &options);
|
||||
bool ExportProjectForPixiPreview(const PreviewExportOptions &options,
|
||||
std::vector<gd::String> &includesFiles);
|
||||
|
||||
/**
|
||||
* \brief Given an include file, returns the name of the file to reference
|
||||
* in the exported game.
|
||||
*
|
||||
* \param fs The abstract file system to use
|
||||
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
|
||||
*/
|
||||
gd::String GetExportedIncludeFilename(
|
||||
static gd::String GetExportedIncludeFilename(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst = 0);
|
||||
|
||||
/**
|
||||
@@ -612,11 +738,36 @@ class ExporterHelper {
|
||||
///< be then copied to the final output directory.
|
||||
|
||||
private:
|
||||
static void SerializeUsedResources(
|
||||
gd::SerializerElement &rootElement,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&layersUsedResources);
|
||||
static void
|
||||
SerializeUsedResources(gd::SerializerElement &rootElement,
|
||||
std::set<gd::String> &projectUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&layersUsedResources,
|
||||
std::unordered_map<gd::String, std::set<gd::String>>
|
||||
&eventsBasedObjectVariantsUsedResources);
|
||||
|
||||
/**
|
||||
* \brief Stript a project and serialize it to JSON
|
||||
*
|
||||
* \param project The project to be exported.
|
||||
*/
|
||||
static void StriptAndSerializeProjectData(gd::Project &project,
|
||||
gd::SerializerElement &rootElement,
|
||||
bool isInGameEdition);
|
||||
|
||||
/**
|
||||
* \brief Add additional resources that are used by the in-game editor to the
|
||||
* project.
|
||||
*
|
||||
* \param project The project to be exported where resource declarations are
|
||||
* added.
|
||||
*
|
||||
* \param projectUsedResources The list of resource to be loaded
|
||||
* globally by the runtime.
|
||||
*/
|
||||
static void
|
||||
AddInGameEditorResources(gd::Project &project,
|
||||
std::set<gd::String> &projectUsedResources);
|
||||
};
|
||||
|
||||
} // namespace gdjs
|
||||
|
@@ -13,7 +13,7 @@ namespace gdjs {
|
||||
export type CustomObjectConfiguration = ObjectConfiguration & {
|
||||
animatable?: SpriteAnimationData[];
|
||||
variant: string;
|
||||
childrenContent: { [objectName: string]: ObjectConfiguration & any };
|
||||
childrenContent?: { [objectName: string]: ObjectConfiguration & any };
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
};
|
||||
|
||||
@@ -118,37 +118,19 @@ namespace gdjs {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventsBasedObjectData.defaultVariant) {
|
||||
eventsBasedObjectData.defaultVariant = {
|
||||
...eventsBasedObjectData,
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
eventsBasedObjectData.defaultVariant.instances.length == 0;
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
if (
|
||||
customObjectData.variant &&
|
||||
!isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
) {
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
variantIndex < eventsBasedObjectData.variants.length;
|
||||
variantIndex++
|
||||
) {
|
||||
const variantData = eventsBasedObjectData.variants[variantIndex];
|
||||
if (variantData.name === customObjectData.variant) {
|
||||
usedVariantData = variantData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const usedVariantData: EventsBasedObjectVariantData | null =
|
||||
this.getRuntimeScene()
|
||||
.getGame()
|
||||
.getEventsBasedObjectVariantData(
|
||||
customObjectData.type,
|
||||
customObjectData.variant
|
||||
);
|
||||
if (!usedVariantData) {
|
||||
// This can't actually happen.
|
||||
logger.error(
|
||||
`Unknown variant "${customObjectData.variant}" for object "${customObjectData.type}".`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._isInnerAreaFollowingParentSize =
|
||||
@@ -178,8 +160,7 @@ namespace gdjs {
|
||||
override reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
|
||||
super.reinitialize(objectData);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(objectData);
|
||||
this._reinitializeContentFromObjectData(objectData);
|
||||
|
||||
// When changing the variant, the instance is like a new instance.
|
||||
// We call again `onCreated` at the end, like done by the constructor
|
||||
@@ -187,6 +168,14 @@ namespace gdjs {
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
private _reinitializeContentFromObjectData(
|
||||
objectData: ObjectData & CustomObjectConfiguration
|
||||
) {
|
||||
this._reinitializeRenderer();
|
||||
this._instanceContainer._unloadContent();
|
||||
this._initializeFromObjectData(objectData);
|
||||
}
|
||||
|
||||
override updateFromObjectData(
|
||||
oldObjectData: ObjectData & CustomObjectConfiguration,
|
||||
newObjectData: ObjectData & CustomObjectConfiguration
|
||||
@@ -214,8 +203,7 @@ namespace gdjs {
|
||||
this._instanceContainer._initialInnerArea.max[1] !==
|
||||
this._innerArea.max[1]);
|
||||
|
||||
this._reinitializeRenderer();
|
||||
this._initializeFromObjectData(newObjectData);
|
||||
this._reinitializeContentFromObjectData(newObjectData);
|
||||
|
||||
// The generated code calls the onCreated super implementation at the end.
|
||||
this.onCreated();
|
||||
@@ -311,15 +299,13 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
override onDeletedFromScene(): void {
|
||||
@@ -658,6 +644,20 @@ namespace gdjs {
|
||||
return this._unrotatedAABB.max[1];
|
||||
}
|
||||
|
||||
getOriginalWidth(): float {
|
||||
return (
|
||||
this._instanceContainer.getInitialUnrotatedViewportMaxX() -
|
||||
this._instanceContainer.getInitialUnrotatedViewportMinX()
|
||||
);
|
||||
}
|
||||
|
||||
getOriginalHeight(): float {
|
||||
return (
|
||||
this._instanceContainer.getInitialUnrotatedViewportMaxY() -
|
||||
this._instanceContainer.getInitialUnrotatedViewportMinY()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the internal width of the object according to its children.
|
||||
*/
|
||||
|
@@ -16,6 +16,7 @@ namespace gdjs {
|
||||
_parent: gdjs.RuntimeInstanceContainer;
|
||||
/** The object that is built with the instances of this container. */
|
||||
_customObject: gdjs.CustomRuntimeObject;
|
||||
// TODO Remove this attribute
|
||||
_isLoaded: boolean = false;
|
||||
/**
|
||||
* The default size defined by users in the custom object initial instances editor.
|
||||
@@ -46,15 +47,28 @@ namespace gdjs {
|
||||
this._debuggerRenderer = new gdjs.DebuggerRenderer(this);
|
||||
}
|
||||
|
||||
// TODO `_layers` and `_orderedLayers` should not be used directly.
|
||||
|
||||
addLayer(layerData: LayerData) {
|
||||
if (this._layers.containsKey(layerData.name)) {
|
||||
return;
|
||||
}
|
||||
// This code is duplicated with `RuntimeScene.addLayer` because it avoids
|
||||
// to expose a method to build a layer.
|
||||
const layer = new gdjs.RuntimeCustomObjectLayer(layerData, this);
|
||||
this._layers.put(layerData.name, layer);
|
||||
this._orderedLayers.push(layer);
|
||||
}
|
||||
|
||||
_unloadContent() {
|
||||
this.onDeletedFromScene(this._parent);
|
||||
// At this point, layer renderers are already removed by
|
||||
// `CustomRuntimeObject._reinitializeRenderer`.
|
||||
// It's not great to do this here, but it allows to keep it private.
|
||||
this._layers.clear();
|
||||
this._orderedLayers.length = 0;
|
||||
}
|
||||
|
||||
createObject(objectName: string): gdjs.RuntimeObject | null {
|
||||
const result = super.createObject(objectName);
|
||||
this._customObject.onChildrenLocationChanged();
|
||||
@@ -63,21 +77,14 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Load the container from the given initial configuration.
|
||||
* @param customObjectData An object containing the container data.
|
||||
* @param customObjectData An object containing the parent object data.
|
||||
* @param eventsBasedObjectVariantData An object containing the container data.
|
||||
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
|
||||
*/
|
||||
loadFrom(
|
||||
customObjectData: ObjectData & CustomObjectConfiguration,
|
||||
eventsBasedObjectVariantData: EventsBasedObjectVariantData
|
||||
) {
|
||||
if (this._isLoaded) {
|
||||
this.onDeletedFromScene(this._parent);
|
||||
}
|
||||
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
this._setOriginalInnerArea(eventsBasedObjectVariantData);
|
||||
|
||||
// Registering objects
|
||||
@@ -87,19 +94,21 @@ namespace gdjs {
|
||||
++i
|
||||
) {
|
||||
const childObjectData = eventsBasedObjectVariantData.objects[i];
|
||||
// The children configuration override only applies to the default variant.
|
||||
if (
|
||||
customObjectData.childrenContent &&
|
||||
(!eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration)
|
||||
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
|
||||
customObjectData,
|
||||
eventsBasedObjectVariantData
|
||||
)
|
||||
) {
|
||||
this.registerObject({
|
||||
...childObjectData,
|
||||
// The custom object overrides its events-based object configuration.
|
||||
// The custom object overrides its variant configuration with
|
||||
// a legacy children configuration.
|
||||
...customObjectData.childrenContent[childObjectData.name],
|
||||
});
|
||||
} else {
|
||||
// The custom object follows its events-based object configuration.
|
||||
// The custom object follows its variant configuration.
|
||||
this.registerObject(childObjectData);
|
||||
}
|
||||
}
|
||||
@@ -154,6 +163,28 @@ namespace gdjs {
|
||||
this._isLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the custom object has a children configuration overriding that
|
||||
* should be used instead of the variant's objects configurations.
|
||||
* @param customObjectData An object containing the parent object data.
|
||||
* @param eventsBasedObjectVariantData An object containing the container data.
|
||||
* @returns
|
||||
*/
|
||||
static hasChildrenConfigurationOverriding(
|
||||
customObjectData: CustomObjectConfiguration,
|
||||
eventsBasedObjectVariantData: EventsBasedObjectVariantData
|
||||
): boolean {
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
!eventsBasedObjectVariantData.name &&
|
||||
eventsBasedObjectVariantData.instances.length == 0;
|
||||
|
||||
// The children configuration override only applies to the default variant.
|
||||
return customObjectData.childrenContent
|
||||
? !eventsBasedObjectVariantData.name ||
|
||||
isForcedToOverrideEventsBasedObjectChildrenConfiguration
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize `_initialInnerArea` if it doesn't exist.
|
||||
* `_initialInnerArea` is shared by every instance to save memory.
|
||||
@@ -161,7 +192,10 @@ namespace gdjs {
|
||||
private _setOriginalInnerArea(
|
||||
eventsBasedObjectData: EventsBasedObjectVariantData
|
||||
) {
|
||||
if (eventsBasedObjectData.instances.length > 0) {
|
||||
if (
|
||||
eventsBasedObjectData.instances.length > 0 ||
|
||||
this.getGame().isInGameEdition()
|
||||
) {
|
||||
if (!eventsBasedObjectData._initialInnerArea) {
|
||||
eventsBasedObjectData._initialInnerArea = {
|
||||
min: [
|
||||
@@ -341,6 +375,12 @@ namespace gdjs {
|
||||
return this._initialInnerArea ? this._initialInnerArea.max[1] : 0;
|
||||
}
|
||||
|
||||
_getInitialInnerAreaDepth(): float {
|
||||
return this._initialInnerArea
|
||||
? this._initialInnerArea.max[2] - this._initialInnerArea.min[2]
|
||||
: 0;
|
||||
}
|
||||
|
||||
getViewportWidth(): float {
|
||||
return this._customObject.getUnscaledWidth();
|
||||
}
|
||||
|
4068
GDJS/Runtime/InGameEditor/InGameEditor.ts
Normal file
4068
GDJS/Runtime/InGameEditor/InGameEditor.ts
Normal file
File diff suppressed because it is too large
Load Diff
BIN
GDJS/Runtime/InGameEditor/Resources/primitivedrawingicon.png
Normal file
BIN
GDJS/Runtime/InGameEditor/Resources/primitivedrawingicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 394 B |
@@ -164,14 +164,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const loadedThreeModel = this._loadedThreeModels.get(resourceData);
|
||||
const loadedThreeModel = this._loadedThreeModels.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (loadedThreeModel) {
|
||||
loadedThreeModel.scene.clear();
|
||||
this._loadedThreeModels.delete(resourceData);
|
||||
}
|
||||
|
||||
const downloadedArrayBuffer =
|
||||
this._downloadedArrayBuffers.get(resourceData);
|
||||
const downloadedArrayBuffer = this._downloadedArrayBuffers.getFromName(
|
||||
resourceData.name
|
||||
);
|
||||
if (downloadedArrayBuffer) {
|
||||
this._downloadedArrayBuffers.delete(resourceData);
|
||||
}
|
||||
|
@@ -282,6 +282,27 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
async loadResources(
|
||||
resourceNames: Array<string>,
|
||||
onProgress: (loadingCount: integer, totalCount: integer) => void
|
||||
): Promise<void> {
|
||||
let loadedCount = 0;
|
||||
await processAndRetryIfNeededWithPromisePool(
|
||||
resourceNames,
|
||||
maxForegroundConcurrency,
|
||||
maxAttempt,
|
||||
async (resourceName) => {
|
||||
const resource = this._resources.get(resourceName);
|
||||
if (resource) {
|
||||
await this._loadResource(resource);
|
||||
await this._processResource(resource);
|
||||
}
|
||||
loadedCount++;
|
||||
onProgress(loadedCount, this._resources.size);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the resources that are needed to launch the first scene.
|
||||
*/
|
||||
@@ -551,6 +572,23 @@ namespace gdjs {
|
||||
// TODO: mark the scene as unloaded so it's not automatically loaded again eagerly.
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called when hot-reloading resources.
|
||||
*/
|
||||
unloadAllResources(): void {
|
||||
debugLogger.log(`Unloading of all resources was requested.`);
|
||||
for (const resource of this._resources.values()) {
|
||||
const resourceManager = this._resourceManagersMap.get(resource.kind);
|
||||
if (resourceManager) {
|
||||
resourceManager.unloadResource(resource);
|
||||
}
|
||||
}
|
||||
for (const sceneLoadingState of this._sceneLoadingStates.values()) {
|
||||
sceneLoadingState.status = 'not-loaded';
|
||||
}
|
||||
debugLogger.log(`Unloading of all resources finished.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a given scene at the end of the queue.
|
||||
*
|
||||
@@ -652,6 +690,9 @@ namespace gdjs {
|
||||
* the resource (this can be for example a token needed to access the resource).
|
||||
*/
|
||||
getFullUrl(url: string) {
|
||||
if (this._runtimeGame.isInGameEdition()) {
|
||||
url = addSearchParameterToUrl(url, 'cache', '' + Date.now());
|
||||
}
|
||||
const { gdevelopResourceToken } = this._runtimeGame._options;
|
||||
if (!gdevelopResourceToken) return url;
|
||||
|
||||
|
@@ -575,6 +575,14 @@ namespace gdjs {
|
||||
this._cacheOrClearRemovedInstances();
|
||||
}
|
||||
|
||||
_updateObjectsForInGameEditor() {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const obj = allInstancesList[i];
|
||||
obj.update(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call each behavior stepPostEvents method.
|
||||
*/
|
||||
@@ -612,7 +620,7 @@ namespace gdjs {
|
||||
getObjects(name: string): gdjs.RuntimeObject[] | undefined {
|
||||
if (!this._instances.containsKey(name)) {
|
||||
logger.info(
|
||||
'RuntimeScene.getObjects: No instances called "' +
|
||||
'RuntimeInstanceContainer.getObjects: No instances called "' +
|
||||
name +
|
||||
'"! Adding it.'
|
||||
);
|
||||
|
@@ -55,13 +55,14 @@ namespace gdjs {
|
||||
_timeScale: float = 1;
|
||||
_defaultZOrder: integer = 0;
|
||||
_hidden: boolean;
|
||||
_initialEffectsData: Array<EffectData>;
|
||||
_initialLayerData: LayerData;
|
||||
|
||||
// TODO EBO Don't store scene layer related data in layers used by custom objects.
|
||||
// (both these 3D settings and the lighting layer properties below).
|
||||
_initialCamera3DFieldOfView: float;
|
||||
_initialCamera3DFarPlaneDistance: float;
|
||||
_initialCamera3DNearPlaneDistance: float;
|
||||
_initialCamera2DPlaneMaxDrawingDistance: float;
|
||||
|
||||
_runtimeScene: gdjs.RuntimeInstanceContainer;
|
||||
_effectsManager: gdjs.EffectsManager;
|
||||
@@ -94,7 +95,9 @@ namespace gdjs {
|
||||
layerData.camera3DNearPlaneDistance || 0.1;
|
||||
this._initialCamera3DFarPlaneDistance =
|
||||
layerData.camera3DFarPlaneDistance || 2000;
|
||||
this._initialEffectsData = layerData.effects || [];
|
||||
this._initialCamera2DPlaneMaxDrawingDistance =
|
||||
layerData.camera2DPlaneMaxDrawingDistance || 5000;
|
||||
this._initialLayerData = layerData;
|
||||
this._runtimeScene = instanceContainer;
|
||||
this._effectsManager = instanceContainer.getGame().getEffectsManager();
|
||||
this._isLightingLayer = layerData.isLightingLayer;
|
||||
@@ -491,6 +494,9 @@ namespace gdjs {
|
||||
getInitialCamera3DFarPlaneDistance(): float {
|
||||
return this._initialCamera3DFarPlaneDistance;
|
||||
}
|
||||
getInitialCamera2DPlaneMaxDrawingDistance(): float {
|
||||
return this._initialCamera2DPlaneMaxDrawingDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the initial effects data for the layer. Only to
|
||||
@@ -498,7 +504,7 @@ namespace gdjs {
|
||||
* @deprecated
|
||||
*/
|
||||
getInitialEffectsData(): EffectData[] {
|
||||
return this._initialEffectsData;
|
||||
return this._initialLayerData.effects || [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -107,8 +107,12 @@ namespace gdjs {
|
||||
exception: Error,
|
||||
runtimeGame: gdjs.RuntimeGame
|
||||
) => {
|
||||
const sceneNames = runtimeGame.getSceneStack().getAllSceneNames();
|
||||
const currentScene = runtimeGame.getSceneStack().getCurrentScene();
|
||||
const currentScene = runtimeGame.isInGameEdition()
|
||||
? runtimeGame.getInGameEditor()?.getCurrentScene()
|
||||
: runtimeGame.getSceneStack().getCurrentScene();
|
||||
const sceneNames = runtimeGame.isInGameEdition()
|
||||
? [currentScene?.getName()]
|
||||
: runtimeGame.getSceneStack().getAllSceneNames();
|
||||
return {
|
||||
type: 'javascript-uncaught-exception',
|
||||
exception,
|
||||
@@ -116,6 +120,7 @@ namespace gdjs {
|
||||
playerId: runtimeGame.getPlayerId(),
|
||||
sessionId: runtimeGame.getSessionId(),
|
||||
isPreview: runtimeGame.isPreview(),
|
||||
isInGameEdition: runtimeGame.isInGameEdition(),
|
||||
gdevelop: {
|
||||
previewContext: runtimeGame.getAdditionalOptions().previewContext,
|
||||
isNativeMobileApp: runtimeGame.getAdditionalOptions().nativeMobileApp,
|
||||
@@ -233,42 +238,250 @@ namespace gdjs {
|
||||
protected handleCommand(data: any) {
|
||||
const that = this;
|
||||
const runtimeGame = this._runtimegame;
|
||||
const inGameEditor = runtimeGame.getInGameEditor();
|
||||
if (!data || !data.command) {
|
||||
// Not a command that's meant to be handled by the debugger, return silently to
|
||||
// avoid polluting the console.
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.command === 'play') {
|
||||
runtimeGame.pause(false);
|
||||
} else if (data.command === 'pause') {
|
||||
runtimeGame.pause(true);
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'refresh') {
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'set') {
|
||||
that.set(data.path, data.newValue);
|
||||
} else if (data.command === 'call') {
|
||||
that.call(data.path, data.args);
|
||||
} else if (data.command === 'profiler.start') {
|
||||
runtimeGame.startCurrentSceneProfiler(function (stoppedProfiler) {
|
||||
that.sendProfilerOutput(
|
||||
stoppedProfiler.getFramesAverageMeasures(),
|
||||
stoppedProfiler.getStats()
|
||||
try {
|
||||
if (data.command === 'play') {
|
||||
runtimeGame.pause(false);
|
||||
} else if (data.command === 'pause') {
|
||||
runtimeGame.pause(true);
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'refresh') {
|
||||
that.sendRuntimeGameDump();
|
||||
} else if (data.command === 'getStatus') {
|
||||
that.sendRuntimeGameStatus();
|
||||
} else if (data.command === 'set') {
|
||||
that.set(data.path, data.newValue);
|
||||
} else if (data.command === 'call') {
|
||||
that.call(data.path, data.args);
|
||||
} else if (data.command === 'profiler.start') {
|
||||
runtimeGame.startCurrentSceneProfiler(function (stoppedProfiler) {
|
||||
that.sendProfilerOutput(
|
||||
stoppedProfiler.getFramesAverageMeasures(),
|
||||
stoppedProfiler.getStats()
|
||||
);
|
||||
that.sendProfilerStopped();
|
||||
});
|
||||
that.sendProfilerStarted();
|
||||
} else if (data.command === 'profiler.stop') {
|
||||
runtimeGame.stopCurrentSceneProfiler();
|
||||
} else if (data.command === 'hotReload') {
|
||||
const runtimeGameOptions: RuntimeGameOptions =
|
||||
data.payload.runtimeGameOptions;
|
||||
if (
|
||||
(runtimeGameOptions.initialRuntimeGameStatus?.isInGameEdition ||
|
||||
false) === runtimeGame.isInGameEdition()
|
||||
) {
|
||||
this._hasLoggedUncaughtException = false;
|
||||
that._hotReloader
|
||||
.hotReload({
|
||||
projectData: data.payload.projectData,
|
||||
runtimeGameOptions,
|
||||
shouldReloadResources:
|
||||
data.payload.shouldReloadResources || false,
|
||||
})
|
||||
.then((logs) => {
|
||||
that.sendHotReloaderLogs(logs);
|
||||
});
|
||||
}
|
||||
} else if (data.command === 'hotReloadObjects') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
that._hotReloader.hotReloadRuntimeSceneObjects(
|
||||
data.payload.updatedObjects,
|
||||
editedInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'hotReloadLayers') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
inGameEditor.onLayersDataChange(
|
||||
data.payload.layers,
|
||||
data.payload.areEffectsHidden
|
||||
);
|
||||
runtimeGame.getGameData().areEffectsHiddenInEditor =
|
||||
data.payload.areEffectsHidden;
|
||||
that._hotReloader.hotReloadRuntimeSceneLayers(
|
||||
data.payload.layers,
|
||||
editedInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'setBackgroundColor') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
const backgroundColor = data.payload.backgroundColor;
|
||||
if (
|
||||
backgroundColor &&
|
||||
editedInstanceContainer instanceof gdjs.RuntimeScene
|
||||
) {
|
||||
const sceneData = runtimeGame.getSceneData(
|
||||
editedInstanceContainer.getScene().getName()
|
||||
);
|
||||
if (sceneData) {
|
||||
editedInstanceContainer._backgroundColor =
|
||||
gdjs.rgbToHexNumber(
|
||||
backgroundColor[0],
|
||||
backgroundColor[1],
|
||||
backgroundColor[2]
|
||||
);
|
||||
sceneData.r = backgroundColor[0];
|
||||
sceneData.v = backgroundColor[1];
|
||||
sceneData.b = backgroundColor[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'hotReloadAllInstances') {
|
||||
if (inGameEditor) {
|
||||
const editedInstanceContainer =
|
||||
inGameEditor.getEditedInstanceContainer();
|
||||
if (editedInstanceContainer) {
|
||||
that._hotReloader.hotReloadRuntimeInstances(
|
||||
inGameEditor.getEditedInstanceDataList(),
|
||||
data.payload.instances,
|
||||
editedInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'switchForInGameEdition') {
|
||||
if (!this._runtimegame.isInGameEdition()) return;
|
||||
|
||||
const sceneName = data.sceneName || null;
|
||||
const eventsBasedObjectType = data.eventsBasedObjectType || null;
|
||||
if (!sceneName && !eventsBasedObjectType) {
|
||||
logger.warn(
|
||||
'No scene name specified, switchForInGameEdition aborted'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (inGameEditor) {
|
||||
const wasPaused = this._runtimegame.isPaused();
|
||||
this._runtimegame.pause(true);
|
||||
inGameEditor.switchToSceneOrVariant(
|
||||
data.editorId || null,
|
||||
sceneName,
|
||||
data.externalLayoutName || null,
|
||||
eventsBasedObjectType,
|
||||
data.eventsBasedObjectVariantName || null,
|
||||
data.editorCamera3D || null
|
||||
);
|
||||
this._runtimegame.pause(wasPaused);
|
||||
}
|
||||
} else if (data.command === 'setVisibleStatus') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setVisibleStatus(data.visible);
|
||||
}
|
||||
} else if (data.command === 'updateInstances') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.reloadInstances(data.payload.instances);
|
||||
}
|
||||
} else if (data.command === 'addInstances') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.addInstances(data.payload.instances);
|
||||
inGameEditor.setSelectedObjects(
|
||||
data.payload.instances.map((instance) => instance.persistentUuid)
|
||||
);
|
||||
if (data.payload.moveUnderCursor) {
|
||||
inGameEditor.moveSelectionUnderCursor();
|
||||
}
|
||||
}
|
||||
} else if (data.command === 'deleteSelection') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.deleteSelection();
|
||||
}
|
||||
} else if (data.command === 'dragNewInstance') {
|
||||
const gameCoords = runtimeGame
|
||||
.getRenderer()
|
||||
.convertPageToGameCoords(data.x, data.y);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseMove(gameCoords[0], gameCoords[1]);
|
||||
|
||||
if (inGameEditor)
|
||||
inGameEditor.dragNewInstance({
|
||||
name: data.name,
|
||||
dropped: data.dropped,
|
||||
isAltPressed: data.isAltPressed,
|
||||
});
|
||||
} else if (data.command === 'cancelDragNewInstance') {
|
||||
if (inGameEditor) inGameEditor.cancelDragNewInstance();
|
||||
} else if (data.command === 'setInstancesEditorSettings') {
|
||||
if (inGameEditor)
|
||||
inGameEditor.updateInstancesEditorSettings(
|
||||
data.payload.instancesEditorSettings
|
||||
);
|
||||
} else if (data.command === 'zoomToInitialPosition') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomToInitialPosition(data.payload.visibleScreenArea);
|
||||
}
|
||||
} else if (data.command === 'zoomToFitContent') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomToFitContent(data.payload.visibleScreenArea);
|
||||
}
|
||||
} else if (data.command === 'setSelectedLayer') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setSelectedLayerName(data.payload.layerName);
|
||||
}
|
||||
} else if (data.command === 'zoomToFitSelection') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomToFitSelection(data.payload.visibleScreenArea);
|
||||
}
|
||||
} else if (data.command === 'zoomBy') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.zoomBy(data.payload.zoomFactor);
|
||||
}
|
||||
} else if (data.command === 'setZoom') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setZoom(data.payload.zoom);
|
||||
}
|
||||
} else if (data.command === 'setSelectedInstances') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.setSelectedObjects(data.payload.instanceUuids);
|
||||
}
|
||||
} else if (data.command === 'centerViewOnLastSelectedInstance') {
|
||||
if (inGameEditor) {
|
||||
// TODO: use data.payload.visibleScreenArea
|
||||
inGameEditor.centerViewOnLastSelectedInstance();
|
||||
}
|
||||
} else if (data.command === 'updateInnerArea') {
|
||||
if (inGameEditor) {
|
||||
inGameEditor.updateInnerArea(
|
||||
data.payload.areaMinX,
|
||||
data.payload.areaMinY,
|
||||
data.payload.areaMinZ,
|
||||
data.payload.areaMaxX,
|
||||
data.payload.areaMaxY,
|
||||
data.payload.areaMaxZ
|
||||
);
|
||||
}
|
||||
} else if (data.command === 'getSelectionAABB') {
|
||||
if (inGameEditor) {
|
||||
this.sendSelectionAABB(data.messageId);
|
||||
}
|
||||
} else if (data.command === 'hardReload') {
|
||||
// This usually means that the preview was modified so much that an entire reload
|
||||
// is needed, or that the runtime itself could have been modified.
|
||||
this.launchHardReload();
|
||||
} else {
|
||||
logger.info(
|
||||
'Unknown command "' + data.command + '" received by the debugger.'
|
||||
);
|
||||
that.sendProfilerStopped();
|
||||
});
|
||||
that.sendProfilerStarted();
|
||||
} else if (data.command === 'profiler.stop') {
|
||||
runtimeGame.stopCurrentSceneProfiler();
|
||||
} else if (data.command === 'hotReload') {
|
||||
that._hotReloader.hotReload().then((logs) => {
|
||||
that.sendHotReloaderLogs(logs);
|
||||
});
|
||||
} else {
|
||||
logger.info(
|
||||
'Unknown command "' + data.command + '" received by the debugger.'
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.onUncaughtException(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,9 +543,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
onUncaughtException(exception: Error): void {
|
||||
logger.error('Uncaught exception: ' + exception);
|
||||
logger.error('Uncaught exception: ', exception, exception.stack);
|
||||
|
||||
this._inGameDebugger.setUncaughtException(exception);
|
||||
const runtimeGame = this._runtimegame;
|
||||
if (!runtimeGame.isInGameEdition()) {
|
||||
this._inGameDebugger.setUncaughtException(exception);
|
||||
}
|
||||
|
||||
if (!this._hasLoggedUncaughtException) {
|
||||
// Only log an uncaught exception once, to avoid spamming the debugger server
|
||||
@@ -435,6 +651,20 @@ namespace gdjs {
|
||||
return true;
|
||||
}
|
||||
|
||||
sendRuntimeGameStatus(): void {
|
||||
const currentScene = this._runtimegame.getSceneStack().getCurrentScene();
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'status',
|
||||
payload: {
|
||||
isPaused: this._runtimegame.isPaused(),
|
||||
isInGameEdition: this._runtimegame.isInGameEdition(),
|
||||
sceneName: currentScene ? currentScene.getName() : null,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all the relevant data from the {@link RuntimeGame} instance and send it to the server.
|
||||
*/
|
||||
@@ -515,7 +745,10 @@ namespace gdjs {
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'hotReloader.logs',
|
||||
payload: logs,
|
||||
payload: {
|
||||
isInGameEdition: this._runtimegame.isInGameEdition(),
|
||||
logs,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -544,26 +777,152 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called when the game is paused.
|
||||
*/
|
||||
sendGamePaused(): void {
|
||||
sendInstanceChanges(changes: {
|
||||
isSendingBackSelectionForDefaultSize: boolean;
|
||||
updatedInstances: Array<InstanceData>;
|
||||
addedInstances: Array<InstanceData>;
|
||||
selectedInstances: Array<InstancePersistentUuidData>;
|
||||
removedInstances: Array<InstancePersistentUuidData>;
|
||||
}): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'game.paused',
|
||||
payload: null,
|
||||
command: 'updateInstances',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: changes,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback called when the game is resumed.
|
||||
*/
|
||||
sendGameResumed(): void {
|
||||
sendOpenContextMenu(cursorX: float, cursorY: float): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'game.resumed',
|
||||
payload: null,
|
||||
command: 'openContextMenu',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: { cursorX, cursorY },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendCameraState(cameraState: EditorCameraState): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'setCameraState',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: cameraState,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendUndo(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'undo',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendRedo(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'redo',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendCopy(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'copy',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendPaste(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'paste',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendCut(): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'cut',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
payload: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sendSelectionAABB(messageId: number): void {
|
||||
const inGameEditor = this._runtimegame.getInGameEditor();
|
||||
if (!inGameEditor) {
|
||||
return;
|
||||
}
|
||||
const selectionAABB = inGameEditor.getSelectionAABB();
|
||||
this._sendMessage(
|
||||
circularSafeStringify({
|
||||
command: 'selectionAABB',
|
||||
editorId: inGameEditor.getEditorId(),
|
||||
messageId,
|
||||
payload: selectionAABB
|
||||
? {
|
||||
minX: selectionAABB.min[0],
|
||||
minY: selectionAABB.min[1],
|
||||
minZ: selectionAABB.min[2],
|
||||
maxX: selectionAABB.max[0],
|
||||
maxY: selectionAABB.max[1],
|
||||
maxZ: selectionAABB.max[2],
|
||||
}
|
||||
: {
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
minZ: 0,
|
||||
maxX: 0,
|
||||
maxY: 0,
|
||||
maxZ: 0,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -587,5 +946,43 @@ namespace gdjs {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
launchHardReload(): void {
|
||||
try {
|
||||
const reloadUrl = new URL(location.href);
|
||||
|
||||
// Construct the initial status to be restored.
|
||||
const initialRuntimeGameStatus =
|
||||
this._runtimegame.getAdditionalOptions().initialRuntimeGameStatus;
|
||||
// We use empty strings to avoid `null` to become `"null"`.
|
||||
const runtimeGameStatus: RuntimeGameStatus = {
|
||||
editorId: initialRuntimeGameStatus?.editorId || '',
|
||||
isPaused: this._runtimegame.isPaused(),
|
||||
isInGameEdition: this._runtimegame.isInGameEdition(),
|
||||
sceneName: initialRuntimeGameStatus?.sceneName || '',
|
||||
injectedExternalLayoutName:
|
||||
initialRuntimeGameStatus?.injectedExternalLayoutName || '',
|
||||
skipCreatingInstancesFromScene:
|
||||
initialRuntimeGameStatus?.skipCreatingInstancesFromScene || false,
|
||||
eventsBasedObjectType:
|
||||
initialRuntimeGameStatus?.eventsBasedObjectType || '',
|
||||
eventsBasedObjectVariantName:
|
||||
initialRuntimeGameStatus?.eventsBasedObjectVariantName || '',
|
||||
editorCamera3D: this._runtimegame.getInGameEditor()?.getCameraState(),
|
||||
};
|
||||
|
||||
reloadUrl.searchParams.set(
|
||||
'runtimeGameStatus',
|
||||
JSON.stringify(runtimeGameStatus)
|
||||
);
|
||||
location.replace(reloadUrl);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
'Could not reload the game with the new initial status',
|
||||
error
|
||||
);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -144,18 +144,30 @@ namespace gdjs {
|
||||
});
|
||||
}
|
||||
|
||||
hotReload(): Promise<HotReloaderLog[]> {
|
||||
async hotReload({
|
||||
shouldReloadResources,
|
||||
projectData: newProjectData,
|
||||
runtimeGameOptions: newRuntimeGameOptions,
|
||||
}: {
|
||||
shouldReloadResources: boolean;
|
||||
projectData: ProjectData;
|
||||
runtimeGameOptions: RuntimeGameOptions;
|
||||
}): Promise<HotReloaderLog[]> {
|
||||
logger.info('Hot reload started');
|
||||
const wasPaused = this._runtimeGame.isPaused();
|
||||
this._runtimeGame.pause(true);
|
||||
this._logs = [];
|
||||
|
||||
// Save old data of the project, to be used to compute
|
||||
// the difference between the old and new project data:
|
||||
|
||||
const oldProjectData: ProjectData = gdjs.projectData;
|
||||
gdjs.projectData = newProjectData;
|
||||
|
||||
const oldScriptFiles = gdjs.runtimeGameOptions
|
||||
.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
const oldRuntimeGameOptions = gdjs.runtimeGameOptions;
|
||||
gdjs.runtimeGameOptions = newRuntimeGameOptions;
|
||||
|
||||
const oldScriptFiles =
|
||||
oldRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
|
||||
oldScriptFiles.forEach((scriptFile) => {
|
||||
this._alreadyLoadedScriptFiles[scriptFile.path] = true;
|
||||
@@ -167,76 +179,102 @@ namespace gdjs {
|
||||
gdjs.behaviorsTypes.items[behaviorTypeName];
|
||||
}
|
||||
|
||||
// Reload projectData and runtimeGameOptions stored by convention in data.js:
|
||||
return this._reloadScript('data.js').then(() => {
|
||||
const newProjectData: ProjectData = gdjs.projectData;
|
||||
if (gdjs.inAppTutorialMessage) {
|
||||
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
|
||||
this._runtimeGame,
|
||||
newRuntimeGameOptions.inAppTutorialMessageInPreview,
|
||||
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
|
||||
);
|
||||
}
|
||||
|
||||
const newRuntimeGameOptions: RuntimeGameOptions =
|
||||
gdjs.runtimeGameOptions;
|
||||
const newScriptFiles =
|
||||
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
const shouldGenerateScenesEventsCode =
|
||||
!!newRuntimeGameOptions.shouldGenerateScenesEventsCode;
|
||||
const shouldReloadLibraries =
|
||||
!!newRuntimeGameOptions.shouldReloadLibraries;
|
||||
|
||||
if (gdjs.inAppTutorialMessage) {
|
||||
gdjs.inAppTutorialMessage.displayInAppTutorialMessage(
|
||||
this._runtimeGame,
|
||||
newRuntimeGameOptions.inAppTutorialMessageInPreview,
|
||||
newRuntimeGameOptions.inAppTutorialMessagePositionInPreview || ''
|
||||
// Reload the changed scripts, which will have the side effects of re-running
|
||||
// the new scripts, potentially replacing the code of the free functions from
|
||||
// extensions (which is fine) and registering updated behaviors (which will
|
||||
// need to be re-instantiated in runtime objects).
|
||||
try {
|
||||
if (shouldReloadLibraries) {
|
||||
await this.reloadScriptFiles(
|
||||
newProjectData,
|
||||
oldScriptFiles,
|
||||
newScriptFiles,
|
||||
shouldGenerateScenesEventsCode
|
||||
);
|
||||
}
|
||||
|
||||
const newScriptFiles =
|
||||
newRuntimeGameOptions.scriptFiles as RuntimeGameOptionsScriptFile[];
|
||||
const projectDataOnlyExport =
|
||||
!!newRuntimeGameOptions.projectDataOnlyExport;
|
||||
|
||||
// Reload the changed scripts, which will have the side effects of re-running
|
||||
// the new scripts, potentially replacing the code of the free functions from
|
||||
// extensions (which is fine) and registering updated behaviors (which will
|
||||
// need to be re-instantiated in runtime objects).
|
||||
return this.reloadScriptFiles(
|
||||
newProjectData,
|
||||
oldScriptFiles,
|
||||
newScriptFiles,
|
||||
projectDataOnlyExport
|
||||
)
|
||||
.then(() => {
|
||||
const changedRuntimeBehaviors =
|
||||
this._computeChangedRuntimeBehaviors(
|
||||
oldBehaviorConstructors,
|
||||
gdjs.behaviorsTypes.items
|
||||
);
|
||||
return this._hotReloadRuntimeGame(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
changedRuntimeBehaviors,
|
||||
this._runtimeGame
|
||||
const newRuntimeGameStatus =
|
||||
newRuntimeGameOptions.initialRuntimeGameStatus;
|
||||
if (
|
||||
newRuntimeGameStatus &&
|
||||
newRuntimeGameStatus.editorId &&
|
||||
newRuntimeGameStatus.isInGameEdition
|
||||
) {
|
||||
if (shouldReloadResources) {
|
||||
// Unloading all resources will force them to be loaded again,
|
||||
// which is sufficient for ensuring they are up-to-date as
|
||||
// resources will be loaded with a 'cache bursting' parameter.
|
||||
this._runtimeGame._resourcesLoader.unloadAllResources();
|
||||
}
|
||||
// The editor don't need to hot-reload the current scene because the
|
||||
// editor always stays in the initial state.
|
||||
this._runtimeGame.setProjectData(newProjectData);
|
||||
await this._runtimeGame.loadFirstAssetsAndStartBackgroundLoading(
|
||||
newRuntimeGameStatus.sceneName || newProjectData.firstLayout,
|
||||
() => {}
|
||||
);
|
||||
const inGameEditor = this._runtimeGame.getInGameEditor();
|
||||
if (inGameEditor) {
|
||||
await inGameEditor.switchToSceneOrVariant(
|
||||
newRuntimeGameStatus.editorId || null,
|
||||
newRuntimeGameStatus.sceneName,
|
||||
newRuntimeGameStatus.injectedExternalLayoutName,
|
||||
newRuntimeGameStatus.eventsBasedObjectType,
|
||||
newRuntimeGameStatus.eventsBasedObjectVariantName,
|
||||
newRuntimeGameStatus.editorCamera3D || null
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorTarget = error.target;
|
||||
if (errorTarget instanceof HTMLScriptElement) {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message: 'Unable to reload script: ' + errorTarget.src,
|
||||
});
|
||||
} else {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message:
|
||||
'Unexpected error happened while hot-reloading: ' +
|
||||
error.message +
|
||||
'\n' +
|
||||
error.stack,
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
logger.info(
|
||||
'Hot reload finished with logs:',
|
||||
this._logs.map((log) => '\n' + log.kind + ': ' + log.message)
|
||||
);
|
||||
this._runtimeGame.pause(false);
|
||||
return this._logs;
|
||||
}
|
||||
} else {
|
||||
const changedRuntimeBehaviors = this._computeChangedRuntimeBehaviors(
|
||||
oldBehaviorConstructors,
|
||||
gdjs.behaviorsTypes.items
|
||||
);
|
||||
await this._hotReloadRuntimeGame(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
changedRuntimeBehaviors,
|
||||
this._runtimeGame
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorTarget = error.target;
|
||||
if (errorTarget instanceof HTMLScriptElement) {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message: 'Unable to reload script: ' + errorTarget.src,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this._logs.push({
|
||||
kind: 'fatal',
|
||||
message:
|
||||
'Unexpected error happened while hot-reloading: ' +
|
||||
error.message +
|
||||
'\n' +
|
||||
error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'Hot reload finished with logs:',
|
||||
this._logs.map((log) => '\n' + log.kind + ': ' + log.message)
|
||||
);
|
||||
this._runtimeGame.pause(wasPaused);
|
||||
return this._logs;
|
||||
}
|
||||
|
||||
_computeChangedRuntimeBehaviors(
|
||||
@@ -281,12 +319,12 @@ namespace gdjs {
|
||||
newProjectData: ProjectData,
|
||||
oldScriptFiles: RuntimeGameOptionsScriptFile[],
|
||||
newScriptFiles: RuntimeGameOptionsScriptFile[],
|
||||
projectDataOnlyExport: boolean
|
||||
shouldGenerateScenesEventsCode: boolean
|
||||
): Promise<void[]> {
|
||||
const reloadPromises: Array<Promise<void>> = [];
|
||||
|
||||
// Reload events, only if they were exported.
|
||||
if (!projectDataOnlyExport) {
|
||||
if (shouldGenerateScenesEventsCode) {
|
||||
newProjectData.layouts.forEach((_layoutData, index) => {
|
||||
reloadPromises.push(this._reloadScript('code' + index + '.js'));
|
||||
});
|
||||
@@ -326,7 +364,7 @@ namespace gdjs {
|
||||
)[0];
|
||||
|
||||
// A file may be removed because of a partial preview.
|
||||
if (!newScriptFile && !projectDataOnlyExport) {
|
||||
if (!newScriptFile && !shouldGenerateScenesEventsCode) {
|
||||
this._logs.push({
|
||||
kind: 'warning',
|
||||
message: 'Script file ' + oldScriptFile.path + ' was removed.',
|
||||
@@ -694,6 +732,16 @@ namespace gdjs {
|
||||
runtimeScene.setEventsGeneratedCodeFunction(newLayoutData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the children object data into every custom object data.
|
||||
*
|
||||
* At the runtime, this is done at the object instantiation.
|
||||
* For hot-reloading, it's done before hands to optimize.
|
||||
*
|
||||
* @param projectData The project data
|
||||
* @param objectDatas The object datas to modify
|
||||
* @returns
|
||||
*/
|
||||
static resolveCustomObjectConfigurations(
|
||||
projectData: ProjectData,
|
||||
objectDatas: ObjectData[]
|
||||
@@ -717,27 +765,43 @@ namespace gdjs {
|
||||
if (!eventsBasedObjectData) {
|
||||
return objectData;
|
||||
}
|
||||
|
||||
const customObjectConfiguration = objectData as ObjectData &
|
||||
CustomObjectConfiguration;
|
||||
const eventsBasedObjectVariantData =
|
||||
gdjs.RuntimeGame._getEventsBasedObjectVariantData(
|
||||
eventsBasedObjectData,
|
||||
customObjectConfiguration.variant
|
||||
);
|
||||
|
||||
// Apply the legacy children configuration overriding if any.
|
||||
const mergedChildObjectDataList =
|
||||
customObjectConfiguration.childrenContent
|
||||
? eventsBasedObjectData.objects.map((objectData) => ({
|
||||
...objectData,
|
||||
...customObjectConfiguration.childrenContent[objectData.name],
|
||||
}))
|
||||
gdjs.CustomRuntimeObjectInstanceContainer.hasChildrenConfigurationOverriding(
|
||||
customObjectConfiguration,
|
||||
eventsBasedObjectVariantData
|
||||
)
|
||||
? eventsBasedObjectData.objects.map((objectData) =>
|
||||
customObjectConfiguration.childrenContent
|
||||
? {
|
||||
...objectData,
|
||||
...customObjectConfiguration.childrenContent[
|
||||
objectData.name
|
||||
],
|
||||
}
|
||||
: objectData
|
||||
)
|
||||
: eventsBasedObjectData.objects;
|
||||
|
||||
const mergedObjectConfiguration = {
|
||||
...eventsBasedObjectData,
|
||||
...objectData,
|
||||
// ObjectData doesn't have an `objects` attribute.
|
||||
// ObjectData doesn't have an `objects` nor `instances` attribute.
|
||||
// This is a small optimization to avoid to create an
|
||||
// InstanceContainerData for each instance to hot-reload their inner
|
||||
// scene (see `_hotReloadRuntimeInstanceContainer` call from
|
||||
// `_hotReloadRuntimeSceneInstances`).
|
||||
...eventsBasedObjectData,
|
||||
...eventsBasedObjectVariantData,
|
||||
objects: mergedChildObjectDataList,
|
||||
// It must be the last one to ensure the object name won't be overridden.
|
||||
...objectData,
|
||||
};
|
||||
return mergedObjectConfiguration;
|
||||
});
|
||||
@@ -751,6 +815,12 @@ namespace gdjs {
|
||||
changedRuntimeBehaviors: ChangedRuntimeBehavior[],
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
if (!oldLayoutData.objects || !newLayoutData.objects) {
|
||||
// It can happen when `hotReloadRuntimeInstances` is executed.
|
||||
// `hotReloadRuntimeInstances` doesn't resolve the custom objects
|
||||
// because it can only modify the 1st level of instances.
|
||||
return;
|
||||
}
|
||||
const oldObjectDataList = HotReloader.resolveCustomObjectConfigurations(
|
||||
oldProjectData,
|
||||
oldLayoutData.objects
|
||||
@@ -921,16 +991,62 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
hotReloadRuntimeSceneObjects(
|
||||
updatedObjects: Array<ObjectData>,
|
||||
// runtimeInstanceContainer gives an access as a map.
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
const oldObjects: Array<ObjectData | null> = updatedObjects.map(
|
||||
(objectData) =>
|
||||
runtimeInstanceContainer._objects.get(objectData.name) || null
|
||||
);
|
||||
|
||||
const projectData: ProjectData = this._runtimeGame._data;
|
||||
const newObjectDataList = HotReloader.resolveCustomObjectConfigurations(
|
||||
projectData,
|
||||
updatedObjects
|
||||
);
|
||||
|
||||
this._hotReloadRuntimeSceneObjects(
|
||||
oldObjects,
|
||||
newObjectDataList,
|
||||
runtimeInstanceContainer
|
||||
);
|
||||
// Update the GameData
|
||||
for (let index = 0; index < updatedObjects.length; index++) {
|
||||
const oldObjectData = oldObjects[index];
|
||||
// When the object is new, the hot-reload call `registerObject`
|
||||
// so `_objects` is already updated.
|
||||
if (oldObjectData) {
|
||||
// In gdjs.CustomRuntimeObjectInstanceContainer.loadFrom, object can
|
||||
// be registered with a different instance from the ProjectData. This
|
||||
// is only done for children of a custom object with a children overriding.
|
||||
// In the case of the editor, the fake custom object used for editing
|
||||
// variants has no children overriding (see
|
||||
// gdjs.RuntimeGame._createSceneWithCustomObject).
|
||||
// Thus, the oldObjectData is always the one from the ProjectData.
|
||||
HotReloader.assignOrDelete(oldObjectData, updatedObjects[index]);
|
||||
} else {
|
||||
console.warn(
|
||||
`Can't update object data for "${updatedObjects[index].name}" because it doesn't exist.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hotReloadRuntimeSceneObjects(
|
||||
oldObjects: ObjectData[],
|
||||
oldObjects: Array<ObjectData | null>,
|
||||
newObjects: ObjectData[],
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
oldObjects.forEach((oldObjectData) => {
|
||||
if (!oldObjectData) {
|
||||
return;
|
||||
}
|
||||
const name = oldObjectData.name;
|
||||
const newObjectData = newObjects.filter(
|
||||
const newObjectData = newObjects.find(
|
||||
(objectData) => objectData.name === name
|
||||
)[0];
|
||||
);
|
||||
|
||||
// Note: if an object is renamed in the editor, it will be considered as removed,
|
||||
// and the new object name as a new object to register.
|
||||
@@ -952,9 +1068,9 @@ namespace gdjs {
|
||||
});
|
||||
newObjects.forEach((newObjectData) => {
|
||||
const name = newObjectData.name;
|
||||
const oldObjectData = oldObjects.filter(
|
||||
(layerData) => layerData.name === name
|
||||
)[0];
|
||||
const oldObjectData = oldObjects.find(
|
||||
(layerData) => layerData && layerData.name === name
|
||||
);
|
||||
if (
|
||||
(!oldObjectData || oldObjectData.type !== newObjectData.type) &&
|
||||
!runtimeInstanceContainer.isObjectRegistered(name)
|
||||
@@ -1192,6 +1308,31 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
hotReloadRuntimeSceneLayers(
|
||||
newLayers: LayerData[],
|
||||
runtimeInstanceContainer: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
const layerNames = [];
|
||||
runtimeInstanceContainer.getAllLayerNames(layerNames);
|
||||
const oldLayers = layerNames.map((layerName) =>
|
||||
runtimeInstanceContainer.hasLayer(layerName)
|
||||
? runtimeInstanceContainer.getLayer(layerName)._initialLayerData
|
||||
: null
|
||||
);
|
||||
this._hotReloadRuntimeSceneLayers(
|
||||
oldLayers.filter(Boolean) as LayerData[],
|
||||
newLayers,
|
||||
runtimeInstanceContainer
|
||||
);
|
||||
// Update the GameData
|
||||
for (let index = 0; index < newLayers.length; index++) {
|
||||
const oldLayer = oldLayers[index];
|
||||
if (oldLayer) {
|
||||
HotReloader.assignOrDelete(oldLayer, newLayers[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hotReloadRuntimeSceneLayers(
|
||||
oldLayers: LayerData[],
|
||||
newLayers: LayerData[],
|
||||
@@ -1273,6 +1414,8 @@ namespace gdjs {
|
||||
newLayer.effects,
|
||||
runtimeLayer
|
||||
);
|
||||
|
||||
runtimeLayer._initialLayerData = newLayer;
|
||||
}
|
||||
|
||||
_hotReloadRuntimeLayerEffects(
|
||||
@@ -1357,6 +1500,28 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
hotReloadRuntimeInstances(
|
||||
oldInstances: InstanceData[],
|
||||
newInstances: InstanceData[],
|
||||
runtimeInstanceContainer: RuntimeInstanceContainer
|
||||
): void {
|
||||
const projectData: ProjectData = gdjs.projectData;
|
||||
const objects: Array<ObjectData> = [];
|
||||
runtimeInstanceContainer._objects.values(objects);
|
||||
projectData.layouts;
|
||||
this._hotReloadRuntimeSceneInstances(
|
||||
projectData,
|
||||
projectData,
|
||||
[],
|
||||
objects,
|
||||
objects,
|
||||
oldInstances,
|
||||
newInstances,
|
||||
runtimeInstanceContainer
|
||||
);
|
||||
gdjs.copyArray(newInstances, oldInstances);
|
||||
}
|
||||
|
||||
_hotReloadRuntimeSceneInstances(
|
||||
oldProjectData: ProjectData,
|
||||
newProjectData: ProjectData,
|
||||
@@ -1423,6 +1588,9 @@ namespace gdjs {
|
||||
);
|
||||
} else {
|
||||
// Reload objects that were created at runtime.
|
||||
// This is a subset of what is done by `_hotReloadRuntimeInstance`.
|
||||
// Since the instance doesn't exist in the editor, it's properties
|
||||
// can't be updated, only the object changes are applied.
|
||||
|
||||
// Update variables
|
||||
this._hotReloadVariablesContainer(
|
||||
@@ -1431,6 +1599,7 @@ namespace gdjs {
|
||||
runtimeObject.getVariables()
|
||||
);
|
||||
|
||||
// Update the content of custom object
|
||||
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
|
||||
const childrenInstanceContainer =
|
||||
runtimeObject.getChildrenContainer();
|
||||
@@ -1443,15 +1612,18 @@ namespace gdjs {
|
||||
CustomObjectConfiguration &
|
||||
InstanceContainerData;
|
||||
|
||||
// Reload the content of custom objects that were created at runtime.
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
|
||||
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
|
||||
// Reload the content of custom objects that were created at runtime.
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1513,22 +1685,16 @@ namespace gdjs {
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (gdjs.Base3DHandler && gdjs.Base3DHandler.is3D(runtimeObject)) {
|
||||
if (oldInstance.z !== newInstance.z && newInstance.z !== undefined) {
|
||||
runtimeObject.setZ(newInstance.z);
|
||||
if (oldInstance.z !== newInstance.z) {
|
||||
runtimeObject.setZ(newInstance.z || 0);
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (
|
||||
oldInstance.rotationX !== newInstance.rotationX &&
|
||||
newInstance.rotationX !== undefined
|
||||
) {
|
||||
runtimeObject.setRotationX(newInstance.rotationX);
|
||||
if (oldInstance.rotationX !== newInstance.rotationX) {
|
||||
runtimeObject.setRotationX(newInstance.rotationX || 0);
|
||||
somethingChanged = true;
|
||||
}
|
||||
if (
|
||||
oldInstance.rotationY !== newInstance.rotationY &&
|
||||
newInstance.rotationY !== undefined
|
||||
) {
|
||||
runtimeObject.setRotationY(newInstance.rotationY);
|
||||
if (oldInstance.rotationY !== newInstance.rotationY) {
|
||||
runtimeObject.setRotationY(newInstance.rotationY || 0);
|
||||
somethingChanged = true;
|
||||
}
|
||||
}
|
||||
@@ -1583,8 +1749,6 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
if (runtimeObject instanceof gdjs.CustomRuntimeObject) {
|
||||
const childrenInstanceContainer = runtimeObject.getChildrenContainer();
|
||||
|
||||
// The `objects` attribute is already resolved by `resolveCustomObjectConfigurations()`.
|
||||
const oldCustomObjectData = oldObjectData as ObjectData &
|
||||
CustomObjectConfiguration &
|
||||
@@ -1593,14 +1757,19 @@ namespace gdjs {
|
||||
CustomObjectConfiguration &
|
||||
InstanceContainerData;
|
||||
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
// Variant swapping is handled by `CustomRuntimeObject.updateFromObjectData`.
|
||||
if (newCustomObjectData.variant === oldCustomObjectData.variant) {
|
||||
const childrenInstanceContainer =
|
||||
runtimeObject.getChildrenContainer();
|
||||
this._hotReloadRuntimeInstanceContainer(
|
||||
oldProjectData,
|
||||
newProjectData,
|
||||
oldCustomObjectData,
|
||||
newCustomObjectData,
|
||||
changedRuntimeBehaviors,
|
||||
childrenInstanceContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update variables
|
||||
@@ -1727,5 +1896,23 @@ namespace gdjs {
|
||||
// true if both NaN, false otherwise
|
||||
return a !== a && b !== b;
|
||||
}
|
||||
|
||||
static assignOrDelete(
|
||||
target: any,
|
||||
source: any,
|
||||
ignoreKeys: string[] = []
|
||||
): void {
|
||||
Object.assign(target, source);
|
||||
for (const key in target) {
|
||||
if (ignoreKeys.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
||||
if (source[key] === undefined) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -56,6 +56,19 @@ namespace gdjs {
|
||||
};
|
||||
this._ws.onclose = function close() {
|
||||
logger.info('Debugger connection closed');
|
||||
|
||||
if (that._runtimegame.isInGameEdition()) {
|
||||
// Sometimes, for example if the editor is launched for a long time and the device goes to sleep,
|
||||
// the WebSocket connection between the editor and the game is closed. When we are in in-game edition,
|
||||
// we can't afford to lose the connection because it means the editor is unusable.
|
||||
// In this case, we hard reload the game to re-establish a new connection.
|
||||
setTimeout(() => {
|
||||
logger.info(
|
||||
'Debugger connection closed while in in-game edition - this is suspicious so hard reloading to re-establish a new connection.'
|
||||
);
|
||||
that.launchHardReload();
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
this._ws.onerror = function errored(error) {
|
||||
logger.warn('Debugger client error:', error);
|
||||
|
@@ -11,7 +11,13 @@ namespace gdjs {
|
||||
constructor(runtimeGame: RuntimeGame) {
|
||||
super(runtimeGame);
|
||||
|
||||
// Opener is either the `opener` for popups, or the `parent` if the game
|
||||
// is running as an iframe (notably: in-game edition).
|
||||
this._opener = window.opener || null;
|
||||
if (!this._opener && window.parent !== window) {
|
||||
this._opener = window.parent;
|
||||
}
|
||||
|
||||
if (!this._opener) {
|
||||
logger.info("`window.opener` not existing, the debugger won't work.");
|
||||
return;
|
||||
|
@@ -207,7 +207,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const resource = this._loadedFontFamily.get(resourceData);
|
||||
const resource = this._loadedFontFamily.getFromName(resourceData.name);
|
||||
if (resource) {
|
||||
this._loadedFontFamily.delete(resourceData);
|
||||
}
|
||||
|
@@ -1157,12 +1157,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const musicRes = this._loadedMusics.get(resourceData);
|
||||
const musicRes = this._loadedMusics.getFromName(resourceData.name);
|
||||
if (musicRes) {
|
||||
this.unloadAudio(resourceData.name, true);
|
||||
}
|
||||
|
||||
const soundRes = this._loadedSounds.get(resourceData);
|
||||
const soundRes = this._loadedSounds.getFromName(resourceData.name);
|
||||
if (soundRes) {
|
||||
this.unloadAudio(resourceData.name, false);
|
||||
}
|
||||
|
@@ -33,24 +33,28 @@ namespace gdjs {
|
||||
/**
|
||||
* The cursor X position (moved by mouse and touch events).
|
||||
*/
|
||||
_cursorX: float = 0;
|
||||
private _cursorX: float = 0;
|
||||
/**
|
||||
* The cursor Y position (moved by mouse and touch events).
|
||||
*/
|
||||
_cursorY: float = 0;
|
||||
private _cursorY: float = 0;
|
||||
/**
|
||||
* The mouse X position (only moved by mouse events).
|
||||
*/
|
||||
_mouseX: float = 0;
|
||||
private _mouseX: float = 0;
|
||||
/**
|
||||
* The mouse Y position (only moved by mouse events).
|
||||
*/
|
||||
_mouseY: float = 0;
|
||||
_isMouseInsideCanvas: boolean = true;
|
||||
_mouseWheelDelta: float = 0;
|
||||
private _mouseY: float = 0;
|
||||
private _isMouseInsideCanvas: boolean = true;
|
||||
private _wheelDeltaX: float = 0;
|
||||
private _wheelDeltaY: float = 0;
|
||||
private _wheelDeltaZ: float = 0;
|
||||
|
||||
// TODO Remove _touches when there is no longer SpritePanelButton 1.2.0
|
||||
// extension in the wild.
|
||||
_touches = {
|
||||
// @ts-ignore
|
||||
private _touches = {
|
||||
firstKey: (): string | number | null => {
|
||||
for (const key in this._mouseOrTouches.items) {
|
||||
// Exclude mouse key.
|
||||
@@ -61,22 +65,23 @@ namespace gdjs {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
_mouseOrTouches: Hashtable<Touch>;
|
||||
|
||||
private _mouseOrTouches: Hashtable<Touch>;
|
||||
//Identifiers of the touches that started during/before the frame.
|
||||
_startedTouches: Array<integer> = [];
|
||||
private _startedTouches: Array<integer> = [];
|
||||
|
||||
//Identifiers of the touches that ended during/before the frame.
|
||||
_endedTouches: Array<integer> = [];
|
||||
_touchSimulateMouse: boolean = true;
|
||||
private _endedTouches: Array<integer> = [];
|
||||
private _touchSimulateMouse: boolean = true;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
_lastStartedTouchIndex = 0;
|
||||
private _lastStartedTouchIndex = 0;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
_lastEndedTouchIndex = 0;
|
||||
private _lastEndedTouchIndex = 0;
|
||||
|
||||
constructor() {
|
||||
this._pressedKeys = new Hashtable();
|
||||
@@ -96,7 +101,7 @@ namespace gdjs {
|
||||
* @param keyCode The raw key code
|
||||
* @param location The location
|
||||
*/
|
||||
_getLocationAwareKeyCode(
|
||||
static getLocationAwareKeyCode(
|
||||
keyCode: number,
|
||||
location: number | null | undefined
|
||||
): integer {
|
||||
@@ -121,7 +126,7 @@ namespace gdjs {
|
||||
* @param location The location of the event.
|
||||
*/
|
||||
onKeyPressed(keyCode: number, location?: number): void {
|
||||
const locationAwareKeyCode = this._getLocationAwareKeyCode(
|
||||
const locationAwareKeyCode = InputManager.getLocationAwareKeyCode(
|
||||
keyCode,
|
||||
location
|
||||
);
|
||||
@@ -138,7 +143,7 @@ namespace gdjs {
|
||||
* @param location The location of the event.
|
||||
*/
|
||||
onKeyReleased(keyCode: number, location?: number): void {
|
||||
const locationAwareKeyCode = this._getLocationAwareKeyCode(
|
||||
const locationAwareKeyCode = InputManager.getLocationAwareKeyCode(
|
||||
keyCode,
|
||||
location
|
||||
);
|
||||
@@ -335,6 +340,19 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if any mouse button is pressed.
|
||||
* @return true if any mouse button is pressed.
|
||||
*/
|
||||
anyMouseButtonPressed(): boolean {
|
||||
for (const buttonCode in this._pressedMouseButtons) {
|
||||
if (this._pressedMouseButtons[buttonCode]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
_setMouseButtonPressed(buttonCode: number): void {
|
||||
this._pressedMouseButtons[buttonCode] = true;
|
||||
this._releasedMouseButtons[buttonCode] = false;
|
||||
@@ -380,17 +398,37 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Should be called whenever the mouse wheel is used
|
||||
* @param wheelDelta The mouse wheel delta
|
||||
* @param wheelDeltaY The mouse wheel delta
|
||||
*/
|
||||
onMouseWheel(wheelDelta: number): void {
|
||||
this._mouseWheelDelta = wheelDelta;
|
||||
onMouseWheel(
|
||||
wheelDeltaY: number,
|
||||
wheelDeltaX: number,
|
||||
wheelDeltaZ: number
|
||||
): void {
|
||||
this._wheelDeltaY = wheelDeltaY;
|
||||
if (wheelDeltaX !== undefined) this._wheelDeltaX = wheelDeltaX;
|
||||
if (wheelDeltaZ !== undefined) this._wheelDeltaZ = wheelDeltaZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mouse wheel delta
|
||||
* Return the mouse wheel delta on Y axis.
|
||||
*/
|
||||
getMouseWheelDelta(): float {
|
||||
return this._mouseWheelDelta;
|
||||
return this._wheelDeltaY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mouse wheel delta on X axis.
|
||||
*/
|
||||
getMouseWheelDeltaX(): float {
|
||||
return this._wheelDeltaX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mouse wheel delta on Z axis.
|
||||
*/
|
||||
getMouseWheelDeltaZ(): float {
|
||||
return this._wheelDeltaZ;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,7 +616,9 @@ namespace gdjs {
|
||||
this._releasedKeys.clear();
|
||||
this._justPressedKeys.clear();
|
||||
this._releasedMouseButtons.length = 0;
|
||||
this._mouseWheelDelta = 0;
|
||||
this._wheelDeltaX = 0;
|
||||
this._wheelDeltaY = 0;
|
||||
this._wheelDeltaZ = 0;
|
||||
this._lastStartedTouchIndex = 0;
|
||||
this._lastEndedTouchIndex = 0;
|
||||
}
|
||||
|
@@ -210,12 +210,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const loadedJson = this._loadedJsons.get(resourceData);
|
||||
const loadedJson = this._loadedJsons.getFromName(resourceData.name);
|
||||
if (loadedJson) {
|
||||
this._loadedJsons.delete(resourceData);
|
||||
}
|
||||
|
||||
const callback = this._callbacks.get(resourceData);
|
||||
const callback = this._callbacks.getFromName(resourceData.name);
|
||||
if (callback) {
|
||||
this._callbacks.delete(resourceData);
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -7,6 +7,178 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('LayerPixiRenderer');
|
||||
|
||||
const FRUSTUM_EDGES: Array<[number, number]> = [
|
||||
// near plane edges
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
[3, 0],
|
||||
// far plane edges
|
||||
[4, 5],
|
||||
[5, 6],
|
||||
[6, 7],
|
||||
[7, 4],
|
||||
// near↔far connections
|
||||
[0, 4],
|
||||
[1, 5],
|
||||
[2, 6],
|
||||
[3, 7],
|
||||
];
|
||||
|
||||
/** Normalized Device Coordinates corners for near (-1) and far (+1) planes (Three.js NDC: z=-1 near, z=+1 far). */
|
||||
const NDC_CORNERS: Array<Array<float>> = [
|
||||
// near
|
||||
[-1, -1, -1],
|
||||
[+1, -1, -1],
|
||||
[+1, +1, -1],
|
||||
[-1, +1, -1],
|
||||
// far
|
||||
[-1, -1, +1],
|
||||
[+1, -1, +1],
|
||||
[+1, +1, +1],
|
||||
[-1, +1, +1],
|
||||
];
|
||||
|
||||
/** Sort convex polygon vertices around centroid to get consistent winding. */
|
||||
const sortConvexPolygon = (points: THREE.Vector3[]): THREE.Vector3[] => {
|
||||
if (points.length <= 2) return points;
|
||||
const cx = points.reduce((s, p) => s + p.x, 0) / points.length;
|
||||
const cy = points.reduce((s, p) => s + p.y, 0) / points.length;
|
||||
return points
|
||||
.map((p) => ({ p, a: Math.atan2(p.y - cy, p.x - cx) }))
|
||||
.sort((u, v) => u.a - v.a)
|
||||
.map((u) => u.p);
|
||||
};
|
||||
|
||||
/**
|
||||
* Intersect a frustum edge segment [a,b] with plane Z=0.
|
||||
* Returns point or null if no intersection on the segment.
|
||||
*/
|
||||
const intersectSegmentWithZ0 = (
|
||||
a: THREE.Vector3,
|
||||
b: THREE.Vector3,
|
||||
eps = 1e-9
|
||||
): THREE.Vector3 | null => {
|
||||
const az = a.z,
|
||||
bz = b.z;
|
||||
const dz = bz - az;
|
||||
|
||||
// If both z on same side and not on plane, no crossing.
|
||||
if (Math.abs(dz) < eps) {
|
||||
// Segment is (almost) parallel to plane.
|
||||
if (Math.abs(az) < eps && Math.abs(bz) < eps) {
|
||||
// Entire segment lies on plane: return endpoints (handled by caller via dedup).
|
||||
// Here we return null and let caller add endpoints if needed.
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Solve a.z + t*(b.z - a.z) = 0 ⇒ t = -a.z / (b.z - a.z)
|
||||
const t = -az / dz;
|
||||
if (t < -eps || t > 1 + eps) {
|
||||
// Intersection beyond the segment bounds.
|
||||
return null;
|
||||
}
|
||||
|
||||
const p = new THREE.Vector3(
|
||||
a.x + t * (b.x - a.x),
|
||||
a.y + t * (b.y - a.y),
|
||||
0
|
||||
);
|
||||
return p;
|
||||
};
|
||||
|
||||
/** Remove near-duplicate points. */
|
||||
const dedupPoints = (
|
||||
points: THREE.Vector3[],
|
||||
eps = 1e-6
|
||||
): THREE.Vector3[] => {
|
||||
const out: THREE.Vector3[] = [];
|
||||
for (const p of points) {
|
||||
const exists = out.some(
|
||||
(q) => Math.abs(p.x - q.x) < eps && Math.abs(p.y - q.y) < eps
|
||||
);
|
||||
if (!exists) out.push(p);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the convex polygon of the camera frustum clipped by plane Z=0.
|
||||
* Returns ordered vertices (world coords, z=0). Empty array if no intersection.
|
||||
*/
|
||||
const clipFrustumAgainstZ0 = (camera: THREE.Camera): THREE.Vector3[] => {
|
||||
camera.updateMatrixWorld(true);
|
||||
|
||||
// Get the 8 corners of the camera frustum in world coordinates.
|
||||
const corners = NDC_CORNERS.map((ndc) =>
|
||||
new THREE.Vector3(ndc[0], ndc[1], ndc[2]).unproject(camera)
|
||||
);
|
||||
if (corners.length !== 8) return [];
|
||||
|
||||
const hits: THREE.Vector3[] = [];
|
||||
|
||||
// 1) Add vertices that already lie on the plane (z≈0).
|
||||
for (const v of corners) {
|
||||
if (Math.abs(v.z) < 1e-9) {
|
||||
hits.push(new THREE.Vector3(v.x, v.y, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Intersect each frustum edge with plane Z=0.
|
||||
for (const [i, j] of FRUSTUM_EDGES) {
|
||||
const a = corners[i],
|
||||
b = corners[j];
|
||||
const p = intersectSegmentWithZ0(a, b);
|
||||
if (p) hits.push(p);
|
||||
}
|
||||
|
||||
// Deduplicate and order.
|
||||
const unique = dedupPoints(hits);
|
||||
if (unique.length < 3) return [];
|
||||
return sortConvexPolygon(unique);
|
||||
};
|
||||
|
||||
/**
|
||||
* Intersect the ray going through a normalized device coordinate (nx, ny)
|
||||
* with the plane Z=0. Returns the hit point in THREE world coords (z=0)
|
||||
* or null if the ray doesn't intersect the plane in front of the camera.
|
||||
*/
|
||||
const projectNDCToZ0 = (
|
||||
camera: THREE.Camera,
|
||||
nx: number,
|
||||
ny: number
|
||||
): THREE.Vector3 | null => {
|
||||
if (!camera) return null;
|
||||
|
||||
camera.updateMatrixWorld(true);
|
||||
|
||||
const origin = new THREE.Vector3();
|
||||
const dir = new THREE.Vector3();
|
||||
const p = new THREE.Vector3(nx, ny, 0.5);
|
||||
|
||||
if (camera instanceof THREE.OrthographicCamera) {
|
||||
// For ortho, unproject a point on the camera plane, and use forward dir.
|
||||
p.z = 0; // on the camera plane
|
||||
p.unproject(camera); // gives a point on the camera plane in world coords
|
||||
origin.copy(p);
|
||||
camera.getWorldDirection(dir);
|
||||
} else {
|
||||
// Perspective: unproject a point on the frustum plane, build a ray.
|
||||
p.unproject(camera);
|
||||
origin.copy(camera.position);
|
||||
dir.copy(p).sub(origin).normalize();
|
||||
}
|
||||
|
||||
const dz = dir.z;
|
||||
if (Math.abs(dz) < 1e-8) return null; // parallel
|
||||
const t = -origin.z / dz;
|
||||
if (t <= 0) return null; // behind the camera => not visible
|
||||
|
||||
return origin.addScaledVector(dir, t).setZ(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* The renderer for a gdjs.Layer using Pixi.js.
|
||||
*/
|
||||
@@ -47,6 +219,7 @@ namespace gdjs {
|
||||
private _threePlaneGeometry: THREE.PlaneGeometry | null = null;
|
||||
private _threePlaneMaterial: THREE.ShaderMaterial | null = null;
|
||||
private _threePlaneMesh: THREE.Mesh | null = null;
|
||||
private _threePlaneMeshDebugOutline: THREE.LineSegments | null = null;
|
||||
|
||||
/**
|
||||
* Pixi doesn't sort children with zIndex == 0.
|
||||
@@ -99,6 +272,9 @@ namespace gdjs {
|
||||
// The layer is now fully initialized. Adapt the 3D camera position
|
||||
// (which we could not do before in `_setup3DRendering`).
|
||||
this._update3DCameraAspectAndPosition();
|
||||
|
||||
// Uncomment to show the outline of the 2D rendering plane.
|
||||
// this.show2DRenderingPlaneDebugOutline(true);
|
||||
}
|
||||
|
||||
onGameResolutionResized() {
|
||||
@@ -134,6 +310,10 @@ namespace gdjs {
|
||||
return this._threeScene;
|
||||
}
|
||||
|
||||
getThreeGroup(): THREE.Group | null {
|
||||
return this._threeGroup;
|
||||
}
|
||||
|
||||
getThreeCamera():
|
||||
| THREE.PerspectiveCamera
|
||||
| THREE.OrthographicCamera
|
||||
@@ -285,6 +465,10 @@ namespace gdjs {
|
||||
'Tried to setup PixiJS plane for 2D rendering in 3D for a layer that is already set up.'
|
||||
);
|
||||
|
||||
this.set2DPlaneMaxDrawingDistance(
|
||||
this._layer.getInitialCamera2DPlaneMaxDrawingDistance()
|
||||
);
|
||||
|
||||
// If we have both 2D and 3D objects to be rendered, create a render texture that PixiJS will use
|
||||
// to render, and that will be projected on a plane by Three.js
|
||||
this._createPixiRenderTexture(pixiRenderer);
|
||||
@@ -388,30 +572,298 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position of the PIXI container. To be called after each change
|
||||
* made to position, zoom or rotation of the camera.
|
||||
* Enable or disable the drawing of an outline of the 2D rendering plane.
|
||||
* Useful to visually see where the 2D rendering is done in the 3D world.
|
||||
*/
|
||||
updatePosition(): void {
|
||||
const angle = -gdjs.toRad(this._layer.getCameraRotation());
|
||||
const zoomFactor = this._layer.getCameraZoom();
|
||||
this._pixiContainer.rotation = angle;
|
||||
this._pixiContainer.scale.x = zoomFactor;
|
||||
this._pixiContainer.scale.y = zoomFactor;
|
||||
const cosValue = Math.cos(angle);
|
||||
const sinValue = Math.sin(angle);
|
||||
const centerX =
|
||||
this._layer.getCameraX() * zoomFactor * cosValue -
|
||||
this._layer.getCameraY() * zoomFactor * sinValue;
|
||||
const centerY =
|
||||
this._layer.getCameraX() * zoomFactor * sinValue +
|
||||
this._layer.getCameraY() * zoomFactor * cosValue;
|
||||
this._pixiContainer.position.x = this._layer.getWidth() / 2 - centerX;
|
||||
this._pixiContainer.position.y = this._layer.getHeight() / 2 - centerY;
|
||||
show2DRenderingPlaneDebugOutline(enable: boolean) {
|
||||
if (!this._threePlaneMesh) return;
|
||||
if (enable && !this._threePlaneMeshDebugOutline) {
|
||||
// Add rectangle outline around the plane.
|
||||
const edges = new THREE.EdgesGeometry(this._threePlaneGeometry);
|
||||
const lineMaterial = new THREE.LineBasicMaterial({
|
||||
color: 0xff0000,
|
||||
});
|
||||
this._threePlaneMeshDebugOutline = new THREE.LineSegments(
|
||||
edges,
|
||||
lineMaterial
|
||||
);
|
||||
|
||||
// Attach the outline to the plane so it follows position/scale/rotation.
|
||||
this._threePlaneMesh.add(this._threePlaneMeshDebugOutline);
|
||||
}
|
||||
if (!enable && this._threePlaneMeshDebugOutline) {
|
||||
this._threePlaneMesh.remove(this._threePlaneMeshDebugOutline);
|
||||
this._threePlaneMeshDebugOutline = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Maximum size of the 2D plane, in pixels. */
|
||||
private _2DPlaneMaxDrawingDistance: number = 5000;
|
||||
/** Tilt degrees below which the 2D plane is not clamped. */
|
||||
private _2DPlaneClampFreeTiltDeg: number = 0.1;
|
||||
/** Tilt degrees below which the 2D plane is fully clamped. */
|
||||
private _2DPlaneClampHardTiltDeg: number = 6;
|
||||
private _2DPlaneClampRampPower: number = 1.5; // 1 = linear, >1 = smoother
|
||||
|
||||
/**
|
||||
* Set the maximum "drawing distance", in pixels, of the 2D when in the 3D world.
|
||||
* This corresponds to the "height" of the 2D plane.
|
||||
* Used when the 3D camera is tilted on the X or Y axis (instead of looking down the Z axis,
|
||||
* as it's done by default for 2D games).
|
||||
* This is useful to avoid the 2D plane being too big when the camera is tilted.
|
||||
*/
|
||||
set2DPlaneMaxDrawingDistance(h: number) {
|
||||
this._2DPlaneMaxDrawingDistance = Math.max(0, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tilt degrees below which the 2D plane is not clamped.
|
||||
*/
|
||||
set2DPlaneClampFreeTiltDegrees(d: number) {
|
||||
this._2DPlaneClampFreeTiltDeg = Math.max(0, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tilt degrees below which the 2D plane is clamped (see `set2DPlaneMaxDrawingDistance`).
|
||||
*/
|
||||
set2DPlaneClampHardTiltDegrees(d: number) {
|
||||
this._2DPlaneClampHardTiltDeg = Math.max(0, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ramp power of the 2D plane clamping (see `set2DPlaneMaxDrawingDistance`). Used
|
||||
* for smoother transition between clamped and unclamped.
|
||||
*/
|
||||
set2DPlaneClampRampPower(p: number) {
|
||||
this._2DPlaneClampRampPower = Math.max(0.1, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the 2D plane, in the world coordinates.
|
||||
*/
|
||||
private _get2DPlaneSize(): [number, number] {
|
||||
if (!this._threeCamera) return [0, 0];
|
||||
|
||||
// Compute the intersection of the frustrum of the camera on the Z=0 plane.
|
||||
// In theory, that's where the entire 2D rendering should be displayed.
|
||||
const poly = clipFrustumAgainstZ0(this._threeCamera);
|
||||
|
||||
if (poly.length === 0) {
|
||||
// No intersection at all: Z=0 not in view.
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
// Compute the axis-aligned bounds on Z=0 (world units) of the polygon,
|
||||
// so we can compute the size of the plane doing the 2D rendering.
|
||||
let minX = Infinity,
|
||||
maxX = -Infinity,
|
||||
minY = Infinity,
|
||||
maxY = -Infinity;
|
||||
for (const p of poly) {
|
||||
if (p.x < minX) minX = p.x;
|
||||
if (p.x > maxX) maxX = p.x;
|
||||
if (p.y < minY) minY = p.y;
|
||||
if (p.y > maxY) maxY = p.y;
|
||||
}
|
||||
let boxW = Math.max(1e-8, maxX - minX);
|
||||
let boxH = Math.max(1e-8, maxY - minY);
|
||||
|
||||
// Keep 2D layer aspect ratio (so texture isn't stretched).
|
||||
const targetAspect = this._layer.getWidth() / this._layer.getHeight();
|
||||
const boxAspect = boxW / boxH;
|
||||
if (boxAspect < targetAspect) {
|
||||
boxW = targetAspect * boxH;
|
||||
} else {
|
||||
boxH = boxW / targetAspect;
|
||||
}
|
||||
|
||||
// Decide if we should cap based on camera tilt (X/Y) ---
|
||||
const forward = new THREE.Vector3();
|
||||
this._threeCamera.getWorldDirection(forward);
|
||||
// |forward.z| ≈ 1 -> no tilt (look mostly perpendicular to Z=0).
|
||||
// |forward.z| ≈ 0 -> grazing the horizon (strong tilt).
|
||||
|
||||
const freeCos = Math.cos(
|
||||
THREE.MathUtils.degToRad(this._2DPlaneClampFreeTiltDeg)
|
||||
);
|
||||
const hardCos = Math.cos(
|
||||
THREE.MathUtils.degToRad(this._2DPlaneClampHardTiltDeg)
|
||||
);
|
||||
const tiltCos = Math.abs(forward.z);
|
||||
|
||||
// Map tiltCos ∈ [hardCos, freeCos] to w ∈ [1, 0]
|
||||
let w = 0;
|
||||
if (tiltCos <= hardCos)
|
||||
w = 1; // fully clamped
|
||||
else if (tiltCos >= freeCos)
|
||||
w = 0; // no clamp
|
||||
else w = (freeCos - tiltCos) / (freeCos - hardCos);
|
||||
|
||||
// Ease it
|
||||
w = Math.pow(w, this._2DPlaneClampRampPower);
|
||||
|
||||
// Interpolate Infinity→base via 1/w (bounded):
|
||||
const BIG = 1e12; // “practically infinite”
|
||||
const denom = Math.max(w, 1e-6);
|
||||
const effectiveMaxH = Math.min(
|
||||
BIG,
|
||||
this._2DPlaneMaxDrawingDistance / denom
|
||||
);
|
||||
|
||||
// Apply the max height.
|
||||
if (effectiveMaxH < BIG) {
|
||||
const clampedH = Math.max(1e-8, Math.min(boxH, effectiveMaxH));
|
||||
if (clampedH !== boxH) {
|
||||
boxH = clampedH;
|
||||
boxW = targetAspect * boxH; // keep aspect
|
||||
}
|
||||
}
|
||||
|
||||
return [boxW, boxH];
|
||||
}
|
||||
|
||||
private _get2DPlanePosition(boxH: number): [number, number] {
|
||||
if (!this._threeCamera) return [0, 0];
|
||||
|
||||
// Choose the plane position (anchor to bottom of screen, heading-invariant) ---
|
||||
const bottomLeft = projectNDCToZ0(this._threeCamera, -1, -1);
|
||||
const bottomRight = projectNDCToZ0(this._threeCamera, +1, -1);
|
||||
|
||||
let cx: number, cy: number;
|
||||
|
||||
if (bottomLeft && bottomRight) {
|
||||
// Midpoint of the bottom-of-screen segment on Z=0:
|
||||
const mx = 0.5 * (bottomLeft.x + bottomRight.x);
|
||||
const my = 0.5 * (bottomLeft.y + bottomRight.y);
|
||||
|
||||
// Tangent along the bottom line (unit):
|
||||
let dx = bottomRight.x - bottomLeft.x;
|
||||
let dy = bottomRight.y - bottomLeft.y;
|
||||
const len = Math.hypot(dx, dy) || 1;
|
||||
dx /= len;
|
||||
dy /= len;
|
||||
|
||||
// Inward normal n = +90° rotation of d in XY plane:
|
||||
// d = (dx, dy) -> n = (-dy, dx)
|
||||
let nx = -dy;
|
||||
let ny = dx;
|
||||
|
||||
// Ensure n points "into the screen":
|
||||
const midIn = projectNDCToZ0(this._threeCamera, 0, -0.5);
|
||||
if (midIn) {
|
||||
const vx = midIn.x - mx;
|
||||
const vy = midIn.y - my;
|
||||
if (vx * nx + vy * ny < 0) {
|
||||
nx = -nx;
|
||||
ny = -ny;
|
||||
}
|
||||
}
|
||||
|
||||
// Place the plane so its bottom edge lies on the bottom-of-screen line:
|
||||
cx = mx + nx * (boxH * 0.5);
|
||||
cy = my + ny * (boxH * 0.5);
|
||||
} else {
|
||||
// Fallback to the camera center projected on Z=0 if bottom line not visible:
|
||||
const centerRay = projectNDCToZ0(this._threeCamera, 0, 0);
|
||||
if (centerRay) {
|
||||
cx = centerRay.x;
|
||||
cy = centerRay.y;
|
||||
} else {
|
||||
// Fallback to the camera position if the center ray is not visible:
|
||||
cx = this._threeCamera.position.x;
|
||||
cy = this._threeCamera.position.y;
|
||||
}
|
||||
}
|
||||
return [cx, cy];
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
// Update the 3D camera position and rotation.
|
||||
if (this._threeCamera) {
|
||||
const angle = -gdjs.toRad(this._layer.getCameraRotation());
|
||||
this._threeCamera.position.x = this._layer.getCameraX();
|
||||
this._threeCamera.position.y = -this._layer.getCameraY(); // scene is mirrored on Y
|
||||
this._threeCamera.rotation.z = angle;
|
||||
|
||||
if (this._threeCamera instanceof THREE.OrthographicCamera) {
|
||||
this._threeCamera.zoom = this._layer.getCameraZoom();
|
||||
this._threeCamera.updateProjectionMatrix();
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(null);
|
||||
} else {
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(
|
||||
this._threeCamera.fov
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let effectivePixiZoom = 1;
|
||||
const angle = -gdjs.toRad(this._layer.getCameraRotation());
|
||||
const angleCosValue = Math.cos(angle);
|
||||
const angleSinValue = Math.sin(angle);
|
||||
|
||||
// Update the 2D plane in the 3D world position, size and rotation,
|
||||
// and update the 2D Pixi container position, size and rotation.
|
||||
if (this._threeCamera && this._threePlaneMesh) {
|
||||
const [boxW, boxH] = this._get2DPlaneSize();
|
||||
|
||||
if (boxW === 0 || boxH === 0) {
|
||||
// No size means the 2D plane is not visible.
|
||||
this._threePlaneMesh.visible = false;
|
||||
} else {
|
||||
this._threePlaneMesh.visible = true;
|
||||
|
||||
const [cx, cy] = this._get2DPlanePosition(boxH);
|
||||
|
||||
// Update the 2D plane size, position and rotation (so 2D remains upright).
|
||||
// Plane size (geometry is 1×1).
|
||||
this._threePlaneMesh.scale.set(boxW, boxH, 1);
|
||||
this._threePlaneMesh.position.set(cx, -cy, 0);
|
||||
this._threePlaneMesh.rotation.set(0, 0, -angle);
|
||||
|
||||
// Update the 2D Pixi container size and rotation to match the "zoom" (which comes from the 2D plane size)
|
||||
// rotation and position.
|
||||
effectivePixiZoom = this._layer.getWidth() / boxW; // == height/boxH
|
||||
this._pixiContainer.scale.set(effectivePixiZoom, effectivePixiZoom);
|
||||
this._pixiContainer.rotation = angle;
|
||||
|
||||
const followX = cx;
|
||||
const followY = -cy;
|
||||
const centerX2d =
|
||||
followX * effectivePixiZoom * angleCosValue -
|
||||
followY * effectivePixiZoom * angleSinValue;
|
||||
const centerY2d =
|
||||
followX * effectivePixiZoom * angleSinValue +
|
||||
followY * effectivePixiZoom * angleCosValue;
|
||||
this._pixiContainer.position.x =
|
||||
this._layer.getWidth() / 2 - centerX2d;
|
||||
this._pixiContainer.position.y =
|
||||
this._layer.getHeight() / 2 - centerY2d;
|
||||
}
|
||||
}
|
||||
|
||||
// 2D only (no 3D rendering and so no 2D plane in the 3D world):
|
||||
// Update the 2D Pixi container position, size and rotation.
|
||||
if (!this._threeCamera || !this._threePlaneMesh) {
|
||||
effectivePixiZoom = this._layer.getCameraZoom();
|
||||
this._pixiContainer.rotation = angle;
|
||||
this._pixiContainer.scale.x = effectivePixiZoom;
|
||||
this._pixiContainer.scale.y = effectivePixiZoom;
|
||||
const centerX =
|
||||
this._layer.getCameraX() * effectivePixiZoom * angleCosValue -
|
||||
this._layer.getCameraY() * effectivePixiZoom * angleSinValue;
|
||||
const centerY =
|
||||
this._layer.getCameraX() * effectivePixiZoom * angleSinValue +
|
||||
this._layer.getCameraY() * effectivePixiZoom * angleCosValue;
|
||||
this._pixiContainer.position.x = this._layer.getWidth() / 2 - centerX;
|
||||
this._pixiContainer.position.y = this._layer.getHeight() / 2 - centerY;
|
||||
}
|
||||
|
||||
// Pixel rounding for the Pixi rendering (be it for 2D only
|
||||
// or for the 2D rendering shown in the 2D plane in the 3D world).
|
||||
if (
|
||||
this._layer.getRuntimeScene().getGame().getPixelsRounding() &&
|
||||
(cosValue === 0 || sinValue === 0) &&
|
||||
Number.isInteger(zoomFactor)
|
||||
(angleCosValue === 0 || angleSinValue === 0) &&
|
||||
Number.isInteger(effectivePixiZoom)
|
||||
) {
|
||||
// Camera rounding is important for pixel perfect games.
|
||||
// Otherwise, the camera position fractional part is added to
|
||||
@@ -467,39 +919,12 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._threeCamera) {
|
||||
// TODO (3D) - improvement: handle camera rounding like down for PixiJS?
|
||||
this._threeCamera.position.x = this._layer.getCameraX();
|
||||
this._threeCamera.position.y = -this._layer.getCameraY(); // Inverted because the scene is mirrored on Y axis.
|
||||
this._threeCamera.rotation.z = angle;
|
||||
|
||||
if (this._threeCamera instanceof THREE.OrthographicCamera) {
|
||||
this._threeCamera.zoom = this._layer.getCameraZoom();
|
||||
this._threeCamera.updateProjectionMatrix();
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(null);
|
||||
} else {
|
||||
this._threeCamera.position.z = this._layer.getCameraZ(
|
||||
this._threeCamera.fov
|
||||
);
|
||||
}
|
||||
|
||||
if (this._threePlaneMesh) {
|
||||
// Adapt the plane size so that it covers the whole screen.
|
||||
this._threePlaneMesh.scale.x = this._layer.getWidth() / zoomFactor;
|
||||
this._threePlaneMesh.scale.y = this._layer.getHeight() / zoomFactor;
|
||||
|
||||
// Adapt the plane position so that it's always displayed on the whole screen.
|
||||
this._threePlaneMesh.position.x = this._threeCamera.position.x;
|
||||
this._threePlaneMesh.position.y = -this._threeCamera.position.y; // Inverted because the scene is mirrored on Y axis.
|
||||
this._threePlaneMesh.rotation.z = -angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateResolution() {
|
||||
if (this._threeEffectComposer) {
|
||||
const game = this._layer.getRuntimeScene().getGame();
|
||||
this._threeEffectComposer.setPixelRatio(window.devicePixelRatio);
|
||||
this._threeEffectComposer.setSize(
|
||||
game.getGameResolutionWidth(),
|
||||
game.getGameResolutionHeight()
|
||||
|
@@ -309,7 +309,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
unloadResource(resourceData: ResourceData): void {
|
||||
const loadedFont = this._loadedFontsData.get(resourceData);
|
||||
const loadedFont = this._loadedFontsData.getFromName(resourceData.name);
|
||||
if (loadedFont) {
|
||||
this._loadedFontsData.delete(resourceData);
|
||||
}
|
||||
|
@@ -103,6 +103,10 @@ namespace gdjs {
|
||||
if (!existingTexture) {
|
||||
return this._invalidTexture;
|
||||
}
|
||||
if (existingTexture.destroyed) {
|
||||
logger.error('Texture for ' + resourceName + ' is not valid anymore.');
|
||||
return this._invalidTexture;
|
||||
}
|
||||
if (!existingTexture.valid) {
|
||||
logger.error(
|
||||
'Texture for ' +
|
||||
|
@@ -101,6 +101,7 @@ namespace gdjs {
|
||||
this._threeRenderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
this._threeRenderer.useLegacyLights = true;
|
||||
this._threeRenderer.autoClear = false;
|
||||
this._threeRenderer.pixelRatio = window.devicePixelRatio;
|
||||
this._threeRenderer.setSize(
|
||||
this._game.getGameResolutionWidth(),
|
||||
this._game.getGameResolutionHeight()
|
||||
@@ -761,7 +762,7 @@ namespace gdjs {
|
||||
};
|
||||
// @ts-ignore
|
||||
canvas.onwheel = function (event) {
|
||||
manager.onMouseWheel(-event.deltaY);
|
||||
manager.onMouseWheel(-event.deltaY, event.deltaX, event.deltaZ);
|
||||
};
|
||||
|
||||
// Touches:
|
||||
@@ -784,6 +785,7 @@ namespace gdjs {
|
||||
touch.pageY
|
||||
);
|
||||
manager.onTouchMove(touch.identifier, pos[0], pos[1]);
|
||||
manager.onTouchMove(touch.identifier, pos[0], pos[1]);
|
||||
// This works because touch events are sent
|
||||
// when they continue outside of the canvas.
|
||||
if (manager.isSimulatingMouseWithTouch()) {
|
||||
|
@@ -115,9 +115,10 @@ namespace gdjs {
|
||||
const runtimeLayerRenderingType = runtimeLayer.getRenderingType();
|
||||
const layerHas3DObjectsToRender = runtimeLayerRenderer.has3DObjects();
|
||||
if (
|
||||
runtimeLayerRenderingType ===
|
||||
!this._runtimeScene.getGame().isInGameEdition() &&
|
||||
(runtimeLayerRenderingType ===
|
||||
gdjs.RuntimeLayerRenderingType.TWO_D ||
|
||||
!layerHas3DObjectsToRender
|
||||
!layerHas3DObjectsToRender)
|
||||
) {
|
||||
// Render a layer with 2D rendering (PixiJS) only if layer is configured as is
|
||||
// or if there is no 3D object to render.
|
||||
|
@@ -41,18 +41,71 @@ namespace gdjs {
|
||||
return supportedCompressionMethods;
|
||||
};
|
||||
|
||||
/**
|
||||
* The desired status of the game, used for previews or in-game edition.
|
||||
* Either stored in the options generated by the preview or in the URL
|
||||
* in case of a hard reload.
|
||||
*/
|
||||
export type RuntimeGameStatus = {
|
||||
isPaused: boolean;
|
||||
isInGameEdition: boolean;
|
||||
sceneName: string | null;
|
||||
injectedExternalLayoutName: string | null;
|
||||
skipCreatingInstancesFromScene: boolean;
|
||||
eventsBasedObjectType: string | null;
|
||||
eventsBasedObjectVariantName: string | null;
|
||||
editorId: string | null;
|
||||
editorCamera3D?: EditorCameraState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read the desired status of the game from the URL. Only useful for previews
|
||||
* when hard reloaded.
|
||||
*/
|
||||
const readRuntimeGameStatusFromUrl = (): RuntimeGameStatus | null => {
|
||||
try {
|
||||
const url = new URL(location.href);
|
||||
const runtimeGameStatus = url.searchParams.get('runtimeGameStatus');
|
||||
if (!runtimeGameStatus) return null;
|
||||
|
||||
const parsedRuntimeGameStatus = JSON.parse(runtimeGameStatus);
|
||||
return {
|
||||
isPaused: !!parsedRuntimeGameStatus.isPaused,
|
||||
isInGameEdition: !!parsedRuntimeGameStatus.isInGameEdition,
|
||||
sceneName: '' + parsedRuntimeGameStatus.sceneName,
|
||||
injectedExternalLayoutName:
|
||||
'' + parsedRuntimeGameStatus.injectedExternalLayoutName,
|
||||
skipCreatingInstancesFromScene:
|
||||
!!parsedRuntimeGameStatus.skipCreatingInstancesFromScene,
|
||||
eventsBasedObjectType: parsedRuntimeGameStatus.eventsBasedObjectType,
|
||||
eventsBasedObjectVariantName:
|
||||
parsedRuntimeGameStatus.eventsBasedObjectVariantName,
|
||||
editorId: parsedRuntimeGameStatus.editorId,
|
||||
editorCamera3D: parsedRuntimeGameStatus.editorCamera3D,
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/** Options given to the game at startup. */
|
||||
export type RuntimeGameOptions = {
|
||||
/** if true, force fullscreen. */
|
||||
forceFullscreen?: boolean;
|
||||
|
||||
/** if true, game is run as a preview launched from an editor. */
|
||||
isPreview?: boolean;
|
||||
/** The name of the external layout to create in the scene at position 0;0. */
|
||||
injectExternalLayout?: string;
|
||||
|
||||
/** if set, the status of the game to be restored. */
|
||||
initialRuntimeGameStatus?: RuntimeGameStatus;
|
||||
|
||||
/** Script files, used for hot-reloading. */
|
||||
scriptFiles?: Array<RuntimeGameOptionsScriptFile>;
|
||||
/** if true, export is a partial preview without events. */
|
||||
projectDataOnlyExport?: boolean;
|
||||
/** if true, export is a partial preview without reloading libraries. */
|
||||
shouldReloadLibraries?: boolean;
|
||||
/** if true, export is a partial preview without generating events. */
|
||||
shouldGenerateScenesEventsCode?: boolean;
|
||||
|
||||
/** if true, preview is launched from GDevelop native mobile app. */
|
||||
nativeMobileApp?: boolean;
|
||||
/** The address of the debugger server, to reach out using WebSocket. */
|
||||
@@ -139,7 +192,13 @@ namespace gdjs {
|
||||
_gameResolutionHeight: integer;
|
||||
_originalWidth: float;
|
||||
_originalHeight: float;
|
||||
_resizeMode: 'adaptWidth' | 'adaptHeight' | string;
|
||||
_resizeMode:
|
||||
| ''
|
||||
| 'scaleOuter'
|
||||
| 'adaptWidth'
|
||||
| 'adaptHeight'
|
||||
| 'native'
|
||||
| string;
|
||||
_adaptGameResolutionAtRuntime: boolean;
|
||||
_scaleMode: 'linear' | 'nearest';
|
||||
_pixelsRounding: boolean;
|
||||
@@ -171,12 +230,8 @@ namespace gdjs {
|
||||
_hasJustResumed: boolean = false;
|
||||
|
||||
//Inputs :
|
||||
_inputManager: InputManager;
|
||||
private _inputManager: InputManager;
|
||||
|
||||
/**
|
||||
* Allow to specify an external layout to insert in the first scene.
|
||||
*/
|
||||
_injectExternalLayout: any;
|
||||
_options: RuntimeGameOptions;
|
||||
|
||||
/**
|
||||
@@ -194,6 +249,7 @@ namespace gdjs {
|
||||
_sessionMetricsInitialized: boolean = false;
|
||||
_disableMetrics: boolean = false;
|
||||
_isPreview: boolean;
|
||||
_isInGameEdition: boolean;
|
||||
|
||||
/**
|
||||
* The capture manager, used to manage captures (screenshots, videos, etc...).
|
||||
@@ -203,12 +259,27 @@ namespace gdjs {
|
||||
/** True if the RuntimeGame has been disposed and should not be used anymore. */
|
||||
_wasDisposed: boolean = false;
|
||||
|
||||
_inGameEditor: InGameEditor | null;
|
||||
|
||||
/**
|
||||
* @param data The object (usually stored in data.json) containing the full project data
|
||||
* @param options The game options
|
||||
*/
|
||||
constructor(data: ProjectData, options?: RuntimeGameOptions) {
|
||||
this._options = options || {};
|
||||
|
||||
this._isPreview = this._options.isPreview || false;
|
||||
if (this._isPreview) {
|
||||
// Check if we need to restore the state from the URL, which is used
|
||||
// when a preview is hard reloaded (search for `hardReload`).
|
||||
const runtimeGameStatusFromUrl = readRuntimeGameStatusFromUrl();
|
||||
if (runtimeGameStatusFromUrl) {
|
||||
this._options.initialRuntimeGameStatus = runtimeGameStatusFromUrl;
|
||||
}
|
||||
}
|
||||
this._isInGameEdition =
|
||||
this._options.initialRuntimeGameStatus?.isInGameEdition || false;
|
||||
|
||||
this._variables = new gdjs.VariablesContainer(data.variables);
|
||||
this._variablesByExtensionName = new Map<
|
||||
string,
|
||||
@@ -237,7 +308,12 @@ namespace gdjs {
|
||||
getGlobalResourceNames(data),
|
||||
data.layouts
|
||||
);
|
||||
|
||||
this._inGameEditor = this._isInGameEdition
|
||||
? new gdjs.InGameEditor(this, data)
|
||||
: null;
|
||||
this._debuggerClient = gdjs.DebuggerClient
|
||||
? new gdjs.DebuggerClient(this)
|
||||
: null;
|
||||
this._effectsManager = new gdjs.EffectsManager();
|
||||
this._maxFPS = this._data.properties.maxFPS;
|
||||
this._minFPS = this._data.properties.minFPS;
|
||||
@@ -265,17 +341,12 @@ namespace gdjs {
|
||||
);
|
||||
this._sceneStack = new gdjs.SceneStack(this);
|
||||
this._inputManager = new gdjs.InputManager();
|
||||
this._injectExternalLayout = this._options.injectExternalLayout || '';
|
||||
this._debuggerClient = gdjs.DebuggerClient
|
||||
? new gdjs.DebuggerClient(this)
|
||||
: null;
|
||||
this._captureManager = gdjs.CaptureManager
|
||||
? new gdjs.CaptureManager(
|
||||
this._renderer,
|
||||
this._options.captureOptions || {}
|
||||
)
|
||||
: null;
|
||||
this._isPreview = this._options.isPreview || false;
|
||||
this._sessionId = null;
|
||||
this._playerId = null;
|
||||
|
||||
@@ -311,6 +382,9 @@ namespace gdjs {
|
||||
* @param projectData The object (usually stored in data.json) containing the full project data
|
||||
*/
|
||||
setProjectData(projectData: ProjectData): void {
|
||||
if (this._inGameEditor) {
|
||||
this._inGameEditor.onProjectDataChange(projectData);
|
||||
}
|
||||
this._data = projectData;
|
||||
this._updateSceneAndExtensionsData();
|
||||
this._resourcesLoader.setResources(
|
||||
@@ -485,6 +559,55 @@ namespace gdjs {
|
||||
return eventsBasedObjectData;
|
||||
}
|
||||
|
||||
getEventsBasedObjectVariantData(
|
||||
type: string,
|
||||
variantName: string
|
||||
): EventsBasedObjectVariantData | null {
|
||||
const eventsBasedObjectData = this.getEventsBasedObjectData(type);
|
||||
if (!eventsBasedObjectData) {
|
||||
return null;
|
||||
}
|
||||
return gdjs.RuntimeGame._getEventsBasedObjectVariantData(
|
||||
eventsBasedObjectData,
|
||||
variantName
|
||||
);
|
||||
}
|
||||
|
||||
static _getEventsBasedObjectVariantData(
|
||||
eventsBasedObjectData: EventsBasedObjectData,
|
||||
variantName: string
|
||||
): EventsBasedObjectVariantData {
|
||||
if (!eventsBasedObjectData.defaultVariant) {
|
||||
eventsBasedObjectData.defaultVariant = {
|
||||
...eventsBasedObjectData,
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
// Legacy events-based objects don't have any instance in their default
|
||||
// variant since there wasn't a graphical editor at the time.
|
||||
// In this case, the editor doesn't allow to choose a variant, but a
|
||||
// variant may have stayed after a user rolled back the extension.
|
||||
// This variant must be ignored to match what the editor shows.
|
||||
const isForcedToOverrideEventsBasedObjectChildrenConfiguration =
|
||||
eventsBasedObjectData.defaultVariant.instances.length == 0;
|
||||
if (isForcedToOverrideEventsBasedObjectChildrenConfiguration) {
|
||||
return eventsBasedObjectData.defaultVariant;
|
||||
}
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
variantIndex < eventsBasedObjectData.variants.length;
|
||||
variantIndex++
|
||||
) {
|
||||
const variantData = eventsBasedObjectData.variants[variantIndex];
|
||||
if (variantData.name === variantName) {
|
||||
usedVariantData = variantData;
|
||||
}
|
||||
}
|
||||
return usedVariantData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data associated to a scene.
|
||||
*
|
||||
@@ -523,6 +646,22 @@ namespace gdjs {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data associated to a scene.
|
||||
*
|
||||
* @param name The name of the scene.
|
||||
* @return The data associated to the scene or null if not found.
|
||||
*/
|
||||
getSceneData(sceneName: string): LayoutData | null {
|
||||
for (let i = 0, len = this._data.layouts.length; i < len; ++i) {
|
||||
const sceneData = this._data.layouts[i];
|
||||
if (sceneData.name == sceneName) {
|
||||
return sceneData;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data associated to an external layout.
|
||||
*
|
||||
@@ -594,7 +733,7 @@ namespace gdjs {
|
||||
|
||||
this._gameResolutionWidth = width;
|
||||
this._gameResolutionHeight = height;
|
||||
if (this._adaptGameResolutionAtRuntime) {
|
||||
if (this._adaptGameResolutionAtRuntime || this._isInGameEdition) {
|
||||
if (
|
||||
gdjs.RuntimeGameRenderer &&
|
||||
gdjs.RuntimeGameRenderer.getWindowInnerWidth &&
|
||||
@@ -606,7 +745,10 @@ namespace gdjs {
|
||||
gdjs.RuntimeGameRenderer.getWindowInnerHeight();
|
||||
|
||||
// Enlarge either the width or the eight to fill the inner window space.
|
||||
if (this._resizeMode === 'adaptWidth') {
|
||||
if (this._isInGameEdition) {
|
||||
this._gameResolutionWidth = windowInnerWidth;
|
||||
this._gameResolutionHeight = windowInnerHeight;
|
||||
} else if (this._resizeMode === 'adaptWidth') {
|
||||
this._gameResolutionWidth =
|
||||
(this._gameResolutionHeight * windowInnerWidth) /
|
||||
windowInnerHeight;
|
||||
@@ -735,9 +877,9 @@ namespace gdjs {
|
||||
if (this._paused === enable) return;
|
||||
|
||||
this._paused = enable;
|
||||
if (this._inGameEditor) this._inGameEditor.activate(enable);
|
||||
if (this._debuggerClient) {
|
||||
if (this._paused) this._debuggerClient.sendGamePaused();
|
||||
else this._debuggerClient.sendGameResumed();
|
||||
this._debuggerClient.sendRuntimeGameStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -914,11 +1056,16 @@ namespace gdjs {
|
||||
await loadAssets(onProgress);
|
||||
|
||||
await loadingScreen.unload();
|
||||
this.pause(false);
|
||||
|
||||
if (!this._isInGameEdition) {
|
||||
this.pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
private _getFirstSceneName(): string {
|
||||
const firstSceneName = this._data.firstLayout;
|
||||
const firstSceneName =
|
||||
this._options.initialRuntimeGameStatus?.sceneName ||
|
||||
this._data.firstLayout;
|
||||
return this.hasScene(firstSceneName)
|
||||
? firstSceneName
|
||||
: // There is always at least a scene
|
||||
@@ -938,10 +1085,41 @@ namespace gdjs {
|
||||
this._forceGameResolutionUpdate();
|
||||
|
||||
// Load the first scene
|
||||
this._sceneStack.push(
|
||||
this._getFirstSceneName(),
|
||||
this._injectExternalLayout
|
||||
);
|
||||
const sceneName = this._getFirstSceneName();
|
||||
const externalLayoutName =
|
||||
this._options.initialRuntimeGameStatus?.injectedExternalLayoutName ||
|
||||
null;
|
||||
if (this._inGameEditor) {
|
||||
const eventsBasedObjectType =
|
||||
this._options.initialRuntimeGameStatus?.eventsBasedObjectType ||
|
||||
null;
|
||||
const eventsBasedObjectVariantName =
|
||||
this._options.initialRuntimeGameStatus
|
||||
?.eventsBasedObjectVariantName || null;
|
||||
const editorId =
|
||||
this._options.initialRuntimeGameStatus?.editorId || null;
|
||||
const editorCamera3D =
|
||||
this._options.initialRuntimeGameStatus?.editorCamera3D || null;
|
||||
this._inGameEditor.switchToSceneOrVariant(
|
||||
editorId,
|
||||
sceneName,
|
||||
externalLayoutName,
|
||||
eventsBasedObjectType,
|
||||
eventsBasedObjectVariantName,
|
||||
editorCamera3D
|
||||
);
|
||||
} else {
|
||||
if (sceneName) {
|
||||
this.getSceneStack().replace({
|
||||
sceneName,
|
||||
externalLayoutName:
|
||||
externalLayoutName === null ? undefined : externalLayoutName,
|
||||
skipCreatingInstancesFromScene: !!externalLayoutName,
|
||||
clear: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._watermark.displayAtStartup();
|
||||
|
||||
//Uncomment to profile the first x frames of the game.
|
||||
@@ -967,15 +1145,33 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
// The standard game loop
|
||||
let lastFrameSceneName: string | null = null;
|
||||
let accumulatedElapsedTime = 0;
|
||||
this._hasJustResumed = false;
|
||||
this._renderer.startGameLoop((lastCallElapsedTime) => {
|
||||
try {
|
||||
if (this._paused) {
|
||||
return true;
|
||||
// Watch the scene name to automatically update debugger when a scene is changed.
|
||||
if (this._debuggerClient) {
|
||||
const currentScene = (
|
||||
this._inGameEditor || this.getSceneStack()
|
||||
).getCurrentScene();
|
||||
if (
|
||||
currentScene &&
|
||||
currentScene.getName() !== lastFrameSceneName
|
||||
) {
|
||||
lastFrameSceneName = currentScene.getName();
|
||||
this._debuggerClient.sendRuntimeGameStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the frame if we rendering frames too fast
|
||||
// If the game is edited, update the target framerate according to interactions.
|
||||
// Do it now (before frame skip), so that if a user interaction happens
|
||||
// we don't wait for a frame to pass at the current, probably very slow framerate.
|
||||
if (this._paused && this._inGameEditor) {
|
||||
this._inGameEditor.updateTargetFramerate(lastCallElapsedTime);
|
||||
}
|
||||
|
||||
// Skip the frame if we rendering frames too fast.
|
||||
accumulatedElapsedTime += lastCallElapsedTime;
|
||||
if (
|
||||
this._maxFPS > 0 &&
|
||||
@@ -992,17 +1188,36 @@ namespace gdjs {
|
||||
|
||||
// Manage resize events.
|
||||
if (this._notifyScenesForGameResolutionResize) {
|
||||
this._sceneStack.onGameResolutionResized();
|
||||
if (this._inGameEditor) {
|
||||
this._inGameEditor.onGameResolutionResized();
|
||||
} else {
|
||||
this._sceneStack.onGameResolutionResized();
|
||||
}
|
||||
this._notifyScenesForGameResolutionResize = false;
|
||||
}
|
||||
|
||||
// Render and step the scene.
|
||||
if (this._sceneStack.step(elapsedTime)) {
|
||||
this.getInputManager().onFrameEnded();
|
||||
// Render and possibly step the game.
|
||||
if (this._paused) {
|
||||
if (this._inGameEditor) {
|
||||
// The game is paused for edition: the in-game editor runs and render
|
||||
// the scene.
|
||||
this._inGameEditor.updateAndRender();
|
||||
} else {
|
||||
// The game is paused (for debugging): the rendering of the scene is done,
|
||||
// but the game logic is not executed (no full "step").
|
||||
this._sceneStack.renderWithoutStep();
|
||||
}
|
||||
} else {
|
||||
// The game is not paused (and so, not edited): both the rendering
|
||||
// and game logic (a full "step") is executed.
|
||||
if (!this._sceneStack.step(elapsedTime)) {
|
||||
return false; // Return if game asked to be stopped.
|
||||
}
|
||||
this._hasJustResumed = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
this.getInputManager().onFrameEnded();
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (this._debuggerClient)
|
||||
this._debuggerClient.onUncaughtException(e);
|
||||
@@ -1323,6 +1538,37 @@ namespace gdjs {
|
||||
return this._isPreview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the game loop is paused, for debugging/edition purposes.
|
||||
* @returns true if the current game is paused
|
||||
*/
|
||||
isPaused(): boolean {
|
||||
return this._paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the game should display in-game edition tools or not.
|
||||
* @returns true if the current game is being edited.
|
||||
*/
|
||||
isInGameEdition(): boolean {
|
||||
return this._isInGameEdition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return in-game editor.
|
||||
*/
|
||||
getInGameEditor(): InGameEditor | null {
|
||||
return this._inGameEditor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum FPS of the game.
|
||||
* @param maximumFps The maximum FPS.
|
||||
*/
|
||||
setMaximumFps(maximumFps: integer) {
|
||||
this._maxFPS = maximumFps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the game should call GDevelop development APIs or not.
|
||||
*
|
||||
|
@@ -1437,6 +1437,22 @@ namespace gdjs {
|
||||
return this.hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the object before any custom size is applied.
|
||||
* @return The width of the object
|
||||
*/
|
||||
getOriginalWidth(): float {
|
||||
return this.getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the object before any custom size is applied.
|
||||
* @return The width of the object
|
||||
*/
|
||||
getOriginalHeight(): float {
|
||||
return this.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the width of the object, if applicable.
|
||||
* @param width The new width in pixels.
|
||||
|
@@ -128,7 +128,8 @@ namespace gdjs {
|
||||
|
||||
/**
|
||||
* Load the runtime scene from the given scene.
|
||||
* @param sceneAndExtensionsData An object containing the scene data.
|
||||
* @param sceneAndExtensionsData The data of the scene and extension variables to be loaded.
|
||||
* @param options Options to change what is loaded.
|
||||
* @see gdjs.RuntimeGame#getSceneAndExtensionsData
|
||||
*/
|
||||
loadFromScene(
|
||||
@@ -195,8 +196,8 @@ namespace gdjs {
|
||||
this.registerObject(sceneData.objects[i]);
|
||||
}
|
||||
|
||||
// Create initial instances of objects
|
||||
if (!options || !options.skipCreatingInstances)
|
||||
//Create initial instances of objects
|
||||
if (!options || !options.skipCreatingInstances) {
|
||||
this.createObjectsFrom(
|
||||
sceneData.instances,
|
||||
0,
|
||||
@@ -205,6 +206,7 @@ namespace gdjs {
|
||||
/*trackByPersistentUuid=*/
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Set up the default z order (for objects created from events)
|
||||
this._setLayerDefaultZOrders();
|
||||
@@ -377,7 +379,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Step and render the scene.
|
||||
* Step (execute the game logic) and render the scene.
|
||||
* @param elapsedTime In milliseconds
|
||||
* @return true if the game loop should continue, false if a scene change/push/pop
|
||||
* or a game stop was requested.
|
||||
@@ -437,6 +439,21 @@ namespace gdjs {
|
||||
if (this._profiler) {
|
||||
this._profiler.end('callbacks and extensions (post-events)');
|
||||
}
|
||||
|
||||
this.render();
|
||||
this._isJustResumed = false;
|
||||
if (this._profiler) {
|
||||
this._profiler.end('render');
|
||||
}
|
||||
if (this._profiler) {
|
||||
this._profiler.endFrame();
|
||||
}
|
||||
return !!this.getRequestedChange();
|
||||
}
|
||||
/**
|
||||
* Render the scene (but do not execute the game logic).
|
||||
*/
|
||||
render() {
|
||||
if (this._profiler) {
|
||||
this._profiler.begin('objects (pre-render, effects update)');
|
||||
}
|
||||
@@ -466,21 +483,6 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
this._isJustResumed = false;
|
||||
this.render();
|
||||
if (this._profiler) {
|
||||
this._profiler.end('render');
|
||||
}
|
||||
if (this._profiler) {
|
||||
this._profiler.endFrame();
|
||||
}
|
||||
return !!this.getRequestedChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the PIXI container associated to the runtimeScene.
|
||||
*/
|
||||
render() {
|
||||
this._renderer.render();
|
||||
}
|
||||
|
||||
|
@@ -93,10 +93,10 @@ namespace gdjs {
|
||||
renderWithoutStep(): boolean {
|
||||
this._throwIfDisposed();
|
||||
|
||||
if (this._stack.length === 0) {
|
||||
const currentScene = this.getCurrentScene();
|
||||
if (!currentScene) {
|
||||
return false;
|
||||
}
|
||||
const currentScene = this._stack[this._stack.length - 1];
|
||||
currentScene.render();
|
||||
return true;
|
||||
}
|
||||
|
@@ -190,15 +190,13 @@ namespace gdjs {
|
||||
this.setWidth(initialInstanceData.width);
|
||||
this.setHeight(initialInstanceData.height);
|
||||
}
|
||||
if (initialInstanceData.opacity !== undefined) {
|
||||
this.setOpacity(initialInstanceData.opacity);
|
||||
}
|
||||
if (initialInstanceData.flippedX) {
|
||||
this.flipX(initialInstanceData.flippedX);
|
||||
}
|
||||
if (initialInstanceData.flippedY) {
|
||||
this.flipY(initialInstanceData.flippedY);
|
||||
}
|
||||
this.setOpacity(
|
||||
initialInstanceData.opacity === undefined
|
||||
? 255
|
||||
: initialInstanceData.opacity
|
||||
);
|
||||
this.flipX(!!initialInstanceData.flippedX);
|
||||
this.flipY(!!initialInstanceData.flippedY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -853,6 +851,14 @@ namespace gdjs {
|
||||
this.setHeight(newHeight);
|
||||
}
|
||||
|
||||
override getOriginalWidth(): float {
|
||||
return this._renderer.getUnscaledWidth() * this._preScale;
|
||||
}
|
||||
|
||||
override getOriginalHeight(): float {
|
||||
return this._renderer.getUnscaledHeight() * this._preScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on X and Y axis of the object.
|
||||
*
|
||||
|
6
GDJS/Runtime/types/global-three-addons.d.ts
vendored
6
GDJS/Runtime/types/global-three-addons.d.ts
vendored
@@ -2,7 +2,10 @@ import { GLTFLoader, GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils';
|
||||
|
||||
import { TransformControls } from 'three/examples/jsm/controls/TransformControls';
|
||||
import { SelectionBox } from 'three/examples/jsm/interactive/SelectionBox';
|
||||
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
|
||||
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
|
||||
import { Pass } from 'three/examples/jsm/postprocessing/Pass';
|
||||
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
|
||||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
|
||||
@@ -21,7 +24,10 @@ declare global {
|
||||
GLTF,
|
||||
DRACOLoader,
|
||||
SkeletonUtils,
|
||||
TransformControls,
|
||||
SelectionBox,
|
||||
EffectComposer,
|
||||
OutlinePass,
|
||||
Pass,
|
||||
RenderPass,
|
||||
ShaderPass,
|
||||
|
30
GDJS/Runtime/types/project-data.d.ts
vendored
30
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -18,6 +18,7 @@ declare interface ProjectData {
|
||||
layouts: LayoutData[];
|
||||
externalLayouts: ExternalLayoutData[];
|
||||
eventsFunctionsExtensions: EventsFunctionsExtensionData[];
|
||||
areEffectsHiddenInEditor?: boolean;
|
||||
}
|
||||
|
||||
declare interface EventsFunctionsVariablesData {
|
||||
@@ -288,6 +289,21 @@ declare interface LayoutData extends InstanceContainerData {
|
||||
usedResources: ResourceReference[];
|
||||
resourcesPreloading?: 'at-startup' | 'never' | 'inherit';
|
||||
resourcesUnloading?: 'at-scene-exit' | 'never' | 'inherit';
|
||||
uiSettings: InstancesEditorSettings;
|
||||
}
|
||||
|
||||
declare interface InstancesEditorSettings {
|
||||
grid: boolean;
|
||||
gridType: 'rectangular' | 'isometric';
|
||||
gridWidth: float;
|
||||
gridHeight: float;
|
||||
gridDepth?: float;
|
||||
gridOffsetX: float;
|
||||
gridOffsetY: float;
|
||||
gridOffsetZ?: float;
|
||||
gridColor: int;
|
||||
gridAlpha: float;
|
||||
snap: boolean;
|
||||
}
|
||||
|
||||
declare interface LayoutNetworkSyncData {
|
||||
@@ -383,6 +399,8 @@ declare interface EventsBasedObjectVariantData extends InstanceContainerData {
|
||||
instances: InstanceData[];
|
||||
objects: ObjectData[];
|
||||
layers: LayerData[];
|
||||
usedResources: ResourceReference[];
|
||||
editionSettings: InstancesEditorSettings;
|
||||
}
|
||||
|
||||
declare interface BehaviorSharedData {
|
||||
@@ -394,13 +412,17 @@ declare interface ExternalLayoutData {
|
||||
name: string;
|
||||
associatedLayout: string;
|
||||
instances: InstanceData[];
|
||||
editionSettings: InstancesEditorSettings;
|
||||
}
|
||||
|
||||
declare interface InstanceData {
|
||||
declare interface InstancePersistentUuidData {
|
||||
persistentUuid: string;
|
||||
}
|
||||
|
||||
declare interface InstanceData extends InstancePersistentUuidData {
|
||||
layer: string;
|
||||
locked: boolean;
|
||||
locked?: boolean;
|
||||
sealed?: boolean;
|
||||
name: string;
|
||||
|
||||
x: number;
|
||||
@@ -451,8 +473,10 @@ declare interface LayerData {
|
||||
camera3DFieldOfView?: float;
|
||||
camera3DFarPlaneDistance?: float;
|
||||
camera3DNearPlaneDistance?: float;
|
||||
camera2DPlaneMaxDrawingDistance?: float;
|
||||
isLightingLayer: boolean;
|
||||
followBaseLayerCamera: boolean;
|
||||
isLocked?: boolean;
|
||||
}
|
||||
|
||||
declare interface CameraData {
|
||||
@@ -497,7 +521,7 @@ declare interface ProjectPropertiesData {
|
||||
pixelsRounding: boolean;
|
||||
antialiasingMode: 'none' | 'MSAA';
|
||||
antialisingEnabledOnMobile: boolean;
|
||||
sizeOnStartupMode: string;
|
||||
sizeOnStartupMode: '' | 'scaleOuter' | 'adaptWidth' | 'adaptHeight';
|
||||
version: string;
|
||||
name: string;
|
||||
author: string;
|
||||
|
@@ -19,6 +19,7 @@ const allowedExtensions = [
|
||||
'.map',
|
||||
'.wasm',
|
||||
'.txt',
|
||||
'.png',
|
||||
];
|
||||
|
||||
// These extensions will be built with esbuild (the other will be copied).
|
||||
@@ -42,6 +43,7 @@ const untransformedPaths = [
|
||||
'GDJS/Runtime/FacebookInstantGames',
|
||||
'GDJS/Runtime/libs/CocoonJS',
|
||||
'GDJS/Runtime/libs/rbush.js',
|
||||
'GDJS/Runtime/InGameEditor/Resources/primitivedrawingicon.png',
|
||||
|
||||
// Extensions pre-built files:
|
||||
'Extensions/Leaderboards/sha256.js',
|
||||
|
@@ -77,6 +77,19 @@ gdjs.getPixiRuntimeGameWithAssets = () => {
|
||||
title: '',
|
||||
variables: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
}
|
||||
},
|
||||
],
|
||||
externalLayouts: [],
|
||||
@@ -210,6 +223,7 @@ gdjs.getPixiRuntimeGameWithAssets = () => {
|
||||
areaMaxY: 64,
|
||||
areaMaxZ: 0,
|
||||
_initialInnerArea: null,
|
||||
variants: [],
|
||||
},
|
||||
],
|
||||
sceneVariables: [],
|
||||
|
@@ -22,6 +22,19 @@ describe('gdjs.ResourceLoader', () => {
|
||||
title: '',
|
||||
variables: [],
|
||||
usedResources,
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
13
GDJS/tests/tests/effects.js
vendored
13
GDJS/tests/tests/effects.js
vendored
@@ -107,6 +107,19 @@ describe('gdjs.EffectsManager', () => {
|
||||
objects: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
}
|
||||
}, usedExtensionsWithVariablesData: []});
|
||||
|
||||
const runtimeLayer = runtimeScene.getLayer('');
|
||||
|
@@ -185,6 +185,19 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
|
||||
? instances.map((instance) => ({ ...defaultInstance, ...instance }))
|
||||
: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -217,6 +230,20 @@ describe('gdjs.HotReloader._hotReloadRuntimeGame', () => {
|
||||
_initialInnerArea: null,
|
||||
isInnerAreaFollowingParentSize: false,
|
||||
variants: [],
|
||||
usedResources: [],
|
||||
editionSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -47,6 +47,19 @@ describe('gdjs.RuntimeScene integration tests', function () {
|
||||
],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
}
|
||||
}, usedExtensionsWithVariablesData: []});
|
||||
|
||||
const object = runtimeScene.createObject('Object1');
|
||||
|
@@ -21,6 +21,20 @@ describe('gdjs.SceneStack', () => {
|
||||
title: '',
|
||||
variables: [],
|
||||
usedResources,
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
/** @type {"rectangular" | "isometric"} */
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -602,6 +602,8 @@ interface Project {
|
||||
boolean IsFolderProject();
|
||||
void SetUseDeprecatedZeroAsDefaultZOrder(boolean enable);
|
||||
boolean GetUseDeprecatedZeroAsDefaultZOrder();
|
||||
boolean AreEffectsHiddenInEditor();
|
||||
void SetEffectsHiddenInEditor(boolean enable);
|
||||
|
||||
void SetLastCompilationDirectory([Const] DOMString path);
|
||||
[Const, Ref] DOMString GetLastCompilationDirectory();
|
||||
@@ -1119,6 +1121,8 @@ interface Layer {
|
||||
void SetCamera3DFarPlaneDistance(double distance);
|
||||
double GetCamera3DFieldOfView();
|
||||
void SetCamera3DFieldOfView(double angle);
|
||||
double GetCamera2DPlaneMaxDrawingDistance();
|
||||
void SetCamera2DPlaneMaxDrawingDistance(double distance);
|
||||
|
||||
void SetAmbientLightColor(unsigned long r, unsigned long g, unsigned long b);
|
||||
unsigned long GetAmbientLightColorRed();
|
||||
@@ -1433,6 +1437,13 @@ interface InitialInstance {
|
||||
void SetCustomDepth(double depth);
|
||||
double GetCustomDepth();
|
||||
|
||||
double GetDefaultWidth();
|
||||
double GetDefaultHeight();
|
||||
double GetDefaultDepth();
|
||||
void SetDefaultWidth(double width);
|
||||
void SetDefaultHeight(double height);
|
||||
void SetDefaultDepth(double depth);
|
||||
|
||||
[Ref] InitialInstance ResetPersistentUuid();
|
||||
[Const, Ref] DOMString GetPersistentUuid();
|
||||
|
||||
@@ -2876,6 +2887,10 @@ interface UsedExtensionsFinder {
|
||||
[Value] UsedExtensionsResult STATIC_ScanProject([Ref] Project project);
|
||||
};
|
||||
|
||||
interface UsedObjectTypeFinder {
|
||||
boolean STATIC_ScanProject([Ref] Project project, [Const] DOMString objectType);
|
||||
};
|
||||
|
||||
interface ExampleExtensionUsagesFinder {
|
||||
[Value] SetString STATIC_GetUsedExtensions([Ref] Project project);
|
||||
};
|
||||
@@ -4005,11 +4020,26 @@ interface PreviewExportOptions {
|
||||
[Ref] PreviewExportOptions SetFallbackAuthor([Const] DOMString id, [Const] DOMString username);
|
||||
[Ref] PreviewExportOptions SetAuthenticatedPlayer([Const] DOMString playerId, [Const] DOMString playerUsername, [Const] DOMString playerToken);
|
||||
[Ref] PreviewExportOptions SetExternalLayoutName([Const] DOMString externalLayoutName);
|
||||
[Ref] PreviewExportOptions SetEventsBasedObjectType([Const] DOMString eventsBasedObjectType);
|
||||
[Ref] PreviewExportOptions SetEventsBasedObjectVariantName([Const] DOMString eventsBasedObjectVariantName);
|
||||
[Ref] PreviewExportOptions SetIncludeFileHash([Const] DOMString includeFile, long hash);
|
||||
[Ref] PreviewExportOptions SetProjectDataOnlyExport(boolean enable);
|
||||
[Ref] PreviewExportOptions SetShouldClearExportFolder(boolean enable);
|
||||
[Ref] PreviewExportOptions SetShouldReloadProjectData(boolean enable);
|
||||
[Ref] PreviewExportOptions SetShouldReloadLibraries(boolean enable);
|
||||
[Ref] PreviewExportOptions SetShouldGenerateScenesEventsCode(boolean enable);
|
||||
[Ref] PreviewExportOptions SetNativeMobileApp(boolean enable);
|
||||
[Ref] PreviewExportOptions SetFullLoadingScreen(boolean enable);
|
||||
[Ref] PreviewExportOptions SetIsDevelopmentEnvironment(boolean enable);
|
||||
[Ref] PreviewExportOptions SetIsInGameEdition(boolean enable);
|
||||
[Ref] PreviewExportOptions SetEditorId([Const] DOMString editorId);
|
||||
[Ref] PreviewExportOptions SetEditorCameraState3D(
|
||||
[Const] DOMString cameraMode,
|
||||
double positionX,
|
||||
double positionY,
|
||||
double positionZ,
|
||||
double rotationAngle,
|
||||
double elevationAngle,
|
||||
double distance);
|
||||
[Ref] PreviewExportOptions SetNonRuntimeScriptsCacheBurst(unsigned long value);
|
||||
[Ref] PreviewExportOptions SetElectronRemoteRequirePath([Const] DOMString electronRemoteRequirePath);
|
||||
[Ref] PreviewExportOptions SetGDevelopResourceToken([Const] DOMString gdevelopResourceToken);
|
||||
@@ -4036,6 +4066,13 @@ interface Exporter {
|
||||
|
||||
boolean ExportProjectForPixiPreview([Const, Ref] PreviewExportOptions options);
|
||||
boolean ExportWholePixiProject([Const, Ref] ExportOptions options);
|
||||
void SerializeProjectData(
|
||||
[Const, Ref] Project project,
|
||||
[Const, Ref] PreviewExportOptions options,
|
||||
[Ref] SerializerElement projectDataElement);
|
||||
void SerializeRuntimeGameOptions(
|
||||
[Const, Ref] PreviewExportOptions options,
|
||||
[Ref] SerializerElement runtimeGameOptionsElement);
|
||||
|
||||
[Const, Ref] DOMString GetLastError();
|
||||
};
|
||||
|
@@ -44,6 +44,7 @@
|
||||
#include <GDCore/IDE/Events/InstructionsTypeRenamer.h>
|
||||
#include <GDCore/IDE/Events/TextFormatting.h>
|
||||
#include <GDCore/IDE/Events/UsedExtensionsFinder.h>
|
||||
#include <GDCore/IDE/Events/UsedObjectTypeFinder.h>
|
||||
#include <GDCore/IDE/Events/ExampleExtensionUsagesFinder.h>
|
||||
#include <GDCore/IDE/EventsFunctionTools.h>
|
||||
#include <GDCore/IDE/ObjectVariableHelper.h>
|
||||
@@ -826,6 +827,7 @@ typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice
|
||||
|
||||
#define STATIC_ScanProject ScanProject
|
||||
#define STATIC_GetUsedExtensions GetUsedExtensions
|
||||
#define STATIC_SerializeProjectData SerializeProjectData
|
||||
|
||||
#define STATIC_ApplyTranslation ApplyTranslation
|
||||
#define STATIC_GetUndefined GetUndefined
|
||||
|
@@ -423,6 +423,14 @@ type CustomObjectConfiguration_EdgeAnchor = 0 | 1 | 2 | 3 | 4`
|
||||
'types/gdexporter.js'
|
||||
);
|
||||
|
||||
// Rename classes from GDJS:
|
||||
shell.sed(
|
||||
'-i',
|
||||
'declare class gdExporterHelper {',
|
||||
'declare class gdjsExporterHelper {',
|
||||
'types/gdexporter.js'
|
||||
);
|
||||
|
||||
// Improve typing of resources kind.
|
||||
shell.sed(
|
||||
'-i',
|
||||
|
26
GDevelop.js/types.d.ts
vendored
26
GDevelop.js/types.d.ts
vendored
@@ -565,6 +565,8 @@ export class Project extends EmscriptenObject {
|
||||
isFolderProject(): boolean;
|
||||
setUseDeprecatedZeroAsDefaultZOrder(enable: boolean): void;
|
||||
getUseDeprecatedZeroAsDefaultZOrder(): boolean;
|
||||
areEffectsHiddenInEditor(): boolean;
|
||||
setEffectsHiddenInEditor(enable: boolean): void;
|
||||
setLastCompilationDirectory(path: string): void;
|
||||
getLastCompilationDirectory(): string;
|
||||
getExtensionProperties(): ExtensionProperties;
|
||||
@@ -923,6 +925,8 @@ export class Layer extends EmscriptenObject {
|
||||
setCamera3DFarPlaneDistance(distance: number): void;
|
||||
getCamera3DFieldOfView(): number;
|
||||
setCamera3DFieldOfView(angle: number): void;
|
||||
getCamera2DPlaneMaxDrawingDistance(): number;
|
||||
setCamera2DPlaneMaxDrawingDistance(distance: number): void;
|
||||
setAmbientLightColor(r: number, g: number, b: number): void;
|
||||
getAmbientLightColorRed(): number;
|
||||
getAmbientLightColorGreen(): number;
|
||||
@@ -1193,6 +1197,12 @@ export class InitialInstance extends EmscriptenObject {
|
||||
getCustomHeight(): number;
|
||||
setCustomDepth(depth: number): void;
|
||||
getCustomDepth(): number;
|
||||
getDefaultWidth(): number;
|
||||
getDefaultHeight(): number;
|
||||
getDefaultDepth(): number;
|
||||
setDefaultWidth(width: number): void;
|
||||
setDefaultHeight(height: number): void;
|
||||
setDefaultDepth(depth: number): void;
|
||||
resetPersistentUuid(): InitialInstance;
|
||||
getPersistentUuid(): string;
|
||||
updateCustomProperty(name: string, value: string, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer): void;
|
||||
@@ -2054,6 +2064,10 @@ export class UsedExtensionsFinder extends EmscriptenObject {
|
||||
static scanProject(project: Project): UsedExtensionsResult;
|
||||
}
|
||||
|
||||
export class UsedObjectTypeFinder extends EmscriptenObject {
|
||||
static scanProject(project: Project, objectType: string): boolean;
|
||||
}
|
||||
|
||||
export class ExampleExtensionUsagesFinder extends EmscriptenObject {
|
||||
static getUsedExtensions(project: Project): SetString;
|
||||
}
|
||||
@@ -2968,11 +2982,19 @@ export class PreviewExportOptions extends EmscriptenObject {
|
||||
setFallbackAuthor(id: string, username: string): PreviewExportOptions;
|
||||
setAuthenticatedPlayer(playerId: string, playerUsername: string, playerToken: string): PreviewExportOptions;
|
||||
setExternalLayoutName(externalLayoutName: string): PreviewExportOptions;
|
||||
setEventsBasedObjectType(eventsBasedObjectType: string): PreviewExportOptions;
|
||||
setEventsBasedObjectVariantName(eventsBasedObjectVariantName: string): PreviewExportOptions;
|
||||
setIncludeFileHash(includeFile: string, hash: number): PreviewExportOptions;
|
||||
setProjectDataOnlyExport(enable: boolean): PreviewExportOptions;
|
||||
setShouldClearExportFolder(enable: boolean): PreviewExportOptions;
|
||||
setShouldReloadProjectData(enable: boolean): PreviewExportOptions;
|
||||
setShouldReloadLibraries(enable: boolean): PreviewExportOptions;
|
||||
setShouldGenerateScenesEventsCode(enable: boolean): PreviewExportOptions;
|
||||
setNativeMobileApp(enable: boolean): PreviewExportOptions;
|
||||
setFullLoadingScreen(enable: boolean): PreviewExportOptions;
|
||||
setIsDevelopmentEnvironment(enable: boolean): PreviewExportOptions;
|
||||
setIsInGameEdition(enable: boolean): PreviewExportOptions;
|
||||
setEditorId(editorId: string): PreviewExportOptions;
|
||||
setEditorCameraState3D(cameraMode: string, positionX: number, positionY: number, positionZ: number, rotationAngle: number, elevationAngle: number, distance: number): PreviewExportOptions;
|
||||
setNonRuntimeScriptsCacheBurst(value: number): PreviewExportOptions;
|
||||
setElectronRemoteRequirePath(electronRemoteRequirePath: string): PreviewExportOptions;
|
||||
setGDevelopResourceToken(gdevelopResourceToken: string): PreviewExportOptions;
|
||||
@@ -2996,6 +3018,8 @@ export class Exporter extends EmscriptenObject {
|
||||
setCodeOutputDirectory(path: string): void;
|
||||
exportProjectForPixiPreview(options: PreviewExportOptions): boolean;
|
||||
exportWholePixiProject(options: ExportOptions): boolean;
|
||||
serializeProjectData(project: Project, options: PreviewExportOptions, projectDataElement: SerializerElement): void;
|
||||
serializeRuntimeGameOptions(options: PreviewExportOptions, runtimeGameOptionsElement: SerializerElement): void;
|
||||
getLastError(): string;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user