Compare commits

...

115 Commits

Author SHA1 Message Date
Florian Rival
23d64aa676 Fix crash/error when exporting to Windows/macOS/Linux 2020-09-01 19:04:32 +01:00
The Gem Dev
532b86ac58 Added link to GDevelop reddit page on start tab (#1935) 2020-09-01 15:05:09 +02:00
Florian Rival
e1bf859ff4 Fix tween behavior not working with BB Text object color 2020-08-31 16:04:22 +01:00
Florian Rival
1ad20ec6c9 Fix Light tests
Don't show in changelog
2020-08-31 13:59:06 +01:00
Florian Rival
b5990ecbe3 Fix tween behavior sometimes not working properly 2020-08-31 13:29:03 +01:00
Florian Rival
f2287dd1ef Fix tween behavior not working with Light object color 2020-08-31 13:27:51 +01:00
Florian Rival
a8714b8522 Bump newIDE version 2020-08-31 00:35:57 +01:00
Florian Rival
ddf0ba7efd Update electron-builder to avoid packaging issues on macOS Catalina
Don't show in changelog
2020-08-31 00:35:25 +01:00
Florian Rival
9e8491420d Add example for the Light objects 2020-08-31 00:30:04 +01:00
Florian Rival
075d918619 Update translations 2020-08-30 22:31:34 +01:00
Florian Rival
b68dfe040e Fix warning 2020-08-30 21:34:52 +01:00
Florian Rival
053f4a68df Add link to more tutorials by Wishforge Games (https://www.youtube.com/channel/UCxsQHU5SwYtO6uc1jiLdvrg) 2020-08-30 21:34:38 +01:00
Florian Rival
216fd30145 Remove a useless translation marker
Variables can't be translated magically like this :)

Don't show in changelog
2020-08-30 20:10:58 +01:00
Florian Rival
fda75e0475 Improve changelog extractor
Don't show in changelog
2020-08-30 12:28:37 +01:00
Florian Rival
7fe057c180 Improve alerts display on small screens 2020-08-30 00:27:03 +01:00
Florian Rival
5d091c0a87 Add multiple light objects fixes (#1929)
Don't show in changelog
2020-08-29 18:52:36 +01:00
The Gem Dev
319cea428e Update the AdMob icon (#1953) 2020-08-29 18:10:45 +01:00
Florian Rival
0ac2ef7892 Allow extensions to require @pixi/... modules in the IDE
Don't show in the changelog
2020-08-29 17:16:51 +01:00
Aurélien Vivet
fd6b9be49c Add a section for developers in the release notes (#1892)
Only show in the developer changelog
2020-08-28 19:06:42 +01:00
Harsimran Virk
2d6f0fad90 Added a polygon to replace the use of hitbox in raycasting algo. 2020-08-26 15:59:27 +05:30
Florian Rival
35f019afa8 Add "Game Feel Demo" by Sleeper Games
* See these game feel examples in a complete game powered by GDevelop: http://hyperspacedogfights.com/
2020-08-26 09:29:41 +01:00
Florian Rival
f7453a6a1d Update yarn.lock 2020-08-26 00:07:46 +01:00
Nilay Majorwar
89570505e6 Add support for customizable keyboard shortcuts (#1938)
* In the preferences, browse the list of existing shortcuts. Try the existing one to speed up your creation worflow! For example, press *F5* to launch a preview.
* For each command available in the command palette, a shortcut can be added, changed or removed.
2020-08-25 23:54:24 +01:00
Florian Rival
082318d7e4 Fix vibration not working in exported Android games.
Fix #1922
2020-08-24 23:13:48 +01:00
Florian Rival
59c9812208 Only add the AdMob plugin when the AdMob App Id is set (#1940)
Don't show in changelog
2020-08-24 22:54:51 +01:00
Florian Rival
62117e42d9 Make the action to send a web request "asynchronous" (not blocking the game execution) (#1937)
* The result from the request is stored in the specified variable (and any error in a second variable)
* This avoids blocking the game execution while the request is being made, and allow multiple requests to be made at the same time.
2020-08-24 20:51:37 +01:00
Arthur Pacaud
cafa0d512f Allow extensions to declare dependencies (npm, cordova...) and custom properties in the project (#1717)
Only show in developer changelog
2020-08-24 20:51:10 +01:00
Harsimran Virk
136964053b Renamed variables and changed doc 2020-08-21 12:52:08 +05:30
Harsimran Virk
6beea7bfaf Changed default ambient light color to (200, 200, 200) 2020-08-20 19:39:11 +05:30
Harsimran Virk
ab6999a16c Working expanded bounding box. 2020-08-19 19:06:10 +05:30
Harsimran Virk
937fd1888a Fixed a weird bug related to debug graphics. 2020-08-19 18:12:49 +05:30
Harsimran Virk
755c72c0bf Fixed undefined handling of light obstacle manager. Moved texture handling in renderer. 2020-08-19 16:20:36 +05:30
Florian Rival
20392d6a79 Update newIDE/electron-app/app/package-lock.json 2020-08-19 09:44:56 +02:00
Florian Rival
0a2033db3d Improve changelog extractor with more ignored messages
Don't show in changelog
2020-08-19 09:43:53 +02:00
Florian Rival
6c0fe0359a Add experimental Peer-to-Peer communication extension (#1842)
* This allows to transmit messages on the network to different remote players, enabling simple multiplayer games.
* Read the [documentation on the wiki](http://wiki.compilgames.net/doku.php/gdevelop5/all-features/p2p) to understand how it works, limitations and capabilities of the extension. In particular, for released games, it's recommended that you host a *broker server* allowing game instances to be discovered and connected.
2020-08-18 22:38:45 +02:00
Florian Rival
66ed1110d2 Update JsExtension.js 2020-08-18 22:36:11 +02:00
Florian Rival
5badb27b35 Update light descriptions and default color
Don't show in changelog
2020-08-17 18:23:10 +02:00
Harsimran Singh Virk
b7902bb141 Add support for dynamic 2D lights (#1881)
* This adds a **Light** object that can be added on the scene, with a customizable color and radius.
* Add the **Light Obstacle** behavior to the object that must acts as obstacle (walls, etc...) to the lights.
* You can customize the ambient color of the rest of the scene from almost white (useful to show light shadows) to entirely black (useful for horror/exploration games) or any color.
* Use effects on the "Lighting" layer like "Kawase Blur" to achieve soft shadows.
2020-08-17 17:48:26 +02:00
Aurélien Vivet
b5b3abd155 Upgrade to Pixi 5.3.3 (#1925) 2020-08-17 17:15:20 +02:00
Aurélien Vivet
06b299c4a2 Delete the artefact Thumbs.db from Windows OS
Previously added in
https://github.com/4ian/GDevelop/pull/1858

Don't show in changelog
2020-08-17 11:47:06 +02:00
The Gem Dev
e42d2cbc6d Add Solarized Dark theme (#1858)
* A new Dark theme based on [Solarized color scheme](https://ethanschoonover.com/solarized/).
2020-08-13 12:06:37 +02:00
Arthur Pacaud
454159db3f [Final Fixes] Add discord rich presence (#1915)
* Add Discord rich presence to GDevelop

* Prettier

* Add error handling when discord not installed

* Try to fix flow typing

* Fix typo

* Fix typo not fixed in last commit

* Switch to hook in mainframe

And apply other review instructions

* fix flow

* Final Fixes
2020-08-12 21:25:32 +02:00
Florian Rival
4324526689 Fix behavior not destroyed in a live preview when the behavior is removed from an object 2020-08-12 16:19:48 +02:00
Florian Rival
b57832a131 Allow modules loaded by extensions to require "pixi.js" in addition to "pixi.js-legacy"
This should allow to not "hack" 3rd party modules so that they require "pixi.js-legacy" instead of "pixi.js". In both cases, we return "pixi.js-legacy".

This still requires modules to be supporting CommonJS (for Electron) and the global PIXI variable (for browsers).
2020-08-12 11:41:02 +02:00
Rahul Saini
8d9f5f0df0 Add script to generate list of community-made extensions (#1913) 2020-08-10 18:44:05 +02:00
Arthur Pacaud
b6ec327dfc Add 4ian as a GitHub codeowner (#1914)
Don't show in changelog
2020-08-09 21:54:36 +02:00
Sanskar Bajpai
005aa64aad Add a close button at the bottom of the search panel
* Feature: Added a close button on the panel.

Implements #1909.

* Stories: Added the new prop in Stories.

This commit introduces the addition of the onCloseSearchPanel prop in the Stories
thus removing all the flow errors. Prettier code formatting has also been run to make
the code look cleaner, and lastly the prop has been destructured in the
SearchPanel.js file.
2020-08-08 19:21:18 +02:00
Arthur Pacaud
a18b813140 Update phonegap-build Version to v9 (#1912) 2020-08-08 14:32:47 +02:00
arthuro555
7cd28062fc Update fixtures 2020-08-08 12:06:14 +02:00
Aurélien Vivet
2b7e2c8814 Fix Menu import in Electron exported games (#1911)
Don't show in changelog
2020-08-07 18:50:30 +02:00
Florian Rival
767f365a78 Revert usage of pick in GenerateObjectCondition
Don't show in changelog
2020-08-05 18:47:39 +02:00
Florian Rival
5f54583ff5 Add TypeScript checks to gdjs.RuntimeScene, gdjs
Only show in developer changelog
2020-08-05 18:47:39 +02:00
Florian Rival
37c260c19d Remove methods polluting Array prototype in gd.js
Only show in developer changelog
2020-08-05 18:47:39 +02:00
arthuro555
003f251c9f Apply review instructions 2020-08-03 23:48:10 +02:00
arthuro555
f8250ec9aa Regen fixtures 2020-08-02 21:34:18 +02:00
arthuro555
aeb4d278cd Merge branch 'master' into add-multiplayer-p2p 2020-08-02 21:31:28 +02:00
Florian Rival
2762329dd6 Update game fixtures for the web-app
Don't show in changelog
2020-08-02 18:38:14 +01:00
Florian Rival
75b1ff5cea Improve changelog extractor
Don't show in changelog
2020-08-02 18:18:36 +01:00
Florian Rival
1f4042bff0 Fix menu bar shown in exported games on Windows/macOS/Linux 2020-08-02 15:28:34 +01:00
Florian Rival
4e04e79b2f Bump newIDE version 2020-07-29 00:13:15 +01:00
Florian Rival
b9ba8e1b7b Update translations 2020-07-28 22:33:49 +01:00
Florian Rival
84ea9a9643 Merge branch 'master' of github.com:4ian/GDevelop 2020-07-28 20:26:28 +01:00
Nilay Majorwar
13c44250f2 Add network preview command, hide debug and network preview commands on web (#1896)
Don't show in changelog
2020-07-28 17:31:20 +01:00
Florian Rival
a5907a6883 Enable the Command Palette by default in the preferences for new users
* If you want to use the Command Palette and have GDevelop already installed, activate it in the preferences
2020-07-28 09:06:13 +01:00
Florian Rival
5902906bcc Add Health Bar And Health Potion video tutorial
Don't show in changelog
2020-07-28 09:05:42 +01:00
Florian Rival
4db041bf10 Add subscription reminder once in a while when using hot-reloading.
Don't show in changelog
2020-07-27 23:36:18 +01:00
arthuro555
63894f9a86 prettier 2020-07-27 22:41:13 +02:00
arthuro555
9a0ec853e7 Try to fix flow again 2020-07-27 15:56:36 +02:00
arthuro555
eb5d120aaf Try to fix flow
Flow doesn't work locally so I have to wait for travis output to be sure
2020-07-27 15:30:52 +02:00
arthuro555
057dd985fc Fix flow typing 2020-07-27 13:16:51 +02:00
arthuro555
8009e45936 Add files forgotten in last commit 2020-07-27 13:16:26 +02:00
arthuro555
b190731940 Change way Peer JS is initialized and update example 2020-07-27 12:40:58 +02:00
arthuro555
a337230195 Add disconnection events 2020-07-26 23:06:55 +02:00
arthuro555
731b9141fa Run prettier 2020-07-26 19:52:05 +02:00
arthuro555
51ba2e7631 Add hints for peer to peer 2020-07-26 19:50:49 +02:00
arthuro555
48a0c2d324 Regenerate fixtures for examples 2020-07-26 13:32:27 +02:00
Arthur Pacaud
25d32ce9bb Merge branch 'master' into add-multiplayer-p2p 2020-07-26 13:28:55 +02:00
arthuro555
a7ec57354d Update example 2020-07-26 13:24:45 +02:00
Florian Rival
cc1d26201e Update yarn.lock
Don't show in changelog
2020-07-26 12:18:17 +01:00
arthuro555
643e3b5c32 Add basic error handling 2020-07-26 13:15:07 +02:00
arthuro555
0cc4676067 Add is p2p ready condition 2020-07-26 12:52:48 +02:00
arthuro555
91b895fd92 Fix event data on no dataloss mode 2020-07-26 12:38:18 +02:00
arthuro555
f95d0ae461 Let user choose incoming message handling 2020-07-26 12:18:46 +02:00
arthuro555
81ce81242b Add option to use own server 2020-07-26 11:19:02 +02:00
Florian Rival
aa71e78507 Add Video tutorials and hints about these tutorials in the editor
* Thanks Wishforge Games (http://wishforge.games/) for these very high quality video tutorials!
2020-07-26 00:49:55 +01:00
Florian Rival
ee49ca6c14 Add support for layer re-ordering in hot reloading
Don't show in changelog
2020-07-25 23:27:23 +01:00
Florian Rival
a649789f4c Improve some typings in GDJS Runtime
Only show in developer changelog
2020-07-25 22:19:35 +01:00
Florian Rival
61e8e95d5b Add typing for PIXI in GDJS Runtime
This means that the global object PIXI will now be properly typed and understood by TypeScript.

Also fix documentation generation

Don't show in changelog
2020-07-25 18:56:38 +01:00
Florian Rival
cc158a9250 Add some standard libraries to the libraries used by Typescript to do the type checking
Don't show in changelog
2020-07-25 18:31:38 +01:00
Florian Rival
a91ccacb89 Improve typing in hot-reloader.js
Promise and other specific standard types can actually be imported using a triple slash directive.

Don't show in changelog
2020-07-25 18:27:05 +01:00
Florian Rival
bb9e8a2ea9 Merge pull request #1840 from 4ian/feature/hot-reload
Add "live previews" a.k.a "hot reloading"
2020-07-25 14:43:34 +01:00
Florian Rival
3bf40cd46c Fix hot-reloading of extensions in Network Preview and fix reloading of some events generated code files
Don't show in changelog
2020-07-25 14:23:02 +01:00
Florian Rival
aa823c1287 Make Network Preview (Preview over Wifi) compatible with live preview ("hot reloading")
* Also allow the debugger to work with games run using Network Preview (Preview over Wifi), including on other devices (phones, tablets...)

Don't show the rest in the changelog:

This removes the "live reloading" of the network preview and makes the hot-reloading and debugging to work with the network preview.
2020-07-25 14:23:02 +01:00
Florian Rival
24a666ab83 Fix hot-reloading of Anchor behavior and BBText and Text objects width
Don't show in changelog
2020-07-25 14:23:02 +01:00
Florian Rival
9e652b228d Don't reload fonts already loaded during a hot-reload
Also only issue a single request when multiple audio resources are pointing to the same file.

Don't show in changelog
2020-07-25 14:23:01 +01:00
Florian Rival
09bedc6ce5 Ensure events generated code is stable across code generation.
This is done by given unique identifiers to "Trigger Once" conditions (stable given the same object in memory) and events list function names (stable given events with same content).

This avoids useless hot-reloading and re-triggering Trigger Once conditions after a hot-reloading.

Don't show in changelog
2020-07-25 14:23:01 +01:00
Florian Rival
91e57340d4 Update icons and fix stale icons in Debugger Toolbar when selecting game
Don't show in changelog
2020-07-25 14:23:01 +01:00
Florian Rival
c385aae845 Add support for "hot reloading" of previews (apply changes to preview without restarting) 2020-07-25 14:23:01 +01:00
Florian Rival
460b582ab9 Refactor changes cancelling of GDevelop.js objects
* Use a typed hook (shorter and type-safe to use)
* Avoid the necessity of providing a function to create an object.
* Only unserialize back to the object if cancelling changes (instead of when applying).
2020-07-25 14:23:00 +01:00
Florian Rival
3a9f896f04 Add hot reloader (electron app only) 2020-07-25 14:23:00 +01:00
Florian Rival
2851a20787 Add persistentUuid to gd::InitialInstance 2020-07-25 14:22:59 +01:00
Florian Rival
9077c5d4f7 Fix saving a file potentially resulting in an empty file in some circumstances
* File integrity is now checked after a project is saved.
* Prevent concurrent save of a file (could happen if Ctrl+S/Cmd+S was kept pressed, and could result in an empty file being saved on disk).

Fix #1813
2020-07-25 14:22:04 +01:00
Arthur Pacaud
693b64cddf Fix documentation typo (#1882)
Don't show in changelog
2020-07-20 16:53:55 +01:00
arthuro555
e6b4373d97 Remove arrow function 2020-07-15 15:52:52 +02:00
arthuro555
72e705a39a Update peerjs 2020-07-15 15:44:50 +02:00
arthuro555
9bc71a42e4 Fixed potential crash 2020-07-15 15:09:09 +02:00
arthuro555
aacef226c4 Add help path 2020-07-03 14:59:26 +02:00
arthuro555
48c91e5587 Add p2p example 2020-07-03 14:39:36 +02:00
arthuro555
43eac4f998 Fix a game crashing bug 2020-07-03 14:37:46 +02:00
arthuro555
d220c59343 Add version number of PeerJS 2020-07-03 10:39:57 +02:00
arthuro555
e5f38f626d Change docstring 2020-07-02 20:00:30 +02:00
arthuro555
34673ace70 Add TS and rename from Multiplayer_P2P to P2P 2020-07-02 19:58:58 +02:00
arthuro555
56b91c4624 Add Icon and Add to web editor extension list 2020-07-01 16:22:14 +02:00
arthuro555
c10ae99c4f Apply review instructions 2020-06-30 16:29:07 +02:00
arthuro555
472c542579 Add basic multiplayer extension 2020-06-30 15:26:58 +02:00
540 changed files with 43352 additions and 6345 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @4ian

View File

@@ -541,7 +541,6 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (MetadataProvider::HasBehaviorAction(
platform, behaviorType, action.GetType()) &&
instrInfos.parameters.size() >= 2) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
for (std::size_t i = 0; i < realObjects.size(); ++i) {
@@ -1112,6 +1111,33 @@ gd::String EventsCodeGenerator::GenerateBehaviorAction(
}
}
size_t EventsCodeGenerator::GenerateSingleUsageUniqueIdForEventsList() {
return eventsListNextUniqueId++;
}
size_t EventsCodeGenerator::GenerateSingleUsageUniqueIdFor(
const Instruction* instruction) {
if (!instruction) {
std::cout << "ERROR: During code generation, a null pointer was passed to "
"GenerateSingleUsageUniqueIdFor."
<< std::endl;
}
// Base the unique id on the adress in memory so that the same instruction
// in memory will get the same id across different code generations.
size_t uniqueId = (size_t)instruction;
// While in most case this function is called a single time for each instruction,
// it's possible for an instruction to be appearing more than once in the events,
// if we used links. In this case, simply increment the unique id to be sure that
// ids are effectively uniques, and stay stable (given the same order of links).
while (instructionUniqueIds.find(uniqueId) != instructionUniqueIds.end()) {
uniqueId++;
}
instructionUniqueIds.insert(uniqueId);
return uniqueId;
}
gd::String EventsCodeGenerator::GetObjectListName(
const gd::String& name, const gd::EventsCodeGenerationContext& context) {
return ManObjListName(name);
@@ -1140,7 +1166,8 @@ EventsCodeGenerator::EventsCodeGenerator(gd::Project& project_,
errorOccurred(false),
compilationForRuntime(false),
maxCustomConditionsDepth(0),
maxConditionsListsSize(0){};
maxConditionsListsSize(0),
eventsListNextUniqueId(0){};
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
@@ -1155,6 +1182,7 @@ EventsCodeGenerator::EventsCodeGenerator(
errorOccurred(false),
compilationForRuntime(false),
maxCustomConditionsDepth(0),
maxConditionsListsSize(0){};
maxConditionsListsSize(0),
eventsListNextUniqueId(0){};
} // namespace gd

View File

@@ -9,6 +9,7 @@
#include <set>
#include <utility>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/String.h"
@@ -123,7 +124,7 @@ class GD_CORE_API EventsCodeGenerator {
*
*/
std::vector<gd::String> GenerateParametersCodes(
const std::vector<gd::Expression> & parameters,
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersInfo,
EventsCodeGenerationContext& context,
std::vector<std::pair<gd::String, gd::String> >*
@@ -321,7 +322,7 @@ class GD_CORE_API EventsCodeGenerator {
* group.
*
* Get a list containing the "real" objects name when the events refers to \a
* objectName :<br> If \a objectName if really an object, the list will only
* objectName :<br> If \a objectName is really an object, the list will only
* contains \a objectName unchanged.<br> If \a objectName is a group, the list
* will contains all the objects of the group.<br> If \a objectName is the
* "current" object in the context ( i.e: The object being used for launching
@@ -411,6 +412,29 @@ class GD_CORE_API EventsCodeGenerator {
enum VariableScope { LAYOUT_VARIABLE = 0, PROJECT_VARIABLE, OBJECT_VARIABLE };
/**
* Generate a single unique number for the specified instruction.
*
* This is useful for instructions that need to identify themselves in the
* generated code like the "Trigger Once" conditions. The id is stable across
* code generations if the instructions are the same objects in memory.
*
* Note that if this function is called multiple times with the same
* instruction, the unique number returned will be *different*. This is
* because a single instruction might appear at multiple places in events due
* to the usage of links.
*/
size_t GenerateSingleUsageUniqueIdFor(const gd::Instruction* instruction);
/**
* Generate a single unique number for an events list.
*
* This is useful to create unique function names for events list, that are
* stable across code generation given the exact same list of events. They are
* *not* stable if events are moved/reorganized.
*/
size_t GenerateSingleUsageUniqueIdForEventsList();
protected:
/**
* \brief Generate the code for a single parameter.
@@ -704,7 +728,8 @@ class GD_CORE_API EventsCodeGenerator {
/**
* Generate the getter to get the name of the specified behavior.
*/
virtual gd::String GenerateGetBehaviorNameCode(const gd::String& behaviorName);
virtual gd::String GenerateGetBehaviorNameCode(
const gd::String& behaviorName);
const gd::Platform& platform; ///< The platform being used.
@@ -732,6 +757,11 @@ class GD_CORE_API EventsCodeGenerator {
size_t maxCustomConditionsDepth; ///< The maximum depth value for all the
///< custom conditions created.
size_t maxConditionsListsSize; ///< The maximum size of a list of conditions.
std::set<size_t>
instructionUniqueIds; ///< The unique ids generated for instructions.
size_t eventsListNextUniqueId; ///< The next identifier to use for an events
///< list function name.
};
} // namespace gd

View File

@@ -4,9 +4,12 @@
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Events/Instruction.h"
#include <assert.h>
#include <iostream>
#include <vector>
#include "GDCore/Events/Expression.h"
#include "GDCore/Events/InstructionsList.h"
#include "GDCore/String.h"
@@ -15,18 +18,14 @@ namespace gd {
gd::Expression Instruction::badExpression("");
Instruction::Instruction(gd::String type_)
: type(type_),
inverted(false) {
Instruction::Instruction(gd::String type_) : type(type_), inverted(false) {
parameters.reserve(8);
}
Instruction::Instruction(gd::String type_,
const std::vector<gd::Expression>& parameters_,
bool inverted_)
: type(type_),
inverted(inverted_),
parameters(parameters_) {
: type(type_), inverted(inverted_), parameters(parameters_) {
parameters.reserve(8);
}
@@ -56,4 +55,17 @@ void Instruction::SetParameter(std::size_t nb, const gd::Expression& val) {
parameters[nb] = val;
}
std::shared_ptr<Instruction> GD_CORE_API
CloneRememberingOriginalElement(std::shared_ptr<Instruction> instruction) {
std::shared_ptr<Instruction> copy =
std::make_shared<Instruction>(*instruction);
// Original instruction is either the original instruction of the copied
// instruction, or the instruction copied.
copy->originalInstruction = instruction->originalInstruction.expired()
? instruction
: instruction->originalInstruction;
return copy;
}
} // namespace gd

View File

@@ -5,7 +5,9 @@
*/
#ifndef INSTRUCTION_H
#define INSTRUCTION_H
#include <memory>
#include <vector>
#include "GDCore/Events/Expression.h"
#include "GDCore/Events/InstructionsList.h"
#include "GDCore/String.h"
@@ -131,6 +133,17 @@ class GD_CORE_API Instruction {
*/
inline gd::InstructionsList& GetSubInstructions() { return subInstructions; };
/**
* \brief Return the original instruction this instruction was copied from.
*
* Useful to get reference to the original instruction in memory during code
* generation, to ensure stable unique identifiers.
*/
std::weak_ptr<Instruction> GetOriginalInstruction() { return originalInstruction; };
friend std::shared_ptr<Instruction> CloneRememberingOriginalElement(
std::shared_ptr<Instruction> instruction);
private:
gd::String type; ///< Instruction type
bool inverted; ///< True if the instruction if inverted. Only applicable for
@@ -139,9 +152,23 @@ class GD_CORE_API Instruction {
parameters; ///< Vector containing the parameters
gd::InstructionsList subInstructions; ///< Sub instructions, if applicable.
std::weak_ptr<Instruction>
originalInstruction; ///< Pointer used to remember which gd::Instruction
///< this instruction was copied from. Useful to
///< ensure the stability of code generation (as
///< some part of code generation uses the pointer
///< to the instruction as a unique identifier).
static gd::Expression badExpression;
};
/**
* Clone the given instruction, returning an instruction for which
* `GetOriginalInstruction()` returns the originally copied instruction.
*/
std::shared_ptr<Instruction> GD_CORE_API
CloneRememberingOriginalElement(std::shared_ptr<Instruction> instruction);
} // namespace gd
#endif // INSTRUCTION_H

View File

@@ -34,10 +34,10 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
"res/actions/net24.png",
"res/actions/net.png")
.AddParameter("string", _("Host, with protocol"))
.SetParameterLongDescription(
_("Example: \"http://example.com/\"."))
.SetParameterLongDescription(_("Example: \"http://example.com/\"."))
.AddParameter("string", _("Path"))
.SetParameterLongDescription(_("Example: \"/user/123\" or \"/some-page.php\"."))
.SetParameterLongDescription(
_("Example: \"/user/123\" or \"/some-page.php\"."))
.AddParameter("string", _("Request body content"))
.AddParameter("string", _("Method: \"POST\" or \"GET\""), "", true)
.SetParameterLongDescription(_("If empty, \"GET\" will be used."))
@@ -51,6 +51,52 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsNetworkExtension(
"variable. If the server returns *JSON*, you may want to use the "
"action \"Convert JSON to a scene variable\" afterwards, to "
"explore the results with a *structure variable*."))
.MarkAsComplex()
.SetHidden();
extension
.AddAction(
"SendAsyncRequest",
_("Send a request to a web page"),
_("Send an asynchronous request to the specified web page.\n\nPlease "
"note that for "
"the web games, the game must be hosted on the same host "
"as specified below, except if the server is configured to answer "
"to all requests (cross-domain requests)."),
_("Send a _PARAM2_ request to _PARAM0_ with body: _PARAM1_, and "
"store the result in _PARAM4_ (or in _PARAM5_ in case of error)"),
_("Network"),
"res/actions/net24.png",
"res/actions/net.png")
.AddParameter("string", _("URL (API or web-page address)"))
.SetParameterLongDescription(
_("Example: \"https://example.com/user/123\". Using *https* is "
"highly recommended."))
.AddParameter("string", _("Request body content"))
.AddParameter("stringWithSelector",
_("Resize mode"),
"[\"GET\", \"POST\", \"PUT\", \"HEAD\", \"DELETE\", "
"\"PATCH\", \"OPTIONS\"]",
false)
.SetParameterLongDescription(_("If empty, \"GET\" will be used."))
.SetDefaultValue("\"GET\"")
.AddParameter("string", _("Content type"), "", true)
.SetParameterLongDescription(
_("If empty, \"application/x-www-form-urlencoded\" will be used."))
.AddParameter(
"scenevar", _("Variable where to store the response"), "", true)
.SetParameterLongDescription(
_("The response of the server will be stored, as a string, in this "
"variable. If the server returns *JSON*, you may want to use the "
"action \"Convert JSON to a scene variable\" afterwards, to "
"explore the results with a *structure variable*."))
.AddParameter(
"scenevar", _("Variable where to store the error message"), "", true)
.SetParameterLongDescription(
_("Optional, only used if an error occurs. This will contain the "
"error message (if request could not be sent) or the [\"status "
"code\"](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes), "
"if the server returns a status >= 400."))
.MarkAsComplex();
extension

View File

@@ -0,0 +1,129 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef DEPENDENCYMETADATA_H
#define DEPENDENCYMETADATA_H
#include <map>
#include <set>
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Contains information about a dependency (library, npm/cordova
* package, or other according to the export) of an extension.
*/
class GD_CORE_API DependencyMetadata {
public:
/**
* \brief Sets the name shown to users.
*/
DependencyMetadata& SetName(const gd::String& name_) {
name = name_;
return *this;
};
/**
* \brief Sets the name written by the exporter.
* Typically, this is what is used by the dependency manager
* to find the dependency.
*
* \example
* \code
* // For depending upon the NPM package is-thirteen
* gd::DependencyMetadata dependencyMetadata = gd::DependencyMetadata();
* dependencyMetadata.setExporterName("is-thirteen");
* \endcode
*/
DependencyMetadata& SetExportName(const gd::String& exportName_) {
exportName = exportName_;
return *this;
};
/**
* \brief Set the version of the dependency to install.
* Use an empty string to use the latest version.
*/
DependencyMetadata& SetVersion(const gd::String& version_) {
version = version_;
return *this;
};
/**
* \brief Sets the type of dependecy (what will be used to install it)
*
* This can either be "npm" or "cordova" for now.
*/
DependencyMetadata& SetDependencyType(const gd::String& dependencyType_) {
dependencyType = dependencyType_;
if (dependencyType != "npm" && dependencyType != "cordova") {
gd::LogWarning("Invalid dependency type: " + dependencyType);
}
return *this;
};
/**
* \brief Sets a dependency type specific setting.
*/
DependencyMetadata& SetExtraSetting(
const gd::String& settingName,
const gd::PropertyDescriptor& settingValue) {
extraData[settingName] = settingValue;
return *this;
};
/**
* \brief Mark the dependency to be included in the export only if the
* specified setting is not empty.
*
* If this is called for multiple settings, all settings must be fulfilled for
* the dependency to be exported.
*/
DependencyMetadata& OnlyIfExtraSettingIsNonEmpty(
const gd::String& settingName) {
nonEmptyExtraSettingsForExport.insert(settingName);
return *this;
};
/**
* \brief Get the list of extra settings that must be fulfilled for the
* dependency to be exported.
*/
const std::set<gd::String>& GetRequiredExtraSettingsForExport() const {
return nonEmptyExtraSettingsForExport;
};
const gd::String& GetName() const { return name; };
const gd::String& GetExportName() const { return exportName; };
const gd::String& GetVersion() const { return version; };
const gd::String& GetDependencyType() const {
if (dependencyType == "")
gd::LogWarning("Dependency has no type, it won't be exported.");
return dependencyType;
};
const std::map<gd::String, gd::PropertyDescriptor>& GetAllExtraSettings()
const {
return extraData;
}
private:
gd::String name; ///< The name of the dependency.
gd::String exportName; ///< The name used to install the package (example:
///< npm package name for npm dependency type).
gd::String version; ///< The version of the dependency
gd::String dependencyType; ///< The tool used to install the dependency.
std::map<gd::String, gd::PropertyDescriptor>
extraData; ///< Contains dependency type specific additional parameters
///< for the dependency.
std::set<gd::String>
nonEmptyExtraSettingsForExport; ///< The set of extra settings that must
///< be non empty for this dependency to
///< be included by the exporter.
};
} // namespace gd
#endif // DEPENDENCYMETADATA_H

View File

@@ -4,9 +4,12 @@
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Extensions/PlatformExtension.h"
#include <algorithm>
#include "GDCore/Events/Event.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/DependencyMetadata.h"
#include "GDCore/Extensions/Metadata/EventMetadata.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
@@ -117,6 +120,13 @@ gd::ExpressionMetadata& PlatformExtension::AddStrExpression(
#endif
}
#if defined(GD_IDE_ONLY)
gd::DependencyMetadata& PlatformExtension::AddDependency() {
extensionDependenciesMetadata.push_back(DependencyMetadata());
return extensionDependenciesMetadata.back();
}
#endif
gd::ObjectMetadata& PlatformExtension::AddObject(
const gd::String& name,
const gd::String& fullname,
@@ -216,8 +226,7 @@ std::vector<gd::String> PlatformExtension::GetExtensionObjectsTypes() const {
std::vector<gd::String> PlatformExtension::GetExtensionEffectTypes() const {
std::vector<gd::String> effectNames;
for (auto& it : effectsMetadata)
effectNames.push_back(it.first);
for (auto& it : effectsMetadata) effectNames.push_back(it.first);
return effectNames;
}
@@ -283,6 +292,10 @@ PlatformExtension::GetAllStrExpressions() {
return strExpressionsInfos;
}
std::vector<gd::DependencyMetadata>& PlatformExtension::GetAllDependencies() {
return extensionDependenciesMetadata;
}
std::map<gd::String, gd::EventMetadata>& PlatformExtension::GetAllEvents() {
return eventsInfos;
}
@@ -404,7 +417,7 @@ void PlatformExtension::SetNameSpace(gd::String nameSpace_) {
name == "BuiltinCommonConversions" ||
name == "BuiltinStringInstructions" ||
name == "BuiltinMathematicalTools" ||
name == "Effects" || // Well-known effects are not namespaced.
name == "Effects" || // Well-known effects are not namespaced.
name == "CommonDialogs") // New name for BuiltinInterface
{
nameSpace = "";

View File

@@ -10,11 +10,14 @@
#include <map>
#include <memory>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/DependencyMetadata.h"
#include "GDCore/Extensions/Metadata/EffectMetadata.h"
#include "GDCore/Extensions/Metadata/EventMetadata.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/EffectMetadata.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/String.h"
#include "GDCore/Tools/VersionPriv.h"
@@ -25,6 +28,7 @@ class ExpressionMetadata;
class ObjectMetadata;
class BehaviorMetadata;
class EffectMetadata;
class DependencyMetadata;
class BaseEvent;
class EventMetadata;
class EventCodeGenerator;
@@ -148,6 +152,8 @@ class GD_CORE_API PlatformExtension {
const gd::String& group_,
const gd::String& smallicon_);
gd::DependencyMetadata& AddDependency();
/**
* \brief Declare a new object as being part of the extension.
* \note This method does nothing when used for GD C++ runtime.
@@ -225,6 +231,15 @@ class GD_CORE_API PlatformExtension {
const gd::String& smallicon_,
std::shared_ptr<gd::BaseEvent> instance);
#if defined(GD_IDE_ONLY)
/**
* \brief Adds a property to the extension.
*/
gd::PropertyDescriptor& RegisterProperty(const gd::String& name) {
return extensionPropertiesMetadata[name];
};
#endif
/**
* \brief Return the name extension user friendly name.
*/
@@ -365,6 +380,12 @@ class GD_CORE_API PlatformExtension {
*/
std::map<gd::String, gd::ExpressionMetadata>& GetAllStrExpressions();
/**
* \brief Return a reference to a vector containing the metadata of all the
* dependencies of the extension.
*/
std::vector<gd::DependencyMetadata>& GetAllDependencies();
/**
* \brief Return a reference to a map containing the names of the actions,
* related to the object type, and the metadata associated with.
@@ -437,6 +458,13 @@ class GD_CORE_API PlatformExtension {
* generator.
*/
void StripUnimplementedInstructionsAndExpressions();
/**
* \brief Get all the properties of the extension
*/
std::map<gd::String, gd::PropertyDescriptor>& GetAllProperties() {
return extensionPropertiesMetadata;
}
#endif
/**
@@ -480,7 +508,9 @@ class GD_CORE_API PlatformExtension {
std::map<gd::String, gd::InstructionMetadata> actionsInfos;
std::map<gd::String, gd::ExpressionMetadata> expressionsInfos;
std::map<gd::String, gd::ExpressionMetadata> strExpressionsInfos;
std::vector<gd::DependencyMetadata> extensionDependenciesMetadata;
std::map<gd::String, gd::EventMetadata> eventsInfos;
std::map<gd::String, gd::PropertyDescriptor> extensionPropertiesMetadata;
#endif
ObjectMetadata badObjectMetadata;

View File

@@ -49,9 +49,15 @@ InstructionSentenceFormatter::GetAsFormattedText(
gd::String sentence = metadata.GetSentence();
std::replace(sentence.Raw().begin(), sentence.Raw().end(), '\n', ' ');
bool parse = true;
size_t loopCount = 0;
bool parse = true;
while (parse) {
if (loopCount > 40) {
break;
}
loopCount++;
// Search first parameter
parse = false;
size_t firstParamPosition = gd::String::npos;

View File

@@ -0,0 +1,56 @@
#include "ExtensionProperties.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertyDescriptor.h"
namespace gd {
const gd::String ExtensionProperties::defaultValue = "";
std::map<gd::String, gd::PropertyDescriptor>
ExtensionProperties::GetAllExtensionProperties(const gd::String& extensionName,
gd::Project& project) {
// Create a copy
std::map<gd::String, gd::PropertyDescriptor> props(
project.GetCurrentPlatform()
.GetExtension(extensionName)
->GetAllProperties());
// Set values
for (std::pair<gd::String, gd::PropertyDescriptor> property : props) {
if (properties.count(extensionName) > 0 &&
properties[extensionName].count(property.first) > 0) {
props[property.first].SetValue(properties[extensionName][property.first]);
}
}
return props;
};
void ExtensionProperties::SerializeTo(SerializerElement& element) const {
element.ConsiderAsArrayOf("extensionProperties");
for (const std::pair<gd::String, std::map<gd::String, gd::String>> extension :
properties) {
for (const std::pair<gd::String, gd::String> property : extension.second) {
SerializerElement& propertyElement =
element.AddChild("extensionProperties");
propertyElement.AddChild("extension").SetStringValue(extension.first);
propertyElement.AddChild("property").SetStringValue(property.first);
propertyElement.AddChild("value").SetStringValue(property.second);
}
}
};
void ExtensionProperties::UnserializeFrom(const SerializerElement& element) {
properties.clear();
element.ConsiderAsArrayOf("extensionProperties");
for (std::pair<const gd::String, std::shared_ptr<SerializerElement>>
extensionProperties : element.GetAllChildren()) {
std::shared_ptr<SerializerElement> extensionPropertiesElement =
extensionProperties.second;
properties
[extensionPropertiesElement->GetChild("extension").GetStringValue()]
[extensionPropertiesElement->GetChild("property").GetStringValue()] =
extensionPropertiesElement->GetChild("value").GetStringValue();
}
};
}; // namespace gd

View File

@@ -0,0 +1,69 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_EXTENSIONPROPERTIES_H
#define GDCORE_EXTENSIONPROPERTIES_H
#include <map>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
namespace gd {
class Project;
class PropertyDescriptor;
} // namespace gd
namespace gd {
class GD_CORE_API ExtensionProperties {
static const gd::String defaultValue;
public:
const gd::String& GetValue(const gd::String& extension,
const gd::String& property) const {
if (properties.count(extension) == 0 ||
properties.at(extension).count(property) == 0) {
return ExtensionProperties::defaultValue;
}
return properties.at(extension).at(property);
};
void SetValue(const gd::String& extension,
const gd::String& property,
const gd::String& newValue) {
properties[extension][property] = newValue;
};
bool HasProperty(const gd::String& extension, const gd::String& property) {
for (std::pair<gd::String, gd::String> propertyPair :
properties[extension]) {
if (propertyPair.first == property) {
return true;
}
}
return false;
}
std::map<gd::String, gd::PropertyDescriptor> GetAllExtensionProperties(
const gd::String& extensionName, gd::Project& project);
///@{
/**
* \brief Serialize the Extension Properties.
*/
virtual void SerializeTo(SerializerElement& element) const;
/**
* \brief Unserialize the Extension Properties.
*/
virtual void UnserializeFrom(const SerializerElement& element);
///@}
private:
std::map<gd::String, std::map<gd::String, gd::String>>
properties; ///< The properties of the project
};
} // namespace gd
#endif // EXTENSIONPROPERTIES_H

View File

@@ -5,10 +5,12 @@
*/
#include "GDCore/Project/InitialInstance.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/UUID/UUID.h"
#if defined(GD_IDE_ONLY)
#include "GDCore/Project/PropertyDescriptor.h"
#endif
@@ -27,7 +29,8 @@ InitialInstance::InitialInstance()
personalizedSize(false),
width(0),
height(0),
locked(false) {}
locked(false),
persistentUuid(UUID::MakeUuid4()) {}
void InitialInstance::UnserializeFrom(const SerializerElement& element) {
SetObjectName(element.GetStringAttribute("name", "", "nom"));
@@ -42,6 +45,9 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
SetLayer(element.GetStringAttribute("layer"));
SetLocked(element.GetBoolAttribute("locked", false));
persistentUuid = element.GetStringAttribute("persistentUuid");
if (persistentUuid.empty()) ResetPersistentUuid();
floatInfos.clear();
const SerializerElement& floatPropElement =
element.GetChild("numberProperties", 0, "floatInfos");
@@ -79,6 +85,9 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
element.SetAttribute("height", GetCustomHeight());
element.SetAttribute("locked", IsLocked());
if (persistentUuid.empty()) persistentUuid = UUID::MakeUuid4();
element.SetStringAttribute("persistentUuid", persistentUuid);
SerializerElement& floatPropElement = element.AddChild("numberProperties");
floatPropElement.ConsiderAsArrayOf("property");
for (std::map<gd::String, float>::const_iterator floatInfo =
@@ -104,6 +113,11 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
GetVariables().SerializeTo(element.AddChild("initialVariables"));
}
InitialInstance& InitialInstance::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
return *this;
}
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor>
InitialInstance::GetCustomProperties(gd::Project& project, gd::Layout& layout) {
@@ -146,13 +160,12 @@ const gd::String& InitialInstance::GetRawStringProperty(
return it != stringInfos.end() ? it->second : *badStringProperyValue;
}
void InitialInstance::SetRawFloatProperty(const gd::String& name, float value)
{
void InitialInstance::SetRawFloatProperty(const gd::String& name, float value) {
floatInfos[name] = value;
}
void InitialInstance::SetRawStringProperty(const gd::String& name, const gd::String& value)
{
void InitialInstance::SetRawStringProperty(const gd::String& name,
const gd::String& value) {
stringInfos[name] = value;
}
#endif

View File

@@ -200,7 +200,7 @@ class GD_CORE_API InitialInstance {
/**
* \brief Get the value of a float property stored in the instance.
* \note Only use this when \a GetCustomProperties is too slow (when rendering
* instances for example).
* instances for example).
* \return the value of the property, or 0 if it does
* not exists.
*/
@@ -209,7 +209,7 @@ class GD_CORE_API InitialInstance {
/**
* \brief Get the value of a string property stored in the instance.
* \note Only use this when \a GetCustomProperties is too slow (when rendering
* instances for example).
* instances for example).
* \return the value of the propety, or an empty
* string if it does not exists.
*/
@@ -240,6 +240,12 @@ class GD_CORE_API InitialInstance {
* \brief Unserialize the instances container.
*/
virtual void UnserializeFrom(const SerializerElement& element);
/**
* \brief Reset the persistent UUID used to recognize
* the same initial instance between serialization.
*/
InitialInstance& ResetPersistentUuid();
///@}
// More properties can be stored in floatInfos and stringInfos.
@@ -260,6 +266,7 @@ class GD_CORE_API InitialInstance {
float height; ///< Object custom height
gd::VariablesContainer initialVariables; ///< Instance specific variables
bool locked; ///< True if the instance is locked
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID, useful for hot reloading.
static gd::String*
badStringProperyValue; ///< Empty string returned by GetRawStringProperty

View File

@@ -13,7 +13,7 @@ namespace gd {
Camera Layer::badCamera;
Effect Layer::badEffect;
Layer::Layer() : isVisible(true) {}
Layer::Layer() : isVisible(true), isLightingLayer(false), followBaseLayerCamera(false) {}
/**
* Change cameras count, automatically adding/removing them.
@@ -29,6 +29,11 @@ void Layer::SetCameraCount(std::size_t n) {
void Layer::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", GetName());
element.SetAttribute("visibility", GetVisibility());
element.SetAttribute("isLightingLayer", IsLightingLayer());
element.SetAttribute("followBaseLayerCamera", IsFollowingBaseLayerCamera());
element.SetAttribute("ambientLightColorR", (int)GetAmbientLightColorRed());
element.SetAttribute("ambientLightColorG", (int)GetAmbientLightColorGreen());
element.SetAttribute("ambientLightColorB", (int)GetAmbientLightColorBlue());
SerializerElement& camerasElement = element.AddChild("cameras");
camerasElement.ConsiderAsArrayOf("camera");
@@ -61,6 +66,11 @@ void Layer::SerializeTo(SerializerElement& element) const {
void Layer::UnserializeFrom(const SerializerElement& element) {
SetName(element.GetStringAttribute("name", "", "Name"));
SetVisibility(element.GetBoolAttribute("visibility", true, "Visibility"));
SetLightingLayer(element.GetBoolAttribute("isLightingLayer", false));
SetFollowBaseLayerCamera(element.GetBoolAttribute("followBaseLayerCamera", false));
SetAmbientLightColor(element.GetIntAttribute("ambientLightColorR", 200),
element.GetIntAttribute("ambientLightColorG", 200),
element.GetIntAttribute("ambientLightColorB", 200));
// Compatibility with GD <= 3.3
if (element.HasChild("Camera")) {

View File

@@ -51,6 +51,26 @@ class GD_CORE_API Layer {
*/
bool GetVisibility() const { return isVisible; }
/**
* \brief Set if the layer is a lightining layer or not.
*/
void SetLightingLayer(bool isLightingLayer_) { isLightingLayer = isLightingLayer_; }
/**
* \brief Return true if the layer is a lighting layer.
*/
bool IsLightingLayer() const { return isLightingLayer; }
/**
* \brief Set if the layer automatically follows the base layer or not.
*/
void SetFollowBaseLayerCamera(bool followBaseLayerCamera_) { followBaseLayerCamera = followBaseLayerCamera_; }
/**
* \brief Return true if the layer follows the base layer.
*/
bool IsFollowingBaseLayerCamera() const { return followBaseLayerCamera; }
/** \name Cameras
*/
///@{
@@ -96,6 +116,30 @@ class GD_CORE_API Layer {
///@}
/**
* Get the ambient light color red component.
*/
unsigned int GetAmbientLightColorRed() const { return ambientLightColorR; }
/**
* Get the ambient light color green component.
*/
unsigned int GetAmbientLightColorGreen() const { return ambientLightColorG; }
/**
* Get the ambient light color blue component.
*/
unsigned int GetAmbientLightColorBlue() const { return ambientLightColorB; }
/**
* Set the ambient light color.
*/
void SetAmbientLightColor(unsigned int r, unsigned int g, unsigned int b) {
ambientLightColorR = r;
ambientLightColorG = g;
ambientLightColorB = b;
}
/** \name Effects
*/
///@{
@@ -177,6 +221,11 @@ class GD_CORE_API Layer {
private:
gd::String name; ///< The name of the layer
bool isVisible; ///< True if the layer is visible
bool isLightingLayer; ///< True if the layer is used to display lights and renders an ambient light.
bool followBaseLayerCamera; ///< True if the layer automatically follows the base layer
unsigned int ambientLightColorR; ///< Ambient light color Red component
unsigned int ambientLightColorG; ///< Ambient light color Green component
unsigned int ambientLightColorB; ///< Ambient light color Blue component
std::vector<gd::Camera> cameras; ///< The camera displayed by the layer
std::vector<std::shared_ptr<gd::Effect>>
effects; ///< The effects applied to the layer.

View File

@@ -23,4 +23,14 @@ void NamedPropertyDescriptor::UnserializeFrom(
name = element.GetChild("name").GetStringValue();
}
void NamedPropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {
PropertyDescriptor::SerializeValuesTo(element);
element.AddChild("name").SetStringValue(name);
}
void NamedPropertyDescriptor::UnserializeValuesFrom(const SerializerElement& element) {
PropertyDescriptor::UnserializeValuesFrom(element);
name = element.GetChild("name").GetStringValue();
}
} // namespace gd

View File

@@ -58,6 +58,16 @@ class GD_CORE_API NamedPropertyDescriptor : public PropertyDescriptor {
* \brief Unserialize the NamedPropertyDescriptor.
*/
void UnserializeFrom(const SerializerElement& element);
/**
* \brief Serialize only the value and extra informations of the property.
*/
virtual void SerializeValuesTo(SerializerElement& element) const;
/**
* \brief Unserialize only the value and extra information of the property.
*/
virtual void UnserializeValuesFrom(const SerializerElement& element);
///@}
/**

View File

@@ -5,13 +5,16 @@
*/
#include "Project.h"
#include <stdio.h>
#include <stdlib.h>
#include <cctype>
#include <SFML/System/Utf.hpp>
#include <cctype>
#include <fstream>
#include <map>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
@@ -51,7 +54,6 @@ Project::Project()
version("1.0.0"),
packageName("com.example.gamename"),
orientation("landscape"),
adMobAppId(""),
folderProject(false),
#endif
windowWidth(800),
@@ -596,7 +598,6 @@ void Project::UnserializeFrom(const SerializerElement& element) {
SetAuthor(propElement.GetChild("author", 0, "Auteur").GetValue().GetString());
SetPackageName(propElement.GetStringAttribute("packageName"));
SetOrientation(propElement.GetStringAttribute("orientation", "default"));
SetAdMobAppId(propElement.GetStringAttribute("adMobAppId", ""));
SetFolderProject(propElement.GetBoolAttribute("folderProject"));
SetProjectFile(propElement.GetStringAttribute("projectFile"));
SetLastCompilationDirectory(propElement
@@ -618,6 +619,20 @@ void Project::UnserializeFrom(const SerializerElement& element) {
propElement.GetStringAttribute("macExecutableFilename");
useExternalSourceFiles =
propElement.GetBoolAttribute("useExternalSourceFiles");
extensionProperties.UnserializeFrom(
propElement.GetChild("extensionProperties"));
// Compatibility with GD <= 5.0.0-beta98
// Move AdMob App ID from project property to extension property.
if (propElement.GetStringAttribute("adMobAppId", "") != "") {
extensionProperties.SetValue(
"AdMob",
"AdMobAppId",
propElement.GetStringAttribute("adMobAppId", ""));
}
// end of compatibility code
#endif
const SerializerElement& extensionsElement =
@@ -866,13 +881,13 @@ void Project::SerializeTo(SerializerElement& element) const {
propElement.AddChild("verticalSync")
.SetValue(IsVerticalSynchronizationEnabledByDefault());
propElement.SetAttribute("scaleMode", scaleMode);
propElement.SetAttribute("adaptGameResolutionAtRuntime", adaptGameResolutionAtRuntime);
propElement.SetAttribute("adaptGameResolutionAtRuntime",
adaptGameResolutionAtRuntime);
propElement.SetAttribute("sizeOnStartupMode", sizeOnStartupMode);
propElement.SetAttribute("projectFile", gameFile);
propElement.SetAttribute("folderProject", folderProject);
propElement.SetAttribute("packageName", packageName);
propElement.SetAttribute("orientation", orientation);
propElement.SetAttribute("adMobAppId", adMobAppId);
platformSpecificAssets.SerializeTo(
propElement.AddChild("platformSpecificAssets"));
loadingScreen.SerializeTo(propElement.AddChild("loadingScreen"));
@@ -882,6 +897,8 @@ void Project::SerializeTo(SerializerElement& element) const {
propElement.SetAttribute("macExecutableFilename", macExecutableFilename);
propElement.SetAttribute("useExternalSourceFiles", useExternalSourceFiles);
extensionProperties.SerializeTo(propElement.AddChild("extensionProperties"));
SerializerElement& extensionsElement = propElement.AddChild("extensions");
extensionsElement.ConsiderAsArrayOf("extension");
for (std::size_t i = 0; i < GetUsedExtensions().size(); ++i)
@@ -1071,13 +1088,14 @@ void Project::Init(const gd::Project& game) {
author = game.author;
packageName = game.packageName;
orientation = game.orientation;
adMobAppId = game.adMobAppId;
folderProject = game.folderProject;
latestCompilationDirectory = game.latestCompilationDirectory;
platformSpecificAssets = game.platformSpecificAssets;
loadingScreen = game.loadingScreen;
objectGroups = game.objectGroups;
extensionProperties = game.extensionProperties;
gdMajorVersion = game.gdMajorVersion;
gdMinorVersion = game.gdMinorVersion;
gdBuildVersion = game.gdBuildVersion;

View File

@@ -9,6 +9,7 @@
#include <memory>
#include <vector>
#include "GDCore/Project/ExtensionProperties.h"
#include "GDCore/Project/LoadingScreen.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
@@ -114,20 +115,6 @@ class GD_CORE_API Project : public ObjectsContainer {
*/
const gd::String& GetOrientation() const { return orientation; }
/**
* \brief Change the project AdMob application ID (needed
* to use the AdMob extension). This has no effect on desktop
* and web browsers.
*/
void SetAdMobAppId(const gd::String& adMobAppId_) {
adMobAppId = adMobAppId_;
};
/**
* \brief Get the project AdMob application ID.
*/
const gd::String& GetAdMobAppId() const { return adMobAppId; }
/**
* Called when project file has changed.
*/
@@ -305,6 +292,26 @@ class GD_CORE_API Project : public ObjectsContainer {
std::vector<gd::String>& GetUsedExtensions() { return extensionsUsed; };
#if defined(GD_IDE_ONLY)
/**
* \brief Get the properties set by extensions.
*
* Each extension can store arbitrary values indexed by a property name, which are
* useful to store project wide settings (AdMob id, etc...).
*/
gd::ExtensionProperties& GetExtensionProperties() {
return extensionProperties;
};
/**
* \brief Get the properties set by extensions.
*
* Each extension can store arbitrary values indexed by a property name, which are
* useful to store project wide settings (AdMob id, etc...).
*/
const gd::ExtensionProperties& GetExtensionProperties() const {
return extensionProperties;
};
/**
* Return the list of platforms used by the project.
*/
@@ -968,7 +975,6 @@ class GD_CORE_API Project : public ObjectsContainer {
gd::String packageName; ///< Game package name
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
gd::String adMobAppId; ///< AdMob application ID.
bool
folderProject; ///< True if folder project, false if single file project.
gd::String gameFile; ///< File of the game
@@ -978,7 +984,9 @@ class GD_CORE_API Project : public ObjectsContainer {
gd::PlatformSpecificAssets platformSpecificAssets;
gd::LoadingScreen loadingScreen;
std::vector<std::unique_ptr<gd::ExternalEvents> >
externalEvents; ///< List of all externals events
externalEvents; ///< List of all externals events
ExtensionProperties
extensionProperties; ///< The properties of the extensions.
mutable unsigned int gdMajorVersion; ///< The GD major version used the last
///< time the project was saved.
mutable unsigned int gdMinorVersion; ///< The GD minor version used the last

View File

@@ -4,7 +4,9 @@
* reserved. This project is released under the MIT License.
*/
#include "PropertyDescriptor.h"
#include <vector>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
@@ -45,4 +47,27 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
: false;
}
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {
element.AddChild("value").SetStringValue(currentValue);
SerializerElement& extraInformationElement =
element.AddChild("extraInformation");
extraInformationElement.ConsiderAsArray();
for (const gd::String& information : extraInformation) {
extraInformationElement.AddChild("").SetStringValue(information);
}
}
void PropertyDescriptor::UnserializeValuesFrom(
const SerializerElement& element) {
currentValue = element.GetChild("value").GetStringValue();
extraInformation.clear();
const SerializerElement& extraInformationElement =
element.GetChild("extraInformation");
extraInformationElement.ConsiderAsArray();
for (std::size_t i = 0; i < extraInformationElement.GetChildrenCount(); ++i)
extraInformation.push_back(
extraInformationElement.GetChild(i).GetStringValue());
}
} // namespace gd

View File

@@ -6,6 +6,7 @@
#ifndef GDCORE_PROPERTYDESCRIPTOR
#define GDCORE_PROPERTYDESCRIPTOR
#include <vector>
#include "GDCore/String.h"
namespace gd {
class SerializerElement;
@@ -120,6 +121,16 @@ class GD_CORE_API PropertyDescriptor {
* \brief Unserialize the PropertyDescriptor.
*/
virtual void UnserializeFrom(const SerializerElement& element);
/**
* \brief Serialize only the value and extra informations.
*/
virtual void SerializeValuesTo(SerializerElement& element) const;
/**
* \brief Unserialize only the value and extra informations.
*/
virtual void UnserializeValuesFrom(const SerializerElement& element);
///@}
private:
@@ -127,7 +138,7 @@ class GD_CORE_API PropertyDescriptor {
gd::String
type; ///< The type of the property. This is arbitrary and interpreted by
///< the class responsible for updating the property grid.
gd::String label; //< The user-friendly property name
gd::String label; //< The user-friendly property name
gd::String description; //< The user-friendly property description
std::vector<gd::String>
extraInformation; ///< Can be used to store for example the available

View File

@@ -90,7 +90,7 @@ template <typename T>
void SPtrList<T>::Init(const gd::SPtrList<T>& other) {
elements.clear();
for (size_t i = 0; i < other.elements.size(); ++i)
elements.push_back(std::make_shared<T>(other[i]));
elements.push_back(CloneRememberingOriginalElement(other.elements[i]));
}
} // namespace gd

View File

@@ -0,0 +1,24 @@
/*
* GDevelop Core
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_TOOLS_UUID_UUID_H
#define GDCORE_TOOLS_UUID_UUID_H
#include "GDCore/String.h"
#include "sole.h"
namespace gd {
namespace UUID {
/**
* Generate a random UUID v4
*/
inline gd::String MakeUuid4() { return gd::String::From(sole::uuid4()); }
} // namespace UUID
} // namespace gd
#endif

View File

@@ -0,0 +1,249 @@
/**
* Modified version of sole (https://github.com/r-lyeh-archived/sole) C++11 library
* to only generate UUID v4.
*
* Sole is a lightweight C++11 library to generate universally unique identificators.
* Sole provides interface for UUID versions 0, 1 and 4.
*
* https://github.com/r-lyeh/sole
* Copyright (c) 2013,2014,2015 r-lyeh. zlib/libpng licensed.
*
* Based on code by Dmitri Bouianov, Philip O'Toole, Poco C++ libraries and anonymous
* code found on the net. Thanks guys!
*
* Theory: (see Hoylen's answer at [1])
* - UUID version 1 (48-bit MAC address + 60-bit clock with a resolution of 100ns)
* Clock wraps in 3603 A.D.
* Up to 10000000 UUIDs per second.
* MAC address revealed.
*
* - UUID Version 4 (122-bits of randomness)
* See [2] or other analysis that describe how very unlikely a duplicate is.
*
* - Use v1 if you need to sort or classify UUIDs per machine.
* Use v1 if you are worried about leaving it up to probabilities (e.g. your are the
* type of person worried about the earth getting destroyed by a large asteroid in your
* lifetime). Just use a v1 and it is guaranteed to be unique till 3603 AD.
*
* - Use v4 if you are worried about security issues and determinism. That is because
* v1 UUIDs reveal the MAC address of the machine it was generated on and they can be
* predictable. Use v4 if you need more than 10 million uuids per second, or if your
* application wants to live past 3603 A.D.
* Additionally a custom UUID v0 is provided:
* - 16-bit PID + 48-bit MAC address + 60-bit clock with a resolution of 100ns since Unix epoch
* - Format is EPOCH_LOW-EPOCH_MID-VERSION(0)|EPOCH_HI-PID-MAC
* - Clock wraps in 3991 A.D.
* - Up to 10000000 UUIDs per second.
* - MAC address and PID revealed.
* References:
* - [1] http://stackoverflow.com/questions/1155008/how-unique-is-uuid
* - [2] http://en.wikipedia.org/wiki/UUID#Random%5FUUID%5Fprobability%5Fof%5Fduplicates
* - http://en.wikipedia.org/wiki/Universally_unique_identifier
* - http://en.cppreference.com/w/cpp/numeric/random/random_device
* - http://www.itu.int/ITU-T/asn1/uuid.html f81d4fae-7dec-11d0-a765-00a0c91e6bf6
* - rlyeh ~~ listening to Hedon Cries / Until The Sun Goes up
*/
//////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <stdint.h>
#include <stdio.h> // for size_t; should be stddef.h instead; however, clang+archlinux fails when compiling it (@Travis-Ci)
#include <sys/types.h> // for uint32_t; should be stdint.h instead; however, GCC 5 on OSX fails when compiling it (See issue #11)
#include <functional>
#include <string>
// public API
namespace sole
{
// 128-bit basic UUID type that allows comparison and sorting.
// Use .str() for printing and .pretty() for pretty printing.
// Also, ostream friendly.
struct uuid
{
uint64_t ab;
uint64_t cd;
bool operator==( const uuid &other ) const;
bool operator!=( const uuid &other ) const;
bool operator <( const uuid &other ) const;
std::string base62() const;
std::string str() const;
template<typename ostream>
inline friend ostream &operator<<( ostream &os, const uuid &self ) {
return os << self.str(), os;
}
};
// Generators
uuid uuid4(); // UUID v4, pros: anonymous, fast; con: uuids "can clash"
// Rebuilders
uuid rebuild( uint64_t ab, uint64_t cd );
uuid rebuild( const std::string &uustr );
}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4127)
#endif
namespace std {
template<>
struct hash< sole::uuid > {
public:
// hash functor: hash uuid to size_t value by pseudorandomizing transform
size_t operator()( const sole::uuid &uuid ) const {
if( sizeof(size_t) > 4 ) {
return size_t( uuid.ab ^ uuid.cd );
} else {
uint64_t hash64 = uuid.ab ^ uuid.cd;
return size_t( uint32_t( hash64 >> 32 ) ^ uint32_t( hash64 ) );
}
}
};
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// implementation
#include <memory.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <random>
#include <sstream>
#include <string>
#include <vector>
#include <unistd.h>
inline bool sole::uuid::operator==( const sole::uuid &other ) const {
return ab == other.ab && cd == other.cd;
}
inline bool sole::uuid::operator!=( const sole::uuid &other ) const {
return !operator==(other);
}
inline bool sole::uuid::operator<( const sole::uuid &other ) const {
if( ab < other.ab ) return true;
if( ab > other.ab ) return false;
if( cd < other.cd ) return true;
return false;
}
namespace sole {
inline std::string uuid::str() const {
std::stringstream ss;
ss << std::hex << std::nouppercase << std::setfill('0');
uint32_t a = (ab >> 32);
uint32_t b = (ab & 0xFFFFFFFF);
uint32_t c = (cd >> 32);
uint32_t d = (cd & 0xFFFFFFFF);
ss << std::setw(8) << (a) << '-';
ss << std::setw(4) << (b >> 16) << '-';
ss << std::setw(4) << (b & 0xFFFF) << '-';
ss << std::setw(4) << (c >> 16 ) << '-';
ss << std::setw(4) << (c & 0xFFFF);
ss << std::setw(8) << d;
return ss.str();
}
inline std::string uuid::base62() const {
int base62len = 10 + 26 + 26;
const char base62[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
char res[24], *end = &res[24]; *(--end) = '\0';
uint64_t rem, AB = ab, CD = cd;
do {
rem = CD % base62len;
*--end = base62[int(rem)];
CD /= base62len;
} while (CD > 0);
*--end = '-';
do {
rem = AB % base62len;
*--end = base62[int(rem)];
AB /= base62len;
} while (AB > 0);
return end;
}
//////////////////////////////////////////////////////////////////////////////////////
// UUID implementations
inline uuid uuid4() {
static std::random_device rd;
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
uuid my;
my.ab = dist(rd);
my.cd = dist(rd);
my.ab = (my.ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
my.cd = (my.cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
return my;
}
inline uuid rebuild( uint64_t ab, uint64_t cd ) {
uuid u;
u.ab = ab; u.cd = cd;
return u;
}
inline uuid rebuild( const std::string &uustr ) {
char sep;
uint64_t a,b,c,d,e;
uuid u = { 0, 0 };
auto idx = uustr.find_first_of("-");
if( idx != std::string::npos ) {
// single separator, base62 notation
if( uustr.find_first_of("-",idx+1) == std::string::npos ) {
auto rebase62 = [&]( const char *input, size_t limit ) -> uint64_t {
int base62len = 10 + 26 + 26;
auto strpos = []( char ch ) -> size_t {
if( ch >= 'a' ) return ch - 'a' + 10 + 26;
if( ch >= 'A' ) return ch - 'A' + 10;
return ch - '0';
};
uint64_t res = strpos( input[0] );
for( size_t i = 1; i < limit; ++i )
res = base62len * res + strpos( input[i] );
return res;
};
u.ab = rebase62( &uustr[0], idx );
u.cd = rebase62( &uustr[idx+1], uustr.size() - (idx+1) );
}
// else classic hex notation
else {
std::stringstream ss( uustr );
if( ss >> std::hex >> a >> sep >> b >> sep >> c >> sep >> d >> sep >> e ) {
if( ss.eof() ) {
u.ab = (a << 32) | (b << 16) | c;
u.cd = (d << 48) | e;
}
}
}
}
return u;
}
} // ::sole

View File

@@ -20,7 +20,10 @@ import { type ObjectsRenderingService, type ObjectsEditorService } from '../JsEx
*/
module.exports = {
createExtension: function(_/*: (string) => string */, gd/*: libGDevelop */) {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
const extension = new gd.PlatformExtension();
extension.setExtensionInformation(
'AdMob',
@@ -32,6 +35,24 @@ module.exports = {
'MIT'
);
extension
.registerProperty('AdMobAppId')
.setLabel(_('AdMob App ID'))
.setDescription('ca-app-pub-XXXXXXXXXXXXXXXX/YYYYYYYYYY')
.setType('string');
extension
.addDependency()
.setName('AdMob Cordova Extension')
.setDependencyType('cordova')
.setExportName('cordova-plugin-admob-free')
.setVersion('~0.21.0')
.setExtraSetting(
'ADMOB_APP_ID',
new gd.PropertyDescriptor('AdMobAppId').setType('ExtensionProperty')
)
.onlyIfExtraSettingIsNonEmpty('ADMOB_APP_ID');
// Banner
extension
.addCondition(
@@ -364,7 +385,10 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function(gd /*: libGDevelop */, extension /*: gdPlatformExtension*/) {
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
return [];
},
};

View File

@@ -40,6 +40,26 @@ gdjs.AnchorRuntimeBehavior.VerticalAnchor = {
PROPORTIONAL: 3
};
gdjs.AnchorRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.leftEdgeAnchor !== newBehaviorData.leftEdgeAnchor) {
this._leftEdgeAnchor = newBehaviorData.leftEdgeAnchor;
}
if (oldBehaviorData.rightEdgeAnchor !== newBehaviorData.rightEdgeAnchor) {
this._rightEdgeAnchor = newBehaviorData.rightEdgeAnchor;
}
if (oldBehaviorData.topEdgeAnchor !== newBehaviorData.topEdgeAnchor) {
this._topEdgeAnchor = newBehaviorData.topEdgeAnchor;
}
if (oldBehaviorData.bottomEdgeAnchor !== newBehaviorData.bottomEdgeAnchor) {
this._bottomEdgeAnchor = newBehaviorData.bottomEdgeAnchor;
}
if (oldBehaviorData.relativeToOriginalWindowSize !== newBehaviorData.relativeToOriginalWindowSize) {
return false;
}
return true;
}
gdjs.AnchorRuntimeBehavior.prototype.onActivate = function() {
this._invalidDistances = true;
};

View File

@@ -18,7 +18,11 @@ gdjs.BBTextRuntimeObjectPixiRenderer = function (runtimeObject, runtimeScene) {
.getFontManager()
.getFontFamily(runtimeObject._fontFamily),
fontSize: runtimeObject._fontSize + 'px',
fill: runtimeObject._color,
fill: gdjs.rgbToHexNumber(
runtimeObject._color[0],
runtimeObject._color[1],
runtimeObject._color[2]
),
tagStyle: 'bbcode',
wordWrap: runtimeObject._wordWrap,
wordWrapWidth: runtimeObject._wrappingWidth,
@@ -72,7 +76,11 @@ gdjs.BBTextRuntimeObjectPixiRenderer.prototype.updateText = function () {
};
gdjs.BBTextRuntimeObjectPixiRenderer.prototype.updateColor = function () {
this._pixiObject.textStyles.default.fill = this._object._color;
this._pixiObject.textStyles.default.fill = gdjs.rgbToHexNumber(
this._object._color[0],
this._object._color[1],
this._object._color[2]
);
this._pixiObject.dirty = true;
};

View File

@@ -29,8 +29,8 @@ gdjs.BBTextRuntimeObject = function(runtimeScene, objectData) {
// parseFloat should not be required, but GDevelop 5.0 beta 92 and below were storing it as a string.
/** @type {string} */
this._text = objectData.content.text;
/** @type {string} */
this._color = objectData.content.color;
/** @type {number[]} color in format [r, g, b], where each component is in the range [0, 255] */
this._color = gdjs.BBTextRuntimeObject.hexToRGBColor(objectData.content.color);
/** @type {string} */
this._fontFamily = objectData.content.fontFamily;
/** @type {number} */
@@ -61,18 +61,58 @@ gdjs.BBTextRuntimeObject.prototype = Object.create(
);
gdjs.registerObject('BBText::BBText', gdjs.BBTextRuntimeObject);
gdjs.BBTextRuntimeObject.hexToRGBColor = function (hex) {
var hexNumber = parseInt(hex.replace('#', ''), 16);
return [(hexNumber >> 16) & 0xff, (hexNumber >> 8) & 0xff, hexNumber & 0xff];
};
gdjs.BBTextRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
/**
* @param {BBTextObjectDataType} oldObjectData
* @param {BBTextObjectDataType} newObjectData
*/
gdjs.BBTextRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
this.setOpacity(newObjectData.content.opacity);
}
if (oldObjectData.content.visible !== newObjectData.content.visible) {
this.hide(!newObjectData.content.visible);
}
if (oldObjectData.content.text !== newObjectData.content.text) {
this.setBBText(newObjectData.content.text);
}
if (oldObjectData.content.color !== newObjectData.content.color) {
this._color = gdjs.BBTextRuntimeObject.hexToRGBColor(newObjectData.content.color);
this._renderer.updateColor();
}
if (oldObjectData.content.fontFamily !== newObjectData.content.fontFamily) {
this.setFontFamily(newObjectData.content.fontFamily);
}
if (oldObjectData.content.fontSize !== newObjectData.content.fontSize) {
this.setFontSize(newObjectData.content.fontSize);
}
if (oldObjectData.content.wordWrap !== newObjectData.content.wordWrap) {
this.setWordWrap(newObjectData.content.wordWrap);
}
if (oldObjectData.content.align !== newObjectData.content.align) {
this.setAlignment(newObjectData.content.align);
}
return true;
};
/**
* Initialize the extra parameters that could be set for an instance.
* @private
*/
gdjs.BBTextRuntimeObject.prototype.extraInitializationFromInitialInstance = function(initialInstanceData) {
// The wrapping width value (this._wrappingWidth) is using the object's width as an innitial value
if (initialInstanceData.customSize)
this.setWrappingWidth(initialInstanceData.width);
else
this.setWrappingWidth(250); // This value is the default wrapping width of the runtime object.
};
gdjs.BBTextRuntimeObject.prototype.onDestroyFromScene = function(runtimeScene) {
@@ -97,19 +137,19 @@ gdjs.BBTextRuntimeObject.prototype.getBBText = function() {
gdjs.BBTextRuntimeObject.prototype.setColor = function(rgbColorString) {
const splitValue = rgbColorString.split(';');
if (splitValue.length !== 3) return;
const hexColor =
'#' +
gdjs.rgbToHex(
parseInt(splitValue[0], 0),
parseInt(splitValue[1], 0),
parseInt(splitValue[2], 0)
);
this._color = hexColor;
this._color[0] = parseInt(splitValue[0], 10);
this._color[1] = parseInt(splitValue[1], 10);
this._color[2] = parseInt(splitValue[2], 10);
this._renderer.updateColor();
};
/**
* Get the base color.
* @return {string} The color as a "R;G;B" string, for example: "255;0;0"
*/
gdjs.BBTextRuntimeObject.prototype.getColor = function() {
return this._color;
return this._color[0] + ";" + this._color[1] + ";" + this._color[2];
};
gdjs.BBTextRuntimeObject.prototype.setFontSize = function(fontSize) {

View File

@@ -21,7 +21,7 @@ class GD_EXTENSION_API DestroyOutsideBehavior : public gd::Behavior {
public:
DestroyOutsideBehavior(){};
virtual ~DestroyOutsideBehavior(){};
virtual Behavior* Clone() const { return new DestroyOutsideBehavior(*this); }
virtual Behavior* Clone() const override { return new DestroyOutsideBehavior(*this); }
#if defined(GD_IDE_ONLY)
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(

View File

@@ -20,6 +20,14 @@ gdjs.DestroyOutsideRuntimeBehavior = function(runtimeScene, behaviorData, owner)
gdjs.DestroyOutsideRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("DestroyOutsideBehavior::DestroyOutside", gdjs.DestroyOutsideRuntimeBehavior);
gdjs.DestroyOutsideRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
this._extraBorder = newBehaviorData.extraBorder;
}
return true;
}
gdjs.DestroyOutsideRuntimeBehavior.prototype.doStepPostEvents = function(runtimeScene) {
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point

View File

@@ -32,6 +32,13 @@ module.exports = {
"Open source (MIT License)"
).setExtensionHelpPath("/all-features/device-vibration");
extension
.addDependency()
.setName('Vibration Cordova Extension')
.setDependencyType('cordova')
.setExportName('cordova-plugin-vibration')
.setVersion('3.1.1');
extension
.addAction(
"StartVibration",

View File

@@ -25,6 +25,11 @@ gdjs.DraggableRuntimeBehavior = function(runtimeScene, behaviorData, owner)
gdjs.DraggableRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("DraggableBehavior::Draggable", gdjs.DraggableRuntimeBehavior);
gdjs.DraggableRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
// Nothing to update.
return true;
}
gdjs.DraggableRuntimeBehavior.prototype.onDeActivate = function() {
this._endDrag();
};

View File

@@ -1,124 +1,160 @@
// @ts-check
describe('gdjs.DraggableRuntimeBehavior', function() {
var runtimeGame = new gdjs.RuntimeGame({
variables: [],
resources: {resources: []},
// @ts-ignore
properties: {windowWidth: 800, windowHeight: 600}
});
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers:[{name:"", visibility: true}],
variables: [],
behaviorsSharedData: [],
objects: [],
instances: []
});
describe('gdjs.DraggableRuntimeBehavior', function () {
var runtimeGame = new gdjs.RuntimeGame({
variables: [],
resources: { resources: [] },
// @ts-ignore
properties: { windowWidth: 800, windowHeight: 600 },
});
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [
{
name: '',
visibility: true,
cameras: [],
effects: [],
ambientLightColorR: 127,
ambientLightColorB: 127,
ambientLightColorG: 127,
isLightingLayer: false,
followBaseLayerCamera: false,
},
],
variables: [],
r: 0,
v: 0,
b: 0,
mangledName: 'Scene1',
name: 'Scene1',
stopSoundsOnStartup: false,
title: '',
behaviorsSharedData: [],
objects: [],
instances: [],
});
var object = new gdjs.RuntimeObject(runtimeScene, {name: "obj1", type: "", behaviors: [{name: "Behavior1", type: "DraggableBehavior::Draggable"}], variables: []});
var object2 = new gdjs.RuntimeObject(runtimeScene, {name: "obj1", type: "", behaviors: [{name: "Behavior1", type: "DraggableBehavior::Draggable"}], variables: []});
runtimeScene.addObject(object);
runtimeScene.addObject(object2);
var object = new gdjs.RuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [{ name: 'Behavior1', type: 'DraggableBehavior::Draggable' }],
variables: [],
});
var object2 = new gdjs.RuntimeObject(runtimeScene, {
name: 'obj1',
type: '',
behaviors: [{ name: 'Behavior1', type: 'DraggableBehavior::Draggable' }],
variables: [],
});
runtimeScene.addObject(object);
runtimeScene.addObject(object2);
it('should handle mouse', function() {
object.setPosition(450, 500);
it('should handle mouse', function () {
object.setPosition(450, 500);
//Drag'n'drop
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseMove(450, 500);
runtimeGame.getInputManager().onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseMove(750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
//Drag'n'drop
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseMove(450, 500);
runtimeGame
.getInputManager()
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseMove(750, 600);
runtimeScene.renderAndStep();
runtimeGame
.getInputManager()
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Mouse move with dragging
runtimeGame.getInputManager().onMouseMove(600, 600);
runtimeScene.renderAndStep();
//Mouse move with dragging
runtimeGame.getInputManager().onMouseMove(600, 600);
runtimeScene.renderAndStep();
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Start dragging again
runtimeGame.getInputManager().onMouseMove(750, 600);
runtimeGame.getInputManager().onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseMove(850, 700);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
//Start dragging again
runtimeGame.getInputManager().onMouseMove(750, 600);
runtimeGame
.getInputManager()
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onMouseMove(850, 700);
runtimeScene.renderAndStep();
runtimeGame
.getInputManager()
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep();
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
});
it('should handle touches', function () {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
});
it('should handle touches', function() {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
//Drag'n'drop
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchEnd(0);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
//Drag'n'drop
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchEnd(0);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Move another unrelated touch
runtimeGame.getInputManager().onTouchMove(1, 750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep();
//Move another unrelated touch
runtimeGame.getInputManager().onTouchMove(1, 750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep();
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Start drag'n'drop with another touch
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchStart(1, 750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
//Start drag'n'drop with another touch
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchStart(1, 750, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
});
it('should handle multitouch', function () {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
object2.setPosition(650, 600);
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
});
it('should handle multitouch', function() {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
object2.setPosition(650, 600);
//Drag'n'drop
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchStart(2, 450, 500);
runtimeGame.getInputManager().onTouchStart(1, 650, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchMove(2, 750, 700);
runtimeGame.getInputManager().onTouchMove(1, 100, 200);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchEnd(2);
//Drag'n'drop
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onTouchStart(2, 450, 500);
runtimeGame.getInputManager().onTouchStart(1, 650, 600);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchMove(2, 750, 700);
runtimeGame.getInputManager().onTouchMove(1, 100, 200);
runtimeScene.renderAndStep();
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchEnd(2);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(700);
expect(object2.getX()).to.be(100);
expect(object2.getY()).to.be(200);
});
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(700);
expect(object2.getX()).to.be(100);
expect(object2.getY()).to.be(200);
});
});

View File

@@ -33,6 +33,32 @@ module.exports = {
'MIT'
);
// Register Properties
extension
.registerProperty('DummyPropertyString')
.setLabel(_('Dummy Property Name'))
.setDescription('Type in anything :)')
.setType('string');
extension
.registerProperty('DummyPropertyNumber')
.setLabel(_('Dummy Numeric Property Name'))
.setDescription('Only numbers here ;)')
.setType('number');
extension
.registerProperty('DummyPropertyBoolean')
.setDescription(_('A boolean property'))
.setType('boolean');
// Register Cordova/NPM dependencies
extension
.addDependency()
.setName('Thirteen Checker')
.setDependencyType('npm')
.setExportName('is-thirteen')
.setVersion('2.0.0');
// Declare effects:
const dummyEffect = extension
.addEffect('DummyEffect')

View File

@@ -22,6 +22,12 @@ gdjs.DummyRuntimeBehavior = function(runtimeScene, behaviorData, owner)
gdjs.DummyRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("MyDummyExtension::DummyBehavior", gdjs.DummyRuntimeBehavior);
gdjs.DummyRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.property1 !== newBehaviorData.property1) {
this._textToSet = newBehaviorData.property1;
}
}
gdjs.DummyRuntimeBehavior.prototype.onDeActivate = function() {
};

View File

@@ -1,6 +1,6 @@
/**
* The PIXI.js renderer for the DummyRuntimeObject.
*
*
* @class DummyRuntimeObjectPixiRenderer
* @constructor
* @param {gdjs.DummyRuntimeObject} runtimeObject The object to render
@@ -9,7 +9,7 @@
gdjs.DummyRuntimeObjectPixiRenderer = function(runtimeObject, runtimeScene)
{
this._object = runtimeObject; // Keep a reference to the object to read from it later.
// Here we're going to create a dummy text as an example.
if ( this._text === undefined ) {
this._text = new PIXI.Text(runtimeObject.getText(), {align:"left"});
@@ -38,6 +38,10 @@ gdjs.DummyRuntimeObjectPixiRenderer.prototype.ensureUpToDate = function() {
this.updatePosition();
};
gdjs.DummyRuntimeObjectPixiRenderer.prototype.updateText = function() {
this._text.text = this._object.getText();
};
gdjs.DummyRuntimeObjectPixiRenderer.prototype.updatePosition = function() {
this._text.position.x = this._object.x+this._text.width/2;
this._text.position.y = this._object.y+this._text.height/2;

View File

@@ -28,6 +28,17 @@ gdjs.DummyRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
gdjs.DummyRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
// Compare previous and new data for the object and update it accordingly.
// This is useful for "hot-reloading".
if (oldObjectData.content.property1 !== newObjectData.content.property1) {
this._property1 = newObjectData.content.property1;
this._renderer.updateText();
}
return true;
};
/**
* Called once during the game loop, before events and rendering.
* @param {gdjs.RuntimeScene} runtimeScene The gdjs.RuntimeScene the object belongs to.
@@ -109,7 +120,6 @@ gdjs.DummyRuntimeObject.prototype.getText = function() {
return this._property1;
};
/**
* A dummy method that can be called from events
*/

View File

@@ -23,6 +23,14 @@ gdjs.DummyWithSharedDataRuntimeBehavior = function(runtimeScene, behaviorData, o
gdjs.DummyWithSharedDataRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("MyDummyExtension::DummyBehaviorWithSharedData", gdjs.DummyRuntimeBehavior);
gdjs.DummyWithSharedDataRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.property1 !== newBehaviorData.property1) {
this._textToSet = newBehaviorData.property1;
}
return true;
}
gdjs.DummyWithSharedDataRuntimeBehavior.prototype.onDeActivate = function() {
};

View File

@@ -0,0 +1,429 @@
// @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(
'Lighting',
_('Lights'),
_(
'Allow to display lights on the screen and mark objects as obstacles for the lights.'
),
'Harsimran Virk',
'MIT'
);
const lightObstacleBehavior = new gd.BehaviorJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.updateProperty = function (
behaviorContent,
propertyName,
newValue
) {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.getProperties = function (behaviorContent) {
const behaviorProperties = new gd.MapStringPropertyDescriptor();
return behaviorProperties;
};
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
lightObstacleBehavior.initializeContent = function (behaviorContent) {};
extension
.addBehavior(
'LightObstacleBehavior',
_('Light Obstacle Behavior'),
'LightObstacleBehavior',
_(
'This behavior makes the object an obstacle to the light. The light emitted by light objects will be stopped by the object.'
),
'',
'CppPlatform/Extensions/lightObstacleIcon32.png',
'LightObstacleBehavior',
lightObstacleBehavior,
new gd.BehaviorsSharedData()
)
.setIncludeFile('Extensions/Lighting/lightobstacleruntimebehavior.js')
.addIncludeFile('Extensions/Lighting/lightruntimeobject.js')
.addIncludeFile(
'Extensions/Lighting/lightruntimeobject-pixi-renderer.js'
);
const lightObject = new gd.ObjectJsImplementation();
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.updateProperty = function (
objectContent,
propertyName,
newValue
) {
if (propertyName === 'radius') {
objectContent.radius = parseFloat(newValue);
return true;
}
if (propertyName === 'color') {
objectContent.color = newValue;
return true;
}
if (propertyName === 'debugMode') {
objectContent.debugMode = newValue === '1';
return true;
}
if (propertyName === 'texture') {
objectContent.texture = newValue;
return true;
}
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.getProperties = function (objectContent) {
const objectProperties = new gd.MapStringPropertyDescriptor();
objectProperties.set(
'radius',
new gd.PropertyDescriptor(objectContent.radius.toString())
.setType('number')
.setLabel(_('Radius'))
);
objectProperties.set(
'color',
new gd.PropertyDescriptor(objectContent.color)
.setType('color')
.setLabel(_('Color'))
);
objectProperties.set(
'debugMode',
new gd.PropertyDescriptor(objectContent.debugMode ? 'true' : 'false')
.setType('boolean')
.setLabel(_('Debug mode'))
.setDescription(
_(
'When activated, display the lines used to render the light - useful to understand how the light is rendered on screen.'
)
)
);
objectProperties
.getOrCreate('texture')
.setValue(objectContent.texture)
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Light texture (optional)'))
.setDescription(
_(
"A texture to be used to display the light. If you don't specify a texture, the light is rendered as fading from bright, in its center, to dark."
)
);
return objectProperties;
};
lightObject.setRawJSONContent(
JSON.stringify({
radius: 50,
color: '#ffffff',
debugMode: false,
texture: '',
})
);
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.updateInitialInstanceProperty = function (
objectContent,
instance,
propertyName,
newValue,
project,
layout
) {
return false;
};
// $FlowExpectedError - ignore Flow warning as we're creating an object.
lightObject.getInitialInstanceProperties = function (
content,
instance,
project,
layout
) {
const instanceProperties = new gd.MapStringPropertyDescriptor();
return instanceProperties;
};
const object = extension
.addObject(
'LightObject',
_('Light'),
_(
'Displays a light on the scene, with a customizable radius and color. Add then the Light Obstacle behavior to the objects that must act as obstacle to the lights.'
),
'CppPlatform/Extensions/lightIcon32.png',
lightObject
)
.setIncludeFile('Extensions/Lighting/lightruntimeobject.js')
.addIncludeFile('Extensions/Lighting/lightruntimeobject-pixi-renderer.js')
.addIncludeFile('Extensions/Lighting/lightobstacleruntimebehavior.js');
object
.addAction(
'SetRadius',
_('Set the radius of light object'),
_('Set the radius of light object'),
_('Set the radius of _PARAM0_ to: _PARAM1_'),
'',
'CppPlatform/Extensions/lightIcon24.png',
'CppPlatform/Extensions/lightIcon16.png'
)
.addParameter('object', _('Object'), 'LightObject', false)
.addParameter('expression', _('Radius'), '', false)
.getCodeExtraInformation()
.setFunctionName('setRadius');
object
.addAction(
'SetColor',
_('Set the color of light object'),
_('Set the color of light object in format "R;G;B" string.'),
_('Set the color of _PARAM0_ to: _PARAM1_'),
'',
'res/actions/color24.png',
'res/actions/color.png'
)
.addParameter('object', _('Object'), 'LightObject', false)
.addParameter('string', _('Color'), '', false)
.getCodeExtraInformation()
.setFunctionName('setColor');
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
return [];
},
registerEditorConfigurations: function (
objectsEditorService /*: ObjectsEditorService */
) {
objectsEditorService.registerEditorConfiguration(
'Lighting::LightObject',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
helpPagePath: '/objects/light',
})
);
},
/**
* Register renderers for instance of objects on the scene editor.
*
* Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;
/**
* Renderer for instances of LightObject inside the IDE.
*
* @extends RenderedInstance
* @class RenderedLightObjectInstance
* @constructor
*/
function RenderedLightObjectInstance(
project,
layout,
instance,
associatedObject,
pixiContainer,
pixiResourcesLoader
) {
RenderedInstance.call(
this,
project,
layout,
instance,
associatedObject,
pixiContainer,
pixiResourcesLoader
);
this._radius = parseFloat(
this._associatedObject
.getProperties(this.project)
.get('radius')
.getValue()
);
if (this._radius <= 0) this._radius = 1;
this._colorHex = parseInt(
this._associatedObject
.getProperties(this.project)
.get('color')
.getValue()
.replace('#', ''),
16
);
this._color = [
((this._colorHex >> 16) & 0xff) / 255,
((this._colorHex >> 8) & 0xff) / 255,
(this._colorHex & 0xff) / 255,
];
const geometry = new PIXI.Geometry();
const shader = PIXI.Shader.from(
`
precision mediump float;
attribute vec2 aVertexPosition;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
varying vec2 vPos;
void main() {
vPos = aVertexPosition;
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
}`,
`
precision mediump float;
uniform vec2 center;
uniform float radius;
uniform vec3 color;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
varying vec2 vPos;
void main() {
float l = length(vPos - center);
float intensity = 0.0;
if(l < radius)
intensity = clamp((radius - l)*(radius - l)/(radius*radius), 0.0, 1.0);
gl_FragColor = vec4(color*intensity, 1.0);
}
`,
{
center: [this._instance.getX(), this._instance.getY()],
radius: this._radius,
color: this._color,
}
);
this._vertexBuffer = new Float32Array([
this._instance.getX() - this._radius,
this._instance.getY() + this._radius,
this._instance.getX() + this._radius,
this._instance.getY() + this._radius,
this._instance.getX() + this._radius,
this._instance.getY() - this._radius,
this._instance.getX() - this._radius,
this._instance.getY() - this._radius,
]);
geometry
.addAttribute('aVertexPosition', this._vertexBuffer, 2)
.addIndex([0, 1, 2, 2, 3, 0]);
this._pixiObject = new PIXI.Mesh(geometry, shader);
this._pixiObject.blendMode = PIXI.BLEND_MODES.ADD;
this._pixiContainer.addChild(this._pixiObject);
this.update();
}
RenderedLightObjectInstance.prototype = Object.create(
RenderedInstance.prototype
);
/**
* Return the path to the thumbnail of the specified object.
*/
RenderedLightObjectInstance.getThumbnail = function (
project,
resourcesLoader,
object
) {
return 'CppPlatform/Extensions/lightIcon32.png';
};
/**
* This is called to update the PIXI object on the scene editor
*/
RenderedLightObjectInstance.prototype.update = function () {
this._pixiObject.shader.uniforms.center = new Float32Array([
this._instance.getX(),
this._instance.getY(),
]);
this._vertexBuffer[0] = this._instance.getX() - this._radius;
this._vertexBuffer[1] = this._instance.getY() + this._radius;
this._vertexBuffer[2] = this._instance.getX() + this._radius;
this._vertexBuffer[3] = this._instance.getY() + this._radius;
this._vertexBuffer[4] = this._instance.getX() + this._radius;
this._vertexBuffer[5] = this._instance.getY() - this._radius;
this._vertexBuffer[6] = this._instance.getX() - this._radius;
this._vertexBuffer[7] = this._instance.getY() - this._radius;
this._pixiObject.geometry
.getBuffer('aVertexPosition')
.update(this._vertexBuffer);
};
/**
* Return the width of the instance, when it's not resized.
*/
RenderedLightObjectInstance.prototype.getDefaultWidth = function () {
return this._pixiObject.width;
};
/**
* Return the height of the instance, when it's not resized.
*/
RenderedLightObjectInstance.prototype.getDefaultHeight = function () {
return this._pixiObject.height;
};
RenderedLightObjectInstance.prototype.getOriginX = function () {
return this._radius;
};
RenderedLightObjectInstance.prototype.getOriginY = function () {
return this._radius;
};
objectsRenderingService.registerInstanceRenderer(
'Lighting::LightObject',
RenderedLightObjectInstance
);
},
};

View File

@@ -0,0 +1,153 @@
/**
* @memberof gdjs
* @class LightObstaclesManager
* @param {gdjs.RuntimeScene} runtimeScene
*/
gdjs.LightObstaclesManager = function (runtimeScene) {
this._obstacleRBush = new rbush(9, [
'.owner.getAABB().min[0]',
'.owner.getAABB().min[1]',
'.owner.getAABB().max[0]',
'.owner.getAABB().max[1]',
]);
};
/**
* Get the light obstacles manager of a scene.
* @param {gdjs.RuntimeScene} runtimeScene
* @returns {gdjs.LightObstaclesManager}
*/
gdjs.LightObstaclesManager.getManager = function (runtimeScene) {
if (!runtimeScene._lightObstaclesManager) {
// Create the shared manager if necessary.
runtimeScene._lightObstaclesManager = new gdjs.LightObstaclesManager(
runtimeScene
);
}
return runtimeScene._lightObstaclesManager;
};
/**
* Add a light obstacle to the list of existing obstacles.
* @param {gdjs.LightObstacleRuntimeBehavior} obstacle
*/
gdjs.LightObstaclesManager.prototype.addObstacle = function (obstacle) {
this._obstacleRBush.insert(obstacle);
};
/**
* Remove a light obstacle from the list of existing obstacles. Be sure that the obstacle was
* added before.
* @param {gdjs.LightObstacleRuntimeBehavior} obstacle
*/
gdjs.LightObstaclesManager.prototype.removeObstacle = function (obstacle) {
this._obstacleRBush.remove(obstacle);
};
/**
* Returns all the light obstacles around the specified object.
* @param {gdjs.RuntimeObject} object The object
* @param {number} radius Radius of the area to be searched.
* @param {gdjs.RuntimeObject[]} result An array with all obstacles near the object.
*/
gdjs.LightObstaclesManager.prototype.getAllObstaclesAround = function (
object,
radius,
result
) {
// 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).
var x = object.getX();
var y = object.getY();
var searchArea = gdjs.staticObject(
gdjs.LightObstaclesManager.prototype.getAllObstaclesAround
);
searchArea.minX = x - radius;
searchArea.minY = y - radius;
searchArea.maxX = x + radius;
searchArea.maxY = y + radius;
var nearbyObstacles = this._obstacleRBush.search(searchArea);
result.length = 0;
result.push.apply(result, nearbyObstacles);
};
/**
* @memberof gdjs
* @class LightObstacleRuntimeBehavior
* @param {gdjs.RuntimeScene} runtimeScene
* @param {BehaviorData} behaviorData
* @param {gdjs.RuntimeObject} owner
*/
gdjs.LightObstacleRuntimeBehavior = function (
runtimeScene,
behaviorData,
owner
) {
gdjs.RuntimeBehavior.call(this, runtimeScene, behaviorData, owner);
this._oldX = 0;
this._oldY = 0;
this._oldWidth = 0;
this._oldHeight = 0;
this._manager = gdjs.LightObstaclesManager.getManager(runtimeScene);
this._registeredInManager = false;
};
gdjs.LightObstacleRuntimeBehavior.prototype = Object.create(
gdjs.RuntimeBehavior.prototype
);
gdjs.registerBehavior(
'Lighting::LightObstacleBehavior',
gdjs.LightObstacleRuntimeBehavior
);
gdjs.LightObstacleRuntimeBehavior.prototype.doStepPreEvents = function (
runtimeScene
) {
// Make sure the obstacle is or is not in the obstacles manager.
if (!this.activated() && this._registeredInManager) {
this._manager.removeObstacle(this);
this._registeredInManager = false;
} else if (this.activated() && !this._registeredInManager) {
this._manager.addObstacle(this);
this._registeredInManager = true;
}
//Track changes in size or position
if (
this._oldX !== this.owner.getX() ||
this._oldY !== this.owner.getY() ||
this._oldWidth !== this.owner.getWidth() ||
this._oldHeight !== this.owner.getHeight()
) {
if (this._registeredInManager) {
this._manager.removeObstacle(this);
this._manager.addObstacle(this);
}
this._oldX = this.owner.getX();
this._oldY = this.owner.getY();
this._oldWidth = this.owner.getWidth();
this._oldHeight = this.owner.getHeight();
}
};
gdjs.LightObstacleRuntimeBehavior.prototype.onDestroy = function () {
if (this._manager && this._registeredInManager)
this._manager.removeObstacle(this);
};
gdjs.LightObstacleRuntimeBehavior.prototype.onActivate = function () {
if (this._registeredInManager) return;
this._manager.addObstacle(this);
this._registeredInManager = true;
};
gdjs.LightObstacleRuntimeBehavior.prototype.onDeActivate = function () {
if (!this._registeredInManager) return;
this._manager.removeObstacle(this);
this._registeredInManager = false;
};

View File

@@ -0,0 +1,523 @@
/**
* Pixi renderer for light runtime objects.
*
* @memberof gdjs
* @constructor LightRuntimeObjectPixiRenderer
* @param {gdjs.LightRuntimeObject} runtimeObject
* @param {gdjs.RuntimeScene} runtimeScene
*/
gdjs.LightRuntimeObjectPixiRenderer = function (runtimeObject, runtimeScene) {
this._object = runtimeObject;
this._runtimeScene = runtimeScene;
this._manager = runtimeObject.getObstaclesManager();
this._radius = runtimeObject.getRadius();
var objectColor = runtimeObject._color;
this._color = [
objectColor[0] / 255,
objectColor[1] / 255,
objectColor[2] / 255,
];
/** @type {?PIXI.Texture} */
this._texture = null;
this.updateTexture();
this._center = new Float32Array([runtimeObject.x, runtimeObject.y]);
this._defaultVertexBuffer = new Float32Array(8);
this._vertexBuffer = new Float32Array([
runtimeObject.x - this._radius,
runtimeObject.y + this._radius,
runtimeObject.x + this._radius,
runtimeObject.y + this._radius,
runtimeObject.x + this._radius,
runtimeObject.y - this._radius,
runtimeObject.x - this._radius,
runtimeObject.y - this._radius,
]);
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
/** @type {?PIXI.Mesh} */
this._light = null;
this.updateMesh();
this._isPreview = runtimeScene.getGame().isPreview();
this._debugMode = null;
/** @type {?PIXI.Container} */
this._debugLight = null;
/** @type {?PIXI.Graphics} */
this._debugGraphics = null;
this.updateDebugMode();
/** @type {gdjs.Polygon} */
this._lightBoundingPoly = new gdjs.Polygon();
for (var i = 0; i < 4; i++) {
this._lightBoundingPoly.vertices.push(
runtimeObject.getHitBoxes()[0].vertices[i]
);
}
};
gdjs.LightRuntimeObjectRenderer = gdjs.LightRuntimeObjectPixiRenderer; //Register the class to let the engine use it.
gdjs.LightRuntimeObjectPixiRenderer._defaultIndexBuffer = new Uint16Array([
0,
1,
2,
0,
2,
3,
]);
gdjs.LightRuntimeObjectPixiRenderer.defaultVertexShader = `
precision mediump float;
attribute vec2 aVertexPosition;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
varying vec2 vPos;
void main() {
vPos = aVertexPosition;
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
}`;
gdjs.LightRuntimeObjectPixiRenderer.defaultFragmentShader = `
precision mediump float;
uniform vec2 center;
uniform float radius;
uniform vec3 color;
varying vec2 vPos;
void main() {
float l = length(vPos - center);
float intensity = 0.0;
if(l < radius)
intensity = clamp((radius - l)*(radius - l)/(radius*radius), 0.0, 1.0);
gl_FragColor = vec4(color*intensity, 1.0);
}`;
gdjs.LightRuntimeObjectPixiRenderer.texturedFragmentShader = `
precision mediump float;
uniform vec2 center;
uniform float radius;
uniform vec3 color;
uniform sampler2D uSampler;
varying vec2 vPos;
void main() {
vec2 topleft = vec2(center.x - radius, center.y - radius);
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
gl_FragColor = vec4(color, 1.0) * texture2D(uSampler, texCoord);
}`;
gdjs.LightRuntimeObjectPixiRenderer._verticesWithAngleComparator = function (
vertexWithAngleA,
vertexWithAngleB
) {
if (vertexWithAngleA.angle < vertexWithAngleB.angle) return -1;
if (vertexWithAngleA.angle === vertexWithAngleB.angle) return 0;
if (vertexWithAngleA.angle > vertexWithAngleB.angle) return 1;
};
gdjs.LightRuntimeObjectPixiRenderer._computeClosestIntersectionPoint = function (
lightObject,
angle,
polygons,
boundingSquareHalfDiag
) {
var centerX = lightObject.getX();
var centerY = lightObject.getY();
var targetX = centerX + boundingSquareHalfDiag * Math.cos(angle);
var targetY = centerY + boundingSquareHalfDiag * Math.sin(angle);
var minSqDist = boundingSquareHalfDiag * boundingSquareHalfDiag;
var closestPoint = [null, null];
for (var poly of polygons) {
var raycastResult = gdjs.Polygon.raycastTest(
poly,
centerX,
centerY,
targetX,
targetY
);
if (raycastResult.collision && raycastResult.closeSqDist <= minSqDist) {
minSqDist = raycastResult.closeSqDist;
closestPoint[0] = raycastResult.closeX;
closestPoint[1] = raycastResult.closeY;
}
}
if (closestPoint[0] && closestPoint[1]) return closestPoint;
return null;
};
/**
* @returns {?PIXI.Mesh | PIXI.Container}
*/
gdjs.LightRuntimeObjectPixiRenderer.prototype.getRendererObject = function () {
if (this._debugLight) {
return this._debugLight;
}
return this._light;
};
gdjs.LightRuntimeObjectPixiRenderer.prototype.ensureUpToDate = function () {
if (this._object.isHidden()) return;
if (this._debugGraphics) this._updateDebugGraphics();
this._updateBuffers();
};
gdjs.LightRuntimeObjectPixiRenderer.prototype.updateMesh = function () {
this.updateTexture();
var fragmentShader =
this._texture === null
? gdjs.LightRuntimeObjectPixiRenderer.defaultFragmentShader
: gdjs.LightRuntimeObjectPixiRenderer.texturedFragmentShader;
var shaderUniforms = {
center: this._center,
radius: this._radius,
color: this._color,
};
if (this._texture) {
shaderUniforms.uSampler = this._texture;
}
var shader = PIXI.Shader.from(
gdjs.LightRuntimeObjectPixiRenderer.defaultVertexShader,
fragmentShader,
shaderUniforms
);
var geometry = new PIXI.Geometry();
geometry
.addAttribute('aVertexPosition', this._vertexBuffer, 2)
.addIndex(this._indexBuffer);
if (!this._light) {
this._light = new PIXI.Mesh(geometry, shader);
this._light.blendMode = PIXI.BLEND_MODES.ADD;
} else {
this._light.shader = shader;
this._light.geometry = geometry;
}
};
gdjs.LightRuntimeObjectPixiRenderer.prototype.updateRadius = function () {
this._radius = this._object.getRadius();
this._light.shader.uniforms.radius = this._radius;
};
gdjs.LightRuntimeObjectPixiRenderer.prototype.updateColor = function () {
var objectColor = this._object._color;
this._color = [
objectColor[0] / 255,
objectColor[1] / 255,
objectColor[2] / 255,
];
this._light.shader.uniforms.color = this._color;
};
gdjs.LightRuntimeObjectPixiRenderer.prototype.updateTexture = function () {
var texture = this._object.getTexture();
this._texture =
texture !== ''
? this._runtimeScene.getGame().getImageManager().getPIXITexture(texture)
: null;
};
gdjs.LightRuntimeObjectPixiRenderer.prototype.updateDebugMode = function () {
this._debugMode = this._object.getDebugMode();
if (!this._debugLight && (this._isPreview || this._debugMode)) {
this._debugLight = new PIXI.Container();
this._debugLight.addChild(this._light);
}
if (this._debugMode && !this._debugGraphics) {
this._debugGraphics = new PIXI.Graphics();
this._debugLight.addChild(this._debugGraphics);
}
if (!this._debugMode && this._debugGraphics) {
this._debugLight.removeChild(this._debugGraphics);
this._debugGraphics.destroy();
this._debugGraphics = null;
}
this.ensureUpToDate();
};
gdjs.LightRuntimeObjectPixiRenderer.prototype._updateDebugGraphics = function () {
var computedVertices = this._computeLightVertices();
if (!computedVertices.length) {
this._debugGraphics.clear();
this._debugGraphics
.lineStyle(1, 0xff0000, 1)
.moveTo(this._object.x, this._object.y)
.lineTo(this._object.x - this._radius, this._object.y + this._radius)
.lineTo(this._object.x + this._radius, this._object.y + this._radius)
.moveTo(this._object.x, this._object.y)
.lineTo(this._object.x + this._radius, this._object.y + this._radius)
.lineTo(this._object.x + this._radius, this._object.y - this._radius)
.moveTo(this._object.x, this._object.y)
.lineTo(this._object.x + this._radius, this._object.y - this._radius)
.lineTo(this._object.x - this._radius, this._object.y - this._radius)
.moveTo(this._object.x, this._object.y)
.lineTo(this._object.x - this._radius, this._object.y - this._radius)
.lineTo(this._object.x - this._radius, this._object.y + this._radius);
return;
}
var vertices = new Array(2 * computedVertices.length + 2);
vertices[0] = this._object.x;
vertices[1] = this._object.y;
for (var i = 2; i < 2 * computedVertices.length + 2; i += 2) {
vertices[i] = computedVertices[i / 2 - 1][0];
vertices[i + 1] = computedVertices[i / 2 - 1][1];
}
this._debugGraphics.clear();
this._debugGraphics.moveTo(vertices[2], vertices[3]);
var verticesCount = vertices.length;
for (var i = 2; i < verticesCount; i += 2) {
var lineColor = i % 4 === 0 ? 0xff0000 : 0x00ff00;
var lastX = i + 2 >= verticesCount ? 2 : i + 2;
var lastY = i + 3 >= verticesCount ? 3 : i + 3;
this._debugGraphics
.lineStyle(1, lineColor, 1)
.lineTo(vertices[i], vertices[i + 1])
.lineTo(vertices[lastX], vertices[lastY])
.moveTo(vertices[0], vertices[1])
.lineTo(vertices[i], vertices[i + 1])
.moveTo(vertices[0], vertices[1])
.lineTo(vertices[lastX], vertices[lastY]);
}
};
gdjs.LightRuntimeObjectPixiRenderer.prototype._updateBuffers = function () {
this._center[0] = this._object.x;
this._center[1] = this._object.y;
this._light.shader.uniforms.center = this._center;
var vertices = this._computeLightVertices();
// Fallback to simple quad when there are no obstacles around.
if (vertices.length === 0) {
this._defaultVertexBuffer[0] = this._object.x - this._radius;
this._defaultVertexBuffer[1] = this._object.y + this._radius;
this._defaultVertexBuffer[2] = this._object.x + this._radius;
this._defaultVertexBuffer[3] = this._object.y + this._radius;
this._defaultVertexBuffer[4] = this._object.x + this._radius;
this._defaultVertexBuffer[5] = this._object.y - this._radius;
this._defaultVertexBuffer[6] = this._object.x - this._radius;
this._defaultVertexBuffer[7] = this._object.y - this._radius;
this._light.geometry
.getBuffer('aVertexPosition')
.update(this._defaultVertexBuffer);
this._light.geometry
.getIndex()
.update(gdjs.LightRuntimeObjectPixiRenderer._defaultIndexBuffer);
return;
}
var verticesCount = vertices.length;
// If the array buffer which is already allocated is atmost
// twice the size of memory required, we could avoid re-allocation
// and instead use a subarray. Otherwise, allocate new array buffers as
// there would be memory wastage.
var isSubArrayUsed = false;
var vertexBufferSubArray = null;
var indexBufferSubArray = null;
if (this._vertexBuffer.length > 2 * verticesCount + 2) {
if (this._vertexBuffer.length < 4 * verticesCount + 4) {
isSubArrayUsed = true;
vertexBufferSubArray = this._vertexBuffer.subarray(
0,
2 * verticesCount + 2
);
indexBufferSubArray = this._indexBuffer.subarray(0, 3 * verticesCount);
} else {
this._vertexBuffer = new Float32Array(2 * verticesCount + 2);
this._indexBuffer = new Uint16Array(3 * verticesCount);
}
}
// When the allocated array buffer has less memory than
// required, we'll have to allocated new array buffers.
if (this._vertexBuffer.length < 2 * verticesCount + 2) {
this._vertexBuffer = new Float32Array(2 * verticesCount + 2);
this._indexBuffer = new Uint16Array(3 * verticesCount);
}
this._vertexBuffer[0] = this._object.x;
this._vertexBuffer[1] = this._object.y;
for (var i = 2; i < 2 * verticesCount + 2; i += 2) {
this._vertexBuffer[i] = vertices[i / 2 - 1][0];
this._vertexBuffer[i + 1] = vertices[i / 2 - 1][1];
}
for (var i = 0; i < 3 * verticesCount; i += 3) {
this._indexBuffer[i] = 0;
this._indexBuffer[i + 1] = i / 3 + 1;
if (i / 3 + 1 !== verticesCount) this._indexBuffer[i + 2] = i / 3 + 2;
else this._indexBuffer[i + 2] = 1;
}
if (!isSubArrayUsed) {
this._light.geometry
.getBuffer('aVertexPosition')
.update(this._vertexBuffer);
this._light.geometry.getIndex().update(this._indexBuffer);
} else {
this._light.geometry
.getBuffer('aVertexPosition')
.update(vertexBufferSubArray);
this._light.geometry.getIndex().update(indexBufferSubArray);
}
};
/**
* Computes the vertices of mesh using raycasting.
* @returns {number[][]} the vertices of mesh.
*/
gdjs.LightRuntimeObjectPixiRenderer.prototype._computeLightVertices = function () {
var lightObstacles = [];
if (this._manager)
this._manager.getAllObstaclesAround(
this._object,
this._radius,
lightObstacles
);
// Bail out early if there are no obstacles.
if (lightObstacles.length === 0) return lightObstacles;
// Synchronize light bounding polygon with the hitbox.
var lightHitboxPoly = this._object.getHitBoxes()[0];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 2; j++) {
this._lightBoundingPoly.vertices[i][j] = lightHitboxPoly.vertices[i][j];
}
}
var obstaclesCount = lightObstacles.length;
var obstacleHitBoxes = new Array(obstaclesCount);
for (var i = 0; i < obstaclesCount; i++) {
obstacleHitBoxes[i] = lightObstacles[i].owner.getHitBoxes();
}
var obstaclePolygons = [];
obstaclePolygons.push(this._lightBoundingPoly);
for (var i = 0; i < obstaclesCount; i++) {
var noOfHitBoxes = obstacleHitBoxes[i].length;
for (var j = 0; j < noOfHitBoxes; j++)
obstaclePolygons.push(obstacleHitBoxes[i][j]);
}
var maxX = this._object.x + this._radius;
var minX = this._object.x - this._radius;
var maxY = this._object.y + this._radius;
var minY = this._object.y - this._radius;
var flattenVertices = [];
for (var i = 1; i < obstaclePolygons.length; i++) {
var vertices = obstaclePolygons[i].vertices;
var verticesCount = vertices.length;
for (var j = 0; j < verticesCount; j++) {
flattenVertices.push(vertices[j]);
if (vertices[j][0] < minX) minX = vertices[j][0];
if (vertices[j][0] > maxX) maxX = vertices[j][0];
if (vertices[j][1] < minY) minY = vertices[j][1];
if (vertices[j][1] > maxY) maxY = vertices[j][1];
}
}
obstaclePolygons[0].vertices[0][0] = minX;
obstaclePolygons[0].vertices[0][1] = minY;
obstaclePolygons[0].vertices[1][0] = maxX;
obstaclePolygons[0].vertices[1][1] = minY;
obstaclePolygons[0].vertices[2][0] = maxX;
obstaclePolygons[0].vertices[2][1] = maxY;
obstaclePolygons[0].vertices[3][0] = minX;
obstaclePolygons[0].vertices[3][1] = maxY;
// Find the largest diagonal length.
var boundingSquareHalfDiag = Math.sqrt(
Math.max(
(this._object.x - minX) * (this._object.x - minX) +
(this._object.y - minY) * (this._object.y - minY),
(maxX - this._object.x) * (maxX - this._object.x) +
(this._object.y - minY) * (this._object.y - minY),
(maxX - this._object.x) * (maxX - this._object.x) +
(maxY - this._object.y) * (maxY - this._object.y),
(this._object.x - minX) * (this._object.x - minX) +
(maxY - this._object.y) * (maxY - this._object.y)
)
);
for (var i = 0; i < 4; i++) {
flattenVertices.push(obstaclePolygons[0].vertices[i]);
}
var closestVertices = [];
var flattenVerticesCount = flattenVertices.length;
for (var i = 0; i < flattenVerticesCount; i++) {
var xdiff = flattenVertices[i][0] - this._object.x;
var ydiff = flattenVertices[i][1] - this._object.y;
var angle = Math.atan2(ydiff, xdiff);
var closestVertex = gdjs.LightRuntimeObjectPixiRenderer._computeClosestIntersectionPoint(
this._object,
angle,
obstaclePolygons,
boundingSquareHalfDiag
);
if (closestVertex) {
closestVertices.push({
vertex: closestVertex,
angle: angle,
});
}
// TODO: Check whether we need to raycast these two extra rays or not.
var closestVertexOffsetLeft = gdjs.LightRuntimeObjectPixiRenderer._computeClosestIntersectionPoint(
this._object,
angle + 0.0001,
obstaclePolygons,
boundingSquareHalfDiag
);
if (closestVertexOffsetLeft) {
closestVertices.push({
vertex: closestVertexOffsetLeft,
angle: angle + 0.0001,
});
}
var closestVertexOffsetRight = gdjs.LightRuntimeObjectPixiRenderer._computeClosestIntersectionPoint(
this._object,
angle - 0.0001,
obstaclePolygons,
boundingSquareHalfDiag
);
if (closestVertexOffsetRight) {
closestVertices.push({
vertex: closestVertexOffsetRight,
angle: angle - 0.0001,
});
}
}
closestVertices.sort(
gdjs.LightRuntimeObjectPixiRenderer._verticesWithAngleComparator
);
var filteredVerticesResult = [closestVertices[0].vertex];
var closestVerticesCount = closestVertices.length;
for (var i = 1; i < closestVerticesCount; i++) {
if (closestVertices[i].angle !== closestVertices[i - 1].angle)
filteredVerticesResult.push(closestVertices[i].vertex);
}
return filteredVerticesResult;
};

View File

@@ -0,0 +1,193 @@
/**
* @typedef {Object} LightObjectDataType
* @property {Object} content The base parameters of light object.
* @property {number} content.radius The radius of light object.
* @property {string} content.color A string representing color in hexadecimal format.
* @property {string} content.texture A string representing the name of texture used for light object.
* @property {boolean} content.debugMode true if the light objects shows debug graphics, false otherwise.
*
* @typedef {ObjectData & LightObjectDataType} LightObjectData
*/
/**
* Displays a Light object.
* @memberof gdjs
* @class LightRuntimeObject
* @extends RuntimeObject
* @param {gdjs.RuntimeScene} runtimeScene
* @param {LightObjectData} lightObjectData
*/
gdjs.LightRuntimeObject = function (runtimeScene, lightObjectData) {
gdjs.RuntimeObject.call(this, runtimeScene, lightObjectData);
/** @type {number} */
this._radius =
lightObjectData.content.radius > 0 ? lightObjectData.content.radius : 1;
/** @type {number[]} color in format [r, g, b], where each component is in the range [0, 255] */
this._color = gdjs.LightRuntimeObject.hexToRGBColor(
lightObjectData.content.color
);
/** @type {boolean} */
this._debugMode = lightObjectData.content.debugMode;
/** @type {string} */
this._texture = lightObjectData.content.texture;
/** @type {gdjs.LightObstaclesManager} */
this._obstaclesManager = gdjs.LightObstaclesManager.getManager(runtimeScene);
if (this._renderer)
gdjs.LightRuntimeObjectRenderer.call(this._renderer, this, runtimeScene);
else this._renderer = new gdjs.LightRuntimeObjectRenderer(this, runtimeScene);
/** @type {gdjs.RuntimeScene} */
this._runtimeScene = runtimeScene;
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
};
gdjs.LightRuntimeObject.prototype = Object.create(gdjs.RuntimeObject.prototype);
gdjs.registerObject('Lighting::LightObject', gdjs.LightRuntimeObject);
gdjs.LightRuntimeObject.hexToRGBColor = function (hex) {
var hexNumber = parseInt(hex.replace('#', ''), 16);
return [(hexNumber >> 16) & 0xff, (hexNumber >> 8) & 0xff, hexNumber & 0xff];
};
gdjs.LightRuntimeObject.prototype.getRendererObject = function () {
return this._renderer.getRendererObject();
};
/**
*
* @param {LightObjectData} oldObjectData
* @param {LightObjectData} newObjectData
*/
gdjs.LightRuntimeObject.prototype.updateFromObjectData = function (
oldObjectData,
newObjectData
) {
if (oldObjectData.content.radius !== newObjectData.content.radius)
this.setRadius(newObjectData.content.radius);
if (oldObjectData.content.color !== newObjectData.content.radius) {
this._color = gdjs.LightRuntimeObject.hexToRGBColor(
newObjectData.content.color
);
this._renderer.updateColor();
}
if (oldObjectData.content.texture !== newObjectData.content.texture) {
this._texture = newObjectData.content.texture;
this._renderer.updateMesh();
}
if (oldObjectData.content.debugMode !== newObjectData.content.debugMode) {
this._debugMode = newObjectData.content.debugMode;
this._renderer.updateDebugMode();
}
return true;
};
gdjs.LightRuntimeObject.prototype.update = function () {
this._renderer.ensureUpToDate();
};
/**
* Get the radius of the light object.
* @returns {number} radius of the light object.
*/
gdjs.LightRuntimeObject.prototype.getRadius = function () {
return this._radius;
};
/**
* Set the radius of the light object.
* @param {number} radius
*/
gdjs.LightRuntimeObject.prototype.setRadius = function (radius) {
this._radius = radius > 0 ? radius : 1;
this._renderer.updateRadius();
};
/**
* Get the height of the light object.
* @returns {number} height of light object.
*/
gdjs.LightRuntimeObject.prototype.getHeight = function () {
return 2 * this._radius;
};
/**
* Get the width of the light object.
* @returns {number} width of light object.
*/
gdjs.LightRuntimeObject.prototype.getWidth = function () {
return 2 * this._radius;
};
/**
* Get the x co-ordinate of the top-left vertex/point of light object.
* @returns {number} x co-ordinate of the top-left vertex/point.
*/
gdjs.LightRuntimeObject.prototype.getDrawableX = function () {
return this.x - this._radius;
};
/**
* Get the y co-ordinate of the top-left vertex/point of light object.
* @returns {number} y co-ordinate of the top-left vertex/point.
*/
gdjs.LightRuntimeObject.prototype.getDrawableY = function () {
return this.y - this._radius;
};
/**
* Get the color of the light object as a "R;G;B" string.
* @returns {string} the color of light object in "R;G;B" format.
*/
gdjs.LightRuntimeObject.prototype.getColor = function () {
return this._color[0] + ';' + this._color[1] + ';' + this._color[2];
};
/**
* Set the color of the light object in format "R;G;B" string, with components in the range of [0-255].
* @param {string} color
*/
gdjs.LightRuntimeObject.prototype.setColor = function (color) {
var rgbColor = color.split(';');
this._color = [
parseInt(rgbColor[0], 10),
parseInt(rgbColor[1], 10),
parseInt(rgbColor[2], 10),
];
this._renderer.updateColor();
};
/**
* Get the light obstacles manager if objects with the behavior exist, null otherwise.
* @returns {?gdjs.LightObstaclesManager} gdjs.LightObstaclesManager if it exists, otherwise null.
*/
gdjs.LightRuntimeObject.prototype.getObstaclesManager = function () {
return this._obstaclesManager;
};
/**
* Returns true if the light shows debug graphics, false otherwise.
* @returns {boolean} true if debug mode is activated.
*/
gdjs.LightRuntimeObject.prototype.getDebugMode = function () {
return this._debugMode;
};
/**
* Returns the path of texture resource.
* @returns {string} the path of texture.
*/
gdjs.LightRuntimeObject.prototype.getTexture = function () {
return this._texture;
};

View File

@@ -0,0 +1,191 @@
/**
* Tests for Light Object
*/
/**
* Utility function for adding light object for tests.
* @param {gdjs.RuntimeScene} runtimeScene
* @param {number} radius
* @returns {gdjs.LightRuntimeObject}
*/
const addLightObject = (runtimeScene, radius) => {
const lightObj = new gdjs.LightRuntimeObject(runtimeScene, {
name: 'lightObject',
type: 'Lighting::LightObject',
variables: [],
behaviors: [],
content: {
radius: radius,
color: '#b4b4b4',
texture: '',
debugMode: false,
},
});
runtimeScene.addObject(lightObj);
return lightObj;
};
/**
* Utility function for adding light obstacle for tests.
* @param {gdjs.RuntimeScene} runtimeScene
* @param {number} width
* @param {number} height
* @returns {gdjs.RuntimeObject}
*/
const addLightObstacle = (runtimeScene, width, height) => {
const obstacle = new gdjs.RuntimeObject(runtimeScene, {
name: 'lightObstacle',
type: '',
behaviors: [
{
type: 'Lighting::LightObstacleBehavior',
},
],
});
obstacle.getWidth = function () {
return width;
};
obstacle.getHeight = function () {
return height;
};
runtimeScene.addObject(obstacle);
return obstacle;
};
describe('gdjs.LightRuntimeObject', function () {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
resources: {
resources: [],
},
properties: { windowWidth: 800, windowHeight: 600 },
});
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
const lightObj = addLightObject(runtimeScene, 100);
lightObj.setPosition(200, 200);
it('check object properties', function () {
expect(lightObj.getRadius()).to.be(100);
expect(lightObj.getColor()).to.eql("180;180;180");
expect(lightObj.getDebugMode()).to.be(false);
expect(lightObj.getDrawableX()).to.be(100);
expect(lightObj.getDrawableY()).to.be(100);
});
it('bail out early while raycasting when there is no light obstacle', function () {
expect(lightObj._renderer._computeLightVertices()).to.eql([]);
lightObj._renderer._updateBuffers();
expect(lightObj._renderer._defaultVertexBuffer).to.eql(
new Float32Array([100, 300, 300, 300, 300, 100, 100, 100])
);
expect(gdjs.LightRuntimeObjectPixiRenderer._defaultIndexBuffer).to.eql(
new Float32Array([0, 1, 2, 0, 2, 3])
);
});
});
describe('Light with obstacles around it', function () {
const runtimeGame = new gdjs.RuntimeGame({
variables: [],
resources: {
resources: [],
},
properties: { windowWidth: 800, windowHeight: 600 },
});
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [{ name: '', visibility: true, effects: [] }],
variables: [],
behaviorsSharedData: [],
objects: [],
instances: [],
});
runtimeScene._timeManager.getElapsedTime = function () {
return (1 / 60) * 1000;
};
const light = addLightObject(runtimeScene, 100);
const obstacle = addLightObstacle(runtimeScene, 50, 50);
it('Vertex and index buffers when light obstacle is present.', function () {
light.setPosition(200, 200);
obstacle.setPosition(250, 250);
runtimeScene.renderAndStep();
light.update();
const vertexBuffer = light._renderer._vertexBuffer;
const indexBuffer = light._renderer._indexBuffer;
// prettier-ignore
const expectedVertexBuffer = [
200, 200, 100, 100.0199966430664, 100, 100, 100.0199966430664, 100, 299.9800109863281,
100, 300, 100, 300, 100.0199966430664, 300, 249.9875030517578, 300, 250, 299.9750061035156,
250, 250.00999450683594, 250, 250, 250, 250,250.00999450683594, 250, 299.9750061035156, 250,
300, 249.9875030517578, 300, 100.0199966430664, 300, 100, 300, 100, 299.9800109863281,
];
// prettier-ignore
const expectedIndexBuffer = [
0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6,
0, 6, 7, 0, 7, 8, 0, 8, 9, 0, 9, 10, 0, 10, 11,
0, 11, 12, 0, 12, 13, 0, 13, 14, 0, 14, 15, 0,
15, 16, 0, 16, 17, 0, 17, 18, 0, 18, 1,
];
expectedVertexBuffer.forEach((val, index) => {
expect(vertexBuffer[index]).to.be(val);
});
expectedIndexBuffer.forEach((val, index) => {
expect(indexBuffer[index]).to.be(val);
});
});
it('Vertex and index buffers after obstacle is moved.', function () {
obstacle.setPosition(150, 250);
runtimeScene.renderAndStep();
light.update();
const vertexBuffer = light._renderer._vertexBuffer;
const indexBuffer = light._renderer._indexBuffer;
// prettier-ignore
const expectedVertexBuffer = [
200, 200, 100, 100.0199966430664, 100, 100, 100.0199966430664, 100, 299.9800109863281,
100, 300, 100, 300, 100.0199966430664, 300, 299.9800109863281, 300, 300, 299.9800109863281,
300, 200.00999450683594, 300, 200, 250, 199.9949951171875, 250, 175.00625610351562, 250,
175, 250, 174.99374389648438, 250, 150.00999450683594, 250, 150, 250, 100, 299.9800109863281,
];
// prettier-ignore
const expectedIndexBuffer = [
0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0,
6, 7, 0, 7, 8, 0, 8, 9, 0, 9, 10, 0, 10, 11, 0,
11, 12, 0, 12, 13, 0, 13, 14, 0, 14, 15, 0, 15,
16, 0, 16, 17, 0, 17, 18, 0, 18, 1,
];
expectedVertexBuffer.forEach((val, index) => {
expect(vertexBuffer[index]).to.be(val);
});
expectedIndexBuffer.forEach((val, index) => {
expect(indexBuffer[index]).to.be(val);
});
});
it("Obstacle moved outside light's radius.", function () {
obstacle.setPosition(400, 400);
runtimeScene.renderAndStep();
light.update();
// Ensure the fallback to simple quads. There shouldn't be anymore calculations
// when the obstacle is not inside light's area.
expect(light._renderer._computeLightVertices().length).to.eql(0);
const vertexBuffer = light._renderer._defaultVertexBuffer;
const indexBuffer = gdjs.LightRuntimeObjectPixiRenderer._defaultIndexBuffer;
const vertexData = [100, 300, 300, 300, 300, 100, 100, 100];
const indexData = [0, 1, 2, 0, 2, 3];
vertexData.forEach((val, index) => {
expect(vertexBuffer[index]).to.be(val);
});
indexData.forEach((val, index) => {
expect(indexBuffer[index]).to.be(val);
});
});
});

View File

@@ -49,7 +49,7 @@ gdjs.LinksManager.prototype.removeAllLinksOf = function(obj) {
if ( this.links.hasOwnProperty(objLinkedObjects[i].id) ) {
var otherObjList = this.links[objLinkedObjects[i].id];
var index = otherObjList.indexOf(obj);
if ( index !== -1) otherObjList.remove(index);
if ( index !== -1) otherObjList.splice(index, 1);
}
}
@@ -63,13 +63,13 @@ gdjs.LinksManager.prototype.removeLinkBetween = function(objA, objB) {
if ( this.links.hasOwnProperty(objA.id) ) {
list = this.links[objA.id];
index = list.indexOf(objB);
if ( index !== -1) list.remove(index);
if ( index !== -1) list.splice(index, 1);
}
if ( this.links.hasOwnProperty(objB.id) ) {
list = this.links[objB.id];
index = list.indexOf(objA);
if ( index !== -1) list.remove(index);
if ( index !== -1) list.splice(index, 1);
}
};

View File

@@ -3,7 +3,7 @@ describe('gdjs.LinksManager', function() {
var runtimeGame = new gdjs.RuntimeGame({variables: [], properties: {windowWidth: 800, windowHeight: 600}, resources: {resources: []}});
var runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers:[{name:"", visibility: true}],
layers:[{name:"", visibility: true, effects: []}],
variables: [],
behaviorsSharedData: [],
objects: [],

71
Extensions/P2P/A_peer.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,339 @@
// @ts-check
/// <reference path="peerjs" />
/**
* Tools for p2p multiplayer.
* @namespace
*/
gdjs.evtTools.p2p = {
/**
* The peer to peer configuration.
*/
peerConfig: { debug: 1 }, // Enable logging of critical errors
/**
* The p2p client.
* @type {?Peer}
*/
peer: null,
/**
* All connected p2p clients, keyed by their id.
*/
connections: {},
/**
* Contains a list of events triggered by other p2p clients.
*/
triggeredEvents: {},
/**
* Contains the latest data sent with each event.
*/
lastEventData: {},
/**
* Tells how to handle an event (with or without data loss)
*/
eventHandling: {},
/**
* True if PeerJS is initialized and ready.
*/
ready: false,
/**
* True if an error occured.
*/
error: false,
/**
* Last error's message.
*/
lastError: '',
/**
* True if a peer diconnected.
*/
peerJustDisconnected: false,
/**
* The last peer that has disconnected.
*/
lastDisconnectedPeerId: '',
};
gdjs.evtTools.p2p.loadPeerJS = function () {
if (gdjs.evtTools.p2p.peer != null) return;
gdjs.evtTools.p2p.peer = new Peer(gdjs.evtTools.p2p.peerConfig);
gdjs.evtTools.p2p.peer.on('open', function () {
gdjs.evtTools.p2p.ready = true;
});
gdjs.evtTools.p2p.peer.on('error', function (errorMessage) {
gdjs.evtTools.p2p.error = true;
gdjs.evtTools.p2p.lastError = errorMessage;
});
gdjs.evtTools.p2p.peer.on('connection', gdjs.evtTools.p2p._onConnection);
gdjs.evtTools.p2p.peer.on('close', function () {
gdjs.evtTools.p2p.peer = null;
gdjs.evtTools.p2p.loadPeerJS();
});
gdjs.evtTools.p2p.peer.on('disconnected', gdjs.evtTools.p2p.peer.reconnect);
};
gdjs.evtTools.p2p._onConnection = function (connection) {
gdjs.evtTools.p2p.connections[connection.peer] = connection;
connection.on('data', function (data) {
if (data.eventName === undefined) return;
var dataLoss = gdjs.evtTools.p2p.eventHandling[data.eventName];
if (typeof dataLoss === 'undefined' || dataLoss === false) {
if (typeof gdjs.evtTools.p2p.lastEventData[data.eventName] !== 'object')
gdjs.evtTools.p2p.lastEventData[data.eventName] = [];
gdjs.evtTools.p2p.lastEventData[data.eventName].push(data.data);
} else {
gdjs.evtTools.p2p.triggeredEvents[data.eventName] = true;
gdjs.evtTools.p2p.lastEventData[data.eventName] = data.data;
}
});
connection.on('error', function () {
// Close event is only for graceful disconnection, also handle error aka ungraceful disconnection
gdjs.evtTools.p2p._onDisconnect(connection.peer);
});
connection.on('close', function () {
gdjs.evtTools.p2p._onDisconnect(connection.peer);
});
// Regularly check for disconnection as the built in way is not reliable.
var disconnectChecker = function () {
if (
connection.peerConnection.connectionState === 'failed' ||
connection.peerConnection.connectionState === 'disconnected'
) {
gdjs.evtTools.p2p._onDisconnect(connection.peer);
} else {
setTimeout(disconnectChecker, 500);
}
};
disconnectChecker();
};
gdjs.evtTools.p2p._onDisconnect = function (connectionID) {
gdjs.evtTools.p2p.peerJustDisconnected = true;
gdjs.evtTools.p2p.lastDisconnectedPeerId = connectionID;
delete gdjs.evtTools.p2p.connections[connectionID];
};
/**
* Connects to another p2p client.
* @param {string} id - The other client's id.
*/
gdjs.evtTools.p2p.connect = function (id) {
var connection = gdjs.evtTools.p2p.peer.connect(id);
connection.on('open', function () {
gdjs.evtTools.p2p._onConnection(connection);
});
};
/**
* Returns true when the event got triggered by another p2p client.
* @param {string} eventName
* @param {boolean} _dataLoss Is data loss allowed (accelerates event handling when true)?
* @returns {boolean}
*/
gdjs.evtTools.p2p.onEvent = function (eventName, _dataLoss) {
var dataLoss = gdjs.evtTools.p2p.eventHandling[eventName];
if (dataLoss == undefined) {
gdjs.evtTools.p2p.eventHandling[eventName] = _dataLoss;
return gdjs.evtTools.p2p.onEvent(eventName, _dataLoss);
}
if (dataLoss) {
var returnValue = gdjs.evtTools.p2p.triggeredEvents[eventName];
if (typeof returnValue === 'undefined') return false;
gdjs.evtTools.p2p.triggeredEvents[eventName] = false;
return returnValue;
} else {
var returnValue = gdjs.evtTools.p2p.lastEventData[eventName];
if (typeof returnValue === 'undefined') return false;
return returnValue.length !== 0;
}
};
/**
* Send an event to one specific connected client.
* @param {string} id - The id of the client to send the event to.
* @param {string} eventName - The event to trigger.
* @param {string} [eventData] - Additional data to send with the event.
*/
gdjs.evtTools.p2p.sendDataTo = function (id, eventName, eventData) {
if (gdjs.evtTools.p2p.connections[id])
gdjs.evtTools.p2p.connections[id].send({
eventName: eventName,
data: eventData,
});
};
/**
* Send an event to all connected clients.
* @param {string} eventName - The event to trigger.
* @param {string} [eventData] - Additional data to send with the event.
*/
gdjs.evtTools.p2p.sendDataToAll = function (eventName, eventData) {
for (var id in gdjs.evtTools.p2p.connections) {
gdjs.evtTools.p2p.connections[id].send({
eventName: eventName,
data: eventData,
});
}
};
/**
* Send an event to one specific connected client.
* @param {string} id - The id of the client to send the event to.
* @param {string} eventName - The event to trigger.
* @param {gdjs.Variable} variable - Additional variable to send with the event.
*/
gdjs.evtTools.p2p.sendVariableTo = function (id, eventName, variable) {
if (gdjs.evtTools.p2p.connections[id])
gdjs.evtTools.p2p.connections[id].send({
eventName: eventName,
data: gdjs.evtTools.network.variableStructureToJSON(variable),
});
};
/**
* Send an event to all connected clients.
* @param {string} eventName - The event to trigger.
* @param {gdjs.Variable} variable - Additional variable to send with the event.
*/
gdjs.evtTools.p2p.sendVariableToAll = function (eventName, variable) {
for (var id in gdjs.evtTools.p2p.connections) {
gdjs.evtTools.p2p.connections[id].send({
eventName: eventName,
data: gdjs.evtTools.network.variableStructureToJSON(variable),
});
}
};
/**
* Get some data associated to the last trigger of an event.
* @param {string} eventName - The event to get data from.
* @returns {string} - The data as JSON.
*/
gdjs.evtTools.p2p.getEventData = function (eventName) {
var dataLoss = gdjs.evtTools.p2p.eventHandling[eventName];
if (typeof dataLoss === 'undefined' || dataLoss === false) {
var event = gdjs.evtTools.p2p.lastEventData[eventName];
return event[event.length - 1];
} else {
return gdjs.evtTools.p2p.lastEventData[eventName];
}
};
/**
* Get a variable associated to the last trigger of an event.
* @param {string} eventName - The event to get the variable from.
* @param {gdjs.Variable} variable - The variable where to store the variable content.
*/
gdjs.evtTools.p2p.getEventVariable = function (eventName, variable) {
gdjs.evtTools.network.jsonToVariableStructure(
gdjs.evtTools.p2p.getEventData(eventName),
variable
);
};
/**
* Connects to a custom broker server.
* @param {string} host The host of the broker server.
* @param {number} port The port of the broker server.
* @param {string} path The path (part of the url after the host) to the broker server.
* @param {string} key Optional password to connect to the broker server.
* @param {boolean} ssl Use ssl?
*/
gdjs.evtTools.p2p.useCustomBrokerServer = function (
host,
port,
path,
key,
ssl
) {
key = key.length === 0 ? 'peerjs' : key; // All servers have "peerjs" as default key
gdjs.evtTools.p2p.peerConfig = {
debug: 1,
host,
port,
path,
secure: ssl,
key,
};
gdjs.evtTools.p2p.loadPeerJS();
};
/**
* Use default broker server.
* This is not recommended for published games,
* this server should only be used for quick testing in development.
*/
gdjs.evtTools.p2p.useDefaultBrokerServer = function () {
gdjs.evtTools.p2p.loadPeerJS();
};
/**
* Returns the own current peer ID
* @see Peer.id
* @returns {string}
*/
gdjs.evtTools.p2p.getCurrentId = function () {
if (gdjs.evtTools.p2p.peer == undefined) return '';
return gdjs.evtTools.p2p.peer.id || '';
};
/**
* Returns true once PeerJS is initialized
* @see gdjs.evtTools.p2p.ready
* @returns {boolean}
*/
gdjs.evtTools.p2p.isReady = function () {
return gdjs.evtTools.p2p.ready;
};
/**
* Returns true once when there is an error.
* @returns {boolean}
*/
gdjs.evtTools.p2p.onError = function () {
var returnValue = gdjs.evtTools.p2p.error;
gdjs.evtTools.p2p.error = false;
return returnValue;
};
/**
* Returns the latest error message.
* @returns {boolean}
*/
gdjs.evtTools.p2p.getLastError = function () {
return gdjs.evtTools.p2p.lastError;
};
/**
* Returns true once a peer disconnected.
* @returns {boolean}
*/
gdjs.evtTools.p2p.onDisconnect = function () {
var returnValue = gdjs.evtTools.p2p.peerJustDisconnected;
gdjs.evtTools.p2p.peerJustDisconnected = false;
return returnValue;
};
gdjs.evtTools.p2p.getDisconnectedPeer = function () {
return gdjs.evtTools.p2p.lastDisconnectedPeerId;
};
gdjs.callbacksRuntimeScenePostEvents.push(function () {
for (var i in gdjs.evtTools.p2p.lastEventData) {
if (
typeof gdjs.evtTools.p2p.lastEventData[i] === 'object' &&
gdjs.evtTools.p2p.lastEventData[i].length > 0
)
gdjs.evtTools.p2p.lastEventData[i].pop();
}
});

View File

@@ -0,0 +1,333 @@
// @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 /*: gdPlatformExtension */ = new gd.PlatformExtension();
extension
.setExtensionInformation(
'P2P',
_('Peer-to-Peer communication (experimental)'),
_(
'Allow game instances to communicate remotely using messages sent via WebRTC (P2P)'
),
'Arthur Pacaud (arthuro555)',
'MIT'
)
.setExtensionHelpPath('/all-features/p2p');
extension
.addCondition(
'OnEvent',
_('Event triggered by peer'),
_('Triggers once when a connected client sends the event'),
_('Event _PARAM0_ received from other client (data loss: _PARAM1_)'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('Event name'), '', false)
.addParameter('yesorno', _('Data loss allowed?'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.onEvent');
extension
.addCondition(
'IsReady',
_('Is P2P ready'),
_(
'True if the peer-to-peer extension initialized and is ready to use.'
),
_('Is P2P ready?'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.isReady');
extension
.addCondition(
'OnError',
_('An error occurred'),
_(
'Triggers once when an error occurs. ' +
'Use P2P::GetLastError() expression to get the content of the error ' +
'if you want to analyse it or display it to the user.'
),
_('P2P error occurred'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.onError');
extension
.addCondition(
'OnDisconnection',
_('Peer disconnected'),
_('Triggers once when a peer disconnects.'),
_('P2P peer disconnected'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.onDisconnect');
extension
.addAction(
'Connect',
_('Connect to another client'),
_('Connects the current client to another client using its id.'),
_('Connect to P2P client _PARAM0_'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('ID of the other client'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.connect');
extension
.addAction(
'UseOwnBroker',
_('Connect to a broker server'),
_('Connects the extension to a broker server.'),
_('Connect to the broker server at http://_PARAM0_:_PARAM1_/'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('Host'), '', false)
.addParameter('number', _('Port'), '', false)
.addParameter('string', _('Path'), '', false)
.addParameter('string', _('Key'), '', false)
.addParameter('yesorno', _('SSl enabled?'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.useCustomBrokerServer');
extension
.addAction(
'UseDefaultBroker',
_('Connect to the default broker server'),
_('Connects to the default broker server.'),
_('Connect to the default broker server'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.useDefaultBrokerServer');
extension
.addAction(
'SendToAll',
_('Trigger event on all connected clients'),
_('Triggers an event on all connected clients'),
_(
'Trigger event _PARAM0_ on all connected clients (extra data: _PARAM1_)'
),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('Event name'), '', false)
.addParameter('string', _('Extra data (optional)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.sendDataToAll');
extension
.addAction(
'SendToOne',
_('Trigger event on a specific client'),
_('Triggers an event on a specific connected client'),
_('Trigger event _PARAM1_ on client _PARAM0_ (extra data: _PARAM2_)'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('ID of the other client'), '', false)
.addParameter('string', _('Event name'), '', false)
.addParameter('string', _('Extra data (optional)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.sendDataTo');
extension
.addAction(
'SendToAllVariable',
_('Trigger event on all connected clients (variable)'),
_('Triggers an event on all connected clients'),
_(
'Trigger event _PARAM0_ on all connected clients (extra data: _PARAM1_)'
),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('Event name'), '', false)
.addParameter(
'scenevar',
_('Variable containing the extra data'),
'',
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.sendDataToAll');
extension
.addAction(
'SendToOneVariable',
_('Trigger event on a specific client (variable)'),
_('Triggers an event on a specific connected client'),
_('Trigger event _PARAM1_ on client _PARAM0_ (extra data: _PARAM2_)'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('ID of the other client'), '', false)
.addParameter('string', _('Event name'), '', false)
.addParameter(
'scenevar',
_('Variable containing the extra data'),
'',
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.sendVariableTo');
extension
.addAction(
'GetEventVariable',
_('Get event data (variable)'),
_(
'Store the data of the specified event in a variable. ' +
'Check in the conditions that the event was received using the "Event received" condition.'
),
_(
'Overwrite _PARAM1_ with variable sent with last trigger of _PARAM0_'
),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('Event name'), '', false)
.addParameter(
'scenevar',
_('Variable where to store the received data'),
'',
false
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getEventVariable');
extension
.addStrExpression(
'GetEventData',
_('Get event data'),
_(
'Returns the data received when the specified event was last triggered'
),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)
.addParameter('string', _('Event name'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getEventData');
extension
.addStrExpression(
'GetID',
_('Get client ID'),
_('Gets the client ID of the current game instance'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getCurrentId');
extension
.addStrExpression(
'GetLastError',
_('Get last error'),
_('Gets the description of the last P2P error'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getLastError');
extension
.addStrExpression(
'GetLastDisconnectedPeer',
_('Get last disconnected peer'),
_('Gets the id of the latest peer that has disconnected.'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getDisconnectedPeer');
return extension;
},
runExtensionSanityTests: function (
gd /*: libGDevelop */,
extension /*: gdPlatformExtension*/
) {
return [];
},
};

208
Extensions/P2P/peerjs.d.ts vendored Normal file
View File

@@ -0,0 +1,208 @@
// Type definitions for the PeerJS class module
// Original definitions by Toshiya Nakakura <https://github.com/nakakura>
// at https://github.com/DefinitelyTyped/DefinitelyTyped
declare class Peer {
prototype: RTCIceServer;
/**
* A peer can connect to other peers and listen for connections.
* @param id Other peers can connect to this peer using the provided ID.
* If no ID is given, one will be generated by the brokering server.
* @param options for specifying details about PeerServer
*/
constructor(id?: string, options?: Peer.PeerJSOption);
/**
* A peer can connect to other peers and listen for connections.
* @param options for specifying details about PeerServer
*/
constructor(options: Peer.PeerJSOption);
/**
* Connects to the remote peer specified by id and returns a data connection.
* @param id The brokering ID of the remote peer (their peer.id).
* @param options for specifying details about Peer Connection
*/
connect(id: string, options?: Peer.PeerConnectOption): Peer.DataConnection;
/**
* Calls the remote peer specified by id and returns a media connection.
* @param id The brokering ID of the remote peer (their peer.id).
* @param stream The caller's media stream
* @param options Metadata associated with the connection, passed in by whoever initiated the connection.
*/
call(id: string, stream: MediaStream, options?: Peer.CallOption): Peer.MediaConnection;
/**
* Set listeners for peer events.
* @param event Event name
* @param cb Callback Function
*/
on(event: string, cb: () => void): void;
/**
* Emitted when a connection to the PeerServer is established.
* @param event Event name
* @param cb id is the brokering ID of the peer
*/
on(event: "open", cb: (id: string) => void): void;
/**
* Emitted when a new data connection is established from a remote peer.
* @param event Event name
* @param cb Callback Function
*/
on(
event: "connection",
cb: (dataConnection: Peer.DataConnection) => void
): void;
/**
* Emitted when a remote peer attempts to call you.
* @param event Event name
* @param cb Callback Function
*/
on(event: "call", cb: (mediaConnection: Peer.MediaConnection) => void): void;
/**
* Emitted when the peer is destroyed and can no longer accept or create any new connections.
* @param event Event name
* @param cb Callback Function
*/
on(event: "close", cb: () => void): void;
/**
* Emitted when the peer is disconnected from the signalling server
* @param event Event name
* @param cb Callback Function
*/
on(event: "disconnected", cb: () => void): void;
/**
* Errors on the peer are almost always fatal and will destroy the peer.
* @param event Event name
* @param cb Callback Function
*/
on(event: "error", cb: (err: any) => void): void;
/**
* Remove event listeners.(EventEmitter3)
* @param {String} event The event we want to remove.
* @param {Function} fn The listener that we need to find.
* @param {Boolean} once Only remove once listeners.
*/
off(event: string, fn: Function, once?: boolean): void;
/**
* Close the connection to the server, leaving all existing data and media connections intact.
*/
disconnect(): void;
/**
* Attempt to reconnect to the server with the peer's old ID
*/
reconnect(): void;
/**
* Close the connection to the server and terminate all existing connections.
*/
destroy(): void;
/**
* Retrieve a data/media connection for this peer.
* @param peerId
* @param connectionId
*/
getConnection(peerId: string, connectionId: string): Peer.MediaConnection | Peer.DataConnection | null;
/**
* Get a list of available peer IDs
* @param callback
*/
listAllPeers(callback: (peerIds: Array<string>) => void): void;
/**
* The brokering ID of this peer
*/
id: string;
/**
* A hash of all connections associated with this peer, keyed by the remote peer's ID.
*/
connections: any;
/**
* false if there is an active connection to the PeerServer.
*/
disconnected: boolean;
/**
* true if this peer and all of its connections can no longer be used.
*/
destroyed: boolean;
}
declare namespace Peer {
interface PeerJSOption {
key?: string;
host?: string;
port?: number;
path?: string;
secure?: boolean;
config?: RTCConfiguration;
debug?: number;
}
interface PeerConnectOption {
label?: string;
metadata?: any;
serialization?: string;
reliable?: boolean;
}
interface CallOption {
metadata?: any;
sdpTransform?: Function;
}
interface AnswerOption {
sdpTransform?: Function;
}
interface DataConnection {
send(data: any): void;
close(): void;
on(event: string, cb: () => void): void;
on(event: "data", cb: (data: any) => void): void;
on(event: "open", cb: () => void): void;
on(event: "close", cb: () => void): void;
on(event: "error", cb: (err: any) => void): void;
off(event: string, fn: Function, once?: boolean): void;
dataChannel: RTCDataChannel;
label: string;
metadata: any;
open: boolean;
peerConnection: RTCPeerConnection;
peer: string;
reliable: boolean;
serialization: string;
type: string;
bufferSize: number;
stringify: (data: any) => string;
parse: (data: string) => any;
}
interface MediaConnection {
answer(stream?: MediaStream, options?: AnswerOption): void;
close(): void;
on(event: string, cb: () => void): void;
on(event: "stream", cb: (stream: MediaStream) => void): void;
on(event: "close", cb: () => void): void;
on(event: "error", cb: (err: any) => void): void;
off(event: string, fn: Function, once?: boolean): void;
open: boolean;
metadata: any;
peerConnection: RTCPeerConnection;
peer: string;
type: string;
}
interface UtilSupportsObj {
browser: boolean,
webRTC: boolean;
audioVideo: boolean;
data: boolean;
binaryBlob: boolean;
reliable: boolean;
}
interface util {
browser: string;
supports: UtilSupportsObj;
}
}

View File

@@ -13,7 +13,7 @@
* @property {number} width The object width
* @property {number} height The object height
* @property {string} texture The name of the resource containing the texture to use
*
*
* @typedef {ObjectData & PanelSpriteObjectDataType} PanelSpriteObjectData
*/
@@ -80,6 +80,49 @@ gdjs.PanelSpriteRuntimeObject.prototype = Object.create(
);
gdjs.registerObject("PanelSpriteObject::PanelSprite", gdjs.PanelSpriteRuntimeObject);
/**
* @param {PanelSpriteObjectData} oldObjectData
* @param {PanelSpriteObjectData} newObjectData
*/
gdjs.PanelSpriteRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.width !== newObjectData.width) {
this.setWidth(newObjectData.width);
}
if (oldObjectData.height !== newObjectData.height) {
this.setHeight(newObjectData.height);
}
var updateTexture = false;
if (oldObjectData.rightMargin !== newObjectData.rightMargin) {
this._rBorder = newObjectData.rightMargin;
updateTexture = true;
}
if (oldObjectData.leftMargin !== newObjectData.leftMargin) {
this._lBorder = newObjectData.leftMargin;
updateTexture = true;
}
if (oldObjectData.topMargin !== newObjectData.topMargin) {
this._tBorder = newObjectData.topMargin;
updateTexture = true;
}
if (oldObjectData.bottomMargin !== newObjectData.bottomMargin) {
this._bBorder = newObjectData.bottomMargin;
updateTexture = true;
}
if (oldObjectData.texture !== newObjectData.texture) {
updateTexture = true;
}
if (updateTexture) {
this.setTexture(newObjectData.texture, this._runtimeScene);
}
if (oldObjectData.tiled !== newObjectData.tiled) {
return false;
}
return true;
};
gdjs.PanelSpriteRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
@@ -215,7 +258,7 @@ gdjs.PanelSpriteRuntimeObject.prototype.setColor = function(rgbColor) {
/**
* Get the tint of the panel sprite object.
*
* @returns {string} rgbColor The color, in RGB format ("128;200;255").
* @returns {string} The color, in RGB format ("128;200;255").
*/
gdjs.PanelSpriteRuntimeObject.prototype.getColor = function() {
return this._renderer.getColor();

View File

@@ -194,7 +194,7 @@ gdjs.ParticleEmitterObjectCocosRenderer = function(runtimeScene, runtimeObject,
this.started = false;
var renderer = runtimeScene.getLayer("").getRenderer();
var renderer = runtimeScene.getLayer(runtimeObject.getLayer()).getRenderer();
renderer.addRendererObject(this.renderer, runtimeObject.getZOrder());
this._convertYPosition = renderer.convertYPosition;
};

View File

@@ -152,7 +152,7 @@ gdjs.ParticleEmitterObjectPixiRenderer = function(runtimeScene, runtimeObject, o
this.emitter.emit = true;
this.started = false;
var layer = runtimeScene.getLayer("");
var layer = runtimeScene.getLayer(runtimeObject.getLayer());
if (layer) layer.getRenderer().addRendererObject(this.renderer, runtimeObject.getZOrder());
};
gdjs.ParticleEmitterObjectRenderer = gdjs.ParticleEmitterObjectPixiRenderer;

View File

@@ -24,17 +24,19 @@
* @property {number} particleBlue2
* @property {number} particleSize1
* @property {number} particleSize2
* @property {number} sizeParam
* @property {number} particleAngle1
* @property {number} particleAngle2
* @property {string} sizeParam
* @property {number} particleAlpha1
* @property {number} particleAlpha2
* @property {string} rendererType
* @property {number} rendererParam1
* @property {number} rendererParam1
* @property {number} rendererParam2
* @property {string} textureParticleName Resource name for image in particle
* @property {number} flow
* @property {number} tank
* @property {boolean} destroyWhenNoParticles Destroy the object when there is no particles?
*
*
* @typedef {ObjectData & ParticleEmitterObjectDataType} ParticleEmitterObjectData
*/
@@ -107,7 +109,7 @@ gdjs.ParticleEmitterObject = function(runtimeScene, particleObjectData){
/** @type {number} */
this.size2 = particleObjectData.particleSize2;
/** @type {number} */
/** @type {string} */
this.sizeParam = particleObjectData.sizeParam;
/** @type {number} */
@@ -121,7 +123,7 @@ gdjs.ParticleEmitterObject = function(runtimeScene, particleObjectData){
/** @type {number} */
this.rendererParam1 = particleObjectData.rendererParam1;
/** @type {number} */
this.rendererParam2 = particleObjectData.rendererParam2;
@@ -185,6 +187,116 @@ gdjs.ParticleEmitterObject.prototype.getRendererObject = function(){
return this._renderer.getRendererObject();
};
/**
* @param {ParticleEmitterObjectData} oldObjectData
* @param {ParticleEmitterObjectData} newObjectData
*/
gdjs.ParticleEmitterObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.emissionEditionSimpleMode !== newObjectData.emissionEditionSimpleMode) {
this.singleAngle = newObjectData.emissionEditionSimpleMode;
this._angleDirty = true;
}
if (oldObjectData.emitterAngleA !== newObjectData.emitterAngleA) {
this.setEmitterAngleA(newObjectData.emitterAngleA);
}
if (oldObjectData.emitterAngleB !== newObjectData.emitterAngleB) {
this.setEmitterAngleB(newObjectData.emitterAngleB);
}
if (oldObjectData.emitterForceMin !== newObjectData.emitterForceMin) {
this.setEmitterForceMin(newObjectData.emitterForceMin);
}
if (oldObjectData.emitterForceMax !== newObjectData.emitterForceMax) {
this.setEmitterForceMax(newObjectData.emitterForceMax);
}
if (oldObjectData.zoneRadius !== newObjectData.zoneRadius) {
this.setZoneRadius(newObjectData.zoneRadius);
}
if (oldObjectData.particleLifeTimeMin !== newObjectData.particleLifeTimeMin) {
this.setParticleLifeTimeMin(newObjectData.particleLifeTimeMin);
}
if (oldObjectData.particleLifeTimeMax !== newObjectData.particleLifeTimeMax) {
this.setParticleLifeTimeMax(newObjectData.particleLifeTimeMax);
}
if (oldObjectData.particleGravityX !== newObjectData.particleGravityX) {
this.setParticleGravityX(newObjectData.particleGravityX);
}
if (oldObjectData.particleGravityY !== newObjectData.particleGravityY) {
this.setParticleGravityY(newObjectData.particleGravityY);
}
if (oldObjectData.particleRed1 !== newObjectData.particleRed1) {
this.setParticleRed1(newObjectData.particleRed1);
}
if (oldObjectData.particleRed2 !== newObjectData.particleRed2) {
this.setParticleRed2(newObjectData.particleRed2);
}
if (oldObjectData.particleGreen1 !== newObjectData.particleGreen1) {
this.setParticleGreen1(newObjectData.particleGreen1);
}
if (oldObjectData.particleGreen2 !== newObjectData.particleGreen2) {
this.setParticleGreen2(newObjectData.particleGreen2);
}
if (oldObjectData.particleBlue1 !== newObjectData.particleBlue1) {
this.setParticleBlue1(newObjectData.particleBlue1);
}
if (oldObjectData.particleBlue2 !== newObjectData.particleBlue2) {
this.setParticleBlue2(newObjectData.particleBlue2);
}
if (oldObjectData.particleSize1 !== newObjectData.particleSize1) {
this.setParticleSize1(newObjectData.particleSize1);
}
if (oldObjectData.particleSize2 !== newObjectData.particleSize2) {
this.setParticleSize2(newObjectData.particleSize2);
}
if (oldObjectData.sizeParam !== newObjectData.sizeParam) {
this.sizeParam = newObjectData.sizeParam;
this._sizeDirty = true;
}
if (oldObjectData.particleAlpha1 !== newObjectData.particleAlpha1) {
this.setParticleAlpha1(newObjectData.particleAlpha1);
}
if (oldObjectData.particleAlpha2 !== newObjectData.particleAlpha2) {
this.setParticleAlpha2(newObjectData.particleAlpha2);
}
if (oldObjectData.textureParticleName !== newObjectData.textureParticleName) {
this.setTexture(newObjectData.textureParticleName, this._runtimeScene);
}
if (oldObjectData.flow !== newObjectData.flow) {
this.setFlow(newObjectData.flow);
}
if (oldObjectData.tank !== newObjectData.tank) {
this.setTank(newObjectData.tank);
}
if (oldObjectData.destroyWhenNoParticles !== newObjectData.destroyWhenNoParticles) {
this.destroyWhenNoParticles = newObjectData.destroyWhenNoParticles;
}
if (oldObjectData.particleSizeRandomness1 !== newObjectData.particleSizeRandomness1 ||
oldObjectData.particleSizeRandomness2 !== newObjectData.particleSizeRandomness2 ||
oldObjectData.particleAngle1 !== newObjectData.particleAngle1 ||
oldObjectData.particleAngle2 !== newObjectData.particleAngle2 ||
oldObjectData.maxParticleNb !== newObjectData.maxParticleNb ||
oldObjectData.additive !== newObjectData.additive ||
oldObjectData.rendererType !== newObjectData.rendererType ||
oldObjectData.rendererParam1 !== newObjectData.rendererParam1 ||
oldObjectData.rendererParam2 !== newObjectData.rendererParam2) {
// Destroy the renderer, ensure it's removed from the layer.
var layer = this._runtimeScene.getLayer(this.layer);
layer.getRenderer().removeRendererObject(this._renderer.getRendererObject());
this._renderer.destroy();
// and recreate the renderer, which will add itself to the layer.
this._renderer = new gdjs.ParticleEmitterObjectRenderer(this._runtimeScene, this, newObjectData);
// Consider every state dirty as the renderer was just re-created, so it needs
// to be repositioned, angle updated, etc...
this._posDirty = this._angleDirty = this._forceDirty = this._zoneRadiusDirty = true;
this._lifeTimeDirty = this._gravityDirty = this._colorDirty = this._sizeDirty = true;
this._alphaDirty = this._flowDirty = this._textureDirty = true;
}
return true;
};
gdjs.ParticleEmitterObject.prototype.update = function(runtimeScene){
if(this._posDirty){
this._renderer.setPosition(this.getX(), this.getY());

View File

@@ -104,6 +104,17 @@ gdjs.PathfindingObstacleRuntimeBehavior = function(runtimeScene, behaviorData, o
gdjs.PathfindingObstacleRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("PathfindingBehavior::PathfindingObstacleBehavior", gdjs.PathfindingObstacleRuntimeBehavior);
gdjs.PathfindingObstacleRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.impassable !== newBehaviorData.impassable) {
this.setImpassable(newBehaviorData.impassable);
}
if (oldBehaviorData.cost !== newBehaviorData.cost) {
this.setCost(newBehaviorData.cost);
}
return true;
};
gdjs.PathfindingObstacleRuntimeBehavior.prototype.onDestroy = function() {
if ( this._manager && this._registeredInManager ) this._manager.removeObstacle(this);
};

View File

@@ -48,6 +48,38 @@ gdjs.PathfindingRuntimeBehavior = function(runtimeScene, behaviorData, owner)
gdjs.PathfindingRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("PathfindingBehavior::PathfindingBehavior", gdjs.PathfindingRuntimeBehavior);
gdjs.PathfindingRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.allowDiagonals !== newBehaviorData.allowDiagonals) {
this.allowDiagonals(newBehaviorData.allowDiagonals);
}
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
this.setAcceleration(newBehaviorData.acceleration);
}
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
this.setMaxSpeed(newBehaviorData.maxSpeed);
}
if (oldBehaviorData.angularMaxSpeed !== newBehaviorData.angularMaxSpeed) {
this.setAngularMaxSpeed(newBehaviorData.angularMaxSpeed);
}
if (oldBehaviorData.rotateObject !== newBehaviorData.rotateObject) {
this.setRotateObject(newBehaviorData.rotateObject);
}
if (oldBehaviorData.angleOffset !== newBehaviorData.angleOffset) {
this.setAngleOffset(newBehaviorData.angleOffset);
}
if (oldBehaviorData.cellWidth !== newBehaviorData.cellWidth) {
this.setCellWidth(newBehaviorData.cellWidth);
}
if (oldBehaviorData.cellHeight !== newBehaviorData.cellHeight) {
this.setCellHeight(newBehaviorData.cellHeight);
}
if (oldBehaviorData.extraBorder !== newBehaviorData.extraBorder) {
this.setExtraBorder(newBehaviorData.extraBorder);
}
return true;
}
gdjs.PathfindingRuntimeBehavior.prototype.setCellWidth = function(width) {
this._cellWidth = width;
};

View File

@@ -53,9 +53,9 @@ gdjs.Physics2SharedData = function(runtimeScene, sharedData) {
var behaviorB = contact.GetFixtureB().GetBody().gdjsAssociatedBehavior;
// Remove each other contact
var i = behaviorA.currentContacts.indexOf(behaviorB);
if (i !== -1) behaviorA.currentContacts.remove(i);
if (i !== -1) behaviorA.currentContacts.splice(i, 1);
i = behaviorB.currentContacts.indexOf(behaviorA);
if (i !== -1) behaviorB.currentContacts.remove(i);
if (i !== -1) behaviorB.currentContacts.splice(i, 1);
};
this.contactListener.PreSolve = function() {};
@@ -252,6 +252,75 @@ gdjs.Physics2RuntimeBehavior.prototype.b2Vec2Sec = function(x, y) {
return this._tempb2Vec2Sec;
};
gdjs.Physics2RuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.bullet !== newBehaviorData.bullet) {
this.setBullet(newBehaviorData.bullet);
}
if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) {
this.setFixedRotation(newBehaviorData.fixedRotation);
}
if (oldBehaviorData.canSleep !== newBehaviorData.canSleep) {
this.setSleepingAllowed(newBehaviorData.canSleep);
}
if (oldBehaviorData.shapeDimensionA !== newBehaviorData.shapeDimensionA) {
this.shapeDimensionA = newBehaviorData.shapeDimensionA;
this.recreateShape();
}
if (oldBehaviorData.shapeDimensionB !== newBehaviorData.shapeDimensionB) {
this.shapeDimensionB = newBehaviorData.shapeDimensionB;
this.recreateShape();
}
if (oldBehaviorData.shapeOffsetX !== newBehaviorData.shapeOffsetX) {
this.shapeOffsetX = newBehaviorData.shapeOffsetX;
this.recreateShape();
}
if (oldBehaviorData.shapeOffsetY !== newBehaviorData.shapeOffsetY) {
this.shapeOffsetY = newBehaviorData.shapeOffsetY;
this.recreateShape();
}
if (oldBehaviorData.polygonOrigin !== newBehaviorData.polygonOrigin) {
this.polygonOrigin = newBehaviorData.polygonOrigin;
this.recreateShape();
}
if (oldBehaviorData.density !== newBehaviorData.density) {
this.setDensity(newBehaviorData.density);
}
if (oldBehaviorData.friction !== newBehaviorData.friction) {
this.setFriction(newBehaviorData.friction);
}
if (oldBehaviorData.restitution !== newBehaviorData.restitution) {
this.setRestitution(newBehaviorData.restitution);
}
if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) {
this.setLinearDamping(newBehaviorData.linearDamping);
}
if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) {
this.setAngularDamping(newBehaviorData.angularDamping);
}
if (oldBehaviorData.gravityScale !== newBehaviorData.gravityScale) {
this.setGravityScale(newBehaviorData.gravityScale);
}
// TODO: make these properties updatable.
if (oldBehaviorData.layers !== newBehaviorData.layers) {
return false;
}
if (oldBehaviorData.masks !== newBehaviorData.masks) {
return false;
}
if (oldBehaviorData.vertices !== newBehaviorData.vertices) {
return false;
}
if (oldBehaviorData.bodyType !== newBehaviorData.bodyType) {
return false;
}
if (oldBehaviorData.shape !== newBehaviorData.shape) {
return false;
}
return true;
};
gdjs.Physics2RuntimeBehavior.prototype.onDeActivate = function() {
if (this._body !== null) {
// When a body is deleted, Box2D removes automatically its joints, leaving an invalid pointer in our joints list
@@ -573,10 +642,21 @@ gdjs.Physics2RuntimeBehavior.prototype.doStepPreEvents = function(
gdjs.Physics2RuntimeBehavior.prototype.doStepPostEvents = function(
runtimeScene
) {
this._updateBodyFromObject();
// Reset world step to update next frame
this._sharedData.stepped = false;
};
gdjs.Physics2RuntimeBehavior.prototype.onObjectHotReloaded = function() {
this._updateBodyFromObject();
}
gdjs.Physics2RuntimeBehavior.prototype._updateBodyFromObject = function() {
// If there is no body, set a new one
if (this._body === null) this.createBody();
// GD object size has changed, recreate shape
// The object size has changed, recreate the shape.
// The width has changed and there is no custom dimension A (box: width, circle: radius, edge: length) or
// The height has changed, the shape is not an edge (edges doesn't have height),
// it isn't a box with custom height or a circle with custom radius
@@ -591,7 +671,7 @@ gdjs.Physics2RuntimeBehavior.prototype.doStepPostEvents = function(
this.recreateShape();
}
// GD object transform has changed, update body transform
// The object object transform has changed, update body transform:
if (
this._objectOldX !== this.owner.getX() ||
this._objectOldY !== this.owner.getY() ||
@@ -606,10 +686,7 @@ gdjs.Physics2RuntimeBehavior.prototype.doStepPostEvents = function(
this._body.SetTransform(pos, gdjs.toRad(this.owner.getAngle()));
this._body.SetAwake(true);
}
// Reset world step to update next frame
this._sharedData.stepped = false;
};
}
gdjs.Physics2RuntimeBehavior.prototype.getGravityX = function() {
return this._sharedData.gravityX;

View File

@@ -58,10 +58,10 @@ gdjs.PhysicsSharedData = function(runtimeScene, sharedData)
behaviorB = contact.GetFixtureB().GetBody().gdjsAssociatedBehavior;
var i = behaviorA.currentContacts.indexOf(behaviorB);
if ( i !== -1 ) behaviorA.currentContacts.remove(i);
if ( i !== -1 ) behaviorA.currentContacts.splice(i, 1);
i = behaviorB.currentContacts.indexOf(behaviorA);
if ( i !== -1 ) behaviorB.currentContacts.remove(i);
if ( i !== -1 ) behaviorB.currentContacts.splice(i, 1);
};
this.contactListener.PreSolve = function() {};
@@ -147,6 +147,43 @@ gdjs.PhysicsRuntimeBehavior = function(runtimeScene, behaviorData, owner)
gdjs.PhysicsRuntimeBehavior.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("PhysicsBehavior::PhysicsBehavior", gdjs.PhysicsRuntimeBehavior);
gdjs.PhysicsRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.dynamic !== newBehaviorData.dynamic) {
if (newBehaviorData.dynamic) this.setDynamic();
else this.setStatic();
}
if (oldBehaviorData.angularDamping !== newBehaviorData.angularDamping) {
this.setAngularDamping(newBehaviorData.angularDamping);
}
if (oldBehaviorData.linearDamping !== newBehaviorData.linearDamping) {
this.setLinearDamping(newBehaviorData.linearDamping);
}
if (oldBehaviorData.isBullet !== newBehaviorData.isBullet) {
if (newBehaviorData.isBullet) this.setAsBullet();
else this.dontSetAsBullet();
}
if (oldBehaviorData.fixedRotation !== newBehaviorData.fixedRotation) {
if (newBehaviorData.fixedRotation) this.setFixedRotation();
else this.setFreeRotation();
}
// TODO: make these properties updatable.
if (oldBehaviorData.massDensity !== newBehaviorData.massDensity) {
return false;
}
if (oldBehaviorData.averageFriction !== newBehaviorData.averageFriction) {
return false;
}
if (oldBehaviorData.averageRestitution !== newBehaviorData.averageRestitution) {
return false;
}
if (oldBehaviorData.shapeType !== newBehaviorData.shapeType) {
return false;
}
return true;
}
gdjs.PhysicsRuntimeBehavior.prototype.onDeActivate = function() {
if ( this._box2DBody !== null ) {
this._sharedData.world.DestroyBody(this._box2DBody);

View File

@@ -77,6 +77,44 @@ gdjs.registerBehavior(
gdjs.PlatformerObjectRuntimeBehavior
);
gdjs.PlatformerObjectRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.roundCoordinates !== newBehaviorData.roundCoordinates) {
this._roundCoordinates = newBehaviorData.roundCoordinates;
}
if (oldBehaviorData.gravity !== newBehaviorData.gravity) {
this.setGravity(newBehaviorData.gravity);
}
if (oldBehaviorData.maxFallingSpeed !== newBehaviorData.maxFallingSpeed) {
this.setMaxFallingSpeed(newBehaviorData.maxFallingSpeed);
}
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
this.setAcceleration(newBehaviorData.acceleration);
}
if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) {
this.setDeceleration(newBehaviorData.deceleration);
}
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
this.setMaxSpeed(newBehaviorData.maxSpeed);
}
if (oldBehaviorData.jumpSpeed !== newBehaviorData.jumpSpeed) {
this.setJumpSpeed(newBehaviorData.jumpSpeed);
}
if (oldBehaviorData.canGrabPlatforms !== newBehaviorData.canGrabPlatforms) {
this.setCanGrabPlatforms(newBehaviorData.canGrabPlatforms);
}
if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) {
this._yGrabOffset = newBehaviorData.yGrabOffset;
}
if (oldBehaviorData.xGrabTolerance !== newBehaviorData.xGrabTolerance) {
this._xGrabTolerance = newBehaviorData.xGrabTolerance;
}
if (oldBehaviorData.jumpSustainTime !== newBehaviorData.jumpSustainTime) {
this.setJumpSustainTime(newBehaviorData.jumpSustainTime);
}
return true;
};
gdjs.PlatformerObjectRuntimeBehavior.prototype.doStepPreEvents = function(
runtimeScene
) {

View File

@@ -106,6 +106,20 @@ gdjs.PlatformRuntimeBehavior.LADDER = 2;
gdjs.PlatformRuntimeBehavior.JUMPTHRU = 1;
gdjs.PlatformRuntimeBehavior.NORMALPLAFTORM = 0;
gdjs.PlatformRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.platformType !== newBehaviorData.platformType) {
this.changePlatformType(newBehaviorData.platformType);
}
if (oldBehaviorData.canBeGrabbed !== newBehaviorData.canBeGrabbed) {
this._canBeGrabbed = newBehaviorData.canBeGrabbed;
}
if (oldBehaviorData.yGrabOffset !== newBehaviorData.yGrabOffset) {
this._yGrabOffset = newBehaviorData.yGrabOffset;
}
return true;
};
gdjs.PlatformRuntimeBehavior.prototype.onDestroy = function() {
if ( this._manager && this._registeredInManager ) this._manager.removePlatform(this);
};

View File

@@ -8,7 +8,7 @@ const makeTestRuntimeScene = () => {
});
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
runtimeScene.loadFromScene({
layers: [{ name: '', visibility: true }],
layers: [{ name: '', visibility: true, effects: [] }],
variables: [],
behaviorsSharedData: [],
objects: [],

View File

@@ -19,7 +19,7 @@
* @property {number} outlineSize The size of the outline of the painted shape, in pixels.
* @property {boolean} absoluteCoordinates Use absolute coordinates?
* @property {boolean} clearBetweenFrames Clear the previous render before the next draw?
*
*
* @typedef {ObjectData & ShapePainterObjectDataType} ShapePainterObjectData
*/
@@ -74,6 +74,50 @@ gdjs.ShapePainterRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
/**
* @param {ShapePainterObjectData} oldObjectData
* @param {ShapePainterObjectData} newObjectData
*/
gdjs.ShapePainterRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.fillColor.r !== newObjectData.fillColor.r ||
oldObjectData.fillColor.g !== newObjectData.fillColor.g ||
oldObjectData.fillColor.b !== newObjectData.fillColor.b) {
this.setFillColor(
'' + newObjectData.fillColor.r + ';' +
newObjectData.fillColor.g + ';' +
newObjectData.fillColor.b
);
}
if (oldObjectData.outlineColor.r !== newObjectData.outlineColor.r ||
oldObjectData.outlineColor.g !== newObjectData.outlineColor.g ||
oldObjectData.outlineColor.b !== newObjectData.outlineColor.b) {
this.setOutlineColor(
'' + newObjectData.outlineColor.r + ';' +
newObjectData.outlineColor.g + ';' +
newObjectData.outlineColor.b
);
}
if (oldObjectData.fillOpacity !== newObjectData.fillOpacity) {
this.setFillOpacity(newObjectData.fillOpacity);
}
if (oldObjectData.outlineOpacity !== newObjectData.outlineOpacity) {
this.setOutlineOpacity(newObjectData.outlineOpacity);
}
if (oldObjectData.outlineSize !== newObjectData.outlineSize) {
this.setOutlineSize(newObjectData.outlineSize);
}
if (oldObjectData.absoluteCoordinates !== newObjectData.absoluteCoordinates) {
this._absoluteCoordinates = newObjectData.absoluteCoordinates;
this._renderer.updateXPosition();
this._renderer.updateYPosition();
}
if (oldObjectData.clearBetweenFrames !== newObjectData.clearBetweenFrames) {
this._clearBetweenFrames = newObjectData.clearBetweenFrames
}
return true;
};
gdjs.ShapePainterRuntimeObject.prototype.stepBehaviorsPreEvents = function(runtimeScene) {
//We redefine stepBehaviorsPreEvents just to clear the graphics before running events.
if(this._clearBetweenFrames){
@@ -120,11 +164,11 @@ gdjs.ShapePainterRuntimeObject.prototype.drawArc = function(centerX, centerY, ra
};
gdjs.ShapePainterRuntimeObject.prototype.drawBezierCurve = function(x1, y1, cpX, cpY, cpX2, cpY2, x2, y2) {
this._renderer.drawBezierCurve(x1, y1, cpX, cpY, cpX2, cpY2, x2, y2);
this._renderer.drawBezierCurve(x1, y1, cpX, cpY, cpX2, cpY2, x2, y2);
};
gdjs.ShapePainterRuntimeObject.prototype.drawQuadraticCurve = function(x1, y1, cpX, cpY, x2, y2) {
this._renderer.drawQuadraticCurve(x1, y1, cpX, cpY, x2, y2);
this._renderer.drawQuadraticCurve(x1, y1, cpX, cpY, x2, y2);
};
gdjs.ShapePainterRuntimeObject.prototype.beginFillPath = function(x1, y1) {
@@ -145,7 +189,7 @@ gdjs.ShapePainterRuntimeObject.prototype.drawPathLineTo = function(x1, y1) {
};
gdjs.ShapePainterRuntimeObject.prototype.drawPathBezierCurveTo = function(cpX, cpY, cpX2, cpY2, toX, toY) {
this._renderer.drawPathBezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
this._renderer.drawPathBezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
};
gdjs.ShapePainterRuntimeObject.prototype.drawPathArc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) {
@@ -153,11 +197,11 @@ gdjs.ShapePainterRuntimeObject.prototype.drawPathArc = function(cx, cy, radius,
};
gdjs.ShapePainterRuntimeObject.prototype.drawPathQuadraticCurveTo = function(cpX, cpY, toX, toY) {
this._renderer.drawPathQuadraticCurveTo(cpX, cpY, toX, toY);
this._renderer.drawPathQuadraticCurveTo(cpX, cpY, toX, toY);
};
gdjs.ShapePainterRuntimeObject.prototype.closePath = function() {
this._renderer.closePath();
this._renderer.closePath();
};
gdjs.ShapePainterRuntimeObject.prototype.setClearBetweenFrames = function(value) {

View File

@@ -35,6 +35,11 @@ gdjs.TextEntryRuntimeObject = function(runtimeScene, textEntryObjectData)
gdjs.TextEntryRuntimeObject.prototype = Object.create( gdjs.RuntimeObject.prototype );
gdjs.registerObject("TextEntryObject::TextEntry", gdjs.TextEntryRuntimeObject);
gdjs.TextEntryRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
// Nothing to update.
return true;
}
gdjs.TextEntryRuntimeObject.prototype.onDestroyFromScene = function(runtimeScene) {
gdjs.RuntimeObject.prototype.onDestroyFromScene.call(this, runtimeScene);

View File

@@ -117,6 +117,41 @@ gdjs.TextRuntimeObject = function(runtimeScene, textObjectData)
gdjs.TextRuntimeObject.prototype = Object.create( gdjs.RuntimeObject.prototype );
gdjs.registerObject("TextObject::Text", gdjs.TextRuntimeObject);
/**
* @param {TextObjectData} oldObjectData
* @param {TextObjectData} newObjectData
*/
gdjs.TextRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.characterSize !== newObjectData.characterSize) {
this.setCharacterSize(newObjectData.characterSize);
}
if (oldObjectData.font !== newObjectData.font) {
this.setFontName(newObjectData.font);
}
if (oldObjectData.bold !== newObjectData.bold) {
this.setBold(newObjectData.bold);
}
if (oldObjectData.italic !== newObjectData.italic) {
this.setItalic(newObjectData.italic);
}
if (oldObjectData.color.r !== newObjectData.color.r ||
oldObjectData.color.g !== newObjectData.color.g ||
oldObjectData.color.b !== newObjectData.color.b) {
this.setColor('' + newObjectData.color.r + ';' + newObjectData.color.g +
';' + newObjectData.color.b);
}
if (oldObjectData.string !== newObjectData.string) {
this.setString(newObjectData.string);
}
if (oldObjectData.underlined !== newObjectData.underlined) {
return false;
}
return true;
};
gdjs.TextRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
@@ -133,6 +168,8 @@ gdjs.TextRuntimeObject.prototype.extraInitializationFromInitialInstance = functi
if ( initialInstanceData.customSize ) {
this.setWrapping(true);
this.setWrappingWidth(initialInstanceData.width);
} else {
this.setWrapping(false);
}
};
@@ -224,6 +261,15 @@ gdjs.TextRuntimeObject.prototype.setCharacterSize = function(newSize) {
this._renderer.updateStyle();
};
/**
* Set the name of the resource to use for the font.
* @param {string} fontResourceName The name of the font resource.
*/
gdjs.TextRuntimeObject.prototype.setFontName = function(fontResourceName) {
this._fontName = fontResourceName;
this._renderer.updateStyle();
};
/**
* Return true if the text is bold.
*/
@@ -338,7 +384,7 @@ gdjs.TextRuntimeObject.prototype.setColor = function(str) {
/**
* Get the text color.
* @return {String} The color as a "R;G;B" string, for example: "255;0;0"
* @return {string} The color as a "R;G;B" string, for example: "255;0;0"
*/
gdjs.TextRuntimeObject.prototype.getColor = function(str) {
return this._color[0] + ";" + this._color[1] + ";" + this._color[2];

View File

@@ -43,6 +43,20 @@ gdjs.TiledSpriteRuntimeObject = function(runtimeScene, tiledSpriteObjectData)
gdjs.TiledSpriteRuntimeObject.prototype = Object.create( gdjs.RuntimeObject.prototype );
gdjs.registerObject("TiledSpriteObject::TiledSprite", gdjs.TiledSpriteRuntimeObject);
gdjs.TiledSpriteRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.texture !== newObjectData.texture) {
this.setTexture(newObjectData.texture, this._runtimeScene);
}
if (oldObjectData.width !== newObjectData.width) {
this.setWidth(newObjectData.width);
}
if (oldObjectData.height !== newObjectData.height) {
this.setHeight(newObjectData.height);
}
return true;
};
gdjs.TiledSpriteRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
@@ -199,7 +213,7 @@ gdjs.TiledSpriteRuntimeObject.prototype.setColor = function(rgbColor) {
/**
* Get the tint of the tiled sprite object.
*
* @returns {string} rgbColor The color, in RGB format ("128;200;255").
* @returns {string} The color, in RGB format ("128;200;255").
*/
gdjs.TiledSpriteRuntimeObject.prototype.getColor = function() {
return this._renderer.getColor();

View File

@@ -46,6 +46,34 @@ gdjs.registerBehavior(
gdjs.TopDownMovementRuntimeBehavior
);
gdjs.TopDownMovementRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
if (oldBehaviorData.allowDiagonals !== newBehaviorData.allowDiagonals) {
this._allowDiagonals = newBehaviorData.allowDiagonals;
}
if (oldBehaviorData.acceleration !== newBehaviorData.acceleration) {
this._acceleration = newBehaviorData.acceleration;
}
if (oldBehaviorData.deceleration !== newBehaviorData.deceleration) {
this._deceleration = newBehaviorData.deceleration;
}
if (oldBehaviorData.maxSpeed !== newBehaviorData.maxSpeed) {
this._maxSpeed = newBehaviorData.maxSpeed;
}
if (oldBehaviorData.angularMaxSpeed !== newBehaviorData.angularMaxSpeed) {
this._angularMaxSpeed = newBehaviorData.angularMaxSpeed;
}
if (oldBehaviorData.rotateObject !== newBehaviorData.rotateObject) {
this._rotateObject = newBehaviorData.rotateObject;
}
if (oldBehaviorData.angleOffset !== newBehaviorData.angleOffset) {
this._angleOffset = newBehaviorData.angleOffset;
}
if (oldBehaviorData.ignoreDefaultControls !== newBehaviorData.ignoreDefaultControls) {
this._ignoreDefaultControls = newBehaviorData.ignoreDefaultControls;
}
return true;
}
gdjs.TopDownMovementRuntimeBehavior.prototype.setAcceleration = function(
acceleration
) {

View File

@@ -66,8 +66,8 @@ module.exports = {
tweenBehavior,
new gd.BehaviorsSharedData()
)
.setIncludeFile("Extensions/TweenBehavior/tweenruntimebehavior.js")
.addIncludeFile("Extensions/TweenBehavior/shifty.js");
.setIncludeFile("Extensions/TweenBehavior/shifty.js")
.addIncludeFile("Extensions/TweenBehavior/tweenruntimebehavior.js");
const easingChoices = JSON.stringify([
"linear",

View File

@@ -58,6 +58,11 @@ gdjs.TweenRuntimeBehavior.easings = [
'easeTo',
];
gdjs.TweenRuntimeBehavior.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
// Nothing to update.
return true;
}
/**
* A tween being played in a behavior.
* @param {shifty.Tweenable} instance The Shifty tween that is played

View File

@@ -56,6 +56,28 @@ gdjs.VideoRuntimeObject.prototype.getRendererObject = function() {
return this._renderer.getRendererObject();
};
/**
* @param {VideoObjectData} oldObjectData
* @param {VideoObjectData} newObjectData
*/
gdjs.VideoRuntimeObject.prototype.updateFromObjectData = function(oldObjectData, newObjectData) {
if (oldObjectData.content.opacity !== newObjectData.content.opacity) {
this.setOpacity(newObjectData.content.opacity);
}
if (oldObjectData.content.loop !== newObjectData.content.loop) {
this.setLoop(newObjectData.content.loop);
}
if (oldObjectData.content.volume !== newObjectData.content.volume) {
this.setVolume(newObjectData.content.volume);
}
if (oldObjectData.content.videoResource !== newObjectData.content.videoResource) {
return false;
}
return true;
};
/**
* Initialize the extra parameters that could be set for an instance.
* @private

View File

@@ -7,6 +7,7 @@
#include <algorithm>
#include <set>
#include <string>
#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h"
#if defined(GD_IDE_ONLY)
#include "GDCore/Events/Builtin/CommentEvent.h"
@@ -215,7 +216,8 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
[](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& parentContext) {
size_t uniqueId = (size_t)&instruction;
size_t uniqueId = codeGenerator.GenerateSingleUsageUniqueIdFor(
instruction.GetOriginalInstruction().lock().get());
return "conditionTrue = runtimeContext->TriggerOnce(" +
gd::String::From(uniqueId) + ");\n";
});

View File

@@ -96,6 +96,17 @@ gd::String BehaviorCodeGenerator::GenerateRuntimeBehaviorCompleteCode(
}
return runtimeBehaviorMethodsCode;
},
[&]() {
gd::String updateFromBehaviorCode;
for (auto& property :
eventsBasedBehavior.GetPropertyDescriptors().GetInternalVector()) {
updateFromBehaviorCode +=
GenerateUpdatePropertyFromBehaviorDataCode(
eventsBasedBehavior, codeNamespace, *property);
}
return updateFromBehaviorCode;
});
}
@@ -105,7 +116,8 @@ gd::String BehaviorCodeGenerator::GenerateRuntimeBehaviorTemplateCode(
const gd::String& codeNamespace,
std::function<gd::String()> generateInitializePropertiesCode,
std::function<gd::String()> generateMethodsCode,
std::function<gd::String()> generatePropertiesCode) {
std::function<gd::String()> generatePropertiesCode,
std::function<gd::String()> generateUpdateFromBehaviorDataCode) {
return gd::String(R"jscode_template(
CODE_NAMESPACE = CODE_NAMESPACE || {};
@@ -128,6 +140,13 @@ CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME = function(runtimeScene, behaviorData,
CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME.prototype = Object.create( gdjs.RuntimeBehavior.prototype );
gdjs.registerBehavior("EXTENSION_NAME::BEHAVIOR_NAME", CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME);
// Hot-reload:
CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME.prototype.updateFromBehaviorData = function(oldBehaviorData, newBehaviorData) {
UPDATE_FROM_BEHAVIOR_DATA_CODE
return true;
}
// Properties:
PROPERTIES_CODE
@@ -142,6 +161,7 @@ METHODS_CODE
.FindAndReplace("CODE_NAMESPACE", codeNamespace)
.FindAndReplace("INITIALIZE_PROPERTIES_CODE",
generateInitializePropertiesCode())
.FindAndReplace("UPDATE_FROM_BEHAVIOR_DATA_CODE", generateUpdateFromBehaviorDataCode())
.FindAndReplace("PROPERTIES_CODE", generatePropertiesCode())
.FindAndReplace("METHODS_CODE", generateMethodsCode());
;
@@ -183,7 +203,16 @@ CODE_NAMESPACE.RUNTIME_BEHAVIOR_CLASSNAME.prototype.SETTER_NAME = function(newVa
.FindAndReplace("RUNTIME_BEHAVIOR_CLASSNAME",
eventsBasedBehavior.GetName())
.FindAndReplace("CODE_NAMESPACE", codeNamespace);
;
}
gd::String BehaviorCodeGenerator::GenerateUpdatePropertyFromBehaviorDataCode(
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& codeNamespace,
const gd::NamedPropertyDescriptor& property) {
return gd::String(R"jscode_template(
if (oldBehaviorData.PROPERTY_NAME !== newBehaviorData.PROPERTY_NAME)
this._behaviorData.PROPERTY_NAME = newBehaviorData.PROPERTY_NAME;)jscode_template")
.FindAndReplace("PROPERTY_NAME", property.GetName());
}
gd::String BehaviorCodeGenerator::GeneratePropertyValueCode(

View File

@@ -64,7 +64,8 @@ class BehaviorCodeGenerator {
const gd::String& codeNamespace,
std::function<gd::String()> generateInitializePropertiesCode,
std::function<gd::String()> generateMethodsCode,
std::function<gd::String()> generatePropertiesCode);
std::function<gd::String()> generatePropertiesCode,
std::function<gd::String()> generateUpdateFromBehaviorDataCode);
gd::String GenerateRuntimeBehaviorPropertyTemplateCode(
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& codeNamespace,
@@ -74,6 +75,10 @@ class BehaviorCodeGenerator {
gd::String GenerateInitializePropertyFromDefaultValueCode(
const gd::NamedPropertyDescriptor& property);
gd::String GeneratePropertyValueCode(const gd::PropertyDescriptor& property);
gd::String GenerateUpdatePropertyFromBehaviorDataCode(
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& codeNamespace,
const gd::NamedPropertyDescriptor& property);
gd::String GenerateBehaviorOnDestroyToDeprecatedOnOwnerRemovedFromScene(
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& codeNamespace);

View File

@@ -63,8 +63,6 @@ gd::String EventsCodeGenerator::GenerateEventsListCompleteFunctionCode(
gd::String globalObjectLists = allObjectsDeclarationsAndResets.first;
gd::String globalObjectListsReset = allObjectsDeclarationsAndResets.second;
codeGenerator.AddAllObjectsIncludeFiles();
// "Booleans" used by conditions
gd::String globalConditionsBooleans =
codeGenerator.GenerateAllConditionsBooleanDeclarations();
@@ -126,8 +124,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParameters(), false),
codeGenerator.GenerateEventsFunctionContext(
eventsFunction.GetParameters(),
"runtimeScene.getOnceTriggers()"),
eventsFunction.GetParameters(), "runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(),
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
@@ -156,8 +153,7 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
// Generate the code setting up the context of the function.
gd::String fullPreludeCode =
preludeCode + "\n" +
"var that = this;\n" +
preludeCode + "\n" + "var that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the behavior
"var runtimeScene = this._runtimeScene;\n" +
@@ -175,7 +171,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
onceTriggersVariable,
// Pass the names of the parameters considered as the current
// object and behavior parameters:
"Object", "Behavior");
"Object",
"Behavior");
gd::String output = GenerateEventsListCompleteFunctionCode(
project,
@@ -327,9 +324,9 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
" createObject: function(objectName) {\n"
" var objectsList = "
"eventsFunctionContext._objectsMap[objectName];\n" +
// TODO: we could speed this up by storing a map of object names, but the
// cost of creating/storing it for each events function might not be
// worth it.
// TODO: we could speed this up by storing a map of object names, but
// the cost of creating/storing it for each events function might not
// be worth it.
" if (objectsList) {\n" +
" return parentEventsFunctionContext ?\n" +
" "
@@ -342,12 +339,11 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
" },\n"
// Getter for arguments that are not objects
" getArgument: function(argName) {\n" +
argumentsGetters + " return \"\";\n" +
" },\n" +
// Expose OnceTriggers (will be pointing either to the runtime scene ones,
// or the ones from the behavior):
" getOnceTriggers: function() { return " + onceTriggersVariable + "; }\n" +
"};\n";
argumentsGetters + " return \"\";\n" + " },\n" +
// Expose OnceTriggers (will be pointing either to the runtime scene
// ones, or the ones from the behavior):
" getOnceTriggers: function() { return " + onceTriggersVariable +
"; }\n" + "};\n";
}
gd::String EventsCodeGenerator::GenerateEventsFunctionReturn(
@@ -396,33 +392,6 @@ EventsCodeGenerator::GenerateAllObjectsDeclarationsAndResets(
return std::make_pair(globalObjectLists, globalObjectListsReset);
}
void EventsCodeGenerator::AddAllObjectsIncludeFiles() {
auto addIncludeFiles = [this](const gd::Object& object) {
gd::String type = gd::GetTypeOfObject(
GetGlobalObjectsAndGroups(), GetObjectsAndGroups(), object.GetName());
// Ensure needed files are included for the object type and its behaviors.
const gd::ObjectMetadata& metadata =
gd::MetadataProvider::GetObjectMetadata(JsPlatform::Get(), type);
AddIncludeFiles(metadata.includeFiles);
std::vector<gd::String> behaviors = object.GetAllBehaviorNames();
for (std::size_t j = 0; j < behaviors.size(); ++j) {
const gd::BehaviorMetadata& metadata =
gd::MetadataProvider::GetBehaviorMetadata(
JsPlatform::Get(),
object.GetBehavior(behaviors[j]).GetTypeName());
AddIncludeFiles(metadata.includeFiles);
}
};
for (std::size_t i = 0; i < globalObjectsAndGroups.GetObjectsCount(); ++i)
addIncludeFiles(globalObjectsAndGroups.GetObject(i));
for (std::size_t i = 0; i < objectsAndGroups.GetObjectsCount(); ++i)
addIncludeFiles(objectsAndGroups.GetObject(i));
}
gd::String EventsCodeGenerator::GenerateAllConditionsBooleanDeclarations() {
gd::String globalConditionsBooleans;
for (unsigned int i = 0; i <= GetMaxCustomConditionsDepth(); ++i) {
@@ -768,16 +737,16 @@ gd::String EventsCodeGenerator::GenerateObjectsDeclarationCode(
gd::String copiedListName =
GetObjectListName(object, *context.GetParentContext());
return objectListName + ".createFrom(" + copiedListName + ");\n";
return "gdjs.copyArray(" + copiedListName + ", " + objectListName + ");\n";
};
gd::String declarationsCode;
for (auto object : context.GetObjectsListsToBeDeclared()) {
gd::String objectListDeclaration = "";
if (!context.ObjectAlreadyDeclared(object)) {
objectListDeclaration += GetObjectListName(object, context) +
".createFrom(" +
GenerateAllInstancesGetterCode(object) + ");";
objectListDeclaration += "gdjs.copyArray(" +
GenerateAllInstancesGetterCode(object) + ", " +
GetObjectListName(object, context) + ");";
context.SetObjectDeclared(object);
} else
objectListDeclaration = declareObjectList(object, context);
@@ -839,15 +808,17 @@ gd::String EventsCodeGenerator::GenerateEventsListCode(
: "runtimeScene, eventsFunctionContext";
// Generate a unique name for the function.
gd::String uniqueId =
gd::String::From(GenerateSingleUsageUniqueIdForEventsList());
gd::String functionName =
GetCodeNamespaceAccessor() + "eventsList" + gd::String::From(&events);
GetCodeNamespaceAccessor() + "eventsList" + uniqueId;
// The only local parameters are runtimeScene and context.
// List of objects, conditions booleans and any variables used by events
// are stored in static variables that are globally available by the whole
// code.
AddCustomCodeOutsideMain(functionName + " = function(" + parametersCode +
") {\n" + code + "\n" + "}; //End of " +
functionName + "\n");
") {\n" + code + "\n" + "};");
// Replace the code of the events by the call to the function. This does not
// interfere with the objects picking as the lists are in static variables

View File

@@ -310,12 +310,6 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
std::pair<gd::String, gd::String> GenerateAllObjectsDeclarationsAndResets(
unsigned int maxDepthLevelReached);
/**
* \brief Add to include files all the files required by the object and their
* behaviors.
*/
void AddAllObjectsIncludeFiles();
/**
* \brief Generate the list of parameters of a function.
*

View File

@@ -4,8 +4,10 @@
* reserved. This project is released under the MIT License.
*/
#include "CommonInstructionsExtension.h"
#include <algorithm>
#include <set>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Builtin/CommentEvent.h"
#include "GDCore/Events/Builtin/ForEachEvent.h"
@@ -227,8 +229,9 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
gd::String::From(parentContext.GetContextDepth()) + "_" +
gd::String::From(parentContext.GetCurrentConditionDepth()) +
"final";
code += codeGenerator.GetObjectListName(*it, parentContext) +
".createFrom(" + finalObjList + ");\n";
code += "gdjs.copyArray(" + finalObjList + ", " +
codeGenerator.GetObjectListName(*it, parentContext) +
");\n";
}
code += "}\n";
@@ -325,7 +328,8 @@ CommonInstructionsExtension::CommonInstructionsExtension() {
[](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
size_t uniqueId = (size_t)&instruction;
size_t uniqueId = codeGenerator.GenerateSingleUsageUniqueIdFor(
instruction.GetOriginalInstruction().lock().get());
gd::String outputCode = codeGenerator.GenerateBooleanFullName(
"conditionTrue", context) +
".val = ";

View File

@@ -16,7 +16,9 @@ NetworkExtension::NetworkExtension() {
gd::BuiltinExtensionsImplementer::ImplementsNetworkExtension(*this);
GetAllActions()["SendRequest"].SetFunctionName(
"gdjs.evtTools.network.sendHttpRequest");
"gdjs.evtTools.network.sendDeprecatedSynchronousRequest");
GetAllActions()["SendAsyncRequest"].SetFunctionName(
"gdjs.evtTools.network.sendAsyncRequest");
GetAllActions()["JSONToVariableStructure"].SetFunctionName(
"gdjs.evtTools.network.jsonToVariableStructure");
GetAllActions()["JSONToGlobalVariableStructure"].SetFunctionName(

View File

@@ -4,11 +4,13 @@
* reserved. This project is released under the MIT License.
*/
#include "GDJS/IDE/Exporter.h"
#include <algorithm>
#include <fstream>
#include <sstream>
#include <streambuf>
#include <string>
#include "GDCore/CommonTools.h"
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
@@ -35,28 +37,10 @@ Exporter::Exporter(gd::AbstractFileSystem& fileSystem, gd::String gdjsRoot_)
Exporter::~Exporter() {}
bool Exporter::ExportLayoutForPixiPreview(gd::Project& project,
gd::Layout& layout,
gd::String exportDir) {
bool Exporter::ExportProjectForPixiPreview(
const PreviewExportOptions& options) {
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
gd::SerializerElement options;
options.AddChild("isPreview").SetBoolValue(true);
return helper.ExportLayoutForPixiPreview(project, layout, exportDir, gd::Serializer::ToJSON(options));
}
bool Exporter::ExportExternalLayoutForPixiPreview(
gd::Project& project,
gd::Layout& layout,
gd::ExternalLayout& externalLayout,
gd::String exportDir) {
gd::SerializerElement options;
options.AddChild("injectExternalLayout").SetValue(externalLayout.GetName());
options.AddChild("isPreview").SetBoolValue(true);
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
return helper.ExportLayoutForPixiPreview(
project, layout, exportDir, gd::Serializer::ToJSON(options));
return helper.ExportProjectForPixiPreview(options);
}
bool Exporter::ExportWholePixiProject(
@@ -68,7 +52,6 @@ bool Exporter::ExportWholePixiProject(
auto exportProject = [this, &exportedProject, &exportOptions, &helper](
gd::String exportDir) {
bool minify = exportOptions["minify"];
bool exportForCordova = exportOptions["exportForCordova"];
bool exportForFacebookInstantGames =
exportOptions["exportForFacebookInstantGames"];
@@ -95,7 +78,11 @@ bool Exporter::ExportWholePixiProject(
// Export engine libraries
helper.AddLibsInclude(true, false, false, includesFiles);
// Export effects (after engine libraries as they auto-register themselves to the engine)
// Export files for object and behaviors
helper.ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
// Export effects (after engine libraries as they auto-register themselves
// to the engine)
helper.ExportEffectIncludes(exportedProject, includesFiles);
// Export events
@@ -120,13 +107,14 @@ bool Exporter::ExportWholePixiProject(
gd::ProjectStripper::StripProjectForExport(exportedProject);
//...and export it
helper.ExportToJSON(
fs, exportedProject, codeOutputDir + "/data.js", "gdjs.projectData");
gd::SerializerElement noRuntimeGameOptions;
helper.ExportProjectData(
fs, exportedProject, codeOutputDir + "/data.js", noRuntimeGameOptions);
includesFiles.push_back(codeOutputDir + "/data.js");
// Copy all dependencies and the index (or metadata) file.
helper.RemoveIncludes(false, true, includesFiles);
helper.ExportIncludesAndLibs(includesFiles, exportDir, minify);
helper.ExportIncludesAndLibs(includesFiles, exportDir);
gd::String source = gdjsRoot + "/Runtime/index.html";
if (exportForCordova)
@@ -195,7 +183,11 @@ bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
// Export engine libraries
helper.AddLibsInclude(false, true, false, includesFiles);
// Export effects (after engine libraries as they auto-register themselves to the engine)
// Export files for object and behaviors
helper.ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
// Export effects (after engine libraries as they auto-register themselves to
// the engine)
helper.ExportEffectIncludes(exportedProject, includesFiles);
// Export events
@@ -219,13 +211,14 @@ bool Exporter::ExportWholeCocos2dProject(gd::Project& project,
gd::ProjectStripper::StripProjectForExport(exportedProject);
//...and export it
helper.ExportToJSON(
fs, exportedProject, codeOutputDir + "/data.js", "gdjs.projectData");
gd::SerializerElement noRuntimeGameOptions;
helper.ExportProjectData(
fs, exportedProject, codeOutputDir + "/data.js", noRuntimeGameOptions);
includesFiles.push_back(codeOutputDir + "/data.js");
// Copy all dependencies and the index (or metadata) file.
helper.RemoveIncludes(true, false, includesFiles);
helper.ExportIncludesAndLibs(includesFiles, exportDir + "/src", false);
helper.ExportIncludesAndLibs(includesFiles, exportDir + "/src");
if (!helper.ExportCocos2dFiles(
project, exportDir, debugMode, includesFiles)) {

View File

@@ -9,6 +9,7 @@
#include <set>
#include <string>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Project;
@@ -16,6 +17,7 @@ class Layout;
class ExternalLayout;
class AbstractFileSystem;
} // namespace gd
namespace gdjs { struct PreviewExportOptions; }
namespace gdjs {
@@ -30,32 +32,13 @@ class Exporter {
virtual ~Exporter();
/**
* \brief Create a preview for the specified layout.
* \brief Create a preview for the specified options.
* \note The preview is not launched, it is the caller responsibility to open
* a browser pointing to the preview.
*
* \param layout The layout to be previewed.
* \param exportDir The directory where the preview must be created.
* \return true if export was successful.
* \param options The options to generate the preview.
*/
bool ExportLayoutForPixiPreview(gd::Project& project,
gd::Layout& layout,
gd::String exportDir);
/**
* \brief Create a preview for the specified external layout and layout.
* \note The preview is not launched, it is the caller responsibility to open
* a browser pointing to the preview.
*
* \param layout The layout to be previewed.
* \param externalLayout The external layout with objects to be created at
* scene startup. \param exportDir The directory where the preview must be
* created. \return true if export was successful.
*/
bool ExportExternalLayoutForPixiPreview(gd::Project& project,
gd::Layout& layout,
gd::ExternalLayout& externalLayout,
gd::String exportDir);
bool ExportProjectForPixiPreview(const PreviewExportOptions& options);
/**
* \brief Export the specified project, using Pixi.js.

View File

@@ -13,6 +13,10 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Events/CodeGeneration/EffectsCodeGenerator.h"
#include "GDCore/Extensions/Metadata/DependencyMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
#include "GDCore/IDE/ProjectStripper.h"
@@ -21,14 +25,53 @@
#include "GDCore/Project/ExternalLayout.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Project/SourceFile.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/TinyXml/tinyxml.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/Log.h"
#include "GDJS/Events/CodeGeneration/LayoutCodeGenerator.h"
#include "GDJS/Extensions/JsPlatform.h"
#undef CopyFile // Disable an annoying macro
namespace {
std::map<gd::String, gd::String> GetExtensionDependencyExtraSettingValues(
const gd::Project &project,
const gd::String &extensionName,
const gd::DependencyMetadata &dependency) {
std::map<gd::String, gd::String> values;
for (const auto &extraSetting : dependency.GetAllExtraSettings()) {
const gd::String &type = extraSetting.second.GetType();
const gd::String extraSettingValue =
type == "ExtensionProperty"
? project.GetExtensionProperties().GetValue(
extensionName, extraSetting.second.GetValue())
: extraSetting.second.GetValue();
if (!extraSettingValue.empty())
values[extraSetting.first] = extraSettingValue;
}
return values;
};
bool AreMapKeysMissingElementOfSet(const std::map<gd::String, gd::String> &map,
const std::set<gd::String> &set) {
bool missingKey = false;
for (auto &key : set) {
if (map.find(key) == map.end()) {
missingKey = true;
}
}
return missingKey;
}
} // namespace
namespace gdjs {
static void InsertUnique(std::vector<gd::String> &container, gd::String str) {
@@ -41,87 +84,124 @@ ExporterHelper::ExporterHelper(gd::AbstractFileSystem &fileSystem,
gd::String codeOutputDir_)
: fs(fileSystem), gdjsRoot(gdjsRoot_), codeOutputDir(codeOutputDir_){};
bool ExporterHelper::ExportLayoutForPixiPreview(gd::Project &project,
gd::Layout &layout,
gd::String exportDir,
gd::String additionalSpec) {
fs.MkDir(exportDir);
fs.ClearDir(exportDir);
bool ExporterHelper::ExportProjectForPixiPreview(
const PreviewExportOptions &options) {
fs.MkDir(options.exportPath);
fs.ClearDir(options.exportPath);
std::vector<gd::String> includesFiles;
gd::Project exportedProject = project;
gd::Project exportedProject = options.project;
// Always disable the splash for preview
exportedProject.GetLoadingScreen().ShowGDevelopSplash(false);
// Export resources (*before* generating events as some resources filenames
// may be updated)
ExportResources(fs, exportedProject, exportDir);
ExportResources(fs, exportedProject, options.exportPath);
// Compatibility with GD <= 5.0-beta56
// Stay compatible with text objects declaring their font as just a filename
// without a font resource - by manually adding these resources.
AddDeprecatedFontFilesToFontResources(
fs, exportedProject.GetResourcesManager(), exportDir);
fs, exportedProject.GetResourcesManager(), options.exportPath);
// end of compatibility code
// Export engine libraries
AddLibsInclude(true, false, true, includesFiles);
// Export files for object and behaviors
ExportObjectAndBehaviorsIncludes(exportedProject, includesFiles);
// Export effects (after engine libraries as they auto-register themselves to
// the engine)
ExportEffectIncludes(exportedProject, includesFiles);
// Generate events code
if (!ExportEventsCode(exportedProject, codeOutputDir, includesFiles, true))
return false;
if (!options.projectDataOnlyExport) {
// Generate events code
if (!ExportEventsCode(exportedProject, codeOutputDir, includesFiles, true))
return false;
// Export source files
if (!ExportExternalSourceFiles(
exportedProject, codeOutputDir, includesFiles)) {
gd::LogError(_("Error during exporting! Unable to export source files:\n") +
lastError);
return false;
// Export source files
if (!ExportExternalSourceFiles(
exportedProject, codeOutputDir, includesFiles)) {
gd::LogError(
_("Error during exporting! Unable to export source files:\n") +
lastError);
return false;
}
}
// Strip the project (*after* generating events as the events may use stripped
// things (objects groups...))
gd::ProjectStripper::StripProjectForExport(exportedProject);
exportedProject.SetFirstLayout(layout.GetName());
exportedProject.SetFirstLayout(options.layoutName);
// Strip the includes to only have Pixi.js files (*before* creating
// runtimeGameOptions, since otherwise Cocos files will be passed to the
// hot-reloader).
RemoveIncludes(false, true, includesFiles);
// Create the setup options passed to the gdjs.RuntimeGame
gd::SerializerElement runtimeGameOptions;
runtimeGameOptions.AddChild("isPreview").SetBoolValue(true);
if (!options.externalLayoutName.empty()) {
runtimeGameOptions.AddChild("injectExternalLayout")
.SetValue(options.externalLayoutName);
}
runtimeGameOptions.AddChild("projectDataOnlyExport")
.SetBoolValue(options.projectDataOnlyExport);
runtimeGameOptions.AddChild("debuggerServerAddress")
.SetStringValue(options.debuggerServerAddress);
runtimeGameOptions.AddChild("debuggerServerPort")
.SetStringValue(options.debuggerServerPort);
// Pass in the options the list of scripts files - useful for hot-reloading.
auto &scriptFilesElement = runtimeGameOptions.AddChild("scriptFiles");
scriptFilesElement.ConsiderAsArrayOf("scriptFile");
for (const auto &includeFile : includesFiles) {
auto hashIt = options.includeFileHashes.find(includeFile);
gd::String scriptSrc = GetExportedIncludeFilename(includeFile);
scriptFilesElement.AddChild("scriptFile")
.SetStringAttribute("path", scriptSrc)
.SetIntAttribute(
"hash",
hashIt != options.includeFileHashes.end() ? hashIt->second : 0);
}
// Export the project
ExportToJSON(
fs, exportedProject, codeOutputDir + "/data.js", "gdjs.projectData");
ExportProjectData(
fs, exportedProject, codeOutputDir + "/data.js", runtimeGameOptions);
includesFiles.push_back(codeOutputDir + "/data.js");
// Copy all the dependencies
RemoveIncludes(false, true, includesFiles);
ExportIncludesAndLibs(includesFiles, exportDir, false);
ExportIncludesAndLibs(includesFiles, options.exportPath);
// Create the index file
if (!ExportPixiIndexFile(exportedProject,
gdjsRoot + "/Runtime/index.html",
exportDir,
options.exportPath,
includesFiles,
additionalSpec))
"gdjs.runtimeGameOptions"))
return false;
return true;
}
gd::String ExporterHelper::ExportToJSON(gd::AbstractFileSystem &fs,
const gd::Project &project,
gd::String filename,
gd::String wrapIntoVariable) {
gd::String ExporterHelper::ExportProjectData(
gd::AbstractFileSystem &fs,
const gd::Project &project,
gd::String filename,
const gd::SerializerElement &runtimeGameOptions) {
fs.MkDir(fs.DirNameFrom(filename));
// Save the project to JSON
gd::SerializerElement rootElement;
project.SerializeTo(rootElement);
gd::String output = gd::Serializer::ToJSON(rootElement);
if (!wrapIntoVariable.empty())
output = wrapIntoVariable + " = " + output + ";";
gd::String output =
"gdjs.projectData = " + gd::Serializer::ToJSON(rootElement) + ";\n" +
"gdjs.runtimeGameOptions = " +
gd::Serializer::ToJSON(runtimeGameOptions) + ";\n";
if (!fs.WriteToFile(filename, output)) return "Unable to write " + filename;
@@ -222,16 +302,41 @@ bool ExporterHelper::ExportCordovaFiles(const gd::Project &project,
.FindAndReplace("<!-- GDJS_ICONS_ANDROID -->", makeIconsAndroid())
.FindAndReplace("<!-- GDJS_ICONS_IOS -->", makeIconsIos());
if (!project.GetAdMobAppId().empty()) {
str = str.FindAndReplace(
"<!-- GDJS_ADMOB_PLUGIN_AND_APPLICATION_ID -->",
"<plugin name=\"cordova-plugin-admob-free\" spec=\"~0.21.0\">\n"
"\t\t<variable name=\"ADMOB_APP_ID\" value=\"" +
project.GetAdMobAppId() +
"\" />\n"
"\t</plugin>");
gd::String plugins = "";
for (std::shared_ptr<gd::PlatformExtension> extension :
project.GetCurrentPlatform().GetAllPlatformExtensions()) {
for (gd::DependencyMetadata dependency : extension->GetAllDependencies()) {
if (dependency.GetDependencyType() == "cordova") {
gd::String plugin;
plugin += "<plugin name=\"" + dependency.GetExportName();
if (dependency.GetVersion() != "") {
plugin += "\" spec=\"" + dependency.GetVersion();
}
plugin += "\">\n";
auto extraSettingValues = GetExtensionDependencyExtraSettingValues(
project, extension->GetName(), dependency);
// For Cordova, all settings are considered a plugin variable.
for (auto &extraSetting : extraSettingValues) {
plugin += "\t\t<variable name=\"" + extraSetting.first +
"\" value=\"" + extraSetting.second + "\" />\n";
}
plugin += "\t</plugin>";
// Don't include the plugin if an extra setting was not fulfilled.
bool missingSetting = AreMapKeysMissingElementOfSet(
extraSettingValues, dependency.GetRequiredExtraSettingsForExport());
if (!missingSetting) plugins += plugin;
}
}
}
str =
str.FindAndReplace("<!-- GDJS_EXTENSION_CORDOVA_DEPENDENCY -->", plugins);
if (!fs.WriteToFile(exportDir + "/config.xml", str)) {
lastError = "Unable to write Cordova config.xml file.";
return false;
@@ -307,15 +412,21 @@ bool ExporterHelper::ExportCocos2dFiles(
{
gd::String includeFilesStr = "";
bool first = true;
for (auto &file : includesFiles) {
if (!fs.FileExists(exportDir + "/src/" + file)) {
std::cout << "Warning: Unable to find " << exportDir + "/" + file << "."
for (auto &include : includesFiles) {
gd::String scriptSrc = GetExportedIncludeFilename(include);
// Sanity check if the file exists - if not skip it to avoid
// including it in the list of scripts.
gd::String absoluteFilename = scriptSrc;
fs.MakeAbsolute(absoluteFilename, exportDir + "/src");
if (!fs.FileExists(absoluteFilename)) {
std::cout << "Warning: Unable to find " << absoluteFilename << "."
<< std::endl;
continue;
}
includeFilesStr +=
gd::String(first ? "" : ", ") + "\"src/" + file + "\"\n";
gd::String(first ? "" : ", ") + "\"src/" + scriptSrc + "\"\n";
first = false;
}
@@ -376,6 +487,39 @@ bool ExporterHelper::ExportElectronFiles(const gd::Project &project,
.FindAndReplace("\"GDJS_GAME_VERSION\"", jsonVersion)
.FindAndReplace("\"GDJS_GAME_MANGLED_NAME\"", jsonMangledName);
gd::String packages = "";
for (std::shared_ptr<gd::PlatformExtension> extension :
project.GetCurrentPlatform()
.GetAllPlatformExtensions()) { // TODO Add a way to select only
// used Extensions
for (gd::DependencyMetadata dependency :
extension->GetAllDependencies()) {
if (dependency.GetDependencyType() == "npm") {
if (dependency.GetVersion() == "") {
gd::LogError(
"Latest Version not available for NPM dependencies, "
"dependency " +
dependency.GetName() +
" is not exported. Please specify a version when calling "
"addDependency.");
continue;
}
packages += "\n\t\"" + dependency.GetExportName() + "\": \"" +
dependency.GetVersion() + "\",";
// For node extra settings are ignored
}
}
}
if (!packages.empty()) {
// Remove the , at the end as last item cannot have , in JSON.
packages = packages.substr(0, packages.size() - 1);
}
str = str.FindAndReplace("\"GDJS_EXTENSION_NPM_DEPENDENCY\": \"0\"",
packages);
if (!fs.WriteToFile(exportDir + "/package.json", str)) {
lastError = "Unable to write Electron package.json file.";
return false;
@@ -425,25 +569,17 @@ bool ExporterHelper::CompleteIndexFile(
if (additionalSpec.empty()) additionalSpec = "{}";
gd::String codeFilesIncludes;
for (std::vector<gd::String>::const_iterator it = includesFiles.begin();
it != includesFiles.end();
++it) {
gd::String scriptSrc = "";
if (fs.IsAbsolute(*it)) {
// Most of the time, script source are file paths relative to GDJS root or
// have been copied in the output directory, so they are relative. It's
// still useful to test here for absolute files as the exporter could be
// configured with a file system dealing with URL.
scriptSrc = *it;
} else {
if (!fs.FileExists(exportDir + "/" + *it)) {
std::cout << "Warning: Unable to find " << exportDir + "/" + *it << "."
<< std::endl;
continue;
}
for (auto &include : includesFiles) {
gd::String scriptSrc = GetExportedIncludeFilename(include);
scriptSrc = exportDir + "/" + *it;
fs.MakeRelative(scriptSrc, exportDir);
// Sanity check if the file exists - if not skip it to avoid
// including it in the list of scripts.
gd::String absoluteFilename = scriptSrc;
fs.MakeAbsolute(absoluteFilename, exportDir);
if (!fs.FileExists(absoluteFilename)) {
std::cout << "Warning: Unable to find " << absoluteFilename << "."
<< std::endl;
continue;
}
codeFilesIncludes += "\t<script src=\"" + scriptSrc +
@@ -500,6 +636,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "events-tools/networktools.js");
if (websocketDebuggerClient) {
InsertUnique(includesFiles, "websocket-debugger-client/hot-reloader.js");
InsertUnique(includesFiles,
"websocket-debugger-client/websocket-debugger-client.js");
}
@@ -635,38 +772,50 @@ bool ExporterHelper::ExportExternalSourceFiles(
return true;
}
gd::String ExporterHelper::GetExportedIncludeFilename(
const gd::String &include) {
if (!fs.IsAbsolute(include)) {
// By convention, an include file that is relative is relative to
// the "<GDJS Root>/Runtime" folder, and will have the same relative
// path when exported.
// We still do this seemingly useless relative to absolute to relative
// conversion, because some filesystems are using a URL for gdjsRoot, and
// will convert the relative include to an absolute URL.
gd::String relativeInclude = gdjsRoot + "/Runtime/" + include;
fs.MakeRelative(relativeInclude, gdjsRoot + "/Runtime/");
return relativeInclude;
} else {
// Note: all the code generated from events are generated in another
// folder and fall in this case:
return fs.FileNameFrom(include);
}
}
bool ExporterHelper::ExportIncludesAndLibs(
std::vector<gd::String> &includesFiles,
gd::String exportDir,
bool /*minify*/) {
for (std::vector<gd::String>::iterator include = includesFiles.begin();
include != includesFiles.end();
++include) {
if (!fs.IsAbsolute(*include)) {
gd::String source = gdjsRoot + "/Runtime/" + *include;
const std::vector<gd::String> &includesFiles, gd::String exportDir) {
for (auto &include : includesFiles) {
if (!fs.IsAbsolute(include)) {
// By convention, an include file that is relative is relative to
// the "<GDJS Root>/Runtime" folder, and will have the same relative
// path when exported.
gd::String source = gdjsRoot + "/Runtime/" + include;
if (fs.FileExists(source)) {
gd::String path = fs.DirNameFrom(exportDir + "/" + *include);
gd::String path = fs.DirNameFrom(exportDir + "/" + include);
if (!fs.DirExists(path)) fs.MkDir(path);
fs.CopyFile(source, exportDir + "/" + *include);
gd::String relativeInclude = source;
fs.MakeRelative(relativeInclude, gdjsRoot + "/Runtime/");
*include = relativeInclude;
fs.CopyFile(source, exportDir + "/" + include);
} else {
std::cout << "Could not find GDJS include file " << *include
std::cout << "Could not find GDJS include file " << include
<< std::endl;
}
} else {
// Note: all the code generated from events are generated in another
// folder and fall in this case:
if (fs.FileExists(*include)) {
fs.CopyFile(*include, exportDir + "/" + fs.FileNameFrom(*include));
*include = fs.FileNameFrom(
*include); // Ensure filename is relative to the export dir.
if (fs.FileExists(include)) {
fs.CopyFile(include, exportDir + "/" + fs.FileNameFrom(include));
} else {
std::cout << "Could not find include file " << *include << std::endl;
std::cout << "Could not find include file " << include << std::endl;
}
}
}
@@ -674,6 +823,45 @@ bool ExporterHelper::ExportIncludesAndLibs(
return true;
}
void ExporterHelper::ExportObjectAndBehaviorsIncludes(
gd::Project &project, std::vector<gd::String> &includesFiles) {
auto addIncludeFiles = [&](const std::vector<gd::String> &newIncludeFiles) {
for (const auto &includeFile : newIncludeFiles) {
InsertUnique(includesFiles, includeFile);
}
};
auto addObjectIncludeFiles = [&](const gd::Object &object) {
// Ensure needed files are included for the object type and its behaviors.
const gd::ObjectMetadata &metadata =
gd::MetadataProvider::GetObjectMetadata(JsPlatform::Get(),
object.GetType());
addIncludeFiles(metadata.includeFiles);
std::vector<gd::String> behaviors = object.GetAllBehaviorNames();
for (std::size_t j = 0; j < behaviors.size(); ++j) {
const gd::BehaviorMetadata &metadata =
gd::MetadataProvider::GetBehaviorMetadata(
JsPlatform::Get(),
object.GetBehavior(behaviors[j]).GetTypeName());
addIncludeFiles(metadata.includeFiles);
}
};
auto addObjectsIncludeFiles =
[&](const gd::ObjectsContainer &objectsContainer) {
for (std::size_t i = 0; i < objectsContainer.GetObjectsCount(); ++i) {
addObjectIncludeFiles(objectsContainer.GetObject(i));
}
};
addObjectsIncludeFiles(project);
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
gd::Layout &layout = project.GetLayout(i);
addObjectsIncludeFiles(layout);
}
}
void ExporterHelper::ExportResources(gd::AbstractFileSystem &fs,
gd::Project &project,
gd::String exportDir) {

View File

@@ -5,14 +5,17 @@
*/
#ifndef EXPORTER_HELPER_H
#define EXPORTER_HELPER_H
#include <map>
#include <set>
#include <string>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Project;
class Layout;
class ExternalLayout;
class SerializerElement;
class AbstractFileSystem;
class ResourcesManager;
} // namespace gd
@@ -20,6 +23,74 @@ class wxProgressDialog;
namespace gdjs {
/**
* \brief The options used to export a project for a preview.
*/
struct PreviewExportOptions {
/**
* \param project_ The project to export
* \param exportPath_ The path in the filesystem where to export the files
*/
PreviewExportOptions(gd::Project &project_, const gd::String &exportPath_)
: project(project_), exportPath(exportPath_), projectDataOnlyExport(false) {};
/**
* \brief Set the address of the debugger server that the game should reach out to,
* using WebSockets.
*/
PreviewExportOptions &SetDebuggerServerAddress(const gd::String& address, const gd::String& port) {
debuggerServerAddress = address;
debuggerServerPort = port;
return *this;
}
/**
* \brief Set the layout to be run first in the previewed game
*/
PreviewExportOptions &SetLayoutName(const gd::String &layoutName_) {
layoutName = layoutName_;
return *this;
}
/**
* \brief Set the (optional) external layout to be instanciated in the scene
* at the beginning of the previewed game.
*/
PreviewExportOptions &SetExternalLayoutName(
const gd::String &externalLayoutName_) {
externalLayoutName = externalLayoutName_;
return *this;
}
/**
* \brief Set the hash associated to an include file. Useful for the preview
* hot-reload, to know if a file changed.
*/
PreviewExportOptions &SetIncludeFileHash(const gd::String &includeFile,
int hash) {
includeFileHashes[includeFile] = hash;
return *this;
}
/**
* \brief Set if the export should only export the project data, not
* exporting events code.
*/
PreviewExportOptions& SetProjectDataOnlyExport(bool enable) {
projectDataOnlyExport = enable;
return *this;
}
gd::Project &project;
gd::String exportPath;
gd::String debuggerServerAddress;
gd::String debuggerServerPort;
gd::String layoutName;
gd::String externalLayoutName;
std::map<gd::String, int> includeFileHashes;
bool projectDataOnlyExport;
};
/**
* \brief Export a project or a layout to a playable HTML5/Javascript based
* game.
@@ -42,15 +113,15 @@ class ExporterHelper {
* \param fs The abstract file system to use to write the file
* \param project The project to be exported.
* \param filename The filename where export the project
* \param wrapIntoVariable If not empty, the resulting json will be wrapped in
* this javascript variable allowing to use it as a classical javascript
* object. \return Empty string if everthing is ok, description of the error
* otherwise.
* \param runtimeGameOptions The content of the extra configuration to store
* in gdjs.runtimeGameOptions \return Empty string if everthing is ok,
* description of the error otherwise.
*/
static gd::String ExportToJSON(gd::AbstractFileSystem &fs,
const gd::Project &project,
gd::String filename,
gd::String wrapIntoVariable);
static gd::String ExportProjectData(
gd::AbstractFileSystem &fs,
const gd::Project &project,
gd::String filename,
const gd::SerializerElement &runtimeGameOptions);
/**
* \brief Copy all the resources of the project to to the export directory,
@@ -82,22 +153,14 @@ class ExporterHelper {
std::vector<gd::String> &includesFiles);
/**
* \brief Copy all the includes files and the standard libraries files to the
* export directory.
*
* The includes files are also modified so as to be relative to the export
* directory ( Files with absolute filenames are copied into the export
* directory and their path are stripped ).
* \brief Copy all the specified files to the
* export directory. Relative files are copied from "<GDJS root>/Runtime" directory.
*
* \param includesFiles A vector with filenames to be copied.
* \param exportDir The directory where the preview must be created.
* \param minify If true, the includes files must be merged into one file
* using Google Closure Compiler. ( includesFiles parameter will be updated
* with the new filename )
* \param exportDir The directory where the files mus tbe copied.
*/
bool ExportIncludesAndLibs(std::vector<gd::String> &includesFiles,
gd::String exportDir,
bool minify);
bool ExportIncludesAndLibs(const std::vector<gd::String> &includesFiles,
gd::String exportDir);
/**
* \brief Generate the events JS code, and save them to the export directory.
@@ -119,6 +182,13 @@ class ExporterHelper {
bool ExportEffectIncludes(gd::Project &project,
std::vector<gd::String> &includesFiles);
/**
* \brief Add the include files for all the objects of the project
* and their behaviors.
*/
void ExportObjectAndBehaviorsIncludes(
gd::Project &project, std::vector<gd::String> &includesFiles);
/**
* \brief Copy the external source files used by the game into the export
* directory, and add them into files to be included.
@@ -208,18 +278,19 @@ class ExporterHelper {
gd::String exportDir);
/**
* \brief Launch all export methods to generate a complete, stand-alone game
* for previewing.
* \brief Create a preview for the specified options.
* \note The preview is not launched, it is the caller responsibility to open
* a browser pointing to the preview.
*
* \param layout The layout to be previewed.
* \param exportDir The directory where the preview must be created.
* \param additionalSpec Any additional parameters to be passed to the
* gdjs.RuntimeGame. \return true if export was successful.
* \param options The options to generate the preview.
*/
bool ExportLayoutForPixiPreview(gd::Project &project,
gd::Layout &layout,
gd::String exportDir,
gd::String additionalSpec);
bool ExportProjectForPixiPreview(const PreviewExportOptions &options);
/**
* \brief Given an include file, returns the name of the file to reference
* in the exported game.
*/
gd::String GetExportedIncludeFilename(const gd::String& include);
/**
* \brief Change the directory where code files are generated.

View File

@@ -28,7 +28,7 @@
<preference name="Fullscreen" value="true" />
<!-- Cordova/Phonegap version -->
<preference name="phonegap-version" value="cli-8.0.0" />
<preference name="phonegap-version" value="cli-9.0.0" />
<!-- GDJS_ADMOB_PLUGIN_AND_APPLICATION_ID -->
<!-- GDJS_EXTENSION_CORDOVA_DEPENDENCY -->
</widget>

View File

@@ -3,7 +3,7 @@
* running in Electron Runtime.
*/
// Modules to control application life and create native browser window
const { app, BrowserWindow, shell } = require("electron");
const { app, BrowserWindow, shell, Menu } = require("electron");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
@@ -31,6 +31,8 @@ function createWindow() {
// and load the index.html of the app.
mainWindow.loadFile("app/index.html");
Menu.setApplicationMenu(null);
// Open the DevTools.
// mainWindow.webContents.openDevTools()

View File

@@ -5,7 +5,9 @@
"description": "GDJS_GAME_NAME",
"author": "GDJS_GAME_AUTHOR",
"version": "GDJS_GAME_VERSION",
"dependencies": {},
"dependencies": {
"GDJS_EXTENSION_NPM_DEPENDENCY": "0"
},
"devDependencies": {
"electron": "8.2.5"
},

View File

@@ -26,6 +26,15 @@ gdjs.CocosImageManager = function(resources)
gdjs.ImageManager = gdjs.CocosImageManager; //Register the class to let the engine use it.
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.CocosImageManager.prototype.setResources = function(resources) {
this._resources = resources;
};
/**
* Return the texture associated to the specified name.
* Returns a placeholder texture if not found.

View File

@@ -16,7 +16,9 @@ gdjs.LayerCocosRenderer = function(layer, runtimeSceneRenderer)
this._layer = layer;
this.convertYPosition = runtimeSceneRenderer.convertYPosition;
var effects = this._layer.getEffectsData();
// Read effects from the layer as we can't dynamically add effects
// in Cocos2d-JS.
var effects = this._layer.getInitialEffectsData();
if (effects.length === 0) {
this._cocosLayer = new CocosLayer();
runtimeSceneRenderer.getCocosScene().addChild(this._cocosLayer);
@@ -60,7 +62,7 @@ gdjs.LayerCocosRenderer.prototype._makeShaders = function() {
return;
}
var effects = this._layer.getEffectsData();
var effects = this._layer.getInitialEffectsData();
if (effects.length === 0) {
return;
} else if (effects.length > 1) {
@@ -127,7 +129,11 @@ gdjs.LayerCocosRenderer.prototype.updateVisibility = function(visible) {
this._cocosLayer.setVisible(visible);
}
gdjs.LayerCocosRenderer.prototype.updateTime = function() {
gdjs.LayerCocosRenderer.prototype.update = function() {
// Unimplemented
}
gdjs.LayerCocosRenderer.prototype.updateClearColor = function() {
// Unimplemented
}
@@ -180,7 +186,8 @@ gdjs.LayerCocosRenderer.prototype.enableEffect = function(name, value) {
};
gdjs.LayerCocosRenderer.prototype.addEffect = function(effectData) {
// Unimplemented
// Unimplemented - adding effects is not supported in Cocos2d-JS.
// All effects are supposed to be added to the layer at its creation.
};
gdjs.LayerCocosRenderer.prototype.removeEffect = function(effect) {
@@ -190,3 +197,8 @@ gdjs.LayerCocosRenderer.prototype.removeEffect = function(effect) {
gdjs.LayerCocosRenderer.prototype.isEffectEnabled = function(name) {
return this.hasEffect(name);
};
gdjs.LayerCocosRenderer.prototype.setLayerIndex = function(layer, index) {
// Unimplemented
};

View File

@@ -187,3 +187,14 @@ gdjs.RuntimeSceneCocosRenderer.prototype.showCursor = function() {
gdjs.RuntimeSceneCocosRenderer.prototype.getCocosScene = function() {
return this._cocosScene;
}
/**
* @param {gdjs.Layer} layer
* @param {number} index
*/
gdjs.RuntimeSceneCocosRenderer.prototype.setLayerIndex = function (
layer,
index
) {
// Not implemented
}

View File

@@ -66,6 +66,15 @@ gdjs.CocosSoundManager = function(resources)
gdjs.SoundManager = gdjs.CocosSoundManager; //Register the class to let the engine use it.
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.CocosSoundManager.prototype.setResources = function(resources) {
this._resources = resources;
};
/**
* Return the file associated to the given sound name.
*

View File

@@ -10,96 +10,157 @@
*/
gdjs.evtTools.network = gdjs.evtTools.network || {};
gdjs.evtTools.network.sendHttpRequest = function(host, uri, body, method, contentType, responseVar)
{
try {
var xhr;
if (typeof XMLHttpRequest !== 'undefined')
xhr = new XMLHttpRequest();
else {
var versions = ["MSXML2.XmlHttp.5.0",
"MSXML2.XmlHttp.4.0",
"MSXML2.XmlHttp.3.0",
"MSXML2.XmlHttp.2.0",
"Microsoft.XmlHttp"]
/**
* Send an asynchronous request to the specified URL, with the specified (text)
* body, method and contentType (defaults to `application/x-www-form-urlencoded`).
* The result is stored in the specified response variable. Any error is stored in
* the specified error variable.
*
* @param {string} url The URL to send the request to.
* @param {string} body The content to be sent.
* @param {string} method The method to use ("GET", "POST", "PUT", "HEAD", "DELETE", "PATCH", "OPTIONS")
* @param {string} contentType The content type. Defaults to `application/x-www-form-urlencoded` if empty.
* @param {gdjs.Variable} responseVar The variable where to store the response text.
* @param {gdjs.Variable} errorVar The variable where to store the error message or status code (if status >= 400).
*/
gdjs.evtTools.network.sendAsyncRequest = function (
url,
body,
method,
contentType,
responseVar,
errorVar
) {
const onError = (err) => {
errorVar.setString('' + err);
};
for(var i = 0, len = versions.length; i < len; i++) {
try {
xhr = new ActiveXObject(versions[i]);
break;
}
catch(e){}
} // end for
}
try {
const request = new XMLHttpRequest();
request.onerror = onError;
request.ontimeout = onError;
request.onabort = onError;
request.onreadystatechange = () => {
if (request.readyState === 4 /* "DONE" */) {
if (request.status >= 400) {
onError('' + request.status);
}
if ( xhr === undefined ) return;
responseVar.setString(request.responseText);
}
};
xhr.open(method, host+uri, false);
xhr.setRequestHeader( "Content-Type", contentType === "" ? "application/x-www-form-urlencoded" : contentType );
xhr.send(body);
responseVar.setString(xhr.responseText);
}
catch(e){}
request.open(method, url);
request.setRequestHeader(
'Content-Type',
contentType === '' ? 'application/x-www-form-urlencoded' : contentType
);
request.send(body);
} catch (err) {
onError(err);
}
};
/** @deprecated */
gdjs.evtTools.network.sendDeprecatedSynchronousRequest = function (
host,
uri,
body,
method,
contentType,
responseVar
) {
try {
var xhr;
if (typeof XMLHttpRequest !== 'undefined') xhr = new XMLHttpRequest();
else {
var versions = [
'MSXML2.XmlHttp.5.0',
'MSXML2.XmlHttp.4.0',
'MSXML2.XmlHttp.3.0',
'MSXML2.XmlHttp.2.0',
'Microsoft.XmlHttp',
];
for (var i = 0, len = versions.length; i < len; i++) {
try {
xhr = new ActiveXObject(versions[i]);
break;
} catch (e) {}
} // end for
}
if (xhr === undefined) return;
xhr.open(method, host + uri, false);
xhr.setRequestHeader(
'Content-Type',
contentType === '' ? 'application/x-www-form-urlencoded' : contentType
);
xhr.send(body);
responseVar.setString(xhr.responseText);
} catch (e) {}
};
/**
* Convert a variable to JSON.
* TODO: Move to gdjs.Variable static
* @param {gdjs.Variable} variable The variable to convert to JSON
* @returns {string} The JSON string representing the variable
* @returns {string} The JSON string representing the variable
*/
gdjs.evtTools.network.variableStructureToJSON = function(variable)
{
if ( !variable.isStructure() ) {
if ( variable.isNumber() )
return JSON.stringify(variable.getAsNumber());
else
return JSON.stringify(variable.getAsString());
gdjs.evtTools.network.variableStructureToJSON = function (variable) {
if (!variable.isStructure()) {
if (variable.isNumber()) return JSON.stringify(variable.getAsNumber());
else return JSON.stringify(variable.getAsString());
}
var str = '{';
var firstChild = true;
var children = variable.getAllChildren();
for (var p in children) {
if (children.hasOwnProperty(p)) {
if (!firstChild) str += ',';
str +=
JSON.stringify(p) +
': ' +
gdjs.evtTools.network.variableStructureToJSON(children[p]);
firstChild = false;
}
}
var str = "{";
var firstChild = true;
var children = variable.getAllChildren();
for(var p in children) {
if (children.hasOwnProperty(p)) {
if ( !firstChild ) str += ",";
str += JSON.stringify(p) + ": " + gdjs.evtTools.network.variableStructureToJSON(children[p]);
firstChild = false;
}
}
str += "}";
return str;
str += '}';
return str;
};
gdjs.evtTools.network.objectVariableStructureToJSON = function(object, variable)
{
return gdjs.evtTools.network.variableStructureToJSON(variable);
}
gdjs.evtTools.network.objectVariableStructureToJSON = function (
object,
variable
) {
return gdjs.evtTools.network.variableStructureToJSON(variable);
};
gdjs.evtTools.network._objectToVariable = function(obj, variable)
{
if(!isNaN(obj)) { //Number
variable.setNumber(obj);
}
else if (typeof obj == 'string' || obj instanceof String) {
variable.setString(obj);
}
else if ( Array.isArray(obj) ) {
for(var i = 0;i<obj.length;++i) {
gdjs.evtTools.network._objectToVariable(obj[i], variable.getChild(i.toString()));
}
}
else {
for(var p in obj) {
if (obj.hasOwnProperty(p)) {
gdjs.evtTools.network._objectToVariable(obj[p], variable.getChild(p));
}
}
}
}
gdjs.evtTools.network._objectToVariable = function (obj, variable) {
if (!isNaN(obj)) {
//Number
variable.setNumber(obj);
} else if (typeof obj == 'string' || obj instanceof String) {
variable.setString(obj);
} else if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; ++i) {
gdjs.evtTools.network._objectToVariable(
obj[i],
variable.getChild(i.toString())
);
}
} else {
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
gdjs.evtTools.network._objectToVariable(obj[p], variable.getChild(p));
}
}
}
};
/**
* Parse the given JSON and fill the content of the variable with it
@@ -108,20 +169,22 @@ gdjs.evtTools.network._objectToVariable = function(obj, variable)
* @param {gdjs.Variable} variable The variable where to put the parsed JSON
* @returns {boolean} true if JSON was properly parsed
*/
gdjs.evtTools.network.jsonToVariableStructure = function(jsonStr, variable)
{
if ( jsonStr.length === 0 ) return false;
try {
var obj = JSON.parse(jsonStr);
gdjs.evtTools.network._objectToVariable(obj, variable);
return true;
} catch(e) {
//Do nothing iF JSON was not properly parsed;
return false;
}
}
gdjs.evtTools.network.jsonToVariableStructure = function (jsonStr, variable) {
if (jsonStr.length === 0) return false;
try {
var obj = JSON.parse(jsonStr);
gdjs.evtTools.network._objectToVariable(obj, variable);
return true;
} catch (e) {
//Do nothing iF JSON was not properly parsed;
return false;
}
};
gdjs.evtTools.network.jsonToObjectVariableStructure = function(jsonStr, object, variable)
{
gdjs.evtTools.network.jsonToVariableStructure(jsonStr, variable);
}
gdjs.evtTools.network.jsonToObjectVariableStructure = function (
jsonStr,
object,
variable
) {
gdjs.evtTools.network.jsonToVariableStructure(jsonStr, variable);
};

View File

@@ -173,44 +173,48 @@ gdjs.evtTools.object.pickObjectsIf = function(predicate, objectsLists, negatePre
var lists = gdjs.staticArray(gdjs.evtTools.object.pickObjectsIf);
objectsLists.values(lists);
//Create a boolean for each object
for(var i = 0, leni = lists.length;i<leni;++i) {
var arr = lists[i];
for(var k = 0, lenk = arr.length;k<lenk;++k) {
arr[k].pick = false;
}
}
//Pick only objects that are fulfilling the predicate
// Pick only objects that are fulfilling the predicate.
for(var i = 0, leni = lists.length;i<leni;++i) {
var arr = lists[i];
for(var k = 0, lenk = arr.length;k<lenk;++k) {
if (negatePredicate ^ predicate(arr[k], extraArg)) {
var object = arr[k];
if (negatePredicate ^ predicate(object, extraArg)) {
isTrue = true;
arr[k].pick = true; //Pick the objects
object.pick = true;
} else {
object.pick = false;
}
}
}
//Trim not picked objects from lists.
// Trim not picked objects from lists.
for(var i = 0, leni = lists.length;i<leni;++i) {
var arr = lists[i];
var finalSize = 0;
for(var k = 0, lenk = arr.length;k<lenk;++k) {
var obj = arr[k];
if ( arr[k].pick ) {
arr[finalSize] = obj;
finalSize++;
}
}
arr.length = finalSize;
gdjs.evtTools.object.filterPickedObjectsList(lists[i]);
}
return isTrue;
};
/**
* Filter in-place the specified array to remove objects for which
* `pick` property is set to false.
* @param {gdjs.RuntimeObject[]} arr
*/
gdjs.evtTools.object.filterPickedObjectsList = function (arr) {
var finalSize = 0;
for (var k = 0, lenk = arr.length; k < lenk; ++k) {
var obj = arr[k];
if (obj.pick) {
arr[finalSize] = obj;
finalSize++;
}
}
arr.length = finalSize;
};
gdjs.evtTools.object.hitBoxesCollisionTest = function(objectsLists1, objectsLists2, inverted, runtimeScene, ignoreTouchingEdges) {
return gdjs.evtTools.object.twoListsTest(gdjs.RuntimeObject.collisionTest,
objectsLists1, objectsLists2, inverted, ignoreTouchingEdges);

View File

@@ -159,5 +159,7 @@ gdjs.evtTools.runtimeScene.createObjectsFromExternalLayout = function(scene, ext
var externalLayoutData = scene.getGame().getExternalLayoutData(externalLayout);
if ( externalLayoutData === null ) return;
scene.createObjectsFrom(externalLayoutData.instances, xPos, yPos);
// trackByPersistentUuid is set to false as we don't want external layouts
// instantiated at runtime to be hot-reloaded.
scene.createObjectsFrom(externalLayoutData.instances, xPos, yPos, /*trackByPersistentUuid=*/ false);
};

View File

@@ -29,6 +29,15 @@ gdjs.FontFaceObserverFontManager = function (resources) {
gdjs.FontManager = gdjs.FontFaceObserverFontManager; //Register the class to let the engine use it.
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.FontFaceObserverFontManager.prototype.setResources = function(resources) {
this._resources = resources;
};
/**
* Return the font family associated to the specified font resource name.
* The font resource must have been loaded before. If that's not the case,
@@ -163,6 +172,10 @@ gdjs.FontFaceObserverFontManager.prototype.loadFonts = function (
for (var i = 0, len = resources.length; i < len; ++i) {
var res = resources[i];
if (res.file && res.kind === 'font') {
if (!!this._loadedFonts[res.name]) {
continue;
}
filesResources[res.file] = filesResources[res.file]
? filesResources[res.file].concat(res)
: [res];

View File

@@ -1,3 +1,4 @@
// @ts-check
/*
* GDevelop JS Platform
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
@@ -8,26 +9,29 @@
* The `gdjs` namespace contains all classes and objects of the game engine.
* @namespace
*/
window.gdjs = {
objectsTypes: new Hashtable(),
behaviorsTypes: new Hashtable(),
/**
* Contains functions used by events (this is a convention only, functions can actually
* be anywhere).
* @namespace
* @memberOf gdjs
*/
evtTools: {},
callbacksFirstRuntimeSceneLoaded: [],
callbacksRuntimeSceneLoaded: [],
callbacksRuntimeScenePreEvents: [],
callbacksRuntimeScenePostEvents: [],
callbacksRuntimeScenePaused: [],
callbacksRuntimeSceneResumed: [],
callbacksRuntimeSceneUnloading: [],
callbacksRuntimeSceneUnloaded: [],
callbacksObjectDeletedFromScene: [],
};
// @ts-ignore - creating the global object acting as a namespace
window.gdjs = {};
/**
* Contains functions used by events (this is a convention only, functions can actually
* be anywhere).
* @namespace
* @memberOf gdjs
*/
gdjs.evtTools = {};
gdjs.objectsTypes = new Hashtable();
gdjs.behaviorsTypes = new Hashtable();
/** @type {Function[]} */ gdjs.callbacksFirstRuntimeSceneLoaded = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeSceneLoaded = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeScenePreEvents = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeScenePostEvents = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeScenePaused = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeSceneResumed = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeSceneUnloading = [];
/** @type {Function[]} */ gdjs.callbacksRuntimeSceneUnloaded = [];
/** @type {Function[]} */ gdjs.callbacksObjectDeletedFromScene = [];
/**
* Convert a rgb color value to a hex string.
@@ -38,7 +42,7 @@ window.gdjs = {
* @param {number} b Blue
* @returns {string}
*/
gdjs.rgbToHex = function(r, g, b) {
gdjs.rgbToHex = function (r, g, b) {
return '' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
@@ -49,7 +53,7 @@ gdjs.rgbToHex = function(r, g, b) {
* @param {number} b Blue
* @returns {number}
*/
gdjs.rgbToHexNumber = function(r, g, b) {
gdjs.rgbToHexNumber = function (r, g, b) {
return (r << 16) + (g << 8) + b;
};
@@ -58,7 +62,7 @@ gdjs.rgbToHexNumber = function(r, g, b) {
* @param {number} max The maximum value (inclusive).
* @returns {number}
*/
gdjs.random = function(max) {
gdjs.random = function (max) {
if (max <= 0) return 0;
return Math.floor(Math.random() * (max + 1));
};
@@ -69,7 +73,7 @@ gdjs.random = function(max) {
* @param {number} max The maximum value (inclusive).
* @returns {number}
*/
gdjs.randomInRange = function(min, max) {
gdjs.randomInRange = function (min, max) {
return min + gdjs.random(max - min); // return min if min >= max
};
@@ -78,7 +82,7 @@ gdjs.randomInRange = function(min, max) {
* @param {number} max The maximum value (exclusive).
* @returns {number}
*/
gdjs.randomFloat = function(max) {
gdjs.randomFloat = function (max) {
if (max <= 0) return 0;
return Math.random() * max;
};
@@ -89,7 +93,7 @@ gdjs.randomFloat = function(max) {
* @param {number} max The maximum value (exclusive).
* @returns {number}
*/
gdjs.randomFloatInRange = function(min, max) {
gdjs.randomFloatInRange = function (min, max) {
return min + gdjs.randomFloat(max - min); // return min if min >= max
};
@@ -100,7 +104,7 @@ gdjs.randomFloatInRange = function(min, max) {
* @param {number} step The interval between each value.
* @returns {number}
*/
gdjs.randomWithStep = function(min, max, step) {
gdjs.randomWithStep = function (min, max, step) {
if (step <= 0) return min + gdjs.random(max - min);
return min + gdjs.random(Math.floor((max - min) / step)) * step; // return min if min >= max
};
@@ -110,7 +114,7 @@ gdjs.randomWithStep = function(min, max, step) {
* @param {number} angleInDegrees The angle in degrees.
* @returns {number}
*/
gdjs.toRad = function(angleInDegrees) {
gdjs.toRad = function (angleInDegrees) {
return (angleInDegrees / 180) * 3.14159;
};
@@ -119,18 +123,10 @@ gdjs.toRad = function(angleInDegrees) {
* @param {number} angleInRadians The angle in radians.
* @returns {number}
*/
gdjs.toDegrees = function(angleInRadians) {
gdjs.toDegrees = function (angleInRadians) {
return (angleInRadians * 180) / 3.14159;
};
/**
* A Constructor for a {@link gdjs.RuntimeObject}.
* @name RuntimeObjectConstructor
* @function
* @param {gdjs.RuntimeScene} runtimeScene The {@link gdjs.RuntimeScene} the object belongs to.
* @param {ObjectData} objectData The initial properties of the object.
*/
/**
* Register a runtime object (class extending {@link gdjs.RuntimeObject}) that can be used in a scene.
*
@@ -139,21 +135,12 @@ gdjs.toDegrees = function(angleInRadians) {
* of the type of the object is "TextObject::Text".
*
* @param {string} objectTypeName The name of the type of the Object.
* @param {RuntimeObjectConstructor} Ctor The constructor of the Object.
* @param {typeof gdjs.RuntimeObject} Ctor The constructor of the Object.
*/
gdjs.registerObject = function(objectTypeName, Ctor) {
gdjs.registerObject = function (objectTypeName, Ctor) {
gdjs.objectsTypes.put(objectTypeName, Ctor);
};
/**
* A Constructor for a {@link gdjs.RuntimeBehavior}.
* @name RuntimeBehaviorConstructor
* @function
* @param {gdjs.RuntimeScene} runtimeScene The scene owning the object of the behavior
* @param {BehaviorData} behaviorData The properties used to setup the behavior
* @param {gdjs.RuntimeObject} owner The object owning the behavior
*/
/**
* Register a runtime behavior (class extending {@link gdjs.RuntimeBehavior}) that can be used by a
* {@link gdjs.RuntimeObject}.
@@ -163,9 +150,9 @@ gdjs.registerObject = function(objectTypeName, Ctor) {
* the full name of the type of the behavior is "DraggableBehavior::Draggable".
*
* @param {string} behaviorTypeName The name of the type of the behavior.
* @param {RuntimeBehaviorConstructor} Ctor The constructor of the Object.
* @param {typeof gdjs.RuntimeBehavior} Ctor The constructor of the Object.
*/
gdjs.registerBehavior = function(behaviorTypeName, Ctor) {
gdjs.registerBehavior = function (behaviorTypeName, Ctor) {
gdjs.behaviorsTypes.put(behaviorTypeName, Ctor);
};
@@ -175,7 +162,7 @@ gdjs.registerBehavior = function(behaviorTypeName, Ctor) {
*
* @param {Function} callback The function to be called.
*/
gdjs.registerFirstRuntimeSceneLoadedCallback = function(callback) {
gdjs.registerFirstRuntimeSceneLoadedCallback = function (callback) {
gdjs.callbacksFirstRuntimeSceneLoaded.push(callback);
};
@@ -183,7 +170,7 @@ gdjs.registerFirstRuntimeSceneLoadedCallback = function(callback) {
* Register a function to be called when a scene is loaded.
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeSceneLoadedCallback = function(callback) {
gdjs.registerRuntimeSceneLoadedCallback = function (callback) {
gdjs.callbacksRuntimeSceneLoaded.push(callback);
};
@@ -192,7 +179,7 @@ gdjs.registerRuntimeSceneLoadedCallback = function(callback) {
* before events are run.
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeScenePreEventsCallback = function(callback) {
gdjs.registerRuntimeScenePreEventsCallback = function (callback) {
gdjs.callbacksRuntimeScenePreEvents.push(callback);
};
@@ -201,7 +188,7 @@ gdjs.registerRuntimeScenePreEventsCallback = function(callback) {
* after events are run and before rendering.
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeScenePostEventsCallback = function(callback) {
gdjs.registerRuntimeScenePostEventsCallback = function (callback) {
gdjs.callbacksRuntimeScenePostEvents.push(callback);
};
@@ -209,7 +196,7 @@ gdjs.registerRuntimeScenePostEventsCallback = function(callback) {
* Register a function to be called when a scene is paused.
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeScenePausedCallback = function(callback) {
gdjs.registerRuntimeScenePausedCallback = function (callback) {
gdjs.callbacksRuntimeScenePaused.push(callback);
};
@@ -217,7 +204,7 @@ gdjs.registerRuntimeScenePausedCallback = function(callback) {
* Register a function to be called when a scene is resumed.
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeSceneResumedCallback = function(callback) {
gdjs.registerRuntimeSceneResumedCallback = function (callback) {
gdjs.callbacksRuntimeSceneResumed.push(callback);
};
@@ -229,7 +216,7 @@ gdjs.registerRuntimeSceneResumedCallback = function(callback) {
*
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeSceneUnloadingCallback = function(callback) {
gdjs.registerRuntimeSceneUnloadingCallback = function (callback) {
gdjs.callbacksRuntimeSceneUnloading.push(callback);
};
@@ -240,7 +227,7 @@ gdjs.registerRuntimeSceneUnloadingCallback = function(callback) {
*
* @param {Function} callback The function to be called.
*/
gdjs.registerRuntimeSceneUnloadedCallback = function(callback) {
gdjs.registerRuntimeSceneUnloadedCallback = function (callback) {
gdjs.callbacksRuntimeSceneUnloaded.push(callback);
};
@@ -248,7 +235,7 @@ gdjs.registerRuntimeSceneUnloadedCallback = function(callback) {
* Register a function to be called when an object is deleted from a scene.
* @param {Function} callback The function to be called.
*/
gdjs.registerObjectDeletedFromSceneCallback = function(callback) {
gdjs.registerObjectDeletedFromSceneCallback = function (callback) {
gdjs.callbacksObjectDeletedFromScene.push(callback);
};
@@ -257,8 +244,8 @@ gdjs.registerObjectDeletedFromSceneCallback = function(callback) {
* @deprecated
* @private
*/
gdjs.registerGlobalCallbacks = function() {
console.warning(
gdjs.registerGlobalCallbacks = function () {
console.warn(
"You're calling gdjs.registerGlobalCallbacks. This method is now useless and you must not call it anymore."
);
};
@@ -268,7 +255,7 @@ gdjs.registerGlobalCallbacks = function() {
*
* Should only be used for testing - this should never be used at runtime.
*/
gdjs.clearGlobalCallbacks = function() {
gdjs.clearGlobalCallbacks = function () {
gdjs.callbacksFirstRuntimeSceneLoaded = [];
gdjs.callbacksRuntimeSceneLoaded = [];
gdjs.callbacksRuntimeScenePreEvents = [];
@@ -284,9 +271,9 @@ gdjs.clearGlobalCallbacks = function() {
* Get the constructor of an object.
*
* @param {string} name The name of the type of the object.
* @returns {ObjectCtor}
* @returns {typeof gdjs.RuntimeObject}
*/
gdjs.getObjectConstructor = function(name) {
gdjs.getObjectConstructor = function (name) {
if (name !== undefined && gdjs.objectsTypes.containsKey(name))
return gdjs.objectsTypes.get(name);
@@ -298,9 +285,9 @@ gdjs.getObjectConstructor = function(name) {
* Get the constructor of a behavior.
*
* @param {string} name The name of the type of the behavior.
* @returns {BehaviorCtor}
* @returns {typeof gdjs.RuntimeBehavior}
*/
gdjs.getBehaviorConstructor = function(name) {
gdjs.getBehaviorConstructor = function (name) {
if (name !== undefined && gdjs.behaviorsTypes.containsKey(name))
return gdjs.behaviorsTypes.get(name);
@@ -313,7 +300,7 @@ gdjs.getBehaviorConstructor = function(name) {
* @param {any} owner The owner of the Array.
* @returns {Array<any>}
*/
gdjs.staticArray = function(owner) {
gdjs.staticArray = function (owner) {
owner._staticArray = owner._staticArray || [];
return owner._staticArray;
};
@@ -323,7 +310,7 @@ gdjs.staticArray = function(owner) {
* @param {any} owner The owner of the Array.
* @returns {Array<any>}
*/
gdjs.staticArray2 = function(owner) {
gdjs.staticArray2 = function (owner) {
owner._staticArray2 = owner._staticArray2 || [];
return owner._staticArray2;
};
@@ -333,7 +320,7 @@ gdjs.staticArray2 = function(owner) {
* @param {any} owner The owner of the Array.
* @returns {Object}
*/
gdjs.staticObject = function(owner) {
gdjs.staticObject = function (owner) {
owner._staticObject = owner._staticObject || {};
return owner._staticObject;
};
@@ -344,7 +331,7 @@ gdjs.staticObject = function(owner) {
* @param objectsLists
* @returns {Array}
*/
gdjs.objectsListsToArray = function(objectsLists) {
gdjs.objectsListsToArray = function (objectsLists) {
var lists = gdjs.staticArray(gdjs.objectsListsToArray);
objectsLists.values(lists);
@@ -358,20 +345,18 @@ gdjs.objectsListsToArray = function(objectsLists) {
return result;
};
Array.prototype.remove = function(from) {
//Adapted from the nice article available at
//https://www.scirra.com/blog/76/how-to-write-low-garbage-real-time-javascript
for (var i = from, len = this.length - 1; i < len; i++) this[i] = this[i + 1];
this.length = len;
};
Array.prototype.createFrom = function(arr) {
var len = arr.length;
/**
* Copy the element for the first array into the second array, so that
* both array contains the same elements.
* @param {Array<any>} src The source array
* @param {Array<any>} dst The destination array
*/
gdjs.copyArray = function (src, dst) {
var len = src.length;
for (var i = 0; i < len; ++i) {
this[i] = arr[i];
dst[i] = src[i];
}
this.length = len;
dst.length = len;
};
//Make sure console.warn and console.error are available.

View File

@@ -14,7 +14,7 @@
* @memberof gdjs
* @class HowlerSound
*/
gdjs.HowlerSound = function(o) {
gdjs.HowlerSound = function (o) {
Howl.call(this, o);
this._paused = false;
this._stopped = true;
@@ -24,14 +24,14 @@ gdjs.HowlerSound = function(o) {
//Add custom events listener to keep
//track of the sound status.
var that = this;
this.on('end', function() {
this.on('end', function () {
if (!that.loop()) {
that._canBeDestroyed = true;
that._paused = false;
that._stopped = true;
}
});
this.on('playerror', function(id, error) {
this.on('playerror', function (id, error) {
console.error(
"Can't play a sound, considering it as stopped. Error is:",
error
@@ -44,11 +44,11 @@ gdjs.HowlerSound = function(o) {
// sync'ed with the sound - though this should be redundant
// with `play`/`pause` methods already doing that. Keeping
// that to be sure that the status is always correct.
this.on('play', function() {
this.on('play', function () {
that._paused = false;
that._stopped = false;
});
this.on('pause', function() {
this.on('pause', function () {
that._paused = true;
that._stopped = false;
});
@@ -59,17 +59,17 @@ gdjs.HowlerSound.prototype = Object.create(Howl.prototype);
// is immediately updated (so that calling `stopped` just after
// `play` will return false).
gdjs.HowlerSound.prototype.stop = function() {
gdjs.HowlerSound.prototype.stop = function () {
this._paused = false;
this._stopped = true;
return Howl.prototype.stop.call(this);
};
gdjs.HowlerSound.prototype.play = function() {
gdjs.HowlerSound.prototype.play = function () {
this._paused = false;
this._stopped = false;
return Howl.prototype.play.call(this);
};
gdjs.HowlerSound.prototype.pause = function() {
gdjs.HowlerSound.prototype.pause = function () {
this._paused = true;
this._stopped = false;
return Howl.prototype.pause.call(this);
@@ -77,22 +77,22 @@ gdjs.HowlerSound.prototype.pause = function() {
// Add methods to query the status of the sound:
gdjs.HowlerSound.prototype.paused = function() {
gdjs.HowlerSound.prototype.paused = function () {
return this._paused;
};
gdjs.HowlerSound.prototype.stopped = function() {
gdjs.HowlerSound.prototype.stopped = function () {
return this._stopped;
};
gdjs.HowlerSound.prototype.canBeDestroyed = function() {
gdjs.HowlerSound.prototype.canBeDestroyed = function () {
return this._canBeDestroyed;
};
// Methods to safely update the rate of the sound:
gdjs.HowlerSound.prototype.getRate = function() {
gdjs.HowlerSound.prototype.getRate = function () {
return this._rate;
};
gdjs.HowlerSound.prototype.setRate = function(rate) {
gdjs.HowlerSound.prototype.setRate = function (rate) {
this._rate = gdjs.HowlerSoundManager.clampRate(rate);
this.rate(this._rate);
};
@@ -106,7 +106,7 @@ gdjs.HowlerSound.prototype.setRate = function(rate) {
* @memberof gdjs
* @class HowlerSoundManager
*/
gdjs.HowlerSoundManager = function(resources) {
gdjs.HowlerSoundManager = function (resources) {
this._resources = resources;
this._availableResources = {}; //Map storing "audio" resources for faster access.
@@ -121,18 +121,18 @@ gdjs.HowlerSoundManager = function(resources) {
this._paused = false;
var that = this;
this._checkForPause = function() {
this._checkForPause = function () {
if (that._paused) {
this.pause();
that._pausedSounds.push(this);
}
};
document.addEventListener('deviceready', function() {
document.addEventListener('deviceready', function () {
// pause/resume sounds in Cordova when the app is being paused/resumed
document.addEventListener(
'pause',
function() {
function () {
var soundList = that._freeSounds.concat(that._freeMusics);
for (var key in that._sounds) {
if (that._sounds.hasOwnProperty(key)) {
@@ -157,7 +157,7 @@ gdjs.HowlerSoundManager = function(resources) {
);
document.addEventListener(
'resume',
function() {
function () {
for (var i = 0; i < that._pausedSounds.length; i++) {
var sound = that._pausedSounds[i];
if (!sound.stopped()) {
@@ -174,12 +174,21 @@ gdjs.HowlerSoundManager = function(resources) {
gdjs.SoundManager = gdjs.HowlerSoundManager; //Register the class to let the engine use it.
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.HowlerSoundManager.prototype.setResources = function (resources) {
this._resources = resources;
};
/**
* Ensure rate is in a range valid for Howler.js
* @return The clamped rate
* @private
*/
gdjs.HowlerSoundManager.clampRate = function(rate) {
gdjs.HowlerSoundManager.clampRate = function (rate) {
if (rate > 4.0) return 4.0;
if (rate < 0.5) return 0.5;
@@ -196,7 +205,7 @@ gdjs.HowlerSoundManager.clampRate = function(rate) {
* @private
* @return The associated filename
*/
gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function(soundName) {
gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function (soundName) {
if (
this._availableResources.hasOwnProperty(soundName) &&
this._availableResources[soundName].file
@@ -217,7 +226,7 @@ gdjs.HowlerSoundManager.prototype._getFileFromSoundName = function(soundName) {
* @return The gdjs.HowlerSound that have been added (i.e: the second parameter).
* @private
*/
gdjs.HowlerSoundManager.prototype._storeSoundInArray = function(arr, sound) {
gdjs.HowlerSoundManager.prototype._storeSoundInArray = function (arr, sound) {
//Try to recycle an old sound.
var index = null;
for (var i = 0, len = arr.length; i < len; ++i) {
@@ -231,7 +240,7 @@ gdjs.HowlerSoundManager.prototype._storeSoundInArray = function(arr, sound) {
return sound;
};
gdjs.HowlerSoundManager.prototype.playSound = function(
gdjs.HowlerSoundManager.prototype.playSound = function (
soundName,
loop,
volume,
@@ -251,7 +260,7 @@ gdjs.HowlerSoundManager.prototype.playSound = function(
sound.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function (
soundName,
channel,
loop,
@@ -278,11 +287,11 @@ gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(
sound.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.getSoundOnChannel = function(channel) {
gdjs.HowlerSoundManager.prototype.getSoundOnChannel = function (channel) {
return this._sounds[channel];
};
gdjs.HowlerSoundManager.prototype.playMusic = function(
gdjs.HowlerSoundManager.prototype.playMusic = function (
soundName,
loop,
volume,
@@ -303,7 +312,7 @@ gdjs.HowlerSoundManager.prototype.playMusic = function(
sound.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function (
soundName,
channel,
loop,
@@ -331,22 +340,22 @@ gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(
music.on('play', this._checkForPause);
};
gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function(channel) {
gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function (channel) {
return this._musics[channel];
};
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function(volume) {
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function (volume) {
this._globalVolume = volume;
if (this._globalVolume > 100) this._globalVolume = 100;
if (this._globalVolume < 0) this._globalVolume = 0;
Howler.volume(this._globalVolume / 100);
};
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function() {
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function () {
return this._globalVolume;
};
gdjs.HowlerSoundManager.prototype.clearAll = function() {
gdjs.HowlerSoundManager.prototype.clearAll = function () {
for (var i = 0; i < this._freeSounds.length; ++i) {
if (this._freeSounds[i]) this._freeSounds[i].unload();
}
@@ -371,7 +380,7 @@ gdjs.HowlerSoundManager.prototype.clearAll = function() {
this._pausedSounds.length = 0;
};
gdjs.HowlerSoundManager.prototype.preloadAudio = function(
gdjs.HowlerSoundManager.prototype.preloadAudio = function (
onProgress,
onComplete,
resources
@@ -381,39 +390,42 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(
//Construct the list of files to be loaded.
//For one loaded file, it can have one or more resources
//that use it.
var files = [];
var files = {};
for (var i = 0, len = resources.length; i < len; ++i) {
var res = resources[i];
if (res.file && res.kind === 'audio') {
if (!!this._availableResources[res.name]) {
continue;
}
this._availableResources[res.name] = res;
if (files.indexOf(res.file) === -1) {
files.push(res.file);
}
files[res.file] = (files[res.file] || []).concat(res);
}
}
if (files.length === 0) return onComplete(files.length);
var loaded = 0;
function onLoad(audioFile) {
loaded++;
if (loaded === files.length) {
return onComplete(files.length);
}
onProgress(loaded, files.length);
}
var totalCount = Object.keys(files).length;
if (totalCount === 0) return onComplete(totalCount); //Nothing to load.
var loadedCount = 0;
var that = this;
for (var i = 0; i < files.length; ++i) {
(function(audioFile) {
var sound = new XMLHttpRequest();
sound.addEventListener('load', onLoad.bind(that, audioFile));
sound.addEventListener('error', onLoad.bind(that, audioFile));
sound.addEventListener('abort', onLoad.bind(that, audioFile));
sound.open('GET', audioFile);
sound.send();
})(files[i]);
function onLoad() {
loadedCount++;
if (loadedCount === totalCount) {
return onComplete(totalCount);
}
onProgress(loadedCount, totalCount);
}
for (var file in files) {
if (files.hasOwnProperty(file)) {
var httpRequest = new XMLHttpRequest();
httpRequest.addEventListener('load', onLoad);
httpRequest.addEventListener('error', onLoad);
httpRequest.addEventListener('abort', onLoad);
httpRequest.open('GET', file);
httpRequest.send();
}
}
};

View File

@@ -1,3 +1,4 @@
// @ts-check
/*
* GDevelop JS Platform
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
@@ -16,13 +17,22 @@
* @memberof gdjs
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.JsonManager = function(resources) {
gdjs.JsonManager = function (resources) {
this._resources = resources;
/** @type Object.<string, Object> */
this._loadedJsons = {};
};
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.JsonManager.prototype.setResources = function (resources) {
this._resources = resources;
};
/**
* The callback called when a json is preloaded
* @callback JsonManagerOnProgressCallback
@@ -41,30 +51,33 @@ gdjs.JsonManager = function(resources) {
/**
* Request all the json resources to be preloaded (unless they are marked as not preloaded).
*
* Note that even if a JSON is already loaded, it will be reloaded (useful for hot-reloading,
* as JSON files can have been modified without the editor knowing).
*
* @param {JsonManagerOnProgressCallback} onProgress The function called after each json is loaded.
* @param {JsonManagerOnCompleteCallback} onComplete The function called when all jsons are loaded.
*/
gdjs.JsonManager.prototype.preloadJsons = function(onProgress, onComplete) {
gdjs.JsonManager.prototype.preloadJsons = function (onProgress, onComplete) {
var resources = this._resources;
var jsonResources = resources.filter(function(resource) {
var jsonResources = resources.filter(function (resource) {
return resource.kind === 'json' && !resource.disablePreload;
});
if (jsonResources.length === 0) return onComplete(jsonResources.length);
var loaded = 0;
/** @type JsonManagerRequestCallback */
var onLoad = function(error, jsonContent) {
var onLoad = function (error) {
if (error) {
console.error('Error while preloading a json resource:' + error);
}
loaded++;
if (loaded === jsonResources.length) {
return onComplete(jsonResources.length);
onComplete(jsonResources.length);
} else {
onProgress(loaded, jsonResources.length);
}
onProgress(loaded, jsonResources.length);
};
for (var i = 0; i < jsonResources.length; ++i) {
@@ -77,7 +90,7 @@ gdjs.JsonManager.prototype.preloadJsons = function(onProgress, onComplete) {
* @callback JsonManagerRequestCallback
* @param {?Error} error The error, if any. `null` otherwise.
* @param {?Object} jsonContent The content of the json file (or null if an error occured).
* @returns {undefined} Nothing
* @returns {void} Nothing
*/
/**
@@ -88,8 +101,9 @@ gdjs.JsonManager.prototype.preloadJsons = function(onProgress, onComplete) {
* @param {string} resourceName The resource pointing to the json file to load.
* @param {JsonManagerRequestCallback} callback The callback function called when json is loaded (or an error occured).
*/
gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
var resource = this._resources.find(function(resource) {
gdjs.JsonManager.prototype.loadJson = function (resourceName, callback) {
// @ts-ignore - find is not ES5
var resource = this._resources.find(function (resource) {
return resource.kind === 'json' && resource.name === resourceName;
});
if (!resource) {
@@ -114,7 +128,7 @@ gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open('GET', resource.file);
xhr.onload = function() {
xhr.onload = function () {
if (xhr.status !== 200) {
callback(
new Error('HTTP error: ' + xhr.status + '(' + xhr.statusText + ')'),
@@ -128,10 +142,10 @@ gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
callback(null, xhr.response);
};
xhr.onerror = function() {
xhr.onerror = function () {
callback(new Error('Network error'), null);
};
xhr.onabort = function() {
xhr.onabort = function () {
callback(new Error('Request aborted'), null);
};
xhr.send();
@@ -142,7 +156,7 @@ gdjs.JsonManager.prototype.loadJson = function(resourceName, callback) {
* @param {string} resourceName The name of the json resource.
* @returns {boolean} true if the content of the json resource is loaded. false otherwise.
*/
gdjs.JsonManager.prototype.isJsonLoaded = function(resourceName) {
gdjs.JsonManager.prototype.isJsonLoaded = function (resourceName) {
return !!this._loadedJsons[resourceName];
};
@@ -153,6 +167,6 @@ gdjs.JsonManager.prototype.isJsonLoaded = function(resourceName) {
* @param {string} resourceName The name of the json resource.
* @returns {?Object} the content of the json resource, if loaded. `null` otherwise.
*/
gdjs.JsonManager.prototype.getLoadedJson = function(resourceName) {
gdjs.JsonManager.prototype.getLoadedJson = function (resourceName) {
return this._loadedJsons[resourceName] || null;
};

View File

@@ -15,13 +15,13 @@
* @param {gdjs.RuntimeScene} runtimeScene The scene in which the layer is used
* @memberof gdjs
*/
gdjs.Layer = function(layerData, runtimeScene) {
gdjs.Layer = function (layerData, runtimeScene) {
this._name = layerData.name;
this._cameraRotation = 0;
this._zoomFactor = 1;
this._timeScale = 1;
this._hidden = !layerData.visibility;
this._effectsData = layerData.effects || [];
this._initialEffectsData = layerData.effects || [];
this._cameraX = runtimeScene.getGame().getGameResolutionWidth() / 2;
this._cameraY = runtimeScene.getGame().getGameResolutionHeight() / 2;
this._cachedGameResolutionWidth = runtimeScene
@@ -32,13 +32,26 @@ gdjs.Layer = function(layerData, runtimeScene) {
.getGameResolutionHeight();
this._runtimeScene = runtimeScene;
// Lighting layer properties.
this._isLightingLayer = layerData.isLightingLayer;
this._followBaseLayerCamera = layerData.followBaseLayerCamera;
this._clearColor = [
layerData.ambientLightColorR / 255,
layerData.ambientLightColorG / 255,
layerData.ambientLightColorB / 255,
1.0,
];
// @ts-ignore - assume the proper renderer is passed
this._renderer = new gdjs.LayerRenderer(this, runtimeScene.getRenderer());
this.show(!this._hidden);
this._setEffectsDefaultParameters();
for (var i = 0; i < layerData.effects.length; ++i) {
this.addEffect(layerData.effects[i]);
}
};
gdjs.Layer.prototype.getRenderer = function() {
gdjs.Layer.prototype.getRenderer = function () {
return this._renderer;
};
@@ -46,7 +59,7 @@ gdjs.Layer.prototype.getRenderer = function() {
* Called by the RuntimeScene whenever the game resolution size is changed.
* Updates the layer width/height and position.
*/
gdjs.Layer.prototype.onGameResolutionResized = function() {
gdjs.Layer.prototype.onGameResolutionResized = function () {
var oldGameResolutionWidth = this._cachedGameResolutionWidth;
var oldGameResolutionHeight = this._cachedGameResolutionHeight;
this._cachedGameResolutionWidth = this._runtimeScene
@@ -74,7 +87,7 @@ gdjs.Layer.prototype.onGameResolutionResized = function() {
* Returns the scene the layer belongs to
* @returns {gdjs.RuntimeScene} the scene the layer belongs to
*/
gdjs.Layer.prototype.getRuntimeScene = function() {
gdjs.Layer.prototype.getRuntimeScene = function () {
return this._runtimeScene;
};
@@ -82,15 +95,16 @@ gdjs.Layer.prototype.getRuntimeScene = function() {
* Called at each frame, after events are run and before rendering.
* @param {gdjs.RuntimeScene} runtimeScene The scene the layer belongs to.
*/
gdjs.Layer.prototype.update = function(runtimeScene) {
return this._renderer.updateTime();
gdjs.Layer.prototype.update = function (runtimeScene) {
if (this._followBaseLayerCamera) this.followBaseLayer();
return this._renderer.update();
};
/**
* Get the name of the layer
* @return {String} The name of the layer
*/
gdjs.Layer.prototype.getName = function() {
gdjs.Layer.prototype.getName = function () {
return this._name;
};
@@ -100,7 +114,7 @@ gdjs.Layer.prototype.getName = function() {
* @param {number=} cameraId The camera number. Currently ignored.
* @return The x position of the camera
*/
gdjs.Layer.prototype.getCameraX = function(cameraId) {
gdjs.Layer.prototype.getCameraX = function (cameraId) {
return this._cameraX;
};
@@ -110,7 +124,7 @@ gdjs.Layer.prototype.getCameraX = function(cameraId) {
* @param {number=} cameraId The camera number. Currently ignored.
* @return The y position of the camera
*/
gdjs.Layer.prototype.getCameraY = function(cameraId) {
gdjs.Layer.prototype.getCameraY = function (cameraId) {
return this._cameraY;
};
@@ -120,7 +134,7 @@ gdjs.Layer.prototype.getCameraY = function(cameraId) {
* @param {number} x The new x position
* @param {number=} cameraId The camera number. Currently ignored.
*/
gdjs.Layer.prototype.setCameraX = function(x, cameraId) {
gdjs.Layer.prototype.setCameraX = function (x, cameraId) {
this._cameraX = x;
this._renderer.updatePosition();
};
@@ -131,7 +145,7 @@ gdjs.Layer.prototype.setCameraX = function(x, cameraId) {
* @param {number} y The new y position
* @param {number=} cameraId The camera number. Currently ignored.
*/
gdjs.Layer.prototype.setCameraY = function(y, cameraId) {
gdjs.Layer.prototype.setCameraY = function (y, cameraId) {
this._cameraY = y;
this._renderer.updatePosition();
};
@@ -143,7 +157,7 @@ gdjs.Layer.prototype.setCameraY = function(y, cameraId) {
* @param {number=} cameraId The camera number. Currently ignored.
* @return {number} The width of the camera
*/
gdjs.Layer.prototype.getCameraWidth = function(cameraId) {
gdjs.Layer.prototype.getCameraWidth = function (cameraId) {
return (+this._cachedGameResolutionWidth * 1) / this._zoomFactor;
};
@@ -154,7 +168,7 @@ gdjs.Layer.prototype.getCameraWidth = function(cameraId) {
* @param {number=} cameraId The camera number. Currently ignored.
* @return {number} The height of the camera
*/
gdjs.Layer.prototype.getCameraHeight = function(cameraId) {
gdjs.Layer.prototype.getCameraHeight = function (cameraId) {
return (+this._cachedGameResolutionHeight * 1) / this._zoomFactor;
};
@@ -162,7 +176,7 @@ gdjs.Layer.prototype.getCameraHeight = function(cameraId) {
* Show (or hide) the layer.
* @param {boolean} enable true to show the layer, false to hide it.
*/
gdjs.Layer.prototype.show = function(enable) {
gdjs.Layer.prototype.show = function (enable) {
this._hidden = !enable;
this._renderer.updateVisibility(enable);
};
@@ -172,7 +186,7 @@ gdjs.Layer.prototype.show = function(enable) {
*
* @return true if the layer is visible.
*/
gdjs.Layer.prototype.isVisible = function() {
gdjs.Layer.prototype.isVisible = function () {
return !this._hidden;
};
@@ -182,7 +196,7 @@ gdjs.Layer.prototype.isVisible = function() {
* @param {number} newZoom The new zoom. Must be superior to 0. 1 is the default zoom.
* @param {number=} cameraId The camera number. Currently ignored.
*/
gdjs.Layer.prototype.setCameraZoom = function(newZoom, cameraId) {
gdjs.Layer.prototype.setCameraZoom = function (newZoom, cameraId) {
this._zoomFactor = newZoom;
this._renderer.updatePosition();
};
@@ -193,7 +207,7 @@ gdjs.Layer.prototype.setCameraZoom = function(newZoom, cameraId) {
* @param {number=} cameraId The camera number. Currently ignored.
* @return {number} The zoom.
*/
gdjs.Layer.prototype.getCameraZoom = function(cameraId) {
gdjs.Layer.prototype.getCameraZoom = function (cameraId) {
return this._zoomFactor;
};
@@ -203,7 +217,7 @@ gdjs.Layer.prototype.getCameraZoom = function(cameraId) {
* @param {number=} cameraId The camera number. Currently ignored.
* @return {number} The rotation, in degrees.
*/
gdjs.Layer.prototype.getCameraRotation = function(cameraId) {
gdjs.Layer.prototype.getCameraRotation = function (cameraId) {
return this._cameraRotation;
};
@@ -214,7 +228,7 @@ gdjs.Layer.prototype.getCameraRotation = function(cameraId) {
* @param {number} rotation The new rotation, in degrees.
* @param {number=} cameraId The camera number. Currently ignored.
*/
gdjs.Layer.prototype.setCameraRotation = function(rotation, cameraId) {
gdjs.Layer.prototype.setCameraRotation = function (rotation, cameraId) {
this._cameraRotation = rotation;
this._renderer.updatePosition();
};
@@ -229,7 +243,7 @@ gdjs.Layer.prototype.setCameraRotation = function(rotation, cameraId) {
* @param {number} y The y position, in canvas coordinates.
* @param {number=} cameraId The camera number. Currently ignored.
*/
gdjs.Layer.prototype.convertCoords = function(x, y, cameraId) {
gdjs.Layer.prototype.convertCoords = function (x, y, cameraId) {
x -= this._cachedGameResolutionWidth / 2;
y -= this._cachedGameResolutionHeight / 2;
x /= Math.abs(this._zoomFactor);
@@ -246,7 +260,7 @@ gdjs.Layer.prototype.convertCoords = function(x, y, cameraId) {
return [x + this.getCameraX(cameraId), y + this.getCameraY(cameraId)];
};
gdjs.Layer.prototype.convertInverseCoords = function(x, y, cameraId) {
gdjs.Layer.prototype.convertInverseCoords = function (x, y, cameraId) {
x -= this.getCameraX(cameraId);
y -= this.getCameraY(cameraId);
@@ -267,33 +281,59 @@ gdjs.Layer.prototype.convertInverseCoords = function(x, y, cameraId) {
];
};
gdjs.Layer.prototype.getWidth = function() {
gdjs.Layer.prototype.getWidth = function () {
return this._cachedGameResolutionWidth;
};
gdjs.Layer.prototype.getHeight = function() {
gdjs.Layer.prototype.getHeight = function () {
return this._cachedGameResolutionHeight;
};
gdjs.Layer.prototype.getEffectsData = function() {
return this._effectsData;
/**
* Return the initial effects data for the layer. Only to
* be used by renderers.
*/
gdjs.Layer.prototype.getInitialEffectsData = function () {
return this._initialEffectsData;
};
/**
* Add a new effect, or replace the one with the same name.
* @param {EffectData} effectData The data of the effect to add.
*/
gdjs.Layer.prototype.addEffect = function(effectData) {
gdjs.Layer.prototype.addEffect = function (effectData) {
this._renderer.addEffect(effectData);
}
for (var name in effectData.doubleParameters) {
this.setEffectDoubleParameter(
effectData.name,
name,
effectData.doubleParameters[name]
);
}
for (var name in effectData.stringParameters) {
this.setEffectStringParameter(
effectData.name,
name,
effectData.stringParameters[name]
);
}
for (var name in effectData.booleanParameters) {
this.setEffectBooleanParameter(
effectData.name,
name,
effectData.booleanParameters[name]
);
}
};
/**
* Remove the effect with the specified name
* @param {string} effectName The name of the effect.
*/
gdjs.Layer.prototype.removeEffect = function(effectName) {
gdjs.Layer.prototype.removeEffect = function (effectName) {
this._renderer.removeEffect(effectName);
}
};
/**
* Change an effect parameter value (for parameters that are numbers).
@@ -301,7 +341,7 @@ gdjs.Layer.prototype.removeEffect = function(effectName) {
* @param {string} parameterName The name of the parameter to update.
* @param {number} value The new value (number).
*/
gdjs.Layer.prototype.setEffectDoubleParameter = function(
gdjs.Layer.prototype.setEffectDoubleParameter = function (
name,
parameterName,
value
@@ -315,7 +355,7 @@ gdjs.Layer.prototype.setEffectDoubleParameter = function(
* @param {string} parameterName The name of the parameter to update.
* @param {string} value The new value (string).
*/
gdjs.Layer.prototype.setEffectStringParameter = function(
gdjs.Layer.prototype.setEffectStringParameter = function (
name,
parameterName,
value
@@ -329,7 +369,7 @@ gdjs.Layer.prototype.setEffectStringParameter = function(
* @param {string} parameterName The name of the parameter to update.
* @param {boolean} value The new value (boolean).
*/
gdjs.Layer.prototype.setEffectBooleanParameter = function(
gdjs.Layer.prototype.setEffectBooleanParameter = function (
name,
parameterName,
value
@@ -342,7 +382,7 @@ gdjs.Layer.prototype.setEffectBooleanParameter = function(
* @param {string} name The name of the effect to enable or disable.
* @param {boolean} enable true to enable, false to disable
*/
gdjs.Layer.prototype.enableEffect = function(name, enable) {
gdjs.Layer.prototype.enableEffect = function (name, enable) {
this._renderer.enableEffect(name, enable);
};
@@ -351,7 +391,7 @@ gdjs.Layer.prototype.enableEffect = function(name, enable) {
* @param {string} name The name of the effect
* @return {boolean} true if the effect is enabled, false otherwise.
*/
gdjs.Layer.prototype.isEffectEnabled = function(name) {
gdjs.Layer.prototype.isEffectEnabled = function (name) {
return this._renderer.isEffectEnabled(name);
};
@@ -360,50 +400,23 @@ gdjs.Layer.prototype.isEffectEnabled = function(name) {
* @param {string} name The name of the effect
* @return {boolean} true if the effect exists, false otherwise.
*/
gdjs.Layer.prototype.hasEffect = function(name) {
gdjs.Layer.prototype.hasEffect = function (name) {
return this._renderer.hasEffect(name);
};
gdjs.Layer.prototype._setEffectsDefaultParameters = function() {
for (var i = 0; i < this._effectsData.length; ++i) {
var effectData = this._effectsData[i];
for (var name in effectData.doubleParameters) {
this.setEffectDoubleParameter(
effectData.name,
name,
effectData.doubleParameters[name]
);
}
for (var name in effectData.stringParameters) {
this.setEffectStringParameter(
effectData.name,
name,
effectData.stringParameters[name]
);
}
for (var name in effectData.booleanParameters) {
this.setEffectBooleanParameter(
effectData.name,
name,
effectData.booleanParameters[name]
);
}
}
};
/**
* Set the time scale for the objects on the layer:
* time will be slower if time scale is < 1, faster if > 1.
* @param {number} timeScale The new time scale (must be positive).
*/
gdjs.Layer.prototype.setTimeScale = function(timeScale) {
gdjs.Layer.prototype.setTimeScale = function (timeScale) {
if (timeScale >= 0) this._timeScale = timeScale;
};
/**
* Get the time scale for the objects on the layer.
*/
gdjs.Layer.prototype.getTimeScale = function() {
gdjs.Layer.prototype.getTimeScale = function () {
return this._timeScale;
};
@@ -411,6 +424,54 @@ gdjs.Layer.prototype.getTimeScale = function() {
* Return the time elapsed since the last frame,
* in milliseconds, for objects on the layer.
*/
gdjs.Layer.prototype.getElapsedTime = function() {
gdjs.Layer.prototype.getElapsedTime = function () {
return this._runtimeScene.getTimeManager().getElapsedTime() * this._timeScale;
};
/**
* Change the position, rotation and scale (zoom) of the layer camera to be the same as the base layer camera.
*/
gdjs.Layer.prototype.followBaseLayer = function () {
var baseLayer = this._runtimeScene.getLayer('');
this.setCameraX(baseLayer.getCameraX());
this.setCameraY(baseLayer.getCameraY());
this.setCameraRotation(baseLayer.getCameraRotation());
this.setCameraZoom(baseLayer.getCameraZoom());
};
/**
* The clear color is defined in the format [r, g, b], with components in the range of 0 to 1.
* @return {number[]} the clear color of layer in the range of [0, 1].
*/
gdjs.Layer.prototype.getClearColor = function () {
return this._clearColor;
};
/**
* Set the clear color in format [r, g, b], with components in the range of 0 to 1.;
* @param {?number} r Red color component in the range 0-255.
* @param {?number} g Green color component in the range 0-255.
* @param {?number} b Blue color component in the range 0-255.
*/
gdjs.Layer.prototype.setClearColor = function (r, g, b) {
if (r) this._clearColor[0] = r / 255;
if (g) this._clearColor[1] = g / 255;
if (b) this._clearColor[2] = b / 255;
this._renderer.updateClearColor();
};
/**
* Set whether layer's camera follows base layer's camera or not.
* @param {boolean} follow
*/
gdjs.Layer.prototype.setFollowBaseLayerCamera = function (follow) {
this._followBaseLayerCamera = follow;
};
/**
* Return true if the layer is a lighting layer, false otherwise.
* @return {boolean} true if it is a lighting layer, false otherwise.
*/
gdjs.Layer.prototype.isLightingLayer = function () {
return this._isLightingLayer;
};

View File

@@ -13,25 +13,50 @@
* @param {gdjs.Layer} layer The layer
* @param {gdjs.RuntimeScenePixiRenderer} runtimeSceneRenderer The scene renderer
*/
gdjs.LayerPixiRenderer = function(layer, runtimeSceneRenderer) {
// @ts-ignore
gdjs.LayerPixiRenderer = function (layer, runtimeSceneRenderer) {
this._pixiContainer = new PIXI.Container();
/** @type Object.<string, gdjsPixiFiltersToolsFilter> */
this._filters = {};
this._layer = layer;
runtimeSceneRenderer.getPIXIContainer().addChild(this._pixiContainer);
this._setupFilters();
/** @type {?PIXI.RenderTexture} */
this._renderTexture = null;
/** @type {?PIXI.Sprite} */
this._lightingSprite = null;
this._runtimeSceneRenderer = runtimeSceneRenderer;
this._pixiRenderer = runtimeSceneRenderer.getPIXIRenderer();
// Width and height are tracked when a render texture is used.
this._oldWidth = null;
this._oldHeight = null;
this._isLightingLayer = layer.isLightingLayer();
this._clearColor = layer.getClearColor();
runtimeSceneRenderer.getPIXIContainer().addChild(this._pixiContainer);
this._pixiContainer.filters = [];
if (this._isLightingLayer) {
this._replaceContainerWithSprite();
}
};
gdjs.LayerRenderer = gdjs.LayerPixiRenderer; //Register the class to let the engine use it.
gdjs.LayerPixiRenderer.prototype.getRendererObject = function () {
return this._pixiContainer;
};
gdjs.LayerPixiRenderer.prototype.getLightingSprite = function () {
return this._lightingSprite;
};
/**
* Update the position of the PIXI container. To be called after each change
* made to position, zoom or rotation of the camera.
* @private
*/
gdjs.LayerPixiRenderer.prototype.updatePosition = function() {
gdjs.LayerPixiRenderer.prototype.updatePosition = function () {
var angle = -gdjs.toRad(this._layer.getCameraRotation());
var zoomFactor = this._layer.getCameraZoom();
@@ -54,34 +79,26 @@ gdjs.LayerPixiRenderer.prototype.updatePosition = function() {
this._pixiContainer.position.y += this._layer.getHeight() / 2;
};
gdjs.LayerPixiRenderer.prototype.updateVisibility = function(visible) {
gdjs.LayerPixiRenderer.prototype.updateVisibility = function (visible) {
this._pixiContainer.visible = !!visible;
};
gdjs.LayerPixiRenderer.prototype.updateTime = function() {
for(var filterName in this._filters) {
gdjs.LayerPixiRenderer.prototype.update = function () {
if (this._renderTexture) {
this._updateRenderTexture();
}
for (var filterName in this._filters) {
var filter = this._filters[filterName];
filter.update(filter.pixiFilter, this._layer);
}
};
gdjs.LayerPixiRenderer.prototype._setupFilters = function() {
var effectsData = this._layer.getEffectsData();
if (effectsData.length === 0) {
return;
}
this._pixiContainer.filters = [];
for (var i = 0; i < effectsData.length; ++i) {
this.addEffect(effectsData[i])
}
};
/**
* Add a new effect, or replace the one with the same name.
* @param {EffectData} effectData The data of the effect to add.
*/
gdjs.LayerPixiRenderer.prototype.addEffect = function(effectData) {
gdjs.LayerPixiRenderer.prototype.addEffect = function (effectData) {
var filterCreator = gdjs.PixiFiltersTools.getFilterCreator(
effectData.effectType
);
@@ -105,23 +122,28 @@ gdjs.LayerPixiRenderer.prototype.addEffect = function(effectData) {
update: filterCreator.update,
};
this._pixiContainer.filters = (this._pixiContainer.filters || [])
.concat(filter.pixiFilter);
if (this._isLightingLayer) filter.pixiFilter.blendMode = PIXI.BLEND_MODES.ADD;
this._pixiContainer.filters = (this._pixiContainer.filters || []).concat(
filter.pixiFilter
);
this._filters[effectData.name] = filter;
}
};
/**
* Remove the effect with the specified name
* @param {string} effectName The name of the effect.
*/
gdjs.LayerPixiRenderer.prototype.removeEffect = function(effectName) {
gdjs.LayerPixiRenderer.prototype.removeEffect = function (effectName) {
var filter = this._filters[effectName];
if (!filter) return;
this._pixiContainer.filters = (this._pixiContainer.filters || [])
.filter(function(pixiFilter) { return pixiFilter !== filter.pixiFilter; });
this._pixiContainer.filters = (this._pixiContainer.filters || []).filter(
function (pixiFilter) {
return pixiFilter !== filter.pixiFilter;
}
);
delete this._filters[effectName];
}
};
/**
* Add a child to the pixi container associated to the layer.
@@ -130,10 +152,11 @@ gdjs.LayerPixiRenderer.prototype.removeEffect = function(effectName) {
* @param child The child (PIXI object) to be added.
* @param zOrder The z order of the associated object.
*/
gdjs.LayerPixiRenderer.prototype.addRendererObject = function(child, zOrder) {
gdjs.LayerPixiRenderer.prototype.addRendererObject = function (child, zOrder) {
child.zOrder = zOrder; //Extend the pixi object with a z order.
for (var i = 0, len = this._pixiContainer.children.length; i < len; ++i) {
// @ts-ignore
if (this._pixiContainer.children[i].zOrder >= zOrder) {
//TODO : Dichotomic search
this._pixiContainer.addChildAt(child, i);
@@ -149,7 +172,7 @@ gdjs.LayerPixiRenderer.prototype.addRendererObject = function(child, zOrder) {
* @param child The child (PIXI object) to be modified.
* @param newZOrder The z order of the associated object.
*/
gdjs.LayerPixiRenderer.prototype.changeRendererObjectZOrder = function(
gdjs.LayerPixiRenderer.prototype.changeRendererObjectZOrder = function (
child,
newZOrder
) {
@@ -163,7 +186,7 @@ gdjs.LayerPixiRenderer.prototype.changeRendererObjectZOrder = function(
*
* @param child The child (PIXI object) to be removed.
*/
gdjs.LayerPixiRenderer.prototype.removeRendererObject = function(child) {
gdjs.LayerPixiRenderer.prototype.removeRendererObject = function (child) {
this._pixiContainer.removeChild(child);
};
@@ -173,7 +196,7 @@ gdjs.LayerPixiRenderer.prototype.removeRendererObject = function(child) {
* @param {string} parameterName The parameter name
* @param {number} value The new value for the parameter
*/
gdjs.LayerPixiRenderer.prototype.setEffectDoubleParameter = function(
gdjs.LayerPixiRenderer.prototype.setEffectDoubleParameter = function (
name,
parameterName,
value
@@ -190,7 +213,7 @@ gdjs.LayerPixiRenderer.prototype.setEffectDoubleParameter = function(
* @param {string} parameterName The parameter name
* @param {string} value The new value for the parameter
*/
gdjs.LayerPixiRenderer.prototype.setEffectStringParameter = function(
gdjs.LayerPixiRenderer.prototype.setEffectStringParameter = function (
name,
parameterName,
value
@@ -207,7 +230,7 @@ gdjs.LayerPixiRenderer.prototype.setEffectStringParameter = function(
* @param {string} parameterName The parameter name
* @param {boolean} value The new value for the parameter
*/
gdjs.LayerPixiRenderer.prototype.setEffectBooleanParameter = function(
gdjs.LayerPixiRenderer.prototype.setEffectBooleanParameter = function (
name,
parameterName,
value
@@ -223,7 +246,7 @@ gdjs.LayerPixiRenderer.prototype.setEffectBooleanParameter = function(
* @param {string} name The effect name
* @returns {boolean} True if the effect exists, false otherwise
*/
gdjs.LayerPixiRenderer.prototype.hasEffect = function(name) {
gdjs.LayerPixiRenderer.prototype.hasEffect = function (name) {
return !!this._filters[name];
};
@@ -232,7 +255,7 @@ gdjs.LayerPixiRenderer.prototype.hasEffect = function(name) {
* @param {string} name The effect name
* @param {boolean} value Set to true to enable, false to disable
*/
gdjs.LayerPixiRenderer.prototype.enableEffect = function(name, value) {
gdjs.LayerPixiRenderer.prototype.enableEffect = function (name, value) {
var filter = this._filters[name];
if (!filter) return;
@@ -244,9 +267,82 @@ gdjs.LayerPixiRenderer.prototype.enableEffect = function(name, value) {
* @param {string} name The effect name
* @return {boolean} true if the filter is enabled
*/
gdjs.LayerPixiRenderer.prototype.isEffectEnabled = function(name) {
gdjs.LayerPixiRenderer.prototype.isEffectEnabled = function (name) {
var filter = this._filters[name];
if (!filter) return false;
return gdjs.PixiFiltersTools.isEffectEnabled(filter);
};
gdjs.LayerPixiRenderer.prototype.updateClearColor = function () {
this._clearColor = this._layer.getClearColor();
this._updateRenderTexture();
};
/**
* Updates the render texture, if it exists.
* Also, render texture is cleared with a specified clear color.
*/
gdjs.LayerPixiRenderer.prototype._updateRenderTexture = function () {
if (!this._pixiRenderer) return;
if (!this._renderTexture) {
this._oldWidth = this._pixiRenderer.screen.width;
this._oldHeight = this._pixiRenderer.screen.height;
var width = this._oldWidth;
var height = this._oldHeight;
var resolution = this._pixiRenderer.resolution;
this._renderTexture = PIXI.RenderTexture.create({
width,
height,
resolution,
});
this._renderTexture.baseTexture.scaleMode = PIXI.SCALE_MODES.LINEAR;
}
if (
this._oldWidth !== this._pixiRenderer.screen.width ||
this._oldHeight !== this._pixiRenderer.screen.height
) {
this._renderTexture.resize(
this._pixiRenderer.screen.width,
this._pixiRenderer.screen.height
);
this._oldWidth = this._pixiRenderer.screen.width;
this._oldHeight = this._pixiRenderer.screen.height;
}
var oldRenderTexture = this._pixiRenderer.renderTexture.current;
var oldSourceFrame = this._pixiRenderer.renderTexture.sourceFrame;
this._pixiRenderer.renderTexture.bind(this._renderTexture);
this._pixiRenderer.renderTexture.clear(this._clearColor);
this._pixiRenderer.render(this._pixiContainer, this._renderTexture, false);
this._pixiRenderer.renderTexture.bind(
oldRenderTexture,
oldSourceFrame,
undefined
);
};
/**
* Enable the use of a PIXI.RenderTexture to render the PIXI.Container
* of the layer and, in the scene PIXI container, replace the container
* of the layer by a sprite showing this texture.
* @private used only in lighting for now as the sprite could have MULTIPLY blend mode.
*/
gdjs.LayerPixiRenderer.prototype._replaceContainerWithSprite = function () {
if (!this._pixiRenderer) return;
this._updateRenderTexture();
if (!this._renderTexture) return;
this._lightingSprite = new PIXI.Sprite(this._renderTexture);
this._lightingSprite.blendMode = PIXI.BLEND_MODES.MULTIPLY;
var sceneContainer = this._runtimeSceneRenderer.getPIXIContainer();
var index = sceneContainer.getChildIndex(this._pixiContainer);
sceneContainer.addChildAt(this._lightingSprite, index);
sceneContainer.removeChild(this._pixiContainer);
};

View File

@@ -23,6 +23,15 @@ gdjs.PixiImageManager = function(resources)
gdjs.ImageManager = gdjs.PixiImageManager; //Register the class to let the engine use it.
/**
* Update the resources data of the game. Useful for hot-reloading, should not be used otherwise.
*
* @param {ResourceData[]} resources The resources data of the game.
*/
gdjs.PixiImageManager.prototype.setResources = function(resources) {
this._resources = resources;
};
/**
* Return the PIXI texture associated to the specified resource name.
* Returns a placeholder texture if not found.
@@ -124,7 +133,6 @@ gdjs.PixiImageManager.prototype.loadTextures = function(onProgress, onComplete)
if ( res.file && res.kind === "image" ) {
if (this._loadedTextures.containsKey(res.name)) {
console.log("Texture \"" + res.name + "\" is already loaded.");
continue;
}

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ gdjs.RuntimeGamePixiRenderer = function(game, forceFullscreen) {
this._isFullscreen = false; //Used to track if the window is displayed as fullscreen (see setFullscreen method).
this._forceFullscreen = forceFullscreen; //If set to true, the canvas will always be displayed as fullscreen, even if _isFullscreen == false.
/** @type {PIXI.SystemRenderer} */
/** @type {?PIXI.Renderer} */
this._pixiRenderer = null;
this._canvasWidth = 0; // Current width of the canvas (might be scaled down/up compared to renderer)
this._canvasHeight = 0; // Current height of the canvas (might be scaled down/up compared to renderer)
@@ -41,7 +41,7 @@ gdjs.RuntimeGamePixiRenderer.prototype.createStandardCanvas = function(
);
parentElement.appendChild(this._pixiRenderer.view); // add the renderer view element to the DOM
this._pixiRenderer.view.style['position'] = 'absolute';
this._pixiRenderer.view.tabindex = '1'; //Ensure that the canvas has the focus.
this._pixiRenderer.view.tabIndex = 1; //Ensure that the canvas has the focus.
this._resizeCanvas();
// Handle scale mode

View File

@@ -1,14 +1,30 @@
gdjs.RuntimeScenePixiRenderer = function(runtimeScene, runtimeGameRenderer) {
// @ts-check
/**
* The renderer for a gdjs.RuntimeScene using Pixi.js.
* @class RuntimeScenePixiRenderer
* @memberof gdjs
* @param {gdjs.RuntimeScene} runtimeScene
* @param {gdjs.RuntimeGamePixiRenderer} runtimeGameRenderer
*/
gdjs.RuntimeScenePixiRenderer = function (runtimeScene, runtimeGameRenderer) {
this._pixiRenderer = runtimeGameRenderer
? runtimeGameRenderer.getPIXIRenderer()
: null;
this._runtimeScene = runtimeScene;
this._pixiContainer = new PIXI.Container(); //The Container meant to contains all pixi objects of the scene.
this._pixiContainer = new PIXI.Container(); // Contains the layers of the scene (and, optionally, debug PIXI objects).
this._pixiContainer.sortableChildren = true;
/** @type {?PIXI.Graphics} */
this._debugDraw = null;
/** @type {?PIXI.Text} */
this._profilerText = null;
};
gdjs.RuntimeSceneRenderer = gdjs.RuntimeScenePixiRenderer; //Register the class to let the engine use it.
gdjs.RuntimeScenePixiRenderer.prototype.onGameResolutionResized = function() {
gdjs.RuntimeScenePixiRenderer.prototype.onGameResolutionResized = function () {
if (!this._pixiRenderer) return;
var runtimeGame = this._runtimeScene.getGame();
@@ -18,7 +34,11 @@ gdjs.RuntimeScenePixiRenderer.prototype.onGameResolutionResized = function() {
this._pixiRenderer.height / runtimeGame.getGameResolutionHeight();
};
gdjs.RuntimeScenePixiRenderer.prototype.render = function() {
gdjs.RuntimeScenePixiRenderer.prototype.onSceneUnloaded = function () {
// Nothing to do.
};
gdjs.RuntimeScenePixiRenderer.prototype.render = function () {
if (!this._pixiRenderer) return;
// this._renderProfileText(); //Uncomment to display profiling times
@@ -28,28 +48,38 @@ gdjs.RuntimeScenePixiRenderer.prototype.render = function() {
this._pixiRenderer.render(this._pixiContainer);
};
gdjs.RuntimeScenePixiRenderer.prototype._renderProfileText = function() {
if (!this._runtimeScene.getProfiler()) return;
gdjs.RuntimeScenePixiRenderer.prototype._renderProfileText = function () {
var profiler = this._runtimeScene.getProfiler();
if (!profiler) return;
if (!this._profilerText) {
this._profilerText = new PIXI.Text(" ", {
align: "left",
stroke: "#FFF",
strokeThickness: 1
this._profilerText = new PIXI.Text(' ', {
align: 'left',
stroke: '#FFF',
strokeThickness: 1,
});
// Add on top of all layers:
this._pixiContainer.addChild(this._profilerText);
}
var average = this._runtimeScene.getProfiler().getFramesAverageMeasures();
var average = profiler.getFramesAverageMeasures();
var outputs = [];
gdjs.Profiler.getProfilerSectionTexts("All", average, outputs);
gdjs.Profiler.getProfilerSectionTexts('All', average, outputs);
this._profilerText.text = outputs.join("\n");
this._profilerText.text = outputs.join('\n');
};
gdjs.RuntimeScenePixiRenderer.prototype.renderDebugDraw = function(instances, layersCameraCoordinates) {
/**
* @param {gdjs.RuntimeObject[]} instances
* @param {Object.<string, number[]>} layersCameraCoordinates
*/
gdjs.RuntimeScenePixiRenderer.prototype.renderDebugDraw = function (
instances,
layersCameraCoordinates
) {
if (!this._debugDraw) {
this._debugDraw = new PIXI.Graphics();
// Add on top of all layers:
this._pixiContainer.addChild(this._debugDraw);
}
/** @type PIXI.Graphics */
@@ -61,7 +91,7 @@ gdjs.RuntimeScenePixiRenderer.prototype.renderDebugDraw = function(instances, la
debugDraw.fill.alpha = 0.1;
debugDraw.alpha = 0.8;
for(var i = 0;i < instances.length;i++) {
for (var i = 0; i < instances.length; i++) {
var object = instances[i];
var cameraCoords = layersCameraCoordinates[object.getLayer()];
var rendererObject = object.getRendererObject();
@@ -69,19 +99,55 @@ gdjs.RuntimeScenePixiRenderer.prototype.renderDebugDraw = function(instances, la
if (!cameraCoords || !rendererObject) continue;
var aabb = object.getAABB();
debugDraw.drawRect(aabb.min[0], aabb.min[1], aabb.max[0] - aabb.min[0], aabb.max[1] - aabb.min[1]);
debugDraw.drawRect(
aabb.min[0],
aabb.min[1],
aabb.max[0] - aabb.min[0],
aabb.max[1] - aabb.min[1]
);
}
debugDraw.endFill();
};
gdjs.RuntimeScenePixiRenderer.prototype.hideCursor = function() {
this._pixiRenderer.view.style.cursor = "none";
gdjs.RuntimeScenePixiRenderer.prototype.hideCursor = function () {
if (!this._pixiRenderer) return;
this._pixiRenderer.view.style.cursor = 'none';
};
gdjs.RuntimeScenePixiRenderer.prototype.showCursor = function() {
this._pixiRenderer.view.style.cursor = "";
gdjs.RuntimeScenePixiRenderer.prototype.showCursor = function () {
if (!this._pixiRenderer) return;
this._pixiRenderer.view.style.cursor = '';
};
gdjs.RuntimeScenePixiRenderer.prototype.getPIXIContainer = function() {
gdjs.RuntimeScenePixiRenderer.prototype.getPIXIContainer = function () {
return this._pixiContainer;
};
gdjs.RuntimeScenePixiRenderer.prototype.getPIXIRenderer = function () {
return this._pixiRenderer;
};
/**
* @param {gdjs.Layer} layer
* @param {number} index
*/
gdjs.RuntimeScenePixiRenderer.prototype.setLayerIndex = function (
layer,
index
) {
/** @type {gdjs.LayerPixiRenderer} */
// @ts-ignore - assume the renderer is the correct one
var layerPixiRenderer = layer.getRenderer();
/** @type {PIXI.Container | ?PIXI.Sprite} */
var layerPixiObject = layerPixiRenderer.getRendererObject();
if (layer.isLightingLayer())
layerPixiObject = layerPixiRenderer.getLightingSprite();
if (!layerPixiObject) return;
if (this._pixiContainer.children.indexOf(layerPixiObject) === index) return;
this._pixiContainer.removeChild(layerPixiObject);
this._pixiContainer.addChildAt(layerPixiObject, index);
};

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