Compare commits

...

86 Commits

Author SHA1 Message Date
Davy Hélard
ae43020c6e Force the Events-based editor to show. 2022-09-29 17:24:00 +02:00
Davy Hélard
7a271fc372 Fix default camera origin update and custom object rendering location. 2022-09-29 17:19:12 +02:00
Davy Hélard
96952e4e6d The generated code calls super.onCreated 2022-09-29 17:19:11 +02:00
Davy Hélard
266f36a160 Rename some runtimeScene to instanceContainer. 2022-09-29 17:19:11 +02:00
Davy Hélard
be5c5ac7cc Review changes. 2022-09-29 17:19:11 +02:00
Davy Hélard
e4b6f90c60 Add onHotReloading life-cycle function. 2022-09-29 17:19:10 +02:00
Davy Hélard
e0a317b624 Fix icon paths. 2022-09-29 17:19:10 +02:00
Davy Hélard
35f5529e9e Pass new configurations to children objects at hot reload. 2022-09-29 17:19:10 +02:00
Davy Hélard
8339567b8b Increase the layout benchmark timeout back to the previous value. 2022-09-29 17:19:09 +02:00
Davy Hélard
c0b7e30b60 Increase the layout benchmark timeout. 2022-09-29 17:19:09 +02:00
Davy Hélard
dfcade50f5 Optimize points transformation. 2022-09-29 17:19:09 +02:00
Davy Hélard
c91e5388e2 Fix cursor coordinate transformation. 2022-09-29 17:19:08 +02:00
Davy Hélard
8e1f9f2d43 Increase the layout benchmark timeout a bit more again. 2022-09-29 17:19:08 +02:00
Davy Hélard
c6180346cc Increase the layout benchmark timeout a lot more. 2022-09-29 17:19:08 +02:00
Davy Hélard
b727fc56d4 Increase the layout benchmark timeout. 2022-09-29 17:19:08 +02:00
Davy Hélard
5995b17b7d Optimize coordinate transformation a bit. 2022-09-29 17:19:07 +02:00
Davy Hélard
be53edb170 Some documentation 2022-09-29 17:19:07 +02:00
Davy Hélard
98db2e9089 Fix type. 2022-09-29 17:19:07 +02:00
Davy Hélard
e6a54bc0c3 Fix test configuration. 2022-09-29 17:19:06 +02:00
Davy Hélard
68e7bb1087 Declare some parameters as optional in gdMultipleInstructionMetadata. 2022-09-29 17:19:06 +02:00
Davy Hélard
f49899fd15 Declare opacity functions. 2022-09-29 17:19:06 +02:00
Davy Hélard
31f1a0108f Fix GDJS tests. 2022-09-29 17:19:06 +02:00
Davy Hélard
3ef59a94e5 Fix LinkedObject object deletion handling. 2022-09-29 17:19:05 +02:00
Davy Hélard
d5b997e37b Remove useless parameter in getElapsedTime calls. 2022-09-29 17:19:05 +02:00
Davy Hélard
b3fb6e02f6 Remove the hack to name Pixi objects. 2022-09-29 17:19:05 +02:00
Davy Hélard
29d226f18e Mark runtimeScene parameter as deprecated in rotation methods. 2022-09-29 17:19:04 +02:00
Davy Hélard
2695766712 Rename runtimeScene variables. 2022-09-29 17:19:04 +02:00
Davy Hélard
b9fb35fd0f Rename InstacesContainer to InstanceContainer. 2022-09-29 17:19:04 +02:00
Davy Hélard
ff54b6abd5 Add tests for custom object composition. 2022-09-29 17:19:03 +02:00
Davy Hélard
269876cf54 Fix test typing. 2022-09-29 17:19:03 +02:00
Davy Hélard
30cfdd6bf3 Handle events-based object profiling. 2022-09-29 17:19:02 +02:00
Davy Hélard
d54b568a01 Fix life-cycle method calls. 2022-09-29 17:19:02 +02:00
Davy Hélard
236a5b3344 Fix center calculus and add tests on custom objects transformation. 2022-09-29 17:19:02 +02:00
Davy Hélard
07130c766a Define common events-based Objects functions. 2022-09-29 17:19:01 +02:00
Davy Hélard
e66ac4af0e Use child-object configurations as default values. 2022-09-29 17:19:01 +02:00
Davy Hélard
a43eb88661 Fix object selection when a function is removed. 2022-09-29 17:19:01 +02:00
Davy Hélard
70c1d600d6 Remove doStepPreEvents. 2022-09-29 17:19:01 +02:00
Davy Hélard
efb076177d In JS extensions, set the right type for the instance container in behavior and object constructors. 2022-09-29 17:19:00 +02:00
Davy Hélard
ebe9c5d617 Fix renderer position update. 2022-09-29 17:19:00 +02:00
Davy Hélard
215411c5bd Fix dependencies analyzing at preview. 2022-09-29 17:19:00 +02:00
Davy Hélard
82f15bc2ac Fix the behavior access in the context. 2022-09-29 17:18:59 +02:00
Davy Hélard
c58c7a83d3 Fix the Tween behavior. 2022-09-29 17:18:59 +02:00
Davy Hélard
38343da8e7 Call behavior life-cycle functions. 2022-09-29 17:18:59 +02:00
Davy Hélard
4358134ece Use invalidateHitboxes everywhere. 2022-09-29 17:18:58 +02:00
Davy Hélard
d9fd695fda Factorize visual debugger, fix object creation, fix hitboxes update. 2022-09-29 17:18:58 +02:00
Davy Hélard
4a71297814 Fix position transformation now go though all layers of the branch. 2022-09-29 17:18:58 +02:00
Davy Hélard
295140e44a Forgotten files from: Define some parameter type 2022-09-29 17:18:57 +02:00
Davy Hélard
f5289ea1e2 Move up updateObjectsForces. 2022-09-29 17:18:57 +02:00
Davy Hélard
76788aa058 Fix tests 2022-09-29 17:18:57 +02:00
Davy Hélard
2231836ffd Define some parameter types. 2022-09-29 17:18:57 +02:00
Davy Hélard
e3e121fe96 Fix generated parameters. 2022-09-29 17:18:56 +02:00
Davy Hélard
622a641adf Fix position calculus. 2022-09-29 17:18:56 +02:00
Davy Hélard
389572d373 Format 2022-09-29 17:18:56 +02:00
Davy Hélard
2c15e42fa2 Fix object list and life-cycle methods. 2022-09-29 17:18:55 +02:00
Davy Hélard
2219520159 Remove logs 2022-09-29 17:18:55 +02:00
Davy Hélard
1e348a2c7b Fix hit boxes updating. 2022-09-29 17:18:55 +02:00
Davy Hélard
cec82d62d5 EventsBasedObject code generation. 2022-09-29 17:18:54 +02:00
Davy Hélard
bbd901467c Fix CustomObjectConfiguration copy. 2022-09-29 17:18:54 +02:00
Davy Hélard
b1f6ed1594 Keep EventBasedObject in data. 2022-09-29 17:18:54 +02:00
Davy Hélard
a199328e8f Index EventsBasedObjectData 2022-09-29 17:18:53 +02:00
Davy Hélard
46b5cd1ee6 CustomObject implementation. 2022-09-29 17:18:53 +02:00
Davy Hélard
f52c9f6c78 Extract RuntimeInstancesContainer from RuntimeScene. 2022-09-29 17:18:53 +02:00
Davy Hélard
772717fdbb Copy RuntimeScene. 2022-09-29 17:18:53 +02:00
D8H
b826f66455 Make the custom object renderer works better with 9-patch and text child-objects (#4335)
* Don't show in changelogs.
2022-09-29 17:16:41 +02:00
D8H
6fc03cccc6 Add help buttons in the expression editor and the extension details dialog (#4337) 2022-09-29 15:44:16 +02:00
AlexandreS
ed7313a330 Add invert condition shortcut (J key by default - configurable in your preferences) (#4334) 2022-09-29 11:18:33 +02:00
D8H
7390f7cd6a Ensure required behavior properties can't be hidden (#4336) 2022-09-29 09:54:01 +02:00
D8H
4619ae824b Allow event-based objects to define a default name for created objects (#4329)
* Don't show in changelogs
2022-09-28 17:43:51 +02:00
D8H
0f69ee435f Add tests on behavior properties initialization and unserialization (#4314)
* Don't show in changelogs
2022-09-28 17:43:18 +02:00
AlexandreS
f46241d5a2 Fix bug that prevented converting a variable to JSON when one previously tried to access an out-of-index child in a variable array (#4333) 2022-09-28 17:39:53 +02:00
D8H
4c8ec48004 Add subsections for extensions categories in the Wiki (#4332)
* Add a warning message on pages for extensions from the community list.
2022-09-28 15:34:03 +02:00
AlexandreS
3ac121be4c Add asset packs that can be purchased in the Asset store home (#4328)
Do not show in changelog
2022-09-28 15:29:45 +02:00
D8H
3aa636861c Custom objects take the icon of one of their sprite child (#4316)
* Don't show in changelogs.
2022-09-27 16:19:12 +02:00
D8H
6d4b422be6 Fix extension description links on the Wiki (#4325) 2022-09-27 16:18:18 +02:00
Clément Pasteau
b8ee27f62c Improve player authentication
* Improve player authentication by indicating when the game is not registered
* Show a link to open the window if blocked

Do not show in changelog
2022-09-27 15:19:06 +02:00
AlexandreS
6996ff452d Fix various memory leaks when using the app (#4323) 2022-09-23 18:34:34 +02:00
AlexandreS
da7934c6ac Add context menu items to manipulate the view on the scene editor (#4307)
- Return to initial position (view matches the game resolution)
- Fit zoom to selected instances
- Fit zoom to the whole scene
- Select all instances of an object on the scene (in the context menu of an object)
2022-09-23 13:27:40 +02:00
Clément Pasteau
90bebcb404 Create GDevelop Authentication extension
* This is an experimental extension!
* It allows you to provide a login/register form to your players, with 1 action
* It connects the player automatically when they launch your game again
* It also provides a new action to submit a leaderboard entry without having to enter a username
* This is the beginning of Player Authentication and more features will come allowing creators to interact with their players
2022-09-23 10:44:58 +02:00
D8H
a29e7aae44 Bump newIDE version (#4317) 2022-09-22 12:22:11 +02:00
D8H
52201e2a36 Fix tiles rotation that was no longer applied in tile maps (#4315) 2022-09-22 10:25:38 +02:00
Florian Rival
50465badd7 Bump newIDE version 2022-09-21 10:01:17 +02:00
D8H
6e1bfb0190 Fix properties on the scene of the Physics behavior not working ("shared data" of behaviors not handled properly) (#4310) 2022-09-21 09:56:38 +02:00
Aurélien Vivet
40c7c57670 Add issue template for Asset Store Submission [skip ci] (#4309)
Don't show in changelog
2022-09-20 21:43:43 +02:00
github-actions[bot]
e51c73b293 Update translations [skip ci] (#4306)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2022-09-19 11:16:17 +02:00
Florian Rival
29d5d5fe75 Bump newIDE version 2022-09-19 10:23:17 +02:00
AlexandreS
6e1f2c4eee Fix parameter fields being reset to their old value after being changed (#4308) 2022-09-19 09:28:50 +02:00
269 changed files with 10043 additions and 3164 deletions

View File

@@ -0,0 +1,19 @@
---
name: "📦 Asset Store submission"
about: Submit a free asset pack for the GDevelop Asset Store.
title: ''
labels: "📦 Asset Store submission"
assignees: ''
---
BEFORE opening a new submission, please make sure that you:
- You have packaged the asset pack according [these rules](https://wiki.gdevelop.io/gdevelop5/community/contribute-to-the-assets-store). Otherwise, your package may be rejected or we will ask you to do the changes.
## Description
- License:
- Author:
- Link to the original website:
- Zip file:

View File

@@ -5,6 +5,8 @@
*/
#include "ProjectStripper.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
@@ -12,7 +14,7 @@
namespace gd {
void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project& project) {
void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project &project) {
project.GetObjectGroups().Clear();
while (project.GetExternalEventsCount() > 0)
project.RemoveExternalEvents(project.GetExternalEvents(0).GetName());
@@ -22,7 +24,28 @@ void GD_CORE_API ProjectStripper::StripProjectForExport(gd::Project& project) {
project.GetLayout(i).GetEvents().Clear();
}
project.ClearEventsFunctionsExtensions();
// Keep the EventsBasedObject object list because it's useful for the Runtime
// to create the child-object.
for (unsigned int extensionIndex = 0;
extensionIndex < project.GetEventsFunctionsExtensionsCount();
++extensionIndex) {
auto &extension = project.GetEventsFunctionsExtension(extensionIndex);
auto &eventsBasedObjects = extension.GetEventsBasedObjects();
if (eventsBasedObjects.size() == 0) {
project.RemoveEventsFunctionsExtension(extension.GetName());
extensionIndex--;
continue;
}
for (unsigned int objectIndex = 0; objectIndex < eventsBasedObjects.size();
++objectIndex) {
auto &eventsBasedObject = eventsBasedObjects.at(objectIndex);
eventsBasedObject.SetFullName("");
eventsBasedObject.SetDescription("");
eventsBasedObject.GetEventsFunctions().GetInternalVector().clear();
eventsBasedObject.GetPropertyDescriptors().GetInternalVector().clear();
}
extension.GetEventsBasedBehaviors().Clear();
}
}
} // namespace gd
} // namespace gd

View File

@@ -22,16 +22,14 @@ void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& object
// There is no default copy for a map of unique_ptr like childObjectConfigurations.
childObjectConfigurations.clear();
for (auto& it : objectConfiguration.childObjectConfigurations) {
childObjectConfigurations[it.first] =
gd::make_unique<gd::ObjectConfiguration>(*it.second);
childObjectConfigurations[it.first] = it.second->Clone();
}
}
gd::ObjectConfiguration CustomObjectConfiguration::badObjectConfiguration;
std::unique_ptr<gd::ObjectConfiguration> CustomObjectConfiguration::Clone() const {
CustomObjectConfiguration* clone = new CustomObjectConfiguration(*this);
return std::unique_ptr<gd::ObjectConfiguration>(clone);
return gd::make_unique<gd::CustomObjectConfiguration>(*this);
}
gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(const gd::String &objectName) {
@@ -51,7 +49,7 @@ gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(
if (configurationPosition == childObjectConfigurations.end()) {
childObjectConfigurations.insert(std::make_pair(
objectName,
project->CreateObjectConfiguration(childObject.GetType())));
childObject.GetConfiguration().Clone()));
return *(childObjectConfigurations[objectName]);
}
else {

View File

@@ -23,12 +23,16 @@ EventsBasedObject::EventsBasedObject(const gd::EventsBasedObject &_eventBasedObj
}
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
element.SetAttribute("defaultName", defaultName);
AbstractEventsBasedEntity::SerializeTo(element);
SerializeObjectsTo(element.AddChild("objects"));
}
void EventsBasedObject::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
defaultName = element.GetStringAttribute("defaultName");
AbstractEventsBasedEntity::UnserializeFrom(project, element);
UnserializeObjectsFrom(project, element.GetChild("objects"));
}

View File

@@ -38,6 +38,19 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
*/
EventsBasedObject* Clone() const { return new EventsBasedObject(*this); };
/**
* \brief Get the default name for created objects.
*/
const gd::String& GetDefaultName() const { return defaultName; };
/**
* \brief Set the default name for created objects.
*/
EventsBasedObject& SetDefaultName(const gd::String& defaultName_) {
defaultName = defaultName_;
return *this;
}
EventsBasedObject& SetDescription(const gd::String& description_) override {
AbstractEventsBasedEntity::SetDescription(description_);
return *this;
@@ -65,6 +78,7 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
const SerializerElement& element) override;
private:
gd::String defaultName;
};
} // namespace gd

View File

@@ -218,32 +218,8 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
if (behaviorsSharedData.find(name) != behaviorsSharedData.end()) continue;
if (project.HasEventsBasedBehavior(allBehaviorsTypes[i])) {
// Events based behaviors don't have shared data yet.
auto sharedData =
gd::make_unique<gd::BehaviorsSharedData>(name, allBehaviorsTypes[i]);
sharedData->InitializeContent();
behaviorsSharedData[name] = std::move(sharedData);
}
else {
const gd::BehaviorMetadata& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(
project.GetCurrentPlatform(),
allBehaviorsTypes[i]);
if (gd::MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
continue;
}
gd::BehaviorsSharedData* behaviorsSharedDataBluePrint =
behaviorMetadata.GetSharedDataInstance();
if (!behaviorsSharedDataBluePrint) continue;
auto sharedData =
gd::make_unique<gd::BehaviorsSharedData>(
*behaviorsSharedDataBluePrint);
sharedData->SetName(name);
sharedData->SetTypeName(allBehaviorsTypes[i]);
sharedData->InitializeContent();
auto sharedData = CreateBehaviorsSharedData(project, name, allBehaviorsTypes[i]);
if (sharedData) {
behaviorsSharedData[name] = std::move(sharedData);
}
}
@@ -264,6 +240,33 @@ void Layout::UpdateBehaviorsSharedData(gd::Project& project) {
}
}
std::unique_ptr<gd::BehaviorsSharedData> Layout::CreateBehaviorsSharedData(gd::Project& project, const gd::String& name, const gd::String& behaviorsType) {
if (project.HasEventsBasedBehavior(behaviorsType)) {
// Events based behaviors don't have shared data yet.
auto sharedData =
gd::make_unique<gd::BehaviorsSharedData>(name, behaviorsType);
sharedData->InitializeContent();
return std::move(sharedData);
}
const gd::BehaviorMetadata& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(
project.GetCurrentPlatform(),
behaviorsType);
if (gd::MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
return nullptr;
}
gd::BehaviorsSharedData* behaviorsSharedDataBluePrint =
behaviorMetadata.GetSharedDataInstance();
if (!behaviorsSharedDataBluePrint) return nullptr;
auto sharedData = behaviorsSharedDataBluePrint->Clone();
sharedData->SetName(name);
sharedData->SetTypeName(behaviorsType);
sharedData->InitializeContent();
return std::unique_ptr<gd::BehaviorsSharedData>(sharedData);
}
void Layout::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());
element.SetAttribute("mangledName", GetMangledName());
@@ -367,20 +370,23 @@ void Layout::UnserializeFrom(gd::Project& project,
"Behavior"); // Compatibility with GD <= 4
gd::String name = sharedDataElement.GetStringAttribute("name", "", "Name");
auto behavior = gd::make_unique<gd::BehaviorsSharedData>(name, type);
// Compatibility with GD <= 4.0.98
// If there is only one child called "content" (in addition to "type" and
// "name"), it's the content of a JavaScript behavior. Move the content
// out of the "content" object (to put it directly at the root of the
// behavior shared data element).
if (sharedDataElement.HasChild("content")) {
behavior->UnserializeFrom(sharedDataElement.GetChild("content"));
auto sharedData = CreateBehaviorsSharedData(project, name, type);
if (sharedData) {
// Compatibility with GD <= 4.0.98
// If there is only one child called "content" (in addition to "type" and
// "name"), it's the content of a JavaScript behavior. Move the content
// out of the "content" object (to put it directly at the root of the
// behavior shared data element).
if (sharedDataElement.HasChild("content")) {
sharedData->UnserializeFrom(sharedDataElement.GetChild("content"));
}
// end of compatibility code
else {
sharedData->UnserializeFrom(sharedDataElement);
}
behaviorsSharedData[name] = std::move(sharedData);
}
// end of compatibility code
else {
behavior->UnserializeFrom(sharedDataElement);
}
behaviorsSharedData[name] = std::move(behavior);
}
}
@@ -402,7 +408,7 @@ void Layout::Init(const Layout& other) {
behaviorsSharedData.clear();
for (const auto& it : other.behaviorsSharedData) {
behaviorsSharedData[it.first] =
gd::make_unique<gd::BehaviorsSharedData>(*(it.second->Clone()));
std::unique_ptr<gd::BehaviorsSharedData>(it.second->Clone());
}
events = other.events;

View File

@@ -404,6 +404,11 @@ class GD_CORE_API Layout : public ObjectsContainer {
* Don't forget to update me if members were changed!
*/
void Init(const gd::Layout& other);
std::unique_ptr<gd::BehaviorsSharedData> CreateBehaviorsSharedData(
gd::Project& project,
const gd::String& name,
const gd::String& behaviorsType);
};
/**

View File

@@ -30,6 +30,8 @@
#include "GDCore/Project/Variable.h"
#include "catch.hpp"
// TODO EBO Add a test where a child is removed form the EventsBasedObject
// and check the configuration still gives access to other child configuration.
namespace {
const gd::StandardEvent &EnsureStandardEvent(const gd::BaseEvent &baseEvent) {

View File

@@ -17,8 +17,12 @@ namespace gdjs {
_bottomEdgeDistance: number = 0;
_useLegacyBottomAndRightAnchors: boolean = false;
constructor(runtimeScene, behaviorData, owner) {
super(runtimeScene, behaviorData, owner);
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(instanceContainer, behaviorData, owner);
this._relativeToOriginalWindowSize = !!behaviorData.relativeToOriginalWindowSize;
this._leftEdgeAnchor = behaviorData.leftEdgeAnchor;
this._rightEdgeAnchor = behaviorData.rightEdgeAnchor;
@@ -65,11 +69,15 @@ namespace gdjs {
this._invalidDistances = true;
}
doStepPreEvents(runtimeScene) {
const game = runtimeScene.getGame();
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
const workingPoint: FloatPoint = gdjs.staticArray(
gdjs.AnchorRuntimeBehavior.prototype.doStepPreEvents
) as FloatPoint;
// TODO EBO Make it work with event based objects or hide this behavior for them.
const game = instanceContainer.getGame();
let rendererWidth = game.getGameResolutionWidth();
let rendererHeight = game.getGameResolutionHeight();
const layer = runtimeScene.getLayer(this.owner.getLayer());
const layer = instanceContainer.getLayer(this.owner.getLayer());
if (this._invalidDistances) {
if (this._relativeToOriginalWindowSize) {
rendererWidth = game.getOriginalWidth();
@@ -79,7 +87,9 @@ namespace gdjs {
//Calculate the distances from the window's bounds.
const topLeftPixel = layer.convertCoords(
this.owner.getDrawableX(),
this.owner.getDrawableY()
this.owner.getDrawableY(),
0,
workingPoint
);
//Left edge
@@ -125,9 +135,12 @@ namespace gdjs {
}
}
}
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
const bottomRightPixel = layer.convertCoords(
this.owner.getDrawableX() + this.owner.getWidth(),
this.owner.getDrawableY() + this.owner.getHeight()
this.owner.getDrawableY() + this.owner.getHeight(),
0,
workingPoint
);
//Right edge
@@ -268,11 +281,24 @@ namespace gdjs {
}
}
}
const topLeftCoord = layer.convertInverseCoords(leftPixel, topPixel);
// It's fine to reuse workingPoint as topLeftPixel is no longer used.
const topLeftCoord = layer.convertInverseCoords(
leftPixel,
topPixel,
0,
workingPoint
);
const left = topLeftCoord[0];
const top = topLeftCoord[1];
const bottomRightCoord = layer.convertInverseCoords(
rightPixel,
bottomPixel
bottomPixel,
0,
workingPoint
);
const right = bottomRightCoord[0];
const bottom = bottomRightCoord[1];
// Compatibility with GD <= 5.0.133
if (this._useLegacyBottomAndRightAnchors) {
@@ -281,25 +307,25 @@ namespace gdjs {
this._rightEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setWidth(bottomRightCoord[0] - topLeftCoord[0]);
this.owner.setWidth(right - left);
}
if (
this._bottomEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setHeight(bottomRightCoord[1] - topLeftCoord[1]);
this.owner.setHeight(bottom - top);
}
if (
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setX(
topLeftCoord[0] + this.owner.getX() - this.owner.getDrawableX()
left + this.owner.getX() - this.owner.getDrawableX()
);
}
if (
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setY(
topLeftCoord[1] + this.owner.getY() - this.owner.getDrawableY()
top + this.owner.getY() - this.owner.getDrawableY()
);
}
}
@@ -311,15 +337,15 @@ namespace gdjs {
AnchorRuntimeBehavior.HorizontalAnchor.NONE &&
this._leftEdgeAnchor !== AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setWidth(bottomRightCoord[0] - topLeftCoord[0]);
this.owner.setX(topLeftCoord[0]);
this.owner.setWidth(right - left);
this.owner.setX(left);
} else {
if (
this._leftEdgeAnchor !==
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setX(
topLeftCoord[0] + this.owner.getX() - this.owner.getDrawableX()
left + this.owner.getX() - this.owner.getDrawableX()
);
}
if (
@@ -327,7 +353,7 @@ namespace gdjs {
AnchorRuntimeBehavior.HorizontalAnchor.NONE
) {
this.owner.setX(
bottomRightCoord[0] +
right +
this.owner.getX() -
this.owner.getDrawableX() -
this.owner.getWidth()
@@ -340,14 +366,14 @@ namespace gdjs {
AnchorRuntimeBehavior.VerticalAnchor.NONE &&
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setHeight(bottomRightCoord[1] - topLeftCoord[1]);
this.owner.setY(topLeftCoord[1]);
this.owner.setHeight(bottom - top);
this.owner.setY(top);
} else {
if (
this._topEdgeAnchor !== AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setY(
topLeftCoord[1] + this.owner.getY() - this.owner.getDrawableY()
top + this.owner.getY() - this.owner.getDrawableY()
);
}
if (
@@ -355,7 +381,7 @@ namespace gdjs {
AnchorRuntimeBehavior.VerticalAnchor.NONE
) {
this.owner.setY(
bottomRightCoord[1] +
bottom +
this.owner.getY() -
this.owner.getDrawableY() -
this.owner.getHeight()
@@ -366,7 +392,7 @@ namespace gdjs {
}
}
doStepPostEvents(runtimeScene) {}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
static HorizontalAnchor = {
NONE: 0,

View File

@@ -498,7 +498,7 @@ module.exports = {
RenderedBBTextInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'JsPlatform/Extensions/bbcode24.png';
};

View File

@@ -10,11 +10,11 @@ namespace gdjs {
/**
* @param runtimeObject The object to render
* @param runtimeScene The gdjs.RuntimeScene in which the object is
* @param instanceContainer The gdjs.RuntimeInstanceContainer in which the object is
*/
constructor(
runtimeObject: gdjs.BBTextRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
@@ -22,7 +22,7 @@ namespace gdjs {
if (this._pixiObject === undefined) {
this._pixiObject = new MultiStyleText(runtimeObject._text, {
default: {
fontFamily: runtimeScene
fontFamily: instanceContainer
.getGame()
.getFontManager()
.getFontFamily(runtimeObject._fontFamily),
@@ -44,7 +44,7 @@ namespace gdjs {
this.updateFontFamily();
this.updateFontSize();
}
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());
@@ -95,7 +95,8 @@ namespace gdjs {
}
updateFontFamily(): void {
this._pixiObject.textStyles.default.fontFamily = this._object._runtimeScene
this._pixiObject.textStyles.default.fontFamily = this._object
.getInstanceContainer()
.getGame()
.getFontManager()
.getFontFamily(this._object._fontFamily);

View File

@@ -48,11 +48,14 @@ namespace gdjs {
hidden: boolean;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The container the object belongs to.
* @param objectData The object data used to initialize the object
*/
constructor(runtimeScene: gdjs.RuntimeScene, objectData: BBTextObjectData) {
super(runtimeScene, objectData);
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: BBTextObjectData
) {
super(instanceContainer, objectData);
// @ts-ignore - parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string.
this._opacity = parseFloat(objectData.content.opacity);
this._text = objectData.content.text;
@@ -62,7 +65,10 @@ namespace gdjs {
this._fontSize = parseFloat(objectData.content.fontSize);
this._wordWrap = objectData.content.wordWrap;
this._align = objectData.content.align;
this._renderer = new gdjs.BBTextRuntimeObjectRenderer(this, runtimeScene);
this._renderer = new gdjs.BBTextRuntimeObjectRenderer(
this,
instanceContainer
);
this.hidden = !objectData.content.visible;
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
@@ -122,8 +128,8 @@ namespace gdjs {
}
}
onDestroyFromScene(runtimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
}
/**
@@ -239,7 +245,7 @@ namespace gdjs {
this._wrappingWidth = width;
this._renderer.updateWrappingWidth();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -254,7 +260,7 @@ namespace gdjs {
this._wordWrap = wordWrap;
this._renderer.updateWordWrap();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
getWordWrap() {

View File

@@ -627,7 +627,7 @@ module.exports = {
RenderedBitmapTextInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'JsPlatform/Extensions/bitmapfont24.png';
};

View File

@@ -10,16 +10,16 @@ namespace gdjs {
/**
* @param runtimeObject The object to render
* @param runtimeScene The gdjs.RuntimeScene in which the object is
* @param instanceContainer The container in which the object is
*/
constructor(
runtimeObject: gdjs.BitmapTextRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
// Obtain the bitmap font to use in the object.
const bitmapFont = runtimeScene
const bitmapFont = instanceContainer
.getGame()
.getBitmapFontManager()
.obtainBitmapFont(
@@ -32,7 +32,7 @@ namespace gdjs {
});
// Set the object on the scene
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());
@@ -59,7 +59,8 @@ namespace gdjs {
onDestroy() {
// Mark the font from the object as not used anymore.
this._object._runtimeScene
this._object
.getInstanceContainer()
.getGame()
.getBitmapFontManager()
.releaseBitmapFont(this._pixiObject.fontName);
@@ -73,7 +74,8 @@ namespace gdjs {
updateFont(): void {
// Get the new bitmap font to use
const bitmapFont = this._object._runtimeScene
const bitmapFont = this._object
.getInstanceContainer()
.getGame()
.getBitmapFontManager()
.obtainBitmapFont(
@@ -82,7 +84,8 @@ namespace gdjs {
);
// Mark the old font as not used anymore
this._object._runtimeScene
this._object
.getInstanceContainer()
.getGame()
.getBitmapFontManager()
.releaseBitmapFont(this._pixiObject.fontName);

View File

@@ -50,14 +50,14 @@ namespace gdjs {
_renderer: gdjs.BitmapTextRuntimeObjectPixiRenderer;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The container the object belongs to.
* @param objectData The object data used to initialize the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: BitmapTextObjectData
) {
super(runtimeScene, objectData);
super(instanceContainer, objectData);
this._opacity = objectData.content.opacity;
this._text = objectData.content.text;
@@ -73,7 +73,7 @@ namespace gdjs {
this._renderer = new gdjs.BitmapTextRuntimeObjectRenderer(
this,
runtimeScene
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
@@ -137,8 +137,8 @@ namespace gdjs {
}
}
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
this._renderer.onDestroy();
}
@@ -148,7 +148,7 @@ namespace gdjs {
setText(text: string): void {
this._text = text;
this._renderer.updateTextContent();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -170,7 +170,7 @@ namespace gdjs {
setScale(scale: float): void {
this._scale = scale;
this._renderer.updateScale();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
getScale(): float {
@@ -276,7 +276,7 @@ namespace gdjs {
setWrappingWidth(width: float): void {
this._wrappingWidth = width;
this._renderer.updateWrappingWidth();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -289,7 +289,7 @@ namespace gdjs {
setWordWrap(wordWrap: boolean): void {
this._wordWrap = wordWrap;
this._renderer.updateWrappingWidth();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
getWordWrap(): boolean {

View File

@@ -7,10 +7,12 @@ namespace gdjs {
export namespace debuggerTools {
/**
* Stop the game execution.
* @param runtimeScene - The current scene.
* @param instanceContainer - The current scene.
*/
export const pause = function (runtimeScene: gdjs.RuntimeScene) {
runtimeScene.getGame().pause(true);
export const pause = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
instanceContainer.getGame().pause(true);
};
/**
@@ -29,20 +31,20 @@ namespace gdjs {
/**
* Enable or disable the debug draw.
* @param runtimeScene - The current scene.
* @param instanceContainer - The current scene.
* @param enableDebugDraw - true to enable the debug draw, false to disable it.
* @param showHiddenInstances - true to apply the debug draw to hidden objects.
* @param showPointsNames - true to show point names.
* @param showCustomPoints - true to show custom points of Sprite objects.
*/
export const enableDebugDraw = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
enableDebugDraw: boolean,
showHiddenInstances: boolean,
showPointsNames: boolean,
showCustomPoints: boolean
) {
runtimeScene.enableDebugDraw(
instanceContainer.enableDebugDraw(
enableDebugDraw,
showHiddenInstances,
showPointsNames,

View File

@@ -11,8 +11,12 @@ namespace gdjs {
export class DestroyOutsideRuntimeBehavior extends gdjs.RuntimeBehavior {
_extraBorder: any;
constructor(runtimeScene, behaviorData, owner) {
super(runtimeScene, behaviorData, owner);
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner
) {
super(instanceContainer, behaviorData, owner);
this._extraBorder = behaviorData.extraBorder || 0;
}
@@ -23,14 +27,14 @@ namespace gdjs {
return true;
}
doStepPostEvents(runtimeScene) {
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
// is not necessarily in the middle of the object (for sprites for example).
const ow = this.owner.getWidth();
const oh = this.owner.getHeight();
const ocx = this.owner.getDrawableX() + this.owner.getCenterX();
const ocy = this.owner.getDrawableY() + this.owner.getCenterY();
const layer = runtimeScene.getLayer(this.owner.getLayer());
const layer = instanceContainer.getLayer(this.owner.getLayer());
const boundingCircleRadius = Math.sqrt(ow * ow + oh * oh) / 2.0;
if (
ocx + boundingCircleRadius + this._extraBorder <
@@ -43,7 +47,7 @@ namespace gdjs {
layer.getCameraY() + layer.getCameraHeight() / 2
) {
//We are outside the camera area.
this.owner.deleteFromScene(runtimeScene);
this.owner.deleteFromScene(instanceContainer);
}
}

View File

@@ -30,16 +30,16 @@ namespace gdjs {
/**
* Load the Dialogue Tree data from a JSON resource.
*
* @param runtimeScene The scene where the dialogue is running.
* @param instanceContainer The scene where the dialogue is running.
* @param jsonResourceName The JSON resource where to load the Dialogue Tree data from. The data is a JSON string usually created with [Yarn Dialogue Editor](https://github.com/InfiniteAmmoInc/Yarn).
* @param startDialogueNode The Dialogue Branch to start the Dialogue Tree from. If left empty, the data will only be loaded, but can later be initialized via another action
*/
gdjs.dialogueTree.loadFromJsonFile = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
jsonResourceName: string,
startDialogueNode: string
) {
runtimeScene
instanceContainer
.getGame()
.getJsonManager()
.loadJson(jsonResourceName, function (error, content) {

View File

@@ -16,8 +16,12 @@ namespace gdjs {
_draggedByDraggableManager: DraggableManager | null = null;
_checkCollisionMask: boolean;
constructor(runtimeScene, behaviorData, owner) {
super(runtimeScene, behaviorData, owner);
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner
) {
super(instanceContainer, behaviorData, owner);
this._checkCollisionMask = behaviorData.checkCollisionMask ? true : false;
}
@@ -45,21 +49,21 @@ namespace gdjs {
this._draggedByDraggableManager = null;
}
_tryBeginDrag(runtimeScene) {
_tryBeginDrag(instanceContainer: gdjs.RuntimeInstanceContainer) {
if (this._draggedByDraggableManager) {
return false;
}
const inputManager = runtimeScene.getGame().getInputManager();
const inputManager = instanceContainer.getGame().getInputManager();
//Try mouse
const mouseDraggableManager = DraggableManager.getMouseManager(
runtimeScene
instanceContainer
);
if (
inputManager.isMouseButtonPressed(0) &&
!mouseDraggableManager.isDragging(this)
) {
if (mouseDraggableManager.tryAndTakeDragging(runtimeScene, this)) {
if (mouseDraggableManager.tryAndTakeDragging(instanceContainer, this)) {
this._draggedByDraggableManager = mouseDraggableManager;
return true;
}
@@ -68,13 +72,15 @@ namespace gdjs {
const touchIds = inputManager.getStartedTouchIdentifiers();
for (let i = 0; i < touchIds.length; ++i) {
const touchDraggableManager = DraggableManager.getTouchManager(
runtimeScene,
instanceContainer,
touchIds[i]
);
if (touchDraggableManager.isDragging(this)) {
continue;
}
if (touchDraggableManager.tryAndTakeDragging(runtimeScene, this)) {
if (
touchDraggableManager.tryAndTakeDragging(instanceContainer, this)
) {
this._draggedByDraggableManager = touchDraggableManager;
return true;
}
@@ -83,40 +89,46 @@ namespace gdjs {
return false;
}
_shouldEndDrag(runtimeScene) {
_shouldEndDrag(instanceContainer: gdjs.RuntimeInstanceContainer) {
if (!this._draggedByDraggableManager) {
return false;
}
return this._draggedByDraggableManager.shouldEndDrag(runtimeScene, this);
return this._draggedByDraggableManager.shouldEndDrag(
instanceContainer,
this
);
}
_updateObjectPosition(runtimeScene) {
_updateObjectPosition(instanceContainer: gdjs.RuntimeInstanceContainer) {
if (!this._draggedByDraggableManager) {
return false;
}
this._draggedByDraggableManager.updateObjectPosition(runtimeScene, this);
this._draggedByDraggableManager.updateObjectPosition(
instanceContainer,
this
);
return true;
}
doStepPreEvents(runtimeScene) {
this._tryBeginDrag(runtimeScene);
if (this._shouldEndDrag(runtimeScene)) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._tryBeginDrag(instanceContainer);
if (this._shouldEndDrag(instanceContainer)) {
this._endDrag();
}
this._updateObjectPosition(runtimeScene);
this._updateObjectPosition(instanceContainer);
}
doStepPostEvents(runtimeScene) {
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
const mouseDraggableManager = DraggableManager.getMouseManager(
runtimeScene
instanceContainer
);
mouseDraggableManager.leftPressedLastFrame = runtimeScene
mouseDraggableManager.leftPressedLastFrame = instanceContainer
.getGame()
.getInputManager()
.isMouseButtonPressed(0);
}
isDragged(runtimeScene): boolean {
isDragged(instanceContainer: gdjs.RuntimeInstanceContainer): boolean {
return !!this._draggedByDraggableManager;
}
}
@@ -137,53 +149,53 @@ namespace gdjs {
protected _xOffset: number = 0;
protected _yOffset: number = 0;
constructor(runtimeScene: gdjs.RuntimeScene) {}
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {}
/**
* Get the platforms manager of a scene.
*/
static getMouseManager(
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): MouseDraggableManager {
// @ts-ignore
if (!runtimeScene.mouseDraggableManager) {
if (!instanceContainer.mouseDraggableManager) {
//Create the shared manager if necessary.
// @ts-ignore
runtimeScene.mouseDraggableManager = new MouseDraggableManager(
runtimeScene
instanceContainer.mouseDraggableManager = new MouseDraggableManager(
instanceContainer
);
}
// @ts-ignore
return runtimeScene.mouseDraggableManager;
return instanceContainer.mouseDraggableManager;
}
/**
* Get the platforms manager of a scene.
*/
static getTouchManager(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
touchId: integer
): DraggableManager {
// @ts-ignore
if (!runtimeScene.touchDraggableManagers) {
if (!instanceContainer.touchDraggableManagers) {
//Create the shared manager if necessary.
// @ts-ignore
runtimeScene.touchDraggableManagers = [];
instanceContainer.touchDraggableManagers = [];
}
// @ts-ignore
if (!runtimeScene.touchDraggableManagers[touchId]) {
if (!instanceContainer.touchDraggableManagers[touchId]) {
//Create the shared manager if necessary.
// @ts-ignore
runtimeScene.touchDraggableManagers[
instanceContainer.touchDraggableManagers[
touchId
] = new TouchDraggableManager(runtimeScene, touchId);
] = new TouchDraggableManager(instanceContainer, touchId);
}
// @ts-ignore
return runtimeScene.touchDraggableManagers[touchId];
return instanceContainer.touchDraggableManagers[touchId];
}
tryAndTakeDragging(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
) {
if (
@@ -193,7 +205,10 @@ namespace gdjs {
) {
return false;
}
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
const position = this.getPosition(
instanceContainer,
draggableRuntimeBehavior
);
if (
!draggableRuntimeBehavior.owner.insideObject(position[0], position[1])
) {
@@ -218,10 +233,13 @@ namespace gdjs {
}
updateObjectPosition(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
) {
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
const position = this.getPosition(
instanceContainer,
draggableRuntimeBehavior
);
if (
draggableRuntimeBehavior.owner.getX() != position[0] - this._xOffset ||
draggableRuntimeBehavior.owner.getY() != position[1] - this._yOffset
@@ -241,11 +259,11 @@ namespace gdjs {
draggableRuntimeBehavior: DraggableRuntimeBehavior
): boolean;
abstract shouldEndDrag(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
): boolean;
abstract getPosition(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
): FloatPoint;
}
@@ -257,8 +275,8 @@ namespace gdjs {
/** Used to only start dragging when clicking. */
leftPressedLastFrame = false;
constructor(runtimeScene: gdjs.RuntimeScene) {
super(runtimeScene);
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
super(instanceContainer);
}
isDragging(draggableRuntimeBehavior: DraggableRuntimeBehavior): boolean {
@@ -266,20 +284,28 @@ namespace gdjs {
}
getPosition(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
): FloatPoint {
const inputManager = runtimeScene.getGame().getInputManager();
return runtimeScene
const workingPoint: FloatPoint = gdjs.staticArray(
MouseDraggableManager.prototype.getPosition
) as FloatPoint;
const inputManager = instanceContainer.getGame().getInputManager();
return instanceContainer
.getLayer(draggableRuntimeBehavior.owner.getLayer())
.convertCoords(inputManager.getMouseX(), inputManager.getMouseY());
.convertCoords(
inputManager.getMouseX(),
inputManager.getMouseY(),
0,
workingPoint
);
}
shouldEndDrag(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
): boolean {
const inputManager = runtimeScene.getGame().getInputManager();
const inputManager = instanceContainer.getGame().getInputManager();
return !inputManager.isMouseButtonPressed(0);
}
}
@@ -290,8 +316,11 @@ namespace gdjs {
class TouchDraggableManager extends DraggableManager {
private _touchId: integer;
constructor(runtimeScene: gdjs.RuntimeScene, touchId: integer) {
super(runtimeScene);
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
touchId: integer
) {
super(instanceContainer);
this._touchId = touchId;
}
@@ -300,23 +329,28 @@ namespace gdjs {
}
getPosition(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
): FloatPoint {
const inputManager = runtimeScene.getGame().getInputManager();
return runtimeScene
const workingPoint: FloatPoint = gdjs.staticArray(
TouchDraggableManager.prototype.getPosition
) as FloatPoint;
const inputManager = instanceContainer.getGame().getInputManager();
return instanceContainer
.getLayer(draggableRuntimeBehavior.owner.getLayer())
.convertCoords(
inputManager.getTouchX(this._touchId),
inputManager.getTouchY(this._touchId)
inputManager.getTouchY(this._touchId),
0,
workingPoint
);
}
shouldEndDrag(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
draggableRuntimeBehavior: DraggableRuntimeBehavior
): boolean {
const inputManager = runtimeScene.getGame().getInputManager();
const inputManager = instanceContainer.getGame().getInputManager();
return (
inputManager.getAllTouchIdentifiers().indexOf(this._touchId) === -1
);

View File

@@ -539,7 +539,7 @@ module.exports = {
RenderedDummyObjectInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'CppPlatform/Extensions/texticon24.png';
};

View File

@@ -11,11 +11,11 @@ namespace gdjs {
_textToSet: string;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData: any,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
// Here you can access to the behavior data (JSON declared in JsExtension.js)
// using behaviorData:
@@ -37,7 +37,7 @@ namespace gdjs {
onDeActivate() {}
doStepPreEvents(runtimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// This is run at every frame, before events are launched.
this.owner
.getVariables()
@@ -45,7 +45,7 @@ namespace gdjs {
.setString(this._textToSet);
}
doStepPostEvents(runtimeScene) {
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// This is run at every frame, after events are launched.
}
}

View File

@@ -11,11 +11,11 @@ namespace gdjs {
/**
* @param runtimeObject The object to render
* @param runtimeScene The gdjs.RuntimeScene in which the object is
* @param instanceContainer The gdjs.RuntimeScene in which the object is
*/
constructor(
runtimeObject: gdjs.DummyRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
@@ -27,12 +27,12 @@ namespace gdjs {
}
// You can also create a PIXI sprite or other PIXI object
// this._imageManager = runtimeScene.getGame().getImageManager();
// this._imageManager = instanceContainer.getGame().getImageManager();
// if ( this._sprite === undefined )
// this._sprite = new PIXI.Sprite(this._imageManager.getInvalidPIXITexture());
this._text.anchor.x = 0.5;
this._text.anchor.y = 0.5;
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._text, runtimeObject.getZOrder());

View File

@@ -14,11 +14,14 @@ namespace gdjs {
// @ts-expect-error ts-migrate(2564) FIXME: Property 'opacity' has no initializer and is not d... Remove this comment to see the full error message
opacity: float;
constructor(runtimeScene, objectData) {
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) {
// *ALWAYS* call the base gdjs.RuntimeObject constructor.
super(runtimeScene, objectData);
super(instanceContainer, objectData);
this._property1 = objectData.content.property1;
this._renderer = new gdjs.DummyRuntimeObjectRenderer(this, runtimeScene);
this._renderer = new gdjs.DummyRuntimeObjectRenderer(
this,
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
@@ -40,9 +43,9 @@ namespace gdjs {
/**
* Called once during the game loop, before events and rendering.
* @param runtimeScene The gdjs.RuntimeScene the object belongs to.
* @param instanceContainer The gdjs.RuntimeScene the object belongs to.
*/
update(runtimeScene: gdjs.RuntimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
// This is an example: typically you want to make sure the renderer
// is up to date with the object.
this._renderer.ensureUpToDate();

View File

@@ -4,20 +4,20 @@ namespace gdjs {
_textToSet: string;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData: any,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
// Here you can access to the behavior data (JSON declared in JsExtension.js)
// using behaviorData:
this._textToSet = behaviorData.property1;
// You can also access to the shared data:
const sharedData = runtimeScene.getInitialSharedDataForBehavior(
behaviorData.name
);
const sharedData = instanceContainer
.getScene()
.getInitialSharedDataForBehavior(behaviorData.name);
this._textToSet = (sharedData as any).sharedProperty1;
// You can also run arbitrary code at the creation of the behavior:
@@ -40,7 +40,7 @@ namespace gdjs {
onDeActivate() {}
doStepPreEvents(runtimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// This is run at every frame, before events are launched.
this.owner
.getVariables()
@@ -48,7 +48,7 @@ namespace gdjs {
.setString(this._textToSet);
}
doStepPostEvents(runtimeScene) {
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// This is run at every frame, after events are launched.
}
}

View File

@@ -25,7 +25,9 @@ namespace gdjs {
* In **rare cases** you may want to run code at the start of the scene. You can define a callback
* that will be called at this moment.
*/
gdjs.registerRuntimeSceneLoadedCallback(function (runtimeScene) {
gdjs.registerRuntimeSceneLoadedCallback(function (
runtimeScene: gdjs.RuntimeScene
) {
logger.log('A gdjs.RuntimeScene was loaded:', runtimeScene);
});
@@ -33,7 +35,9 @@ namespace gdjs {
* In **rare cases** you may want to run code at the end of a scene. You can define a callback
* that will be called at this moment.
*/
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
gdjs.registerRuntimeSceneUnloadedCallback(function (
runtimeScene: gdjs.RuntimeScene
) {
logger.log('A gdjs.RuntimeScene was unloaded:', runtimeScene);
});
@@ -41,12 +45,12 @@ namespace gdjs {
* In **very rare cases** you may want to run code whenever an object is deleted.
*/
gdjs.registerObjectDeletedFromSceneCallback(function (
runtimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
runtimeObject
) {
logger.log(
'A gdjs.RuntimeObject was deleted from a gdjs.RuntimeScene:',
runtimeScene,
instanceContainer,
runtimeObject
);
});

View File

@@ -48,13 +48,16 @@ namespace gdjs {
/**
* Get the path to 'Desktop' folder.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to the desktop folder
*/
export const getDesktopPath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('desktop') || '';
@@ -65,13 +68,16 @@ namespace gdjs {
/**
* Get the path to 'Documents' folder.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to the documents folder
*/
export const getDocumentsPath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('documents') || '';
@@ -82,13 +88,16 @@ namespace gdjs {
/**
* Get the path to 'Pictures' folder.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to the pictures folder
*/
export const getPicturesPath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('pictures') || '';
@@ -99,13 +108,16 @@ namespace gdjs {
/**
* Get the path to this application 'Executable' file.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to this applications executable file
*/
export const getExecutablePath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('exe') || '';
@@ -116,14 +128,16 @@ namespace gdjs {
/**
* Get the path to this application 'Executable' folder.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to this applications executable folder
*/
export const getExecutableFolderPath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const path = gdjs.fileSystem._getPath();
const executablePath = gdjs.fileSystem.getExecutablePath(runtimeScene);
const executablePath = gdjs.fileSystem.getExecutablePath(
instanceContainer
);
if (!path) {
return '';
}
@@ -132,13 +146,16 @@ namespace gdjs {
/**
* Get the path to 'UserData' folder.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to userdata folder
*/
export const getUserdataPath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('userData') || '';
@@ -152,9 +169,12 @@ namespace gdjs {
* @return The path to user's "home" folder
*/
export const getUserHomePath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('home') || '';
@@ -165,13 +185,16 @@ namespace gdjs {
/**
* Get the path to 'Temp' folder.
* @param runtimeScene The current scene
* @param instanceContainer The current container
* @return The path to temp folder
*/
export const getTempPath = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): string {
const remote = runtimeScene.getGame().getRenderer().getElectronRemote();
const remote = instanceContainer
.getGame()
.getRenderer()
.getElectronRemote();
const app = remote ? remote.app : null;
if (app) {
return app.getPath('temp') || '';

View File

@@ -1,7 +1,13 @@
namespace gdjs {
export interface RuntimeGame {
inventories: { [name: string]: gdjs.Inventory };
}
export class InventoryManager {
static get(runtimeScene, name): gdjs.Inventory {
const game = runtimeScene.getGame();
static get(
instanceContainer: gdjs.RuntimeInstanceContainer,
name: string
): gdjs.Inventory {
const game = instanceContainer.getGame();
if (!game.inventories) {
game.inventories = {};
}
@@ -15,70 +21,106 @@ namespace gdjs {
export namespace evtTools {
export namespace inventory {
export const add = function (runtimeScene, inventoryName, name) {
return InventoryManager.get(runtimeScene, inventoryName).add(name);
export const add = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string
) {
return InventoryManager.get(instanceContainer, inventoryName).add(name);
};
export const remove = function (runtimeScene, inventoryName, name) {
return InventoryManager.get(runtimeScene, inventoryName).remove(name);
export const remove = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string
) {
return InventoryManager.get(instanceContainer, inventoryName).remove(
name
);
};
export const count = function (runtimeScene, inventoryName, name) {
return InventoryManager.get(runtimeScene, inventoryName).count(name);
export const count = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string
) {
return InventoryManager.get(instanceContainer, inventoryName).count(
name
);
};
export const has = function (runtimeScene, inventoryName, name) {
return InventoryManager.get(runtimeScene, inventoryName).has(name);
export const has = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string
) {
return InventoryManager.get(instanceContainer, inventoryName).has(name);
};
export const setMaximum = function (
runtimeScene,
inventoryName,
name,
maxCount
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string,
maxCount: number
) {
return InventoryManager.get(runtimeScene, inventoryName).setMaximum(
name,
maxCount
);
return InventoryManager.get(
instanceContainer,
inventoryName
).setMaximum(name, maxCount);
};
export const setUnlimited = function (
runtimeScene,
inventoryName,
name,
enable
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string,
enable: boolean
) {
return InventoryManager.get(runtimeScene, inventoryName).setUnlimited(
name,
enable
return InventoryManager.get(
instanceContainer,
inventoryName
).setUnlimited(name, enable);
};
export const isFull = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string
) {
return InventoryManager.get(instanceContainer, inventoryName).isFull(
name
);
};
export const isFull = function (runtimeScene, inventoryName, name) {
return InventoryManager.get(runtimeScene, inventoryName).isFull(name);
};
export const equip = function (runtimeScene, inventoryName, name, equip) {
return InventoryManager.get(runtimeScene, inventoryName).equip(
export const equip = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string,
equip: boolean
) {
return InventoryManager.get(instanceContainer, inventoryName).equip(
name,
equip
);
};
export const isEquipped = function (runtimeScene, inventoryName, name) {
return InventoryManager.get(runtimeScene, inventoryName).isEquipped(
name
);
export const isEquipped = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
name: string
) {
return InventoryManager.get(
instanceContainer,
inventoryName
).isEquipped(name);
};
export const serializeToVariable = function (
runtimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
variable: gdjs.Variable
) {
const allItems = gdjs.InventoryManager.get(
runtimeScene,
instanceContainer,
inventoryName
).getAllItems();
for (const name in allItems) {
@@ -92,12 +134,12 @@ namespace gdjs {
};
export const unserializeFromVariable = function (
runtimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
inventoryName: string,
variable: gdjs.Variable
) {
const inventory = gdjs.InventoryManager.get(
runtimeScene,
instanceContainer,
inventoryName
);
inventory.clear();

View File

@@ -20,7 +20,7 @@ export type ObjectsRenderingService = {
RenderedInstance: any,
registerInstanceRenderer: (objectType: string, renderer: any) => void,
requireModule: (dirname: string, moduleName: string) => any,
getThumbnail: (project: gdProject, object: gdObject) => string,
getThumbnail: (project: gdProject, objectConfiguration: gdObjectConfiguration) => string,
rgbOrHexToHexNumber: (value: string) => number,
};
export type ObjectsEditorService = {

View File

@@ -34,7 +34,7 @@ module.exports = {
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/leaderboards')
.setCategory('Leaderboards')
.setCategory('Players')
.addInstructionOrExpressionGroupMetadata(_('Leaderboards (experimental)'))
.setIcon('JsPlatform/Extensions/leaderboard.svg');
@@ -65,6 +65,32 @@ module.exports = {
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
.setFunctionName('gdjs.evtTools.leaderboards.savePlayerScore');
extension
.addAction(
'SaveConnectedPlayerScore',
_('Save connected player score'),
_("Save the connected player's score to the given leaderboard."),
_(
'Send to leaderboard _PARAM1_ the score _PARAM2_ for the connected player'
),
_('Save score'),
'JsPlatform/Extensions/leaderboard.svg',
'JsPlatform/Extensions/leaderboard.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('leaderboardId', _('Leaderboard'), '', false)
.addParameter(
'expression',
_('Score to register for the player'),
'',
false
)
.setHelpPath('/all-features/leaderboards')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Leaderboards/sha256.js')
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
.setFunctionName('gdjs.evtTools.leaderboards.saveConnectedPlayerScore');
extension
.addCondition(
'HasLastSaveErrored',

View File

@@ -1,5 +1,6 @@
/// <reference path="sha256.d.ts" />
// TODO EBO Replace runtimeScene to instanceContainer.
namespace gdjs {
const logger = new gdjs.Logger('Leaderboards');
export namespace evtTools {
@@ -16,8 +17,10 @@ namespace gdjs {
lastScoreSavingSucceededAt: number | null;
currentlySavingScore: number | null;
currentlySavingPlayerName: string | null;
currentlySavingPlayerId: string | null;
lastSavedScore: number | null;
lastSavedPlayerName: string | null;
lastSavedPlayerId: string | null;
lastSaveError: string | null;
isScoreSaving: boolean;
hasScoreBeenSaved: boolean;
@@ -28,25 +31,45 @@ namespace gdjs {
this.lastScoreSavingSucceededAt = null;
this.currentlySavingScore = null;
this.currentlySavingPlayerName = null;
this.currentlySavingPlayerId = null;
this.lastSavedScore = null;
this.lastSavedPlayerName = null;
this.lastSavedPlayerId = null;
this.lastSaveError = null;
this.isScoreSaving = false;
this.hasScoreBeenSaved = false;
this.hasScoreSavingErrored = false;
}
isSameAsLastScore(playerName: string, score: number): boolean {
isSameAsLastScore({
playerName,
playerId,
score,
}: {
playerName?: string;
playerId?: string;
score: number;
}): boolean {
return (
this.lastSavedPlayerName === playerName &&
((!!playerName && this.lastSavedPlayerName === playerName) ||
(!!playerId && this.lastSavedPlayerId === playerId)) &&
this.lastSavedScore === score
);
}
isAlreadySavingThisScore(playerName: string, score: number): boolean {
isAlreadySavingThisScore({
playerName,
playerId,
score,
}: {
playerName?: string;
playerId?: string;
score: number;
}): boolean {
return (
((!!playerName && this.currentlySavingPlayerName === playerName) ||
(!!playerId && this.currentlySavingPlayerId === playerId)) &&
this.isScoreSaving &&
this.currentlySavingPlayerName === playerName &&
this.currentlySavingScore === score
);
}
@@ -58,19 +81,29 @@ namespace gdjs {
);
}
startSaving(playerName: string, score: number): void {
startSaving({
playerName,
playerId,
score,
}: {
playerName?: string;
playerId?: string;
score: number;
}): void {
this.lastScoreSavingStartedAt = Date.now();
this.isScoreSaving = true;
this.hasScoreBeenSaved = false;
this.hasScoreSavingErrored = false;
this.currentlySavingScore = score;
this.currentlySavingPlayerName = playerName;
if (playerName) this.currentlySavingPlayerName = playerName;
if (playerId) this.currentlySavingPlayerId = playerId;
}
closeSaving(): void {
this.lastScoreSavingSucceededAt = Date.now();
this.lastSavedScore = this.currentlySavingScore;
this.lastSavedPlayerName = this.currentlySavingPlayerName;
this.lastSavedPlayerId = this.currentlySavingPlayerId;
this.isScoreSaving = false;
this.hasScoreBeenSaved = true;
}
@@ -155,50 +188,26 @@ namespace gdjs {
return lastScoreSavingState;
};
export const savePlayerScore = function (
runtimeScene: gdjs.RuntimeScene,
leaderboardId: string,
score: float,
playerName: string
) {
let scoreSavingState: ScoreSavingState;
if (_scoreSavingStateByLeaderboard[leaderboardId]) {
scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
if (scoreSavingState.isAlreadySavingThisScore(playerName, score)) {
logger.warn(
'There is already a request to save with this player name and this score. Ignoring this one.'
);
return;
}
if (scoreSavingState.isSameAsLastScore(playerName, score)) {
logger.warn(
'The player and score to be sent are the same as previous one. Ignoring this one.'
);
const errorCode = 'SAME_AS_PREVIOUS';
scoreSavingState.setError(errorCode);
return;
}
if (scoreSavingState.isTooSoonToSaveAnotherScore()) {
logger.warn(
'Last entry was sent too little time ago. Ignoring this one.'
);
const errorCode = 'TOO_FAST';
scoreSavingState.setError(errorCode);
return;
}
} else {
scoreSavingState = new ScoreSavingState();
_scoreSavingStateByLeaderboard[leaderboardId] = scoreSavingState;
}
scoreSavingState.startSaving(playerName, score);
const baseUrl = 'https://api.gdevelop-app.com/play';
const saveScore = function ({
leaderboardId,
playerName,
playerId,
playerToken,
score,
scoreSavingState,
runtimeScene,
}: {
leaderboardId: string;
playerName?: string;
playerId?: string;
playerToken?: string;
score: number;
scoreSavingState: ScoreSavingState;
runtimeScene: gdjs.RuntimeScene;
}) {
const baseUrl = 'https://api.gdevelop.io/play';
const game = runtimeScene.getGame();
const payload = JSON.stringify({
playerName: formatPlayerName(playerName),
const payloadObject = {
score: score,
sessionId: game.getSessionId(),
clientPlayerId: game.getPlayerId(),
@@ -206,18 +215,25 @@ namespace gdjs {
typeof window !== 'undefined' && (window as any).location
? (window as any).location.href
: '',
});
fetch(
`${baseUrl}/game/${gdjs.projectData.properties.projectUuid}/leaderboard/${leaderboardId}/entry`,
{
body: payload,
method: 'POST',
headers: {
'Content-Type': 'application/json',
Digest: computeDigest(payload),
},
}
).then(
};
if (playerName)
payloadObject['playerName'] = formatPlayerName(playerName);
const payload = JSON.stringify(payloadObject);
const headers = {
'Content-Type': 'application/json',
Digest: computeDigest(payload),
};
if (playerToken)
headers['Authorization'] = `player-game-token ${playerToken}`;
let leaderboardEntryCreationUrl = `${baseUrl}/game/${gdjs.projectData.properties.projectUuid}/leaderboard/${leaderboardId}/entry`;
if (playerId) {
leaderboardEntryCreationUrl += `?playerId=${playerId}`;
}
fetch(leaderboardEntryCreationUrl, {
body: payload,
method: 'POST',
headers: headers,
}).then(
(response) => {
if (!response.ok) {
const errorCode = response.status.toString();
@@ -250,6 +266,114 @@ namespace gdjs {
);
};
export const savePlayerScore = function (
runtimeScene: gdjs.RuntimeScene,
leaderboardId: string,
score: float,
playerName: string
) {
let scoreSavingState: ScoreSavingState;
if (_scoreSavingStateByLeaderboard[leaderboardId]) {
scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
if (
scoreSavingState.isAlreadySavingThisScore({ playerName, score })
) {
logger.warn(
'There is already a request to save with this player name and this score. Ignoring this one.'
);
return;
}
if (scoreSavingState.isSameAsLastScore({ playerName, score })) {
logger.warn(
'The player and score to be sent are the same as previous one. Ignoring this one.'
);
const errorCode = 'SAME_AS_PREVIOUS';
scoreSavingState.setError(errorCode);
return;
}
if (scoreSavingState.isTooSoonToSaveAnotherScore()) {
logger.warn(
'Last entry was sent too little time ago. Ignoring this one.'
);
const errorCode = 'TOO_FAST';
scoreSavingState.setError(errorCode);
return;
}
} else {
scoreSavingState = new ScoreSavingState();
_scoreSavingStateByLeaderboard[leaderboardId] = scoreSavingState;
}
scoreSavingState.startSaving({ playerName, score });
saveScore({
leaderboardId,
playerName,
score,
scoreSavingState,
runtimeScene,
});
};
export const saveConnectedPlayerScore = function (
runtimeScene: gdjs.RuntimeScene,
leaderboardId: string,
score: float
) {
let scoreSavingState: ScoreSavingState;
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!playerId || !playerToken) {
logger.warn(
'Cannot save a score for a connected player if the player is not connected.'
);
return;
}
if (_scoreSavingStateByLeaderboard[leaderboardId]) {
scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
if (scoreSavingState.isAlreadySavingThisScore({ playerId, score })) {
logger.warn(
'There is already a request to save with this player ID and this score. Ignoring this one.'
);
return;
}
if (scoreSavingState.isSameAsLastScore({ playerId, score })) {
logger.warn(
'The player and score to be sent are the same as previous one. Ignoring this one.'
);
const errorCode = 'SAME_AS_PREVIOUS';
scoreSavingState.setError(errorCode);
return;
}
if (scoreSavingState.isTooSoonToSaveAnotherScore()) {
logger.warn(
'Last entry was sent too little time ago. Ignoring this one.'
);
const errorCode = 'TOO_FAST';
scoreSavingState.setError(errorCode);
return;
}
} else {
scoreSavingState = new ScoreSavingState();
_scoreSavingStateByLeaderboard[leaderboardId] = scoreSavingState;
}
scoreSavingState.startSaving({ playerId, score });
saveScore({
leaderboardId,
playerId,
playerToken,
score,
scoreSavingState,
runtimeScene,
});
};
export const isSaving = function (leaderboardId?: string): boolean {
if (leaderboardId) {
return _scoreSavingStateByLeaderboard[leaderboardId]

View File

@@ -370,7 +370,7 @@ module.exports = {
RenderedLightObjectInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'CppPlatform/Extensions/lightIcon32.png';
};

View File

@@ -4,7 +4,7 @@ namespace gdjs {
export class LightObstaclesManager {
_obstacleRBush: any;
constructor(runtimeScene: gdjs.RuntimeScene) {
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._obstacleRBush = new rbush();
}
@@ -12,18 +12,18 @@ namespace gdjs {
* Get the light obstacles manager of a scene.
*/
static getManager(
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): gdjs.LightObstaclesManager {
// @ts-ignore
if (!runtimeScene._lightObstaclesManager) {
if (!instanceContainer._lightObstaclesManager) {
// Create the shared manager if necessary.
// @ts-ignore
runtimeScene._lightObstaclesManager = new gdjs.LightObstaclesManager(
runtimeScene
instanceContainer._lightObstaclesManager = new gdjs.LightObstaclesManager(
instanceContainer
);
}
// @ts-ignore
return runtimeScene._lightObstaclesManager;
return instanceContainer._lightObstaclesManager;
}
/**
@@ -92,15 +92,15 @@ namespace gdjs {
_registeredInManager: boolean = false;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
this._manager = LightObstaclesManager.getManager(runtimeScene);
super(instanceContainer, behaviorData, owner);
this._manager = LightObstaclesManager.getManager(instanceContainer);
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Make sure the obstacle is or is not in the obstacles manager.
if (!this.activated() && this._registeredInManager) {
this._manager.removeObstacle(this);

View File

@@ -7,7 +7,7 @@ namespace gdjs {
*/
export class LightRuntimeObjectPixiRenderer {
_object: gdjs.LightRuntimeObject;
_runtimeScene: gdjs.RuntimeScene;
_instanceContainer: gdjs.RuntimeInstanceContainer;
_manager: gdjs.LightObstaclesManager;
_radius: number;
_color: [number, number, number];
@@ -30,10 +30,10 @@ namespace gdjs {
constructor(
runtimeObject: gdjs.LightRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
this._runtimeScene = runtimeScene;
this._instanceContainer = instanceContainer;
this._manager = runtimeObject.getObstaclesManager();
this._radius = runtimeObject.getRadius();
const objectColor = runtimeObject._color;
@@ -57,14 +57,14 @@ namespace gdjs {
]);
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
this.updateMesh();
this._isPreview = runtimeScene.getGame().isPreview();
this._isPreview = instanceContainer.getGame().isPreview();
this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0);
this.updateDebugMode();
// Objects will be added in lighting layer, this is just to maintain consistency.
if (this._light) {
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(
@@ -198,7 +198,7 @@ namespace gdjs {
const texture = this._object.getTexture();
this._texture =
texture !== ''
? (this._runtimeScene
? (this._instanceContainer
.getGame()
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
texture

View File

@@ -27,7 +27,7 @@ namespace gdjs {
_texture: string;
_obstaclesManager: gdjs.LightObstaclesManager;
_renderer: gdjs.LightRuntimeObjectRenderer;
_runtimeScene: gdjs.RuntimeScene;
_instanceContainer: gdjs.RuntimeScene;
constructor(
runtimeScene: gdjs.RuntimeScene,
@@ -43,7 +43,7 @@ namespace gdjs {
runtimeScene
);
this._renderer = new gdjs.LightRuntimeObjectRenderer(this, runtimeScene);
this._runtimeScene = runtimeScene;
this._instanceContainer = runtimeScene;
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();

View File

@@ -12,15 +12,17 @@ namespace gdjs {
/**
* Get the links manager of a scene.
*/
static getManager(runtimeScene: gdjs.RuntimeScene): gdjs.LinksManager {
static getManager(
instanceContainer: gdjs.RuntimeInstanceContainer
): gdjs.LinksManager {
// @ts-ignore
if (!runtimeScene.linkedObjectsManager) {
if (!instanceContainer.linkedObjectsManager) {
//Create the shared manager if necessary.
// @ts-ignore
runtimeScene.linkedObjectsManager = new gdjs.LinksManager();
instanceContainer.linkedObjectsManager = new gdjs.LinksManager();
}
// @ts-ignore
return runtimeScene.linkedObjectsManager;
return instanceContainer.linkedObjectsManager;
}
/**
@@ -184,44 +186,50 @@ namespace gdjs {
export namespace evtTools {
export namespace linkedObjects {
gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, obj) {
LinksManager.getManager(runtimeScene).removeAllLinksOf(obj);
gdjs.registerObjectDeletedFromSceneCallback(function (
instanceContainer,
obj
) {
LinksManager.getManager(instanceContainer).removeAllLinksOf(obj);
});
export const linkObjects = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
objA: gdjs.RuntimeObject,
objB: gdjs.RuntimeObject
) {
if (objA === null || objB === null) {
return;
}
LinksManager.getManager(runtimeScene).linkObjects(objA, objB);
LinksManager.getManager(instanceContainer).linkObjects(objA, objB);
};
export const removeLinkBetween = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
objA: gdjs.RuntimeObject,
objB: gdjs.RuntimeObject
) {
if (objA === null || objB === null) {
return;
}
LinksManager.getManager(runtimeScene).removeLinkBetween(objA, objB);
LinksManager.getManager(instanceContainer).removeLinkBetween(
objA,
objB
);
};
export const removeAllLinksOf = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
objA: gdjs.RuntimeObject
) {
if (objA === null) {
return;
}
LinksManager.getManager(runtimeScene).removeAllLinksOf(objA);
LinksManager.getManager(instanceContainer).removeAllLinksOf(objA);
};
export const pickObjectsLinkedTo = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
objectsLists: Hashtable<gdjs.RuntimeObject[]>,
obj: gdjs.RuntimeObject,
eventsFunctionContext: EventsFunctionContext | undefined
@@ -230,7 +238,7 @@ namespace gdjs {
return false;
}
const linkedObjectMap = LinksManager.getManager(
runtimeScene
instanceContainer
)._getMapOfObjectsLinkedWith(obj);
let pickedSomething = false;
@@ -273,7 +281,7 @@ namespace gdjs {
// avoid running an intersection with the picked objects later.
let objectCount = 0;
for (const objectName of parentEventPickedObjectNames) {
objectCount += runtimeScene.getObjects(objectName).length;
objectCount += instanceContainer.getObjects(objectName)!.length;
}
if (parentEventPickedObjects.length === objectCount) {

View File

@@ -20,12 +20,12 @@ namespace gdjs {
constructor(
runtimeObject: gdjs.PanelSpriteRuntimeObject,
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
textureName: string,
tiled: boolean
) {
this._object = runtimeObject;
const texture = (runtimeScene
const texture = (instanceContainer
.getGame()
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
textureName
@@ -58,14 +58,14 @@ namespace gdjs {
];
//Bottom-Right
this.setTexture(textureName, runtimeScene);
this.setTexture(textureName, instanceContainer);
this._spritesContainer.removeChildren();
this._spritesContainer.addChild(this._centerSprite);
for (let i = 0; i < this._borderSprites.length; ++i) {
this._spritesContainer.addChild(this._borderSprites[i]);
}
this._wrapperContainer.addChild(this._spritesContainer);
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._wrapperContainer, runtimeObject.getZOrder());
@@ -188,12 +188,19 @@ namespace gdjs {
this._spritesContainer.cacheAsBitmap = false;
}
setTexture(textureName, runtimeScene): void {
setTexture(
textureName: string,
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
const obj = this._object;
const texture = runtimeScene
// @ts-ignore
const texture = instanceContainer
.getGame()
.getImageManager()
.getPIXITexture(textureName);
.getPIXITexture(textureName) as PIXI.BaseTexture<
PIXI.Resource,
PIXI.IAutoDetectOptions
>;
this._textureWidth = texture.width;
this._textureHeight = texture.height;

View File

@@ -43,14 +43,14 @@ namespace gdjs {
_renderer: gdjs.PanelSpriteRuntimeObjectRenderer;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The scene the object belongs to.
* @param panelSpriteObjectData The initial properties of the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
panelSpriteObjectData: PanelSpriteObjectData
) {
super(runtimeScene, panelSpriteObjectData);
super(instanceContainer, panelSpriteObjectData);
this._rBorder = panelSpriteObjectData.rightMargin;
this._lBorder = panelSpriteObjectData.leftMargin;
this._tBorder = panelSpriteObjectData.topMargin;
@@ -60,7 +60,7 @@ namespace gdjs {
this._height = panelSpriteObjectData.height;
this._renderer = new gdjs.PanelSpriteRuntimeObjectRenderer(
this,
runtimeScene,
instanceContainer,
panelSpriteObjectData.texture,
panelSpriteObjectData.tiled
);
@@ -100,7 +100,7 @@ namespace gdjs {
updateTexture = true;
}
if (updateTexture) {
this.setTexture(newObjectData.texture, this._runtimeScene);
this.setTexture(newObjectData.texture, this.getRuntimeScene());
}
if (oldObjectData.tiled !== newObjectData.tiled) {
return false;
@@ -112,8 +112,8 @@ namespace gdjs {
return this._renderer.getRendererObject();
}
onDestroyFromScene(runtimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
// @ts-ignore
if (this._renderer.onDestroy) {
// @ts-ignore
@@ -121,7 +121,7 @@ namespace gdjs {
}
}
update(runtimeScene: gdjs.RuntimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.ensureUpToDate();
}
@@ -156,10 +156,13 @@ namespace gdjs {
/**
* Set the texture of the panel sprite.
* @param textureName The name of the texture.
* @param runtimeScene The scene the object lives in.
* @param instanceContainer The container the object lives in.
*/
setTexture(textureName: string, runtimeScene: gdjs.RuntimeScene): void {
this._renderer.setTexture(textureName, runtimeScene);
setTexture(
textureName: string,
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
this._renderer.setTexture(textureName, instanceContainer);
}
/**
@@ -196,7 +199,7 @@ namespace gdjs {
this._width = width;
this._renderer.updateWidth();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -208,7 +211,7 @@ namespace gdjs {
this._height = height;
this._renderer.updateHeight();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**

View File

@@ -12,7 +12,7 @@ namespace gdjs {
started: boolean = false;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
runtimeObject: gdjs.RuntimeObject,
objectData: any
) {
@@ -40,7 +40,7 @@ namespace gdjs {
);
} else if (objectData.textureParticleName) {
const sprite = new PIXI.Sprite(
(runtimeScene
(instanceContainer
.getGame()
.getImageManager() as gdjs.PixiImageManager).getPIXITexture(
objectData.textureParticleName
@@ -62,7 +62,7 @@ namespace gdjs {
// Render the texture from graphics using the PIXI Renderer.
// TODO: could be optimized by generating the texture only once per object type,
// instead of at each object creation.
const pixiRenderer = runtimeScene
const pixiRenderer = instanceContainer
.getGame()
.getRenderer()
.getPIXIRenderer();
@@ -167,7 +167,7 @@ namespace gdjs {
// @ts-ignore
this.emitter = new PIXI.particles.Emitter(this.renderer, texture, config);
this.start();
const layer = runtimeScene.getLayer(runtimeObject.getLayer());
const layer = instanceContainer.getLayer(runtimeObject.getLayer());
if (layer) {
layer
.getRenderer()
@@ -267,25 +267,28 @@ namespace gdjs {
isTextureNameValid(
texture: string,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): boolean {
const invalidPixiTexture = runtimeScene
const invalidPixiTexture = instanceContainer
.getGame()
.getImageManager()
.getInvalidPIXITexture();
const pixiTexture = runtimeScene
const pixiTexture = instanceContainer
.getGame()
.getImageManager()
.getPIXITexture(texture);
return pixiTexture.valid && pixiTexture !== invalidPixiTexture;
}
setTextureName(texture: string, runtimeScene: gdjs.RuntimeScene): void {
const invalidPixiTexture = runtimeScene
setTextureName(
texture: string,
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
const invalidPixiTexture = instanceContainer
.getGame()
.getImageManager()
.getInvalidPIXITexture();
const pixiTexture = runtimeScene
const pixiTexture = instanceContainer
.getGame()
.getImageManager()
.getPIXITexture(texture);

View File

@@ -97,12 +97,12 @@ namespace gdjs {
* @param particleObjectData The initial properties of the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
particleObjectData: ParticleEmitterObjectData
) {
super(runtimeScene, particleObjectData);
super(instanceContainer, particleObjectData);
this._renderer = new gdjs.ParticleEmitterObjectRenderer(
runtimeScene,
instanceContainer,
this,
particleObjectData
);
@@ -231,7 +231,10 @@ namespace gdjs {
if (
oldObjectData.textureParticleName !== newObjectData.textureParticleName
) {
this.setTexture(newObjectData.textureParticleName, this._runtimeScene);
this.setTexture(
newObjectData.textureParticleName,
this.getRuntimeScene()
);
}
if (oldObjectData.flow !== newObjectData.flow) {
this.setFlow(newObjectData.flow);
@@ -259,7 +262,7 @@ namespace gdjs {
oldObjectData.rendererParam2 !== newObjectData.rendererParam2
) {
// Destroy the renderer, ensure it's removed from the layer.
const layer = this._runtimeScene.getLayer(this.layer);
const layer = this.getInstanceContainer().getLayer(this.layer);
layer
.getRenderer()
.removeRendererObject(this._renderer.getRendererObject());
@@ -267,7 +270,7 @@ namespace gdjs {
// and recreate the renderer, which will add itself to the layer.
this._renderer = new gdjs.ParticleEmitterObjectRenderer(
this._runtimeScene,
this.getInstanceContainer(),
this,
newObjectData
);
@@ -281,7 +284,7 @@ namespace gdjs {
return true;
}
update(runtimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
if (this._posDirty) {
this._renderer.setPosition(this.getX(), this.getY());
}
@@ -324,24 +327,24 @@ namespace gdjs {
this._renderer.resetEmission(this.flow, this.tank);
}
if (this._textureDirty) {
this._renderer.setTextureName(this.texture, runtimeScene);
this._renderer.setTextureName(this.texture, instanceContainer);
}
this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = false;
this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = false;
this._alphaDirty = this._flowDirty = this._textureDirty = this._tankDirty = false;
this._renderer.update(this.getElapsedTime(runtimeScene) / 1000.0);
this._renderer.update(this.getElapsedTime() / 1000.0);
if (
this._renderer.hasStarted() &&
this.getParticleCount() === 0 &&
this.destroyWhenNoParticles
) {
this.deleteFromScene(runtimeScene);
this.deleteFromScene(instanceContainer);
}
}
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.destroy();
super.onDestroyFromScene(runtimeScene);
super.onDestroyFromScene(instanceContainer);
}
getEmitterForceMin(): number {
@@ -734,9 +737,12 @@ namespace gdjs {
return this.texture;
}
setTexture(texture: string, runtimeScene: gdjs.RuntimeScene): void {
setTexture(
texture: string,
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
if (this.texture !== texture) {
if (this._renderer.isTextureNameValid(texture, runtimeScene)) {
if (this._renderer.isTextureNameValid(texture, instanceContainer)) {
this.texture = texture;
this._textureDirty = true;
}

View File

@@ -4,6 +4,9 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
*/
namespace gdjs {
export interface RuntimeInstanceContainer {
pathfindingObstaclesManager: gdjs.PathfindingObstaclesManager;
}
declare var rbush: any;
/**
@@ -15,24 +18,21 @@ namespace gdjs {
export class PathfindingObstaclesManager {
_obstaclesRBush: any;
/**
* @param object The object
*/
constructor(runtimeScene: gdjs.RuntimeScene) {
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._obstaclesRBush = new rbush();
}
/**
* Get the obstacles manager of a scene.
*/
static getManager(runtimeScene) {
if (!runtimeScene.pathfindingObstaclesManager) {
static getManager(instanceContainer: gdjs.RuntimeInstanceContainer) {
if (!instanceContainer.pathfindingObstaclesManager) {
//Create the shared manager if necessary.
runtimeScene.pathfindingObstaclesManager = new gdjs.PathfindingObstaclesManager(
runtimeScene
instanceContainer.pathfindingObstaclesManager = new gdjs.PathfindingObstaclesManager(
instanceContainer
);
}
return runtimeScene.pathfindingObstaclesManager;
return instanceContainer.pathfindingObstaclesManager;
}
/**
@@ -111,14 +111,14 @@ namespace gdjs {
> | null = null;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
this._impassable = behaviorData.impassable;
this._cost = behaviorData.cost;
this._manager = PathfindingObstaclesManager.getManager(runtimeScene);
this._manager = PathfindingObstaclesManager.getManager(instanceContainer);
//Note that we can't use getX(), getWidth()... of owner here:
//The owner is not yet fully constructed.
@@ -140,7 +140,7 @@ namespace gdjs {
}
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
//Make sure the obstacle is or is not in the obstacles manager.
if (!this.activated() && this._registeredInManager) {
this._manager.removeObstacle(this);
@@ -170,7 +170,7 @@ namespace gdjs {
}
}
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
getAABB() {
return this.owner.getAABB();

View File

@@ -38,11 +38,11 @@ namespace gdjs {
_movementAngle: float = 0;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
//The path computed and followed by the object (Array of arrays containing x and y position)
if (this._path === undefined) {
@@ -60,7 +60,9 @@ namespace gdjs {
this._gridOffsetX = behaviorData.gridOffsetX || 0;
this._gridOffsetY = behaviorData.gridOffsetY || 0;
this._extraBorder = behaviorData.extraBorder;
this._manager = gdjs.PathfindingObstaclesManager.getManager(runtimeScene);
this._manager = gdjs.PathfindingObstaclesManager.getManager(
instanceContainer
);
this._searchContext = new gdjs.PathfindingRuntimeBehavior.SearchContext(
this._manager
);
@@ -313,7 +315,11 @@ namespace gdjs {
/**
* Compute and move on the path to the specified destination.
*/
moveTo(runtimeScene: gdjs.RuntimeScene, x: float, y: float) {
moveTo(
instanceContainer: gdjs.RuntimeInstanceContainer,
x: float,
y: float
) {
const owner = this.owner;
//First be sure that there is a path to compute.
@@ -403,13 +409,13 @@ namespace gdjs {
}
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
if (this._path.length === 0 || this._reachedEnd) {
return;
}
// Update the speed of the object
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
const timeDelta = this.owner.getElapsedTime() / 1000;
const previousSpeed = this._speed;
if (this._speed !== this._maxSpeed) {
this._speed += this._acceleration * timeDelta;
@@ -452,8 +458,7 @@ namespace gdjs {
) {
this.owner.rotateTowardAngle(
this._movementAngle + this._angleOffset,
this._angularSpeed,
runtimeScene
this._angularSpeed
);
}
} else {
@@ -463,7 +468,7 @@ namespace gdjs {
this.owner.setY(newPos[1]);
}
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
/**
* Compute the euclidean distance between two positions.

View File

@@ -61,9 +61,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
sprites: [
{
originPoint: objectCenteredOnCells
? { x: 80, y: 80 }
: { x: 87, y: 87 },
centerPoint: { x: 80, y: 80 },
? { name: 'Origin', x: 80, y: 80 }
: { name: 'Origin', x: 87, y: 87 },
centerPoint: {
name: 'Center',
x: 80,
y: 80,
automatic: false,
},
points: [
{ name: 'Center', x: 80, y: 80 },
objectCenteredOnCells
@@ -71,13 +76,17 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
: { name: 'Origin', x: 87, y: 87 },
],
hasCustomCollisionMask: false,
customCollisionMask: [],
image: '',
},
],
timeBetweenFrames: 0,
looping: false,
},
],
useMultipleDirections: false,
},
],
effects: [],
behaviors: [
{
type: 'PathfindingBehavior::PathfindingBehavior',
@@ -93,6 +102,9 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
extraBorder: 0,
},
],
variables: [],
effects: [],
updateIfNotVisible: true,
});
player.getWidth = function () {
return 160;
@@ -116,9 +128,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
sprites: [
{
originPoint: objectCenteredOnCells
? { x: 80, y: 80 }
: { x: 87, y: 87 },
centerPoint: { x: 80, y: 80 },
? { name: 'Origin', x: 80, y: 80 }
: { name: 'Origin', x: 87, y: 87 },
centerPoint: {
name: 'Center',
x: 80,
y: 80,
automatic: false,
},
points: [
{ name: 'Center', x: 80, y: 80 },
objectCenteredOnCells
@@ -126,13 +143,17 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
: { name: 'Origin', x: 87, y: 87 },
],
hasCustomCollisionMask: false,
customCollisionMask: [],
image: '',
},
],
timeBetweenFrames: 0,
looping: false,
},
],
useMultipleDirections: false,
},
],
effects: [],
behaviors: [
{
type: 'PathfindingBehavior::PathfindingObstacleBehavior',
@@ -140,6 +161,9 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
cost: 2,
},
],
variables: [],
effects: [],
updateIfNotVisible: true,
});
obstacle.getWidth = function () {
return 160;

View File

@@ -3816,6 +3816,23 @@ module.exports = {
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
return [];
const dummyBehavior = extension
.getBehaviorMetadata('Physics2::Physics2Behavior')
.get();
const sharedData = extension
.getBehaviorMetadata('Physics2::Physics2Behavior')
.getSharedDataInstance();
return [
gd.ProjectHelper.sanityCheckBehaviorProperty(
dummyBehavior,
'density',
'123'
),
gd.ProjectHelper.sanityCheckBehaviorsSharedDataProperty(
sharedData,
'gravityY',
'456'
),
];
},
};

View File

@@ -35,7 +35,7 @@ namespace gdjs {
*/
_registeredBehaviors: Set<Physics2RuntimeBehavior>;
constructor(runtimeScene: gdjs.RuntimeScene, sharedData) {
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, sharedData) {
this._registeredBehaviors = new Set();
this.gravityX = sharedData.gravityX;
this.gravityY = sharedData.gravityY;
@@ -349,11 +349,11 @@ namespace gdjs {
_verticesBuffer: integer = 0;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
this.bodyType = behaviorData.bodyType;
this.bullet = behaviorData.bullet;
this.fixedRotation = behaviorData.fixedRotation;
@@ -382,7 +382,7 @@ namespace gdjs {
this.currentContacts.length = 0;
this.destroyedDuringFrameLogic = false;
this._sharedData = Physics2SharedData.getSharedData(
runtimeScene,
instanceContainer.getScene(),
behaviorData.name
);
this._tempb2Vec2 = new Box2D.b2Vec2();
@@ -824,14 +824,15 @@ namespace gdjs {
return true;
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Step the world if not done this frame yet
if (!this._sharedData.stepped) {
// Reset started and ended contacts array for all physics instances.
this._sharedData.resetStartedAndEndedCollisions();
this._sharedData.updateBodiesFromObjects();
this._sharedData.step(
runtimeScene.getTimeManager().getElapsedTime() / 1000.0
instanceContainer.getScene().getTimeManager().getElapsedTime() /
1000.0
);
}
@@ -862,7 +863,7 @@ namespace gdjs {
this._objectOldAngle = this.owner.getAngle();
}
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Reset world step to update next frame
this._sharedData.stepped = false;
}

View File

@@ -117,11 +117,11 @@ namespace gdjs {
private _manager: gdjs.PlatformObjectsManager;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
this._gravity = behaviorData.gravity;
this._maxFallingSpeed = behaviorData.maxFallingSpeed;
this._ladderClimbingSpeed = behaviorData.ladderClimbingSpeed || 150;
@@ -146,7 +146,7 @@ namespace gdjs {
this._potentialCollidingObjects = [];
this._overlappedJumpThru = [];
this._manager = gdjs.PlatformObjectsManager.getManager(runtimeScene);
this._manager = gdjs.PlatformObjectsManager.getManager(instanceContainer);
this._falling = new Falling(this);
this._onFloor = new OnFloor(this);
@@ -210,7 +210,7 @@ namespace gdjs {
return true;
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
const LEFTKEY = 37;
const UPKEY = 38;
const RIGHTKEY = 39;
@@ -219,13 +219,13 @@ namespace gdjs {
const RSHIFTKEY = 2016;
const SPACEKEY = 32;
const object = this.owner;
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
const timeDelta = this.owner.getElapsedTime() / 1000;
//0.1) Get the player input:
this._requestedDeltaX = 0;
this._requestedDeltaY = 0;
const inputManager = runtimeScene.getGame().getInputManager();
const inputManager = instanceContainer.getGame().getInputManager();
this._leftKey ||
(this._leftKey =
!this._ignoreDefaultControls && inputManager.isKeyPressed(LEFTKEY));
@@ -329,7 +329,7 @@ namespace gdjs {
this._lastDeltaY = object.getY() - oldY;
}
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
private _updateSpeed(timeDelta: float): float {
const previousSpeed = this._currentSpeed;

View File

@@ -15,27 +15,24 @@ namespace gdjs {
export class PlatformObjectsManager {
private _platformRBush: any;
/**
* @param object The object
*/
constructor(runtimeScene: gdjs.RuntimeScene) {
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._platformRBush = new rbush();
}
/**
* Get the platforms manager of a scene.
*/
static getManager(runtimeScene: gdjs.RuntimeScene) {
static getManager(instanceContainer: gdjs.RuntimeInstanceContainer) {
// @ts-ignore
if (!runtimeScene.platformsObjectsManager) {
if (!instanceContainer.platformsObjectsManager) {
//Create the shared manager if necessary.
// @ts-ignore
runtimeScene.platformsObjectsManager = new gdjs.PlatformObjectsManager(
runtimeScene
instanceContainer.platformsObjectsManager = new gdjs.PlatformObjectsManager(
instanceContainer
);
}
// @ts-ignore
return runtimeScene.platformsObjectsManager;
return instanceContainer.platformsObjectsManager;
}
/**
@@ -132,11 +129,11 @@ namespace gdjs {
_registeredInManager: boolean = false;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
this._platformType = behaviorData.platformType;
if (behaviorData.platformType === 'Ladder') {
this._platformType = PlatformRuntimeBehavior.LADDER;
@@ -147,7 +144,7 @@ namespace gdjs {
}
this._canBeGrabbed = behaviorData.canBeGrabbed || false;
this._yGrabOffset = behaviorData.yGrabOffset || 0;
this._manager = PlatformObjectsManager.getManager(runtimeScene);
this._manager = PlatformObjectsManager.getManager(instanceContainer);
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
@@ -169,7 +166,7 @@ namespace gdjs {
}
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
//Scene change is not supported
/*if ( parentScene != &scene ) //Parent scene has changed
{
@@ -211,7 +208,7 @@ namespace gdjs {
}
}
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
onActivate() {
if (this._registeredInManager) {

View File

@@ -0,0 +1,197 @@
// @flow
/**
* This is a declaration of an extension for GDevelop 5.
*
* Changes in this file are watched and automatically imported if the editor
* is running. You can also manually run `node import-GDJS-Runtime.js` (in newIDE/app/scripts).
*
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
* ⚠️ If you make a change and the extension is not loaded, open the developer console
* and search for any errors.
*
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/*::
// Import types to allow Flow to do static type checking on this file.
// Extensions declaration are typed using Flow (like the editor), but the files
// for the game engine are checked with TypeScript annotations.
import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsExtensionTypes.flow.js'
*/
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
'PlayerAuthentication',
_('Player Authentication (experimental)'),
_('Allow your game to authenticate players.'),
'Florian Rival',
'Open source (MIT License)'
)
.setExtensionHelpPath('/all-features/player-authentication')
.setCategory('Players');
extension
.addInstructionOrExpressionGroupMetadata(
_('Player Authentication (experimental)')
)
.setIcon('JsPlatform/Extensions/authentication.svg');
extension
.addDependency()
.setName('InAppBrowser Cordova plugin')
.setDependencyType('cordova')
.setExportName('cordova-plugin-inappbrowser');
extension
.addAction(
'DisplayAuthenticationBanner',
_('Display authentication banner'),
_(
'Display an authentication banner at the top of the game screen, for the player to log in.'
),
_('Display an authentication banner'),
'',
'JsPlatform/Extensions/authentication.svg',
'JsPlatform/Extensions/authentication.svg'
)
.addCodeOnlyParameter('currentScene', '')
.setHelpPath('/all-features/player-authentication')
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.displayAuthenticationBanner');
extension
.addAction(
'OpenAuthenticationWindow',
_('Open authentication window'),
_('Open an authentication window for the player to log in.'),
_('Open an authentication window'),
'',
'JsPlatform/Extensions/authentication.svg',
'JsPlatform/Extensions/authentication.svg'
)
.addCodeOnlyParameter('currentScene', '')
.setHelpPath('/all-features/player-authentication')
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.openAuthenticationWindow');
extension
.addCondition(
'IsAuthenticationWindowOpen',
_('Authentication window is open'),
_('Check if the authentication window is open.'),
_('Authentication window is open'),
'',
'JsPlatform/Extensions/authentication.svg',
'JsPlatform/Extensions/authentication.svg'
)
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.isAuthenticationWindowOpen');
extension
.addAction(
'LogOut',
_('Log out the player'),
_('Log out the player.'),
_('Log out the player'),
'',
'JsPlatform/Extensions/authentication.svg',
'JsPlatform/Extensions/authentication.svg'
)
.addCodeOnlyParameter('currentScene', '')
.setHelpPath('/all-features/player-authentication')
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.logout');
extension
.addStrExpression(
'Username',
_('Username'),
_('Get the username of the authenticated player.'),
'',
'JsPlatform/Extensions/authentication.svg'
)
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.getUsername');
extension
.addCondition(
'IsPlayerAuthenticated',
_('Player is authenticated'),
_('Check if the player is authenticated.'),
_('Player is authenticated'),
'',
'JsPlatform/Extensions/authentication.svg',
'JsPlatform/Extensions/authentication.svg'
)
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.isAuthenticated');
extension
.addCondition(
'HasPlayerLoggedIn',
_('Player has logged in'),
_('Check if the player has just logged in.'),
_('Player has logged in'),
'',
'JsPlatform/Extensions/authentication.svg',
'JsPlatform/Extensions/authentication.svg'
)
.getCodeExtraInformation()
.setIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.setFunctionName('gdjs.playerAuthentication.hasLoggedIn');
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
return [];
},
};

View File

@@ -0,0 +1,448 @@
namespace gdjs {
const logger = new gdjs.Logger('Player Authentication');
export namespace playerAuthenticationComponents {
const getPlayerLoginMessages = ({
platform,
isGameRegistered,
}: {
platform: 'cordova' | 'electron' | 'web';
isGameRegistered: boolean;
}) =>
isGameRegistered
? {
title: 'Logging in...',
text1:
platform === 'cordova'
? "One moment, we're opening a window for you to log in."
: "One moment, we're opening a new page with your web browser for you to log in.",
text2:
'If the window did not open, please check your pop-up blocker and click the button below to try again.',
}
: {
title: 'Your game is not registered!',
text1:
'In order to use player authentication, this game must be registered with GDevelop Services first.',
text2: 'Head to your Game Dashboard, then try again.',
};
/**
* Creates a DOM element that will contain the loader or a message if the game is not registered.
*/
export const computeAuthenticationContainer = function (
onCloseAuthenticationContainer: () => void
): {
rootContainer: HTMLDivElement;
loaderContainer: HTMLDivElement;
} {
const rootContainer = document.createElement('div');
rootContainer.id = 'authentication-root-container';
rootContainer.style.position = 'relative';
rootContainer.style.backgroundColor = 'rgba(14, 6, 45, 0.5)';
rootContainer.style.opacity = '1';
rootContainer.style.width = '100%';
rootContainer.style.height = '100%';
rootContainer.style.zIndex = '2';
rootContainer.style.pointerEvents = 'all';
const subContainer = document.createElement('div');
subContainer.id = 'authentication-sub-container';
subContainer.style.backgroundColor = '#FFFFFF';
subContainer.style.position = 'absolute';
subContainer.style.top = '16px';
subContainer.style.bottom = '16px';
subContainer.style.left = '16px';
subContainer.style.right = '16px';
subContainer.style.borderRadius = '8px';
subContainer.style.boxShadow = '0px 4px 4px rgba(0, 0, 0, 0.25)';
subContainer.style.padding = '16px';
const _closeContainer: HTMLDivElement = document.createElement('div');
_closeContainer.style.cursor = 'pointer';
_closeContainer.style.display = 'flex';
_closeContainer.style.justifyContent = 'right';
_closeContainer.style.alignItems = 'center';
_closeContainer.style.zIndex = '3';
addTouchAndClickEventListeners(
_closeContainer,
onCloseAuthenticationContainer
);
const _close = document.createElement('img');
_close.setAttribute('width', '15px');
_close.setAttribute(
'src',
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOCIgaGVpZ2h0PSI4IiB2aWV3Qm94PSIwIDAgOCA4IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTcuODUzNTUgMC4xNDY0NDdDOC4wNDg4MiAwLjM0MTcwOSA4LjA0ODgyIDAuNjU4MjkxIDcuODUzNTUgMC44NTM1NTNMMC44NTM1NTMgNy44NTM1NUMwLjY1ODI5MSA4LjA0ODgyIDAuMzQxNzA5IDguMDQ4ODIgMC4xNDY0NDcgNy44NTM1NUMtMC4wNDg4MTU1IDcuNjU4MjkgLTAuMDQ4ODE1NSA3LjM0MTcxIDAuMTQ2NDQ3IDcuMTQ2NDVMNy4xNDY0NSAwLjE0NjQ0N0M3LjM0MTcxIC0wLjA0ODgxNTUgNy42NTgyOSAtMC4wNDg4MTU1IDcuODUzNTUgMC4xNDY0NDdaIiBmaWxsPSIjMUQxRDI2Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMC4xNDY0NDcgMC4xNDY0NDdDMC4zNDE3MDkgLTAuMDQ4ODE1NSAwLjY1ODI5MSAtMC4wNDg4MTU1IDAuODUzNTUzIDAuMTQ2NDQ3TDcuODUzNTUgNy4xNDY0NUM4LjA0ODgyIDcuMzQxNzEgOC4wNDg4MiA3LjY1ODI5IDcuODUzNTUgNy44NTM1NUM3LjY1ODI5IDguMDQ4ODIgNy4zNDE3MSA4LjA0ODgyIDcuMTQ2NDUgNy44NTM1NUwwLjE0NjQ0NyAwLjg1MzU1M0MtMC4wNDg4MTU1IDAuNjU4MjkxIC0wLjA0ODgxNTUgMC4zNDE3MDkgMC4xNDY0NDcgMC4xNDY0NDdaIiBmaWxsPSIjMUQxRDI2Ii8+Cjwvc3ZnPgo='
);
_closeContainer.appendChild(_close);
const loaderContainer: HTMLDivElement = document.createElement('div');
loaderContainer.id = 'authentication-container-loader';
loaderContainer.style.display = 'flex';
loaderContainer.style.flexDirection = 'column';
loaderContainer.style.height = '100%';
loaderContainer.style.width = '100%';
loaderContainer.style.justifyContent = 'center';
loaderContainer.style.alignItems = 'center';
const _loader = document.createElement('img');
_loader.setAttribute('width', '28px');
_loader.setAttribute(
'src',
'data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iYW5pbWF0ZS1zcGluIC1tbC0xIG1yLTMgaC01IHctNSB0ZXh0LXdoaXRlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CjxjaXJjbGUgb3BhY2l0eT0nMC4yNScgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSI0Ij48L2NpcmNsZT4KPHBhdGggb3BhY2l0eT0nMC43NScgZmlsbD0iY3VycmVudENvbG9yIiBkPSJNNCAxMmE4IDggMCAwMTgtOFYwQzUuMzczIDAgMCA1LjM3MyAwIDEyaDR6bTIgNS4yOTFBNy45NjIgNy45NjIgMCAwMTQgMTJIMGMwIDMuMDQyIDEuMTM1IDUuODI0IDMgNy45MzhsMy0yLjY0N3oiPjwvcGF0aD4KPC9zdmc+'
);
_loader.style.marginTop = '50px';
try {
_loader.animate(
[{ transform: 'rotate(0deg)' }, { transform: 'rotate(359deg)' }],
{
duration: 3000,
iterations: Infinity,
}
);
} catch {
logger.warn('Animation not supported, loader will be fixed.');
}
loaderContainer.appendChild(_loader);
subContainer.appendChild(_closeContainer);
subContainer.appendChild(loaderContainer);
rootContainer.appendChild(subContainer);
return { rootContainer, loaderContainer };
};
/**
* Helper to add the texts to the authentication container
* based on the platform or if the game is registered.
*/
export const addAuthenticationTextsToLoadingContainer = (
loaderContainer: HTMLDivElement,
platform,
isGameRegistered
) => {
const textContainer: HTMLDivElement = document.createElement('div');
textContainer.id = 'authentication-container-texts';
textContainer.style.display = 'flex';
textContainer.style.flexDirection = 'column';
textContainer.style.width = '100%';
textContainer.style.justifyContent = 'center';
textContainer.style.alignItems = 'center';
textContainer.style.position = 'relative';
textContainer.style.zIndex = '3';
textContainer.style.fontSize = '11pt';
textContainer.style.fontFamily =
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
const messages = getPlayerLoginMessages({
platform,
isGameRegistered,
});
const title = document.createElement('h1');
title.innerText = messages.title;
title.style.fontSize = '20pt';
title.style.fontWeight = 'bold';
const text1 = document.createElement('p');
text1.innerText = messages.text1;
const text2 = document.createElement('p');
text2.innerText = messages.text2;
textContainer.appendChild(title);
textContainer.appendChild(text1);
textContainer.appendChild(text2);
if (!isGameRegistered) {
// Remove the loader.
loaderContainer.innerHTML = '';
}
loaderContainer.prepend(textContainer);
return textContainer;
};
/**
* Helper to add the authentication link in case the window hasn't opened properly.
* Useful for Electron & Web platforms.
*/
export const addAuthenticationUrlToTextsContainer = (
onClick: () => void,
textContainer: HTMLDivElement
) => {
const link = document.createElement('a');
addTouchAndClickEventListeners(link, onClick);
link.innerText = 'Click here to authenticate';
link.style.color = '#0078d4';
link.style.textDecoration = 'none';
link.style.textDecoration = 'underline';
link.style.cursor = 'pointer';
textContainer.appendChild(link);
};
/**
* Creates a DOM element to display a dismissable banner.
*/
export const computeDismissableBanner = function (
onDismissBanner: () => void
): HTMLDivElement {
const divContainer = document.createElement('div');
divContainer.id = 'authenticated-banner';
divContainer.style.position = 'absolute';
divContainer.style.pointerEvents = 'all';
divContainer.style.backgroundColor = '#0E062D';
divContainer.style.top = '0px';
divContainer.style.height = '48px';
divContainer.style.left = '0px';
divContainer.style.width = '100%';
divContainer.style.padding = '6px 16px';
// Use zIndex 1 to make sure it is below the authentication iframe or webview.
divContainer.style.zIndex = '1';
divContainer.style.display = 'flex';
divContainer.style.flexDirection = 'row-reverse';
divContainer.style.justifyContent = 'space-between';
divContainer.style.alignItems = 'center';
divContainer.style.boxShadow = '0px 4px 4px rgba(0, 0, 0, 0.25)';
divContainer.style.fontSize = '11pt';
divContainer.style.color = '#FFFFFF';
divContainer.style.fontFamily =
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
const _closeContainer: HTMLDivElement = document.createElement('div');
_closeContainer.style.cursor = 'pointer';
_closeContainer.style.display = 'flex';
_closeContainer.style.justifyContent = 'center';
_closeContainer.style.alignItems = 'center';
_closeContainer.style.zIndex = '3';
_closeContainer.style.marginRight = '32px';
_closeContainer.style.height = '100%';
addTouchAndClickEventListeners(_closeContainer, onDismissBanner);
const _close = document.createElement('img');
_close.setAttribute('width', '30px');
_close.setAttribute(
'src',
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIzLjc1IDguMDEyNUwyMS45ODc1IDYuMjVMMTUgMTMuMjM3NUw4LjAxMjUgNi4yNUw2LjI1IDguMDEyNUwxMy4yMzc1IDE1TDYuMjUgMjEuOTg3NUw4LjAxMjUgMjMuNzVMMTUgMTYuNzYyNUwyMS45ODc1IDIzLjc1TDIzLjc1IDIxLjk4NzVMMTYuNzYyNSAxNUwyMy43NSA4LjAxMjVaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K'
);
_closeContainer.appendChild(_close);
divContainer.appendChild(_closeContainer);
return divContainer;
};
/**
* Creates a DOM element representing a banner for the user to know which account
* they're using and also to allow switching to another account.
*/
export const computeAuthenticatedBanner = function (
onOpenAuthenticationWindow: () => void,
onDismissBanner: () => void,
username: string | null
): HTMLDivElement {
const divContainer = computeDismissableBanner(onDismissBanner);
const playerUsername = username || 'Anonymous';
const _textContainer: HTMLDivElement = document.createElement('div');
const loggedText = document.createElement('p');
loggedText.id = 'loggedText';
loggedText.innerHTML = `<img style="margin-right:4px" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNC4xNjY2NyAwQzEuODY2NjcgMCAwIDEuODY2NjcgMCA0LjE2NjY3QzAgNi40NjY2NyAxLjg2NjY3IDguMzMzMzMgNC4xNjY2NyA4LjMzMzMzQzYuNDY2NjcgOC4zMzMzMyA4LjMzMzMzIDYuNDY2NjcgOC4zMzMzMyA0LjE2NjY3QzguMzMzMzMgMS44NjY2NyA2LjQ2NjY3IDAgNC4xNjY2NyAwWk0zLjMzMzMzIDYuMjVMMS4yNSA0LjE2NjY3TDEuODM3NSAzLjU3OTE3TDMuMzMzMzMgNS4wNzA4M0w2LjQ5NTgzIDEuOTA4MzNMNy4wODMzMyAyLjVMMy4zMzMzMyA2LjI1WiIgZmlsbD0iIzAwQ0M4MyIvPgo8L3N2Zz4K" />
Logged as ${playerUsername}`;
loggedText.style.margin = '0px';
const changeAccountText = document.createElement('p');
changeAccountText.id = 'changeAccountText';
changeAccountText.innerText = `Click here to switch to another account.`;
changeAccountText.style.margin = '0px';
changeAccountText.style.marginTop = '4px';
changeAccountText.style.textDecoration = 'underline';
changeAccountText.style.cursor = 'pointer';
addTouchAndClickEventListeners(
changeAccountText,
onOpenAuthenticationWindow
);
_textContainer.appendChild(loggedText);
_textContainer.appendChild(changeAccountText);
divContainer.appendChild(_textContainer);
return divContainer;
};
/**
* Creates a DOM element representing a banner for the user to know
* they are not connected and to allow logging in.
*/
export const computeNotAuthenticatedBanner = function (
onOpenAuthenticationWindow: () => void,
onDismissBanner: () => void
): HTMLDivElement {
const divContainer = computeDismissableBanner(onDismissBanner);
const _textContainer: HTMLDivElement = document.createElement('div');
const loggedText = document.createElement('p');
loggedText.id = 'loggedText';
loggedText.innerHTML = `You are not authenticated.`;
loggedText.style.margin = '0px';
const changeAccountText = document.createElement('p');
changeAccountText.id = 'changeAccountText';
changeAccountText.innerText = `Click here to log in.`;
changeAccountText.style.margin = '0px';
changeAccountText.style.marginTop = '4px';
changeAccountText.style.textDecoration = 'underline';
changeAccountText.style.cursor = 'pointer';
addTouchAndClickEventListeners(
changeAccountText,
onOpenAuthenticationWindow
);
_textContainer.appendChild(loggedText);
_textContainer.appendChild(changeAccountText);
divContainer.appendChild(_textContainer);
return divContainer;
};
/**
* Create, display, and hide the logged in confirmation.
*/
export const displayLoggedInNotification = function (
domContainer: HTMLDivElement,
username: string
) {
showNotification(
domContainer,
'authenticated-notification',
`<img style="margin-right:4px" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNC4xNjY2NyAwQzEuODY2NjcgMCAwIDEuODY2NjcgMCA0LjE2NjY3QzAgNi40NjY2NyAxLjg2NjY3IDguMzMzMzMgNC4xNjY2NyA4LjMzMzMzQzYuNDY2NjcgOC4zMzMzMyA4LjMzMzMzIDYuNDY2NjcgOC4zMzMzMyA0LjE2NjY3QzguMzMzMzMgMS44NjY2NyA2LjQ2NjY3IDAgNC4xNjY2NyAwWk0zLjMzMzMzIDYuMjVMMS4yNSA0LjE2NjY3TDEuODM3NSAzLjU3OTE3TDMuMzMzMzMgNS4wNzA4M0w2LjQ5NTgzIDEuOTA4MzNMNy4wODMzMyAyLjVMMy4zMzMzMyA2LjI1WiIgZmlsbD0iIzAwQ0M4MyIvPgo8L3N2Zz4K" />
Logged as ${username}`,
'success'
);
};
/**
* Create, display, and hide the logged in confirmation.
*/
export const displayLoggedOutNotification = function (
domContainer: HTMLDivElement
) {
showNotification(
domContainer,
'authenticated-notification',
`<img style="margin-right:4px" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOSIgaGVpZ2h0PSI5IiB2aWV3Qm94PSIwIDAgOSA5IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNC4xNjY2NyAwQzEuODY2NjcgMCAwIDEuODY2NjcgMCA0LjE2NjY3QzAgNi40NjY2NyAxLjg2NjY3IDguMzMzMzMgNC4xNjY2NyA4LjMzMzMzQzYuNDY2NjcgOC4zMzMzMyA4LjMzMzMzIDYuNDY2NjcgOC4zMzMzMyA0LjE2NjY3QzguMzMzMzMgMS44NjY2NyA2LjQ2NjY3IDAgNC4xNjY2NyAwWk0zLjMzMzMzIDYuMjVMMS4yNSA0LjE2NjY3TDEuODM3NSAzLjU3OTE3TDMuMzMzMzMgNS4wNzA4M0w2LjQ5NTgzIDEuOTA4MzNMNy4wODMzMyAyLjVMMy4zMzMzMyA2LjI1WiIgZmlsbD0iIzAwQ0M4MyIvPgo8L3N2Zz4K" />
Logged out`,
'success'
);
};
/**
* Create, display, and hide an error notification.
*/
export const displayErrorNotification = function (
domContainer: HTMLDivElement
) {
showNotification(
domContainer,
'error-notification',
'An error occurred while authenticating, please try again.',
'error'
);
};
/**
* Helper to show a notification to the user, that disappears automatically.
*/
export const showNotification = function (
domContainer: HTMLDivElement,
id: string,
content: string,
type: 'success' | 'error'
) {
const divContainer = document.createElement('div');
divContainer.id = id;
divContainer.style.position = 'absolute';
divContainer.style.pointerEvents = 'all';
divContainer.style.backgroundColor =
type === 'success' ? '#0E062D' : 'red';
divContainer.style.top = '12px';
divContainer.style.right = '16px';
divContainer.style.padding = '6px 32px 6px 6px';
// Use zIndex 1 to make sure it is below the authentication iframe or webview.
divContainer.style.zIndex = '1';
divContainer.style.display = 'flex';
divContainer.style.flexDirection = 'row-reverse';
divContainer.style.justifyContent = 'space-between';
divContainer.style.alignItems = 'center';
divContainer.style.boxShadow = '0px 4px 4px rgba(0, 0, 0, 0.25)';
divContainer.style.borderRadius = '4px';
divContainer.style.fontSize = '11pt';
divContainer.style.color = '#FFFFFF';
divContainer.style.fontFamily =
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
try {
divContainer.animate(
[
{ transform: 'translateY(-30px)', opacity: 0 },
{ transform: 'translateY(0px)', opacity: 1 },
],
{
duration: 700,
easing: 'ease-out',
}
);
} catch {
logger.warn('Animation not supported, div will be fixed.');
}
const loggedText = document.createElement('p');
loggedText.id = 'loggedText';
loggedText.innerHTML = content;
loggedText.style.margin = '0px';
divContainer.appendChild(loggedText);
domContainer.appendChild(divContainer);
const animationTime = 700;
const notificationTime = 5000;
setTimeout(() => {
try {
divContainer.animate(
[
{ transform: 'translateY(0px)', opacity: 1 },
{ transform: 'translateY(-30px)', opacity: 0 },
],
{
duration: animationTime,
easing: 'ease-in',
}
);
} catch {
logger.warn('Animation not supported, div will be fixed.');
}
}, notificationTime);
// Use timeout because onanimationend listener does not work.
setTimeout(() => {
divContainer.remove();
}, notificationTime + animationTime);
};
/**
* Helper to add event listeners on a pressable/clickable element
* to work on both desktop and mobile.
*/
export const addTouchAndClickEventListeners = function (
element: HTMLElement,
action: () => void
) {
// Touch start event listener for mobile.
element.addEventListener('touchstart', (event) => {
action();
});
// Click event listener for desktop.
element.addEventListener('click', (event) => {
action();
});
};
}
}

View File

@@ -0,0 +1,717 @@
namespace gdjs {
declare var cordova: any;
const logger = new gdjs.Logger('Player Authentication');
const authComponents = gdjs.playerAuthenticationComponents;
// TODO EBO Replace runtimeScene to instanceContainer.
export namespace playerAuthentication {
// In order to test in development mode, change this to true.
const dev = false;
// Authentication information.
let _username: string | null = null;
let _userId: string | null = null;
let _userToken: string | null = null;
let _justLoggedIn = false;
let _checkedLocalStorage: boolean = false;
// Authentication display
let _authenticationWindow: Window | null = null; // For Web.
let _authenticationInAppWindow: Window | null = null; // For Cordova.
let _authenticationRootContainer: HTMLDivElement | null = null;
let _authenticationLoaderContainer: HTMLDivElement | null = null;
let _authenticationTextContainer: HTMLDivElement | null = null;
let _authenticationBanner: HTMLDivElement | null = null;
let _authenticationTimeoutId: NodeJS.Timeout | null = null;
// Communication methods.
let _authenticationMessageCallback:
| ((event: MessageEvent) => void)
| null = null;
let _cordovaAuthenticationMessageCallback:
| ((event: MessageEvent) => void)
| null = null;
let _websocket: WebSocket | null = null;
// Ensure that the condition "just logged in" is valid only for one frame.
gdjs.registerRuntimeScenePostEventsCallback(() => {
_justLoggedIn = false;
});
const getLocalStorageKey = (gameId: string) =>
`${gameId}_authenticatedUser`;
const getAuthWindowUrl = ({
gameId,
connectionId,
}: {
gameId: string;
connectionId?: string;
}) =>
`https://liluo.io/auth?gameId=${gameId}${
connectionId ? `&connectionId=${connectionId}` : ''
}${dev ? '&dev=true' : ''}`;
/**
* Helper returning the platform.
*/
const getPlatform = (
runtimeScene: RuntimeScene
): 'electron' | 'cordova' | 'web' => {
const electron = runtimeScene.getGame().getRenderer().getElectron();
if (electron) {
return 'electron';
}
if (typeof cordova !== 'undefined') return 'cordova';
return 'web';
};
/**
* Returns true if a user token is present in the local storage.
*/
export const isAuthenticated = () => {
if (!_checkedLocalStorage) {
readAuthenticatedUserFromLocalStorage();
}
return _userToken !== null;
};
/**
* Returns true if the user just logged in.
* Useful to update username or trigger messages in the game.
*/
export const hasLoggedIn = () => _justLoggedIn;
/**
* Returns the username from the local storage.
*/
export const getUsername = () => {
if (!_checkedLocalStorage) {
readAuthenticatedUserFromLocalStorage();
}
return _username || '';
};
/**
* Returns the user token from the local storage.
*/
export const getUserToken = () => {
if (!_checkedLocalStorage) {
readAuthenticatedUserFromLocalStorage();
}
return _userToken || null;
};
/**
* Returns the username from the local storage.
*/
export const getUserId = () => {
if (!_checkedLocalStorage) {
readAuthenticatedUserFromLocalStorage();
}
return _userId || null;
};
/**
* Returns true if the game is registered, false otherwise.
* Useful to display a message to the user to register the game before logging in.
*/
const checkIfGameIsRegistered = (
gameId: string,
tries: number = 0
): Promise<boolean> => {
const rootApi = dev
? 'https://api-dev.gdevelop.io'
: 'https://api.gdevelop.io';
const url = `${rootApi}/game/public-game/${gameId}`;
return fetch(url, { method: 'HEAD' }).then(
(response) => {
if (response.status !== 200) {
logger.warn(
`Error while fetching the game: ${response.status} ${response.statusText}`
);
// If the response is not 404, it may be a timeout, so retry a few times.
if (response.status === 404 || tries > 2) {
return false;
}
return checkIfGameIsRegistered(gameId, tries + 1);
}
return true;
},
(err) => {
logger.error('Error while fetching game:', err);
return false;
}
);
};
/**
* Remove the user information from the local storage.
*/
export const logout = (runtimeScene: RuntimeScene) => {
_username = null;
_userToken = null;
_userId = null;
const gameId = gdjs.projectData.properties.projectUuid;
if (!gameId) {
logger.error('Missing game id in project properties.');
return;
}
window.localStorage.removeItem(getLocalStorageKey(gameId));
cleanUpAuthWindowAndCallbacks(runtimeScene);
removeAuthenticationBanner(runtimeScene);
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
);
return;
}
authComponents.displayLoggedOutNotification(domElementContainer);
};
/**
* Retrieves the user information from the local storage, and store
* them in the extension variables.
*/
const readAuthenticatedUserFromLocalStorage = () => {
const gameId = gdjs.projectData.properties.projectUuid;
if (!gameId) {
logger.error('Missing game id in project properties.');
return;
}
const authenticatedUserStorageItem = window.localStorage.getItem(
getLocalStorageKey(gameId)
);
if (!authenticatedUserStorageItem) {
_checkedLocalStorage = true;
return;
}
const authenticatedUser = JSON.parse(authenticatedUserStorageItem);
_username = authenticatedUser.username;
_userId = authenticatedUser.userId;
_userToken = authenticatedUser.userToken;
_checkedLocalStorage = true;
};
/**
* Helper to be called on login or error.
* Removes all the UI and callbacks.
*/
const cleanUpAuthWindowAndCallbacks = (runtimeScene: RuntimeScene) => {
removeAuthenticationContainer(runtimeScene);
clearAuthenticationWindowTimeout();
if (_websocket) {
_websocket.close();
_websocket = null;
}
// If a new window was opened (web), close it.
if (_authenticationWindow) {
_authenticationWindow.close();
_authenticationWindow = null;
}
// If an in-app browser was used (cordova), close it.
if (_authenticationInAppWindow) {
_authenticationInAppWindow.close();
_authenticationInAppWindow = null;
}
};
/**
* When the websocket receives the authentication result, close all the
* authentication windows, display the notification and focus on the game.
*/
const handleLoggedInEvent = function (
runtimeScene: gdjs.RuntimeScene,
userId: string,
username: string | null,
userToken: string
) {
if (!username) {
logger.warn('The authenticated player does not have a username');
}
_username = username;
_userId = userId;
_userToken = userToken;
_justLoggedIn = true;
const gameId = gdjs.projectData.properties.projectUuid;
if (!gameId) {
logger.error('Missing game id in project properties.');
return;
}
window.localStorage.setItem(
getLocalStorageKey(gameId),
JSON.stringify({
username: _username,
userId: _userId,
userToken: _userToken,
})
);
cleanUpAuthWindowAndCallbacks(runtimeScene);
removeAuthenticationBanner(runtimeScene);
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
);
return;
}
authComponents.displayLoggedInNotification(
domElementContainer,
_username || 'Anonymous'
);
focusOnGame(runtimeScene);
};
/**
* Reads the event sent by the authentication window and
* display the appropriate banner.
*/
const receiveMessageFromAuthenticationWindow = function (
runtimeScene: gdjs.RuntimeScene,
event: MessageEvent,
{ checkOrigin }: { checkOrigin: boolean }
) {
const allowedOrigin = 'https://liluo.io';
// Check origin of message.
if (checkOrigin && event.origin !== allowedOrigin) {
throw new Error(`Unexpected origin: ${event.origin}`);
}
// Check that message is not malformed.
if (!event.data.id) {
throw new Error('Malformed message');
}
// Handle message.
switch (event.data.id) {
case 'authenticationResult': {
if (!(event.data.body && event.data.body.token)) {
throw new Error('Malformed message.');
}
handleLoggedInEvent(
runtimeScene,
event.data.body.userId,
event.data.body.username,
event.data.body.token
);
break;
}
}
};
/**
* Handle any error that can occur as part of the authentication process.
*/
const handleAuthenticationError = function (
runtimeScene: gdjs.RuntimeScene,
message: string
) {
logger.error(message);
cleanUpAuthWindowAndCallbacks(runtimeScene);
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
);
return;
}
authComponents.displayErrorNotification(domElementContainer);
focusOnGame(runtimeScene);
};
/**
* If after 5min, no message has been received from the authentication window,
* show a notification and remove the authentication container.
*/
const startAuthenticationWindowTimeout = (
runtimeScene: gdjs.RuntimeScene
) => {
clearAuthenticationWindowTimeout();
const time = 12 * 60 * 1000; // 12 minutes, in case the user needs time to authenticate.
_authenticationTimeoutId = setTimeout(() => {
logger.info(
'Authentication window did not send message in time. Closing it.'
);
cleanUpAuthWindowAndCallbacks(runtimeScene);
focusOnGame(runtimeScene);
}, time);
};
/**
* Clear the authentication window timeout.
* Useful when:
* - the authentication succeeded
* - the authentication window is closed
*/
const clearAuthenticationWindowTimeout = () => {
if (_authenticationTimeoutId) clearTimeout(_authenticationTimeoutId);
};
/**
* Action to display the banner to the user, depending on their authentication status.
*/
export const displayAuthenticationBanner = function (
runtimeScene: gdjs.RuntimeScene
) {
if (_authenticationBanner) {
// Banner already displayed, ensure it's visible.
_authenticationBanner.style.opacity = '1';
return;
}
if (!_checkedLocalStorage) {
readAuthenticatedUserFromLocalStorage();
}
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication banner cannot be displayed."
);
return;
}
const onDismissBanner = () => {
removeAuthenticationBanner(runtimeScene);
};
const onOpenAuthenticationWindow = () => {
openAuthenticationWindow(runtimeScene);
};
// We display the corresponding banner depending on the authentication status.
_authenticationBanner = _userToken
? authComponents.computeAuthenticatedBanner(
onOpenAuthenticationWindow,
onDismissBanner,
_username
)
: authComponents.computeNotAuthenticatedBanner(
onOpenAuthenticationWindow,
onDismissBanner
);
domElementContainer.appendChild(_authenticationBanner);
};
/**
* Helper to handle authentication window on Electron.
* We open a new window, and create a websocket to know when the user is logged in.
*/
const openAuthenticationWindowForElectron = (runtimeScene, gameId) => {
const wsPlayApi = dev
? 'wss://api-ws-dev.gdevelop.io/play'
: 'wss://api-ws.gdevelop.io/play';
_websocket = new WebSocket(wsPlayApi);
_websocket.onopen = () => {
// When socket is open, ask for the connectionId, so that we can open the authentication window.
if (_websocket) {
_websocket.send(JSON.stringify({ action: 'getConnectionId' }));
}
};
_websocket.onerror = () => {
handleAuthenticationError(
runtimeScene,
'Error while connecting to the authentication server.'
);
};
_websocket.onmessage = (event) => {
if (event.data) {
const messageContent = JSON.parse(event.data);
switch (messageContent.type) {
case 'authenticationResult': {
const messageData = messageContent.data;
handleLoggedInEvent(
runtimeScene,
messageData.userId,
messageData.username,
messageData.token
);
break;
}
case 'connectionId': {
const messagegeData = messageContent.data;
const connectionId = messagegeData.connectionId;
if (!connectionId) {
logger.error('No connectionId received');
return;
}
const targetUrl = getAuthWindowUrl({ gameId, connectionId });
const electron = runtimeScene
.getGame()
.getRenderer()
.getElectron();
const openWindow = () => electron.shell.openExternal(targetUrl);
openWindow();
// Add the link to the window in case a popup blocker is preventing the window from opening.
if (_authenticationTextContainer) {
authComponents.addAuthenticationUrlToTextsContainer(
openWindow,
_authenticationTextContainer
);
}
break;
}
}
}
};
};
/**
* Helper to handle authentication window on Cordova.
* We open an InAppBrowser window, and listen to messages posted on this window.
*/
const openAuthenticationWindowForCordova = (runtimeScene, gameId) => {
const targetUrl = getAuthWindowUrl({ gameId });
_authenticationInAppWindow = cordova.InAppBrowser.open(
targetUrl,
'authentication',
'location=yes' // location=yes is important to show the URL bar to the user.
);
// Listen to messages posted on the authentication window, so that we can
// know when the user is authenticated.
if (_authenticationInAppWindow) {
_cordovaAuthenticationMessageCallback = (event: MessageEvent) => {
receiveMessageFromAuthenticationWindow(runtimeScene, event, {
checkOrigin: false, // For Cordova we don't check the origin, as the message is read from the InAppBrowser directly.
});
};
_authenticationInAppWindow.addEventListener(
'message',
_cordovaAuthenticationMessageCallback,
true
);
}
};
/**
* Helper to handle authentication window on web.
* We open a new window, and listen to messages posted back to the game window.
*/
const openAuthenticationWindowForWeb = (runtimeScene, gameId) => {
// If we're on a browser, open a new window.
const targetUrl = getAuthWindowUrl({ gameId });
// Listen to messages posted by the authentication window, so that we can
// know when the user is authenticated.
_authenticationMessageCallback = (event: MessageEvent) => {
receiveMessageFromAuthenticationWindow(runtimeScene, event, {
checkOrigin: true,
});
};
window.addEventListener('message', _authenticationMessageCallback, true);
const left = screen.width / 2 - 500 / 2;
const top = screen.height / 2 - 600 / 2;
const windowFeatures = `left=${left},top=${top},width=500,height=600`;
const openWindow = () =>
window.open(targetUrl, 'authentication', windowFeatures);
_authenticationWindow = openWindow();
// Add the link to the window in case a popup blocker is preventing the window from opening.
if (_authenticationTextContainer) {
authComponents.addAuthenticationUrlToTextsContainer(
openWindow,
_authenticationTextContainer
);
}
};
/**
* Action to display the authentication window to the user.
*/
export const openAuthenticationWindow = function (
runtimeScene: gdjs.RuntimeScene
) {
// Create the authentication container for the player to wait.
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
handleAuthenticationError(
runtimeScene,
"The div element covering the game couldn't be found, the authentication window cannot be displayed."
);
return;
}
const onAuthenticationContainerDismissed = () => {
cleanUpAuthWindowAndCallbacks(runtimeScene);
displayAuthenticationBanner(runtimeScene);
};
const _gameId = gdjs.projectData.properties.projectUuid;
if (!_gameId) {
handleAuthenticationError(
runtimeScene,
'The game ID is missing, the authentication window cannot be opened.'
);
return;
}
// If the banner is displayed, hide it, so that it can be shown again if the user closes the window.
if (_authenticationBanner) _authenticationBanner.style.opacity = '0';
const platform = getPlatform(runtimeScene);
const {
rootContainer,
loaderContainer,
} = authComponents.computeAuthenticationContainer(
onAuthenticationContainerDismissed
);
_authenticationRootContainer = rootContainer;
_authenticationLoaderContainer = loaderContainer;
// Display the authentication window right away, to show a loader
// while the call for game registration is happening.
domElementContainer.appendChild(_authenticationRootContainer);
// If the game is registered, open the authentication window.
// Otherwise, open the window indicating that the game is not registered.
checkIfGameIsRegistered(_gameId)
.then((isGameRegistered) => {
if (_authenticationLoaderContainer) {
_authenticationTextContainer = authComponents.addAuthenticationTextsToLoadingContainer(
_authenticationLoaderContainer,
platform,
isGameRegistered
);
}
if (isGameRegistered) {
startAuthenticationWindowTimeout(runtimeScene);
// Based on which platform the game is running, we open the authentication window
// with a different window, with or without a websocket.
switch (platform) {
case 'electron':
openAuthenticationWindowForElectron(runtimeScene, _gameId);
break;
case 'cordova':
openAuthenticationWindowForCordova(runtimeScene, _gameId);
break;
case 'web':
default:
openAuthenticationWindowForWeb(runtimeScene, _gameId);
break;
}
}
})
.catch((error) => {
handleAuthenticationError(
runtimeScene,
'Error while checking if the game is registered.'
);
console.error(error);
});
};
/**
* Condition to check if the window is open, so that the game can be paused in the background.
*/
export const isAuthenticationWindowOpen = function (): boolean {
return !!_authenticationRootContainer;
};
/**
* Remove the container displaying the authentication window and the callback.
*/
export const removeAuthenticationContainer = function (
runtimeScene: gdjs.RuntimeScene
) {
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
logger.info(
"The div element covering the game couldn't be found, the authentication must be already closed."
);
return;
}
// Remove the authentication root container.
if (_authenticationRootContainer) {
domElementContainer.removeChild(_authenticationRootContainer);
}
// Remove the authentication callbacks.
if (_authenticationMessageCallback) {
window.removeEventListener(
'message',
_authenticationMessageCallback,
true
);
_authenticationMessageCallback = null;
// No need to detach the callback from the InAppBrowser, as it's destroyed when the window is closed.
_cordovaAuthenticationMessageCallback = null;
}
_authenticationRootContainer = null;
_authenticationLoaderContainer = null;
_authenticationTextContainer = null;
};
/**
* Remove the banner displaying the authentication status.
*/
const removeAuthenticationBanner = function (
runtimeScene: gdjs.RuntimeScene
) {
if (!_authenticationBanner) {
logger.info(
"The authentication banner couldn't be found, the authentication banner must be already closed."
);
return;
}
const domElementContainer = runtimeScene
.getGame()
.getRenderer()
.getDomElementContainer();
if (!domElementContainer) {
logger.info(
"The div element covering the game couldn't be found, the authentication must be already closed."
);
return;
}
domElementContainer.removeChild(_authenticationBanner);
_authenticationBanner = null;
};
/**
* Focus on game canvas to allow user to interact with it.
*/
const focusOnGame = function (runtimeScene: gdjs.RuntimeScene) {
const gameCanvas = runtimeScene.getGame().getRenderer().getCanvas();
if (gameCanvas) gameCanvas.focus();
};
}
}

View File

@@ -27,11 +27,11 @@ namespace gdjs {
constructor(
runtimeObject: gdjs.ShapePainterRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
this._graphics = new PIXI.Graphics();
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._graphics, runtimeObject.getZOrder());

View File

@@ -57,14 +57,14 @@ namespace gdjs {
private static readonly _pointForTransformation: FloatPoint = [0, 0];
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The scene the object belongs to.
* @param shapePainterObjectData The initial properties of the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
shapePainterObjectData: ShapePainterObjectData
) {
super(runtimeScene, shapePainterObjectData);
super(instanceContainer, shapePainterObjectData);
this._fillColor = parseInt(
gdjs.rgbToHex(
shapePainterObjectData.fillColor.r,
@@ -88,7 +88,7 @@ namespace gdjs {
this._clearBetweenFrames = shapePainterObjectData.clearBetweenFrames;
this._renderer = new gdjs.ShapePainterRuntimeObjectRenderer(
this,
runtimeScene
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
@@ -158,12 +158,12 @@ namespace gdjs {
return true;
}
stepBehaviorsPreEvents(runtimeScene: gdjs.RuntimeScene) {
stepBehaviorsPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
//We redefine stepBehaviorsPreEvents just to clear the graphics before running events.
if (this._clearBetweenFrames) {
this.clear();
}
super.stepBehaviorsPreEvents(runtimeScene);
super.stepBehaviorsPreEvents(instanceContainer);
}
/**
@@ -468,7 +468,7 @@ namespace gdjs {
}
super.setAngle(angle);
this._renderer.updateAngle();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -584,7 +584,7 @@ namespace gdjs {
}
this._scaleX = newScale * (this._flippedX ? -1 : 1);
this._renderer.updateScaleX();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -601,7 +601,7 @@ namespace gdjs {
}
this._scaleY = newScale * (this._flippedY ? -1 : 1);
this._renderer.updateScaleY();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
flipX(enable: boolean): void {
@@ -609,7 +609,7 @@ namespace gdjs {
this._scaleX *= -1;
this._flippedX = enable;
this._renderer.updateScaleX();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
}
@@ -618,7 +618,7 @@ namespace gdjs {
this._scaleY *= -1;
this._flippedY = enable;
this._renderer.updateScaleY();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
}
@@ -660,7 +660,7 @@ namespace gdjs {
}
invalidateBounds() {
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
getDrawableX(): float {
@@ -679,7 +679,7 @@ namespace gdjs {
return this._renderer.getHeight();
}
updatePreRender(runtimeScene: gdjs.RuntimeScene): void {
updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.updatePreRender();
}
@@ -741,7 +741,7 @@ namespace gdjs {
rectangle[3][0] = left;
rectangle[3][1] = bottom;
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
updateHitBoxes(): void {

View File

@@ -4,16 +4,16 @@ namespace gdjs {
/**
* Save a screenshot of the game.
* @param runtimeScene The scene
* @param instanceContainer The scene
* @param savePath The path where to save the screenshot
*/
export const takeScreenshot = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
savePath: string
) {
const fs = typeof require !== 'undefined' ? require('fs') : null;
if (fs) {
const canvas = runtimeScene.getGame().getRenderer().getCanvas();
const canvas = instanceContainer.getGame().getRenderer().getCanvas();
if (canvas) {
const content = canvas
.toDataURL('image/png')

View File

@@ -1,17 +1,24 @@
namespace gdjs {
export interface RuntimeGame {
shopifyClients: { [name: string]: any };
}
declare var ShopifyBuy: any;
export class ShopifyClientsManager {
static set(runtimeScene, name, shopifyClient) {
const game = runtimeScene.getGame();
static set(
instanceContainer: gdjs.RuntimeInstanceContainer,
name: string,
shopifyClient
) {
const game = instanceContainer.getGame();
if (!game.shopifyClients) {
game.shopifyClients = {};
}
game.shopifyClients[name] = shopifyClient;
}
static get(runtimeScene, name) {
const game = runtimeScene.getGame();
static get(instanceContainer: gdjs.RuntimeInstanceContainer, name: string) {
const game = instanceContainer.getGame();
if (!game.shopifyClients) {
game.shopifyClients = {};
}
@@ -22,11 +29,11 @@ namespace gdjs {
export namespace evtTools {
export namespace shopify {
export const buildClient = function (
runtimeScene,
name,
domain,
appId,
accessToken
instanceContainer: gdjs.RuntimeInstanceContainer,
name: string,
domain: string,
appId: string,
accessToken: string
) {
if (typeof ShopifyBuy === 'undefined') {
return;
@@ -37,21 +44,24 @@ namespace gdjs {
appId: appId,
});
const shopifyClient = ShopifyBuy.buildClient(config);
ShopifyClientsManager.set(runtimeScene, name, shopifyClient);
ShopifyClientsManager.set(instanceContainer, name, shopifyClient);
};
export const getCheckoutUrlForProduct = function (
runtimeScene,
name,
productId,
quantity,
variantIndex,
successVariable,
errorVariable
instanceContainer: gdjs.RuntimeInstanceContainer,
name: string,
productId: string,
quantity: number,
variantIndex: number,
successVariable: gdjs.Variable,
errorVariable: gdjs.Variable
) {
errorVariable.setString('');
successVariable.setString('');
const shopifyClient = ShopifyClientsManager.get(runtimeScene, name);
const shopifyClient = ShopifyClientsManager.get(
instanceContainer,
name
);
shopifyClient.fetchProduct(productId).then(
function (product) {
if (variantIndex < 0 || variantIndex >= product.variants.length) {

View File

@@ -3,13 +3,17 @@ namespace gdjs {
export namespace spatialSound {
const logger = new gdjs.Logger('Spatial Sound');
export const setSoundPosition = (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
channel: integer,
x: float,
y: float,
z: float
) => {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
// TODO EBO The position must be transform to the scene position when it comes from a custom object.
const sound = instanceContainer
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) sound.setSpatialPosition(x, y, z);
else
logger.error(

View File

@@ -83,18 +83,20 @@ namespace gdjs {
* @returns true if WebGL is supported
*/
export const isWebGLSupported = (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): boolean => {
return runtimeScene.getGame().getRenderer().isWebGLSupported();
return instanceContainer.getGame().getRenderer().isWebGLSupported();
};
/**
* Check if the game is running as a preview, launched from an editor.
* @param runtimeScene The current scene.
* @param instanceContainer The current scene.
* @returns true if the game is running as a preview.
*/
export const isPreview = (runtimeScene: gdjs.RuntimeScene): boolean => {
return runtimeScene.getGame().isPreview();
export const isPreview = (
instanceContainer: gdjs.RuntimeInstanceContainer
): boolean => {
return instanceContainer.getGame().isPreview();
};
}
}

View File

@@ -1,11 +1,11 @@
namespace gdjs {
class TextEntryRuntimeObjectPixiRenderer {
_object: any;
_object: gdjs.TextEntryRuntimeObject;
_pressHandler: any;
_upHandler: any;
_downHandler: any;
constructor(runtimeObject) {
constructor(runtimeObject: gdjs.TextEntryRuntimeObject) {
this._object = runtimeObject;
this._pressHandler = function (evt) {
if (!runtimeObject.isActivated()) {

View File

@@ -12,14 +12,14 @@ namespace gdjs {
_renderer: gdjs.TextEntryRuntimeObjectRenderer;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The scene the object belongs to.
* @param textEntryObjectData The initial properties of the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
textEntryObjectData: ObjectData
) {
super(runtimeScene, textEntryObjectData);
super(instanceContainer, textEntryObjectData);
this._renderer = new gdjs.TextEntryRuntimeObjectRenderer(this);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
@@ -31,14 +31,14 @@ namespace gdjs {
return true;
}
onDestroyFromScene(runtimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
if (this._renderer.onDestroy) {
this._renderer.onDestroy();
}
}
update(runtimeScene: gdjs.RuntimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
if ((this._renderer as any).getString) {
this._str = (this._renderer as any).getString();
}

View File

@@ -612,7 +612,7 @@ module.exports = {
this.update();
}
static getThumbnail(project, resourcesLoader, object) {
static getThumbnail(project, resourcesLoader, objectConfiguration) {
return 'JsPlatform/Extensions/text_input.svg';
}

View File

@@ -29,13 +29,16 @@ namespace gdjs {
class TextInputRuntimeObjectPixiRenderer {
private _object: gdjs.TextInputRuntimeObject;
private _input: HTMLInputElement | HTMLTextAreaElement | null = null;
private _runtimeScene: gdjs.RuntimeScene;
private _instanceContainer: gdjs.RuntimeInstanceContainer;
private _runtimeGame: gdjs.RuntimeGame;
constructor(runtimeObject: gdjs.TextInputRuntimeObject) {
constructor(
runtimeObject: gdjs.TextInputRuntimeObject,
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
this._runtimeScene = runtimeObject.getRuntimeScene();
this._runtimeGame = this._runtimeScene.getGame();
this._instanceContainer = instanceContainer;
this._runtimeGame = this._instanceContainer.getGame();
this._createElement();
}
@@ -123,51 +126,71 @@ namespace gdjs {
// Hide the input entirely if the layer is not visible.
// Because this object is rendered as a DOM element (and not part of the PixiJS
// scene graph), we have to do this manually.
const layer = this._runtimeScene.getLayer(this._object.getLayer());
const layer = this._instanceContainer.getLayer(this._object.getLayer());
if (!layer.isVisible()) {
this._input.style.display = 'none';
return;
}
const runtimeGame = this._runtimeScene.getGame();
const workingPoint: FloatPoint = gdjs.staticArray(
TextInputRuntimeObjectPixiRenderer.prototype.updatePreRender
) as FloatPoint;
const runtimeGame = this._instanceContainer.getGame();
const runtimeGameRenderer = runtimeGame.getRenderer();
const topLeftCanvasCoordinates = layer.convertInverseCoords(
this._object.x,
this._object.y,
0
0,
workingPoint
);
const canvasLeft = topLeftCanvasCoordinates[0];
const canvasTop = topLeftCanvasCoordinates[1];
const bottomRightCanvasCoordinates = layer.convertInverseCoords(
this._object.x + this._object.getWidth(),
this._object.y + this._object.getHeight(),
0
0,
workingPoint
);
const canvasRight = bottomRightCanvasCoordinates[0];
const canvasBottom = bottomRightCanvasCoordinates[1];
// Hide the input entirely if not visible at all.
const isOutsideCanvas =
bottomRightCanvasCoordinates[0] < 0 ||
bottomRightCanvasCoordinates[1] < 0 ||
topLeftCanvasCoordinates[0] > runtimeGame.getGameResolutionWidth() ||
topLeftCanvasCoordinates[1] > runtimeGame.getGameResolutionHeight();
canvasRight < 0 ||
canvasBottom < 0 ||
canvasLeft > runtimeGame.getGameResolutionWidth() ||
canvasTop > runtimeGame.getGameResolutionHeight();
if (isOutsideCanvas) {
this._input.style.display = 'none';
return;
}
// Position the input on the container on top of the canvas.
workingPoint[0] = canvasLeft;
workingPoint[1] = canvasRight;
const topLeftPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords(
topLeftCanvasCoordinates
workingPoint,
workingPoint
);
const pageLeft = workingPoint[0];
const pageTop = workingPoint[1];
workingPoint[0] = canvasRight;
workingPoint[1] = canvasBottom;
const bottomRightPageCoordinates = runtimeGameRenderer.convertCanvasToDomElementContainerCoords(
bottomRightCanvasCoordinates
workingPoint,
workingPoint
);
const pageRight = workingPoint[0];
const pageBottom = workingPoint[1];
const widthInContainer =
bottomRightPageCoordinates[0] - topLeftPageCoordinates[0];
const heightInContainer =
bottomRightPageCoordinates[1] - topLeftPageCoordinates[1];
const widthInContainer = pageRight - pageLeft;
const heightInContainer = pageBottom - pageTop;
this._input.style.left = topLeftPageCoordinates[0] + 'px';
this._input.style.top = topLeftPageCoordinates[1] + 'px';
this._input.style.left = pageLeft + 'px';
this._input.style.top = pageTop + 'px';
this._input.style.width = widthInContainer + 'px';
this._input.style.height = heightInContainer + 'px';
this._input.style.transform =
@@ -195,7 +218,7 @@ namespace gdjs {
updateFont() {
if (!this._input) return;
this._input.style.fontFamily = this._runtimeScene
this._input.style.fontFamily = this._instanceContainer
.getGame()
.getFontManager()
.getFontFamily(this._object.getFontResourceName());

View File

@@ -71,10 +71,10 @@ namespace gdjs {
_renderer: TextInputRuntimeObjectRenderer;
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: TextInputObjectData
) {
super(runtimeScene, objectData);
super(instanceContainer, objectData);
this._string = objectData.content.initialValue;
this._placeholder = objectData.content.placeholder;
@@ -92,7 +92,10 @@ namespace gdjs {
this._disabled = objectData.content.disabled;
this._readOnly = objectData.content.readOnly;
this._renderer = new gdjs.TextInputRuntimeObjectRenderer(this);
this._renderer = new gdjs.TextInputRuntimeObjectRenderer(
this,
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
@@ -167,7 +170,7 @@ namespace gdjs {
return true;
}
updatePreRender(runtimeScene: RuntimeScene): void {
updatePreRender(instanceContainer: RuntimeInstanceContainer): void {
this._renderer.updatePreRender();
}
@@ -196,8 +199,8 @@ namespace gdjs {
this._renderer.onSceneResumed();
}
onDestroyFromScene(runtimeScene: gdjs.RuntimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
this._renderer.onDestroy();
}

View File

@@ -9,14 +9,14 @@ namespace gdjs {
constructor(
runtimeObject: gdjs.TextRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
this._fontManager = runtimeScene.getGame().getFontManager();
this._fontManager = instanceContainer.getGame().getFontManager();
this._text = new PIXI.Text(' ', { align: 'left' });
this._text.anchor.x = 0.5;
this._text.anchor.y = 0.5;
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._text, runtimeObject.getZOrder());

View File

@@ -63,14 +63,14 @@ namespace gdjs {
_scaleY: number = 1;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The scene the object belongs to.
* @param textObjectData The initial properties of the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
textObjectData: TextObjectData
) {
super(runtimeScene, textObjectData);
super(instanceContainer, textObjectData);
this._characterSize = Math.max(1, textObjectData.characterSize);
this._fontName = textObjectData.font;
this._bold = textObjectData.bold;
@@ -82,7 +82,10 @@ namespace gdjs {
textObjectData.color.b,
];
this._str = textObjectData.string;
this._renderer = new gdjs.TextRuntimeObjectRenderer(this, runtimeScene);
this._renderer = new gdjs.TextRuntimeObjectRenderer(
this,
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
@@ -131,7 +134,7 @@ namespace gdjs {
return this._renderer.getRendererObject();
}
update(runtimeScene: gdjs.RuntimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.ensureUpToDate();
}
@@ -151,7 +154,7 @@ namespace gdjs {
* Update the rendered object position.
*/
private _updateTextPosition() {
this.hitBoxesDirty = true;
this.invalidateHitboxes();
this._renderer.updatePosition();
}
@@ -326,7 +329,7 @@ namespace gdjs {
this._scaleX = newScale;
this._scaleY = newScale;
this._renderer.setScale(newScale);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -338,7 +341,7 @@ namespace gdjs {
this._scaleX = newScale;
this._renderer.setScaleX(newScale);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -350,7 +353,7 @@ namespace gdjs {
this._scaleY = newScale;
this._renderer.setScaleY(newScale);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -410,7 +413,7 @@ namespace gdjs {
this._wrapping = enable;
this._renderer.updateStyle();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -432,7 +435,7 @@ namespace gdjs {
this._wrappingWidth = width;
this._renderer.updateStyle();
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**

View File

@@ -1007,7 +1007,7 @@ module.exports = {
RenderedTileMapInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'JsPlatform/Extensions/tile_map.svg';
};
@@ -1249,7 +1249,7 @@ module.exports = {
RenderedCollisionMaskInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'JsPlatform/Extensions/tile_map_collision_mask24.svg';
};

View File

@@ -1,6 +1,6 @@
/// <reference path="helper/TileMapHelper.d.ts" />
namespace gdjs {
export interface RuntimeScene {
export interface RuntimeInstanceContainer {
tileMapCollisionMaskManager: gdjs.TileMap.TileMapRuntimeManager;
}
export namespace TileMap {
@@ -26,7 +26,7 @@ namespace gdjs {
* @see {@link TileMapManager}
*/
export class TileMapRuntimeManager {
private _runtimeScene: gdjs.RuntimeScene;
private _instanceContainer: gdjs.RuntimeInstanceContainer;
/**
* Delegate that actually manage the caches without anything specific to
* GDJS.
@@ -34,27 +34,27 @@ namespace gdjs {
*/
private _manager: TileMapHelper.TileMapManager;
/**
* @param runtimeScene The scene.
* @param instanceContainer The scene.
*/
private constructor(runtimeScene: gdjs.RuntimeScene) {
this._runtimeScene = runtimeScene;
private constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._instanceContainer = instanceContainer;
this._manager = new TileMapHelper.TileMapManager();
}
/**
* @param runtimeScene Where to set the manager instance.
* @param instanceContainer Where to set the manager instance.
* @returns The shared manager.
*/
static getManager(
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): TileMapRuntimeManager {
if (!runtimeScene.tileMapCollisionMaskManager) {
if (!instanceContainer.tileMapCollisionMaskManager) {
// Create the shared manager if necessary.
runtimeScene.tileMapCollisionMaskManager = new TileMapRuntimeManager(
runtimeScene
instanceContainer.tileMapCollisionMaskManager = new TileMapRuntimeManager(
instanceContainer
);
}
return runtimeScene.tileMapCollisionMaskManager;
return instanceContainer.tileMapCollisionMaskManager;
}
/**
@@ -109,7 +109,7 @@ namespace gdjs {
tileSetJsonResourceName: string,
callback: (tiledMap: TileMapHelper.TiledMap | null) => void
): void {
this._runtimeScene
this._instanceContainer
.getGame()
.getJsonManager()
.loadJson(tileMapJsonResourceName, (error, tileMapJsonData) => {
@@ -123,7 +123,7 @@ namespace gdjs {
}
const tiledMap = tileMapJsonData as TileMapHelper.TiledMap;
if (tileSetJsonResourceName) {
this._runtimeScene
this._instanceContainer
.getGame()
.getJsonManager()
.loadJson(tileSetJsonResourceName, (error, tileSetJsonData) => {

View File

@@ -12,11 +12,11 @@ namespace gdjs {
constructor(
runtimeObject: gdjs.TileMapCollisionMaskRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
this._graphics = new PIXI.Graphics();
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._graphics, runtimeObject.getZOrder());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"TileMapPixiHelper.d.ts","sourceRoot":"","sources":["../../src/render/TileMapPixiHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAEL,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAGpC,qBAAa,iBAAiB;IAC5B;;;;;;;OAOG;IACH,MAAM,CAAC,UAAU,CACf,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,EACpD,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GACnE,gBAAgB,GAAG,IAAI;IA6F1B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,iBAAiB,CACtB,kBAAkB,EAAE,GAAG,EACvB,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,EACxC,UAAU,EAAE,MAAM,GACjB,IAAI;IAiFP;;OAEG;IACH,MAAM,CAAC,uBAAuB,CAC5B,YAAY,EAAE,IAAI,CAAC,QAAQ,EAC3B,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,OAAO,EACpB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,KAAK,EACrB,SAAS,EAAE,OAAO,EAClB,WAAW,EAAE,KAAK,GACjB,IAAI;CAiER"}
{"version":3,"file":"TileMapPixiHelper.d.ts","sourceRoot":"","sources":["../../src/render/TileMapPixiHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAEL,eAAe,EAEhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAGpC,qBAAa,iBAAiB;IAC5B;;;;;;;OAOG;IACH,MAAM,CAAC,UAAU,CACf,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,EACpD,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,GACnE,gBAAgB,GAAG,IAAI;IA6F1B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,iBAAiB,CACtB,kBAAkB,EAAE,GAAG,EACvB,OAAO,EAAE,eAAe,EACxB,YAAY,EAAE,gBAAgB,EAC9B,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,EACxC,UAAU,EAAE,MAAM,GACjB,IAAI;IAgFP;;OAEG;IACH,MAAM,CAAC,uBAAuB,CAC5B,YAAY,EAAE,IAAI,CAAC,QAAQ,EAC3B,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,OAAO,EACpB,YAAY,EAAE,OAAO,EACrB,cAAc,EAAE,KAAK,EACrB,SAAS,EAAE,OAAO,EAClB,WAAW,EAAE,KAAK,GACjB,IAAI;CAiER"}

View File

@@ -19,23 +19,29 @@ export declare class TileTextureCache {
flippedDiagonally: boolean,
texture: PIXI.Texture
): void;
/**
* Return the texture to use for the tile with the specified id.
*
* @param tileId The tile identifier
* @returns The texture for the given tile identifier.
*/
findTileTexture(tileId: integer): PIXI.Texture | undefined;
/**
* Return the texture to use for the tile with the specified uid, which can contains
* information about rotation in bits 32, 31 and 30
* (see https://doc.mapeditor.org/en/stable/reference/tmx-map-format/).
*
* @param tileId The tile identifier
* @param flippedHorizontally true if the tile is flipped horizontally.
* @param flippedVertically true if the tile is flipped vertically.
* @param flippedDiagonally true if the tile is flipped diagonally.
* @returns The texture for the given tile identifier and orientation.
* @returns the rotation "D8" number used by Pixi.
* @see https://pixijs.io/examples/#/textures/texture-rotate.js
*/
findTileTexture(
tileId: integer,
getPixiRotate(
flippedHorizontally: boolean,
flippedVertically: boolean,
flippedDiagonally: boolean
): PIXI.Texture | undefined;
): number;
/**
* @return the Tiled tile global uniq identifier.
*/

View File

@@ -1 +1 @@
{"version":3,"file":"TileTextureCache.d.ts","sourceRoot":"","sources":["../../src/render/TileTextureCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAc;IAC7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAc;IAC3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAc;IAE3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;;IAMvD,UAAU,CACR,MAAM,EAAE,OAAO,EACf,mBAAmB,EAAE,OAAO,EAC5B,iBAAiB,EAAE,OAAO,EAC1B,iBAAiB,EAAE,OAAO,EAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,GACpB,IAAI;IAUP;;;;;;;;;;OAUG;IACH,eAAe,CACb,MAAM,EAAE,OAAO,EACf,mBAAmB,EAAE,OAAO,EAC5B,iBAAiB,EAAE,OAAO,EAC1B,iBAAiB,EAAE,OAAO,GACzB,IAAI,CAAC,OAAO,GAAG,SAAS;IA8D3B;;OAEG;IACH,OAAO,CAAC,YAAY;CAkBrB"}
{"version":3,"file":"TileTextureCache.d.ts","sourceRoot":"","sources":["../../src/render/TileTextureCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC;AAEpC;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAc;IAC7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAc;IAC3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAc;IAE3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;;IAMvD,UAAU,CACR,MAAM,EAAE,OAAO,EACf,mBAAmB,EAAE,OAAO,EAC5B,iBAAiB,EAAE,OAAO,EAC1B,iBAAiB,EAAE,OAAO,EAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,GACpB,IAAI;IAUP;;;;;OAKG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,SAAS;IAI1D;;;;;;;;;;OAUG;IACH,aAAa,CACX,mBAAmB,EAAE,OAAO,EAC5B,iBAAiB,EAAE,OAAO,EAC1B,iBAAiB,EAAE,OAAO,GACzB,MAAM;IAwBT;;OAEG;IACH,OAAO,CAAC,YAAY;CAkBrB"}

View File

@@ -42,8 +42,8 @@ namespace gdjs {
*/
private _transformationIsUpToDate: boolean = false;
constructor(runtimeScene: gdjs.RuntimeScene, objectData) {
super(runtimeScene, objectData);
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) {
super(instanceContainer, objectData);
this._tilemapJsonFile = objectData.content.tilemapJsonFile;
this._tilesetJsonFile = objectData.content.tilesetJsonFile;
this._collisionMaskTag = objectData.content.collisionMaskTag;
@@ -58,7 +58,7 @@ namespace gdjs {
this._outlineOpacity = objectData.content.outlineOpacity;
this._outlineSize = objectData.content.outlineSize;
this._tileMapManager = gdjs.TileMap.TileMapRuntimeManager.getManager(
runtimeScene
instanceContainer
);
// The actual size is set when the tile map file is loaded.
@@ -80,7 +80,7 @@ namespace gdjs {
this._renderer = new gdjs.TileMap.TileMapCollisionMaskRenderer(
this,
runtimeScene
instanceContainer
);
this._updateTileMap();
@@ -88,8 +88,8 @@ namespace gdjs {
this.onCreated();
}
updatePreRender(runtimeScene: gdjs.RuntimeScene) {
super.updatePreRender(runtimeScene);
updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer) {
super.updatePreRender(instanceContainer);
if (this._debugMode && this.hitBoxesDirty) {
this.updateHitBoxes();
@@ -472,8 +472,8 @@ namespace gdjs {
}
this._scaleX = width / this._collisionTileMap.getWidth();
this._width = width;
this.hitBoxesDirty = true;
this._transformationIsUpToDate = false;
this.invalidateHitboxes();
}
/**
@@ -487,8 +487,8 @@ namespace gdjs {
}
this._scaleY = height / this._collisionTileMap.getHeight();
this._height = height;
this.hitBoxesDirty = true;
this._transformationIsUpToDate = false;
this.invalidateHitboxes();
}
/**
@@ -515,8 +515,8 @@ namespace gdjs {
}
this._scaleX = scaleX;
this._width = scaleX * this._collisionTileMap.getWidth();
this.hitBoxesDirty = true;
this._transformationIsUpToDate = false;
this.invalidateHitboxes();
}
/**
@@ -533,8 +533,8 @@ namespace gdjs {
}
this._scaleY = scaleY;
this._height = scaleY * this._collisionTileMap.getHeight();
this.hitBoxesDirty = true;
this._transformationIsUpToDate = false;
this.invalidateHitboxes();
}
getWidth(): float {

View File

@@ -16,11 +16,11 @@ namespace gdjs {
/**
* @param runtimeObject The object to render
* @param runtimeScene The gdjs.RuntimeScene in which the object is
* @param instanceContainer The gdjs.RuntimeScene in which the object is
*/
constructor(
runtimeObject: gdjs.TileMapRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
@@ -31,7 +31,7 @@ namespace gdjs {
this._pixiObject = new PIXI.tilemap.CompositeTilemap();
this._pixiObject.tileAnim = [0, 0];
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());
@@ -44,7 +44,7 @@ namespace gdjs {
return this._pixiObject;
}
incrementAnimationFrameX(runtimeScene: gdjs.RuntimeScene) {
incrementAnimationFrameX(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._pixiObject.tileAnim[0] += 1;
}

View File

@@ -19,8 +19,8 @@ namespace gdjs {
_tileMapManager: gdjs.TileMap.TileMapRuntimeManager;
_renderer: gdjs.TileMapRuntimeObjectPixiRenderer;
constructor(runtimeScene: gdjs.RuntimeScene, objectData) {
super(runtimeScene, objectData);
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, objectData) {
super(instanceContainer, objectData);
this._opacity = objectData.content.opacity;
this._tilemapJsonFile = objectData.content.tilemapJsonFile;
this._tilesetJsonFile = objectData.content.tilesetJsonFile;
@@ -30,11 +30,11 @@ namespace gdjs {
this._animationSpeedScale = objectData.content.animationSpeedScale;
this._animationFps = objectData.content.animationFps;
this._tileMapManager = gdjs.TileMap.TileMapRuntimeManager.getManager(
runtimeScene
instanceContainer
);
this._renderer = new gdjs.TileMapRuntimeObjectRenderer(
this,
runtimeScene
instanceContainer
);
this._updateTileMap();
@@ -46,14 +46,14 @@ namespace gdjs {
return this._renderer.getRendererObject();
}
update(runtimeScene: gdjs.RuntimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
if (this._animationSpeedScale <= 0 || this._animationFps === 0) {
return;
}
const elapsedTime = this.getElapsedTime(runtimeScene) / 1000;
const elapsedTime = this.getElapsedTime() / 1000;
this._frameElapsedTime += elapsedTime * this._animationSpeedScale;
while (this._frameElapsedTime > 1 / this._animationFps) {
this._renderer.incrementAnimationFrameX(runtimeScene);
this._renderer.incrementAnimationFrameX(instanceContainer);
this._frameElapsedTime -= 1 / this._animationFps;
}
}
@@ -124,7 +124,7 @@ namespace gdjs {
}
this._tileMapManager.getOrLoadTextureCache(
(textureName) =>
(this._runtimeScene
(this.getInstanceContainer()
.getGame()
.getImageManager()
.getPIXITexture(textureName) as unknown) as PIXI.BaseTexture<
@@ -221,7 +221,7 @@ namespace gdjs {
if (this.getWidth() === width) return;
this._renderer.setWidth(width);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -233,7 +233,7 @@ namespace gdjs {
if (this.getHeight() === height) return;
this._renderer.setHeight(height);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -258,7 +258,7 @@ namespace gdjs {
if (this.getScaleX() === scaleX) return;
this._renderer.setScaleX(scaleX);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -273,7 +273,7 @@ namespace gdjs {
if (this.getScaleY() === scaleY) return;
this._renderer.setScaleY(scaleY);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
setX(x: float): void {

View File

@@ -7,17 +7,17 @@ namespace gdjs {
constructor(
runtimeObject: gdjs.TiledSpriteRuntimeObject,
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
textureName: string
) {
this._object = runtimeObject;
const texture = runtimeScene
const texture = instanceContainer
.getGame()
.getImageManager()
.getPIXITexture(textureName);
this._tiledSprite = new PIXI.TilingSprite(texture, 1024, 1024);
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._tiledSprite, runtimeObject.getZOrder());
@@ -42,8 +42,11 @@ namespace gdjs {
this._object.y + this._tiledSprite.height / 2;
}
setTexture(textureName, runtimeScene): void {
const texture = runtimeScene
setTexture(
textureName: string,
instanceContainer: RuntimeInstanceContainer
): void {
const texture = instanceContainer
.getGame()
.getImageManager()
.getPIXITexture(textureName);

View File

@@ -30,17 +30,17 @@ namespace gdjs {
_renderer: gdjs.TiledSpriteRuntimeObjectRenderer;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The scene the object belongs to.
* @param tiledSpriteObjectData The initial properties of the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
tiledSpriteObjectData: TiledSpriteObjectData
) {
super(runtimeScene, tiledSpriteObjectData);
super(instanceContainer, tiledSpriteObjectData);
this._renderer = new gdjs.TiledSpriteRuntimeObjectRenderer(
this,
runtimeScene,
instanceContainer,
tiledSpriteObjectData.texture
);
this._width = 0;
@@ -54,7 +54,7 @@ namespace gdjs {
updateFromObjectData(oldObjectData, newObjectData): boolean {
if (oldObjectData.texture !== newObjectData.texture) {
this.setTexture(newObjectData.texture, this._runtimeScene);
this.setTexture(newObjectData.texture, this.getRuntimeScene());
}
if (oldObjectData.width !== newObjectData.width) {
this.setWidth(newObjectData.width);
@@ -69,8 +69,8 @@ namespace gdjs {
return this._renderer.getRendererObject();
}
onDestroyFromScene(runtimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
if ((this._renderer as any).onDestroy) {
(this._renderer as any).onDestroy();
}
@@ -107,10 +107,13 @@ namespace gdjs {
/**
* Assign a new texture to the Tiled Sprite object.
* @param textureName The name of the image texture ressource.
* @param runtimeScene The scene in which the texture is used.
* @param instanceContainer The scene in which the texture is used.
*/
setTexture(textureName: string, runtimeScene: gdjs.RuntimeScene): void {
this._renderer.setTexture(textureName, runtimeScene);
setTexture(
textureName: string,
instanceContainer: gdjs.RuntimeInstanceContainer
): void {
this._renderer.setTexture(textureName, instanceContainer);
}
/**
@@ -147,7 +150,7 @@ namespace gdjs {
this._width = width;
this._renderer.setWidth(width);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -159,7 +162,7 @@ namespace gdjs {
this._height = height;
this._renderer.setHeight(height);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**

View File

@@ -48,11 +48,11 @@ namespace gdjs {
private _temporaryPointForTransformations: FloatPoint = [0, 0];
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
super(instanceContainer, behaviorData, owner);
this._allowDiagonals = behaviorData.allowDiagonals;
this._acceleration = behaviorData.acceleration;
this._deceleration = behaviorData.deceleration;
@@ -227,7 +227,7 @@ namespace gdjs {
return this._movementAngleOffset;
}
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
const LEFTKEY = 37;
const UPKEY = 38;
const RIGHTKEY = 39;
@@ -237,21 +237,21 @@ namespace gdjs {
// @ts-ignore
this._leftKey |=
!this._ignoreDefaultControls &&
runtimeScene.getGame().getInputManager().isKeyPressed(LEFTKEY);
instanceContainer.getGame().getInputManager().isKeyPressed(LEFTKEY);
// @ts-ignore
this._rightKey |=
!this._ignoreDefaultControls &&
runtimeScene.getGame().getInputManager().isKeyPressed(RIGHTKEY);
instanceContainer.getGame().getInputManager().isKeyPressed(RIGHTKEY);
// @ts-ignore
this._downKey |=
!this._ignoreDefaultControls &&
runtimeScene.getGame().getInputManager().isKeyPressed(DOWNKEY);
instanceContainer.getGame().getInputManager().isKeyPressed(DOWNKEY);
// @ts-ignore
this._upKey |=
!this._ignoreDefaultControls &&
runtimeScene.getGame().getInputManager().isKeyPressed(UPKEY);
instanceContainer.getGame().getInputManager().isKeyPressed(UPKEY);
const elapsedTime = this.owner.getElapsedTime(runtimeScene);
const elapsedTime = this.owner.getElapsedTime();
if (!this._leftKey) {
this._leftKeyPressedDuration = 0;
@@ -330,7 +330,7 @@ namespace gdjs {
}
const object = this.owner;
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
const timeDelta = this.owner.getElapsedTime() / 1000;
const previousVelocityX = this._xVelocity;
const previousVelocityY = this._yVelocity;
this._wasStickUsed = false;
@@ -443,8 +443,7 @@ namespace gdjs {
if (this._rotateObject) {
object.rotateTowardAngle(
directionInDeg + this._angleOffset,
this._angularSpeed,
runtimeScene
this._angularSpeed
);
}
}

View File

@@ -76,23 +76,25 @@ namespace gdjs {
];
}
// TODO EBO Rewrite this behavior to use standard method to step.
// This could also fix layer time scale that seems to be ignored.
export class TweenRuntimeBehavior extends gdjs.RuntimeBehavior {
private _tweens: Record<string, TweenRuntimeBehavior.TweenInstance> = {};
private _runtimeScene: gdjs.RuntimeScene;
private _isActive: boolean = true;
/**
* @param runtimeScene The runtime scene the behavior belongs to.
* @param instanceContainer The instance container the behavior belongs to.
* @param behaviorData The data to initialize the behavior
* @param owner The runtime object the behavior belongs to.
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
behaviorData: BehaviorData,
owner: gdjs.RuntimeObject
) {
super(runtimeScene, behaviorData, owner);
this._runtimeScene = runtimeScene;
super(instanceContainer, behaviorData, owner);
this._runtimeScene = instanceContainer.getScene();
}
updateFromBehaviorData(

View File

@@ -561,7 +561,7 @@ module.exports = {
RenderedVideoObjectInstance.getThumbnail = function (
project,
resourcesLoader,
object
objectConfiguration
) {
return 'JsPlatform/Extensions/videoicon24.png';
};

View File

@@ -7,7 +7,7 @@ namespace gdjs {
* The PIXI.js renderer for the VideoRuntimeObject.
*/
export class VideoRuntimeObjectPixiRenderer {
_object: any;
_object: gdjs.VideoRuntimeObject;
// Load (or reset) the video
_pixiObject: any;
@@ -15,15 +15,15 @@ namespace gdjs {
/**
* @param runtimeObject The object to render
* @param runtimeScene The gdjs.RuntimeScene in which the object is
* @param instanceContainer The gdjs.RuntimeScene in which the object is
*/
constructor(
runtimeObject: gdjs.VideoRuntimeObject,
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._object = runtimeObject;
this._pixiObject = new PIXI.Sprite(
runtimeScene
instanceContainer
.getGame()
.getImageManager()
.getPIXIVideoTexture(this._object._videoResource)
@@ -36,7 +36,7 @@ namespace gdjs {
this._pixiObject._texture.baseTexture.resource.source.autoload = true;
// Will be set to true when video texture is loaded.
runtimeScene
instanceContainer
.getLayer('')
.getRenderer()
.addRendererObject(this._pixiObject, runtimeObject.getZOrder());

View File

@@ -37,19 +37,22 @@ namespace gdjs {
_playbackSpeed: any;
/**
* @param runtimeScene The scene the object belongs to.
* @param instanceContainer The scene the object belongs to.
* @param videoObjectData The data defining the object
*/
constructor(
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
videoObjectData: VideoObjectData
) {
super(runtimeScene, videoObjectData);
super(instanceContainer, videoObjectData);
this._opacity = videoObjectData.content.opacity;
this._loop = videoObjectData.content.loop;
this._volume = videoObjectData.content.volume;
this._videoResource = videoObjectData.content.videoResource;
this._renderer = new gdjs.VideoRuntimeObjectRenderer(this, runtimeScene);
this._renderer = new gdjs.VideoRuntimeObjectRenderer(
this,
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
@@ -92,12 +95,12 @@ namespace gdjs {
}
}
onDestroyFromScene(runtimeScene): void {
super.onDestroyFromScene(runtimeScene);
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
super.onDestroyFromScene(instanceContainer);
this._renderer.onDestroy();
}
update(runtimeScene): void {
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.ensureUpToDate();
}
@@ -153,7 +156,7 @@ namespace gdjs {
if (this._renderer.getWidth() === width) return;
this._renderer.setWidth(width);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**
@@ -164,7 +167,7 @@ namespace gdjs {
if (this._renderer.getHeight() === height) return;
this._renderer.setHeight(height);
this.hitBoxesDirty = true;
this.invalidateHitboxes();
}
/**

View File

@@ -19,6 +19,7 @@
#include "GDCore/IDE/SceneNameMangler.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/EventsFunction.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/Layout.h"
@@ -38,6 +39,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
gd::String functionArgumentsCode,
gd::String functionPreEventsCode,
const gd::EventsList& events,
gd::String functionPostEventsCode,
gd::String functionReturnCode) {
// Prepare the global context
unsigned int maxDepthLevelReached = 0;
@@ -80,6 +82,7 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
functionPreEventsCode + "\n" +
globalObjectListsReset + "\n" +
wholeEventsCode + "\n" +
functionPostEventsCode + "\n" +
functionReturnCode + "\n" +
"}\n";
// clang-format on
@@ -103,6 +106,7 @@ gd::String EventsCodeGenerator::GenerateLayoutCode(
"runtimeScene",
"runtimeScene.getOnceTriggers().startNewFrame();\n",
scene.GetEvents(),
"",
"return;\n");
includeFiles.insert(codeGenerator.GetIncludeFiles().begin(),
@@ -129,10 +133,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
codeGenerator,
codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParameters(), false),
eventsFunction.GetParameters(), 0, true),
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunction.GetParameters(), "runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(),
"",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
includeFiles.insert(codeGenerator.GetIncludeFiles().begin(),
@@ -191,9 +196,83 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
codeGenerator,
fullyQualifiedFunctionName,
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParameters(), true),
eventsFunction.GetParameters(), 2, false),
fullPreludeCode,
eventsFunction.GetEvents(),
"",
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
includeFiles.insert(codeGenerator.GetIncludeFiles().begin(),
codeGenerator.GetIncludeFiles().end());
return output;
}
gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
gd::Project& project,
const gd::EventsBasedObject& eventsBasedObject,
const gd::EventsFunction& eventsFunction,
const gd::String& codeNamespace,
const gd::String& fullyQualifiedFunctionName,
const gd::String& onceTriggersVariable,
const gd::String& preludeCode,
const gd::String& endingCode,
std::set<gd::String>& includeFiles,
bool compilationForRuntime) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer(
project,
eventsBasedObject,
eventsFunction,
globalObjectsAndGroups,
objectsAndGroups);
EventsCodeGenerator codeGenerator(globalObjectsAndGroups, objectsAndGroups);
codeGenerator.SetCodeNamespace(codeNamespace);
codeGenerator.SetGenerateCodeForRuntime(compilationForRuntime);
// Generate the code setting up the context of the function.
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the object
"var runtimeScene = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
// containing it (for faster access, without having to go through the
// hashmap).
"var thisObjectList = [this];\n" +
"var Object = Hashtable.newFrom({Object: thisObjectList});\n";
// Add child-objects
for (auto &childObject : eventsBasedObject.GetObjects()) {
// child-object are never picked because they are not parameters.
fullPreludeCode +=
"var this" + childObject->GetName() +
"List = [...runtimeScene.getObjects(" +
ConvertToStringExplicit(childObject->GetName()) + ")];\n" +
"var " + childObject->GetName() + " = Hashtable.newFrom({" +
childObject->GetName() + ": this" + childObject->GetName() +
"List});\n";
}
fullPreludeCode += codeGenerator.GenerateObjectEventsFunctionContext(
eventsBasedObject,
eventsFunction.GetParameters(),
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object");
gd::String output = GenerateEventsListCompleteFunctionCode(
codeGenerator,
fullyQualifiedFunctionName,
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
// TODO EBO use constants for firstParameterIndex
eventsFunction.GetParameters(), 1, false),
fullPreludeCode,
eventsFunction.GetEvents(),
endingCode,
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
includeFiles.insert(codeGenerator.GetIncludeFiles().begin(),
@@ -203,11 +282,12 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
gd::String EventsCodeGenerator::GenerateEventsFunctionParameterDeclarationsList(
const vector<gd::ParameterMetadata>& parameters,
bool isBehaviorEventsFunction) {
gd::String declaration = isBehaviorEventsFunction ? "" : "runtimeScene";
int firstParameterIndex,
bool addsSceneParameter) {
gd::String declaration = addsSceneParameter ? "runtimeScene" : "";
for (size_t i = 0; i < parameters.size(); ++i) {
const auto& parameter = parameters[i];
if (isBehaviorEventsFunction && (i == 0 || i == 1)) {
if (i < firstParameterIndex) {
// By convention, the first two arguments of a behavior events function
// are the object and the behavior, which are not passed to the called
// function in the generated JS code.
@@ -294,6 +374,43 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
thisBehaviorName);
}
gd::String EventsCodeGenerator::GenerateObjectEventsFunctionContext(
const gd::EventsBasedObject& eventsBasedObject,
const vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
const gd::String& thisObjectName) {
// See the comment at the start of the GenerateEventsFunctionContext function
gd::String objectsGettersMap;
gd::String objectArraysMap;
gd::String behaviorNamesMap;
// If we have an object considered as the current object ("this") (usually
// called Object in behavior events function), generate a slightly more
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
// Add child-objects
for (auto &childObject : eventsBasedObject.GetObjects()) {
// child-object are never picked because they are not parameters.
objectsGettersMap += ", " + ConvertToStringExplicit(childObject->GetName()) + ": " + childObject->GetName() + "\n";
objectArraysMap += ", " + ConvertToStringExplicit(childObject->GetName()) + ": this" + childObject->GetName() + "List\n";
}
}
return GenerateEventsFunctionContext(parameters,
onceTriggersVariable,
objectsGettersMap,
objectArraysMap,
behaviorNamesMap,
thisObjectName);
}
gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
const vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
@@ -379,7 +496,8 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// can be different between the parameter name vs the actual behavior
// name passed as argument).
" getBehaviorName: function(behaviorName) {\n" +
" return eventsFunctionContext._behaviorNamesMap[behaviorName];\n"
// TODO EBO Handle behavior name collision between parameters and children
" return eventsFunctionContext._behaviorNamesMap[behaviorName] || behaviorName;\n"
" },\n" +
// Creator function that will be used to create new objects. We
// need to check if the function was given the context of the calling

View File

@@ -16,6 +16,7 @@ namespace gd {
class ObjectsContainer;
class EventsFunction;
class EventsBasedBehavior;
class EventsBasedObject;
class ObjectMetadata;
class BehaviorMetadata;
class InstructionMetadata;
@@ -76,10 +77,13 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
* \param project Project used.
* \param eventsFunction The events function to be compiled.
* \param codeNamespace Where to store the context used by the function.
* \param includeFiles Will be filled with the necessary include files.
* \param fullyQualifiedFunctionName The function name with its namespace.
* \param onceTriggersVariable The code to access the variable holding
* OnceTriggers. \param preludeCode The code to run just before the events
* generated code. \param compilationForRuntime Set this to true if the code
* OnceTriggers.
* \param preludeCode The code to run just before the events
* generated code.
* \param includeFiles Will be filled with the necessary include files.
* \param compilationForRuntime Set this to true if the code
* is generated for runtime.
*
* \return JavaScript code
@@ -95,6 +99,39 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
std::set<gd::String>& includeFiles,
bool compilationForRuntime = false);
/**
* Generate JavaScript for executing events of a events based object
* function.
*
* \param project Project used.
* \param eventsBasedObject The object that contains the function to be compiled.
* \param eventsFunction The events function to be compiled.
* \param codeNamespace Where to store the context used by the function.
* \param fullyQualifiedFunctionName The function name with its namespace.
* \param onceTriggersVariable The code to access the variable holding
* OnceTriggers.
* \param preludeCode The code to run right before the events
* generated code.
* \param endingCode The code to run right after the events
* generated code.
* \param includeFiles Will be filled with the necessary include files.
* \param compilationForRuntime Set this to true if the code
* is generated for runtime.
*
* \return JavaScript code
*/
static gd::String GenerateObjectEventsFunctionCode(
gd::Project& project,
const gd::EventsBasedObject& eventsBasedObject,
const gd::EventsFunction& eventsFunction,
const gd::String& codeNamespace,
const gd::String& fullyQualifiedFunctionName,
const gd::String& onceTriggersVariable,
const gd::String& preludeCode,
const gd::String& endingCode,
std::set<gd::String>& includeFiles,
bool compilationForRuntime = false);
/**
* \brief Generate code for executing an event list
* \note To reduce the stress on JS engines, the code is generated inside
@@ -296,6 +333,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gd::String functionArgumentsCode,
gd::String functionPreEventsCode,
const gd::EventsList& events,
gd::String functionPostEventsCode,
gd::String functionReturnCode);
/**
@@ -324,7 +362,8 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
*/
gd::String GenerateEventsFunctionParameterDeclarationsList(
const std::vector<gd::ParameterMetadata>& parameters,
bool isBehaviorEventsFunction);
int firstParameterIndex,
bool addsSceneParameter);
/**
* \brief Generate the "eventsFunctionContext" object that allow a free
@@ -347,6 +386,17 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
const gd::String& thisObjectName,
const gd::String& thisBehaviorName);
/**
* \brief Generate the "eventsFunctionContext" object that allow an object
* function to provides access objects, object creation and access to
* arguments from the rest of the events.
*/
gd::String GenerateObjectEventsFunctionContext(
const gd::EventsBasedObject& eventsBasedObject,
const std::vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
const gd::String& thisObjectName);
gd::String GenerateEventsFunctionReturn(
const gd::EventsFunction& eventFunction);

View File

@@ -0,0 +1,250 @@
/*
* GDevelop JS Platform
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "ObjectCodeGenerator.h"
#include "EventsCodeGenerator.h"
namespace gdjs {
gd::String ObjectCodeGenerator::onCreatedFunctionName =
"onCreated";
gd::String ObjectCodeGenerator::doStepPreEventsFunctionName =
"doStepPreEvents";
gd::String ObjectCodeGenerator::GenerateRuntimeObjectCompleteCode(
const gd::String& extensionName,
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace,
const std::map<gd::String, gd::String>& objectMethodMangledNames,
std::set<gd::String>& includeFiles,
bool compilationForRuntime) {
auto& eventsFunctionsVector =
eventsBasedObject.GetEventsFunctions().GetInternalVector();
return GenerateRuntimeObjectTemplateCode(
extensionName,
eventsBasedObject,
codeNamespace,
[&]() {
gd::String runtimeObjectDataInitializationCode;
for (auto& property :
eventsBasedObject.GetPropertyDescriptors().GetInternalVector()) {
runtimeObjectDataInitializationCode +=
property->IsHidden()
? GenerateInitializePropertyFromDefaultValueCode(*property)
: GenerateInitializePropertyFromDataCode(*property);
}
return runtimeObjectDataInitializationCode;
},
[&]() {
gd::String runtimeObjectPropertyMethodsCode;
for (auto& property :
eventsBasedObject.GetPropertyDescriptors().GetInternalVector()) {
runtimeObjectPropertyMethodsCode +=
GenerateRuntimeObjectPropertyTemplateCode(
eventsBasedObject, *property);
}
return runtimeObjectPropertyMethodsCode;
},
// TODO: Update code generation to be able to generate methods (which would allow
// for a cleaner output, not having to add methods to the prototype).
[&]() {
gd::String runtimeObjectMethodsCode;
for (auto& eventsFunction : eventsFunctionsVector) {
const gd::String& functionName =
objectMethodMangledNames.find(eventsFunction->GetName()) !=
objectMethodMangledNames.end()
? objectMethodMangledNames.find(eventsFunction->GetName())
->second
: "UNKNOWN_FUNCTION_fix_objectMethodMangledNames_please";
gd::String methodCodeNamespace =
codeNamespace + "." + eventsBasedObject.GetName() +
".prototype." + functionName + "Context";
gd::String methodFullyQualifiedName = codeNamespace + "." +
eventsBasedObject.GetName() +
".prototype." + functionName;
runtimeObjectMethodsCode +=
EventsCodeGenerator::GenerateObjectEventsFunctionCode(
project,
eventsBasedObject,
*eventsFunction,
methodCodeNamespace,
methodFullyQualifiedName,
"that._onceTriggers",
functionName == doStepPreEventsFunctionName
? GenerateDoStepPreEventsPreludeCode()
: "",
functionName == onCreatedFunctionName
? "gdjs.CustomRuntimeObject.prototype.onCreated.call(this);\n"
: "",
includeFiles,
compilationForRuntime);
}
bool hasDoStepPreEventsFunction =
eventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
doStepPreEventsFunctionName);
if (!hasDoStepPreEventsFunction) {
runtimeObjectMethodsCode +=
GenerateDefaultDoStepPreEventsFunctionCode(eventsBasedObject,
codeNamespace);
}
return runtimeObjectMethodsCode;
},
[&]() {
gd::String updateFromObjectCode;
updateFromObjectCode += "super.updateFromObjectData(oldObjectData, newObjectData);";
for (auto& property :
eventsBasedObject.GetPropertyDescriptors().GetInternalVector()) {
updateFromObjectCode +=
GenerateUpdatePropertyFromObjectDataCode(
eventsBasedObject, *property);
}
return updateFromObjectCode;
});
}
gd::String ObjectCodeGenerator::GenerateRuntimeObjectTemplateCode(
const gd::String& extensionName,
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace,
std::function<gd::String()> generateInitializePropertiesCode,
std::function<gd::String()> generatePropertiesCode,
std::function<gd::String()> generateMethodsCode,
std::function<gd::String()> generateUpdateFromObjectDataCode) {
return gd::String(R"jscode_template(
CODE_NAMESPACE = CODE_NAMESPACE || {};
/**
* Object generated from OBJECT_FULL_NAME
*/
CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME = class RUNTIME_OBJECT_CLASSNAME extends gdjs.CustomRuntimeObject {
constructor(runtimeScene, objectData) {
super(runtimeScene, objectData);
this._runtimeScene = runtimeScene;
this._onceTriggers = new gdjs.OnceTriggers();
this._behaviorData = {};
INITIALIZE_PROPERTIES_CODE
}
// Hot-reload:
updateFromObjectData(oldObjectData, newObjectData) {
UPDATE_FROM_OBJECT_DATA_CODE
this.onHotReloading(this.getInstanceContainer());
return true;
}
// Properties:
PROPERTIES_CODE
}
// Methods:
METHODS_CODE
gdjs.registerObject("EXTENSION_NAME::OBJECT_NAME", CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME);
)jscode_template")
.FindAndReplace("EXTENSION_NAME", extensionName)
.FindAndReplace("OBJECT_NAME", eventsBasedObject.GetName())
.FindAndReplace("OBJECT_FULL_NAME", eventsBasedObject.GetFullName())
.FindAndReplace("RUNTIME_OBJECT_CLASSNAME",
eventsBasedObject.GetName())
.FindAndReplace("CODE_NAMESPACE", codeNamespace)
.FindAndReplace("INITIALIZE_PROPERTIES_CODE",
generateInitializePropertiesCode())
.FindAndReplace("UPDATE_FROM_OBJECT_DATA_CODE", generateUpdateFromObjectDataCode())
.FindAndReplace("PROPERTIES_CODE", generatePropertiesCode())
.FindAndReplace("METHODS_CODE", generateMethodsCode());
;
}
// TODO these 2 methods are probably not needed if the properties are merged by GDJS.
gd::String ObjectCodeGenerator::GenerateInitializePropertyFromDataCode(
const gd::NamedPropertyDescriptor& property) {
return gd::String(R"jscode_template(
this._objectData.content.PROPERTY_NAME = objectData.content.PROPERTY_NAME !== undefined ? objectData.content.PROPERTY_NAME : DEFAULT_VALUE;)jscode_template")
.FindAndReplace("PROPERTY_NAME", property.GetName())
.FindAndReplace("DEFAULT_VALUE", GeneratePropertyValueCode(property));
}
gd::String
ObjectCodeGenerator::GenerateInitializePropertyFromDefaultValueCode(
const gd::NamedPropertyDescriptor& property) {
return gd::String(R"jscode_template(
this._objectData.content.PROPERTY_NAME = DEFAULT_VALUE;)jscode_template")
.FindAndReplace("PROPERTY_NAME", property.GetName())
.FindAndReplace("DEFAULT_VALUE", GeneratePropertyValueCode(property));
}
gd::String ObjectCodeGenerator::GenerateRuntimeObjectPropertyTemplateCode(
const gd::EventsBasedObject& eventsBasedObject,
const gd::NamedPropertyDescriptor& property) {
return gd::String(R"jscode_template(
GETTER_NAME() {
return this._objectData.content.PROPERTY_NAME !== undefined ? this._objectData.content.PROPERTY_NAME : DEFAULT_VALUE;
}
SETTER_NAME(newValue) {
this._objectData.content.PROPERTY_NAME = newValue;
})jscode_template")
.FindAndReplace("PROPERTY_NAME", property.GetName())
.FindAndReplace("GETTER_NAME",
GetObjectPropertyGetterName(property.GetName()))
.FindAndReplace("SETTER_NAME",
GetObjectPropertySetterName(property.GetName()))
.FindAndReplace("DEFAULT_VALUE", GeneratePropertyValueCode(property))
.FindAndReplace("RUNTIME_OBJECT_CLASSNAME",
eventsBasedObject.GetName());
}
gd::String ObjectCodeGenerator::GenerateUpdatePropertyFromObjectDataCode(
const gd::EventsBasedObject& eventsBasedObject,
const gd::NamedPropertyDescriptor& property) {
return gd::String(R"jscode_template(
if (oldObjectData.content.PROPERTY_NAME !== newObjectData.content.PROPERTY_NAME)
this._objectData.content.PROPERTY_NAME = newObjectData.content.PROPERTY_NAME;)jscode_template")
.FindAndReplace("PROPERTY_NAME", property.GetName());
}
gd::String ObjectCodeGenerator::GeneratePropertyValueCode(
const gd::PropertyDescriptor& property) {
if (property.GetType() == "String" ||
property.GetType() == "Choice" ||
property.GetType() == "Color") {
return EventsCodeGenerator::ConvertToStringExplicit(property.GetValue());
} else if (property.GetType() == "Number") {
return "Number(" +
EventsCodeGenerator::ConvertToStringExplicit(property.GetValue()) +
") || 0";
} else if (property.GetType() == "Boolean") { // TODO: Check if working
return property.GetValue() == "true" ? "true" : "false";
}
return "0 /* Error: property was of an unrecognized type */";
}
gd::String ObjectCodeGenerator::GenerateDefaultDoStepPreEventsFunctionCode(
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace) {
return gd::String(R"jscode_template(
CODE_NAMESPACE.RUNTIME_OBJECT_CLASSNAME.prototype.doStepPreEvents = function() {
PRELUDE_CODE
};
)jscode_template")
.FindAndReplace("RUNTIME_OBJECT_CLASSNAME",
eventsBasedObject.GetName())
.FindAndReplace("CODE_NAMESPACE", codeNamespace)
.FindAndReplace("PRELUDE_CODE", GenerateDoStepPreEventsPreludeCode());
}
gd::String ObjectCodeGenerator::GenerateDoStepPreEventsPreludeCode() {
return "this._onceTriggers.startNewFrame();";
}
} // namespace gdjs

View File

@@ -0,0 +1,95 @@
/*
* GDevelop JS Platform
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDJS_OBJECTCODEGENERATOR_H
#define GDJS_OBJECTCODEGENERATOR_H
#include <map>
#include <set>
#include <string>
#include <vector>
#include "GDCore/Project/EventsBasedObject.h"
namespace gd {
class NamedPropertyDescriptor;
}
namespace gdjs {
/**
* \brief The class being responsible for generating JavaScript code for
* EventsBasedObject.
*
* See also gd::EventsCodeGenerator.
*/
class ObjectCodeGenerator {
public:
ObjectCodeGenerator(gd::Project& project_) : project(project_){};
/**
* \brief Generate the complete JS class (`gdjs.CustomRuntimeObject`) for the
* object.
*/
gd::String GenerateRuntimeObjectCompleteCode(
const gd::String& extensionName,
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace,
const std::map<gd::String, gd::String>& objectMethodMangledNames,
std::set<gd::String>& includeFiles,
bool compilationForRuntime = false);
/**
* \brief Generate the name of the method to get the value of the property
* of a object.
*/
static gd::String GetObjectPropertyGetterName(
const gd::String& propertyName) {
return "_get" + propertyName;
}
/**
* \brief Generate the name of the method to set the value of the property
* of a object.
*/
static gd::String GetObjectPropertySetterName(
const gd::String& propertyName) {
return "_set" + propertyName;
}
private:
gd::String GenerateRuntimeObjectTemplateCode(
const gd::String& extensionName,
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace,
std::function<gd::String()> generateInitializePropertiesCode,
std::function<gd::String()> generateMethodsCode,
std::function<gd::String()> generatePropertiesCode,
std::function<gd::String()> generateUpdateFromObjectDataCode);
gd::String GenerateRuntimeObjectPropertyTemplateCode(
const gd::EventsBasedObject& eventsBasedObject,
const gd::NamedPropertyDescriptor& property);
gd::String GenerateInitializePropertyFromDataCode(
const gd::NamedPropertyDescriptor& property);
gd::String GenerateInitializePropertyFromDefaultValueCode(
const gd::NamedPropertyDescriptor& property);
gd::String GeneratePropertyValueCode(const gd::PropertyDescriptor& property);
gd::String GenerateUpdatePropertyFromObjectDataCode(
const gd::EventsBasedObject& eventsBasedObject,
const gd::NamedPropertyDescriptor& property);
gd::String GenerateObjectOnDestroyToDeprecatedOnOwnerRemovedFromScene(
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace);
gd::String GenerateDefaultDoStepPreEventsFunctionCode(
const gd::EventsBasedObject& eventsBasedObject,
const gd::String& codeNamespace);
gd::String GenerateDoStepPreEventsPreludeCode();
gd::Project& project;
static gd::String onCreatedFunctionName;
static gd::String doStepPreEventsFunctionName;
};
} // namespace gdjs
#endif // GDJS_OBJECTCODEGENERATOR_H

View File

@@ -31,6 +31,8 @@
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Project/SourceFile.h"
#include "GDCore/Serialization/Serializer.h"
@@ -557,6 +559,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "polygon.js");
InsertUnique(includesFiles, "runtimeobject.js");
InsertUnique(includesFiles, "profiler.js");
InsertUnique(includesFiles, "RuntimeInstanceContainer.js");
InsertUnique(includesFiles, "runtimescene.js");
InsertUnique(includesFiles, "scenestack.js");
InsertUnique(includesFiles, "force.js");
@@ -569,6 +572,8 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "runtimebehavior.js");
InsertUnique(includesFiles, "spriteruntimeobject.js");
InsertUnique(includesFiles, "affinetransformation.js");
InsertUnique(includesFiles, "CustomRuntimeObjectInstanceContainer.js");
InsertUnique(includesFiles, "CustomRuntimeObject.js");
// Common includes for events only.
InsertUnique(includesFiles, "events-tools/commontools.js");
@@ -615,6 +620,10 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "pixi-renderers/pixi-bitmapfont-manager.js");
InsertUnique(includesFiles,
"pixi-renderers/spriteruntimeobject-pixi-renderer.js");
InsertUnique(includesFiles,
"pixi-renderers/CustomObjectPixiRenderer.js");
InsertUnique(includesFiles,
"pixi-renderers/DebuggerPixiRenderer.js");
InsertUnique(includesFiles,
"pixi-renderers/loadingscreen-pixi-renderer.js");
InsertUnique(includesFiles, "pixi-renderers/pixi-effects-manager.js");
@@ -816,11 +825,25 @@ void ExporterHelper::ExportObjectAndBehaviorsIncludes(
}
};
// TODO UsedExtensionsFinder should be used instead to find the file to include.
// The Exporter class already use it.
addObjectsIncludeFiles(project);
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
const gd::Layout &layout = project.GetLayout(i);
addObjectsIncludeFiles(layout);
}
// Event based objects children
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount(); e++) {
auto& eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto&& eventsBasedObjectUniquePtr :
eventsFunctionsExtension.GetEventsBasedObjects()
.GetInternalVector()) {
auto eventsBasedObject = eventsBasedObjectUniquePtr.get();
addObjectsIncludeFiles(*eventsBasedObject);
}
}
}
void ExporterHelper::ExportObjectAndBehaviorsRequiredFiles(

View File

@@ -0,0 +1,587 @@
/*
* GDevelop JS Platform
* Copyright 2013-2022 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
export type ObjectConfiguration = {
content: any;
};
export type CustomObjectConfiguration = ObjectConfiguration & {
childrenContent: { [objectName: string]: ObjectConfiguration & any };
};
/**
* An object that contains other object.
*
* This is the base class for objects generated from EventsBasedObject.
*
* @see gdjs.CustomRuntimeObjectInstanceContainer
*/
export class CustomRuntimeObject extends gdjs.RuntimeObject {
/** It contains the children of this object. */
_instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer;
_isUntransformedHitBoxesDirty: boolean = true;
/** It contains shallow copies of the children hitboxes */
_untransformedHitBoxes: gdjs.Polygon[] = [];
/** The dimension of this object is calculated from it's children AABB. */
_unrotatedAABB: AABB = { min: [0, 0], max: [0, 0] };
_scaleX: number = 1;
_scaleY: number = 1;
_flippedX: boolean = false;
_flippedY: boolean = false;
opacity: float = 255;
_objectData: ObjectData & CustomObjectConfiguration;
/**
* @param parent The container the object belongs to
* @param objectData The object data used to initialize the object
*/
constructor(
parent: gdjs.RuntimeInstanceContainer,
objectData: ObjectData & CustomObjectConfiguration
) {
super(parent, objectData);
this._instanceContainer = new gdjs.CustomRuntimeObjectInstanceContainer(
parent,
this
);
this._objectData = objectData;
this._instanceContainer.loadFrom(objectData);
this.getRenderer().reinitialize(this, parent);
// The generated code calls the onCreated super implementation at the end.
this.onCreated();
}
reinitialize(objectData: ObjectData & CustomObjectConfiguration) {
super.reinitialize(objectData);
this._instanceContainer.loadFrom(objectData);
this.getRenderer().reinitialize(this, this.getParent());
// The generated code calls the onCreated super implementation at the end.
this.onCreated();
}
updateFromObjectData(
oldObjectData: ObjectData & CustomObjectConfiguration,
newObjectData: ObjectData & CustomObjectConfiguration
): boolean {
return this._instanceContainer.updateFrom(oldObjectData, newObjectData);
}
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
if (initialInstanceData.customSize) {
this.setWidth(initialInstanceData.width);
this.setHeight(initialInstanceData.height);
}
}
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this.onDestroy(instanceContainer);
super.onDestroyFromScene(instanceContainer);
this._instanceContainer.onDestroyFromScene(instanceContainer);
}
update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._instanceContainer._updateObjectsPreEvents();
this.doStepPreEvents(instanceContainer);
const profiler = this.getRuntimeScene().getProfiler();
if (profiler) {
profiler.begin(this._objectData.type);
}
// This is a bit like the "scene" events for custom objects.
this.doStepPostEvents(instanceContainer);
if (profiler) {
profiler.end(this._objectData.type);
}
this._instanceContainer._updateObjectsPostEvents();
}
/**
* This method is called when the preview is being hot-reloaded.
*/
onHotReloading(instanceContainer: gdjs.RuntimeInstanceContainer) {}
// This is only to handle trigger once.
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
/**
* This method is called each tick after events are done.
* @param instanceContainer The instanceContainer owning the object
*/
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
/**
* This method is called when the object is being removed from its parent
* container and is about to be destroyed/reused later.
*/
onDestroy(instanceContainer: gdjs.RuntimeInstanceContainer) {}
updatePreRender(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._instanceContainer._updateObjectsPreRender();
this.getRenderer().ensureUpToDate();
}
getRendererObject() {
return this.getRenderer().getRendererObject();
}
getRenderer() {
return this._instanceContainer.getRenderer();
}
onChildrenLocationChanged() {
this._isUntransformedHitBoxesDirty = true;
this.invalidateHitboxes();
this.getRenderer().update();
}
updateHitBoxes(): void {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
//Update the current hitboxes with the frame custom hit boxes
//and apply transformations.
for (let i = 0; i < this._untransformedHitBoxes.length; ++i) {
if (i >= this.hitBoxes.length) {
this.hitBoxes.push(new gdjs.Polygon());
}
for (
let j = 0;
j < this._untransformedHitBoxes[i].vertices.length;
++j
) {
if (j >= this.hitBoxes[i].vertices.length) {
this.hitBoxes[i].vertices.push([0, 0]);
}
this.applyObjectTransformation(
this._untransformedHitBoxes[i].vertices[j][0],
this._untransformedHitBoxes[i].vertices[j][1],
this.hitBoxes[i].vertices[j]
);
}
this.hitBoxes[i].vertices.length = this._untransformedHitBoxes[
i
].vertices.length;
}
}
/**
* Merge the hitboxes of the children.
*/
_updateUntransformedHitBoxes() {
this._isUntransformedHitBoxesDirty = false;
const oldUnscaledCenterX =
(this._unrotatedAABB.max[0] + this._unrotatedAABB.min[0]) / 2;
const oldUnscaledCenterY =
(this._unrotatedAABB.max[1] + this._unrotatedAABB.min[1]) / 2;
this._untransformedHitBoxes.length = 0;
if (this._instanceContainer.getAdhocListOfAllInstances().length === 0) {
this._unrotatedAABB.min[0] = 0;
this._unrotatedAABB.min[1] = 0;
this._unrotatedAABB.max[0] = 0;
this._unrotatedAABB.max[1] = 0;
} else {
let minX = Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxX = -Number.MAX_VALUE;
let maxY = -Number.MAX_VALUE;
for (const childInstance of this._instanceContainer.getAdhocListOfAllInstances()) {
Array.prototype.push.apply(
this._untransformedHitBoxes,
childInstance.getHitBoxes()
);
const childAABB = childInstance.getAABB();
minX = Math.min(minX, childAABB.min[0]);
minY = Math.min(minY, childAABB.min[1]);
maxX = Math.max(maxX, childAABB.max[0]);
maxY = Math.max(maxY, childAABB.max[1]);
}
this._unrotatedAABB.min[0] = minX;
this._unrotatedAABB.min[1] = minY;
this._unrotatedAABB.max[0] = maxX;
this._unrotatedAABB.max[1] = maxY;
while (this.hitBoxes.length < this._untransformedHitBoxes.length) {
this.hitBoxes.push(new gdjs.Polygon());
}
this.hitBoxes.length = this._untransformedHitBoxes.length;
}
if (
this.getUnscaledCenterX() !== oldUnscaledCenterX ||
this.getUnscaledCenterY() !== oldUnscaledCenterY
) {
this._instanceContainer.onObjectUnscaledCenterChanged(
oldUnscaledCenterX,
oldUnscaledCenterY
);
}
}
// Position:
/**
* Return an array containing the coordinates of the point passed as parameter
* in parent coordinate coordinates (as opposed to the object local coordinates).
*
* All transformations (flipping, scale, rotation) are supported.
*
* @param x The X position of the point, in object coordinates.
* @param y The Y position of the point, in object coordinates.
* @param result Array that will be updated with the result
* (x and y position of the point in parent coordinates).
*/
applyObjectTransformation(x: float, y: float, result: number[]) {
let cx = this.getCenterX();
let cy = this.getCenterY();
// Flipping
if (this._flippedX) {
x = x + (cx - x) * 2;
}
if (this._flippedY) {
y = y + (cy - y) * 2;
}
// Scale
const absScaleX = Math.abs(this._scaleX);
const absScaleY = Math.abs(this._scaleY);
x *= absScaleX;
y *= absScaleY;
cx *= absScaleX;
cy *= absScaleY;
// Rotation
const oldX = x;
const angleInRadians = (this.angle / 180) * Math.PI;
const cosValue = Math.cos(angleInRadians);
const sinValue = Math.sin(angleInRadians);
const xToCenterXDelta = x - cx;
const yToCenterYDelta = y - cy;
x = cx + cosValue * xToCenterXDelta - sinValue * yToCenterYDelta;
y = cy + sinValue * xToCenterXDelta + cosValue * yToCenterYDelta;
result.length = 2;
result[0] = x + this.x;
result[1] = y + this.y;
}
/**
* Return an array containing the coordinates of the point passed as parameter
* in object local coordinates (as opposed to the parent coordinate coordinates).
*
* All transformations (flipping, scale, rotation) are supported.
*
* @param x The X position of the point, in parent coordinates.
* @param y The Y position of the point, in parent coordinates.
* @param result Array that will be updated with the result
* (x and y position of the point in object coordinates).
*/
applyObjectInverseTransformation(x: float, y: float, result: number[]) {
x -= this.getCenterXInScene();
y -= this.getCenterYInScene();
const absScaleX = Math.abs(this._scaleX);
const absScaleY = Math.abs(this._scaleY);
// Rotation
const angleInRadians = (this.angle / 180) * Math.PI;
const cosValue = Math.cos(-angleInRadians);
const sinValue = Math.sin(-angleInRadians);
const oldX = x;
x = cosValue * x - sinValue * y;
y = sinValue * oldX + cosValue * y;
// Scale
x /= absScaleX;
y /= absScaleY;
// Flipping
if (this._flippedX) {
x = -x;
}
if (this._flippedY) {
y = -y;
}
const positionToCenterX =
this.getUnscaledWidth() / 2 + this._unrotatedAABB.min[0];
const positionToCenterY =
this.getUnscaledHeight() / 2 + this._unrotatedAABB.min[1];
result[0] = x + positionToCenterX;
result[1] = y + positionToCenterY;
}
getDrawableX(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return this.x + this._unrotatedAABB.min[0] * this._scaleX;
}
getDrawableY(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return this.y + this._unrotatedAABB.min[1] * this._scaleY;
}
/**
* @return the internal width of the object according to its children.
*/
getUnscaledWidth(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return this._unrotatedAABB.max[0] - this._unrotatedAABB.min[0];
}
/**
* @return the internal height of the object according to its children.
*/
getUnscaledHeight(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return this._unrotatedAABB.max[1] - this._unrotatedAABB.min[1];
}
/**
* @returns the center X from the local origin (0;0).
*/
getUnscaledCenterX(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return (this._unrotatedAABB.min[0] + this._unrotatedAABB.max[0]) / 2;
}
/**
* @returns the center Y from the local origin (0;0).
*/
getUnscaledCenterY(): float {
if (this._isUntransformedHitBoxesDirty) {
this._updateUntransformedHitBoxes();
}
return (this._unrotatedAABB.min[1] + this._unrotatedAABB.max[1]) / 2;
}
getWidth(): float {
return this.getUnscaledWidth() * this.getScaleX();
}
getHeight(): float {
return this.getUnscaledHeight() * this.getScaleY();
}
setWidth(newWidth: float): void {
const unscaledWidth = this.getUnscaledWidth();
if (unscaledWidth !== 0) {
this.setScaleX(newWidth / unscaledWidth);
}
}
setHeight(newHeight: float): void {
const unscaledHeight = this.getUnscaledHeight();
if (unscaledHeight !== 0) {
this.setScaleY(newHeight / unscaledHeight);
}
}
/**
* Change the size of the object.
*
* @param newWidth The new width of the object, in pixels.
* @param newHeight The new height of the object, in pixels.
*/
setSize(newWidth: float, newHeight: float): void {
this.setWidth(newWidth);
this.setHeight(newHeight);
}
setX(x: float): void {
if (x === this.x) {
return;
}
this.x = x;
this.invalidateHitboxes();
this.getRenderer().updateX();
}
setY(y: float): void {
if (y === this.y) {
return;
}
this.y = y;
this.invalidateHitboxes();
this.getRenderer().updateY();
}
setAngle(angle: float): void {
if (this.angle === angle) {
return;
}
this.angle = angle;
this.invalidateHitboxes();
this.getRenderer().updateAngle();
}
/**
* Change the scale on X and Y axis of the object.
*
* @param newScale The new scale (must be greater than 0).
*/
setScale(newScale: number): void {
if (newScale < 0) {
newScale = 0;
}
if (
newScale === Math.abs(this._scaleX) &&
newScale === Math.abs(this._scaleY)
) {
return;
}
this._scaleX = newScale * (this._flippedX ? -1 : 1);
this._scaleY = newScale * (this._flippedY ? -1 : 1);
this.invalidateHitboxes();
this.getRenderer().update();
}
/**
* Change the scale on X axis of the object (changing its width).
*
* @param newScale The new scale (must be greater than 0).
*/
setScaleX(newScale: number): void {
if (newScale < 0) {
newScale = 0;
}
if (newScale === Math.abs(this._scaleX)) {
return;
}
this._scaleX = newScale * (this._flippedX ? -1 : 1);
this.invalidateHitboxes();
this.getRenderer().update();
}
/**
* Change the scale on Y axis of the object (changing its height).
*
* @param newScale The new scale (must be greater than 0).
*/
setScaleY(newScale: number): void {
if (newScale < 0) {
newScale = 0;
}
if (newScale === Math.abs(this._scaleY)) {
return;
}
this._scaleY = newScale * (this._flippedY ? -1 : 1);
this.invalidateHitboxes();
this.getRenderer().update();
}
/**
* Get the scale of the object (or the average of the X and Y scale in case
* they are different).
*
* @return the scale of the object (or the average of the X and Y scale in
* case they are different).
*/
getScale(): number {
return (Math.abs(this._scaleX) + Math.abs(this._scaleY)) / 2.0;
}
/**
* Get the scale of the object on Y axis.
*
* @return the scale of the object on Y axis
*/
getScaleY(): float {
return Math.abs(this._scaleY);
}
/**
* Get the scale of the object on X axis.
*
* @return the scale of the object on X axis
*/
getScaleX(): float {
return Math.abs(this._scaleX);
}
// Visibility and display :
/**
* Change the transparency of the object.
* @param opacity The new opacity, between 0 (transparent) and 255 (opaque).
*/
setOpacity(opacity: float): void {
if (opacity < 0) {
opacity = 0;
}
if (opacity > 255) {
opacity = 255;
}
this.opacity = opacity;
this.getRenderer().updateOpacity();
}
/**
* Get the transparency of the object.
* @return The opacity, between 0 (transparent) and 255 (opaque).
*/
getOpacity(): number {
return this.opacity;
}
/**
* Hide (or show) the object
* @param enable true to hide the object, false to show it again.
*/
hide(enable: boolean): void {
if (enable === undefined) {
enable = true;
}
this.hidden = enable;
this.getRenderer().updateVisibility();
}
flipX(enable: boolean) {
if (enable !== this._flippedX) {
this._scaleX *= -1;
this._flippedX = enable;
this.invalidateHitboxes();
this.getRenderer().update();
}
}
flipY(enable: boolean) {
if (enable !== this._flippedY) {
this._scaleY *= -1;
this._flippedY = enable;
this.invalidateHitboxes();
this.getRenderer().update();
}
}
isFlippedX(): boolean {
return this._flippedX;
}
isFlippedY(): boolean {
return this._flippedY;
}
}
// Others initialization and internal state management :
CustomRuntimeObject.supportsReinitialization = true;
}

View File

@@ -0,0 +1,401 @@
/*
* GDevelop JS Platform
* Copyright 2013-2022 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
const logger = new gdjs.Logger('CustomRuntimeObject');
const setupWarningLogger = new gdjs.Logger(
'CustomRuntimeObject (setup warnings)'
);
/**
* The instance container of a custom object, containing instances of objects rendered on screen.
*
* @see gdjs.CustomRuntimeObject
*/
export class CustomRuntimeObjectInstanceContainer extends gdjs.RuntimeInstanceContainer {
_renderer: gdjs.CustomObjectRenderer;
_debuggerRenderer: gdjs.DebuggerRenderer;
_runtimeScene: gdjs.RuntimeScene;
/** The parent container that contains the object associated with this container. */
_parent: gdjs.RuntimeInstanceContainer;
/** The object that is built with the instances of this container. */
_customObject: gdjs.CustomRuntimeObject;
_isLoaded: boolean = false;
/**
* @param parent the parent container that contains the object associated
* with this container.
* @param customObject the object that is built with the instances of this
* container.
*/
constructor(
parent: gdjs.RuntimeInstanceContainer,
customObject: gdjs.CustomRuntimeObject
) {
super();
this._parent = parent;
this._customObject = customObject;
this._runtimeScene = parent.getScene();
this._renderer = new gdjs.CustomObjectRenderer(
customObject,
this,
parent
);
this._debuggerRenderer = new gdjs.DebuggerRenderer(this);
}
createObject(objectName: string): gdjs.RuntimeObject | null {
const result = super.createObject(objectName);
this._customObject.onChildrenLocationChanged();
return result;
}
/**
* Load the container from the given initial configuration.
* @param customObjectData An object containing the container data.
* @see gdjs.RuntimeGame#getSceneData
*/
loadFrom(customObjectData: ObjectData & CustomObjectConfiguration) {
if (this._isLoaded) {
this.onDestroyFromScene(this._parent);
}
const eventsBasedObjectData = this._runtimeScene
.getGame()
.getEventsBasedObjectData(customObjectData.type);
if (!eventsBasedObjectData) {
logger.error('loadFrom was called without an events-based object');
return;
}
// Registering objects
for (
let i = 0, len = eventsBasedObjectData.objects.length;
i < len;
++i
) {
const childObjectData = eventsBasedObjectData.objects[i];
this.registerObject({
...childObjectData,
...customObjectData.childrenContent[childObjectData.name],
});
}
// TODO EBO Remove it when the instance editor is done.
// Add a default layer
this.addLayer({
name: '',
visibility: true,
cameras: [
{
defaultSize: true,
defaultViewport: true,
height: 0,
viewportBottom: 0,
viewportLeft: 0,
viewportRight: 0,
viewportTop: 0,
width: 0,
},
],
effects: [],
ambientLightColorR: 0,
ambientLightColorG: 0,
ambientLightColorB: 0,
isLightingLayer: false,
followBaseLayerCamera: false,
});
// Set up the default z order (for objects created from events)
this._setLayerDefaultZOrders();
this._isLoaded = true;
}
/**
* Called when the container must be updated using the specified
* objectData. This is the case during hot-reload, and is only called if
* the object was modified.
*
* @param oldCustomObjectData The previous data for the object.
* @param newCustomObjectData The new data for the object.
* @returns true if the object was updated, false if it could not
* (i.e: hot-reload is not supported).
*/
updateFrom(
oldCustomObjectData: ObjectData & CustomObjectConfiguration,
newCustomObjectData: ObjectData & CustomObjectConfiguration
): boolean {
const eventsBasedObjectData = this._runtimeScene
.getGame()
.getEventsBasedObjectData(newCustomObjectData.type);
if (!eventsBasedObjectData) {
logger.error('updateFrom was called without an events-based object');
return false;
}
for (
let i = 0, len = eventsBasedObjectData.objects.length;
i < len;
++i
) {
const childName = eventsBasedObjectData.objects[i].name;
const oldChildData = {
...eventsBasedObjectData.objects[i],
...oldCustomObjectData.childrenContent[childName],
};
const newChildData = {
...eventsBasedObjectData.objects[i],
...newCustomObjectData.childrenContent[childName],
};
this.updateObject(newChildData);
for (const child of this.getInstancesOf(childName)) {
child.updateFromObjectData(oldChildData, newChildData);
}
}
return true;
}
/**
* Called when the associated object is destroyed (because it is removed
* from its parent container or the scene is being unloaded).
*
* @param instanceContainer The container owning the object.
*/
onDestroyFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
if (!this._isLoaded) {
return;
}
// Notify the objects they are being destroyed
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const object = allInstancesList[i];
object.onDestroyFromScene(this);
}
this._destroy();
this._isLoaded = false;
}
_destroy() {
// It should not be necessary to reset these variables, but this help
// ensuring that all memory related to the container is released immediately.
super._destroy();
// @ts-ignore We are deleting the object
this._onceTriggers = null;
}
_updateLayersCameraCoordinates(scale: float) {
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const theLayer = this._layers.items[name];
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
name
] || [0, 0, 0, 0];
this._layersCameraCoordinates[name][0] =
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][1] =
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
this._layersCameraCoordinates[name][2] =
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][3] =
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
}
}
}
/**
* Called to update effects of layers before rendering.
*/
_updateLayersPreRender() {
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const layer = this._layers.items[name];
layer.updatePreRender(this);
}
}
}
/**
* Called to update visibility of the renderers of objects
* rendered on the scene ("culling"), update effects (of visible objects)
* and give a last chance for objects to update before rendering.
*
* Visibility is set to false if object is hidden, or if
* object is too far from the camera of its layer ("culling").
*/
_updateObjectsPreRender() {
const allInstancesList = this.getAdhocListOfAllInstances();
for (
let i = 0, len = this.getAdhocListOfAllInstances().length;
i < len;
++i
) {
const object = this.getAdhocListOfAllInstances()[i];
const rendererObject = object.getRendererObject();
if (rendererObject) {
rendererObject.visible = !object.isHidden();
// Update effects, only for visible objects.
if (rendererObject.visible) {
this.getGame()
.getEffectsManager()
.updatePreRender(object.getRendererEffects(), object);
}
}
// Set to true to enable debug rendering (look for the implementation in the renderer
// to see what is rendered).
if (this._debugDrawEnabled) {
this._debuggerRenderer.renderDebugDraw(
this.getAdhocListOfAllInstances(),
this._debugDrawShowHiddenInstances,
this._debugDrawShowPointsNames,
this._debugDrawShowCustomPoints
);
}
// Perform pre-render update.
object.updatePreRender(this);
}
return;
}
/**
* Update the objects before launching the events.
*/
_updateObjectsPreEvents() {
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const obj = allInstancesList[i];
const elapsedTime = obj.getElapsedTime();
if (!obj.hasNoForces()) {
const averageForce = obj.getAverageForce();
const elapsedTimeInSeconds = elapsedTime / 1000;
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
obj.update(this);
obj.updateForces(elapsedTimeInSeconds);
} else {
obj.update(this);
}
obj.updateTimers(elapsedTime);
obj.stepBehaviorsPreEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/**
* Update the objects (update positions, time management...)
*/
_updateObjectsPostEvents() {
this._cacheOrClearRemovedInstances();
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
allInstancesList[i].stepBehaviorsPostEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/**
* Get the renderer associated to the RuntimeScene.
*/
getRenderer(): gdjs.CustomObjectRenderer {
return this._renderer;
}
getDebuggerRenderer() {
return this._debuggerRenderer;
}
getGame() {
return this._runtimeScene.getGame();
}
getScene() {
return this._runtimeScene;
}
getViewportWidth(): float {
return this._customObject.getUnscaledWidth();
}
getViewportHeight(): float {
return this._customObject.getUnscaledHeight();
}
getViewportOriginX(): float {
return this._customObject.getUnscaledCenterX();
}
getViewportOriginY(): float {
return this._customObject.getUnscaledCenterY();
}
onChildrenLocationChanged(): void {
this._customObject.onChildrenLocationChanged();
}
/**
* Triggered when the object dimensions are changed.
*
* It adapts the layers camera positions.
*/
onObjectUnscaledCenterChanged(oldOriginX: float, oldOriginY: float): void {
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
/** @type gdjs.Layer */
const theLayer: gdjs.Layer = this._layers.items[name];
theLayer.onGameResolutionResized(oldOriginX, oldOriginY);
}
}
}
convertCoords(x: float, y: float, result: FloatPoint): FloatPoint {
// The result parameter used to be optional.
let position = result || [0, 0];
position = this._parent
.getLayer(this._customObject.getLayer())
.convertCoords(x, y, 0, position);
this._customObject.applyObjectInverseTransformation(
position[0],
position[1],
position
);
return position;
}
convertInverseCoords(
sceneX: float,
sceneY: float,
result: FloatPoint
): FloatPoint {
const position = result || [0, 0];
this._customObject.applyObjectTransformation(sceneX, sceneY, position);
return this._parent.convertInverseCoords(
position[0],
position[1],
position
);
}
/**
* Return the time elapsed since the last frame,
* in milliseconds, for objects on the layer.
*/
getElapsedTime(): float {
return this._parent.getElapsedTime();
}
}
}

View File

@@ -0,0 +1,686 @@
/*
* GDevelop JS Platform
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
const logger = new gdjs.Logger('RuntimeInstanceContainer');
const setupWarningLogger = new gdjs.Logger(
'RuntimeInstanceContainer (setup warnings)'
);
/**
* A container of object instances rendered on screen.
*/
export abstract class RuntimeInstanceContainer {
/** Contains the instances living on the container */
_instances: Hashtable<RuntimeObject[]>;
/**
* An array used to create a list of all instance when necessary.
* @see gdjs.RuntimeInstanceContainer#_constructListOfAllInstances}
*/
private _allInstancesList: gdjs.RuntimeObject[] = [];
_allInstancesListIsUpToDate = true;
/** Used to recycle destroyed instance instead of creating new ones. */
_instancesCache: Hashtable<RuntimeObject[]>;
/** The instances removed from the container and waiting to be sent to the cache. */
_instancesRemoved: gdjs.RuntimeObject[] = [];
/** Contains the objects data stored in the project */
_objects: Hashtable<ObjectData>;
_objectsCtor: Hashtable<typeof RuntimeObject>;
_layers: Hashtable<Layer>;
_layersCameraCoordinates: Record<string, [float, float, float, float]> = {};
// Options for the debug draw:
_debugDrawEnabled: boolean = false;
_debugDrawShowHiddenInstances: boolean = false;
_debugDrawShowPointsNames: boolean = false;
_debugDrawShowCustomPoints: boolean = false;
constructor() {
this._instances = new Hashtable();
this._instancesCache = new Hashtable();
this._objects = new Hashtable();
this._objectsCtor = new Hashtable();
this._layers = new Hashtable();
}
/**
* Return the time elapsed since the last frame,
* in milliseconds, for objects on the layer.
*/
abstract getElapsedTime(): float;
/**
* Get the renderer associated to the container.
*/
abstract getRenderer(): gdjs.RuntimeInstanceContainerRenderer;
/**
* Get the renderer for visual debugging associated to the container.
*/
abstract getDebuggerRenderer(): gdjs.DebuggerRenderer;
/**
* Get the {@link gdjs.RuntimeGame} associated to this.
*/
abstract getGame(): gdjs.RuntimeGame;
/**
* Get the {@link gdjs.RuntimeScene} associated to this.
*/
abstract getScene(): gdjs.RuntimeScene;
/**
* Convert a point from the canvas coordinates (for example,
* the mouse position) to the container coordinates.
*
* @param x The x position, in container coordinates.
* @param y The y position, in container coordinates.
* @param result The point instance that is used to return the result.
*/
abstract convertCoords(x: float, y: float, result?: FloatPoint): FloatPoint;
/**
* Convert a point from the container coordinates (for example,
* an object position) to the canvas coordinates.
*
* @param sceneX The x position, in container coordinates.
* @param sceneY The y position, in container coordinates.
* @param result The point instance that is used to return the result.
*/
abstract convertInverseCoords(
sceneX: float,
sceneY: float,
result: FloatPoint
): FloatPoint;
/**
* @return the width of the game resolution for a {@link gdjs.RuntimeScene} or the
* internal size for a {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportWidth(): float;
/**
* @return the height of the game resolution for a {@link gdjs.RuntimeScene} or the
* internal size for a {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportHeight(): float;
/**
* @return the center X of the game resolution for a {@link gdjs.RuntimeScene} or the
* internal size for a {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportOriginX(): float;
/**
* @return the center Y of the game resolution for a {@link gdjs.RuntimeScene} or the
* internal size for a {@link gdjs.CustomRuntimeObject}.
*/
abstract getViewportOriginY(): float;
/**
* Triggered when the AABB of a child of the container could have changed.
*/
abstract onChildrenLocationChanged(): void;
/**
* Activate or deactivate the debug visualization for collisions and points.
*/
enableDebugDraw(
enableDebugDraw: boolean,
showHiddenInstances: boolean,
showPointsNames: boolean,
showCustomPoints: boolean
): void {
if (this._debugDrawEnabled && !enableDebugDraw) {
this.getDebuggerRenderer().clearDebugDraw();
}
this._debugDrawEnabled = enableDebugDraw;
this._debugDrawShowHiddenInstances = showHiddenInstances;
this._debugDrawShowPointsNames = showPointsNames;
this._debugDrawShowCustomPoints = showCustomPoints;
}
/**
* Check if an object is registered, meaning that instances of it can be
* created and lives in the container.
* @see gdjs.RuntimeInstanceContainer#registerObject
*/
isObjectRegistered(objectName: string): boolean {
return (
this._objects.containsKey(objectName) &&
this._instances.containsKey(objectName) &&
this._objectsCtor.containsKey(objectName)
);
}
/**
* Register a {@link gdjs.RuntimeObject} so that instances of it can be
* used in the container.
* @param objectData The data for the object to register.
*/
registerObject(objectData: ObjectData) {
this._objects.put(objectData.name, objectData);
this._instances.put(objectData.name, []);
// Cache the constructor
const Ctor = gdjs.getObjectConstructor(objectData.type);
this._objectsCtor.put(objectData.name, Ctor);
// Also prepare a cache for recycled instances, if the object supports it.
if (Ctor.supportsReinitialization) {
this._instancesCache.put(objectData.name, []);
}
}
/**
* Update the data of a {@link gdjs.RuntimeObject} so that instances use
* this when constructed.
* @param objectData The data for the object to register.
*/
updateObject(objectData: ObjectData): void {
if (!this.isObjectRegistered(objectData.name)) {
logger.warn(
'Tried to call updateObject for an object that was not registered (' +
objectData.name +
'). Call registerObject first.'
);
}
this._objects.put(objectData.name, objectData);
}
// Don't erase instances, nor instances cache, or objectsCtor cache.
/**
* Unregister a {@link gdjs.RuntimeObject}. Instances will be destroyed.
* @param objectName The name of the object to unregister.
*/
unregisterObject(objectName: string) {
const instances = this._instances.get(objectName);
if (instances) {
// This is sub-optimal: markObjectForDeletion will search the instance to
// remove in instances, so cost is O(n^2), n being the number of instances.
// As we're unregistering an object which only happen during a hot-reloading,
// this is fine.
const instancesToRemove = instances.slice();
for (let i = 0; i < instancesToRemove.length; i++) {
this.markObjectForDeletion(instancesToRemove[i]);
}
this._cacheOrClearRemovedInstances();
}
this._objects.remove(objectName);
this._instances.remove(objectName);
this._instancesCache.remove(objectName);
this._objectsCtor.remove(objectName);
}
/**
* Create objects from initial instances data (for example, the initial instances
* of the scene or the instances of an external layout).
*
* @param data The instances data
* @param xPos The offset on X axis
* @param yPos The offset on Y axis
* @param trackByPersistentUuid If true, objects are tracked by setting their `persistentUuid`
* to the same as the associated instance. Useful for hot-reloading when instances are changed.
*/
createObjectsFrom(
data: InstanceData[],
xPos: float,
yPos: float,
trackByPersistentUuid: boolean
) {
for (let i = 0, len = data.length; i < len; ++i) {
const instanceData = data[i];
const objectName = instanceData.name;
const newObject = this.createObject(objectName);
if (newObject !== null) {
if (trackByPersistentUuid) {
// Give the object the same persistentUuid as the instance, so that
// it can be hot-reloaded.
newObject.persistentUuid = instanceData.persistentUuid || null;
}
newObject.setPosition(instanceData.x + xPos, instanceData.y + yPos);
newObject.setZOrder(instanceData.zOrder);
newObject.setAngle(instanceData.angle);
newObject.setLayer(instanceData.layer);
newObject
.getVariables()
.initFrom(instanceData.initialVariables, true);
newObject.extraInitializationFromInitialInstance(instanceData);
}
}
}
/**
* Set the default Z order for each layer, which is the highest Z order found on each layer.
* Useful as it ensures that instances created from events are, by default, shown in front
* of other instances.
*/
_setLayerDefaultZOrders() {
if (
this.getGame().getGameData().properties.useDeprecatedZeroAsDefaultZOrder
) {
// Deprecated option to still support games that were made considered 0 as the
// default Z order for all layers.
return;
}
const layerHighestZOrders: Record<string, number> = {};
const allInstances = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstances.length; i < len; ++i) {
const object = allInstances[i];
let layerName = object.getLayer();
const zOrder = object.getZOrder();
if (
layerHighestZOrders[layerName] === undefined ||
layerHighestZOrders[layerName] < zOrder
) {
layerHighestZOrders[layerName] = zOrder;
}
}
for (let layerName in layerHighestZOrders) {
this.getLayer(layerName).setDefaultZOrder(
layerHighestZOrders[layerName] + 1
);
}
}
_updateLayersCameraCoordinates(scale: float) {
this._layersCameraCoordinates = this._layersCameraCoordinates || {};
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const theLayer = this._layers.items[name];
this._layersCameraCoordinates[name] = this._layersCameraCoordinates[
name
] || [0, 0, 0, 0];
this._layersCameraCoordinates[name][0] =
theLayer.getCameraX() - (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][1] =
theLayer.getCameraY() - (theLayer.getCameraHeight() / 2) * scale;
this._layersCameraCoordinates[name][2] =
theLayer.getCameraX() + (theLayer.getCameraWidth() / 2) * scale;
this._layersCameraCoordinates[name][3] =
theLayer.getCameraY() + (theLayer.getCameraHeight() / 2) * scale;
}
}
}
/**
* Called to update effects of layers before rendering.
*/
_updateLayersPreRender() {
for (const name in this._layers.items) {
if (this._layers.items.hasOwnProperty(name)) {
const layer = this._layers.items[name];
layer.updatePreRender(this);
}
}
}
/**
* Called to update visibility of the renderers of objects
* rendered on the scene ("culling"), update effects (of visible objects)
* and give a last chance for objects to update before rendering.
*
* Visibility is set to false if object is hidden, or if
* object is too far from the camera of its layer ("culling").
*/
_updateObjectsPreRender() {
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const object = allInstancesList[i];
const rendererObject = object.getRendererObject();
if (rendererObject) {
rendererObject.visible = !object.isHidden();
// Update effects, only for visible objects.
if (rendererObject.visible) {
this.getGame()
.getEffectsManager()
.updatePreRender(object.getRendererEffects(), object);
}
}
// Perform pre-render update.
object.updatePreRender(this);
}
return;
}
/**
* Empty the list of the removed objects:
*
* When an object is removed from the container, it is still kept in
* {@link gdjs.RuntimeInstanceContainer#_instancesRemoved}.
*
* This method should be called regularly (after events or behaviors steps) so as to clear this list
* and allows the removed objects to be cached (or destroyed if the cache is full).
*
* The removed objects could not be sent directly to the cache, as events may still be using them after
* removing them from the scene for example.
*/
_cacheOrClearRemovedInstances() {
for (let k = 0, lenk = this._instancesRemoved.length; k < lenk; ++k) {
// Cache the instance to recycle it into a new instance later.
// If the object does not support recycling, the cache won't be defined.
const cache = this._instancesCache.get(
this._instancesRemoved[k].getName()
);
if (cache) {
if (cache.length < 128) {
cache.push(this._instancesRemoved[k]);
}
}
}
this._instancesRemoved.length = 0;
}
/**
* Tool function filling _allInstancesList member with all the living object instances.
*/
private _constructListOfAllInstances() {
let currentListSize = 0;
for (const name in this._instances.items) {
if (this._instances.items.hasOwnProperty(name)) {
const list = this._instances.items[name];
const oldSize = currentListSize;
currentListSize += list.length;
for (let j = 0, lenj = list.length; j < lenj; ++j) {
if (oldSize + j < this._allInstancesList.length) {
this._allInstancesList[oldSize + j] = list[j];
} else {
this._allInstancesList.push(list[j]);
}
}
}
}
this._allInstancesList.length = currentListSize;
this._allInstancesListIsUpToDate = true;
}
/**
* @param objectName The name of the object
* @returns the instances of a given object in the container.
*/
getInstancesOf(objectName: string): gdjs.RuntimeObject[] {
return this._instances.items[objectName];
}
/**
* Get a list of all {@link gdjs.RuntimeObject} living in the container.
* You should not, normally, need this method at all. It's only to be used
* in exceptional use cases where you need to loop through all objects,
* and it won't be performant.
*
* @returns The list of all runtime objects in the container
*/
getAdhocListOfAllInstances(): gdjs.RuntimeObject[] {
if (!this._allInstancesListIsUpToDate) {
this._constructListOfAllInstances();
}
return this._allInstancesList;
}
/**
* Update the objects before launching the events.
*/
_updateObjectsPreEvents() {
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
// may delete the objects.
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const obj = allInstancesList[i];
const elapsedTime = obj.getElapsedTime();
if (!obj.hasNoForces()) {
const averageForce = obj.getAverageForce();
const elapsedTimeInSeconds = elapsedTime / 1000;
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
obj.update(this);
obj.updateForces(elapsedTimeInSeconds);
} else {
obj.update(this);
}
obj.updateTimers(elapsedTime);
allInstancesList[i].stepBehaviorsPreEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/**
* Update the objects (update positions, time management...)
*/
_updateObjectsPostEvents() {
this._cacheOrClearRemovedInstances();
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
// may delete the objects.
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
allInstancesList[i].stepBehaviorsPostEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/**
* Add an object to the instances living in the container.
* @param obj The object to be added.
*/
addObject(obj: gdjs.RuntimeObject) {
if (!this._instances.containsKey(obj.name)) {
this._instances.put(obj.name, []);
}
this._instances.get(obj.name).push(obj);
this._allInstancesListIsUpToDate = false;
}
/**
* Get all the instances of the object called name.
* @param name Name of the object for which the instances must be returned.
* @return The list of objects with the given name
*/
getObjects(name: string): gdjs.RuntimeObject[] | undefined {
if (!this._instances.containsKey(name)) {
logger.info(
'RuntimeScene.getObjects: No instances called "' +
name +
'"! Adding it.'
);
this._instances.put(name, []);
}
return this._instances.get(name);
}
/**
* Create a new object from its name. The object is also added to the instances
* living in the container (No need to call {@link gdjs.RuntimeScene.addObject})
* @param objectName The name of the object to be created
* @return The created object
*/
createObject(objectName: string): gdjs.RuntimeObject | null {
if (
!this._objectsCtor.containsKey(objectName) ||
!this._objects.containsKey(objectName)
) {
// There is no such object in this container.
return null;
}
// Create a new object using the object constructor (cached during loading)
// and the stored object's data:
const cache = this._instancesCache.get(objectName);
const ctor = this._objectsCtor.get(objectName);
let obj;
if (!cache || cache.length === 0) {
obj = new ctor(this, this._objects.get(objectName));
} else {
// Reuse an objet destroyed before. If there is an object in the cache,
// then it means it does support reinitialization.
obj = cache.pop();
obj.reinitialize(this._objects.get(objectName));
}
this.addObject(obj);
return obj;
}
/**
* Must be called whenever an object must be removed from the container.
* @param obj The object to be removed.
*/
markObjectForDeletion(obj: gdjs.RuntimeObject) {
// Add to the objects removed list.
// The objects will be sent to the instances cache or really deleted from memory later.
if (this._instancesRemoved.indexOf(obj) === -1) {
this._instancesRemoved.push(obj);
}
// Delete from the living instances.
if (this._instances.containsKey(obj.getName())) {
const objId = obj.id;
const allInstances = this._instances.get(obj.getName());
for (let i = 0, len = allInstances.length; i < len; ++i) {
if (allInstances[i].id == objId) {
allInstances.splice(i, 1);
this._allInstancesListIsUpToDate = false;
break;
}
}
}
// Notify the object it was removed from the container
obj.onDestroyFromScene(this);
// Notify the global callbacks
for (let j = 0; j < gdjs.callbacksObjectDeletedFromScene.length; ++j) {
gdjs.callbacksObjectDeletedFromScene[j](this, obj);
}
return;
}
/**
* Get the layer with the given name
* @param name The name of the layer
* @returns The layer, or the base layer if not found
*/
getLayer(name: string): gdjs.Layer {
if (this._layers.containsKey(name)) {
return this._layers.get(name);
}
return this._layers.get('');
}
/**
* Check if a layer exists
* @param name The name of the layer
*/
hasLayer(name: string): boolean {
return this._layers.containsKey(name);
}
/**
* Add a layer.
* @param layerData The data to construct the layer
*/
addLayer(layerData: LayerData) {
this._layers.put(layerData.name, new gdjs.Layer(layerData, this));
}
/**
* Remove a layer. All {@link gdjs.RuntimeObject} on this layer will
* be moved back to the base layer.
* @param layerName The name of the layer to remove
*/
removeLayer(layerName: string) {
const allInstances = this.getAdhocListOfAllInstances();
for (let i = 0; i < allInstances.length; ++i) {
const runtimeObject = allInstances[i];
if (runtimeObject.getLayer() === layerName) {
runtimeObject.setLayer('');
}
}
this._layers.remove(layerName);
}
/**
* Change the position of a layer.
*
* @param layerName The name of the layer to reorder
* @param index The new position in the list of layers
*/
setLayerIndex(layerName: string, index: integer): void {
const layer: gdjs.Layer = this._layers.get(layerName);
if (!layer) {
return;
}
this.getRenderer().setLayerIndex(layer, index);
}
/**
* Fill the array passed as argument with the names of all layers
* @param result The array where to put the layer names
*/
getAllLayerNames(result: string[]) {
this._layers.keys(result);
}
/**
* Return the number of instances of the specified object living in the container.
* @param objectName The object name for which instances must be counted.
*/
getInstancesCountOnScene(objectName: string): integer {
const instances = this._instances.get(objectName);
if (instances) {
return instances.length;
}
return 0;
}
/**
* Update the objects positions according to their forces
*/
updateObjectsForces(): void {
for (const name in this._instances.items) {
if (this._instances.items.hasOwnProperty(name)) {
const list = this._instances.items[name];
for (let j = 0, listLen = list.length; j < listLen; ++j) {
const obj = list[j];
if (!obj.hasNoForces()) {
const averageForce = obj.getAverageForce();
const elapsedTimeInSeconds = obj.getElapsedTime() / 1000;
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
obj.updateForces(elapsedTimeInSeconds);
}
}
}
}
}
/**
* Clear any data structures to make sure memory is freed as soon as
* possible.
*/
_destroy() {
// It should not be necessary to reset these variables, but this help
// ensuring that all memory related to the container is released immediately.
this._layers = new Hashtable();
this._objects = new Hashtable();
this._instances = new Hashtable();
this._instancesCache = new Hashtable();
this._objectsCtor = new Hashtable();
this._allInstancesList = [];
this._instancesRemoved = [];
}
}
}

View File

@@ -639,7 +639,7 @@ namespace gdjs {
newObjects.forEach((newObjectData) => {
const objectName = newObjectData.name;
const newBehaviors = newObjectData.behaviors;
const runtimeObjects = runtimeScene.getObjects(objectName);
const runtimeObjects = runtimeScene.getObjects(objectName)!;
changedRuntimeBehaviors.forEach((changedRuntimeBehavior) => {
const behaviorTypeName = changedRuntimeBehavior.behaviorTypeName;
@@ -784,7 +784,7 @@ namespace gdjs {
runtimeScene.updateObject(newObjectData);
// Update existing instances
const runtimeObjects = runtimeScene.getObjects(newObjectData.name);
const runtimeObjects = runtimeScene.getObjects(newObjectData.name)!;
// Update instances state
runtimeObjects.forEach((runtimeObject) => {

View File

@@ -4,218 +4,221 @@
* This project is released under the MIT License.
*/
namespace gdjs {
// TODO EBO Replace instanceContainer by instanceContainer.
export namespace evtTools {
export namespace camera {
export const setCameraX = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
x: float,
layer: string,
cameraId: integer
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
runtimeScene.getLayer(layer).setCameraX(x, cameraId);
instanceContainer.getLayer(layer).setCameraX(x, cameraId);
};
export const setCameraY = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
y: float,
layer: string,
cameraId: integer
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
runtimeScene.getLayer(layer).setCameraY(y, cameraId);
instanceContainer.getLayer(layer).setCameraY(y, cameraId);
};
export const getCameraX = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getCameraX();
return instanceContainer.getLayer(layer).getCameraX();
};
export const getCameraY = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getCameraY();
return instanceContainer.getLayer(layer).getCameraY();
};
export const getCameraWidth = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getCameraWidth();
return instanceContainer.getLayer(layer).getCameraWidth();
};
export const getCameraHeight = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getCameraHeight();
return instanceContainer.getLayer(layer).getCameraHeight();
};
export const getCameraBorderLeft = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return (
getCameraX(runtimeScene, layer, cameraId) -
getCameraWidth(runtimeScene, layer, cameraId) / 2
getCameraX(instanceContainer, layer, cameraId) -
getCameraWidth(instanceContainer, layer, cameraId) / 2
);
};
export const getCameraBorderRight = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return (
getCameraX(runtimeScene, layer, cameraId) +
getCameraWidth(runtimeScene, layer, cameraId) / 2
getCameraX(instanceContainer, layer, cameraId) +
getCameraWidth(instanceContainer, layer, cameraId) / 2
);
};
export const getCameraBorderTop = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return (
getCameraY(runtimeScene, layer, cameraId) -
getCameraHeight(runtimeScene, layer, cameraId) / 2
getCameraY(instanceContainer, layer, cameraId) -
getCameraHeight(instanceContainer, layer, cameraId) / 2
);
};
export const getCameraBorderBottom = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return (
getCameraY(runtimeScene, layer, cameraId) +
getCameraHeight(runtimeScene, layer, cameraId) / 2
getCameraY(instanceContainer, layer, cameraId) +
getCameraHeight(instanceContainer, layer, cameraId) / 2
);
};
export const showLayer = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene.getLayer(layer).show(true);
return instanceContainer.getLayer(layer).show(true);
};
export const hideLayer = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene.getLayer(layer).show(false);
return instanceContainer.getLayer(layer).show(false);
};
export const layerIsVisible = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string
): boolean {
return (
runtimeScene.hasLayer(layer) &&
runtimeScene.getLayer(layer).isVisible()
instanceContainer.hasLayer(layer) &&
instanceContainer.getLayer(layer).isVisible()
);
};
export const setCameraRotation = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
rotation: float,
layer: string,
cameraId: integer
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene
return instanceContainer
.getLayer(layer)
.setCameraRotation(rotation, cameraId);
};
export const getCameraRotation = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getCameraRotation(cameraId);
return instanceContainer.getLayer(layer).getCameraRotation(cameraId);
};
export const getCameraZoom = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
cameraId: integer
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getCameraZoom(cameraId);
return instanceContainer.getLayer(layer).getCameraZoom(cameraId);
};
export const setCameraZoom = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
newZoom: float,
layer: string,
cameraId: integer
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene.getLayer(layer).setCameraZoom(newZoom, cameraId);
return instanceContainer
.getLayer(layer)
.setCameraZoom(newZoom, cameraId);
};
export const centerCamera = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
object: gdjs.RuntimeObject | null,
anticipateMove: boolean,
layerName: string,
cameraId: integer
) {
if (!runtimeScene.hasLayer(layerName) || object == null) {
if (!instanceContainer.hasLayer(layerName) || object == null) {
return;
}
let xOffset = 0;
@@ -223,11 +226,11 @@ namespace gdjs {
if (anticipateMove && !object.hasNoForces()) {
const objectAverageForce = object.getAverageForce();
const elapsedTimeInSeconds =
object.getElapsedTime(runtimeScene) / 1000;
object.getElapsedTime(instanceContainer) / 1000;
xOffset = objectAverageForce.getX() * elapsedTimeInSeconds;
yOffset = objectAverageForce.getY() * elapsedTimeInSeconds;
}
const layer = runtimeScene.getLayer(layerName);
const layer = instanceContainer.getLayer(layerName);
layer.setCameraX(object.getCenterXInScene() + xOffset, cameraId);
layer.setCameraY(object.getCenterYInScene() + yOffset, cameraId);
};
@@ -236,7 +239,7 @@ namespace gdjs {
* @deprecated prefer using centerCamera and clampCamera.
*/
export const centerCameraWithinLimits = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
object: gdjs.RuntimeObject | null,
left: number,
top: number,
@@ -246,9 +249,15 @@ namespace gdjs {
layerName: string,
cameraId: integer
) {
centerCamera(runtimeScene, object, anticipateMove, layerName, cameraId);
centerCamera(
instanceContainer,
object,
anticipateMove,
layerName,
cameraId
);
clampCamera(
runtimeScene,
instanceContainer,
left,
top,
right,
@@ -259,7 +268,7 @@ namespace gdjs {
};
export const clampCamera = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
left: float,
top: float,
right: float,
@@ -267,10 +276,10 @@ namespace gdjs {
layerName: string,
cameraId: integer
) {
if (!runtimeScene.hasLayer(layerName)) {
if (!instanceContainer.hasLayer(layerName)) {
return;
}
const layer = runtimeScene.getLayer(layerName);
const layer = instanceContainer.getLayer(layerName);
const cameraHalfWidth = layer.getCameraWidth(cameraId) / 2;
const cameraHalfHeight = layer.getCameraHeight(cameraId) / 2;
@@ -304,150 +313,152 @@ namespace gdjs {
/**
* Update a layer effect parameter (with a number).
* @param runtimeScene The scene
* @param instanceContainer The scene
* @param layer The name of the layer
* @param effect The name of the effect
* @param parameter The parameter to update
* @param value The new value
*/
export const setLayerEffectDoubleParameter = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
effect: string,
parameter: string,
value: float
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene
return instanceContainer
.getLayer(layer)
.setEffectDoubleParameter(effect, parameter, value);
};
/**
* Update a layer effect parameter (with a string).
* @param runtimeScene The scene
* @param instanceContainer The scene
* @param layer The name of the layer
* @param effect The name of the effect
* @param parameter The parameter to update
* @param value The new value
*/
export const setLayerEffectStringParameter = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
effect: string,
parameter: string,
value: string
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene
return instanceContainer
.getLayer(layer)
.setEffectStringParameter(effect, parameter, value);
};
/**
* Enable or disable a layer effect parameter (boolean).
* @param runtimeScene The scene
* @param instanceContainer The scene
* @param layer The name of the layer
* @param effect The name of the effect
* @param parameter The parameter to update
* @param value The new value
*/
export const setLayerEffectBooleanParameter = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
effect: string,
parameter: string,
value: boolean
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene
return instanceContainer
.getLayer(layer)
.setEffectBooleanParameter(effect, parameter, value);
};
/**
* Enable, or disable, an effect of a layer.
* @param runtimeScene The scene
* @param instanceContainer The scene
* @param layer The name of the layer
* @param effect The name of the effect
* @param enabled true to enable, false to disable.
*/
export const enableLayerEffect = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
effect: string,
enabled: boolean
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
runtimeScene.getLayer(layer).enableEffect(effect, enabled);
instanceContainer.getLayer(layer).enableEffect(effect, enabled);
};
/**
* Check if an effect is enabled.
* @param runtimeScene The scene
* @param instanceContainer The scene
* @param layer The name of the layer
* @param effect The name of the effect
* @return true if the effect is enabled, false otherwise.
*/
export const layerEffectEnabled = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
effect: string
): boolean {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return true;
}
return runtimeScene.getLayer(layer).isEffectEnabled(effect);
return instanceContainer.getLayer(layer).isEffectEnabled(effect);
};
export const setLayerTimeScale = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
timeScale: float
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene.getLayer(layer).setTimeScale(timeScale);
return instanceContainer.getLayer(layer).setTimeScale(timeScale);
};
export const getLayerTimeScale = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 1;
}
return runtimeScene.getLayer(layer).getTimeScale();
return instanceContainer.getLayer(layer).getTimeScale();
};
export const setLayerDefaultZOrder = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
defaultZOrder: integer
) {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return;
}
return runtimeScene.getLayer(layer).setDefaultZOrder(defaultZOrder);
return instanceContainer
.getLayer(layer)
.setDefaultZOrder(defaultZOrder);
};
export const getLayerDefaultZOrder = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string
): number {
if (!runtimeScene.hasLayer(layer)) {
if (!instanceContainer.hasLayer(layer)) {
return 0;
}
return runtimeScene.getLayer(layer).getDefaultZOrder();
return instanceContainer.getLayer(layer).getDefaultZOrder();
};
/**
@@ -455,13 +466,13 @@ namespace gdjs {
* @param rgbColor The color, in RGB format ("128;200;255").
*/
export const setLayerAmbientLightColor = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
layerName: string,
rgbColor: string
) {
if (
!runtimeScene.hasLayer(layerName) ||
!runtimeScene.getLayer(layerName).isLightingLayer()
!instanceContainer.hasLayer(layerName) ||
!instanceContainer.getLayer(layerName).isLightingLayer()
) {
return;
}
@@ -469,7 +480,7 @@ namespace gdjs {
if (colors.length < 3) {
return;
}
return runtimeScene
return instanceContainer
.getLayer(layerName)
.setClearColor(
parseInt(colors[0], 10),

View File

@@ -144,9 +144,12 @@ namespace gdjs {
* Return true if the specified key is pressed
*
*/
export const isKeyPressed = function (runtimeScene, key) {
export const isKeyPressed = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
key: string
) {
if (gdjs.evtTools.input.keysNameToCode.hasOwnProperty(key)) {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isKeyPressed(gdjs.evtTools.input.keysNameToCode[key]);
@@ -158,9 +161,12 @@ namespace gdjs {
* Return true if the specified key was just released
*
*/
export const wasKeyReleased = function (runtimeScene, key) {
export const wasKeyReleased = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
key: string
) {
if (gdjs.evtTools.input.keysNameToCode.hasOwnProperty(key)) {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.wasKeyReleased(gdjs.evtTools.input.keysNameToCode[key]);
@@ -171,8 +177,10 @@ namespace gdjs {
/**
* Return the name of the last key pressed in the game
*/
export const lastPressedKey = function (runtimeScene) {
const keyCode = runtimeScene
export const lastPressedKey = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
const keyCode = instanceContainer
.getGame()
.getInputManager()
.getLastPressedKey();
@@ -182,29 +190,36 @@ namespace gdjs {
return '';
};
export const anyKeyPressed = function (runtimeScene) {
return runtimeScene.getGame().getInputManager().anyKeyPressed();
export const anyKeyPressed = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return instanceContainer.getGame().getInputManager().anyKeyPressed();
};
export const anyKeyReleased = function (runtimeScene) {
return runtimeScene.getGame().getInputManager().anyKeyReleased();
export const anyKeyReleased = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return instanceContainer.getGame().getInputManager().anyKeyReleased();
};
export const isMouseButtonPressed = function (runtimeScene, button) {
export const isMouseButtonPressed = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
button: string
) {
if (button === 'Left') {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isMouseButtonPressed(0);
}
if (button === 'Right') {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isMouseButtonPressed(1);
}
if (button === 'Middle') {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isMouseButtonPressed(2);
@@ -212,21 +227,24 @@ namespace gdjs {
return false;
};
export const isMouseButtonReleased = function (runtimeScene, button) {
export const isMouseButtonReleased = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
button: string
) {
if (button === 'Left') {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isMouseButtonReleased(0);
}
if (button === 'Right') {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isMouseButtonReleased(1);
}
if (button === 'Middle') {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.isMouseButtonReleased(2);
@@ -234,129 +252,178 @@ namespace gdjs {
return false;
};
export const hideCursor = function (runtimeScene) {
runtimeScene.getRenderer().hideCursor();
export const hideCursor = function (
instanceContainer: gdjs.RuntimeScene
) {
instanceContainer.getScene().getRenderer().hideCursor();
};
export const showCursor = function (runtimeScene) {
runtimeScene.getRenderer().showCursor();
export const showCursor = function (
instanceContainer: gdjs.RuntimeScene
) {
instanceContainer.getScene().getRenderer().showCursor();
};
export const getMouseWheelDelta = function (runtimeScene) {
return runtimeScene.getGame().getInputManager().getMouseWheelDelta();
export const getMouseWheelDelta = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return instanceContainer
.getGame()
.getInputManager()
.getMouseWheelDelta();
};
export const isScrollingUp = function (runtimeScene) {
return runtimeScene.getGame().getInputManager().isScrollingUp();
export const isScrollingUp = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return instanceContainer.getGame().getInputManager().isScrollingUp();
};
export const isScrollingDown = function (runtimeScene) {
return runtimeScene.getGame().getInputManager().isScrollingDown();
export const isScrollingDown = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return instanceContainer.getGame().getInputManager().isScrollingDown();
};
export const getMouseX = function (runtimeScene, layer, camera) {
return runtimeScene
export const getMouseX = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
camera: integer
) {
const workingPoint: FloatPoint = gdjs.staticArray(
gdjs.evtTools.input.getMouseX
) as FloatPoint;
return instanceContainer
.getLayer(layer)
.convertCoords(
runtimeScene.getGame().getInputManager().getMouseX(),
runtimeScene.getGame().getInputManager().getMouseY()
instanceContainer.getGame().getInputManager().getMouseX(),
instanceContainer.getGame().getInputManager().getMouseY(),
0,
workingPoint
)[0];
};
export const getMouseY = function (runtimeScene, layer, camera) {
return runtimeScene
export const getMouseY = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
layer: string,
camera: integer
) {
const workingPoint: FloatPoint = gdjs.staticArray(
gdjs.evtTools.input.getMouseY
) as FloatPoint;
return instanceContainer
.getLayer(layer)
.convertCoords(
runtimeScene.getGame().getInputManager().getMouseX(),
runtimeScene.getGame().getInputManager().getMouseY()
instanceContainer.getGame().getInputManager().getMouseX(),
instanceContainer.getGame().getInputManager().getMouseY(),
0,
workingPoint
)[1];
};
export const isMouseInsideCanvas = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return runtimeScene.getGame().getInputManager().isMouseInsideCanvas();
return instanceContainer
.getGame()
.getInputManager()
.isMouseInsideCanvas();
};
export const _cursorIsOnObject = function (obj, runtimeScene) {
return obj.cursorOnObject(runtimeScene);
export const _cursorIsOnObject = function (
obj: gdjs.RuntimeObject,
instanceContainer: gdjs.RuntimeInstanceContainer
) {
return obj.cursorOnObject(instanceContainer);
};
export const cursorOnObject = function (
objectsLists,
runtimeScene,
accurate,
inverted
objectsLists: Hashtable<gdjs.RuntimeObject[]>,
instanceContainer: gdjs.RuntimeInstanceContainer,
accurate: boolean,
inverted: boolean
) {
return gdjs.evtTools.object.pickObjectsIf(
gdjs.evtTools.input._cursorIsOnObject,
objectsLists,
inverted,
runtimeScene
instanceContainer
);
};
export const getTouchX = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
identifier: integer,
layer: string,
camera: integer
) {
return runtimeScene
const workingPoint: FloatPoint = gdjs.staticArray(
gdjs.evtTools.input.getTouchX
) as FloatPoint;
return instanceContainer
.getLayer(layer)
.convertCoords(
runtimeScene.getGame().getInputManager().getTouchX(identifier),
runtimeScene.getGame().getInputManager().getTouchY(identifier)
instanceContainer.getGame().getInputManager().getTouchX(identifier),
instanceContainer.getGame().getInputManager().getTouchY(identifier),
0,
workingPoint
)[0];
};
export const getTouchY = (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
identifier: integer,
layer: string,
camera: integer
) => {
return runtimeScene
const workingPoint: FloatPoint = gdjs.staticArray(
gdjs.evtTools.input.getTouchY
) as FloatPoint;
return instanceContainer
.getLayer(layer)
.convertCoords(
runtimeScene.getGame().getInputManager().getTouchX(identifier),
runtimeScene.getGame().getInputManager().getTouchY(identifier)
instanceContainer.getGame().getInputManager().getTouchX(identifier),
instanceContainer.getGame().getInputManager().getTouchY(identifier),
0,
workingPoint
)[1];
};
export const hasAnyTouchStarted = (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): boolean => {
return (
runtimeScene.getGame().getInputManager().getStartedTouchIdentifiers()
.length > 0
instanceContainer
.getGame()
.getInputManager()
.getStartedTouchIdentifiers().length > 0
);
};
export const getStartedTouchCount = (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
): integer => {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.getStartedTouchIdentifiers().length;
};
export const getStartedTouchIdentifier = (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
index: integer
): integer => {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.getStartedTouchIdentifiers()[index];
};
export const hasTouchEnded = (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
identifier: integer
): boolean => {
return runtimeScene
return instanceContainer
.getGame()
.getInputManager()
.hasTouchEnded(identifier);
@@ -380,9 +447,9 @@ namespace gdjs {
* @deprecated
*/
export const popStartedTouch = function (
runtimeScene: gdjs.RuntimeScene
instanceContainer: gdjs.RuntimeInstanceContainer
) {
const startedTouchId = runtimeScene
const startedTouchId = instanceContainer
.getGame()
.getInputManager()
.popStartedTouch();
@@ -396,8 +463,10 @@ namespace gdjs {
/**
* @deprecated
*/
export const popEndedTouch = function (runtimeScene: gdjs.RuntimeScene) {
const endedTouchId = runtimeScene
export const popEndedTouch = function (
instanceContainer: gdjs.RuntimeInstanceContainer
) {
const endedTouchId = instanceContainer
.getGame()
.getInputManager()
.popEndedTouch();
@@ -408,8 +477,14 @@ namespace gdjs {
return false;
};
export const touchSimulateMouse = function (runtimeScene, enable) {
runtimeScene.getGame().getInputManager().touchSimulateMouse(enable);
export const touchSimulateMouse = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
enable: boolean
) {
instanceContainer
.getGame()
.getInputManager()
.touchSimulateMouse(enable);
};
}
}

View File

@@ -147,10 +147,10 @@ namespace gdjs {
};
export const enableMetrics = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
enable: boolean
) {
runtimeScene.getGame().enableMetrics(enable);
instanceContainer.getGame().enableMetrics(enable);
};
/**

View File

@@ -13,7 +13,7 @@ namespace gdjs {
* @param runtimeObject The object to keep in the lists
*/
export const pickOnly = function (
objectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists: ObjectsLists,
runtimeObject: gdjs.RuntimeObject
) {
for (const listName in objectsLists.items) {
@@ -36,7 +36,7 @@ namespace gdjs {
/**
* Do a test on two tables of objects so as to pick only the pair of objects for which the test is true.
*
* Note that the predicate method is not called stricly for each pair: When considering a pair of objects, if
* Note that the predicate method is not called strictly for each pair: When considering a pair of objects, if
* these objects have already been marked as picked, the predicate method won't be called again.
*
* Cost (Worst case, predicate being always false):
@@ -63,8 +63,8 @@ namespace gdjs {
object2: gdjs.RuntimeObject,
extraArg: any
) => boolean,
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists1: ObjectsLists,
objectsLists2: ObjectsLists,
inverted: boolean,
extraArg: any
) {
@@ -170,7 +170,7 @@ namespace gdjs {
*/
export const pickObjectsIf = function (
predicate: Function,
objectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists: ObjectsLists,
negatePredicate: boolean,
extraArg: any
): boolean {
@@ -219,10 +219,10 @@ namespace gdjs {
};
export const hitBoxesCollisionTest = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists1: ObjectsLists,
objectsLists2: ObjectsLists,
inverted: boolean,
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
ignoreTouchingEdges: boolean
) {
return gdjs.evtTools.object.twoListsTest(
@@ -239,8 +239,8 @@ namespace gdjs {
};
export const distanceTest = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists1: ObjectsLists,
objectsLists2: ObjectsLists,
distance: float,
inverted: boolean
) {
@@ -278,8 +278,8 @@ namespace gdjs {
};
export const movesTowardTest = function (
objectsLists1: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists2: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists1: ObjectsLists,
objectsLists2: ObjectsLists,
tolerance: float,
inverted: boolean
) {
@@ -337,7 +337,10 @@ namespace gdjs {
return true;
};
export const pickRandomObject = function (runtimeScene, objectsLists) {
export const pickRandomObject = function (
instanceContainer: gdjs.RuntimeInstanceContainer,
objectsLists: ObjectsLists
) {
// Compute one many objects we have
let objectsCount = 0;
for (let listName in objectsLists.items) {
@@ -403,7 +406,7 @@ namespace gdjs {
};
export const raycastObject = function (
objectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists: ObjectsLists,
x: float,
y: float,
angle: float,
@@ -425,7 +428,7 @@ namespace gdjs {
};
export const raycastObjectToPosition = function (
objectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
objectsLists: ObjectsLists,
x: float,
y: float,
endX: float,
@@ -481,9 +484,9 @@ namespace gdjs {
export const doCreateObjectOnScene = function (
objectsContext: EventsFunctionContext | gdjs.RuntimeScene,
objectName: string,
objectsLists: Hashtable<Array<gdjs.RuntimeObject>>,
x,
y,
objectsLists: ObjectsLists,
x: float,
y: float,
layerName: string
) {
// objectsContext will either be the gdjs.RuntimeScene or, in an events function, the
@@ -509,19 +512,19 @@ namespace gdjs {
* Allows events to create a new object on a scene.
*/
export const createObjectOnScene = function (
objectsContext,
objectsLists,
x,
y,
layer
objectsContext: EventsFunctionContext | gdjs.RuntimeScene,
objectsLists: ObjectsLists,
x: float,
y: float,
layerName: string
) {
gdjs.evtTools.object.doCreateObjectOnScene(
objectsContext,
objectsLists.firstKey(),
objectsLists.firstKey() as string,
objectsLists,
x,
y,
layer
layerName
);
};
@@ -529,12 +532,12 @@ namespace gdjs {
* Allows events to create a new object on a scene.
*/
export const createObjectFromGroupOnScene = function (
objectsContext,
objectsLists,
objectName,
x,
y,
layer
objectsContext: EventsFunctionContext | gdjs.RuntimeScene,
objectsLists: ObjectsLists,
objectName: string,
x: float,
y: float,
layerName: string
) {
gdjs.evtTools.object.doCreateObjectOnScene(
objectsContext,
@@ -542,7 +545,7 @@ namespace gdjs {
objectsLists,
x,
y,
layer
layerName
);
};

View File

@@ -6,40 +6,54 @@
namespace gdjs {
export namespace evtTools {
export namespace runtimeScene {
export const sceneJustBegins = function (runtimeScene) {
return runtimeScene.getTimeManager().isFirstFrame();
export const sceneJustBegins = function (
runtimeScene: gdjs.RuntimeScene
) {
return runtimeScene.getScene().getTimeManager().isFirstFrame();
};
export const sceneJustResumed = function (runtimeScene) {
return runtimeScene.sceneJustResumed();
export const sceneJustResumed = function (
runtimeScene: gdjs.RuntimeScene
) {
return runtimeScene.getScene().sceneJustResumed();
};
export const getSceneName = function (runtimeScene) {
return runtimeScene.getName();
export const getSceneName = function (runtimeScene: gdjs.RuntimeScene) {
return runtimeScene.getScene().getName();
};
export const setBackgroundColor = function (runtimeScene, rgbColor) {
export const setBackgroundColor = function (
runtimeScene: gdjs.RuntimeScene,
rgbColor: string
) {
const colors = rgbColor.split(';');
if (colors.length < 3) {
return;
}
runtimeScene.setBackgroundColor(
parseInt(colors[0]),
parseInt(colors[1]),
parseInt(colors[2])
);
runtimeScene
.getScene()
.setBackgroundColor(
parseInt(colors[0]),
parseInt(colors[1]),
parseInt(colors[2])
);
};
export const getElapsedTimeInSeconds = function (runtimeScene) {
return runtimeScene.getTimeManager().getElapsedTime() / 1000;
export const getElapsedTimeInSeconds = function (
runtimeScene: gdjs.RuntimeScene
) {
return runtimeScene.getScene().getTimeManager().getElapsedTime() / 1000;
};
export const setTimeScale = function (runtimeScene, timeScale) {
return runtimeScene.getTimeManager().setTimeScale(timeScale);
export const setTimeScale = function (
runtimeScene: gdjs.RuntimeScene,
timeScale: float
) {
return runtimeScene.getScene().getTimeManager().setTimeScale(timeScale);
};
export const getTimeScale = function (runtimeScene) {
return runtimeScene.getTimeManager().getTimeScale();
export const getTimeScale = function (runtimeScene: gdjs.RuntimeScene) {
return runtimeScene.getScene().getTimeManager().getTimeScale();
};
/**
@@ -57,7 +71,7 @@ namespace gdjs {
timeInSeconds: float,
timerName: string
) {
const timeManager = runtimeScene.getTimeManager();
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
timeManager.addTimer(timerName);
return false;
@@ -67,16 +81,22 @@ namespace gdjs {
);
};
export const timerPaused = function (runtimeScene, timerName) {
const timeManager = runtimeScene.getTimeManager();
export const timerPaused = function (
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
return false;
}
return timeManager.getTimer(timerName).isPaused();
};
export const resetTimer = function (runtimeScene, timerName) {
const timeManager = runtimeScene.getTimeManager();
export const resetTimer = function (
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
timeManager.addTimer(timerName);
} else {
@@ -84,24 +104,33 @@ namespace gdjs {
}
};
export const pauseTimer = function (runtimeScene, timerName) {
const timeManager = runtimeScene.getTimeManager();
export const pauseTimer = function (
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
timeManager.addTimer(timerName);
}
timeManager.getTimer(timerName).setPaused(true);
};
export const unpauseTimer = function (runtimeScene, timerName) {
const timeManager = runtimeScene.getTimeManager();
export const unpauseTimer = function (
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
timeManager.addTimer(timerName);
}
return timeManager.getTimer(timerName).setPaused(false);
};
export const removeTimer = function (runtimeScene, timerName) {
const timeManager = runtimeScene.getTimeManager();
export const removeTimer = function (
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getScene().getTimeManager();
timeManager.removeTimer(timerName);
};
@@ -116,6 +145,7 @@ namespace gdjs {
update(runtimeScene: RuntimeScene): boolean {
this.timeElapsedOnScene += runtimeScene
.getScene()
.getTimeManager()
.getElapsedTime();
return this.timeElapsedOnScene >= this.duration;
@@ -139,7 +169,7 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getTimeManager();
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
return 0;
}
@@ -158,7 +188,7 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
timerName: string
) {
const timeManager = runtimeScene.getTimeManager();
const timeManager = runtimeScene.getScene().getTimeManager();
if (!timeManager.hasTimer(timerName)) {
return Number.NaN;
}
@@ -168,7 +198,9 @@ namespace gdjs {
export const getTimeFromStartInSeconds = function (
runtimeScene: gdjs.RuntimeScene
) {
return runtimeScene.getTimeManager().getTimeFromStart() / 1000;
return (
runtimeScene.getScene().getTimeManager().getTimeFromStart() / 1000
);
};
export const getTime = function (
@@ -211,34 +243,42 @@ namespace gdjs {
if (!runtimeScene.getGame().getSceneData(newSceneName)) {
return;
}
runtimeScene.requestChange(
clearOthers
? gdjs.SceneChangeRequest.CLEAR_SCENES
: gdjs.SceneChangeRequest.REPLACE_SCENE,
newSceneName
);
runtimeScene
.getScene()
.requestChange(
clearOthers
? gdjs.SceneChangeRequest.CLEAR_SCENES
: gdjs.SceneChangeRequest.REPLACE_SCENE,
newSceneName
);
};
export const pushScene = function (runtimeScene, newSceneName) {
export const pushScene = function (
runtimeScene: gdjs.RuntimeScene,
newSceneName: string
) {
if (!runtimeScene.getGame().getSceneData(newSceneName)) {
return;
}
runtimeScene.requestChange(
gdjs.SceneChangeRequest.PUSH_SCENE,
newSceneName
);
runtimeScene
.getScene()
.requestChange(gdjs.SceneChangeRequest.PUSH_SCENE, newSceneName);
};
export const popScene = function (runtimeScene) {
runtimeScene.requestChange(gdjs.SceneChangeRequest.POP_SCENE);
export const popScene = function (runtimeScene: gdjs.RuntimeScene) {
runtimeScene
.getScene()
.requestChange(gdjs.SceneChangeRequest.POP_SCENE);
};
export const stopGame = function (runtimeScene) {
runtimeScene.requestChange(gdjs.SceneChangeRequest.STOP_GAME);
export const stopGame = function (runtimeScene: gdjs.RuntimeScene) {
runtimeScene
.getScene()
.requestChange(gdjs.SceneChangeRequest.STOP_GAME);
};
export const createObjectsFromExternalLayout = function (
scene: gdjs.RuntimeScene,
scene: gdjs.RuntimeInstanceContainer,
externalLayout: string,
xPos: float,
yPos: float
@@ -252,7 +292,7 @@ namespace gdjs {
// trackByPersistentUuid is set to false as we don't want external layouts
// instantiated at runtime to be hot-reloaded.
scene.createObjectsFrom(
scene.getScene().createObjectsFrom(
externalLayoutData.instances,
xPos,
yPos,

View File

@@ -11,20 +11,20 @@ namespace gdjs {
export const getGlobalVolume = function (
runtimeScene: gdjs.RuntimeScene
): float {
return runtimeScene.getSoundManager().getGlobalVolume();
return runtimeScene.getScene().getSoundManager().getGlobalVolume();
};
export const setGlobalVolume = function (
runtimeScene: gdjs.RuntimeScene,
globalVolume: float
): void {
runtimeScene.getSoundManager().setGlobalVolume(globalVolume);
runtimeScene.getScene().getSoundManager().setGlobalVolume(globalVolume);
};
export const unloadAllAudio = function (
runtimeScene: gdjs.RuntimeScene
): void {
runtimeScene.getSoundManager().unloadAll();
runtimeScene.getScene().getSoundManager().unloadAll();
};
// Sounds:
@@ -36,6 +36,7 @@ namespace gdjs {
pitch: float
): void {
runtimeScene
.getScene()
.getSoundManager()
.playSound(soundFile, loop, volume, pitch);
};
@@ -49,6 +50,7 @@ namespace gdjs {
pitch: float
): void {
runtimeScene
.getScene()
.getSoundManager()
.playSoundOnChannel(soundFile, channel, loop, volume, pitch);
};
@@ -57,7 +59,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): void {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) sound.stop();
else {
logger.error(`Cannot stop non-existing sound on channel ${channel}.`);
@@ -68,7 +73,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): void {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) sound.pause();
else {
logger.error(
@@ -81,7 +89,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): void {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) {
if (!sound.playing()) sound.play();
} else {
@@ -95,7 +106,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): boolean {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
return sound ? sound.playing() : false;
};
@@ -103,7 +117,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): boolean {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) return sound.paused();
else {
logger.error(
@@ -117,7 +134,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): boolean {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) return sound.stopped();
else {
logger.error(
@@ -131,7 +151,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): float {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) return sound.getVolume() * 100;
else {
logger.error(
@@ -146,7 +169,10 @@ namespace gdjs {
channel: integer,
volume: float
): void {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) sound.setVolume(volume / 100);
else {
logger.error(
@@ -159,7 +185,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): float {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) return sound.getSeek();
else {
logger.error(
@@ -174,7 +203,10 @@ namespace gdjs {
channel: integer,
playingOffset: float
): void {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) sound.setSeek(playingOffset);
else {
logger.error(
@@ -187,7 +219,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): float {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) return sound.getRate();
else {
logger.error(
@@ -202,7 +237,10 @@ namespace gdjs {
channel: integer,
pitch: float
): void {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) sound.setRate(pitch);
else {
logger.error(
@@ -216,6 +254,7 @@ namespace gdjs {
soundFile: string
) =>
runtimeScene
.getScene()
.getSoundManager()
.loadAudio(soundFile, /* isMusic= */ false);
@@ -224,6 +263,7 @@ namespace gdjs {
soundFile: string
) =>
runtimeScene
.getScene()
.getSoundManager()
.unloadAudio(soundFile, /* isMusic= */ false);
@@ -236,6 +276,7 @@ namespace gdjs {
pitch: float
): void {
runtimeScene
.getScene()
.getSoundManager()
.playMusic(soundFile, loop, volume, pitch);
};
@@ -249,6 +290,7 @@ namespace gdjs {
pitch: float
): void {
runtimeScene
.getScene()
.getSoundManager()
.playMusicOnChannel(soundFile, channel, loop, volume, pitch);
};
@@ -257,7 +299,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): void {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) music.stop();
else {
logger.error(
@@ -270,7 +315,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): void {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) music.pause();
else {
logger.error(
@@ -283,7 +331,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): void {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) {
if (!music.playing()) music.play();
} else {
@@ -297,7 +348,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): boolean {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
return music ? music.playing() : false;
};
@@ -305,7 +359,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): boolean {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) return music.paused();
else {
logger.error(
@@ -319,7 +376,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): boolean {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) return music.stopped();
else {
logger.error(
@@ -333,7 +393,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): float {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) return music.getVolume() * 100;
else {
logger.error(
@@ -348,7 +411,10 @@ namespace gdjs {
channel: integer,
volume: float
): void {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) music.setVolume(volume / 100);
else {
logger.error(
@@ -361,7 +427,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): float {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) return music.getSeek();
else {
logger.error(
@@ -376,7 +445,10 @@ namespace gdjs {
channel: integer,
playingOffset: float
): void {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) music.setSeek(playingOffset);
else {
logger.error(
@@ -389,7 +461,10 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
channel: integer
): float {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) return music.getRate();
else {
logger.error(
@@ -404,7 +479,10 @@ namespace gdjs {
channel: integer,
pitch: float
): void {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) music.setRate(pitch);
else {
logger.error(
@@ -418,6 +496,7 @@ namespace gdjs {
soundFile: string
) =>
runtimeScene
.getScene()
.getSoundManager()
.loadAudio(soundFile, /* isMusic= */ true);
@@ -426,6 +505,7 @@ namespace gdjs {
soundFile: string
) =>
runtimeScene
.getScene()
.getSoundManager()
.unloadAudio(soundFile, /* isMusic= */ true);
@@ -435,7 +515,10 @@ namespace gdjs {
toVolume: float,
timeOfFade: float /* in seconds */
) => {
const sound = runtimeScene.getSoundManager().getSoundOnChannel(channel);
const sound = runtimeScene
.getScene()
.getSoundManager()
.getSoundOnChannel(channel);
if (sound) {
sound.fade(sound.getVolume(), toVolume / 100, timeOfFade * 1000);
} else {
@@ -450,7 +533,10 @@ namespace gdjs {
toVolume: float,
timeOfFade: float /* in seconds */
) => {
const music = runtimeScene.getSoundManager().getMusicOnChannel(channel);
const music = runtimeScene
.getScene()
.getSoundManager()
.getMusicOnChannel(channel);
if (music) {
music.fade(music.getVolume(), toVolume / 100, timeOfFade * 1000);
} else {

View File

@@ -224,7 +224,7 @@ namespace gdjs {
export const readNumberFromJSONFile = (
name: string,
elementPath: string,
runtimeScene: gdjs.RuntimeScene | null,
instanceContainer: gdjs.RuntimeInstanceContainer | null,
variable: gdjs.Variable
) => {
return loadObject(name, (jsObject) => {
@@ -250,7 +250,7 @@ namespace gdjs {
export const readStringFromJSONFile = (
name: string,
elementPath: string,
runtimeScene: gdjs.RuntimeScene | null,
instanceContainer: gdjs.RuntimeInstanceContainer | null,
variable: gdjs.Variable
) => {
return loadObject(name, (jsObject) => {

View File

@@ -26,21 +26,25 @@ namespace gdjs {
/**
* Return the uppercased version of the string.
*/
export const toUpperCase = function (str) {
export const toUpperCase = function (str: string) {
return str.toUpperCase();
};
/**
* Return the lowercased version of the string.
*/
export const toLowerCase = function (str) {
export const toLowerCase = function (str: string) {
return str.toLowerCase();
};
/**
* Return a new string containing the substring of the original string.
*/
export const subStr = function (str, start, len) {
export const subStr = function (
str: string,
start: integer,
len: integer
) {
if (start < str.length && start >= 0) {
return str.substr(start, len);
}
@@ -50,7 +54,7 @@ namespace gdjs {
/**
* Return a new string containing the character at the specified position.
*/
export const strAt = function (str, start) {
export const strAt = function (str: string, start: integer) {
if (start < str.length && start >= 0) {
return str.substr(start, 1);
}
@@ -60,7 +64,7 @@ namespace gdjs {
/**
* Return the string repeated.
*/
export const strRepeat = function (str, count) {
export const strRepeat = function (str: string, count: integer) {
let result = '';
for (let i = 0; i < count; i++) {
result += str;
@@ -71,21 +75,21 @@ namespace gdjs {
/**
* Return the length of the string
*/
export const strLen = function (str) {
export const strLen = function (str: string) {
return str.length;
};
/**
* Search the first occurence in a string (return the position of the result, from the beginning of the string, or -1 if not found)
* Search the first occurrence in a string (return the position of the result, from the beginning of the string, or -1 if not found)
*/
export const strFind = function (str, what) {
export const strFind = function (str: string, what: string) {
return str.indexOf(what);
};
/**
* Search the last occurence in a string (return the position of the result, from the beginning of the string, or -1 if not found)
* Search the last occurrence in a string (return the position of the result, from the beginning of the string, or -1 if not found)
*/
export const strFindLast = function (str, what) {
export const strFindLast = function (str: string, what: string) {
return str.lastIndexOf(what);
};
@@ -95,16 +99,24 @@ namespace gdjs {
export const strRFind = gdjs.evtTools.string.strFindLast;
/**
* Search the first occurence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found)
* Search the first occurrence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found)
*/
export const strFindFrom = function (str, what, pos) {
export const strFindFrom = function (
str: string,
what: string,
pos: integer
) {
return str.indexOf(what, pos);
};
/**
* Search the last occurence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found)
* Search the last occurrence in a string, starting from a specified position (return the position of the result, from the beginning of the string, or -1 if not found)
*/
export const strFindLastFrom = function (str, what, pos) {
export const strFindLastFrom = function (
str: string,
what: string,
pos: integer
) {
return str.lastIndexOf(what, pos);
};

View File

@@ -76,7 +76,7 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
variableName: string
): boolean {
return runtimeScene.getVariables().has(variableName);
return runtimeScene.getScene().getVariables().has(variableName);
};
/**
@@ -85,10 +85,10 @@ namespace gdjs {
* @private
*/
export const globalVariableExists = function (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
variableName: string
): boolean {
return runtimeScene.getGame().getVariables().has(variableName);
return instanceContainer.getGame().getVariables().has(variableName);
};
/**

View File

@@ -26,7 +26,7 @@ namespace gdjs {
type RuntimeSceneCallback = (runtimeScene: gdjs.RuntimeScene) => void;
type RuntimeSceneRuntimeObjectCallback = (
runtimeScene: gdjs.RuntimeScene,
instanceContainer: gdjs.RuntimeInstanceContainer,
runtimeObject: gdjs.RuntimeObject
) => void;

View File

@@ -5,7 +5,7 @@
*/
namespace gdjs {
/**
* Represents a layer of a scene, used to display objects.
* Represents a layer of a container, used to display objects.
*
* Viewports and multiple cameras are not supported.
*/
@@ -19,10 +19,8 @@ namespace gdjs {
_initialEffectsData: Array<EffectData>;
_cameraX: float;
_cameraY: float;
_cachedGameResolutionWidth: integer;
_cachedGameResolutionHeight: integer;
_runtimeScene: gdjs.RuntimeScene;
_runtimeScene: gdjs.RuntimeInstanceContainer;
_effectsManager: gdjs.EffectsManager;
// Lighting layer properties.
@@ -31,26 +29,23 @@ namespace gdjs {
_clearColor: Array<integer>;
_rendererEffects: Record<string, PixiFiltersTools.Filter> = {};
_renderer: LayerRenderer;
_renderer: gdjs.LayerRenderer;
/**
* @param layerData The data used to initialize the layer
* @param runtimeScene The scene in which the layer is used
* @param instanceContainer The container in which the layer is used
*/
constructor(layerData: LayerData, runtimeScene: gdjs.RuntimeScene) {
constructor(
layerData: LayerData,
instanceContainer: gdjs.RuntimeInstanceContainer
) {
this._name = layerData.name;
this._hidden = !layerData.visibility;
this._initialEffectsData = layerData.effects || [];
this._cameraX = runtimeScene.getGame().getGameResolutionWidth() / 2;
this._cameraY = runtimeScene.getGame().getGameResolutionHeight() / 2;
this._cachedGameResolutionWidth = runtimeScene
.getGame()
.getGameResolutionWidth();
this._cachedGameResolutionHeight = runtimeScene
.getGame()
.getGameResolutionHeight();
this._runtimeScene = runtimeScene;
this._effectsManager = runtimeScene.getGame().getEffectsManager();
this._runtimeScene = instanceContainer;
this._cameraX = this.getWidth() / 2;
this._cameraY = this.getHeight() / 2;
this._effectsManager = instanceContainer.getGame().getEffectsManager();
this._isLightingLayer = layerData.isLightingLayer;
this._followBaseLayerCamera = layerData.followBaseLayerCamera;
this._clearColor = [
@@ -59,7 +54,11 @@ namespace gdjs {
layerData.ambientLightColorB / 255,
1.0,
];
this._renderer = new gdjs.LayerRenderer(this, runtimeScene.getRenderer());
this._renderer = new gdjs.LayerRenderer(
this,
instanceContainer.getRenderer(),
instanceContainer.getGame().getRenderer().getPIXIRenderer()
);
this.show(!this._hidden);
for (let i = 0; i < layerData.effects.length; ++i) {
this.addEffect(layerData.effects[i]);
@@ -90,16 +89,10 @@ namespace gdjs {
* Called by the RuntimeScene whenever the game resolution size is changed.
* Updates the layer width/height and position.
*/
onGameResolutionResized(): void {
const oldGameResolutionWidth = this._cachedGameResolutionWidth;
const oldGameResolutionHeight = this._cachedGameResolutionHeight;
this._cachedGameResolutionWidth = this._runtimeScene
.getGame()
.getGameResolutionWidth();
this._cachedGameResolutionHeight = this._runtimeScene
.getGame()
.getGameResolutionHeight();
onGameResolutionResized(
oldGameResolutionOriginX: float,
oldGameResolutionOriginY: float
): void {
// Adapt position of the camera center as:
// * Most cameras following a player/object on the scene will be updating this
// in events anyway.
@@ -108,24 +101,24 @@ namespace gdjs {
// move from its initial position (which is centered in the screen) - and anchor
// behavior would behave counterintuitively.
this._cameraX +=
(this._cachedGameResolutionWidth - oldGameResolutionWidth) / 2;
this._runtimeScene.getViewportOriginX() - oldGameResolutionOriginX;
this._cameraY +=
(this._cachedGameResolutionHeight - oldGameResolutionHeight) / 2;
this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY;
this._renderer.updatePosition();
}
/**
* Returns the scene the layer belongs to
* @returns the scene the layer belongs to
* Returns the scene the layer belongs to directly or indirectly
* @returns the scene the layer belongs to directly or indirectly
*/
getRuntimeScene(): gdjs.RuntimeScene {
return this._runtimeScene;
return this._runtimeScene.getScene();
}
/**
* Called at each frame, after events are run and before rendering.
*/
updatePreRender(runtimeScene?: gdjs.RuntimeScene): void {
updatePreRender(instanceContainer?: gdjs.RuntimeInstanceContainer): void {
if (this._followBaseLayerCamera) {
this.followBaseLayer();
}
@@ -148,6 +141,7 @@ namespace gdjs {
* @return The x position of the camera
*/
getCameraX(cameraId?: integer): float {
this._forceDimensionUpdate();
return this._cameraX;
}
@@ -158,6 +152,7 @@ namespace gdjs {
* @return The y position of the camera
*/
getCameraY(cameraId?: integer): float {
this._forceDimensionUpdate();
return this._cameraY;
}
@@ -168,6 +163,7 @@ namespace gdjs {
* @param cameraId The camera number. Currently ignored.
*/
setCameraX(x: float, cameraId?: integer): void {
this._forceDimensionUpdate();
this._cameraX = x;
this._renderer.updatePosition();
}
@@ -179,6 +175,7 @@ namespace gdjs {
* @param cameraId The camera number. Currently ignored.
*/
setCameraY(y: float, cameraId?: integer): void {
this._forceDimensionUpdate();
this._cameraY = y;
this._renderer.updatePosition();
}
@@ -191,7 +188,7 @@ namespace gdjs {
* @return The width of the camera
*/
getCameraWidth(cameraId?: integer): float {
return (+this._cachedGameResolutionWidth * 1) / this._zoomFactor;
return this.getWidth() / this._zoomFactor;
}
/**
@@ -202,7 +199,7 @@ namespace gdjs {
* @return The height of the camera
*/
getCameraHeight(cameraId?: integer): float {
return (+this._cachedGameResolutionHeight * 1) / this._zoomFactor;
return this.getHeight() / this._zoomFactor;
}
/**
@@ -268,15 +265,51 @@ namespace gdjs {
/**
* Convert a point from the canvas coordinates (for example,
* the mouse position) to the scene coordinates.
* the mouse position) to the container coordinates.
*
* @param x The x position, in canvas coordinates.
* @param y The y position, in canvas coordinates.
* @param cameraId The camera number. Currently ignored.
* @param result The point instance that is used to return the result.
*/
convertCoords(x: float, y: float, cameraId?: integer): FloatPoint {
x -= this._cachedGameResolutionWidth / 2;
y -= this._cachedGameResolutionHeight / 2;
convertCoords(
x: float,
y: float,
cameraId: integer = 0,
result: FloatPoint
): FloatPoint {
// The result parameter used to be optional.
let position = result || [0, 0];
// TODO EBO use an AffineTransformation to avoid chained calls.
position = this._runtimeScene.convertCoords(x, y, position);
return this.applyLayerInverseTransformation(
position[0],
position[1],
cameraId,
position
);
}
/**
* Return an array containing the coordinates of the point passed as parameter
* in layer local coordinates (as opposed to the parent coordinate coordinates).
*
* All transformations (scale, rotation) are supported.
*
* @param x The X position of the point, in parent coordinates.
* @param y The Y position of the point, in parent coordinates.
* @param result Array that will be updated with the result
* @param result The point instance that is used to return the result.
* (x and y position of the point in layer coordinates).
*/
applyLayerInverseTransformation(
x: float,
y: float,
cameraId: integer,
result: FloatPoint
): FloatPoint {
x -= this._runtimeScene.getViewportOriginX();
y -= this._runtimeScene.getViewportOriginY();
x /= Math.abs(this._zoomFactor);
y /= Math.abs(this._zoomFactor);
@@ -287,18 +320,54 @@ namespace gdjs {
const sinValue = Math.sin(angleInRadians);
x = cosValue * x - sinValue * y;
y = sinValue * tmp + cosValue * y;
return [x + this.getCameraX(cameraId), y + this.getCameraY(cameraId)];
result[0] = x + this.getCameraX(cameraId);
result[1] = y + this.getCameraY(cameraId);
return result;
}
/**
* Convert a point from the scene coordinates (for example,
* Convert a point from the container coordinates (for example,
* an object position) to the canvas coordinates.
*
* @param x The x position, in scene coordinates.
* @param y The y position, in scene coordinates.
* @param x The x position, in container coordinates.
* @param y The y position, in container coordinates.
* @param cameraId The camera number. Currently ignored.
* @param result The point instance that is used to return the result.
*/
convertInverseCoords(x: float, y: float, cameraId?: integer): FloatPoint {
convertInverseCoords(
x: float,
y: float,
cameraId: integer = 0,
result: FloatPoint
): FloatPoint {
let position = result || [0, 0];
// TODO EBO use an AffineTransformation to avoid chained calls.
this.applyLayerTransformation(x, y, cameraId, position);
return this._runtimeScene.convertInverseCoords(
position[0],
position[1],
position
);
}
/**
* Return an array containing the coordinates of the point passed as parameter
* in parent coordinate coordinates (as opposed to the layer local coordinates).
*
* All transformations (scale, rotation) are supported.
*
* @param x The X position of the point, in layer coordinates.
* @param y The Y position of the point, in layer coordinates.
* @param result Array that will be updated with the result
* (x and y position of the point in parent coordinates).
*/
applyLayerTransformation(
x: float,
y: float,
cameraId: integer,
result: FloatPoint
): FloatPoint {
x -= this.getCameraX(cameraId);
y -= this.getCameraY(cameraId);
@@ -311,18 +380,31 @@ namespace gdjs {
y = sinValue * tmp + cosValue * y;
x *= Math.abs(this._zoomFactor);
y *= Math.abs(this._zoomFactor);
return [
x + this._cachedGameResolutionWidth / 2,
y + this._cachedGameResolutionHeight / 2,
];
x += this._runtimeScene.getViewportOriginX();
y += this._runtimeScene.getViewportOriginY();
result[0] = x;
result[1] = y;
return result;
}
getWidth(): float {
return this._cachedGameResolutionWidth;
return this._runtimeScene.getViewportWidth();
}
getHeight(): float {
return this._cachedGameResolutionHeight;
return this._runtimeScene.getViewportHeight();
}
/**
* This ensure that the viewport dimensions are up to date.
*
* It's needed because custom objects dimensions are only updated on
* demand for efficiency reasons.
*/
private _forceDimensionUpdate(): void {
this.getWidth();
this.getHeight();
}
/**
@@ -464,10 +546,12 @@ namespace gdjs {
/**
* Return the time elapsed since the last frame,
* in milliseconds, for objects on the layer.
*
* @param instanceContainer The instance container the layer belongs to (deprecated - can be omitted).
*/
getElapsedTime(runtimeScene?: RuntimeScene): float {
runtimeScene = runtimeScene || this._runtimeScene;
return runtimeScene.getTimeManager().getElapsedTime() * this._timeScale;
getElapsedTime(instanceContainer?: gdjs.RuntimeInstanceContainer): float {
const container = instanceContainer || this._runtimeScene;
return container.getElapsedTime() * this._timeScale;
}
/**

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