mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
184 Commits
remove-unu
...
refactor/p
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ae0f2687a5 | ||
![]() |
56bcb79dcd | ||
![]() |
4ca859c8b9 | ||
![]() |
701b5a3250 | ||
![]() |
1e87b74d48 | ||
![]() |
b7a4bab53c | ||
![]() |
2223b6516b | ||
![]() |
7d89208e7b | ||
![]() |
70508292e4 | ||
![]() |
c899db1dba | ||
![]() |
2fb27f430e | ||
![]() |
6cd2400848 | ||
![]() |
fadccba757 | ||
![]() |
29a43514ce | ||
![]() |
e64ab94f76 | ||
![]() |
ed969d2909 | ||
![]() |
77bf67fdf0 | ||
![]() |
580c7a370c | ||
![]() |
266f0f2b6e | ||
![]() |
58af6aacb4 | ||
![]() |
1c4ee1c928 | ||
![]() |
0275476bec | ||
![]() |
3f0194acf0 | ||
![]() |
017d8b28c2 | ||
![]() |
4b518ba0fc | ||
![]() |
e4938b25b5 | ||
![]() |
c56ad2d277 | ||
![]() |
f069f9d942 | ||
![]() |
9f7c60a69e | ||
![]() |
774bf0db61 | ||
![]() |
ccd9c5c0ff | ||
![]() |
5b130a34e3 | ||
![]() |
3e6d147660 | ||
![]() |
433d85db94 | ||
![]() |
b75f707b30 | ||
![]() |
67799eb3eb | ||
![]() |
1173210386 | ||
![]() |
007cc48291 | ||
![]() |
12813c9ad9 | ||
![]() |
e503e34059 | ||
![]() |
4bf576a889 | ||
![]() |
eff1c1bcb6 | ||
![]() |
abce34f2b1 | ||
![]() |
07276d5e16 | ||
![]() |
1fdd8cc792 | ||
![]() |
79e40605d5 | ||
![]() |
fcc91e3fea | ||
![]() |
5c66623631 | ||
![]() |
5637642e1b | ||
![]() |
7e8b44af2e | ||
![]() |
0dd4650aae | ||
![]() |
c7cac31830 | ||
![]() |
56cb8581c4 | ||
![]() |
1993040b70 | ||
![]() |
883991081a | ||
![]() |
7d8afef1ad | ||
![]() |
8178595546 | ||
![]() |
a478068c64 | ||
![]() |
368da1b610 | ||
![]() |
4ee43202e9 | ||
![]() |
602fdf4bfd | ||
![]() |
6110acafcc | ||
![]() |
a3696ca9d1 | ||
![]() |
1bb473b0b0 | ||
![]() |
4376b4f36e | ||
![]() |
6ecbae9c35 | ||
![]() |
93e9fc6aed | ||
![]() |
7c0a7a4152 | ||
![]() |
d4cdb3ff83 | ||
![]() |
a7cac91e45 | ||
![]() |
6a6d72cd9a | ||
![]() |
11d74b3ea5 | ||
![]() |
5b65cf84eb | ||
![]() |
6b7af0474f | ||
![]() |
99f7e55044 | ||
![]() |
6b522b1c31 | ||
![]() |
0f35e48690 | ||
![]() |
53d19dd628 | ||
![]() |
155dc1bec8 | ||
![]() |
be26e39eae | ||
![]() |
90fa5ea8e8 | ||
![]() |
4845251f78 | ||
![]() |
31ef3fec58 | ||
![]() |
292b23ea17 | ||
![]() |
9c5a5db7a8 | ||
![]() |
d73ae4c56e | ||
![]() |
339929e021 | ||
![]() |
cea1cf20f1 | ||
![]() |
4ec2705f75 | ||
![]() |
408f6f8134 | ||
![]() |
c64bac0010 | ||
![]() |
f8d5c89ebf | ||
![]() |
ca3bb40e96 | ||
![]() |
713d437b70 | ||
![]() |
09381c3836 | ||
![]() |
ea4c2e0827 | ||
![]() |
a5d1149c21 | ||
![]() |
119b0fadce | ||
![]() |
7111859768 | ||
![]() |
08dfb4d36b | ||
![]() |
cacd482af9 | ||
![]() |
3568a58019 | ||
![]() |
a32d5fcd7e | ||
![]() |
1a999fd2dd | ||
![]() |
76648764bb | ||
![]() |
c25803c122 | ||
![]() |
ba8d7f4e38 | ||
![]() |
e1c22f6994 | ||
![]() |
7b70f9172f | ||
![]() |
a871e0f2ec | ||
![]() |
f6499e1163 | ||
![]() |
d03e58636d | ||
![]() |
a7be928f2f | ||
![]() |
7c83610d28 | ||
![]() |
1a1e92b072 | ||
![]() |
5118f13e0b | ||
![]() |
f3ee18cdc7 | ||
![]() |
c5584f746e | ||
![]() |
706a6de94c | ||
![]() |
8c750b54dd | ||
![]() |
649d744664 | ||
![]() |
12a6fec18e | ||
![]() |
7fc4aa47f6 | ||
![]() |
483f78fa75 | ||
![]() |
35b80818dc | ||
![]() |
b3f19726dc | ||
![]() |
dd332e3cce | ||
![]() |
dc95a7511f | ||
![]() |
77130c7d2e | ||
![]() |
8d1c3e8290 | ||
![]() |
63570fd3c8 | ||
![]() |
6945409280 | ||
![]() |
e345b2726f | ||
![]() |
3a2a662f62 | ||
![]() |
73d850f933 | ||
![]() |
1f2293e7d6 | ||
![]() |
b038984097 | ||
![]() |
a45a5bfe7e | ||
![]() |
8cbefa0cde | ||
![]() |
1d607d474a | ||
![]() |
3cd6e36a73 | ||
![]() |
cfbec2df4a | ||
![]() |
2823fde86a | ||
![]() |
f29ae50f2e | ||
![]() |
ba40c67941 | ||
![]() |
c8ca3c2931 | ||
![]() |
0bb0969ab8 | ||
![]() |
3b0a7c442f | ||
![]() |
df3c95c466 | ||
![]() |
482d52ff5a | ||
![]() |
5a78c763ef | ||
![]() |
3393af4b3b | ||
![]() |
c2b77c9df7 | ||
![]() |
514e4692ab | ||
![]() |
210e59f201 | ||
![]() |
e2058052f2 | ||
![]() |
90dca41e20 | ||
![]() |
15b463dad1 | ||
![]() |
a97a1f8a86 | ||
![]() |
901f84daaa | ||
![]() |
6af50ae5c0 | ||
![]() |
f7bd4bee6e | ||
![]() |
4a4b015242 | ||
![]() |
75ee0b68e3 | ||
![]() |
778104447c | ||
![]() |
a4ddc0a6ef | ||
![]() |
3e713029e2 | ||
![]() |
7eb5402ee2 | ||
![]() |
25adb026b8 | ||
![]() |
54bd2960ac | ||
![]() |
1fde65b5e3 | ||
![]() |
b4295b4077 | ||
![]() |
c7a929374d | ||
![]() |
7571e64396 | ||
![]() |
198267d7cb | ||
![]() |
0982424d0d | ||
![]() |
29747690c2 | ||
![]() |
ad74532752 | ||
![]() |
80e5376f74 | ||
![]() |
cc24eab2aa | ||
![]() |
423c8165ad | ||
![]() |
0af00818dc | ||
![]() |
9f5c783d73 | ||
![]() |
ad65971c01 |
4
.github/workflows/update-translations.yml
vendored
4
.github/workflows/update-translations.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: "newIDE/app/package-lock.json"
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
working-directory: newIDE/app
|
||||
|
||||
- name: Create a Pull Request with the changes
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
commit-message: Update translations [skip ci]
|
||||
branch: chore/update-translations
|
||||
|
@@ -56,6 +56,7 @@ blocks:
|
||||
- name: GDJS typing and documentation generation
|
||||
commands:
|
||||
- checkout
|
||||
- cache restore newIDE-app-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum newIDE/app/package-lock.json)
|
||||
- cache restore GDJS-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/package-lock.json)
|
||||
- cache restore GDJS-tests-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/tests/package-lock.json)
|
||||
- cd GDJS
|
||||
|
1
.vscode/tasks.json
vendored
1
.vscode/tasks.json
vendored
@@ -8,6 +8,7 @@
|
||||
"group": "build",
|
||||
"label": "Start development server",
|
||||
"detail": "Starts the GDevelop development server.",
|
||||
"options": { "env": { "NODE_OPTIONS": "--max-old-space-size=8192" } },
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "cra",
|
||||
|
@@ -6,8 +6,6 @@
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
|
||||
#include <vector>
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
@@ -4,13 +4,11 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef GDCORE_ANIMATION_H
|
||||
#define GDCORE_ANIMATION_H
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "GDCore/String.h"
|
||||
namespace gd {
|
||||
class Direction;
|
||||
}
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
@@ -93,4 +91,3 @@ class GD_CORE_API Animation {
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
#endif // GDCORE_ANIMATION_H
|
||||
|
@@ -3,12 +3,12 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef GDCORE_DIRECTION_H
|
||||
#define GDCORE_DIRECTION_H
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
|
||||
namespace gd {
|
||||
class Sprite;
|
||||
class SerializerElement;
|
||||
}
|
||||
|
||||
@@ -142,4 +142,3 @@ class GD_CORE_API Direction {
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
#endif // GDCORE_DIRECTION_H
|
||||
|
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/Project/InitialInstance.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
Animation SpriteAnimationList::badAnimation;
|
||||
|
||||
SpriteAnimationList::SpriteAnimationList()
|
||||
: adaptCollisionMaskAutomatically(true) {}
|
||||
|
||||
SpriteAnimationList::~SpriteAnimationList(){};
|
||||
|
||||
void SpriteAnimationList::UnserializeFrom(const gd::SerializerElement& element) {
|
||||
adaptCollisionMaskAutomatically =
|
||||
element.GetBoolAttribute("adaptCollisionMaskAutomatically", false);
|
||||
|
||||
RemoveAllAnimations();
|
||||
const gd::SerializerElement& animationsElement =
|
||||
element.GetChild("animations", 0, "Animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation", "Animation");
|
||||
for (std::size_t i = 0; i < animationsElement.GetChildrenCount(); ++i) {
|
||||
const gd::SerializerElement& animationElement =
|
||||
animationsElement.GetChild(i);
|
||||
Animation newAnimation;
|
||||
|
||||
newAnimation.useMultipleDirections = animationElement.GetBoolAttribute(
|
||||
"useMultipleDirections", false, "typeNormal");
|
||||
newAnimation.SetName(animationElement.GetStringAttribute("name", ""));
|
||||
|
||||
// Compatibility with GD <= 3.3
|
||||
if (animationElement.HasChild("Direction")) {
|
||||
for (std::size_t j = 0;
|
||||
j < animationElement.GetChildrenCount("Direction");
|
||||
++j) {
|
||||
Direction direction;
|
||||
direction.UnserializeFrom(animationElement.GetChild("Direction", j));
|
||||
|
||||
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
|
||||
newAnimation.SetDirection(direction,
|
||||
newAnimation.GetDirectionsCount() - 1);
|
||||
}
|
||||
}
|
||||
// End of compatibility code
|
||||
else {
|
||||
const gd::SerializerElement& directionsElement =
|
||||
animationElement.GetChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
for (std::size_t j = 0; j < directionsElement.GetChildrenCount(); ++j) {
|
||||
Direction direction;
|
||||
direction.UnserializeFrom(directionsElement.GetChild(j));
|
||||
|
||||
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
|
||||
newAnimation.SetDirection(direction,
|
||||
newAnimation.GetDirectionsCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
AddAnimation(newAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAnimationList::SerializeTo(gd::SerializerElement& element) const {
|
||||
element.SetAttribute("adaptCollisionMaskAutomatically",
|
||||
adaptCollisionMaskAutomatically);
|
||||
|
||||
// Animations
|
||||
gd::SerializerElement& animationsElement = element.AddChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
for (std::size_t k = 0; k < GetAnimationsCount(); k++) {
|
||||
gd::SerializerElement& animationElement =
|
||||
animationsElement.AddChild("animation");
|
||||
|
||||
animationElement.SetAttribute("useMultipleDirections",
|
||||
GetAnimation(k).useMultipleDirections);
|
||||
animationElement.SetAttribute("name", GetAnimation(k).GetName());
|
||||
|
||||
gd::SerializerElement& directionsElement =
|
||||
animationElement.AddChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
for (std::size_t l = 0; l < GetAnimation(k).GetDirectionsCount(); l++) {
|
||||
GetAnimation(k).GetDirection(l).SerializeTo(
|
||||
directionsElement.AddChild("direction"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAnimationList::ExposeResources(gd::ArbitraryResourceWorker& worker) {
|
||||
for (std::size_t j = 0; j < GetAnimationsCount(); j++) {
|
||||
for (std::size_t k = 0; k < GetAnimation(j).GetDirectionsCount(); k++) {
|
||||
for (std::size_t l = 0;
|
||||
l < GetAnimation(j).GetDirection(k).GetSpritesCount();
|
||||
l++) {
|
||||
worker.ExposeImage(
|
||||
GetAnimation(j).GetDirection(k).GetSprite(l).GetImageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Animation& SpriteAnimationList::GetAnimation(std::size_t nb) const {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
Animation& SpriteAnimationList::GetAnimation(std::size_t nb) {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
void SpriteAnimationList::AddAnimation(const Animation& animation) {
|
||||
animations.push_back(animation);
|
||||
}
|
||||
|
||||
bool SpriteAnimationList::RemoveAnimation(std::size_t nb) {
|
||||
if (nb >= GetAnimationsCount()) return false;
|
||||
|
||||
animations.erase(animations.begin() + nb);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpriteAnimationList::SwapAnimations(std::size_t firstIndex,
|
||||
std::size_t secondIndex) {
|
||||
if (firstIndex < animations.size() && secondIndex < animations.size() &&
|
||||
firstIndex != secondIndex)
|
||||
std::swap(animations[firstIndex], animations[secondIndex]);
|
||||
}
|
||||
|
||||
void SpriteAnimationList::MoveAnimation(std::size_t oldIndex, std::size_t newIndex) {
|
||||
if (oldIndex >= animations.size() || newIndex >= animations.size()) return;
|
||||
|
||||
auto animation = animations[oldIndex];
|
||||
animations.erase(animations.begin() + oldIndex);
|
||||
animations.insert(animations.begin() + newIndex, animation);
|
||||
}
|
||||
|
||||
} // namespace gd
|
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
|
||||
|
||||
namespace gd {
|
||||
class InitialInstance;
|
||||
class SerializerElement;
|
||||
class PropertyDescriptor;
|
||||
class ArbitraryResourceWorker;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief A list of animations, containing directions with images and collision mask.
|
||||
*
|
||||
* It's used in the configuration of object that implements image-based animations.
|
||||
*
|
||||
* \see Animation
|
||||
* \see Direction
|
||||
* \see Sprite
|
||||
* \ingroup SpriteObjectExtension
|
||||
*/
|
||||
class GD_CORE_API SpriteAnimationList {
|
||||
public:
|
||||
SpriteAnimationList();
|
||||
virtual ~SpriteAnimationList();
|
||||
|
||||
void ExposeResources(gd::ArbitraryResourceWorker& worker);
|
||||
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
*/
|
||||
const Animation& GetAnimation(std::size_t nb) const;
|
||||
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
*/
|
||||
Animation& GetAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Return the number of animations this object has.
|
||||
*/
|
||||
std::size_t GetAnimationsCount() const { return animations.size(); };
|
||||
|
||||
/**
|
||||
* \brief Add an animation at the end of the existing ones.
|
||||
*/
|
||||
void AddAnimation(const Animation& animation);
|
||||
|
||||
/**
|
||||
* \brief Remove an animation.
|
||||
*/
|
||||
bool RemoveAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Remove all animations.
|
||||
*/
|
||||
void RemoveAllAnimations() { animations.clear(); }
|
||||
|
||||
/**
|
||||
* \brief Return true if the object hasn't any animation.
|
||||
*/
|
||||
bool HasNoAnimations() const { return animations.empty(); }
|
||||
|
||||
/**
|
||||
* \brief Swap the position of two animations
|
||||
*/
|
||||
void SwapAnimations(std::size_t firstIndex, std::size_t secondIndex);
|
||||
|
||||
/**
|
||||
* \brief Change the position of the specified animation
|
||||
*/
|
||||
void MoveAnimation(std::size_t oldIndex, std::size_t newIndex);
|
||||
|
||||
/**
|
||||
* \brief Return a read-only reference to the vector containing all the
|
||||
* animation of the object.
|
||||
*/
|
||||
const std::vector<Animation>& GetAllAnimations() const { return animations; }
|
||||
|
||||
/**
|
||||
* @brief Check if the collision mask adapts automatically to the animation.
|
||||
*/
|
||||
bool AdaptCollisionMaskAutomatically() const {
|
||||
return adaptCollisionMaskAutomatically;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set if the collision mask adapts automatically to the animation.
|
||||
*/
|
||||
void SetAdaptCollisionMaskAutomatically(bool enable) {
|
||||
adaptCollisionMaskAutomatically = enable;
|
||||
}
|
||||
|
||||
void UnserializeFrom(const gd::SerializerElement& element);
|
||||
void SerializeTo(gd::SerializerElement& element) const;
|
||||
|
||||
private:
|
||||
|
||||
mutable std::vector<Animation> animations;
|
||||
|
||||
static Animation badAnimation; //< Bad animation when an out of bound
|
||||
// animation is requested.
|
||||
bool adaptCollisionMaskAutomatically; ///< If set to true, the collision
|
||||
///< mask will be automatically
|
||||
///< adapted to the animation of the
|
||||
///< object.
|
||||
};
|
||||
|
||||
} // namespace gd
|
@@ -23,88 +23,20 @@
|
||||
|
||||
namespace gd {
|
||||
|
||||
Animation SpriteObject::badAnimation;
|
||||
|
||||
SpriteObject::SpriteObject()
|
||||
: updateIfNotVisible(false), adaptCollisionMaskAutomatically(true) {}
|
||||
: updateIfNotVisible(false) {}
|
||||
|
||||
SpriteObject::~SpriteObject(){};
|
||||
|
||||
void SpriteObject::DoUnserializeFrom(gd::Project& project,
|
||||
const gd::SerializerElement& element) {
|
||||
updateIfNotVisible = element.GetBoolAttribute("updateIfNotVisible", true);
|
||||
adaptCollisionMaskAutomatically =
|
||||
element.GetBoolAttribute("adaptCollisionMaskAutomatically", false);
|
||||
|
||||
RemoveAllAnimations();
|
||||
const gd::SerializerElement& animationsElement =
|
||||
element.GetChild("animations", 0, "Animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation", "Animation");
|
||||
for (std::size_t i = 0; i < animationsElement.GetChildrenCount(); ++i) {
|
||||
const gd::SerializerElement& animationElement =
|
||||
animationsElement.GetChild(i);
|
||||
Animation newAnimation;
|
||||
|
||||
newAnimation.useMultipleDirections = animationElement.GetBoolAttribute(
|
||||
"useMultipleDirections", false, "typeNormal");
|
||||
newAnimation.SetName(animationElement.GetStringAttribute("name", ""));
|
||||
|
||||
// Compatibility with GD <= 3.3
|
||||
if (animationElement.HasChild("Direction")) {
|
||||
for (std::size_t j = 0;
|
||||
j < animationElement.GetChildrenCount("Direction");
|
||||
++j) {
|
||||
Direction direction;
|
||||
direction.UnserializeFrom(animationElement.GetChild("Direction", j));
|
||||
|
||||
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
|
||||
newAnimation.SetDirection(direction,
|
||||
newAnimation.GetDirectionsCount() - 1);
|
||||
}
|
||||
}
|
||||
// End of compatibility code
|
||||
else {
|
||||
const gd::SerializerElement& directionsElement =
|
||||
animationElement.GetChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
for (std::size_t j = 0; j < directionsElement.GetChildrenCount(); ++j) {
|
||||
Direction direction;
|
||||
direction.UnserializeFrom(directionsElement.GetChild(j));
|
||||
|
||||
newAnimation.SetDirectionsCount(newAnimation.GetDirectionsCount() + 1);
|
||||
newAnimation.SetDirection(direction,
|
||||
newAnimation.GetDirectionsCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
AddAnimation(newAnimation);
|
||||
}
|
||||
animations.UnserializeFrom(element);
|
||||
}
|
||||
|
||||
void SpriteObject::DoSerializeTo(gd::SerializerElement& element) const {
|
||||
element.SetAttribute("updateIfNotVisible", updateIfNotVisible);
|
||||
element.SetAttribute("adaptCollisionMaskAutomatically",
|
||||
adaptCollisionMaskAutomatically);
|
||||
|
||||
// Animations
|
||||
gd::SerializerElement& animationsElement = element.AddChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
for (std::size_t k = 0; k < GetAnimationsCount(); k++) {
|
||||
gd::SerializerElement& animationElement =
|
||||
animationsElement.AddChild("animation");
|
||||
|
||||
animationElement.SetAttribute("useMultipleDirections",
|
||||
GetAnimation(k).useMultipleDirections);
|
||||
animationElement.SetAttribute("name", GetAnimation(k).GetName());
|
||||
|
||||
gd::SerializerElement& directionsElement =
|
||||
animationElement.AddChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
for (std::size_t l = 0; l < GetAnimation(k).GetDirectionsCount(); l++) {
|
||||
GetAnimation(k).GetDirection(l).SerializeTo(
|
||||
directionsElement.AddChild("direction"));
|
||||
}
|
||||
}
|
||||
animations.SerializeTo(element);
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> SpriteObject::GetProperties()
|
||||
@@ -127,16 +59,7 @@ bool SpriteObject::UpdateProperty(const gd::String& name,
|
||||
}
|
||||
|
||||
void SpriteObject::ExposeResources(gd::ArbitraryResourceWorker& worker) {
|
||||
for (std::size_t j = 0; j < GetAnimationsCount(); j++) {
|
||||
for (std::size_t k = 0; k < GetAnimation(j).GetDirectionsCount(); k++) {
|
||||
for (std::size_t l = 0;
|
||||
l < GetAnimation(j).GetDirection(k).GetSpritesCount();
|
||||
l++) {
|
||||
worker.ExposeImage(
|
||||
GetAnimation(j).GetDirection(k).GetSprite(l).GetImageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
animations.ExposeResources(worker);
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
@@ -168,42 +91,12 @@ bool SpriteObject::UpdateInitialInstanceProperty(
|
||||
return true;
|
||||
}
|
||||
|
||||
const Animation& SpriteObject::GetAnimation(std::size_t nb) const {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
const SpriteAnimationList& SpriteObject::GetAnimations() const {
|
||||
return animations;
|
||||
}
|
||||
|
||||
Animation& SpriteObject::GetAnimation(std::size_t nb) {
|
||||
if (nb >= animations.size()) return badAnimation;
|
||||
|
||||
return animations[nb];
|
||||
}
|
||||
|
||||
void SpriteObject::AddAnimation(const Animation& animation) {
|
||||
animations.push_back(animation);
|
||||
}
|
||||
|
||||
bool SpriteObject::RemoveAnimation(std::size_t nb) {
|
||||
if (nb >= GetAnimationsCount()) return false;
|
||||
|
||||
animations.erase(animations.begin() + nb);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpriteObject::SwapAnimations(std::size_t firstIndex,
|
||||
std::size_t secondIndex) {
|
||||
if (firstIndex < animations.size() && secondIndex < animations.size() &&
|
||||
firstIndex != secondIndex)
|
||||
std::swap(animations[firstIndex], animations[secondIndex]);
|
||||
}
|
||||
|
||||
void SpriteObject::MoveAnimation(std::size_t oldIndex, std::size_t newIndex) {
|
||||
if (oldIndex >= animations.size() || newIndex >= animations.size()) return;
|
||||
|
||||
auto animation = animations[oldIndex];
|
||||
animations.erase(animations.begin() + oldIndex);
|
||||
animations.insert(animations.begin() + newIndex, animation);
|
||||
SpriteAnimationList& SpriteObject::GetAnimations() {
|
||||
return animations;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -4,18 +4,15 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef GDCORE_SPRITEOBJECT_H
|
||||
#define GDCORE_SPRITEOBJECT_H
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Animation.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Direction.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/Sprite.h"
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
|
||||
namespace gd {
|
||||
class InitialInstance;
|
||||
class Object;
|
||||
class Layout;
|
||||
class Sprite;
|
||||
class Animation;
|
||||
class SerializerElement;
|
||||
class PropertyDescriptor;
|
||||
} // namespace gd
|
||||
@@ -59,76 +56,15 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) override;
|
||||
|
||||
/** \name Animations
|
||||
* Methods related to animations management
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
* \brief Return the animation configuration.
|
||||
*/
|
||||
const Animation& GetAnimation(std::size_t nb) const;
|
||||
const SpriteAnimationList& GetAnimations() const;
|
||||
|
||||
/**
|
||||
* \brief Return the animation at the specified index.
|
||||
* If the index is out of bound, a "bad animation" object is returned.
|
||||
* @brief Return the animation configuration.
|
||||
*/
|
||||
Animation& GetAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Return the number of animations this object has.
|
||||
*/
|
||||
std::size_t GetAnimationsCount() const { return animations.size(); };
|
||||
|
||||
/**
|
||||
* \brief Add an animation at the end of the existing ones.
|
||||
*/
|
||||
void AddAnimation(const Animation& animation);
|
||||
|
||||
/**
|
||||
* \brief Remove an animation.
|
||||
*/
|
||||
bool RemoveAnimation(std::size_t nb);
|
||||
|
||||
/**
|
||||
* \brief Remove all animations.
|
||||
*/
|
||||
void RemoveAllAnimations() { animations.clear(); }
|
||||
|
||||
/**
|
||||
* \brief Return true if the object hasn't any animation.
|
||||
*/
|
||||
bool HasNoAnimations() const { return animations.empty(); }
|
||||
|
||||
/**
|
||||
* \brief Swap the position of two animations
|
||||
*/
|
||||
void SwapAnimations(std::size_t firstIndex, std::size_t secondIndex);
|
||||
|
||||
/**
|
||||
* \brief Change the position of the specified animation
|
||||
*/
|
||||
void MoveAnimation(std::size_t oldIndex, std::size_t newIndex);
|
||||
|
||||
/**
|
||||
* \brief Return a read-only reference to the vector containing all the
|
||||
* animation of the object.
|
||||
*/
|
||||
const std::vector<Animation>& GetAllAnimations() const { return animations; }
|
||||
|
||||
/**
|
||||
* @brief Check if the collision mask adapts automatically to the animation.
|
||||
*/
|
||||
bool AdaptCollisionMaskAutomatically() const {
|
||||
return adaptCollisionMaskAutomatically;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set if the collision mask adapts automatically to the animation.
|
||||
*/
|
||||
void SetAdaptCollisionMaskAutomatically(bool enable) {
|
||||
adaptCollisionMaskAutomatically = enable;
|
||||
}
|
||||
SpriteAnimationList& GetAnimations();
|
||||
|
||||
/**
|
||||
* \brief Set if the object animation should be played even if the object is
|
||||
@@ -143,25 +79,17 @@ class GD_CORE_API SpriteObject : public gd::ObjectConfiguration {
|
||||
* is hidden or far from the camera (false by default).
|
||||
*/
|
||||
bool GetUpdateIfNotVisible() const { return updateIfNotVisible; }
|
||||
///@}
|
||||
|
||||
private:
|
||||
void DoUnserializeFrom(gd::Project& project,
|
||||
const gd::SerializerElement& element) override;
|
||||
void DoSerializeTo(gd::SerializerElement& element) const override;
|
||||
|
||||
mutable std::vector<Animation> animations;
|
||||
SpriteAnimationList animations;
|
||||
|
||||
bool updateIfNotVisible; ///< If set to true, ask the game engine to play
|
||||
///< object animation even if hidden or far from
|
||||
///< the screen.
|
||||
|
||||
static Animation badAnimation; //< Bad animation when an out of bound
|
||||
// animation is requested.
|
||||
bool adaptCollisionMaskAutomatically; ///< If set to true, the collision
|
||||
///< mask will be automatically
|
||||
///< adapted to the animation of the
|
||||
///< object.
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
#endif // GDCORE_SPRITEOBJECT_H
|
||||
|
@@ -114,6 +114,8 @@ public:
|
||||
|
||||
/**
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
virtual AbstractFunctionMetadata &
|
||||
SetIncludeFile(const gd::String &includeFile) = 0;
|
||||
|
@@ -13,12 +13,15 @@
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/BehaviorsSharedData.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Tools/MakeUnique.h"
|
||||
#include "GDCore/Tools/Log.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
const std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::badProperties;
|
||||
|
||||
BehaviorMetadata::BehaviorMetadata(
|
||||
const gd::String& extensionNamespace_,
|
||||
const gd::String& nameWithNamespace,
|
||||
@@ -47,8 +50,14 @@ BehaviorMetadata::BehaviorMetadata(
|
||||
"BehaviorMetadata is valid for: " + nameWithNamespace);
|
||||
}
|
||||
|
||||
if (instance) instance->SetTypeName(nameWithNamespace);
|
||||
if (sharedDatasInstance) sharedDatasInstance->SetTypeName(nameWithNamespace);
|
||||
if (instance) {
|
||||
instance->SetTypeName(nameWithNamespace);
|
||||
instance->InitializeContent();
|
||||
}
|
||||
if (sharedDatasInstance) {
|
||||
sharedDatasInstance->SetTypeName(nameWithNamespace);
|
||||
sharedDatasInstance->InitializeContent();
|
||||
}
|
||||
}
|
||||
|
||||
gd::InstructionMetadata& BehaviorMetadata::AddCondition(
|
||||
@@ -405,10 +414,30 @@ gd::Behavior& BehaviorMetadata::Get() const {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetProperties() const {
|
||||
if (!instance) {
|
||||
return badProperties;
|
||||
}
|
||||
// TODO Properties should be declared on BehaviorMetadata directly.
|
||||
// - Add 2 `properties` members (one for shared properties)
|
||||
// - Add methods to declare new properties
|
||||
return instance->GetProperties();
|
||||
}
|
||||
|
||||
gd::BehaviorsSharedData* BehaviorMetadata::GetSharedDataInstance() const {
|
||||
return sharedDatasInstance.get();
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> BehaviorMetadata::GetSharedProperties() const {
|
||||
if (!sharedDatasInstance) {
|
||||
return badProperties;
|
||||
}
|
||||
// TODO Properties should be declared on BehaviorMetadata directly.
|
||||
// - Add 2 `properties` members (one for shared properties)
|
||||
// - Add methods to declare new properties
|
||||
return sharedDatasInstance->GetProperties();
|
||||
}
|
||||
|
||||
const std::vector<gd::String>& BehaviorMetadata::GetRequiredBehaviorTypes() const {
|
||||
requiredBehaviors.clear();
|
||||
for (auto& property : Get().GetProperties()) {
|
||||
|
@@ -18,6 +18,7 @@ class BehaviorsSharedData;
|
||||
class MultipleInstructionMetadata;
|
||||
class InstructionMetadata;
|
||||
class ExpressionMetadata;
|
||||
class PropertyDescriptor;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
@@ -204,6 +205,8 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \note The requirement may vary depending on the platform: Most of the time,
|
||||
* the include file contains the declaration of the behavior.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
BehaviorMetadata& SetIncludeFile(const gd::String& includeFile) override;
|
||||
|
||||
@@ -302,6 +305,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
*/
|
||||
gd::Behavior& Get() const;
|
||||
|
||||
/**
|
||||
* \brief Called when the IDE wants to know about the custom properties of the
|
||||
* behavior.
|
||||
*
|
||||
* \return a std::map with properties names as key.
|
||||
* \see gd::PropertyDescriptor
|
||||
*/
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const;
|
||||
|
||||
/**
|
||||
* \brief Return the associated gd::BehaviorsSharedData, handling behavior
|
||||
* shared data, if any (nullptr if none).
|
||||
@@ -311,6 +323,15 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
*/
|
||||
gd::BehaviorsSharedData* GetSharedDataInstance() const;
|
||||
|
||||
/**
|
||||
* \brief Called when the IDE wants to know about the custom shared properties
|
||||
* of the behavior.
|
||||
*
|
||||
* \return a std::map with properties names as key.
|
||||
* \see gd::PropertyDescriptor
|
||||
*/
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetSharedProperties() const;
|
||||
|
||||
/**
|
||||
* \brief Return a reference to a map containing the names of the actions
|
||||
* (as keys) and the metadata associated with (as values).
|
||||
@@ -357,6 +378,8 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
|
||||
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
|
||||
std::shared_ptr<gd::Behavior> instance;
|
||||
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance;
|
||||
|
||||
static const std::map<gd::String, gd::PropertyDescriptor> badProperties;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -65,6 +65,8 @@ class GD_CORE_API EffectMetadata {
|
||||
|
||||
/**
|
||||
* \brief Clear any existing include file and add the specified include file.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
EffectMetadata& SetIncludeFile(const gd::String& includeFile);
|
||||
|
||||
|
@@ -288,6 +288,8 @@ class GD_CORE_API ExpressionMetadata : public gd::AbstractFunctionMetadata {
|
||||
|
||||
/**
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
ExpressionMetadata& SetIncludeFile(
|
||||
const gd::String& includeFile) override {
|
||||
|
@@ -494,6 +494,8 @@ class GD_CORE_API InstructionMetadata : public gd::AbstractFunctionMetadata {
|
||||
|
||||
/**
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
InstructionMetadata &SetIncludeFile(const gd::String &includeFile) override {
|
||||
codeExtraInformation.includeFiles.clear();
|
||||
|
@@ -142,6 +142,8 @@ public:
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \note The requirement may vary depending on the platform: Most of the time,
|
||||
* the include file contains the declaration of the behavior.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
virtual InstructionOrExpressionContainerMetadata &
|
||||
SetIncludeFile(const gd::String &includeFile) = 0;
|
||||
|
@@ -150,6 +150,10 @@ class GD_CORE_API MultipleInstructionMetadata : public AbstractFunctionMetadata
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
MultipleInstructionMetadata &SetIncludeFile(const gd::String &includeFile) override {
|
||||
if (expression)
|
||||
expression->SetIncludeFile(includeFile);
|
||||
|
@@ -264,6 +264,8 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
|
||||
* \brief Erase any existing include file and add the specified include.
|
||||
* \note The requirement may vary depending on the platform: Most of the time,
|
||||
* the include file contains the declaration of the object.
|
||||
* \deprecated Use `AddIncludeFile` instead as clearing the list is more
|
||||
* error prone.
|
||||
*/
|
||||
ObjectMetadata& SetIncludeFile(const gd::String& includeFile) override;
|
||||
|
||||
|
@@ -285,22 +285,6 @@ class GD_CORE_API PlatformExtension {
|
||||
std::shared_ptr<gd::Behavior> instance,
|
||||
std::shared_ptr<gd::BehaviorsSharedData> sharedDatasInstance);
|
||||
|
||||
/**
|
||||
* \brief Declare a new events based behavior as being part of the extension.
|
||||
*
|
||||
* \param name The name of the behavior
|
||||
* \param fullname The user friendly name of the behavior
|
||||
* \param description The user friendly description of the behavior
|
||||
* \param group The behavior category label
|
||||
* \param icon The icon of the behavior.
|
||||
*/
|
||||
gd::BehaviorMetadata& AddEventsBasedBehavior(
|
||||
const gd::String& name_,
|
||||
const gd::String& fullname_,
|
||||
const gd::String& description_,
|
||||
const gd::String& group_,
|
||||
const gd::String& icon_);
|
||||
|
||||
/**
|
||||
* \brief Declare a new effect as being part of the extension.
|
||||
* \param name The internal name of the effect (also called effect type).
|
||||
|
@@ -86,8 +86,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
void OnVisitOperatorNode(OperatorNode& node) override {
|
||||
ReportAnyError(node);
|
||||
|
||||
// The "required" type ("parentType") will be used when visiting the first operand.
|
||||
// Note that it may be refined thanks to this first operand (see later).
|
||||
node.leftHandSide->Visit(*this);
|
||||
const Type leftType = childType;
|
||||
const Type leftType = childType; // Store the type of the first operand.
|
||||
|
||||
if (leftType == Type::Number) {
|
||||
if (node.op == ' ') {
|
||||
@@ -120,15 +122,19 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
node.rightHandSide->location);
|
||||
}
|
||||
|
||||
parentType = leftType;
|
||||
// The "required" type ("parentType") of the second operator is decided by:
|
||||
// - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
|
||||
// - the first operand.
|
||||
parentType = ShouldTypeBeRefined(parentType) ? leftType : parentType;
|
||||
node.rightHandSide->Visit(*this);
|
||||
const Type rightType = childType;
|
||||
|
||||
// The type is decided by the first operand, unless it can (`number|string`)
|
||||
// or should (`unknown`) be refined, in which case we go for the right
|
||||
// operand (which got visited knowing the type of the first operand, so it's
|
||||
// The type of the overall operator ("childType") is decided by:
|
||||
// - the parent type. Unless it can (`number|string`) or should (`unknown`) be refined, then:
|
||||
// - the first operand. Unless it can (`number|string`) or should (`unknown`) be refined, then:
|
||||
// - the right operand (which got visited knowing the type of the first operand, so it's
|
||||
// equal or strictly more precise than the left operand).
|
||||
childType = (leftType == Type::Unknown || leftType == Type::NumberOrString) ? leftType : rightType;
|
||||
childType = ShouldTypeBeRefined(parentType) ? (ShouldTypeBeRefined(leftType) ? leftType : rightType) : parentType;
|
||||
}
|
||||
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
|
||||
ReportAnyError(node);
|
||||
@@ -395,6 +401,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
}
|
||||
}
|
||||
|
||||
static bool ShouldTypeBeRefined(Type type) {
|
||||
return (type == Type::Unknown || type == Type::NumberOrString);
|
||||
}
|
||||
|
||||
static Type StringToType(const gd::String &type);
|
||||
static const gd::String &TypeToString(Type type);
|
||||
static const gd::String unknownTypeString;
|
||||
|
@@ -39,7 +39,7 @@ ObjectAssetSerializer::GetObjectExtensionName(const gd::Object &object) {
|
||||
void ObjectAssetSerializer::SerializeTo(
|
||||
gd::Project &project, const gd::Object &object,
|
||||
const gd::String &objectFullName, SerializerElement &element,
|
||||
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap) {
|
||||
std::vector<gd::String> &usedResourceNames) {
|
||||
auto cleanObject = object.Clone();
|
||||
cleanObject->GetVariables().Clear();
|
||||
cleanObject->GetEffects().Clear();
|
||||
@@ -49,11 +49,6 @@ void ObjectAssetSerializer::SerializeTo(
|
||||
|
||||
gd::String extensionName = GetObjectExtensionName(*cleanObject);
|
||||
|
||||
std::map<gd::String, gd::String> resourcesNameReverseMap;
|
||||
gd::ObjectAssetSerializer::RenameObjectResourceFiles(
|
||||
project, *cleanObject, "", objectFullName, resourcesFileNameMap,
|
||||
resourcesNameReverseMap);
|
||||
|
||||
element.SetAttribute("id", "");
|
||||
element.SetAttribute("name", "");
|
||||
element.SetAttribute("license", "");
|
||||
@@ -86,23 +81,16 @@ void ObjectAssetSerializer::SerializeTo(
|
||||
auto &resourcesManager = project.GetResourcesManager();
|
||||
gd::ResourcesInUseHelper resourcesInUse(resourcesManager);
|
||||
cleanObject->GetConfiguration().ExposeResources(resourcesInUse);
|
||||
for (auto &&newResourceName : resourcesInUse.GetAllResources()) {
|
||||
if (newResourceName.length() == 0) {
|
||||
for (auto &&resourceName : resourcesInUse.GetAllResources()) {
|
||||
if (resourceName.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
auto &resource = resourcesManager.GetResource(
|
||||
resourcesNameReverseMap.find(newResourceName) !=
|
||||
resourcesNameReverseMap.end()
|
||||
? resourcesNameReverseMap[newResourceName]
|
||||
: newResourceName);
|
||||
usedResourceNames.push_back(resourceName);
|
||||
auto &resource = resourcesManager.GetResource(resourceName);
|
||||
SerializerElement &resourceElement = resourcesElement.AddChild("resource");
|
||||
resource.SerializeTo(resourceElement);
|
||||
// Override name and file because the project and the asset don't use the
|
||||
// same one.
|
||||
resourceElement.SetAttribute("kind", resource.GetKind());
|
||||
resourceElement.SetAttribute("name", newResourceName);
|
||||
auto &oldFilePath = resource.GetFile();
|
||||
resourceElement.SetAttribute("file", newResourceName);
|
||||
resourceElement.SetAttribute("name", resource.GetName());
|
||||
}
|
||||
|
||||
SerializerElement &requiredExtensionsElement =
|
||||
@@ -120,127 +108,4 @@ void ObjectAssetSerializer::SerializeTo(
|
||||
objectAssetElement.AddChild("customization");
|
||||
customizationElement.ConsiderAsArrayOf("empty");
|
||||
}
|
||||
|
||||
void ObjectAssetSerializer::RenameObjectResourceFiles(
|
||||
gd::Project &project, gd::Object &object,
|
||||
const gd::String &destinationDirectory, const gd::String &objectFullName,
|
||||
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap,
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap) {
|
||||
std::map<gd::String, gd::String> cleanedResourcesFileNameMap;
|
||||
gd::AssetResourcePathCleaner assetResourcePathCleaner(
|
||||
project.GetResourcesManager(), cleanedResourcesFileNameMap,
|
||||
resourcesNameReverseMap);
|
||||
object.GetConfiguration().ExposeResources(assetResourcePathCleaner);
|
||||
|
||||
// Use asset store script naming conventions for sprite resource files.
|
||||
if (object.GetConfiguration().GetType() == "Sprite") {
|
||||
gd::SpriteObject &spriteConfiguration =
|
||||
dynamic_cast<gd::SpriteObject &>(object.GetConfiguration());
|
||||
/// Resource files may be duplicated because the files names allow the
|
||||
/// asset store script to rebuild the animations.
|
||||
std::map<gd::String, std::vector<gd::String>> normalizedFileNames;
|
||||
|
||||
for (std::size_t animationIndex = 0;
|
||||
animationIndex < spriteConfiguration.GetAnimationsCount();
|
||||
animationIndex++) {
|
||||
auto &animation = spriteConfiguration.GetAnimation(animationIndex);
|
||||
auto &direction = animation.GetDirection(0);
|
||||
|
||||
const gd::String &animationName =
|
||||
animation.GetName().empty()
|
||||
? gd::String::From(animationIndex)
|
||||
: animation.GetName().FindAndReplace("_", " ", true);
|
||||
|
||||
// Search frames that share the same resource.
|
||||
std::map<gd::String, std::vector<int>> frameIndexes;
|
||||
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
|
||||
frameIndex++) {
|
||||
auto &frame = direction.GetSprite(frameIndex);
|
||||
|
||||
if (frameIndexes.find(frame.GetImageName()) == frameIndexes.end()) {
|
||||
std::vector<int> emptyVector;
|
||||
frameIndexes[frame.GetImageName()] = emptyVector;
|
||||
}
|
||||
auto &indexes = frameIndexes[frame.GetImageName()];
|
||||
indexes.push_back(frameIndex);
|
||||
}
|
||||
|
||||
for (std::size_t frameIndex = 0; frameIndex < direction.GetSpritesCount();
|
||||
frameIndex++) {
|
||||
auto &frame = direction.GetSprite(frameIndex);
|
||||
auto oldName = frame.GetImageName();
|
||||
|
||||
if (normalizedFileNames.find(oldName) == normalizedFileNames.end()) {
|
||||
std::vector<gd::String> value;
|
||||
normalizedFileNames[oldName] = value;
|
||||
}
|
||||
|
||||
gd::String newName = objectFullName;
|
||||
if (spriteConfiguration.GetAnimationsCount() > 1) {
|
||||
newName += "_" + animationName;
|
||||
}
|
||||
if (direction.GetSpritesCount() > 1) {
|
||||
newName += "_";
|
||||
auto &indexes = frameIndexes[frame.GetImageName()];
|
||||
for (size_t i = 0; i < indexes.size(); i++) {
|
||||
newName += gd::String::From(indexes.at(i) + 1);
|
||||
if (i < indexes.size() - 1) {
|
||||
newName += ";";
|
||||
}
|
||||
}
|
||||
}
|
||||
gd::String extension = oldName.substr(oldName.find_last_of("."));
|
||||
newName += extension;
|
||||
|
||||
frame.SetImageName(newName);
|
||||
auto &newNames = normalizedFileNames[oldName];
|
||||
if (find(newNames.begin(), newNames.end(), newName) == newNames.end()) {
|
||||
newNames.push_back(newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::map<gd::String, gd::String>::const_iterator it =
|
||||
cleanedResourcesFileNameMap.begin();
|
||||
it != cleanedResourcesFileNameMap.end(); ++it) {
|
||||
if (!it->first.empty()) {
|
||||
const gd::String &originFile = it->first;
|
||||
const gd::String &destinationFile = it->second;
|
||||
|
||||
std::vector<gd::String> value;
|
||||
for (auto &&normalizedFileName : normalizedFileNames[destinationFile]) {
|
||||
value.push_back(normalizedFileName);
|
||||
}
|
||||
resourcesFileNameMap[originFile] = value;
|
||||
}
|
||||
}
|
||||
auto clonedResourcesNameReverseMap = resourcesNameReverseMap;
|
||||
resourcesNameReverseMap.clear();
|
||||
for (std::map<gd::String, gd::String>::const_iterator it =
|
||||
clonedResourcesNameReverseMap.begin();
|
||||
it != clonedResourcesNameReverseMap.end(); ++it) {
|
||||
if (!it->first.empty()) {
|
||||
const gd::String& newResourceName = it->first;
|
||||
const gd::String& oldResourceName = it->second;
|
||||
for (auto&& normalizedFileName : normalizedFileNames[newResourceName]) {
|
||||
resourcesNameReverseMap[normalizedFileName] =
|
||||
oldResourceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (std::map<gd::String, gd::String>::const_iterator it =
|
||||
cleanedResourcesFileNameMap.begin();
|
||||
it != cleanedResourcesFileNameMap.end(); ++it) {
|
||||
if (!it->first.empty()) {
|
||||
const gd::String &originFile = it->first;
|
||||
const gd::String &destinationFile = it->second;
|
||||
|
||||
std::vector<gd::String> value;
|
||||
value.push_back(destinationFile);
|
||||
resourcesFileNameMap[originFile] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace gd
|
||||
|
@@ -39,27 +39,18 @@ public:
|
||||
* \param object The object to serialize as an asset.
|
||||
* \param objectFullName The object name with spaces instead of PascalCase.
|
||||
* \param element The element where the asset is serialize.
|
||||
* \param resourcesFileNameMap The map from project resource file paths to
|
||||
* asset resource file paths. Resource files of Sprite objects may be
|
||||
* duplicated because the files names allow the asset store script to rebuild
|
||||
* the animations.
|
||||
* \param usedResourceNames Return the names of the resources used by the asset.
|
||||
*/
|
||||
static void
|
||||
SerializeTo(gd::Project &project, const gd::Object &object,
|
||||
const gd::String &objectFullName, SerializerElement &element,
|
||||
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap);
|
||||
std::vector<gd::String> &usedResourceNames);
|
||||
|
||||
~ObjectAssetSerializer(){};
|
||||
|
||||
private:
|
||||
ObjectAssetSerializer(){};
|
||||
|
||||
static void RenameObjectResourceFiles(
|
||||
gd::Project &project, gd::Object &object,
|
||||
const gd::String &destinationDirectory, const gd::String &objectFullName,
|
||||
std::map<gd::String, std::vector<gd::String>> &resourcesFileNameMap,
|
||||
std::map<gd::String, gd::String> &resourcesNameReverseMap);
|
||||
|
||||
static gd::String GetObjectExtensionName(const gd::Object &object);
|
||||
};
|
||||
|
||||
|
@@ -4,8 +4,6 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include <iostream>
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
|
@@ -13,12 +13,14 @@
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Log.h"
|
||||
#include "GDCore/Project/CustomConfigurationHelper.h"
|
||||
#include "GDCore/Project/InitialInstance.h"
|
||||
|
||||
using namespace gd;
|
||||
|
||||
void CustomObjectConfiguration::Init(const gd::CustomObjectConfiguration& objectConfiguration) {
|
||||
project = objectConfiguration.project;
|
||||
objectContent = objectConfiguration.objectContent;
|
||||
animations = objectConfiguration.animations;
|
||||
|
||||
// There is no default copy for a map of unique_ptr like childObjectConfigurations.
|
||||
childObjectConfigurations.clear();
|
||||
@@ -88,23 +90,38 @@ bool CustomObjectConfiguration::UpdateProperty(const gd::String& propertyName,
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor>
|
||||
CustomObjectConfiguration::GetInitialInstanceProperties(
|
||||
const gd::InitialInstance& instance,
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) {
|
||||
return std::map<gd::String, gd::PropertyDescriptor>();
|
||||
const gd::InitialInstance &initialInstance, gd::Project &project,
|
||||
gd::Layout &scene) {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
if (!animations.HasNoAnimations()) {
|
||||
properties["animation"] =
|
||||
gd::PropertyDescriptor(
|
||||
gd::String::From(initialInstance.GetRawDoubleProperty("animation")))
|
||||
.SetLabel(_("Animation"))
|
||||
.SetType("number");
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool CustomObjectConfiguration::UpdateInitialInstanceProperty(
|
||||
gd::InitialInstance& instance,
|
||||
const gd::String& name,
|
||||
const gd::String& value,
|
||||
gd::Project& project,
|
||||
gd::Layout& scene) {
|
||||
return false;
|
||||
gd::InitialInstance &initialInstance, const gd::String &name,
|
||||
const gd::String &value, gd::Project &project, gd::Layout &scene) {
|
||||
if (name == "animation") {
|
||||
initialInstance.SetRawDoubleProperty(
|
||||
"animation", std::max(0, value.empty() ? 0 : value.To<int>()));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const {
|
||||
element.AddChild("content") = objectContent;
|
||||
|
||||
if (!animations.HasNoAnimations()) {
|
||||
auto &animatableElement = element.AddChild("animatable");
|
||||
animations.SerializeTo(animatableElement);
|
||||
}
|
||||
|
||||
auto &childrenContentElement = element.AddChild("childrenContent");
|
||||
for (auto &pair : childObjectConfigurations) {
|
||||
auto &childName = pair.first;
|
||||
@@ -116,6 +133,12 @@ void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const
|
||||
void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
|
||||
const SerializerElement& element) {
|
||||
objectContent = element.GetChild("content");
|
||||
|
||||
if (element.HasChild("animatable")) {
|
||||
auto &animatableElement = element.GetChild("animatable");
|
||||
animations.UnserializeFrom(animatableElement);
|
||||
}
|
||||
|
||||
auto &childrenContentElement = element.GetChild("childrenContent");
|
||||
for (auto &pair : childrenContentElement.GetAllChildren()) {
|
||||
auto &childName = pair.first;
|
||||
@@ -126,6 +149,8 @@ void CustomObjectConfiguration::DoUnserializeFrom(Project& project,
|
||||
}
|
||||
|
||||
void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& worker) {
|
||||
animations.ExposeResources(worker);
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties = GetProperties();
|
||||
|
||||
for (auto& property : properties) {
|
||||
@@ -178,3 +203,11 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
|
||||
configuration.ExposeResources(worker);
|
||||
}
|
||||
}
|
||||
|
||||
const SpriteAnimationList& CustomObjectConfiguration::GetAnimations() const {
|
||||
return animations;
|
||||
}
|
||||
|
||||
SpriteAnimationList& CustomObjectConfiguration::GetAnimations() {
|
||||
return animations;
|
||||
}
|
||||
|
@@ -3,8 +3,7 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#ifndef GDCORE_CUSTOMOBJECTCONFIGURATION_H
|
||||
#define GDCORE_CUSTOMOBJECTCONFIGURATION_H
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
|
||||
@@ -16,7 +15,7 @@
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/Serializer.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteAnimationList.h"
|
||||
|
||||
using namespace gd;
|
||||
|
||||
@@ -72,7 +71,17 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
|
||||
|
||||
gd::ObjectConfiguration &GetChildObjectConfiguration(const gd::String& objectName);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* \brief Return the animation configuration for Animatable custom objects.
|
||||
*/
|
||||
const SpriteAnimationList& GetAnimations() const;
|
||||
|
||||
/**
|
||||
* @brief Return the animation configuration for Animatable custom objects.
|
||||
*/
|
||||
SpriteAnimationList& GetAnimations();
|
||||
|
||||
protected:
|
||||
void DoSerializeTo(SerializerElement& element) const override;
|
||||
void DoUnserializeFrom(Project& project, const SerializerElement& element) override;
|
||||
|
||||
@@ -84,6 +93,8 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
|
||||
|
||||
static gd::ObjectConfiguration badObjectConfiguration;
|
||||
|
||||
SpriteAnimationList animations;
|
||||
|
||||
/**
|
||||
* Initialize configuration using another configuration. Used by copy-ctor
|
||||
* and assign-op.
|
||||
@@ -95,6 +106,5 @@ class CustomObjectConfiguration : public gd::ObjectConfiguration {
|
||||
*/
|
||||
void Init(const gd::CustomObjectConfiguration& object);
|
||||
};
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_CUSTOMOBJECTCONFIGURATION_H
|
||||
} // namespace gd
|
||||
|
@@ -13,7 +13,10 @@ EventsBasedObject::EventsBasedObject()
|
||||
: AbstractEventsBasedEntity(
|
||||
"MyObject",
|
||||
gd::EventsFunctionsContainer::FunctionOwner::Object),
|
||||
ObjectsContainer() {
|
||||
ObjectsContainer(),
|
||||
isRenderedIn3D(false),
|
||||
isAnimatable(false),
|
||||
isTextContainer(false) {
|
||||
}
|
||||
|
||||
EventsBasedObject::~EventsBasedObject() {}
|
||||
@@ -30,6 +33,12 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
|
||||
if (isRenderedIn3D) {
|
||||
element.SetBoolAttribute("is3D", true);
|
||||
}
|
||||
if (isAnimatable) {
|
||||
element.SetBoolAttribute("isAnimatable", true);
|
||||
}
|
||||
if (isTextContainer) {
|
||||
element.SetBoolAttribute("isTextContainer", true);
|
||||
}
|
||||
|
||||
AbstractEventsBasedEntity::SerializeTo(element);
|
||||
SerializeObjectsTo(element.AddChild("objects"));
|
||||
@@ -40,6 +49,8 @@ void EventsBasedObject::UnserializeFrom(gd::Project& project,
|
||||
const SerializerElement& element) {
|
||||
defaultName = element.GetStringAttribute("defaultName");
|
||||
isRenderedIn3D = element.GetBoolAttribute("is3D", false);
|
||||
isAnimatable = element.GetBoolAttribute("isAnimatable", false);
|
||||
isTextContainer = element.GetBoolAttribute("isTextContainer", false);
|
||||
|
||||
AbstractEventsBasedEntity::UnserializeFrom(project, element);
|
||||
UnserializeObjectsFrom(project, element.GetChild("objects"));
|
||||
|
@@ -85,6 +85,32 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
|
||||
*/
|
||||
bool IsRenderedIn3D() const { return isRenderedIn3D; }
|
||||
|
||||
/**
|
||||
* \brief Declare an Animatable capability.
|
||||
*/
|
||||
EventsBasedObject& MarkAsAnimatable(bool isAnimatable_) {
|
||||
isAnimatable = isAnimatable_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return true if the object needs an Animatable capability.
|
||||
*/
|
||||
bool IsAnimatable() const { return isAnimatable; }
|
||||
|
||||
/**
|
||||
* \brief Declare a TextContainer capability.
|
||||
*/
|
||||
EventsBasedObject& MarkAsTextContainer(bool isTextContainer_) {
|
||||
isTextContainer = isTextContainer_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return true if the object needs a TextContainer capability.
|
||||
*/
|
||||
bool IsTextContainer() const { return isTextContainer; }
|
||||
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
|
||||
void UnserializeFrom(gd::Project& project,
|
||||
@@ -93,6 +119,8 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
|
||||
private:
|
||||
gd::String defaultName;
|
||||
bool isRenderedIn3D;
|
||||
bool isAnimatable;
|
||||
bool isTextContainer;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
namespace gd {
|
||||
|
||||
gd::String* InitialInstance::badStringProperyValue = NULL;
|
||||
gd::String* InitialInstance::badStringPropertyValue = NULL;
|
||||
|
||||
InitialInstance::InitialInstance()
|
||||
: objectName(""),
|
||||
@@ -34,6 +34,7 @@ InitialInstance::InitialInstance()
|
||||
depth(0),
|
||||
locked(false),
|
||||
sealed(false),
|
||||
keepRatio(true),
|
||||
persistentUuid(UUID::MakeUuid4()) {}
|
||||
|
||||
void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
@@ -58,6 +59,7 @@ void InitialInstance::UnserializeFrom(const SerializerElement& element) {
|
||||
SetLayer(element.GetStringAttribute("layer"));
|
||||
SetLocked(element.GetBoolAttribute("locked", false));
|
||||
SetSealed(element.GetBoolAttribute("sealed", false));
|
||||
SetShouldKeepRatio(element.GetBoolAttribute("keepRatio", false));
|
||||
|
||||
persistentUuid = element.GetStringAttribute("persistentUuid");
|
||||
if (persistentUuid.empty()) ResetPersistentUuid();
|
||||
@@ -120,6 +122,7 @@ void InitialInstance::SerializeTo(SerializerElement& element) const {
|
||||
if (HasCustomDepth()) element.SetAttribute("depth", GetCustomDepth());
|
||||
if (IsLocked()) element.SetAttribute("locked", IsLocked());
|
||||
if (IsSealed()) element.SetAttribute("sealed", IsSealed());
|
||||
if (ShouldKeepRatio()) element.SetAttribute("keepRatio", ShouldKeepRatio());
|
||||
|
||||
if (persistentUuid.empty()) persistentUuid = UUID::MakeUuid4();
|
||||
element.SetStringAttribute("persistentUuid", persistentUuid);
|
||||
@@ -188,10 +191,10 @@ double InitialInstance::GetRawDoubleProperty(const gd::String& name) const {
|
||||
|
||||
const gd::String& InitialInstance::GetRawStringProperty(
|
||||
const gd::String& name) const {
|
||||
if (!badStringProperyValue) badStringProperyValue = new gd::String("");
|
||||
if (!badStringPropertyValue) badStringPropertyValue = new gd::String("");
|
||||
|
||||
const auto& it = stringProperties.find(name);
|
||||
return it != stringProperties.end() ? it->second : *badStringProperyValue;
|
||||
return it != stringProperties.end() ? it->second : *badStringPropertyValue;
|
||||
}
|
||||
|
||||
void InitialInstance::SetRawDoubleProperty(const gd::String& name,
|
||||
|
@@ -206,6 +206,17 @@ class GD_CORE_API InitialInstance {
|
||||
*/
|
||||
void SetSealed(bool enable = true) { sealed = enable; }
|
||||
|
||||
/**
|
||||
* \brief Return true if the dimensions (width, height and depth) should keep
|
||||
* the same ratio.
|
||||
*/
|
||||
bool ShouldKeepRatio() const { return keepRatio; };
|
||||
|
||||
/**
|
||||
* \brief Define if instance's dimensions should keep the same ratio.
|
||||
*/
|
||||
void SetShouldKeepRatio(bool enable = true) { keepRatio = enable; }
|
||||
|
||||
///@}
|
||||
|
||||
/** \name Variable management
|
||||
@@ -340,11 +351,13 @@ class GD_CORE_API InitialInstance {
|
||||
gd::VariablesContainer initialVariables; ///< Instance specific variables
|
||||
bool locked; ///< True if the instance is locked
|
||||
bool sealed; ///< True if the instance is sealed
|
||||
bool keepRatio; ///< True if the instance's dimensions
|
||||
/// should keep the same ratio.
|
||||
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
|
||||
///< useful for hot reloading.
|
||||
/// useful for hot reloading.
|
||||
|
||||
static gd::String*
|
||||
badStringProperyValue; ///< Empty string returned by GetRawStringProperty
|
||||
badStringPropertyValue; ///< Empty string returned by GetRawStringProperty
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -120,9 +120,6 @@ class GD_CORE_API Object {
|
||||
*/
|
||||
const gd::String& GetType() const { return configuration->GetType(); }
|
||||
|
||||
/** \brief Shortcut to check if the object is a 3D object.
|
||||
*/
|
||||
bool Is3DObject() const { return configuration->Is3DObject(); }
|
||||
///@}
|
||||
|
||||
/** \name Behaviors management
|
||||
|
@@ -5,11 +5,8 @@
|
||||
*/
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
|
||||
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/CustomBehavior.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
@@ -20,7 +17,7 @@ namespace gd {
|
||||
|
||||
ObjectConfiguration::~ObjectConfiguration() {}
|
||||
|
||||
ObjectConfiguration::ObjectConfiguration(): is3DObject(false) {}
|
||||
ObjectConfiguration::ObjectConfiguration() {}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> ObjectConfiguration::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> nothing;
|
||||
|
@@ -63,20 +63,12 @@ class GD_CORE_API ObjectConfiguration {
|
||||
*/
|
||||
void SetType(const gd::String& type_) {
|
||||
type = type_;
|
||||
|
||||
// For now, as a shortcut, consider only the objects from the built-in 3D extension
|
||||
// to be 3D object.
|
||||
is3DObject = type.find("Scene3D::") == 0;
|
||||
}
|
||||
|
||||
/** \brief Return the type of the object.
|
||||
*/
|
||||
const gd::String& GetType() const { return type; }
|
||||
|
||||
/** \brief Shortcut to check if the object is a 3D object.
|
||||
*/
|
||||
bool Is3DObject() const { return is3DObject; }
|
||||
|
||||
/** \name Object properties
|
||||
* Reading and updating object configuration properties
|
||||
*/
|
||||
@@ -180,7 +172,6 @@ class GD_CORE_API ObjectConfiguration {
|
||||
protected:
|
||||
gd::String type; ///< Which type of object is represented by this
|
||||
///< configuration.
|
||||
bool is3DObject;
|
||||
|
||||
/**
|
||||
* \brief Derived object configuration can redefine this method to load
|
||||
|
@@ -102,21 +102,19 @@ std::unique_ptr<gd::Object> Project::CreateObject(
|
||||
behavior->SetDefaultBehavior(true);
|
||||
};
|
||||
|
||||
if (Project::HasEventsBasedObject(objectType)) {
|
||||
addDefaultBehavior("EffectCapability::EffectBehavior");
|
||||
addDefaultBehavior("ResizableCapability::ResizableBehavior");
|
||||
addDefaultBehavior("ScalableCapability::ScalableBehavior");
|
||||
addDefaultBehavior("FlippableCapability::FlippableBehavior");
|
||||
} else {
|
||||
auto& objectMetadata =
|
||||
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
|
||||
if (MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
|
||||
gd::LogWarning("Object: " + name + " has an unknown type: " + objectType);
|
||||
}
|
||||
for (auto& behaviorType : objectMetadata.GetDefaultBehaviors()) {
|
||||
auto &objectMetadata =
|
||||
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
|
||||
if (!MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
|
||||
for (auto &behaviorType : objectMetadata.GetDefaultBehaviors()) {
|
||||
addDefaultBehavior(behaviorType);
|
||||
}
|
||||
}
|
||||
// During project deserialization, event-based object metadata are not yet
|
||||
// generated. Default behaviors will be added by
|
||||
// MetadataDeclarationHelper::UpdateCustomObjectDefaultBehaviors
|
||||
else if (!project.HasEventsBasedObject(objectType)) {
|
||||
gd::LogWarning("Object: " + name + " has an unknown type: " + objectType);
|
||||
}
|
||||
|
||||
return std::move(object);
|
||||
}
|
||||
|
@@ -29,10 +29,15 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
|
||||
for (const gd::String& information : extraInformation) {
|
||||
extraInformationElement.AddChild("").SetStringValue(information);
|
||||
}
|
||||
element.AddChild("hidden").SetBoolValue(hidden);
|
||||
if (hidden) {
|
||||
element.AddChild("hidden").SetBoolValue(hidden);
|
||||
}
|
||||
if (deprecated) {
|
||||
element.AddChild("deprecated").SetBoolValue(deprecated);
|
||||
}
|
||||
if (advanced) {
|
||||
element.AddChild("advanced").SetBoolValue(advanced);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
@@ -64,6 +69,9 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
deprecated = element.HasChild("deprecated")
|
||||
? element.GetChild("deprecated").GetBoolValue()
|
||||
: false;
|
||||
advanced = element.HasChild("advanced")
|
||||
? element.GetChild("advanced").GetBoolValue()
|
||||
: false;
|
||||
}
|
||||
|
||||
void PropertyDescriptor::SerializeValuesTo(SerializerElement& element) const {
|
||||
|
@@ -31,14 +31,14 @@ class GD_CORE_API PropertyDescriptor {
|
||||
*/
|
||||
PropertyDescriptor(gd::String propertyValue)
|
||||
: currentValue(propertyValue), type("string"), label(""), hidden(false),
|
||||
deprecated(false),
|
||||
deprecated(false), advanced(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()) {}
|
||||
|
||||
/**
|
||||
* \brief Empty constructor creating an empty property to be displayed.
|
||||
*/
|
||||
PropertyDescriptor()
|
||||
: hidden(false), deprecated(false),
|
||||
: hidden(false), deprecated(false), advanced(false),
|
||||
measurementUnit(gd::MeasurementUnit::GetUndefined()){};
|
||||
|
||||
/**
|
||||
@@ -159,6 +159,19 @@ class GD_CORE_API PropertyDescriptor {
|
||||
*/
|
||||
bool IsDeprecated() const { return deprecated; }
|
||||
|
||||
/**
|
||||
* \brief Set if the property is marked as advanced.
|
||||
*/
|
||||
PropertyDescriptor& SetAdvanced(bool enable = true) {
|
||||
advanced = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check if the property is marked as advanced.
|
||||
*/
|
||||
bool IsAdvanced() const { return advanced; }
|
||||
|
||||
/** \name Serialization
|
||||
*/
|
||||
///@{
|
||||
@@ -197,6 +210,7 @@ class GD_CORE_API PropertyDescriptor {
|
||||
///< box.
|
||||
bool hidden;
|
||||
bool deprecated;
|
||||
bool advanced;
|
||||
gd::MeasurementUnit measurementUnit; //< The unit of measurement of the property vale.
|
||||
};
|
||||
|
||||
|
@@ -91,7 +91,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
|
||||
sprite.SetImageName("res1");
|
||||
anim.SetDirectionsCount(1);
|
||||
anim.GetDirection(0).AddSprite(sprite);
|
||||
spriteConfiguration.AddAnimation(anim);
|
||||
spriteConfiguration.GetAnimations().AddAnimation(anim);
|
||||
|
||||
gd::Object obj("myObject", "", spriteConfiguration.Clone());
|
||||
project.InsertObject(obj, 0);
|
||||
@@ -138,7 +138,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
|
||||
sprite.SetImageName("res1");
|
||||
anim.SetDirectionsCount(1);
|
||||
anim.GetDirection(0).AddSprite(sprite);
|
||||
spriteConfiguration.AddAnimation(anim);
|
||||
spriteConfiguration.GetAnimations().AddAnimation(anim);
|
||||
|
||||
gd::Object obj("myObject", "", spriteConfiguration.Clone());
|
||||
layout.InsertObject(obj, 0);
|
||||
@@ -437,7 +437,7 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
|
||||
sprite.SetImageName("res1");
|
||||
anim.SetDirectionsCount(1);
|
||||
anim.GetDirection(0).AddSprite(sprite);
|
||||
spriteConfiguration.AddAnimation(anim);
|
||||
spriteConfiguration.GetAnimations().AddAnimation(anim);
|
||||
|
||||
gd::Object obj("myObject", "", spriteConfiguration.Clone());
|
||||
layout.InsertObject(obj, 0);
|
||||
|
@@ -30,6 +30,21 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
|
||||
layout1.GetVariables().InsertNew("MySceneBooleanVariable").SetBool(true);
|
||||
layout1.GetVariables().InsertNew("MySceneStructureVariable").GetChild("MyChild");
|
||||
layout1.GetVariables().InsertNew("MySceneStructureVariable2").GetChild("MyChild");
|
||||
layout1.GetVariables().InsertNew("MySceneEmptyArrayVariable").CastTo(gd::Variable::Type::Array);
|
||||
{
|
||||
auto& variable = layout1.GetVariables().InsertNew("MySceneNumberArrayVariable");
|
||||
variable.CastTo(gd::Variable::Type::Array);
|
||||
variable.PushNew().SetValue(1);
|
||||
variable.PushNew().SetValue(2);
|
||||
variable.PushNew().SetValue(3);
|
||||
}
|
||||
{
|
||||
auto& variable = layout1.GetVariables().InsertNew("MySceneStringArrayVariable");
|
||||
variable.CastTo(gd::Variable::Type::Array);
|
||||
variable.PushNew().SetString("1");
|
||||
variable.PushNew().SetString("2");
|
||||
variable.PushNew().SetString("3");
|
||||
}
|
||||
|
||||
auto &mySpriteObject = layout1.InsertNewObject(project, "MyExtension::Sprite", "MySpriteObject", 0);
|
||||
mySpriteObject.GetVariables().InsertNew("MyNumberVariable").SetValue(123);
|
||||
@@ -1295,6 +1310,221 @@ TEST_CASE("ExpressionCodeGenerator", "[common][events]") {
|
||||
REQUIRE(output == "getVariableForObject(MySpriteObject, MyOtherSpriteObject).getChild(\"Child\").getChild(\"Grandchild\")");
|
||||
}
|
||||
}
|
||||
SECTION("Type conversions (valid operators with variables having different types than the expression)") {
|
||||
SECTION("Expression/parent type is 'string'") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("\"You have \" + MySceneVariable + \" points\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "\"You have \" + getLayoutVariable(MySceneVariable).getAsString() + \" points\"");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneVariable + MySceneStringVariable");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsString() + getLayoutVariable(MySceneStringVariable).getAsString()");
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'string' (with an unknown variable)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("\"You have \" + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \" points\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "\"You have \" + getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsString() + \" points\"");
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'string' (2 number variables)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneVariable + MySceneVariable2 + \"world\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsString() + getLayoutVariable(MySceneVariable2).getAsString() + \"world\"");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneVariable + MySceneVariable2 + MySceneStringVariable");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneVariable).getAsString() + getLayoutVariable(MySceneVariable2).getAsString() + getLayoutVariable(MySceneStringVariable).getAsString()");
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'string' (array variable)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("\"hello\" + MySceneNumberArrayVariable[2] + \"world\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneNumberArrayVariable).getChild(2).getAsString() + \"world\"");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("\"hello\" + MySceneEmptyArrayVariable[2] + \"world\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneEmptyArrayVariable).getChild(2).getAsString() + \"world\"");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Expression/parent type is 'number'") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("123 + MySceneVariable + 456");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneVariable).getAsNumber() + 456");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStringVariable + MySceneVariable");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneVariable).getAsNumber()");
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'string' (with an unknown variable)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("123 + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 456");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneStructureVariable).getChild(\"MyChild\").getChild(\"CantKnownTheTypeSoStayGeneric\").getAsNumber() + 456");
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'number' (2 string variables)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStringVariable + MySceneStringVariable + 456");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneStringVariable).getAsNumber() + 456");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStringVariable + MySceneStringVariable + MySceneVariable");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneStringVariable).getAsNumber() + getLayoutVariable(MySceneVariable).getAsNumber()");
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'number' (array variable)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("123 + MySceneNumberArrayVariable[2] + 456");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneNumberArrayVariable).getChild(2).getAsNumber() + 456");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("123 + MySceneEmptyArrayVariable[2] + 456");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("number",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "123 + getLayoutVariable(MySceneEmptyArrayVariable).getChild(2).getAsNumber() + 456");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SECTION("Multiple type conversions in sub expressions or same expression") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("\"hello\" + MySceneNumberArrayVariable[2 + MySceneStringVariable] + \"world\" + MySceneVariable + \"world 2\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneNumberArrayVariable).getChild(2 + getLayoutVariable(MySceneStringVariable).getAsNumber()).getAsString() + \"world\" + getLayoutVariable(MySceneVariable).getAsString() + \"world 2\"");
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("\"hello\" + MySceneNumberArrayVariable[\"foo\" + MySceneVariable + \"bar\"] + \"world\" + MySceneVariable + \"world 2\"");
|
||||
gd::ExpressionCodeGenerator expressionCodeGenerator("string",
|
||||
"",
|
||||
codeGenerator,
|
||||
context);
|
||||
|
||||
REQUIRE(node);
|
||||
node->Visit(expressionCodeGenerator);
|
||||
REQUIRE(expressionCodeGenerator.GetOutput() == "\"hello\" + getLayoutVariable(MySceneNumberArrayVariable).getChild(\"foo\" + getLayoutVariable(MySceneVariable).getAsString() + \"bar\").getAsString() + \"world\" + getLayoutVariable(MySceneVariable).getAsString() + \"world 2\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
SECTION("Mixed test (1)") {
|
||||
{
|
||||
auto node = parser.ParseExpression("-+-MyExtension::MouseX(,)");
|
||||
|
@@ -2954,6 +2954,238 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Valid operators with variables having different types than the expression") {
|
||||
SECTION("Expression/parent type is 'string'") {
|
||||
// A trivial test (everything is a string).
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneStringVariable + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A string concatenated with a number variable (will have to be casted to a string in code generation)
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A string concatenated with a number variable (will have to be casted to a string in code generation)
|
||||
// and then with a string again.
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A string concatenated with an unknown variable (will have to be casted to a string in code generation)
|
||||
// and then with a string again.
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'number'") {
|
||||
// A trivial test (everything is a string).
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneNumberVariable + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A number concatenated with a string variable (will have to be casted to a number in code generation)
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStringVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A number concatenated with a string variable (will have to be casted to a number in code generation)
|
||||
// and then with a number again.
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStringVariable + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A number concatenated with an unknown variable (will have to be casted to a number in code generation)
|
||||
// and then with a number again.
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'number|string'") {
|
||||
SECTION("Expression/parent inferred type is 'string'") {
|
||||
// A trivial test (everything is a string).
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneStringVariable + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A string concatenated with a number variable (will have to be casted to a string in code generation)
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A string concatenated with a number variable (will have to be casted to a string in code generation)
|
||||
// and then with a string again.
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A string concatenated with an unknown variable (will have to be casted to a string in code generation)
|
||||
// and then with a string again.
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent inferred type is 'number'") {
|
||||
// A trivial test (everything is a string).
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneNumberVariable + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A number concatenated with a string variable (will have to be casted to a number in code generation)
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStringVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A number concatenated with a string variable (will have to be casted to a number in code generation)
|
||||
// and then with a number again.
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStringVariable + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
// A number concatenated with an unknown variable (will have to be casted to a number in code generation)
|
||||
// and then with a number again.
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStructureVariable.MyChild.CantKnownTheTypeSoStayGeneric + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Invalid operators with variables having different types than the expression") {
|
||||
// Try to do a sum between numbers in a string expression
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + 2 + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a number, but a text was expected (in quotes).");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 38);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 39);
|
||||
}
|
||||
// Try to do a sum between numbers in a number|string expression (that is inferred as a string with the first operand)
|
||||
{
|
||||
auto node = parser.ParseExpression("\"You have \" + MySceneNumberVariable + 2 + \" points\"");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a number, but a text was expected (in quotes).");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 38);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 39);
|
||||
}
|
||||
// Try to do a string concatenation in a number expression
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStringVariable + \"hello\" + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a text, but a number was expected.");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 30);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 37);
|
||||
}
|
||||
// Try to do a string concatenation in a number|string expression (that is inferred as a number with the first operand)
|
||||
{
|
||||
auto node = parser.ParseExpression("123 + MySceneStringVariable + \"hello\" + 456");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
REQUIRE(validator.GetFatalErrors().size() == 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "You entered a text, but a number was expected.");
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetStartPosition() == 30);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetEndPosition() == 37);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Valid function call with object variable") {
|
||||
{
|
||||
// Note that in this test we need to use an expression with "objectvar",
|
||||
|
@@ -71,19 +71,17 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
|
||||
frame.SetImageName("assets/Idle.png");
|
||||
direction.AddSprite(frame);
|
||||
|
||||
spriteConfiguration->AddAnimation(animation);
|
||||
spriteConfiguration->GetAnimations().AddAnimation(animation);
|
||||
}
|
||||
|
||||
SerializerElement assetElement;
|
||||
std::map<gd::String, std::vector<gd::String>> resourcesFileNameMap;
|
||||
std::vector<gd::String> usedResourceNames;
|
||||
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
|
||||
assetElement, resourcesFileNameMap);
|
||||
assetElement, usedResourceNames);
|
||||
|
||||
// This mapping is used to copy resource files.
|
||||
REQUIRE(resourcesFileNameMap.find("assets/Idle.png") !=
|
||||
resourcesFileNameMap.end());
|
||||
REQUIRE(resourcesFileNameMap["assets/Idle.png"].size() == 1);
|
||||
REQUIRE(resourcesFileNameMap["assets/Idle.png"][0] == "Idle.png");
|
||||
// This list is used to copy resource files.
|
||||
REQUIRE(usedResourceNames.size() == 1);
|
||||
REQUIRE(usedResourceNames[0] == "assets/Idle.png");
|
||||
|
||||
// Check that the project is left untouched.
|
||||
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
|
||||
@@ -113,8 +111,8 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
|
||||
REQUIRE(resourcesElement.GetChildrenCount() == 1);
|
||||
{
|
||||
auto &resourceElement = resourcesElement.GetChild(0);
|
||||
REQUIRE(resourceElement.GetStringAttribute("name") == "Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("file") == "Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("name") == "assets/Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("file") == "assets/Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
|
||||
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
|
||||
}
|
||||
@@ -144,6 +142,6 @@ TEST_CASE("ObjectAssetSerializer", "[common]") {
|
||||
spritesElement.ConsiderAsArrayOf("sprite");
|
||||
REQUIRE(spritesElement.GetChildrenCount() == 1);
|
||||
auto &spriteElement = spritesElement.GetChild(0);
|
||||
REQUIRE(spriteElement.GetStringAttribute("image") == "Idle.png");
|
||||
REQUIRE(spriteElement.GetStringAttribute("image") == "assets/Idle.png");
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ void SetupSpriteConfiguration(gd::ObjectConfiguration &configuration) {
|
||||
REQUIRE(spriteConfiguration != nullptr);
|
||||
gd::Animation animation;
|
||||
animation.SetName("Idle");
|
||||
spriteConfiguration->AddAnimation(animation);
|
||||
spriteConfiguration->GetAnimations().AddAnimation(animation);
|
||||
};
|
||||
|
||||
gd::Object &SetupProjectWithSprite(gd::Project &project,
|
||||
@@ -83,9 +83,9 @@ void CheckSpriteConfigurationInProjectElement(
|
||||
void CheckSpriteConfiguration(gd::ObjectConfiguration &configuration) {
|
||||
auto *spriteConfiguration = dynamic_cast<gd::SpriteObject *>(&configuration);
|
||||
REQUIRE(spriteConfiguration);
|
||||
REQUIRE(spriteConfiguration->GetAnimationsCount() == 1);
|
||||
REQUIRE(spriteConfiguration->GetAnimations().GetAnimationsCount() == 1);
|
||||
|
||||
auto &animation = spriteConfiguration->GetAnimation(0);
|
||||
auto &animation = spriteConfiguration->GetAnimations().GetAnimation(0);
|
||||
REQUIRE(animation.GetName() == "Idle");
|
||||
};
|
||||
|
||||
|
1
Extensions/.gitignore
vendored
1
Extensions/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
Spine/pixi-spine
|
@@ -154,7 +154,23 @@ namespace gdjs {
|
||||
* @return The Z position of the rendered object.
|
||||
*/
|
||||
getDrawableZ(): float {
|
||||
return this.getZ();
|
||||
return this._z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bottom Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMinZ(): number {
|
||||
return this.getDrawableZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.getDrawableZ() + this.getDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,8 +24,8 @@ namespace gdjs {
|
||||
|
||||
updatePosition() {
|
||||
this._threeObject3D.position.set(
|
||||
this._object.x + this._object.getWidth() / 2,
|
||||
this._object.y + this._object.getHeight() / 2,
|
||||
this._object.getX() + this._object.getWidth() / 2,
|
||||
this._object.getY() + this._object.getHeight() / 2,
|
||||
this._object.getZ() + this._object.getDepth() / 2
|
||||
);
|
||||
}
|
||||
|
@@ -103,6 +103,27 @@ namespace gdjs {
|
||||
flipZ(enable: boolean): void;
|
||||
|
||||
isFlippedZ(): boolean;
|
||||
|
||||
/**
|
||||
* Return the bottom Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMinZ(): number;
|
||||
|
||||
/**
|
||||
* Return the top Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number;
|
||||
}
|
||||
|
||||
export namespace Base3DHandler {
|
||||
export const is3D = (
|
||||
object: gdjs.RuntimeObject
|
||||
): object is gdjs.RuntimeObject & gdjs.Base3DHandler => {
|
||||
//@ts-ignore We are checking if the methods are present.
|
||||
return object.getZ && object.setZ;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,6 +223,14 @@ namespace gdjs {
|
||||
isFlippedZ(): boolean {
|
||||
return this.object.isFlippedZ();
|
||||
}
|
||||
|
||||
getUnrotatedAABBMinZ(): number {
|
||||
return this.object.getUnrotatedAABBMinZ();
|
||||
}
|
||||
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.object.getUnrotatedAABBMaxZ();
|
||||
}
|
||||
}
|
||||
|
||||
gdjs.registerBehavior('Scene3D::Base3DBehavior', gdjs.Base3DBehavior);
|
||||
|
89
Extensions/3D/BloomEffect.ts
Normal file
89
Extensions/3D/BloomEffect.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::Bloom',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.UnrealBloomPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.UnrealBloomPass(
|
||||
new THREE.Vector2(256, 256),
|
||||
1,
|
||||
0,
|
||||
0
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'strength') {
|
||||
this.shaderPass.strength = value;
|
||||
}
|
||||
if (parameterName === 'radius') {
|
||||
this.shaderPass.radius = value;
|
||||
}
|
||||
if (parameterName === 'threshold') {
|
||||
this.shaderPass.threshold = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'strength') {
|
||||
return this.shaderPass.strength;
|
||||
}
|
||||
if (parameterName === 'radius') {
|
||||
return this.shaderPass.radius;
|
||||
}
|
||||
if (parameterName === 'threshold') {
|
||||
return this.shaderPass.threshold;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
80
Extensions/3D/BrightnessAndContrastEffect.ts
Normal file
80
Extensions/3D/BrightnessAndContrastEffect.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::BrightnessAndContrast',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.ShaderPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.BrightnessContrastShader
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'brightness') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
if (parameterName === 'contrast') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'brightness') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
if (parameterName === 'contrast') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
373
Extensions/3D/CustomRuntimeObject3D.ts
Normal file
373
Extensions/3D/CustomRuntimeObject3D.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
namespace gdjs {
|
||||
export interface Object3DDataContent {
|
||||
width: float;
|
||||
height: float;
|
||||
depth: float;
|
||||
}
|
||||
/** Base parameters for {@link gdjs.RuntimeObject3D} */
|
||||
export interface Object3DData extends ObjectData {
|
||||
/** The base parameters of the RuntimeObject3D */
|
||||
content: Object3DDataContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for 3D custom objects.
|
||||
*/
|
||||
export class CustomRuntimeObject3D
|
||||
extends gdjs.CustomRuntimeObject
|
||||
implements gdjs.Base3DHandler {
|
||||
/**
|
||||
* Position on the Z axis.
|
||||
*/
|
||||
private _z: float = 0;
|
||||
private _minZ: float = 0;
|
||||
private _maxZ: float = 0;
|
||||
private _scaleZ: float = 1;
|
||||
private _flippedZ: boolean = false;
|
||||
/**
|
||||
* Euler angle with the `ZYX` order.
|
||||
*
|
||||
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
|
||||
*/
|
||||
private _rotationX: float = 0;
|
||||
/**
|
||||
* Euler angle with the `ZYX` order.
|
||||
*
|
||||
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
|
||||
*/
|
||||
private _rotationY: float = 0;
|
||||
private _customCenterZ: float = 0;
|
||||
private static _temporaryVector = new THREE.Vector3();
|
||||
|
||||
constructor(
|
||||
parent: gdjs.RuntimeInstanceContainer,
|
||||
objectData: Object3DData & CustomObjectConfiguration
|
||||
) {
|
||||
super(parent, objectData);
|
||||
this._renderer.reinitialize(this, parent);
|
||||
}
|
||||
|
||||
protected _createRender() {
|
||||
const parent = this._runtimeScene;
|
||||
return new gdjs.CustomRuntimeObject3DRenderer(
|
||||
this,
|
||||
this._instanceContainer,
|
||||
parent
|
||||
);
|
||||
}
|
||||
|
||||
protected _reinitializeRenderer(): void {
|
||||
this.getRenderer().reinitialize(this, this.getParent());
|
||||
}
|
||||
|
||||
getRenderer(): gdjs.CustomRuntimeObject3DRenderer {
|
||||
return super.getRenderer() as gdjs.CustomRuntimeObject3DRenderer;
|
||||
}
|
||||
|
||||
get3DRendererObject() {
|
||||
// It can't be null because Three.js is always loaded
|
||||
// when a custom 3D object is used.
|
||||
return this.getRenderer().get3DRendererObject()!;
|
||||
}
|
||||
|
||||
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
||||
super.extraInitializationFromInitialInstance(initialInstanceData);
|
||||
if (initialInstanceData.depth !== undefined)
|
||||
this.setDepth(initialInstanceData.depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object position on the Z axis.
|
||||
*/
|
||||
setZ(z: float): void {
|
||||
if (z === this._z) return;
|
||||
this._z = z;
|
||||
this.getRenderer().updatePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object position on the Z axis.
|
||||
*/
|
||||
getZ(): float {
|
||||
return this._z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Z position of the rendered object.
|
||||
*
|
||||
* For most objects, this will returns the same value as getZ(). But if the
|
||||
* object has an origin that is not the same as the point (0,0,0) of the
|
||||
* object displayed, getDrawableZ will differ.
|
||||
*
|
||||
* @return The Z position of the rendered object.
|
||||
*/
|
||||
getDrawableZ(): float {
|
||||
if (this._isUntransformedHitBoxesDirty) {
|
||||
this._updateUntransformedHitBoxes();
|
||||
}
|
||||
return this._z + this._minZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Z position of the object center, **relative to the object Z
|
||||
* position** (`getDrawableX`).
|
||||
*
|
||||
* Use `getCenterZInScene` to get the position of the center in the scene.
|
||||
*
|
||||
* @return the Z position of the object center, relative to
|
||||
* `getDrawableZ()`.
|
||||
*/
|
||||
getCenterZ(): float {
|
||||
return this.getDepth() / 2;
|
||||
}
|
||||
|
||||
getCenterZInScene(): float {
|
||||
return this.getDrawableZ() + this.getCenterZ();
|
||||
}
|
||||
|
||||
setCenterZInScene(z: float): void {
|
||||
this.setZ(z + this._z - (this.getDrawableZ() + this.getCenterZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bottom Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMinZ(): number {
|
||||
return this.getDrawableZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the top Z of the object.
|
||||
* Rotations around X and Y are not taken into account.
|
||||
*/
|
||||
getUnrotatedAABBMaxZ(): number {
|
||||
return this.getDrawableZ() + this.getDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object rotation on the X axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
setRotationX(angle: float): void {
|
||||
this._rotationX = angle;
|
||||
this.getRenderer().updateRotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object rotation on the Y axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
setRotationY(angle: float): void {
|
||||
this._rotationY = angle;
|
||||
this.getRenderer().updateRotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object rotation on the X axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
getRotationX(): float {
|
||||
return this._rotationX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object rotation on the Y axis.
|
||||
*
|
||||
* This is an Euler angle. Objects use the `ZYX` order.
|
||||
*/
|
||||
getRotationY(): float {
|
||||
return this._rotationY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the object around the scene x axis at its center.
|
||||
* @param deltaAngle the rotation angle
|
||||
*/
|
||||
turnAroundX(deltaAngle: float): void {
|
||||
const axisX = gdjs.CustomRuntimeObject3D._temporaryVector;
|
||||
axisX.set(1, 0, 0);
|
||||
|
||||
const mesh = this.get3DRendererObject();
|
||||
mesh.rotateOnWorldAxis(axisX, gdjs.toRad(deltaAngle));
|
||||
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
||||
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the object around the scene y axis at its center.
|
||||
* @param deltaAngle the rotation angle
|
||||
*/
|
||||
turnAroundY(deltaAngle: float): void {
|
||||
const axisY = gdjs.CustomRuntimeObject3D._temporaryVector;
|
||||
axisY.set(0, 1, 0);
|
||||
|
||||
const mesh = this.get3DRendererObject();
|
||||
mesh.rotateOnWorldAxis(axisY, gdjs.toRad(deltaAngle));
|
||||
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
||||
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the object around the scene z axis at its center.
|
||||
* @param deltaAngle the rotation angle
|
||||
*/
|
||||
turnAroundZ(deltaAngle: float): void {
|
||||
const axisZ = gdjs.CustomRuntimeObject3D._temporaryVector;
|
||||
axisZ.set(0, 0, 1);
|
||||
|
||||
const mesh = this.get3DRendererObject();
|
||||
mesh.rotateOnWorldAxis(axisZ, gdjs.toRad(deltaAngle));
|
||||
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
||||
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
||||
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the internal width of the object according to its children.
|
||||
*/
|
||||
getUnscaledDepth(): float {
|
||||
if (this._isUntransformedHitBoxesDirty) {
|
||||
this._updateUntransformedHitBoxes();
|
||||
}
|
||||
return this._maxZ - this._minZ;
|
||||
}
|
||||
|
||||
_updateUntransformedHitBoxes(): void {
|
||||
super._updateUntransformedHitBoxes();
|
||||
|
||||
let minZ = Number.MAX_VALUE;
|
||||
let maxZ = -Number.MAX_VALUE;
|
||||
for (const childInstance of this._instanceContainer.getAdhocListOfAllInstances()) {
|
||||
if (!childInstance.isIncludedInParentCollisionMask()) {
|
||||
continue;
|
||||
}
|
||||
if (!gdjs.Base3DHandler.is3D(childInstance)) {
|
||||
continue;
|
||||
}
|
||||
minZ = Math.min(minZ, childInstance.getUnrotatedAABBMinZ());
|
||||
maxZ = Math.max(maxZ, childInstance.getUnrotatedAABBMaxZ());
|
||||
}
|
||||
if (minZ === Number.MAX_VALUE) {
|
||||
// The unscaled size can't be 0 because setWidth and setHeight wouldn't
|
||||
// have any effect.
|
||||
minZ = 0;
|
||||
maxZ = 1;
|
||||
}
|
||||
this._minZ = minZ;
|
||||
this._maxZ = maxZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the center Z from the local origin (0;0).
|
||||
*/
|
||||
getUnscaledCenterZ(): float {
|
||||
if (this.hasCustomRotationCenter()) {
|
||||
return this._customCenterZ;
|
||||
}
|
||||
if (this._isUntransformedHitBoxesDirty) {
|
||||
this._updateUntransformedHitBoxes();
|
||||
}
|
||||
return (this._minZ + this._maxZ) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* The center of rotation is defined relatively to the origin (the object
|
||||
* position).
|
||||
* This avoids the center to move when children push the bounds.
|
||||
*
|
||||
* When no custom center is defined, it will move
|
||||
* to stay at the center of the children bounds.
|
||||
*
|
||||
* @param x coordinate of the custom center
|
||||
* @param y coordinate of the custom center
|
||||
*/
|
||||
setRotationCenter3D(x: float, y: float, z: float) {
|
||||
this._customCenterZ = z;
|
||||
this.setRotationCenter(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object size on the Z axis (called "depth").
|
||||
*/
|
||||
getDepth(): float {
|
||||
return this.getUnscaledDepth() * this.getScaleZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object size on the Z axis (called "depth").
|
||||
*/
|
||||
setDepth(depth: float): void {
|
||||
const unscaledDepth = this.getUnscaledDepth();
|
||||
if (unscaledDepth !== 0) {
|
||||
this.setScaleZ(depth / unscaledDepth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on X, Y and Z axis of the object.
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScale(newScale: number): void {
|
||||
super.setScale(newScale);
|
||||
this.setScaleZ(newScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the scale on Z axis of the object (changing its height).
|
||||
*
|
||||
* @param newScale The new scale (must be greater than 0).
|
||||
*/
|
||||
setScaleZ(newScale: number): void {
|
||||
if (newScale < 0) {
|
||||
newScale = 0;
|
||||
}
|
||||
if (newScale === Math.abs(this._scaleZ)) {
|
||||
return;
|
||||
}
|
||||
this._scaleZ = newScale * (this._flippedZ ? -1 : 1);
|
||||
this.getRenderer().updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
|
||||
*
|
||||
* @return the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
|
||||
*/
|
||||
getScale(): number {
|
||||
const scaleX = this.getScaleX();
|
||||
const scaleY = this.getScaleY();
|
||||
const scaleZ = this.getScaleZ();
|
||||
return scaleX === scaleY && scaleX === scaleZ
|
||||
? scaleX
|
||||
: Math.pow(scaleX * scaleY * scaleZ, 1 / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scale of the object on Z axis.
|
||||
*
|
||||
* @return the scale of the object on Z axis
|
||||
*/
|
||||
getScaleZ(): float {
|
||||
return Math.abs(this._scaleZ);
|
||||
}
|
||||
|
||||
flipZ(enable: boolean) {
|
||||
if (enable === this._flippedZ) {
|
||||
return;
|
||||
}
|
||||
this._flippedZ = enable;
|
||||
this.getRenderer().updateSize();
|
||||
}
|
||||
|
||||
isFlippedZ(): boolean {
|
||||
return this._flippedZ;
|
||||
}
|
||||
}
|
||||
}
|
179
Extensions/3D/CustomRuntimeObject3DRenderer.ts
Normal file
179
Extensions/3D/CustomRuntimeObject3DRenderer.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
namespace gdjs {
|
||||
export interface PixiImageManager {
|
||||
_threeAnimationFrameTextureManager: ThreeAnimationFrameTextureManager;
|
||||
}
|
||||
/**
|
||||
* The renderer for a {@link gdjs.CustomRuntimeObject3D} using Three.js.
|
||||
*/
|
||||
export class CustomRuntimeObject3DRenderer
|
||||
implements gdjs.RuntimeInstanceContainerRenderer {
|
||||
_object: gdjs.CustomRuntimeObject3D;
|
||||
_instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer;
|
||||
_isContainerDirty: boolean = true;
|
||||
_threeGroup: THREE.Group;
|
||||
|
||||
constructor(
|
||||
object: gdjs.CustomRuntimeObject3D,
|
||||
instanceContainer: gdjs.CustomRuntimeObjectInstanceContainer,
|
||||
parent: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = object;
|
||||
this._instanceContainer = instanceContainer;
|
||||
|
||||
this._threeGroup = new THREE.Group();
|
||||
this._threeGroup.rotation.order = 'ZYX';
|
||||
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
get3DRendererObject(): THREE.Object3D {
|
||||
return this._threeGroup;
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
reinitialize(
|
||||
object: gdjs.CustomRuntimeObject3D,
|
||||
parent: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
this._object = object;
|
||||
this._isContainerDirty = true;
|
||||
const layer = parent.getLayer('');
|
||||
if (layer) {
|
||||
layer.getRenderer().add3DRendererObject(this._threeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
_updateThreeGroup() {
|
||||
const threeObject3D = this.get3DRendererObject();
|
||||
|
||||
const scaleX = this._object.getScaleX();
|
||||
const scaleY = this._object.getScaleY();
|
||||
const scaleZ = this._object.getScaleZ();
|
||||
const pivotX = this._object.getUnscaledCenterX() * scaleX;
|
||||
const pivotY = this._object.getUnscaledCenterY() * scaleY;
|
||||
const pivotZ = this._object.getUnscaledCenterZ() * scaleZ;
|
||||
|
||||
threeObject3D.rotation.set(
|
||||
gdjs.toRad(this._object.getRotationX()),
|
||||
gdjs.toRad(this._object.getRotationY()),
|
||||
gdjs.toRad(this._object.angle)
|
||||
);
|
||||
|
||||
threeObject3D.position.set(
|
||||
this._object.isFlippedX() ? pivotX : -pivotX,
|
||||
this._object.isFlippedY() ? pivotY : -pivotY,
|
||||
this._object.isFlippedZ() ? pivotZ : -pivotZ
|
||||
);
|
||||
threeObject3D.position.applyEuler(threeObject3D.rotation);
|
||||
threeObject3D.position.x += this._object.getX() + pivotX;
|
||||
threeObject3D.position.y += this._object.getY() + pivotY;
|
||||
threeObject3D.position.z += this._object.getZ() + pivotZ;
|
||||
|
||||
threeObject3D.scale.set(
|
||||
this._object.isFlippedX() ? -scaleX : scaleX,
|
||||
this._object.isFlippedY() ? -scaleY : scaleY,
|
||||
this._object.isFlippedZ() ? -scaleZ : scaleZ
|
||||
);
|
||||
|
||||
threeObject3D.visible = !this._object.hidden;
|
||||
|
||||
this._isContainerDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to make sure the object is ready to be rendered.
|
||||
*/
|
||||
ensureUpToDate() {
|
||||
if (this._isContainerDirty) {
|
||||
this._updateThreeGroup();
|
||||
}
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateX(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateY(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateAngle(): void {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updatePosition() {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateRotation() {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateSize() {
|
||||
this._isContainerDirty = true;
|
||||
}
|
||||
|
||||
updateVisibility(): void {
|
||||
this._threeGroup.visible = !this._object.hidden;
|
||||
}
|
||||
|
||||
updateOpacity(): void {
|
||||
// Opacity is not handled by 3D custom objects.
|
||||
}
|
||||
|
||||
setLayerIndex(layer: gdjs.RuntimeLayer, index: float): void {
|
||||
// Layers are not handled for 3D custom objects.
|
||||
}
|
||||
|
||||
static getAnimationFrameTextureManager(
|
||||
imageManager: gdjs.PixiImageManager
|
||||
): ThreeAnimationFrameTextureManager {
|
||||
if (!imageManager._threeAnimationFrameTextureManager) {
|
||||
imageManager._threeAnimationFrameTextureManager = new ThreeAnimationFrameTextureManager(
|
||||
imageManager
|
||||
);
|
||||
}
|
||||
return imageManager._threeAnimationFrameTextureManager;
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeAnimationFrameTextureManager
|
||||
implements gdjs.AnimationFrameTextureManager<THREE.Material> {
|
||||
private _imageManager: gdjs.PixiImageManager;
|
||||
|
||||
constructor(imageManager: gdjs.PixiImageManager) {
|
||||
this._imageManager = imageManager;
|
||||
}
|
||||
|
||||
getAnimationFrameTexture(imageName: string) {
|
||||
return this._imageManager.getThreeMaterial(imageName, {
|
||||
useTransparentTexture: true,
|
||||
forceBasicMaterial: true,
|
||||
});
|
||||
}
|
||||
|
||||
getAnimationFrameWidth(material: THREE.Material) {
|
||||
const map = (material as
|
||||
| THREE.MeshBasicMaterial
|
||||
| THREE.MeshStandardMaterial).map;
|
||||
return map ? map.image.width : 0;
|
||||
}
|
||||
|
||||
getAnimationFrameHeight(material: THREE.Material) {
|
||||
const map = (material as
|
||||
| THREE.MeshBasicMaterial
|
||||
| THREE.MeshStandardMaterial).map;
|
||||
return map ? map.image.height : 0;
|
||||
}
|
||||
}
|
||||
}
|
74
Extensions/3D/ExposureEffect.ts
Normal file
74
Extensions/3D/ExposureEffect.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::Exposure',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.ShaderPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.ExposureShader
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'exposure') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'exposure') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
80
Extensions/3D/HueAndSaturationEffect.ts
Normal file
80
Extensions/3D/HueAndSaturationEffect.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
namespace gdjs {
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::HueAndSaturation',
|
||||
new (class implements gdjs.PixiFiltersTools.FilterCreator {
|
||||
makeFilter(
|
||||
target: EffectsTarget,
|
||||
effectData: EffectData
|
||||
): gdjs.PixiFiltersTools.Filter {
|
||||
if (typeof THREE === 'undefined') {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.ShaderPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.HueSaturationShader
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
isEnabled(target: EffectsTarget): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
|
||||
if (this._isEnabled === enabled) {
|
||||
return true;
|
||||
}
|
||||
if (enabled) {
|
||||
return this.applyEffect(target);
|
||||
} else {
|
||||
return this.removeEffect(target);
|
||||
}
|
||||
}
|
||||
applyEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'hue') {
|
||||
this.shaderPass.uniforms[parameterName].value = value / 180;
|
||||
}
|
||||
if (parameterName === 'saturation') {
|
||||
this.shaderPass.uniforms[parameterName].value = value;
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'hue') {
|
||||
return this.shaderPass.uniforms[parameterName].value * 180;
|
||||
}
|
||||
if (parameterName === 'saturation') {
|
||||
return this.shaderPass.uniforms[parameterName].value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -811,7 +803,6 @@ module.exports = {
|
||||
}
|
||||
|
||||
const Cube3DObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
Cube3DObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
@@ -860,7 +851,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
Cube3DObject.getProperties = function (objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -1100,7 +1090,6 @@ module.exports = {
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError
|
||||
Cube3DObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
@@ -1112,7 +1101,6 @@ module.exports = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// $FlowExpectedError
|
||||
Cube3DObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
@@ -1665,7 +1653,7 @@ module.exports = {
|
||||
'Change the camera rotation to look at an object. The camera top always face the screen.'
|
||||
),
|
||||
_('Change the camera rotation of _PARAM2_ to look at _PARAM1_'),
|
||||
_("Layers and cameras"),
|
||||
_('Layers and cameras'),
|
||||
'res/conditions/3d_box.svg',
|
||||
'res/conditions/3d_box.svg'
|
||||
)
|
||||
@@ -1939,6 +1927,86 @@ module.exports = {
|
||||
.setType('number')
|
||||
.setGroup(_('Orientation'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('HueAndSaturation')
|
||||
.setFullName(_('Hue and saturation'))
|
||||
.setDescription(_('Adjust hue and saturation.'))
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/HueAndSaturationEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('hue')
|
||||
.setValue('0')
|
||||
.setLabel(_('Hue in degrees (between -180 and 180)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('saturation')
|
||||
.setValue('0')
|
||||
.setLabel(_('Saturation (between -1 and 1)'))
|
||||
.setType('number');
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('Exposure')
|
||||
.setFullName(_('Exposure'))
|
||||
.setDescription(_('Adjust exposure.'))
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/ExposureEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('exposure')
|
||||
.setValue('1')
|
||||
.setLabel(_('Exposure (positive value)'))
|
||||
.setType('number');
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('Bloom')
|
||||
.setFullName(_('Bloom'))
|
||||
.setDescription(_('Apply a bloom effect.'))
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/BloomEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('strength')
|
||||
.setValue('1')
|
||||
.setLabel(_('Strength (between 0 and 3)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('radius')
|
||||
.setValue('0')
|
||||
.setLabel(_('Radius (between 0 and 1)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('threshold')
|
||||
.setValue('0')
|
||||
.setLabel(_('Threshold (between 0 and 1)'))
|
||||
.setType('number');
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('BrightnessAndContrast')
|
||||
.setFullName(_('Brightness and contrast.'))
|
||||
.setDescription(_('Adjust brightness and contrast.'))
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/BrightnessAndContrastEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('brightness')
|
||||
.setValue('0')
|
||||
.setLabel(_('Brightness (between -1 and 1)'))
|
||||
.setType('number');
|
||||
properties
|
||||
.getOrCreate('contrast')
|
||||
.setValue('0')
|
||||
.setLabel(_('Contrast (between -1 and 1)'))
|
||||
.setType('number');
|
||||
}
|
||||
// Don't forget to update the alert condition in Model3DEditor.js when
|
||||
// adding a new light.
|
||||
|
||||
@@ -1954,10 +2022,7 @@ module.exports = {
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
@@ -1965,17 +2030,13 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {},
|
||||
registerEditorConfigurations: function (objectsEditorService) {},
|
||||
/**
|
||||
* 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 */
|
||||
) {
|
||||
registerInstanceRenderers: function (objectsRenderingService) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const Rendered3DInstance = objectsRenderingService.Rendered3DInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
@@ -1983,39 +2044,25 @@ module.exports = {
|
||||
const THREE_ADDONS = objectsRenderingService.THREE_ADDONS;
|
||||
|
||||
const materialIndexToFaceIndex = {
|
||||
// $FlowFixMe
|
||||
0: 3,
|
||||
// $FlowFixMe
|
||||
1: 2,
|
||||
// $FlowFixMe
|
||||
2: 5,
|
||||
// $FlowFixMe
|
||||
3: 4,
|
||||
// $FlowFixMe
|
||||
4: 0,
|
||||
// $FlowFixMe
|
||||
5: 1,
|
||||
};
|
||||
|
||||
const noRepeatTextureVertexIndexToUvMapping = {
|
||||
// $FlowFixMe
|
||||
0: [0, 0],
|
||||
// $FlowFixMe
|
||||
1: [1, 0],
|
||||
// $FlowFixMe
|
||||
2: [0, 1],
|
||||
// $FlowFixMe
|
||||
3: [1, 1],
|
||||
};
|
||||
|
||||
const noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ = {
|
||||
// $FlowFixMe
|
||||
0: [0, 1],
|
||||
// $FlowFixMe
|
||||
1: [0, 0],
|
||||
// $FlowFixMe
|
||||
2: [1, 1],
|
||||
// $FlowFixMe
|
||||
3: [1, 0],
|
||||
};
|
||||
|
||||
@@ -2061,6 +2108,11 @@ module.exports = {
|
||||
};
|
||||
|
||||
class RenderedCube3DObject2DInstance extends RenderedInstance {
|
||||
/** @type {number} */
|
||||
_centerX = 0;
|
||||
/** @type {number} */
|
||||
_centerY = 0;
|
||||
|
||||
constructor(
|
||||
project,
|
||||
layout,
|
||||
@@ -2077,10 +2129,9 @@ module.exports = {
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
/**
|
||||
* Name of the resource that is rendered.
|
||||
* If no face is visible, this will be null.
|
||||
*/
|
||||
|
||||
// Name of the resource that is rendered.
|
||||
// If no face is visible, this will be null.
|
||||
this._renderedResourceName = undefined;
|
||||
const properties = associatedObjectConfiguration.getProperties();
|
||||
this._defaultWidth = parseFloat(properties.get('width').getValue());
|
||||
@@ -2110,12 +2161,9 @@ module.exports = {
|
||||
}
|
||||
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
const instance = this._instance;
|
||||
|
||||
const textureResourceName =
|
||||
RenderedCube3DObject2DInstance._getResourceNameToDisplay(
|
||||
objectConfiguration
|
||||
);
|
||||
const textureResourceName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
|
||||
objectConfiguration
|
||||
);
|
||||
if (textureResourceName) {
|
||||
return resourcesLoader.getResourceFullUrl(
|
||||
project,
|
||||
@@ -2127,20 +2175,18 @@ module.exports = {
|
||||
}
|
||||
|
||||
updateTextureIfNeeded() {
|
||||
const textureName =
|
||||
RenderedCube3DObject2DInstance._getResourceNameToDisplay(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
const textureName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
if (textureName === this._renderedResourceName) return;
|
||||
|
||||
this.updateTexture();
|
||||
}
|
||||
|
||||
updateTexture() {
|
||||
const textureName =
|
||||
RenderedCube3DObject2DInstance._getResourceNameToDisplay(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
const textureName = RenderedCube3DObject2DInstance._getResourceNameToDisplay(
|
||||
this._associatedObjectConfiguration
|
||||
);
|
||||
|
||||
if (!textureName) {
|
||||
this._renderFallbackObject = true;
|
||||
@@ -2398,10 +2444,9 @@ module.exports = {
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldRepeatTexture =
|
||||
this._shouldRepeatTextureOnFace[
|
||||
materialIndexToFaceIndex[materialIndex]
|
||||
];
|
||||
const shouldRepeatTexture = this._shouldRepeatTextureOnFace[
|
||||
materialIndexToFaceIndex[materialIndex]
|
||||
];
|
||||
|
||||
const shouldOrientateFacesTowardsY = this._facesOrientation === 'Y';
|
||||
|
||||
@@ -2436,13 +2481,16 @@ module.exports = {
|
||||
}
|
||||
} else {
|
||||
if (shouldOrientateFacesTowardsY) {
|
||||
[x, y] =
|
||||
noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
||||
[x, y] = noRepeatTextureVertexIndexToUvMapping[
|
||||
vertexIndex % 4
|
||||
];
|
||||
} else {
|
||||
[x, y] =
|
||||
noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
|
||||
vertexIndex % 4
|
||||
];
|
||||
[
|
||||
x,
|
||||
y,
|
||||
] = noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
|
||||
vertexIndex % 4
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -2472,13 +2520,16 @@ module.exports = {
|
||||
}
|
||||
} else {
|
||||
if (shouldOrientateFacesTowardsY) {
|
||||
[x, y] =
|
||||
noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
||||
[x, y] = noRepeatTextureVertexIndexToUvMapping[
|
||||
vertexIndex % 4
|
||||
];
|
||||
} else {
|
||||
[x, y] =
|
||||
noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
|
||||
vertexIndex % 4
|
||||
];
|
||||
[
|
||||
x,
|
||||
y,
|
||||
] = noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
|
||||
vertexIndex % 4
|
||||
];
|
||||
x = -x;
|
||||
y = -y;
|
||||
}
|
||||
@@ -2624,6 +2675,8 @@ module.exports = {
|
||||
RenderedCube3DObject3DInstance
|
||||
);
|
||||
|
||||
const epsilon = 1 / (1 << 16);
|
||||
|
||||
class Model3DRendered2DInstance extends RenderedInstance {
|
||||
_modelOriginPoint = [0, 0, 0];
|
||||
|
||||
@@ -2746,13 +2799,24 @@ module.exports = {
|
||||
);
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
const shouldKeepModelOrigin = !this._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
// Keep the origin as part of the model.
|
||||
// For instance, a model can be 1 face of a cube and we want to keep the
|
||||
// inside as part of the object even if it's just void.
|
||||
// It also avoids to have the origin outside of the object box.
|
||||
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
const modelWidth = boundingBox.max.x - boundingBox.min.x;
|
||||
const modelHeight = boundingBox.max.y - boundingBox.min.y;
|
||||
const modelDepth = boundingBox.max.z - boundingBox.min.z;
|
||||
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
|
||||
this._modelOriginPoint[0] =
|
||||
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] =
|
||||
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] =
|
||||
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
|
||||
|
||||
// The model is flipped on Y axis.
|
||||
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
|
||||
@@ -2761,19 +2825,10 @@ module.exports = {
|
||||
const centerPoint = this._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
-(
|
||||
boundingBox.min.x +
|
||||
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
|
||||
),
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(
|
||||
boundingBox.min.y +
|
||||
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
|
||||
),
|
||||
-(
|
||||
boundingBox.min.z +
|
||||
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
|
||||
)
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
-(boundingBox.min.z + modelDepth * centerPoint[2])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2786,9 +2841,9 @@ module.exports = {
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
const scaleX = 1 / modelWidth;
|
||||
const scaleY = 1 / modelHeight;
|
||||
const scaleZ = 1 / modelDepth;
|
||||
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
|
||||
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
|
||||
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
|
||||
|
||||
const scaleMatrix = new THREE.Matrix4();
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
@@ -2799,10 +2854,22 @@ module.exports = {
|
||||
|
||||
if (keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio = originalWidth / modelWidth;
|
||||
const heightRatio = originalHeight / modelHeight;
|
||||
const depthRatio = originalDepth / modelDepth;
|
||||
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(scaleRatio)) {
|
||||
scaleRatio = 1;
|
||||
}
|
||||
|
||||
this._defaultWidth = scaleRatio * modelWidth;
|
||||
this._defaultHeight = scaleRatio * modelHeight;
|
||||
@@ -2954,6 +3021,11 @@ module.exports = {
|
||||
return this.getHeight() * originPoint[1];
|
||||
}
|
||||
|
||||
getOriginZ() {
|
||||
const originPoint = this.getOriginPoint();
|
||||
return this.getDepth() * originPoint[2];
|
||||
}
|
||||
|
||||
getCenterX() {
|
||||
const centerPoint = this.getCenterPoint();
|
||||
return this.getWidth() * centerPoint[0];
|
||||
@@ -2964,6 +3036,11 @@ module.exports = {
|
||||
return this.getHeight() * centerPoint[1];
|
||||
}
|
||||
|
||||
getCenterZ() {
|
||||
const centerPoint = this.getCenterPoint();
|
||||
return this.getDepth() * centerPoint[2];
|
||||
}
|
||||
|
||||
getOriginPoint() {
|
||||
return this._originPoint || this._modelOriginPoint;
|
||||
}
|
||||
@@ -2990,12 +3067,24 @@ module.exports = {
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
|
||||
const shouldKeepModelOrigin = !this._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
// Keep the origin as part of the model.
|
||||
// For instance, a model can be 1 face of a cube and we want to keep the
|
||||
// inside as part of the object even if it's just void.
|
||||
// It also avoids to have the origin outside of the object box.
|
||||
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
const modelWidth = boundingBox.max.x - boundingBox.min.x;
|
||||
const modelHeight = boundingBox.max.y - boundingBox.min.y;
|
||||
const modelDepth = boundingBox.max.z - boundingBox.min.z;
|
||||
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
|
||||
this._modelOriginPoint[0] =
|
||||
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] =
|
||||
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] =
|
||||
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
|
||||
|
||||
// The model is flipped on Y axis.
|
||||
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
|
||||
@@ -3004,19 +3093,10 @@ module.exports = {
|
||||
const centerPoint = this._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
-(
|
||||
boundingBox.min.x +
|
||||
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
|
||||
),
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(
|
||||
boundingBox.min.y +
|
||||
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
|
||||
),
|
||||
-(
|
||||
boundingBox.min.z +
|
||||
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
|
||||
)
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
-(boundingBox.min.z + modelDepth * centerPoint[2])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3029,9 +3109,9 @@ module.exports = {
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
const scaleX = 1 / modelWidth;
|
||||
const scaleY = 1 / modelHeight;
|
||||
const scaleZ = 1 / modelDepth;
|
||||
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
|
||||
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
|
||||
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
|
||||
|
||||
const scaleMatrix = new THREE.Matrix4();
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
@@ -3042,14 +3122,63 @@ module.exports = {
|
||||
|
||||
if (keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio = originalWidth / modelWidth;
|
||||
const heightRatio = originalHeight / modelHeight;
|
||||
const depthRatio = originalDepth / modelDepth;
|
||||
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
const minScaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(minScaleRatio)) {
|
||||
this._defaultWidth = modelWidth;
|
||||
this._defaultHeight = modelHeight;
|
||||
this._defaultDepth = modelDepth;
|
||||
} else {
|
||||
if (widthRatio === minScaleRatio) {
|
||||
this._defaultWidth = originalWidth;
|
||||
this._defaultHeight = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelWidth,
|
||||
newReferenceValue: originalWidth,
|
||||
valueToApplyTo: modelHeight,
|
||||
});
|
||||
this._defaultDepth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelWidth,
|
||||
newReferenceValue: originalWidth,
|
||||
valueToApplyTo: modelDepth,
|
||||
});
|
||||
} else if (heightRatio === minScaleRatio) {
|
||||
this._defaultWidth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelHeight,
|
||||
newReferenceValue: originalHeight,
|
||||
valueToApplyTo: modelWidth,
|
||||
});
|
||||
|
||||
this._defaultWidth = scaleRatio * modelWidth;
|
||||
this._defaultHeight = scaleRatio * modelHeight;
|
||||
this._defaultDepth = scaleRatio * modelDepth;
|
||||
this._defaultHeight = originalHeight;
|
||||
this._defaultDepth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelHeight,
|
||||
newReferenceValue: originalHeight,
|
||||
valueToApplyTo: modelDepth,
|
||||
});
|
||||
} else {
|
||||
this._defaultWidth = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelDepth,
|
||||
newReferenceValue: originalDepth,
|
||||
valueToApplyTo: modelWidth,
|
||||
});
|
||||
this._defaultHeight = Rendered3DInstance.applyRatio({
|
||||
oldReferenceValue: modelDepth,
|
||||
newReferenceValue: originalDepth,
|
||||
valueToApplyTo: modelHeight,
|
||||
});
|
||||
this._defaultDepth = originalDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
namespace gdjs {
|
||||
type FloatPoint3D = [float, float, float];
|
||||
|
||||
const epsilon = 1 / (1 << 16);
|
||||
|
||||
const removeMetalness = (material: THREE.Material): void => {
|
||||
//@ts-ignore
|
||||
if (material.metalness) {
|
||||
@@ -9,7 +11,7 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const removeMetalnessFromMesh = (node: THREE.Object3D<THREE.Event>) => {
|
||||
const removeMetalnessFromMesh = (node: THREE.Object3D) => {
|
||||
const mesh = node as THREE.Mesh;
|
||||
if (!mesh.material) {
|
||||
return;
|
||||
@@ -23,9 +25,8 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const traverseToRemoveMetalnessFromMeshes = (
|
||||
node: THREE.Object3D<THREE.Event>
|
||||
) => node.traverse(removeMetalnessFromMesh);
|
||||
const traverseToRemoveMetalnessFromMeshes = (node: THREE.Object3D) =>
|
||||
node.traverse(removeMetalnessFromMesh);
|
||||
|
||||
const convertToBasicMaterial = (
|
||||
material: THREE.Material
|
||||
@@ -44,7 +45,7 @@ namespace gdjs {
|
||||
return basicMaterial;
|
||||
};
|
||||
|
||||
const setBasicMaterialTo = (node: THREE.Object3D<THREE.Event>): void => {
|
||||
const setBasicMaterialTo = (node: THREE.Object3D): void => {
|
||||
const mesh = node as THREE.Mesh;
|
||||
if (!mesh.material) {
|
||||
return;
|
||||
@@ -59,9 +60,8 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
const traverseToSetBasicMaterialFromMeshes = (
|
||||
node: THREE.Object3D<THREE.Event>
|
||||
) => node.traverse(setBasicMaterialTo);
|
||||
const traverseToSetBasicMaterialFromMeshes = (node: THREE.Object3D) =>
|
||||
node.traverse(setBasicMaterialTo);
|
||||
|
||||
class Model3DRuntimeObject3DRenderer extends gdjs.RuntimeObject3DRenderer {
|
||||
private _model3DRuntimeObject: gdjs.Model3DRuntimeObject;
|
||||
@@ -158,12 +158,24 @@ namespace gdjs {
|
||||
threeObject.updateMatrixWorld(true);
|
||||
const boundingBox = new THREE.Box3().setFromObject(threeObject);
|
||||
|
||||
const shouldKeepModelOrigin = !this._model3DRuntimeObject._originPoint;
|
||||
if (shouldKeepModelOrigin) {
|
||||
// Keep the origin as part of the model.
|
||||
// For instance, a model can be 1 face of a cube and we want to keep the
|
||||
// inside as part of the object even if it's just void.
|
||||
// It also avoids to have the origin outside of the object box.
|
||||
boundingBox.expandByPoint(new THREE.Vector3(0, 0, 0));
|
||||
}
|
||||
|
||||
const modelWidth = boundingBox.max.x - boundingBox.min.x;
|
||||
const modelHeight = boundingBox.max.y - boundingBox.min.y;
|
||||
const modelDepth = boundingBox.max.z - boundingBox.min.z;
|
||||
this._modelOriginPoint[0] = -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] = -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] = -boundingBox.min.z / modelDepth;
|
||||
this._modelOriginPoint[0] =
|
||||
modelWidth < epsilon ? 0 : -boundingBox.min.x / modelWidth;
|
||||
this._modelOriginPoint[1] =
|
||||
modelHeight < epsilon ? 0 : -boundingBox.min.y / modelHeight;
|
||||
this._modelOriginPoint[2] =
|
||||
modelDepth < epsilon ? 0 : -boundingBox.min.z / modelDepth;
|
||||
|
||||
// The model is flipped on Y axis.
|
||||
this._modelOriginPoint[1] = 1 - this._modelOriginPoint[1];
|
||||
@@ -172,19 +184,10 @@ namespace gdjs {
|
||||
const centerPoint = this._model3DRuntimeObject._centerPoint;
|
||||
if (centerPoint) {
|
||||
threeObject.position.set(
|
||||
-(
|
||||
boundingBox.min.x +
|
||||
(boundingBox.max.x - boundingBox.min.x) * centerPoint[0]
|
||||
),
|
||||
-(boundingBox.min.x + modelWidth * centerPoint[0]),
|
||||
// The model is flipped on Y axis.
|
||||
-(
|
||||
boundingBox.min.y +
|
||||
(boundingBox.max.y - boundingBox.min.y) * (1 - centerPoint[1])
|
||||
),
|
||||
-(
|
||||
boundingBox.min.z +
|
||||
(boundingBox.max.z - boundingBox.min.z) * centerPoint[2]
|
||||
)
|
||||
-(boundingBox.min.y + modelHeight * (1 - centerPoint[1])),
|
||||
-(boundingBox.min.z + modelDepth * centerPoint[2])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,9 +200,9 @@ namespace gdjs {
|
||||
);
|
||||
|
||||
// Stretch the model in a 1x1x1 cube.
|
||||
const scaleX = 1 / modelWidth;
|
||||
const scaleY = 1 / modelHeight;
|
||||
const scaleZ = 1 / modelDepth;
|
||||
const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
|
||||
const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
|
||||
const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
|
||||
|
||||
const scaleMatrix = new THREE.Matrix4();
|
||||
// Flip on Y because the Y axis is on the opposite side of direct basis.
|
||||
@@ -210,10 +213,22 @@ namespace gdjs {
|
||||
|
||||
if (keepAspectRatio) {
|
||||
// Reduce the object dimensions to keep aspect ratio.
|
||||
const widthRatio = originalWidth / modelWidth;
|
||||
const heightRatio = originalHeight / modelHeight;
|
||||
const depthRatio = originalDepth / modelDepth;
|
||||
const scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
const widthRatio =
|
||||
modelWidth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalWidth / modelWidth;
|
||||
const heightRatio =
|
||||
modelHeight < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalHeight / modelHeight;
|
||||
const depthRatio =
|
||||
modelDepth < epsilon
|
||||
? Number.POSITIVE_INFINITY
|
||||
: originalDepth / modelDepth;
|
||||
let scaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
|
||||
if (!Number.isFinite(scaleRatio)) {
|
||||
scaleRatio = 1;
|
||||
}
|
||||
|
||||
this._object._setOriginalWidth(scaleRatio * modelWidth);
|
||||
this._object._setOriginalHeight(scaleRatio * modelHeight);
|
||||
|
@@ -11,12 +11,11 @@ namespace gdjs {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const layerRenderer = layer.getRenderer();
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
const fov =
|
||||
threeCamera instanceof THREE.OrthographicCamera
|
||||
const fov = threeCamera
|
||||
? threeCamera instanceof THREE.OrthographicCamera
|
||||
? null
|
||||
: threeCamera
|
||||
? threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
: threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
return layer.getCameraZ(fov, cameraIndex);
|
||||
};
|
||||
|
||||
@@ -29,12 +28,11 @@ namespace gdjs {
|
||||
const layer = runtimeScene.getLayer(layerName);
|
||||
const layerRenderer = layer.getRenderer();
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
const fov =
|
||||
threeCamera instanceof THREE.OrthographicCamera
|
||||
const fov = threeCamera
|
||||
? threeCamera instanceof THREE.OrthographicCamera
|
||||
? null
|
||||
: threeCamera
|
||||
? threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
: threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
layer.setCameraZ(z, fov, cameraIndex);
|
||||
};
|
||||
|
||||
@@ -223,10 +221,11 @@ namespace gdjs {
|
||||
const layerRenderer = layer.getRenderer();
|
||||
|
||||
const threeCamera = layerRenderer.getThreeCamera();
|
||||
if (!threeCamera) return assumedFovIn2D;
|
||||
return threeCamera instanceof THREE.OrthographicCamera
|
||||
? 0
|
||||
: threeCamera.fov;
|
||||
return threeCamera
|
||||
? threeCamera instanceof THREE.OrthographicCamera
|
||||
? 0
|
||||
: threeCamera.fov
|
||||
: assumedFovIn2D;
|
||||
};
|
||||
|
||||
export const setFov = (
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -41,6 +33,14 @@ module.exports = {
|
||||
.addInstructionOrExpressionGroupMetadata(_('AdMob'))
|
||||
.setIcon('JsPlatform/Extensions/admobicon24.png');
|
||||
|
||||
extension
|
||||
.addDependency()
|
||||
.setName('Consent Cordova plugin')
|
||||
.setDependencyType('cordova')
|
||||
.setExportName('cordova-plugin-consent')
|
||||
.setVersion('2.4.0')
|
||||
.onlyIfOtherDependencyIsExported('AdMob Cordova plugin');
|
||||
|
||||
extension
|
||||
.registerProperty('AdMobAppIdAndroid')
|
||||
.setLabel(_('AdMob Android App ID'))
|
||||
@@ -71,13 +71,6 @@ module.exports = {
|
||||
)
|
||||
.onlyIfSomeExtraSettingsNonEmpty();
|
||||
|
||||
extension
|
||||
.addDependency()
|
||||
.setName('Consent Cordova plugin')
|
||||
.setDependencyType('cordova')
|
||||
.setExportName('cordova-plugin-consent')
|
||||
.onlyIfOtherDependencyIsExported('AdMob Cordova plugin');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SetTestMode',
|
||||
@@ -789,10 +782,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,11 +13,9 @@
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -710,10 +709,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,20 +13,11 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
const stringifyOptions = (options) => '["' + options.join('","') + '"]';
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -42,7 +34,6 @@ module.exports = {
|
||||
.setIcon('JsPlatform/Extensions/bbcode32.png');
|
||||
|
||||
var objectBBText = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError
|
||||
objectBBText.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
@@ -59,7 +50,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
objectBBText.getProperties = function (objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -126,7 +116,8 @@ module.exports = {
|
||||
};
|
||||
objectBBText.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text: '[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
|
||||
text:
|
||||
'[b]bold[/b] [i]italic[/i] [size=15]smaller[/size] [font=times]times[/font] font\n[spacing=12]spaced out[/spacing]\n[outline=yellow]outlined[/outline] [shadow=red]DropShadow[/shadow] ',
|
||||
opacity: 255,
|
||||
fontSize: 20,
|
||||
visible: true,
|
||||
@@ -137,7 +128,6 @@ module.exports = {
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError
|
||||
objectBBText.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
@@ -148,7 +138,6 @@ module.exports = {
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
objectBBText.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
@@ -223,10 +212,9 @@ module.exports = {
|
||||
parameterType === 'string' ||
|
||||
parameterType === 'stringWithSelector'
|
||||
) {
|
||||
const parameterOptions =
|
||||
gd.ParameterOptions.makeNewOptions().setDescription(
|
||||
property.paramLabel
|
||||
);
|
||||
const parameterOptions = gd.ParameterOptions.makeNewOptions().setDescription(
|
||||
property.paramLabel
|
||||
);
|
||||
if (property.options) {
|
||||
parameterOptions.setTypeExtraInfo(
|
||||
stringifyOptions(property.options)
|
||||
@@ -276,10 +264,9 @@ module.exports = {
|
||||
parameterType === 'number' ||
|
||||
parameterType === 'stringWithSelector'
|
||||
) {
|
||||
const parameterOptions =
|
||||
gd.ParameterOptions.makeNewOptions().setDescription(
|
||||
property.paramLabel
|
||||
);
|
||||
const parameterOptions = gd.ParameterOptions.makeNewOptions().setDescription(
|
||||
property.paramLabel
|
||||
);
|
||||
if (property.options) {
|
||||
parameterOptions.setTypeExtraInfo(
|
||||
stringifyOptions(property.options)
|
||||
@@ -436,6 +423,21 @@ module.exports = {
|
||||
|
||||
addSettersAndGettersToObject(object, setterAndGetterProperties, 'BBText');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
`SetFontFamily2`,
|
||||
_('Font family'),
|
||||
_('Set font family'),
|
||||
_('Set the font of _PARAM0_ to _PARAM1_'),
|
||||
'',
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font24.png'
|
||||
)
|
||||
.addParameter('object', 'BBText', 'BBText', false)
|
||||
.addParameter('fontResource', _('Font family'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName(`setFontFamily`);
|
||||
|
||||
const actions = object.getAllActions();
|
||||
const conditions = object.getAllConditions();
|
||||
const expressions = object.getAllExpressions();
|
||||
@@ -443,6 +445,9 @@ module.exports = {
|
||||
actions.get('BBText::SetOpacity').setHidden();
|
||||
conditions.get('BBText::IsOpacity').setHidden();
|
||||
expressions.get('GetOpacity').setHidden();
|
||||
// Action deprecated because it's using the `string` type instead of the more
|
||||
// user-friendly `fontResource` type.
|
||||
actions.get('BBText::SetFontFamily').setHidden();
|
||||
|
||||
return extension;
|
||||
},
|
||||
@@ -457,10 +462,7 @@ module.exports = {
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
@@ -468,9 +470,7 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {
|
||||
registerEditorConfigurations: function (objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'BBText::BBText',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
@@ -483,11 +483,8 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
registerInstanceRenderers: function (objectsRenderingService) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
const MultiStyleText = objectsRenderingService.requireModule(
|
||||
__dirname,
|
||||
'pixi-multistyle-text/dist/pixi-multistyle-text.umd'
|
||||
@@ -496,150 +493,145 @@ module.exports = {
|
||||
/**
|
||||
* Renderer for instances of BBText inside the IDE.
|
||||
*
|
||||
* @extends RenderedBBTextInstance
|
||||
* @extends RenderedInstance
|
||||
* @class RenderedBBTextInstance
|
||||
* @constructor
|
||||
*/
|
||||
function RenderedBBTextInstance(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
) {
|
||||
RenderedInstance.call(
|
||||
this,
|
||||
class RenderedBBTextInstance extends RenderedInstance {
|
||||
constructor(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
) {
|
||||
super(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
const bbTextStyles = {
|
||||
default: {
|
||||
// Use a default font family the time for the resource font to be loaded.
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '24px',
|
||||
fill: '#cccccc',
|
||||
tagStyle: 'bbcode',
|
||||
wordWrap: true,
|
||||
wordWrapWidth: 250, // This value is the default wrapping width of the runtime object.
|
||||
align: 'left',
|
||||
},
|
||||
};
|
||||
const bbTextStyles = {
|
||||
default: {
|
||||
// Use a default font family the time for the resource font to be loaded.
|
||||
fontFamily: 'Arial',
|
||||
fontSize: '24px',
|
||||
fill: '#cccccc',
|
||||
tagStyle: 'bbcode',
|
||||
wordWrap: true,
|
||||
wordWrapWidth: 250, // This value is the default wrapping width of the runtime object.
|
||||
align: 'left',
|
||||
},
|
||||
};
|
||||
|
||||
this._pixiObject = new MultiStyleText('', bbTextStyles);
|
||||
this._pixiObject = new MultiStyleText('', bbTextStyles);
|
||||
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
RenderedBBTextInstance.prototype = Object.create(
|
||||
RenderedInstance.prototype
|
||||
);
|
||||
|
||||
/**
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
*/
|
||||
RenderedBBTextInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
objectConfiguration
|
||||
) {
|
||||
return 'JsPlatform/Extensions/bbcode24.png';
|
||||
};
|
||||
|
||||
/**
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
RenderedBBTextInstance.prototype.update = function () {
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
|
||||
const rawText = properties.get('text').getValue();
|
||||
if (rawText !== this._pixiObject.text) {
|
||||
this._pixiObject.text = rawText;
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
|
||||
const opacity = properties.get('opacity').getValue();
|
||||
this._pixiObject.alpha = opacity / 255;
|
||||
|
||||
const color = properties.get('color').getValue();
|
||||
this._pixiObject.textStyles.default.fill =
|
||||
objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
|
||||
const fontSize = properties.get('fontSize').getValue();
|
||||
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
|
||||
|
||||
const fontResourceName = properties.get('fontFamily').getValue();
|
||||
|
||||
if (this._fontResourceName !== fontResourceName) {
|
||||
this._fontResourceName = fontResourceName;
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.loadFontFamily(this._project, fontResourceName)
|
||||
.then((fontFamily) => {
|
||||
// Once the font is loaded, we can use the given fontFamily.
|
||||
this._pixiObject.textStyles.default.fontFamily = fontFamily;
|
||||
this._pixiObject.dirty = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
// Ignore errors
|
||||
console.warn(
|
||||
'Unable to load font family for RenderedBBTextInstance',
|
||||
err
|
||||
);
|
||||
});
|
||||
/**
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
*/
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/bbcode24.png';
|
||||
}
|
||||
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
if (wordWrap !== this._pixiObject._style.wordWrap) {
|
||||
this._pixiObject._style.wordWrap = wordWrap;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
/**
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
update() {
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
if (align !== this._pixiObject._style.align) {
|
||||
this._pixiObject._style.align = align;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
const rawText = properties.get('text').getValue();
|
||||
if (rawText !== this._pixiObject.text) {
|
||||
this._pixiObject.text = rawText;
|
||||
}
|
||||
|
||||
this._pixiObject.position.x =
|
||||
this._instance.getX() + this._pixiObject.width / 2;
|
||||
this._pixiObject.position.y =
|
||||
this._instance.getY() + this._pixiObject.height / 2;
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
this._instance.getAngle()
|
||||
);
|
||||
const opacity = +properties.get('opacity').getValue();
|
||||
this._pixiObject.alpha = opacity / 255;
|
||||
|
||||
if (this._instance.hasCustomSize() && this._pixiObject) {
|
||||
const customWidth = this.getCustomWidth();
|
||||
if (
|
||||
this._pixiObject &&
|
||||
this._pixiObject._style.wordWrapWidth !== customWidth
|
||||
) {
|
||||
this._pixiObject._style.wordWrapWidth = customWidth;
|
||||
const color = properties.get('color').getValue();
|
||||
this._pixiObject.textStyles.default.fill = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
color
|
||||
);
|
||||
|
||||
const fontSize = properties.get('fontSize').getValue();
|
||||
this._pixiObject.textStyles.default.fontSize = `${fontSize}px`;
|
||||
|
||||
const fontResourceName = properties.get('fontFamily').getValue();
|
||||
|
||||
if (this._fontResourceName !== fontResourceName) {
|
||||
this._fontResourceName = fontResourceName;
|
||||
|
||||
this._pixiResourcesLoader
|
||||
.loadFontFamily(this._project, fontResourceName)
|
||||
.then((fontFamily) => {
|
||||
// Once the font is loaded, we can use the given fontFamily.
|
||||
this._pixiObject.textStyles.default.fontFamily = fontFamily;
|
||||
this._pixiObject.dirty = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
// Ignore errors
|
||||
console.warn(
|
||||
'Unable to load font family for RenderedBBTextInstance',
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
if (wordWrap !== this._pixiObject._style.wordWrap) {
|
||||
this._pixiObject._style.wordWrap = wordWrap;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
if (align !== this._pixiObject._style.align) {
|
||||
this._pixiObject._style.align = align;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
this._pixiObject.position.x =
|
||||
this._instance.getX() + this._pixiObject.width / 2;
|
||||
this._pixiObject.position.y =
|
||||
this._instance.getY() + this._pixiObject.height / 2;
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
this._instance.getAngle()
|
||||
);
|
||||
|
||||
if (this._instance.hasCustomSize() && this._pixiObject) {
|
||||
const customWidth = this.getCustomWidth();
|
||||
if (
|
||||
this._pixiObject &&
|
||||
this._pixiObject._style.wordWrapWidth !== customWidth
|
||||
) {
|
||||
this._pixiObject._style.wordWrapWidth = customWidth;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the width of the instance, when it's not resized.
|
||||
*/
|
||||
RenderedBBTextInstance.prototype.getDefaultWidth = function () {
|
||||
return this._pixiObject.width;
|
||||
};
|
||||
/**
|
||||
* Return the width of the instance, when it's not resized.
|
||||
*/
|
||||
getDefaultWidth() {
|
||||
return this._pixiObject.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the instance, when it's not resized.
|
||||
*/
|
||||
RenderedBBTextInstance.prototype.getDefaultHeight = function () {
|
||||
return this._pixiObject.height;
|
||||
};
|
||||
/**
|
||||
* Return the height of the instance, when it's not resized.
|
||||
*/
|
||||
getDefaultHeight() {
|
||||
return this._pixiObject.height;
|
||||
}
|
||||
}
|
||||
|
||||
objectsRenderingService.registerInstanceRenderer(
|
||||
'BBText::BBText',
|
||||
|
@@ -141,6 +141,7 @@ namespace gdjs {
|
||||
setBBText(text): void {
|
||||
this._text = text;
|
||||
this._renderer.updateText();
|
||||
this.invalidateHitboxes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -42,7 +34,6 @@ module.exports = {
|
||||
.setIcon('JsPlatform/Extensions/bitmapfont32.png');
|
||||
|
||||
const bitmapTextObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
@@ -59,7 +50,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.getProperties = function (objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -127,7 +117,8 @@ module.exports = {
|
||||
};
|
||||
bitmapTextObject.setRawJSONContent(
|
||||
JSON.stringify({
|
||||
text: 'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
text:
|
||||
'This text use the default bitmap font.\nUse a custom Bitmap Font to create your own texts.',
|
||||
opacity: 255,
|
||||
scale: 1,
|
||||
fontSize: 20,
|
||||
@@ -139,7 +130,6 @@ module.exports = {
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
@@ -150,7 +140,6 @@ module.exports = {
|
||||
) {
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError
|
||||
bitmapTextObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
@@ -176,7 +165,7 @@ module.exports = {
|
||||
'Extensions/BitmapText/bitmaptextruntimeobject-pixi-renderer.js'
|
||||
)
|
||||
.setCategoryFullName(_('Text'))
|
||||
.addDefaultBehavior("TextContainerCapability::TextContainerBehavior")
|
||||
.addDefaultBehavior('TextContainerCapability::TextContainerBehavior')
|
||||
.addDefaultBehavior('EffectCapability::EffectBehavior')
|
||||
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
|
||||
.addDefaultBehavior('ScalableCapability::ScalableBehavior');
|
||||
@@ -327,33 +316,33 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
|
||||
|
||||
object
|
||||
.addAction(
|
||||
'SetBitmapFontAndTextureAtlasResourceName2',
|
||||
_('Bitmap files resources'),
|
||||
_('Change the Bitmap Font and/or the atlas image used by the object.'),
|
||||
_(
|
||||
'Set the bitmap font of _PARAM0_ to _PARAM1_ and the atlas to _PARAM2_'
|
||||
),
|
||||
'',
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter(
|
||||
'bitmapFontResource',
|
||||
_('Bitmap font resource name'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
'imageResource',
|
||||
_('Texture atlas resource name'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
|
||||
object
|
||||
.addAction(
|
||||
'SetBitmapFontAndTextureAtlasResourceName2',
|
||||
_('Bitmap files resources'),
|
||||
_('Change the Bitmap Font and/or the atlas image used by the object.'),
|
||||
_(
|
||||
'Set the bitmap font of _PARAM0_ to _PARAM1_ and the atlas to _PARAM2_'
|
||||
),
|
||||
'',
|
||||
'res/actions/font24.png',
|
||||
'res/actions/font.png'
|
||||
)
|
||||
.addParameter('object', _('Bitmap text'), 'BitmapTextObject', false)
|
||||
.addParameter(
|
||||
'bitmapFontResource',
|
||||
_('Bitmap font resource name'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
'imageResource',
|
||||
_('Texture atlas resource name'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setBitmapFontAndTextureAtlasResourceName');
|
||||
|
||||
object
|
||||
.addExpressionAndCondition(
|
||||
@@ -451,10 +440,7 @@ module.exports = {
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
@@ -462,9 +448,7 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {
|
||||
registerEditorConfigurations: function (objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'BitmapText::BitmapTextObject',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
@@ -477,9 +461,7 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
registerInstanceRenderers: function (objectsRenderingService) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
|
||||
@@ -649,156 +631,144 @@ module.exports = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Renderer for instances of BitmapText inside the IDE.
|
||||
*
|
||||
* @extends RenderedBitmapTextInstance
|
||||
* @class RenderedBitmapTextInstance
|
||||
* @constructor
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
* This is called to update the PIXI object on the scene editor
|
||||
*/
|
||||
function RenderedBitmapTextInstance(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
) {
|
||||
RenderedInstance.call(
|
||||
this,
|
||||
class RenderedBitmapTextInstance extends RenderedInstance {
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'JsPlatform/Extensions/bitmapfont24.png';
|
||||
}
|
||||
|
||||
constructor(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
// We'll track changes of the font to trigger the loading of the new font.
|
||||
this._currentBitmapFontResourceName = '';
|
||||
this._currentTextureAtlasResourceName = '';
|
||||
|
||||
this._pixiObject = new PIXI.BitmapText('', {
|
||||
// Use a default font. The proper font will be loaded in `update` method.
|
||||
fontName: getDefaultBitmapFont().font,
|
||||
});
|
||||
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
RenderedBitmapTextInstance.prototype = Object.create(
|
||||
RenderedInstance.prototype
|
||||
);
|
||||
|
||||
/**
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
*/
|
||||
RenderedBitmapTextInstance.getThumbnail = function (
|
||||
project,
|
||||
resourcesLoader,
|
||||
objectConfiguration
|
||||
) {
|
||||
return 'JsPlatform/Extensions/bitmapfont24.png';
|
||||
};
|
||||
|
||||
// This is called to update the PIXI object on the scene editor
|
||||
RenderedBitmapTextInstance.prototype.update = function () {
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
|
||||
// Update the rendered text properties (note: Pixi is only
|
||||
// applying changes if there were changed).
|
||||
const rawText = properties.get('text').getValue();
|
||||
this._pixiObject.text = rawText;
|
||||
|
||||
const opacity = properties.get('opacity').getValue();
|
||||
this._pixiObject.alpha = opacity / 255;
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
this._pixiObject.align = align;
|
||||
|
||||
const color = properties.get('tint').getValue();
|
||||
this._pixiObject.tint =
|
||||
objectsRenderingService.rgbOrHexToHexNumber(color);
|
||||
|
||||
const scale = properties.get('scale').getValue() || 1;
|
||||
this._pixiObject.scale.set(scale);
|
||||
|
||||
// Track the changes in font to load the new requested font.
|
||||
const bitmapFontResourceName = properties
|
||||
.get('bitmapFontResourceName')
|
||||
.getValue();
|
||||
const textureAtlasResourceName = properties
|
||||
.get('textureAtlasResourceName')
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
this._currentBitmapFontResourceName !== bitmapFontResourceName ||
|
||||
this._currentTextureAtlasResourceName !== textureAtlasResourceName
|
||||
) {
|
||||
// Release the old font (if it was installed).
|
||||
releaseBitmapFont(this._pixiObject.fontName);
|
||||
super(
|
||||
project,
|
||||
layout,
|
||||
instance,
|
||||
associatedObjectConfiguration,
|
||||
pixiContainer,
|
||||
pixiResourcesLoader
|
||||
);
|
||||
|
||||
// Temporarily go back to the default font, as the PIXI.BitmapText
|
||||
// object does not support being displayed with a font not installed at all.
|
||||
// It will be replaced as soon as the proper font is loaded.
|
||||
this._pixiObject.fontName = getDefaultBitmapFont().font;
|
||||
// We'll track changes of the font to trigger the loading of the new font.
|
||||
this._currentBitmapFontResourceName = '';
|
||||
this._currentTextureAtlasResourceName = '';
|
||||
|
||||
this._currentBitmapFontResourceName = bitmapFontResourceName;
|
||||
this._currentTextureAtlasResourceName = textureAtlasResourceName;
|
||||
obtainBitmapFont(
|
||||
this._pixiResourcesLoader,
|
||||
this._project,
|
||||
this._currentBitmapFontResourceName,
|
||||
this._currentTextureAtlasResourceName
|
||||
).then((bitmapFont) => {
|
||||
this._pixiObject.fontName = bitmapFont.font;
|
||||
this._pixiObject.fontSize = bitmapFont.size;
|
||||
this._pixiObject.dirty = true;
|
||||
this._pixiObject = new PIXI.BitmapText('', {
|
||||
// Use a default font. The proper font will be loaded in `update` method.
|
||||
fontName: getDefaultBitmapFont().font,
|
||||
});
|
||||
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
this._pixiContainer.addChild(this._pixiObject);
|
||||
this.update();
|
||||
}
|
||||
|
||||
// Set up the wrapping width if enabled.
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
if (wordWrap && this._instance.hasCustomSize()) {
|
||||
this._pixiObject.maxWidth =
|
||||
this.getCustomWidth() / this._pixiObject.scale.x;
|
||||
this._pixiObject.dirty = true;
|
||||
} else {
|
||||
this._pixiObject.maxWidth = 0;
|
||||
this._pixiObject.dirty = true;
|
||||
update() {
|
||||
const properties = this._associatedObjectConfiguration.getProperties();
|
||||
|
||||
// Update the rendered text properties (note: Pixi is only
|
||||
// applying changes if there were changed).
|
||||
const rawText = properties.get('text').getValue();
|
||||
this._pixiObject.text = rawText;
|
||||
|
||||
const opacity = +properties.get('opacity').getValue();
|
||||
this._pixiObject.alpha = opacity / 255;
|
||||
|
||||
const align = properties.get('align').getValue();
|
||||
this._pixiObject.align = align;
|
||||
|
||||
const color = properties.get('tint').getValue();
|
||||
this._pixiObject.tint = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
color
|
||||
);
|
||||
|
||||
const scale = +(properties.get('scale').getValue() || 1);
|
||||
this._pixiObject.scale.set(scale);
|
||||
|
||||
// Track the changes in font to load the new requested font.
|
||||
const bitmapFontResourceName = properties
|
||||
.get('bitmapFontResourceName')
|
||||
.getValue();
|
||||
const textureAtlasResourceName = properties
|
||||
.get('textureAtlasResourceName')
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
this._currentBitmapFontResourceName !== bitmapFontResourceName ||
|
||||
this._currentTextureAtlasResourceName !== textureAtlasResourceName
|
||||
) {
|
||||
// Release the old font (if it was installed).
|
||||
releaseBitmapFont(this._pixiObject.fontName);
|
||||
|
||||
// Temporarily go back to the default font, as the PIXI.BitmapText
|
||||
// object does not support being displayed with a font not installed at all.
|
||||
// It will be replaced as soon as the proper font is loaded.
|
||||
this._pixiObject.fontName = getDefaultBitmapFont().font;
|
||||
|
||||
this._currentBitmapFontResourceName = bitmapFontResourceName;
|
||||
this._currentTextureAtlasResourceName = textureAtlasResourceName;
|
||||
obtainBitmapFont(
|
||||
this._pixiResourcesLoader,
|
||||
this._project,
|
||||
this._currentBitmapFontResourceName,
|
||||
this._currentTextureAtlasResourceName
|
||||
).then((bitmapFont) => {
|
||||
this._pixiObject.fontName = bitmapFont.font;
|
||||
this._pixiObject.fontSize = bitmapFont.size;
|
||||
this._pixiObject.dirty = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Set up the wrapping width if enabled.
|
||||
const wordWrap = properties.get('wordWrap').getValue() === 'true';
|
||||
if (wordWrap && this._instance.hasCustomSize()) {
|
||||
this._pixiObject.maxWidth =
|
||||
this.getCustomWidth() / this._pixiObject.scale.x;
|
||||
this._pixiObject.dirty = true;
|
||||
} else {
|
||||
this._pixiObject.maxWidth = 0;
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
this._pixiObject.position.x =
|
||||
this._instance.getX() + (this._pixiObject.textWidth * scale) / 2;
|
||||
this._pixiObject.position.y =
|
||||
this._instance.getY() + (this._pixiObject.textHeight * scale) / 2;
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
this._instance.getAngle()
|
||||
);
|
||||
}
|
||||
|
||||
this._pixiObject.position.x =
|
||||
this._instance.getX() + (this._pixiObject.textWidth * scale) / 2;
|
||||
this._pixiObject.position.y =
|
||||
this._instance.getY() + (this._pixiObject.textHeight * scale) / 2;
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
this._instance.getAngle()
|
||||
);
|
||||
};
|
||||
onRemovedFromScene() {
|
||||
RenderedInstance.prototype.onRemovedFromScene.call(this);
|
||||
|
||||
RenderedBitmapTextInstance.prototype.onRemovedFromScene = function () {
|
||||
RenderedInstance.prototype.onRemovedFromScene.call(this);
|
||||
const fontName = this._pixiObject.fontName;
|
||||
this._pixiObject.destroy();
|
||||
releaseBitmapFont(fontName);
|
||||
}
|
||||
|
||||
const fontName = this._pixiObject.fontName;
|
||||
this._pixiObject.destroy();
|
||||
releaseBitmapFont(fontName);
|
||||
};
|
||||
/**
|
||||
* Return the width of the instance, when it's not resized.
|
||||
*/
|
||||
getDefaultWidth() {
|
||||
return this._pixiObject.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the instance, when it's not resized.
|
||||
*/
|
||||
RenderedBitmapTextInstance.prototype.getDefaultWidth = function () {
|
||||
return this._pixiObject.width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the height of the instance, when it's not resized.
|
||||
*/
|
||||
RenderedBitmapTextInstance.prototype.getDefaultHeight = function () {
|
||||
return this._pixiObject.height;
|
||||
};
|
||||
/**
|
||||
* Return the height of the instance, when it's not resized.
|
||||
*/
|
||||
getDefaultHeight() {
|
||||
return this._pixiObject.height;
|
||||
}
|
||||
}
|
||||
|
||||
objectsRenderingService.registerInstanceRenderer(
|
||||
'BitmapText::BitmapTextObject',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -114,10 +106,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,450 +13,389 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function(_/*: (string) => string */, gd/*: libGDevelop */) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension.setExtensionInformation(
|
||||
"DeviceSensors",
|
||||
_("Device sensors"),
|
||||
_(
|
||||
"Allow the game to access the sensors of a mobile device."
|
||||
),
|
||||
"Matthias Meike",
|
||||
"Open source (MIT License)"
|
||||
).setExtensionHelpPath("/all-features/device-sensors")
|
||||
.setCategory('Input');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Device sensors"))
|
||||
.setIcon("JsPlatform/Extensions/orientation_active32.png");
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'DeviceSensors',
|
||||
_('Device sensors'),
|
||||
_('Allow the game to access the sensors of a mobile device.'),
|
||||
'Matthias Meike',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/device-sensors')
|
||||
.setCategory('Input');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Device sensors'))
|
||||
.setIcon('JsPlatform/Extensions/orientation_active32.png');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
"OrientationSensorActive",
|
||||
_("Sensor active"),
|
||||
'OrientationSensorActive',
|
||||
_('Sensor active'),
|
||||
_(
|
||||
"The condition is true if the device orientation sensor is currently active"
|
||||
'The condition is true if the device orientation sensor is currently active'
|
||||
),
|
||||
_("Orientation sensor is active"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_active32.png",
|
||||
"JsPlatform/Extensions/orientation_active32.png"
|
||||
_('Orientation sensor is active'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_active32.png',
|
||||
'JsPlatform/Extensions/orientation_active32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.isActive");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.isActive');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"OrientationAlpha",
|
||||
_("Compare the value of orientation alpha"),
|
||||
_(
|
||||
"Compare the value of orientation alpha. (Range: 0 to 360°)"
|
||||
),
|
||||
_("the orientation alpha"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_alpha32.png",
|
||||
"JsPlatform/Extensions/orientation_alpha32.png"
|
||||
'OrientationAlpha',
|
||||
_('Compare the value of orientation alpha'),
|
||||
_('Compare the value of orientation alpha. (Range: 0 to 360°)'),
|
||||
_('the orientation alpha'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_alpha32.png',
|
||||
'JsPlatform/Extensions/orientation_alpha32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationAlpha");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationAlpha');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"OrientationBeta",
|
||||
_("Compare the value of orientation beta"),
|
||||
_(
|
||||
"Compare the value of orientation beta. (Range: -180 to 180°)"
|
||||
),
|
||||
_("the orientation beta"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_beta32.png",
|
||||
"JsPlatform/Extensions/orientation_beta32.png"
|
||||
'OrientationBeta',
|
||||
_('Compare the value of orientation beta'),
|
||||
_('Compare the value of orientation beta. (Range: -180 to 180°)'),
|
||||
_('the orientation beta'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_beta32.png',
|
||||
'JsPlatform/Extensions/orientation_beta32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationBeta");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationBeta');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"OrientationGamma",
|
||||
_("Compare the value of orientation gamma"),
|
||||
_(
|
||||
"Compare the value of orientation gamma. (Range: -90 to 90°)"
|
||||
),
|
||||
_("the orientation gamma"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_gamma32.png",
|
||||
"JsPlatform/Extensions/orientation_gamma32.png"
|
||||
'OrientationGamma',
|
||||
_('Compare the value of orientation gamma'),
|
||||
_('Compare the value of orientation gamma. (Range: -90 to 90°)'),
|
||||
_('the orientation gamma'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_gamma32.png',
|
||||
'JsPlatform/Extensions/orientation_gamma32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationGamma");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationGamma');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"ActivateOrientationListener",
|
||||
_("Activate orientation sensor"),
|
||||
_("Activate the orientation sensor. (remember to turn it off again)"),
|
||||
_("Activate the orientation sensor."),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_active32.png",
|
||||
"JsPlatform/Extensions/orientation_active32.png"
|
||||
'ActivateOrientationListener',
|
||||
_('Activate orientation sensor'),
|
||||
_('Activate the orientation sensor. (remember to turn it off again)'),
|
||||
_('Activate the orientation sensor.'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_active32.png',
|
||||
'JsPlatform/Extensions/orientation_active32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.activateOrientationSensor");
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName(
|
||||
'gdjs.deviceSensors.orientation.activateOrientationSensor'
|
||||
);
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"DeactivateOrientationListener",
|
||||
_("Deactivate orientation sensor"),
|
||||
_("Deactivate the orientation sensor."),
|
||||
_("Deactivate the orientation sensor."),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_inactive32.png",
|
||||
"JsPlatform/Extensions/orientation_inactive32.png"
|
||||
'DeactivateOrientationListener',
|
||||
_('Deactivate orientation sensor'),
|
||||
_('Deactivate the orientation sensor.'),
|
||||
_('Deactivate the orientation sensor.'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_inactive32.png',
|
||||
'JsPlatform/Extensions/orientation_inactive32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.deactivateOrientationSensor");
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName(
|
||||
'gdjs.deviceSensors.orientation.deactivateOrientationSensor'
|
||||
);
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"OrientationAbsolute",
|
||||
_("Is Absolute"),
|
||||
_("Get if the devices orientation is absolute and not relative"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_absolute16.png"
|
||||
'OrientationAbsolute',
|
||||
_('Is Absolute'),
|
||||
_('Get if the devices orientation is absolute and not relative'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_absolute16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationAbsolute");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationAbsolute');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"OrientationAlpha",
|
||||
_("Alpha value"),
|
||||
_("Get the devices orientation Alpha (compass)"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_alpha16.png"
|
||||
'OrientationAlpha',
|
||||
_('Alpha value'),
|
||||
_('Get the devices orientation Alpha (compass)'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_alpha16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationAlpha");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationAlpha');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"OrientationBeta",
|
||||
_("Beta value"),
|
||||
_("Get the devices orientation Beta"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_beta16.png"
|
||||
'OrientationBeta',
|
||||
_('Beta value'),
|
||||
_('Get the devices orientation Beta'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_beta16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationBeta");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationBeta');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"OrientationGamma",
|
||||
_("Gamma value"),
|
||||
_("Get the devices orientation Gamma value"),
|
||||
_("Orientation"),
|
||||
"JsPlatform/Extensions/orientation_gamma16.png"
|
||||
'OrientationGamma',
|
||||
_('Gamma value'),
|
||||
_('Get the devices orientation Gamma value'),
|
||||
_('Orientation'),
|
||||
'JsPlatform/Extensions/orientation_gamma16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.orientation.getOrientationGamma");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.orientation.getOrientationGamma');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"MotionSensorActive",
|
||||
_("Sensor active"),
|
||||
'MotionSensorActive',
|
||||
_('Sensor active'),
|
||||
_(
|
||||
"The condition is true if the device motion sensor is currently active"
|
||||
'The condition is true if the device motion sensor is currently active'
|
||||
),
|
||||
_("Motion sensor is active"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_active32.png",
|
||||
"JsPlatform/Extensions/motion_active32.png"
|
||||
_('Motion sensor is active'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_active32.png',
|
||||
'JsPlatform/Extensions/motion_active32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.isActive");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.isActive');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"RotationAlpha",
|
||||
_("Compare the value of rotation alpha"),
|
||||
'RotationAlpha',
|
||||
_('Compare the value of rotation alpha'),
|
||||
_(
|
||||
"Compare the value of rotation alpha. (Note: few devices support this sensor)"
|
||||
'Compare the value of rotation alpha. (Note: few devices support this sensor)'
|
||||
),
|
||||
_("the rotation alpha"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_rotation_alpha32.png",
|
||||
"JsPlatform/Extensions/motion_rotation_alpha32.png"
|
||||
_('the rotation alpha'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_rotation_alpha32.png',
|
||||
'JsPlatform/Extensions/motion_rotation_alpha32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value (m/s²)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getRotationAlpha");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getRotationAlpha');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"RotationBeta",
|
||||
_("Compare the value of rotation beta"),
|
||||
'RotationBeta',
|
||||
_('Compare the value of rotation beta'),
|
||||
_(
|
||||
"Compare the value of rotation beta. (Note: few devices support this sensor)"
|
||||
'Compare the value of rotation beta. (Note: few devices support this sensor)'
|
||||
),
|
||||
_("the rotation beta"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_rotation_beta32.png",
|
||||
"JsPlatform/Extensions/motion_rotation_beta32.png"
|
||||
_('the rotation beta'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_rotation_beta32.png',
|
||||
'JsPlatform/Extensions/motion_rotation_beta32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value (m/s²)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getRotationBeta");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getRotationBeta');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"RotationGamma",
|
||||
_("Compare the value of rotation gamma"),
|
||||
'RotationGamma',
|
||||
_('Compare the value of rotation gamma'),
|
||||
_(
|
||||
"Compare the value of rotation gamma. (Note: few devices support this sensor)"
|
||||
'Compare the value of rotation gamma. (Note: few devices support this sensor)'
|
||||
),
|
||||
_("the rotation gamma"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_rotation_gamma32.png",
|
||||
"JsPlatform/Extensions/motion_rotation_gamma32.png"
|
||||
_('the rotation gamma'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_rotation_gamma32.png',
|
||||
'JsPlatform/Extensions/motion_rotation_gamma32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value (m/s²)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getRotationGamma");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getRotationGamma');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"AccelerationX",
|
||||
_("Compare the value of acceleration on X-axis"),
|
||||
_(
|
||||
"Compare the value of acceleration on the X-axis (m/s²)."
|
||||
),
|
||||
_("the acceleration X"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_acceleration_x32.png",
|
||||
"JsPlatform/Extensions/motion_acceleration_x32.png"
|
||||
'AccelerationX',
|
||||
_('Compare the value of acceleration on X-axis'),
|
||||
_('Compare the value of acceleration on the X-axis (m/s²).'),
|
||||
_('the acceleration X'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_acceleration_x32.png',
|
||||
'JsPlatform/Extensions/motion_acceleration_x32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value (m/s²)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationX");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationX');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"AccelerationY",
|
||||
_("Compare the value of acceleration on Y-axis"),
|
||||
_(
|
||||
"Compare the value of acceleration on the Y-axis (m/s²)."
|
||||
),
|
||||
_("the acceleration Y"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_acceleration_y32.png",
|
||||
"JsPlatform/Extensions/motion_acceleration_y32.png"
|
||||
'AccelerationY',
|
||||
_('Compare the value of acceleration on Y-axis'),
|
||||
_('Compare the value of acceleration on the Y-axis (m/s²).'),
|
||||
_('the acceleration Y'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_acceleration_y32.png',
|
||||
'JsPlatform/Extensions/motion_acceleration_y32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value (m/s²)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationY");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationY');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addCondition(
|
||||
"AccelerationZ",
|
||||
_("Compare the value of acceleration on Z-axis"),
|
||||
_(
|
||||
"Compare the value of acceleration on the Z-axis (m/s²)."
|
||||
),
|
||||
_("the acceleration Z"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_acceleration_z32.png",
|
||||
"JsPlatform/Extensions/motion_acceleration_z32.png"
|
||||
'AccelerationZ',
|
||||
_('Compare the value of acceleration on Z-axis'),
|
||||
_('Compare the value of acceleration on the Z-axis (m/s²).'),
|
||||
_('the acceleration Z'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_acceleration_z32.png',
|
||||
'JsPlatform/Extensions/motion_acceleration_z32.png'
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.addParameter('relationalOperator', _('Sign of the test'), 'number')
|
||||
.addParameter('expression', _('Value (m/s²)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationZ");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationZ');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"ActivateMotionListener",
|
||||
_("Activate motion sensor"),
|
||||
_("Activate the motion sensor. (remember to turn it off again)"),
|
||||
_("Activate the motion sensor."),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_active32.png",
|
||||
"JsPlatform/Extensions/motion_active32.png"
|
||||
'ActivateMotionListener',
|
||||
_('Activate motion sensor'),
|
||||
_('Activate the motion sensor. (remember to turn it off again)'),
|
||||
_('Activate the motion sensor.'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_active32.png',
|
||||
'JsPlatform/Extensions/motion_active32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.activateMotionSensor");
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.activateMotionSensor');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"DeactivateMotionListener",
|
||||
_("Deactivate motion sensor"),
|
||||
_("Deactivate the motion sensor."),
|
||||
_("Deactivate the motion sensor."),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_inactive32.png",
|
||||
"JsPlatform/Extensions/motion_inactive32.png"
|
||||
'DeactivateMotionListener',
|
||||
_('Deactivate motion sensor'),
|
||||
_('Deactivate the motion sensor.'),
|
||||
_('Deactivate the motion sensor.'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_inactive32.png',
|
||||
'JsPlatform/Extensions/motion_inactive32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.deactivateMotionSensor");
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.deactivateMotionSensor');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"RotationAlpha",
|
||||
_("Alpha value"),
|
||||
_("Get the devices rotation Alpha"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_rotation_alpha16.png"
|
||||
'RotationAlpha',
|
||||
_('Alpha value'),
|
||||
_('Get the devices rotation Alpha'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_rotation_alpha16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getRotationAlpha");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getRotationAlpha');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"RotationBeta",
|
||||
_("Beta value"),
|
||||
_("Get the devices rotation Beta"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_rotation_beta16.png"
|
||||
'RotationBeta',
|
||||
_('Beta value'),
|
||||
_('Get the devices rotation Beta'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_rotation_beta16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getRotationBeta");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getRotationBeta');
|
||||
|
||||
extension
|
||||
.addExpression(
|
||||
"RotationGamma",
|
||||
_("Gamma value"),
|
||||
_("Get the devices rotation Gamma"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_rotation_gamma16.png"
|
||||
'RotationGamma',
|
||||
_('Gamma value'),
|
||||
_('Get the devices rotation Gamma'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_rotation_gamma16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getRotationGamma");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getRotationGamma');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addExpression(
|
||||
"AccelerationX",
|
||||
_("Acceleration X value"),
|
||||
_("Get the devices acceleration on the X-axis (m/s²)"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_acceleration_x16.png"
|
||||
'AccelerationX',
|
||||
_('Acceleration X value'),
|
||||
_('Get the devices acceleration on the X-axis (m/s²)'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_acceleration_x16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationX");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationX');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addExpression(
|
||||
"AccelerationY",
|
||||
_("Acceleration Y value"),
|
||||
_("Get the devices acceleration on the Y-axis (m/s²)"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_acceleration_y16.png"
|
||||
'AccelerationY',
|
||||
_('Acceleration Y value'),
|
||||
_('Get the devices acceleration on the Y-axis (m/s²)'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_acceleration_y16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationY");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationY');
|
||||
|
||||
extension
|
||||
extension
|
||||
.addExpression(
|
||||
"AccelerationZ",
|
||||
_("Acceleration Z value"),
|
||||
_("Get the devices acceleration on the Z-axis (m/s²)"),
|
||||
_("Motion"),
|
||||
"JsPlatform/Extensions/motion_acceleration_z16.png"
|
||||
'AccelerationZ',
|
||||
_('Acceleration Z value'),
|
||||
_('Get the devices acceleration on the Z-axis (m/s²)'),
|
||||
_('Motion'),
|
||||
'JsPlatform/Extensions/motion_acceleration_z16.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/DeviceSensors/devicesensortools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.deviceSensors.motion.getAccelerationZ");
|
||||
.setIncludeFile('Extensions/DeviceSensors/devicesensortools.js')
|
||||
.setFunctionName('gdjs.deviceSensors.motion.getAccelerationZ');
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function(gd /*: libGDevelop */, extension /*: gdPlatformExtension*/) { return []; },
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -36,8 +28,9 @@ module.exports = {
|
||||
)
|
||||
.setExtensionHelpPath('/all-features/device-vibration')
|
||||
.setCategory('User interface');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Device vibration"))
|
||||
.setIcon("JsPlatform/Extensions/vibration_start32.png");
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Device vibration'))
|
||||
.setIcon('JsPlatform/Extensions/vibration_start32.png');
|
||||
|
||||
extension
|
||||
.addDependency()
|
||||
@@ -99,10 +92,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -721,10 +713,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,28 +13,20 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension.setExtensionInformation(
|
||||
'Effects',
|
||||
'Effects',
|
||||
'Lots of different effects to be used in your game.',
|
||||
'Various contributors from PixiJS, PixiJS filters and GDevelop',
|
||||
'MIT'
|
||||
)
|
||||
.setCategory('Visual effect')
|
||||
.setExtensionHelpPath('/interface/scene-editor/layer-effects');
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'Effects',
|
||||
'Effects',
|
||||
'Lots of different effects to be used in your game.',
|
||||
'Various contributors from PixiJS, PixiJS filters and GDevelop',
|
||||
'MIT'
|
||||
)
|
||||
.setCategory('Visual effect')
|
||||
.setExtensionHelpPath('/interface/scene-editor/layer-effects');
|
||||
|
||||
// ℹ️ You can declare an effect here. Please order the effects by alphabetical order.
|
||||
// This file is for common effects that are well-known/"battle-tested". If you have an
|
||||
@@ -230,7 +223,11 @@ module.exports = {
|
||||
const blurEffect = extension
|
||||
.addEffect('Blur')
|
||||
.setFullName(_('Blur (Gaussian, slow - prefer to use Kawase blur)'))
|
||||
.setDescription(_('Blur the rendered image. This is slow, so prefer to use Kawase blur in most cases.'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Blur the rendered image. This is slow, so prefer to use Kawase blur in most cases.'
|
||||
)
|
||||
)
|
||||
.markAsOnlyWorkingFor2D()
|
||||
.addIncludeFile('Extensions/Effects/blur-pixi-filter.js');
|
||||
const blurProperties = blurEffect.getProperties();
|
||||
@@ -728,13 +725,11 @@ module.exports = {
|
||||
const hslAdjustmentEffect = extension
|
||||
.addEffect('HslAdjustment')
|
||||
.setFullName(_('HSL Adjustment'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Adjust hue, saturation and lightness.'
|
||||
)
|
||||
)
|
||||
.setDescription(_('Adjust hue, saturation and lightness.'))
|
||||
.markAsOnlyWorkingFor2D()
|
||||
.addIncludeFile('Extensions/Effects/pixi-filters/filter-hsl-adjustment.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/Effects/pixi-filters/filter-hsl-adjustment.js'
|
||||
)
|
||||
.addIncludeFile('Extensions/Effects/hsl-adjustment-pixi-filter.js');
|
||||
const hslAdjustmentProperties = hslAdjustmentEffect.getProperties();
|
||||
hslAdjustmentProperties
|
||||
@@ -767,7 +762,9 @@ module.exports = {
|
||||
.addEffect('KawaseBlur')
|
||||
.setFullName(_('Blur (Kawase, fast)'))
|
||||
.setDescription(
|
||||
_('Blur the rendered image, with much better performance than Gaussian blur.')
|
||||
_(
|
||||
'Blur the rendered image, with much better performance than Gaussian blur.'
|
||||
)
|
||||
)
|
||||
.markAsOnlyWorkingFor2D()
|
||||
.addIncludeFile('Extensions/Effects/pixi-filters/filter-kawase-blur.js')
|
||||
@@ -816,9 +813,7 @@ module.exports = {
|
||||
const motionBlurEffect = extension
|
||||
.addEffect('MotionBlur')
|
||||
.setFullName(_('Motion Blur'))
|
||||
.setDescription(
|
||||
_('Blur the rendered image to give a feeling of speed.')
|
||||
)
|
||||
.setDescription(_('Blur the rendered image to give a feeling of speed.'))
|
||||
.markAsOnlyWorkingFor2D()
|
||||
.addIncludeFile('Extensions/Effects/pixi-filters/filter-motion-blur.js')
|
||||
.addIncludeFile('Extensions/Effects/motion-blur-pixi-filter.js');
|
||||
@@ -1174,7 +1169,9 @@ module.exports = {
|
||||
.setValue('0')
|
||||
.setLabel(_('Elapsed time'))
|
||||
.setType('number')
|
||||
.setDescription('It can be set back to 0 to play the shockwave animation again.');
|
||||
.setDescription(
|
||||
'It can be set back to 0 to play the shockwave animation again.'
|
||||
);
|
||||
shockwaveEffectProperties
|
||||
.getOrCreate('speed')
|
||||
.setValue('500')
|
||||
@@ -1311,10 +1308,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension.setExtensionInformation(
|
||||
'MyDummyExtension',
|
||||
@@ -153,7 +145,6 @@ module.exports = {
|
||||
// Everything that is stored inside the behavior is in "behaviorContent" and is automatically
|
||||
// saved/loaded to JSON.
|
||||
var dummyBehavior = new gd.BehaviorJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
dummyBehavior.updateProperty = function (
|
||||
behaviorContent,
|
||||
propertyName,
|
||||
@@ -170,7 +161,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
dummyBehavior.getProperties = function (behaviorContent) {
|
||||
var behaviorProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -187,7 +177,6 @@ module.exports = {
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
dummyBehavior.initializeContent = function (behaviorContent) {
|
||||
behaviorContent.setStringAttribute('property1', 'Initial value 1');
|
||||
behaviorContent.setBoolAttribute('property2', true);
|
||||
@@ -201,6 +190,7 @@ module.exports = {
|
||||
'',
|
||||
'CppPlatform/Extensions/topdownmovementicon.png',
|
||||
'DummyBehavior',
|
||||
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
|
||||
dummyBehavior,
|
||||
new gd.BehaviorsSharedData()
|
||||
)
|
||||
@@ -215,7 +205,6 @@ module.exports = {
|
||||
// Create a new gd.BehaviorSharedDataJsImplementation object and implement the methods
|
||||
// that are called to get and set the properties of the shared data.
|
||||
var dummyBehaviorWithSharedData = new gd.BehaviorJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
dummyBehaviorWithSharedData.updateProperty = function (
|
||||
behaviorContent,
|
||||
propertyName,
|
||||
@@ -228,7 +217,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
dummyBehaviorWithSharedData.getProperties = function (behaviorContent) {
|
||||
var behaviorProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -238,13 +226,11 @@ module.exports = {
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
dummyBehaviorWithSharedData.initializeContent = function (behaviorContent) {
|
||||
behaviorContent.setStringAttribute('property1', 'Initial value 1');
|
||||
};
|
||||
|
||||
var sharedData = new gd.BehaviorSharedDataJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
sharedData.updateProperty = function (
|
||||
sharedContent,
|
||||
propertyName,
|
||||
@@ -257,7 +243,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
sharedData.getProperties = function (sharedContent) {
|
||||
var sharedProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -267,7 +252,6 @@ module.exports = {
|
||||
|
||||
return sharedProperties;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
sharedData.initializeContent = function (behaviorContent) {
|
||||
behaviorContent.setStringAttribute(
|
||||
'sharedProperty1',
|
||||
@@ -284,6 +268,7 @@ module.exports = {
|
||||
'',
|
||||
'CppPlatform/Extensions/topdownmovementicon.png',
|
||||
'DummyBehaviorWithSharedData',
|
||||
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
|
||||
dummyBehaviorWithSharedData,
|
||||
sharedData
|
||||
)
|
||||
@@ -302,7 +287,6 @@ module.exports = {
|
||||
// Everything that is stored inside the object is in "content" and is automatically
|
||||
// saved/loaded to JSON.
|
||||
var dummyObject = new gd.ObjectJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
dummyObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
@@ -327,7 +311,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
dummyObject.getProperties = function (objectContent) {
|
||||
var objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -362,7 +345,6 @@ module.exports = {
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
dummyObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
@@ -382,7 +364,6 @@ module.exports = {
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object
|
||||
dummyObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
@@ -446,10 +427,7 @@ module.exports = {
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
const dummyBehavior = extension
|
||||
.getBehaviorMetadata('MyDummyExtension::DummyBehavior')
|
||||
.get();
|
||||
@@ -474,9 +452,7 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {
|
||||
registerEditorConfigurations: function (objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'MyDummyExtension::DummyObject',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
@@ -489,9 +465,7 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
registerInstanceRenderers: function (objectsRenderingService) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,368 +13,414 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function(_/*: (string) => string */, gd/*: libGDevelop */) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
"FacebookInstantGames",
|
||||
_("Facebook Instant Games"),
|
||||
'FacebookInstantGames',
|
||||
_('Facebook Instant Games'),
|
||||
_(
|
||||
"Allow your game to send scores and interact with the Facebook Instant Games platform."
|
||||
'Allow your game to send scores and interact with the Facebook Instant Games platform.'
|
||||
),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)"
|
||||
'Florian Rival',
|
||||
'Open source (MIT License)'
|
||||
)
|
||||
.setExtensionHelpPath("/publishing/publishing-to-facebook-instant-games")
|
||||
.setExtensionHelpPath('/publishing/publishing-to-facebook-instant-games')
|
||||
.setCategory('Third-party');
|
||||
extension.addInstructionOrExpressionGroupMetadata(_("Facebook Instant Games"))
|
||||
.setIcon("JsPlatform/Extensions/facebookicon32.png");
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Facebook Instant Games'))
|
||||
.setIcon('JsPlatform/Extensions/facebookicon32.png');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"SavePlayerData",
|
||||
_("Save player data"),
|
||||
'SavePlayerData',
|
||||
_('Save player data'),
|
||||
_(
|
||||
"Save the content of the given scene variable in the player data, stored on Facebook Instant Games servers"
|
||||
'Save the content of the given scene variable in the player data, stored on Facebook Instant Games servers'
|
||||
),
|
||||
_(
|
||||
"Save the content of _PARAM1_ in key _PARAM0_ of player data (store success message in _PARAM2_ or error in _PARAM3_)"
|
||||
'Save the content of _PARAM1_ in key _PARAM0_ of player data (store success message in _PARAM2_ or error in _PARAM3_)'
|
||||
),
|
||||
_("Player data"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
_('Player data'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter("string", 'Data key name (e.g: "Lives")', "", false)
|
||||
.addParameter("scenevar", "Scene variable with the content to save", "", false)
|
||||
.addParameter('string', 'Data key name (e.g: "Lives")', '', false)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the success message (optional)"),
|
||||
"",
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.setPlayerData");
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"LoadPlayerData",
|
||||
_("Load player data"),
|
||||
_("Load the player data with the given key in a variable"),
|
||||
_(
|
||||
"Load player data with key _PARAM0_ in _PARAM1_ (or error in _PARAM2_)"
|
||||
),
|
||||
_("Player data"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
)
|
||||
.addParameter("string", _('Data key name (e.g: "Lives")'), "", false)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store loaded data"),
|
||||
"",
|
||||
'scenevar',
|
||||
'Scene variable with the content to save',
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_('Variable where to store the success message (optional)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.loadPlayerData");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.setPlayerData');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"SavePlayerScore",
|
||||
_("Save player score"),
|
||||
'LoadPlayerData',
|
||||
_('Load player data'),
|
||||
_('Load the player data with the given key in a variable'),
|
||||
_(
|
||||
"Save the score, and optionally the content of the given variable in the player score, for the given metadata."
|
||||
'Load player data with key _PARAM0_ in _PARAM1_ (or error in _PARAM2_)'
|
||||
),
|
||||
_(
|
||||
"In leaderboard _PARAM0_, save score _PARAM1_ for the player and extra data from _PARAM2_ (store success message in _PARAM3_ or error in _PARAM4_)"
|
||||
),
|
||||
_("Leaderboards"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
_('Player data'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter('string', _('Data key name (e.g: "Lives")'), '', false)
|
||||
.addParameter(
|
||||
'scenevar',
|
||||
_('Variable where to store loaded data'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
"string",
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.loadPlayerData');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SavePlayerScore',
|
||||
_('Save player score'),
|
||||
_(
|
||||
'Save the score, and optionally the content of the given variable in the player score, for the given metadata.'
|
||||
),
|
||||
_(
|
||||
'In leaderboard _PARAM0_, save score _PARAM1_ for the player and extra data from _PARAM2_ (store success message in _PARAM3_ or error in _PARAM4_)'
|
||||
),
|
||||
_('Leaderboards'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter(
|
||||
'string',
|
||||
'Leaderboard name (e.g: "PlayersBestTimes")',
|
||||
"",
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter("expression", "Score to register for the player", "", false)
|
||||
.addParameter('expression', 'Score to register for the player', '', false)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Optional variable with metadata to save"),
|
||||
"",
|
||||
'scenevar',
|
||||
_('Optional variable with metadata to save'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the success message (optional)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_('Variable where to store the success message (optional)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.setPlayerScore");
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"LoadPlayerEntry",
|
||||
_("Load player entry"),
|
||||
_("Load the player entry in the given leaderboard"),
|
||||
'scenevar',
|
||||
_(
|
||||
"Load player entry from leaderboard _PARAM0_. Set rank in _PARAM1_, score in _PARAM2_ (extra data if any in _PARAM3_ and error in _PARAM4_)"
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
_("Leaderboards"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.setPlayerScore');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'LoadPlayerEntry',
|
||||
_('Load player entry'),
|
||||
_('Load the player entry in the given leaderboard'),
|
||||
_(
|
||||
'Load player entry from leaderboard _PARAM0_. Set rank in _PARAM1_, score in _PARAM2_ (extra data if any in _PARAM3_ and error in _PARAM4_)'
|
||||
),
|
||||
_('Leaderboards'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter(
|
||||
"string",
|
||||
'string',
|
||||
_('Leaderboard name (e.g: "PlayersBestTimes")'),
|
||||
"",
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the player rank (of -1 if not ranked)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_('Variable where to store the player rank (of -1 if not ranked)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the player score (of -1 if no score)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_('Variable where to store the player score (of -1 if no score)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store extra data (if any)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_('Variable where to store extra data (if any)'),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.getPlayerEntry");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.getPlayerEntry');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
"AreAdsSupported",
|
||||
_("Check if ads are supported"),
|
||||
_("Check if showing ads is supported on this device (only mobile phones can show ads)"),
|
||||
_("Ads can be shown on this device"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'AreAdsSupported',
|
||||
_('Check if ads are supported'),
|
||||
_(
|
||||
'Check if showing ads is supported on this device (only mobile phones can show ads)'
|
||||
),
|
||||
_('Ads can be shown on this device'),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.areAdsSupported");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.areAdsSupported');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
"IsInterstitialAdReady",
|
||||
_("Is the interstitial ad ready"),
|
||||
_("Check if the interstitial ad requested from Facebook is loaded and ready to be shown."),
|
||||
_("The interstitial ad is loaded and ready to be shown"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'IsInterstitialAdReady',
|
||||
_('Is the interstitial ad ready'),
|
||||
_(
|
||||
'Check if the interstitial ad requested from Facebook is loaded and ready to be shown.'
|
||||
),
|
||||
_('The interstitial ad is loaded and ready to be shown'),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.isInterstitialAdReady");
|
||||
.setFunctionName(
|
||||
'gdjs.evtTools.facebookInstantGames.isInterstitialAdReady'
|
||||
);
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"LoadInterstitialAd",
|
||||
_("Load and prepare an interstitial ad"),
|
||||
_("Request and load an interstitial ad from Facebook, so that it is ready to be shown."),
|
||||
_("Request and load an interstitial ad from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'LoadInterstitialAd',
|
||||
_('Load and prepare an interstitial ad'),
|
||||
_(
|
||||
'Request and load an interstitial ad from Facebook, so that it is ready to be shown.'
|
||||
),
|
||||
_(
|
||||
'Request and load an interstitial ad from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)'
|
||||
),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter(
|
||||
"string",
|
||||
_("The Ad Placement id (can be found while setting up the ad on Facebook)"),
|
||||
"",
|
||||
'string',
|
||||
_(
|
||||
'The Ad Placement id (can be found while setting up the ad on Facebook)'
|
||||
),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.loadInterstitialAd");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.loadInterstitialAd');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"ShowInterstitialAd",
|
||||
_("Show the loaded interstitial ad"),
|
||||
_("Show the interstitial ad previously loaded in memory. This won't work if you did not load the interstitial before."),
|
||||
_("Show the interstitial ad previously loaded in memory (if any error, store it in _PARAM0_)"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'ShowInterstitialAd',
|
||||
_('Show the loaded interstitial ad'),
|
||||
_(
|
||||
"Show the interstitial ad previously loaded in memory. This won't work if you did not load the interstitial before."
|
||||
),
|
||||
_(
|
||||
'Show the interstitial ad previously loaded in memory (if any error, store it in _PARAM0_)'
|
||||
),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.showInterstitialAd");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.showInterstitialAd');
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
"IsRewardedVideoReady",
|
||||
_("Is the rewarded video ready"),
|
||||
_("Check if the rewarded video requested from Facebook is loaded and ready to be shown."),
|
||||
_("The rewarded video is loaded and ready to be shown"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'IsRewardedVideoReady',
|
||||
_('Is the rewarded video ready'),
|
||||
_(
|
||||
'Check if the rewarded video requested from Facebook is loaded and ready to be shown.'
|
||||
),
|
||||
_('The rewarded video is loaded and ready to be shown'),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.isRewardedVideoReady");
|
||||
.setFunctionName(
|
||||
'gdjs.evtTools.facebookInstantGames.isRewardedVideoReady'
|
||||
);
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"LoadRewardedVideo",
|
||||
_("Load and prepare a rewarded video"),
|
||||
_("Request and load a rewarded video from Facebook, so that it is ready to be shown."),
|
||||
_("Request and load a rewarded video from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'LoadRewardedVideo',
|
||||
_('Load and prepare a rewarded video'),
|
||||
_(
|
||||
'Request and load a rewarded video from Facebook, so that it is ready to be shown.'
|
||||
),
|
||||
_(
|
||||
'Request and load a rewarded video from Facebook (ad placement id: _PARAM0_, error in _PARAM1_)'
|
||||
),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter(
|
||||
"string",
|
||||
_("The Ad Placement id (can be found while setting up the ad on Facebook)"),
|
||||
"",
|
||||
'string',
|
||||
_(
|
||||
'The Ad Placement id (can be found while setting up the ad on Facebook)'
|
||||
),
|
||||
'',
|
||||
false
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.loadRewardedVideo");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.loadRewardedVideo');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
"ShowRewardedVideo",
|
||||
_("Show the loaded rewarded video"),
|
||||
_("Show the rewarded video previously loaded in memory. This won't work if you did not load the video before."),
|
||||
_("Show the rewarded video previously loaded in memory (if any error, store it in _PARAM0_)"),
|
||||
_("Ads"),
|
||||
"JsPlatform/Extensions/facebookicon32.png",
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'ShowRewardedVideo',
|
||||
_('Show the loaded rewarded video'),
|
||||
_(
|
||||
"Show the rewarded video previously loaded in memory. This won't work if you did not load the video before."
|
||||
),
|
||||
_(
|
||||
'Show the rewarded video previously loaded in memory (if any error, store it in _PARAM0_)'
|
||||
),
|
||||
_('Ads'),
|
||||
'JsPlatform/Extensions/facebookicon32.png',
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.addParameter(
|
||||
"scenevar",
|
||||
_("Variable where to store the error message (optional, if an error occurs)"),
|
||||
"",
|
||||
'scenevar',
|
||||
_(
|
||||
'Variable where to store the error message (optional, if an error occurs)'
|
||||
),
|
||||
'',
|
||||
true
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.showRewardedVideo");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.showRewardedVideo');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
"PlayerId",
|
||||
_("Player identifier"),
|
||||
_("Get the player unique identifier"),
|
||||
'PlayerId',
|
||||
_('Player identifier'),
|
||||
_('Get the player unique identifier'),
|
||||
'',
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.getPlayerId");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.getPlayerId');
|
||||
|
||||
extension
|
||||
.addStrExpression(
|
||||
"PlayerName",
|
||||
_("Player name"),
|
||||
_("Get the player name"),
|
||||
'PlayerName',
|
||||
_('Player name'),
|
||||
_('Get the player name'),
|
||||
'',
|
||||
"JsPlatform/Extensions/facebookicon32.png"
|
||||
'JsPlatform/Extensions/facebookicon32.png'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
"Extensions/FacebookInstantGames/facebookinstantgamestools.js"
|
||||
'Extensions/FacebookInstantGames/facebookinstantgamestools.js'
|
||||
)
|
||||
.setFunctionName("gdjs.evtTools.facebookInstantGames.getPlayerName");
|
||||
.setFunctionName('gdjs.evtTools.facebookInstantGames.getPlayerName');
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function(gd /*: libGDevelop */, extension /*: gdPlatformExtension*/) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -11,11 +12,10 @@
|
||||
*
|
||||
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
|
||||
extension
|
||||
@@ -2314,10 +2314,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension */
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
339
Extensions/JsExtensionTypes.d.ts
vendored
Normal file
339
Extensions/JsExtensionTypes.d.ts
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
type GDNamespace = typeof import('../GDevelop.js/types');
|
||||
|
||||
// This is necessary for typescript to interpret the identifier PIXI as a namespace
|
||||
// in this file and merge it with the other namespace declarations.
|
||||
declare namespace PIXI {}
|
||||
|
||||
/**
|
||||
* Expose functions to load PIXI textures or fonts, given the names of
|
||||
* resources and a gd.Project.
|
||||
*/
|
||||
declare class PixiResourcesLoader {
|
||||
burstCache();
|
||||
|
||||
async reloadResource(project: gd.Project, resourceName: string);
|
||||
|
||||
/**
|
||||
* Reload the given resources.
|
||||
*/
|
||||
async reloadResources(
|
||||
project: gd.Project,
|
||||
resourceNames: Array<string>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Return the PIXI texture represented by the given resource.
|
||||
* If not loaded, it will load it.
|
||||
*/
|
||||
getPIXITexture(project: gd.Project, resourceName: string): PIXI.Texture;
|
||||
|
||||
/**
|
||||
* Return the three.js texture associated to the specified resource name.
|
||||
* Returns a placeholder texture if not found.
|
||||
* @param project The project
|
||||
* @param resourceName The name of the resource
|
||||
* @returns The requested texture, or a placeholder if not found.
|
||||
*/
|
||||
getThreeTexture(project: gd.Project, resourceName: string): THREE.Texture;
|
||||
|
||||
/**
|
||||
* Return the three.js material associated to the specified resource name.
|
||||
* @param project The project
|
||||
* @param resourceName The name of the resource
|
||||
* @param options Set if the material should be transparent or not.
|
||||
* @returns The requested material.
|
||||
*/
|
||||
getThreeMaterial(
|
||||
project: gd.Project,
|
||||
resourceName: string,
|
||||
{ useTransparentTexture }: { useTransparentTexture: boolean }
|
||||
): THREE.Material;
|
||||
|
||||
/**
|
||||
* Return the three.js material associated to the specified resource name.
|
||||
* @param project The project
|
||||
* @param resourceName The name of the resource
|
||||
* @param options
|
||||
* @returns The requested material.
|
||||
*/
|
||||
get3DModel(
|
||||
project: gd.Project,
|
||||
resourceName: string
|
||||
): Promise<THREE.THREE_ADDONS.GLTF>;
|
||||
|
||||
/**
|
||||
* Return the Pixi spine data for the specified resource name.
|
||||
* @param project The project
|
||||
* @param spineName The name of the spine json resource
|
||||
* @returns The requested spine skeleton.
|
||||
*/
|
||||
async getSpineData(
|
||||
project: gd.Project,
|
||||
spineName: string
|
||||
): Promise<SpineDataOrLoadingError>;
|
||||
|
||||
/**
|
||||
* Return the PIXI video texture represented by the given resource.
|
||||
* If not loaded, it will load it.
|
||||
* @returns The PIXI.Texture to be used. It can be loading, so you
|
||||
* should listen to PIXI.Texture `update` event, and refresh your object
|
||||
* if this event is triggered.
|
||||
*/
|
||||
getPIXIVideoTexture(project: gd.Project, resourceName: string): PIXI.Texture;
|
||||
|
||||
/**
|
||||
* Load the given font from its url/filename.
|
||||
* @returns a Promise that resolves with the font-family to be used
|
||||
* to render a text with the font.
|
||||
*/
|
||||
loadFontFamily(project: gd.Project, resourceName: string): Promise<string>;
|
||||
|
||||
/**
|
||||
* Get the font family name for the given font resource.
|
||||
* The font won't be loaded.
|
||||
* @returns The font-family to be used to render a text with the font.
|
||||
*/
|
||||
getFontFamily(project: gd.Project, resourceName: string): string;
|
||||
|
||||
/**
|
||||
* Get the data from a bitmap font file (fnt/xml) resource in the IDE.
|
||||
*/
|
||||
getBitmapFontData(project: gd.Project, resourceName: string): Promise<any>;
|
||||
|
||||
getInvalidPIXITexture();
|
||||
|
||||
getResourceJsonData(project: gd.Project, resourceName: string);
|
||||
}
|
||||
|
||||
/**
|
||||
* RenderedInstance is the base class used for creating 2D renderers of instances,
|
||||
* which display on the scene editor, using Pixi.js, the instance of an object (see InstancesEditor).
|
||||
*/
|
||||
class RenderedInstance {
|
||||
_project: gd.Project;
|
||||
_layout: gd.Layout;
|
||||
_instance: gd.InitialInstance;
|
||||
_associatedObjectConfiguration: gd.ObjectConfiguration;
|
||||
_pixiContainer: PIXI.Container;
|
||||
_pixiResourcesLoader: PixiResourcesLoader;
|
||||
_pixiObject: PIXI.DisplayObject;
|
||||
wasUsed: boolean;
|
||||
|
||||
constructor(
|
||||
project: gd.Project,
|
||||
layout: gd.Layout,
|
||||
instance: gd.InitialInstance,
|
||||
associatedObjectConfiguration: gd.ObjectConfiguration,
|
||||
pixiContainer: PIXI.Container,
|
||||
pixiResourcesLoader: PixiResourcesLoader
|
||||
);
|
||||
|
||||
/**
|
||||
* Convert an angle from degrees to radians.
|
||||
*/
|
||||
static toRad(angleInDegrees: number): number;
|
||||
|
||||
/**
|
||||
* Called when the scene editor is rendered.
|
||||
*/
|
||||
update(): void;
|
||||
|
||||
getPixiObject(): PIXI.DisplayObject | null;
|
||||
|
||||
getInstance(): gd.InitialInstance;
|
||||
|
||||
/**
|
||||
* Called to notify the instance renderer that its associated instance was removed from
|
||||
* the scene. The PIXI object should probably be removed from the container: This is what
|
||||
* the default implementation of the method does.
|
||||
*/
|
||||
onRemovedFromScene(): void;
|
||||
|
||||
getOriginX(): number;
|
||||
|
||||
getOriginY(): number;
|
||||
|
||||
getCenterX(): number;
|
||||
|
||||
getCenterY(): number;
|
||||
|
||||
getCustomWidth(): number;
|
||||
|
||||
getCustomHeight(): number;
|
||||
|
||||
getWidth(): number;
|
||||
|
||||
getHeight(): number;
|
||||
|
||||
getDepth(): number;
|
||||
|
||||
/**
|
||||
* Return the width of the instance when the instance doesn't have a custom size.
|
||||
*/
|
||||
getDefaultWidth(): number;
|
||||
|
||||
/**
|
||||
* Return the height of the instance when the instance doesn't have a custom size.
|
||||
*/
|
||||
getDefaultHeight(): number;
|
||||
|
||||
getDefaultDepth(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendered3DInstance is the base class used for creating 3D renderers of instances,
|
||||
* which display on the scene editor, using Three.js, the instance of an object (see InstancesEditor).
|
||||
* It can also display 2D artifacts on Pixi 2D plane (3D object shadow projected on the plane for instance).
|
||||
*/
|
||||
class Rendered3DInstance {
|
||||
_project: gd.Project;
|
||||
_layout: gd.Layout;
|
||||
_instance: gd.InitialInstance;
|
||||
_associatedObjectConfiguration: gd.ObjectConfiguration;
|
||||
_pixiContainer: PIXI.Container;
|
||||
_threeGroup: THREE.Group;
|
||||
_pixiResourcesLoader: PixiResourcesLoader;
|
||||
_pixiObject: PIXI.DisplayObject;
|
||||
_threeObject: THREE.Object3D | null;
|
||||
wasUsed: boolean;
|
||||
|
||||
constructor(
|
||||
project: gd.Project,
|
||||
layout: gd.Layout,
|
||||
instance: gd.InitialInstance,
|
||||
associatedObjectConfiguration: gd.ObjectConfiguration,
|
||||
pixiContainer: PIXI.Container,
|
||||
threeGroup: THREE.Group,
|
||||
pixiResourcesLoader: PixiResourcesLoader
|
||||
);
|
||||
|
||||
/**
|
||||
* Convert an angle from degrees to radians.
|
||||
*/
|
||||
static toRad(angleInDegrees: number): number;
|
||||
|
||||
/**
|
||||
* Applies ratio to value without intermediary value to avoid precision issues.
|
||||
*/
|
||||
static applyRatio({
|
||||
oldReferenceValue,
|
||||
newReferenceValue,
|
||||
valueToApplyTo,
|
||||
}: {
|
||||
oldReferenceValue: number;
|
||||
newReferenceValue: number;
|
||||
valueToApplyTo: number;
|
||||
}): number;
|
||||
|
||||
/**
|
||||
* Called when the scene editor is rendered.
|
||||
*/
|
||||
update(): void;
|
||||
|
||||
getPixiObject(): PIXI.DisplayObject;
|
||||
|
||||
getThreeObject(): THREE.Object3D;
|
||||
|
||||
getInstance(): gd.InitialInstance;
|
||||
|
||||
/**
|
||||
* Called to notify the instance renderer that its associated instance was removed from
|
||||
* the scene. The PIXI object should probably be removed from the container: This is what
|
||||
* the default implementation of the method does.
|
||||
*/
|
||||
onRemovedFromScene(): void;
|
||||
|
||||
getOriginX(): number;
|
||||
|
||||
getOriginY(): number;
|
||||
|
||||
getCenterX(): number;
|
||||
|
||||
getCenterY(): number;
|
||||
|
||||
getWidth(): number;
|
||||
|
||||
getHeight(): number;
|
||||
|
||||
getDepth(): number;
|
||||
|
||||
/**
|
||||
* Return the width of the instance when the instance doesn't have a custom size.
|
||||
*/
|
||||
getDefaultWidth(): number;
|
||||
|
||||
/**
|
||||
* Return the height of the instance when the instance doesn't have a custom size.
|
||||
*/
|
||||
getDefaultHeight(): number;
|
||||
|
||||
/**
|
||||
* Return the depth of the instance when the instance doesn't have a custom size.
|
||||
*/
|
||||
getDefaultDepth(): number;
|
||||
}
|
||||
|
||||
declare type ObjectsRenderingService = {
|
||||
gd: GDNamespace;
|
||||
PIXI: PIXI;
|
||||
THREE: typeof import('../newIDE/app/node_modules/three');
|
||||
THREE_ADDONS: { SkeletonUtils: any };
|
||||
RenderedInstance: typeof RenderedInstance;
|
||||
Rendered3DInstance: typeof Rendered3DInstance;
|
||||
registerInstanceRenderer: (objectType: string, renderer: any) => void;
|
||||
registerInstance3DRenderer: (objectType: string, renderer: any) => void;
|
||||
requireModule: (dirname: string, moduleName: string) => any;
|
||||
getThumbnail: (
|
||||
project: gd.Project,
|
||||
objectConfiguration: gd.ObjectConfiguration
|
||||
) => string;
|
||||
rgbOrHexToHexNumber: (value: string) => number;
|
||||
registerClearCache: (clearCache: (_: any) => void) => void;
|
||||
};
|
||||
|
||||
declare type ObjectsEditorService = {
|
||||
registerEditorConfiguration: (
|
||||
objectType: string,
|
||||
editorConfiguration: any
|
||||
) => void;
|
||||
getDefaultObjectJsImplementationPropertiesEditor: ({
|
||||
helpPagePath: string,
|
||||
}) => any;
|
||||
};
|
||||
|
||||
declare type ExtensionModule = {
|
||||
createExtension: (
|
||||
_: (string) => string,
|
||||
gd: GDNamespace
|
||||
) => gd.PlatformExtension;
|
||||
/**
|
||||
* You can optionally add sanity tests that will check the basic working
|
||||
* of your extension behaviors/objects by instantiating behaviors/objects
|
||||
* and setting the property to a given value.
|
||||
*
|
||||
* If you don't have any tests, you can simply return an empty array.
|
||||
*
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: (
|
||||
gd: GDNamespace,
|
||||
extension: gd.PlatformExtension
|
||||
) => string[];
|
||||
/**
|
||||
* Register editors for objects.
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations?: (
|
||||
objectsEditorService: ObjectsEditorService
|
||||
) => void;
|
||||
/**
|
||||
* 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?: (
|
||||
objectsRenderingService: ObjectsRenderingService
|
||||
) => void;
|
||||
};
|
@@ -1,35 +0,0 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* @file This file contains the (Flow) types that are used in the JavaScript
|
||||
* extensions declaration (i.e: JsExtension.js files).
|
||||
* Extension runtime files are in TypeScript (ts files) and not using Flow.
|
||||
*
|
||||
* If you do changes here, run `node import-GDJS-Runtime.js` (in newIDE/app/scripts),
|
||||
* and be sure that the types declared here are reflecting the types exposed by the editor.
|
||||
*
|
||||
* Note that Flow comments are used to avoid having to preprocess this file and the
|
||||
* JsExtension.js files through Babel. This allows to keep plain JS files, while allowing
|
||||
* Flow static type checking to be run on them when integrated in the editor.
|
||||
*/
|
||||
|
||||
/*::
|
||||
export type ObjectsRenderingService = {
|
||||
gd: libGDevelop,
|
||||
PIXI: any,
|
||||
THREE: any,
|
||||
THREE_ADDONS: {SkeletonUtils: any},
|
||||
RenderedInstance: any,
|
||||
Rendered3DInstance: any,
|
||||
registerInstanceRenderer: (objectType: string, renderer: any) => void,
|
||||
registerInstance3DRenderer: (objectType: string, renderer: any) => void,
|
||||
requireModule: (dirname: string, moduleName: string) => any,
|
||||
getThumbnail: (project: gdProject, objectConfiguration: gdObjectConfiguration) => string,
|
||||
rgbOrHexToHexNumber: (value: string) => number,
|
||||
registerClearCache: (clearCache: any => void) => void,
|
||||
};
|
||||
export type ObjectsEditorService = {
|
||||
registerEditorConfiguration: (objectType: string, editorConfiguration: any) => void,
|
||||
getDefaultObjectJsImplementationPropertiesEditor: ({| helpPagePath: string |}) => any,
|
||||
};
|
||||
*/
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -42,7 +34,9 @@ module.exports = {
|
||||
.addAction(
|
||||
'SavePlayerScore',
|
||||
_('Save player score'),
|
||||
_("Save the player's score to the given leaderboard."),
|
||||
_(
|
||||
"Save the player's score to the given leaderboard. If the player is connected, the score will be attached to the connected player (unless disabled)."
|
||||
),
|
||||
_(
|
||||
'Send to leaderboard _PARAM1_ the score _PARAM2_ with player name: _PARAM3_'
|
||||
),
|
||||
@@ -68,6 +62,12 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Leaderboards/sha256.js')
|
||||
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.evtTools.leaderboards.savePlayerScore')
|
||||
.setAsyncFunctionName('gdjs.evtTools.leaderboards.savePlayerScore');
|
||||
|
||||
@@ -95,8 +95,37 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Leaderboards/sha256.js')
|
||||
.addIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.evtTools.leaderboards.saveConnectedPlayerScore')
|
||||
.setAsyncFunctionName('gdjs.evtTools.leaderboards.saveConnectedPlayerScore');
|
||||
.setAsyncFunctionName(
|
||||
'gdjs.evtTools.leaderboards.saveConnectedPlayerScore'
|
||||
);
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'SetPreferSendConnectedPlayerScore',
|
||||
_('Always attach scores to the connected player'),
|
||||
_(
|
||||
'Set if the score sent to a leaderboard is always attached to the connected player - if any. This is on by default.'
|
||||
),
|
||||
_('Always attach the score to the connected player: _PARAM1_'),
|
||||
_('Setup'),
|
||||
'JsPlatform/Extensions/leaderboard.svg',
|
||||
'JsPlatform/Extensions/leaderboard.svg'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.addParameter('yesorno', _('Enable?'), '', false)
|
||||
.setHelpPath('/all-features/leaderboards')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
|
||||
.setFunctionName(
|
||||
'gdjs.evtTools.leaderboards.setPreferSendConnectedPlayerScore'
|
||||
);
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
@@ -279,6 +308,12 @@ module.exports = {
|
||||
.setHelpPath('/all-features/leaderboards')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Leaderboards/leaderboardstools.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.evtTools.leaderboards.displayLeaderboard');
|
||||
|
||||
extension
|
||||
@@ -299,10 +334,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -6,6 +6,7 @@ namespace gdjs {
|
||||
export namespace evtTools {
|
||||
export namespace leaderboards {
|
||||
let _hasPlayerJustClosedLeaderboardView = false;
|
||||
let _preferSendConnectedPlayerScore = true;
|
||||
|
||||
gdjs.registerRuntimeScenePostEventsCallback(() => {
|
||||
// Set it back to false for the next frame.
|
||||
@@ -24,6 +25,14 @@ namespace gdjs {
|
||||
return shaObj.getHash('B64');
|
||||
};
|
||||
|
||||
const leaderboardHostBaseUrl = 'https://gd.games';
|
||||
// const leaderboardHostBaseUrl = 'http://localhost:4000';
|
||||
|
||||
type PublicLeaderboardEntry = {
|
||||
id: string;
|
||||
claimSecret?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hold the state of the save of a score for a leaderboard.
|
||||
*/
|
||||
@@ -45,7 +54,7 @@ namespace gdjs {
|
||||
private _lastSavedPlayerId: string | null = null;
|
||||
|
||||
/** The id of the entry in the leaderboard, for the last score saved with success. */
|
||||
lastSavedLeaderboardEntryId: string | null = null;
|
||||
lastSavedLeaderboardEntry: PublicLeaderboardEntry | null = null;
|
||||
|
||||
/** Last error that happened when saving the score (useful if `hasScoreSavingErrored` is true). */
|
||||
lastSaveError: string | null = null;
|
||||
@@ -114,7 +123,7 @@ namespace gdjs {
|
||||
playerId?: string;
|
||||
score: number;
|
||||
}): {
|
||||
closeSaving: (leaderboardEntryId: string | null) => void;
|
||||
closeSaving: (leaderboardEntry: PublicLeaderboardEntry) => void;
|
||||
closeSavingWithError(errorCode: string);
|
||||
} {
|
||||
if (this._isAlreadySavingThisScore({ playerName, playerId, score })) {
|
||||
@@ -159,7 +168,7 @@ namespace gdjs {
|
||||
if (playerId) this._currentlySavingPlayerId = playerId;
|
||||
|
||||
return {
|
||||
closeSaving: (leaderboardEntryId) => {
|
||||
closeSaving: (leaderboardEntry) => {
|
||||
if (savingPromise !== this.lastSavingPromise) {
|
||||
logger.info(
|
||||
'Score saving result received, but another save was launched in the meantime - ignoring the result of this one.'
|
||||
@@ -174,7 +183,7 @@ namespace gdjs {
|
||||
this._lastSavedScore = this._currentlySavingScore;
|
||||
this._lastSavedPlayerName = this._currentlySavingPlayerName;
|
||||
this._lastSavedPlayerId = this._currentlySavingPlayerId;
|
||||
this.lastSavedLeaderboardEntryId = leaderboardEntryId;
|
||||
this.lastSavedLeaderboardEntry = leaderboardEntry;
|
||||
this.hasScoreBeenSaved = true;
|
||||
|
||||
resolveSavingPromise();
|
||||
@@ -214,7 +223,7 @@ namespace gdjs {
|
||||
let _leaderboardViewIframeLoading: boolean = false;
|
||||
let _leaderboardViewIframeLoaded: boolean = false;
|
||||
let _errorTimeoutId: NodeJS.Timeout | null = null;
|
||||
let _leaderboardViewClosingCallback:
|
||||
let _leaderboardMessageListener:
|
||||
| ((event: MessageEvent) => void)
|
||||
| null = null;
|
||||
|
||||
@@ -288,7 +297,7 @@ namespace gdjs {
|
||||
authenticatedPlayerData?: { playerId: string; playerToken: string };
|
||||
score: number;
|
||||
runtimeScene: gdjs.RuntimeScene;
|
||||
}) {
|
||||
}): Promise<PublicLeaderboardEntry> {
|
||||
const rootApi = runtimeScene
|
||||
.getGame()
|
||||
.isUsingGDevelopDevelopmentEnvironment()
|
||||
@@ -339,18 +348,18 @@ namespace gdjs {
|
||||
throw errorCode;
|
||||
}
|
||||
|
||||
let leaderboardEntryId: string | null = null;
|
||||
try {
|
||||
const leaderboardEntry = await response.json();
|
||||
leaderboardEntryId = leaderboardEntry.id;
|
||||
return leaderboardEntry;
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
'An error occurred when reading response but score has been saved:',
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
return leaderboardEntryId;
|
||||
const errorCode = 'SAVED_ENTRY_CANT_BE_READ';
|
||||
throw errorCode;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error while submitting a leaderboard score:', error);
|
||||
const errorCode = 'REQUEST_NOT_SENT';
|
||||
@@ -359,13 +368,27 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
export const setPreferSendConnectedPlayerScore = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
enable: boolean
|
||||
) => {
|
||||
_preferSendConnectedPlayerScore = enable;
|
||||
};
|
||||
|
||||
export const savePlayerScore = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
leaderboardId: string,
|
||||
score: float,
|
||||
playerName: string
|
||||
) =>
|
||||
new gdjs.PromiseTask(
|
||||
) => {
|
||||
if (
|
||||
_preferSendConnectedPlayerScore &&
|
||||
gdjs.playerAuthentication.isAuthenticated()
|
||||
) {
|
||||
return saveConnectedPlayerScore(runtimeScene, leaderboardId, score);
|
||||
}
|
||||
|
||||
return new gdjs.PromiseTask(
|
||||
(async () => {
|
||||
const scoreSavingState = (_scoreSavingStateByLeaderboard[
|
||||
leaderboardId
|
||||
@@ -380,13 +403,13 @@ namespace gdjs {
|
||||
} = scoreSavingState.startSaving({ playerName, score });
|
||||
|
||||
try {
|
||||
const leaderboardEntryId = await saveScore({
|
||||
const leaderboardEntry = await saveScore({
|
||||
leaderboardId,
|
||||
playerName,
|
||||
score,
|
||||
runtimeScene,
|
||||
});
|
||||
closeSaving(leaderboardEntryId);
|
||||
closeSaving(leaderboardEntry);
|
||||
} catch (errorCode) {
|
||||
closeSavingWithError(errorCode);
|
||||
}
|
||||
@@ -395,6 +418,7 @@ namespace gdjs {
|
||||
}
|
||||
})()
|
||||
);
|
||||
};
|
||||
|
||||
export const saveConnectedPlayerScore = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
@@ -551,7 +575,64 @@ namespace gdjs {
|
||||
displayLoader: boolean,
|
||||
event: MessageEvent
|
||||
) {
|
||||
switch (event.data) {
|
||||
const messageId =
|
||||
typeof event.data === 'string' ? event.data : event.data.id;
|
||||
switch (messageId) {
|
||||
case 'playerAuthenticated':
|
||||
gdjs.playerAuthentication.login({
|
||||
runtimeScene,
|
||||
userId: event.data.userId,
|
||||
username: event.data.username,
|
||||
userToken: event.data.userToken,
|
||||
});
|
||||
break;
|
||||
case 'openPlayerAuthentication':
|
||||
gdjs.playerAuthentication
|
||||
.openAuthenticationWindow(runtimeScene)
|
||||
.promise.then(({ status }) => {
|
||||
if (
|
||||
!_leaderboardViewIframe ||
|
||||
!_leaderboardViewIframe.contentWindow
|
||||
) {
|
||||
logger.warn(
|
||||
'Unable to transmit the new login status to the leaderboard view.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === 'errored') {
|
||||
_leaderboardViewIframe.contentWindow.postMessage(
|
||||
{
|
||||
id: 'onPlayerAuthenticationErrored',
|
||||
},
|
||||
leaderboardHostBaseUrl
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const playerId = gdjs.playerAuthentication.getUserId();
|
||||
const playerToken = gdjs.playerAuthentication.getUserToken();
|
||||
if (status === 'dismissed' || !playerId || !playerToken) {
|
||||
_leaderboardViewIframe.contentWindow.postMessage(
|
||||
{
|
||||
id: 'onPlayerAuthenticationDismissed',
|
||||
},
|
||||
leaderboardHostBaseUrl
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_leaderboardViewIframe.contentWindow.postMessage(
|
||||
{
|
||||
id: 'onPlayerAuthenticated',
|
||||
playerId,
|
||||
playerUsername: gdjs.playerAuthentication.getUsername(),
|
||||
playerToken: playerToken,
|
||||
},
|
||||
leaderboardHostBaseUrl
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'closeLeaderboardView':
|
||||
_hasPlayerJustClosedLeaderboardView = true;
|
||||
closeLeaderboardView(runtimeScene);
|
||||
@@ -599,7 +680,7 @@ namespace gdjs {
|
||||
'Leaderboard page did not send message in time. Closing leaderboard view.'
|
||||
);
|
||||
}
|
||||
}, 5000);
|
||||
}, 15000);
|
||||
};
|
||||
|
||||
const displayLoaderInLeaderboardView = function (
|
||||
@@ -701,15 +782,15 @@ namespace gdjs {
|
||||
});
|
||||
}
|
||||
|
||||
// If a save is being done for this leaderboard, wait for it to end so that the `lastSavedLeaderboardEntryId`
|
||||
// If a save is being done for this leaderboard, wait for it to end so that the `lastSavedLeaderboardEntry`
|
||||
// can be saved and then used to show the player score.
|
||||
const scoreSavingState = _scoreSavingStateByLeaderboard[leaderboardId];
|
||||
if (scoreSavingState && scoreSavingState.lastSavingPromise) {
|
||||
await scoreSavingState.lastSavingPromise;
|
||||
}
|
||||
|
||||
const lastSavedLeaderboardEntryId = scoreSavingState
|
||||
? scoreSavingState.lastSavedLeaderboardEntryId
|
||||
const lastSavedLeaderboardEntry = scoreSavingState
|
||||
? scoreSavingState.lastSavedLeaderboardEntry
|
||||
: null;
|
||||
|
||||
const gameId = gdjs.projectData.properties.projectUuid;
|
||||
@@ -720,13 +801,30 @@ namespace gdjs {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('inGameEmbedded', 'true');
|
||||
if (isDev) searchParams.set('dev', 'true');
|
||||
if (lastSavedLeaderboardEntryId)
|
||||
if (lastSavedLeaderboardEntry) {
|
||||
searchParams.set(
|
||||
'playerLeaderboardEntryId',
|
||||
lastSavedLeaderboardEntryId
|
||||
lastSavedLeaderboardEntry.id
|
||||
);
|
||||
if (lastSavedLeaderboardEntry.claimSecret) {
|
||||
searchParams.set(
|
||||
'playerLeaderboardEntryClaimSecret',
|
||||
lastSavedLeaderboardEntry.claimSecret
|
||||
);
|
||||
}
|
||||
}
|
||||
const playerId = gdjs.playerAuthentication.getUserId();
|
||||
const playerToken = gdjs.playerAuthentication.getUserToken();
|
||||
if (playerId && playerToken) {
|
||||
searchParams.set('playerId', playerId);
|
||||
searchParams.set('playerToken', playerToken);
|
||||
searchParams.set(
|
||||
'playerUsername',
|
||||
gdjs.playerAuthentication.getUsername()
|
||||
);
|
||||
}
|
||||
|
||||
const targetUrl = `https://gd.games/games/${gameId}/leaderboard/${leaderboardId}?${searchParams}`;
|
||||
const targetUrl = `${leaderboardHostBaseUrl}/games/${gameId}/leaderboard/${leaderboardId}?${searchParams}`;
|
||||
|
||||
try {
|
||||
const isAvailable = await checkLeaderboardAvailability(targetUrl);
|
||||
@@ -772,7 +870,7 @@ namespace gdjs {
|
||||
targetUrl
|
||||
);
|
||||
if (typeof window !== 'undefined') {
|
||||
_leaderboardViewClosingCallback = (event: MessageEvent) => {
|
||||
_leaderboardMessageListener = (event: MessageEvent) => {
|
||||
receiveMessageFromLeaderboardView(
|
||||
runtimeScene,
|
||||
displayLoader,
|
||||
@@ -781,7 +879,7 @@ namespace gdjs {
|
||||
};
|
||||
(window as any).addEventListener(
|
||||
'message',
|
||||
_leaderboardViewClosingCallback,
|
||||
_leaderboardMessageListener,
|
||||
true
|
||||
);
|
||||
}
|
||||
@@ -836,10 +934,10 @@ namespace gdjs {
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).removeEventListener(
|
||||
'message',
|
||||
_leaderboardViewClosingCallback,
|
||||
_leaderboardMessageListener,
|
||||
true
|
||||
);
|
||||
_leaderboardViewClosingCallback = null;
|
||||
_leaderboardMessageListener = null;
|
||||
}
|
||||
domElementContainer.removeChild(_leaderboardViewIframe);
|
||||
_leaderboardViewIframe = null;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,32 +13,23 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension.setExtensionInformation(
|
||||
'Lighting',
|
||||
_('Lights'),
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
'Lighting',
|
||||
_('Lights'),
|
||||
|
||||
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
|
||||
'Harsimran Virk',
|
||||
'MIT'
|
||||
)
|
||||
.setCategory('Visual effect')
|
||||
.setTags("light");
|
||||
'This provides a light object, and a behavior to mark other objects as being obstacles for the lights. This is a great way to create a special atmosphere to your game, along with effects, make it more realistic or to create gameplays based on lights.',
|
||||
'Harsimran Virk',
|
||||
'MIT'
|
||||
)
|
||||
.setCategory('Visual effect')
|
||||
.setTags('light');
|
||||
|
||||
const lightObstacleBehavior = new gd.BehaviorJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
lightObstacleBehavior.updateProperty = function (
|
||||
behaviorContent,
|
||||
propertyName,
|
||||
@@ -46,14 +38,12 @@ module.exports = {
|
||||
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(
|
||||
@@ -66,6 +56,7 @@ module.exports = {
|
||||
'',
|
||||
'CppPlatform/Extensions/lightObstacleIcon32.png',
|
||||
'LightObstacleBehavior',
|
||||
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
|
||||
lightObstacleBehavior,
|
||||
new gd.BehaviorsSharedData()
|
||||
)
|
||||
@@ -77,7 +68,6 @@ module.exports = {
|
||||
|
||||
const lightObject = new gd.ObjectJsImplementation();
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object.
|
||||
lightObject.updateProperty = function (
|
||||
objectContent,
|
||||
propertyName,
|
||||
@@ -106,7 +96,6 @@ module.exports = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object.
|
||||
lightObject.getProperties = function (objectContent) {
|
||||
const objectProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -160,7 +149,6 @@ module.exports = {
|
||||
})
|
||||
);
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object.
|
||||
lightObject.updateInitialInstanceProperty = function (
|
||||
objectContent,
|
||||
instance,
|
||||
@@ -172,7 +160,6 @@ module.exports = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating an object.
|
||||
lightObject.getInitialInstanceProperties = function (
|
||||
content,
|
||||
instance,
|
||||
@@ -233,16 +220,11 @@ module.exports = {
|
||||
return extension;
|
||||
},
|
||||
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {
|
||||
registerEditorConfigurations: function (objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'Lighting::LightObject',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
@@ -255,9 +237,7 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerInstanceRenderers: function (
|
||||
objectsRenderingService /*: ObjectsRenderingService */
|
||||
) {
|
||||
registerInstanceRenderers: function (objectsRenderingService) {
|
||||
const RenderedInstance = objectsRenderingService.RenderedInstance;
|
||||
const PIXI = objectsRenderingService.PIXI;
|
||||
|
||||
@@ -283,32 +263,34 @@ module.exports = {
|
||||
);
|
||||
this._radius = parseFloat(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties(this.project)
|
||||
.getProperties()
|
||||
.get('radius')
|
||||
.getValue()
|
||||
);
|
||||
if (this._radius <= 0) this._radius = 1;
|
||||
const color = objectsRenderingService.rgbOrHexToHexNumber(
|
||||
this._associatedObjectConfiguration
|
||||
.getProperties(this.project)
|
||||
.getProperties()
|
||||
.get('color')
|
||||
.getValue()
|
||||
);
|
||||
|
||||
// The icon in the middle.
|
||||
const lightIconSprite = new PIXI.Sprite(PIXI.Texture.from('CppPlatform/Extensions/lightIcon32.png'));
|
||||
const lightIconSprite = new PIXI.Sprite(
|
||||
PIXI.Texture.from('CppPlatform/Extensions/lightIcon32.png')
|
||||
);
|
||||
lightIconSprite.anchor.x = 0.5;
|
||||
lightIconSprite.anchor.y = 0.5;
|
||||
|
||||
// The circle to show the radius of the light.
|
||||
const radiusBorderWidth = 2;
|
||||
const radiusGraphics = new PIXI.Graphics();
|
||||
radiusGraphics.lineStyle(
|
||||
radiusBorderWidth,
|
||||
color,
|
||||
0.8
|
||||
radiusGraphics.lineStyle(radiusBorderWidth, color, 0.8);
|
||||
radiusGraphics.drawCircle(
|
||||
0,
|
||||
0,
|
||||
Math.max(1, this._radius - radiusBorderWidth)
|
||||
);
|
||||
radiusGraphics.drawCircle(0, 0, Math.max(1, this._radius - radiusBorderWidth));
|
||||
|
||||
this._pixiObject = new PIXI.Container();
|
||||
this._pixiObject.addChild(lightIconSprite);
|
||||
@@ -326,11 +308,7 @@ module.exports = {
|
||||
/**
|
||||
* Return the path to the thumbnail of the specified object.
|
||||
*/
|
||||
static getThumbnail(
|
||||
project,
|
||||
resourcesLoader,
|
||||
objectConfiguration
|
||||
) {
|
||||
static getThumbnail(project, resourcesLoader, objectConfiguration) {
|
||||
return 'CppPlatform/Extensions/lightIcon32.png';
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ GDevelop - LinkedObjects Extension
|
||||
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('LinkedObjects');
|
||||
/**
|
||||
* Manages the links between objects.
|
||||
*/
|
||||
@@ -114,7 +115,14 @@ namespace gdjs {
|
||||
if (this._links.has(linkedObject.id)) {
|
||||
const otherObjList = this._links
|
||||
.get(linkedObject.id)!
|
||||
.linkedObjectMap.get(removedObject.getName())!;
|
||||
.linkedObjectMap.get(removedObject.getName());
|
||||
|
||||
if (!otherObjList) {
|
||||
logger.error(
|
||||
`Can't find link from ${linkedObject.id} (${linkedObject.name}) to ${removedObject.id} (${removedObject.name})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = otherObjList.indexOf(removedObject);
|
||||
if (index !== -1) {
|
||||
|
10
Extensions/P2P/A_peer.js
vendored
10
Extensions/P2P/A_peer.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -422,6 +422,17 @@ namespace gdjs {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Forces the usage of a relay (TURN) server, to avoid sharing IP addresses with the other peers.
|
||||
* @param shouldUseRelayServer Whether relay-only should be enabled or disabled.
|
||||
*/
|
||||
export const forceUseRelayServer = (shouldUseRelayServer: boolean) => {
|
||||
peerConfig.config = peerConfig.config || {};
|
||||
peerConfig.config.iceTransportPolicy = shouldUseRelayServer
|
||||
? 'relay'
|
||||
: 'all';
|
||||
};
|
||||
|
||||
/**
|
||||
* Overrides the default peer ID. Must be called before connecting to a broker.
|
||||
* Overriding the ID may have unwanted consequences. Do not use this feature
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension /*: gdPlatformExtension */ = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -182,6 +174,33 @@ module.exports = {
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setFunctionName('gdjs.evtTools.p2p.useCustomICECandidate');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'ForceRelayServer',
|
||||
_('Disable IP address sharing'),
|
||||
_(
|
||||
'Disables the sharing of IP addresses with the other peers. ' +
|
||||
'This action needs to be called BEFORE connecting to the broker server.'
|
||||
),
|
||||
_('Disable IP sharing: _PARAM0_'),
|
||||
'',
|
||||
'JsPlatform/Extensions/p2picon.svg',
|
||||
'JsPlatform/Extensions/p2picon.svg'
|
||||
)
|
||||
.addParameter(
|
||||
'yesorno',
|
||||
_('Disable sharing of IP addresses'),
|
||||
'Generally, it is recommended to keep sharing of IP addressed enabled ' +
|
||||
'to make connections faster and more often possible. ' +
|
||||
'Disabling IP address sharing will force all connections to pass messages through a ' +
|
||||
'TURN relay server, you can make P2P use one by adding one as an ICE candidate.',
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setFunctionName('gdjs.evtTools.p2p.forceUseRelayServer');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'UseDefaultBroker',
|
||||
@@ -474,10 +493,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -68,7 +68,10 @@ namespace gdjs {
|
||||
max: objectData.particleLifeTimeMax,
|
||||
},
|
||||
// A negative flow is "infinite flow" (all particles burst)
|
||||
frequency: objectData.flow < 0 ? 0.0001 : 1.0 / objectData.flow,
|
||||
frequency:
|
||||
objectData.flow < 0
|
||||
? ParticleEmitterObjectPixiRenderer.frequencyMinimumValue
|
||||
: 1.0 / objectData.flow,
|
||||
spawnChance: 1,
|
||||
particlesPerWave: objectData.flow < 0 ? objectData.maxParticleNb : 1,
|
||||
maxParticles: objectData.maxParticleNb,
|
||||
@@ -347,7 +350,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
setFlow(flow: number, tank: number): void {
|
||||
this.emitter.frequency = flow < 0 ? 0.0001 : 1.0 / flow;
|
||||
this.emitter.frequency =
|
||||
flow < 0
|
||||
? ParticleEmitterObjectPixiRenderer.frequencyMinimumValue
|
||||
: 1.0 / flow;
|
||||
this.emitter.emitterLifetime = ParticleEmitterObjectPixiRenderer.computeLifetime(
|
||||
flow,
|
||||
tank
|
||||
@@ -420,11 +426,33 @@ namespace gdjs {
|
||||
return this.started;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` at the end of emission or at the start if it's paused.
|
||||
* Returns false if there is no limit.
|
||||
*/
|
||||
_mayHaveEndedEmission(): boolean {
|
||||
return (
|
||||
// No end can be reached if there is no flow.
|
||||
this.emitter.frequency >
|
||||
ParticleEmitterObjectPixiRenderer.frequencyMinimumValue &&
|
||||
// No end can be reached when there is no limit.
|
||||
this.emitter.emitterLifetime >= 0 &&
|
||||
// Pixi stops the emission at the end.
|
||||
!this.emitter.emit &&
|
||||
// Pixi reset `_emitterLife` to `emitterLifetime` at the end of emission
|
||||
// so there is no way to know if it is the end or the start.
|
||||
// @ts-ignore Use a private attribute.
|
||||
this.emitter._emitterLife === this.emitter.emitterLifetime
|
||||
);
|
||||
}
|
||||
|
||||
static computeLifetime(flow: number, tank: number): float {
|
||||
if (tank < 0) return -1;
|
||||
else if (flow < 0) return 0.001;
|
||||
else return (tank + 0.1) / flow;
|
||||
}
|
||||
|
||||
private static readonly frequencyMinimumValue = 0.0001;
|
||||
}
|
||||
|
||||
// @ts-ignore - Register the class to let the engine use it.
|
||||
|
@@ -113,6 +113,11 @@ namespace gdjs {
|
||||
_additiveRenderingDirty: boolean = true;
|
||||
// Don't mark texture as dirty if not using one.
|
||||
_textureDirty: boolean;
|
||||
/**
|
||||
* `true` only when the emission is paused by events.
|
||||
* It allows to tell the end of emission apart from it.
|
||||
*/
|
||||
_isEmissionPaused: boolean = false;
|
||||
|
||||
// @ts-ignore
|
||||
_renderer: gdjs.ParticleEmitterObjectRenderer;
|
||||
@@ -390,9 +395,11 @@ namespace gdjs {
|
||||
this._additiveRenderingDirty = this._maxParticlesCountDirty = this._particleRotationSpeedDirty = false;
|
||||
this._renderer.update(this.getElapsedTime() / 1000.0);
|
||||
if (
|
||||
this._renderer.hasStarted() &&
|
||||
this.destroyWhenNoParticles &&
|
||||
this.getParticleCount() === 0 &&
|
||||
this.destroyWhenNoParticles
|
||||
this._renderer.hasStarted() &&
|
||||
!this._isEmissionPaused &&
|
||||
this._renderer._mayHaveEndedEmission()
|
||||
) {
|
||||
this.deleteFromScene(instanceContainer);
|
||||
}
|
||||
@@ -803,10 +810,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
startEmission(): void {
|
||||
this._isEmissionPaused = false;
|
||||
this._renderer.start();
|
||||
}
|
||||
|
||||
stopEmission(): void {
|
||||
this._isEmissionPaused = true;
|
||||
this._renderer.stop();
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
.SetValue(behaviorContent.GetBoolAttribute("allowDiagonals") ? "true"
|
||||
: "false")
|
||||
.SetGroup(_("Path smoothing"))
|
||||
.SetAdvanced()
|
||||
.SetType("Boolean");
|
||||
properties["Acceleration"]
|
||||
.SetLabel(_("Acceleration"))
|
||||
@@ -99,6 +100,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
properties["ExtraBorder"]
|
||||
.SetDescription(_("Extra border size"))
|
||||
.SetGroup(_("Collision"))
|
||||
.SetAdvanced()
|
||||
.SetType("Number")
|
||||
.SetMeasurementUnit(gd::MeasurementUnit::GetPixel())
|
||||
.SetValue(
|
||||
@@ -108,6 +110,7 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("smoothingMaxCellGap")))
|
||||
.SetGroup(_("Path smoothing"))
|
||||
.SetAdvanced()
|
||||
.SetDescription(_("It's recommended to leave a max gap of 1 cell. "
|
||||
"Setting it to 0 disable the smoothing."));
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -35,13 +27,12 @@ module.exports = {
|
||||
)
|
||||
.setExtensionHelpPath('/behaviors/physics2')
|
||||
.setCategory('Movement')
|
||||
.setTags("physics, gravity, obstacle, collision");
|
||||
.setTags('physics, gravity, obstacle, collision');
|
||||
extension
|
||||
.addInstructionOrExpressionGroupMetadata(_('Physics Engine 2.0'))
|
||||
.setIcon('res/physics32.png');
|
||||
|
||||
var physics2Behavior = new gd.BehaviorJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
physics2Behavior.updateProperty = function (
|
||||
behaviorContent,
|
||||
propertyName,
|
||||
@@ -51,104 +42,138 @@ module.exports = {
|
||||
behaviorContent.getChild('bodyType').setStringValue(newValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'bullet') {
|
||||
behaviorContent.getChild('bullet').setBoolValue(newValue === '1');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'fixedRotation') {
|
||||
behaviorContent
|
||||
.getChild('fixedRotation')
|
||||
.setBoolValue(newValue === '1');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'canSleep') {
|
||||
behaviorContent.getChild('canSleep').setBoolValue(newValue === '1');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'shape') {
|
||||
behaviorContent.getChild('shape').setStringValue(newValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'shapeDimensionA') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('shapeDimensionA').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('shapeDimensionA')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'shapeDimensionB') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('shapeDimensionB').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('shapeDimensionB')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'shapeOffsetX') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('shapeOffsetX').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('shapeOffsetX')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'shapeOffsetY') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('shapeOffsetY').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('shapeOffsetY')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'polygonOrigin') {
|
||||
behaviorContent.addChild('polygonOrigin').setStringValue(newValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'vertices') {
|
||||
behaviorContent.addChild('vertices');
|
||||
// $FlowFixMe
|
||||
behaviorContent.setChild('vertices', gd.Serializer.fromJSON(newValue));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'density') {
|
||||
behaviorContent
|
||||
.getChild('density')
|
||||
.setDoubleValue(parseFloat(newValue));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'friction') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('friction').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent.getChild('friction').setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'restitution') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('restitution').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('restitution')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'linearDamping') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('linearDamping').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('linearDamping')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'angularDamping') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('angularDamping').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('angularDamping')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'gravityScale') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
behaviorContent.getChild('gravityScale').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
behaviorContent
|
||||
.getChild('gravityScale')
|
||||
.setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'layers') {
|
||||
behaviorContent.getChild('layers').setIntValue(parseInt(newValue, 10));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'masks') {
|
||||
behaviorContent.getChild('masks').setIntValue(parseInt(newValue, 10));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
physics2Behavior.getProperties = function (behaviorContent) {
|
||||
var behaviorProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -312,7 +337,6 @@ module.exports = {
|
||||
return behaviorProperties;
|
||||
};
|
||||
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
physics2Behavior.initializeContent = function (behaviorContent) {
|
||||
behaviorContent.addChild('bodyType').setStringValue('Dynamic');
|
||||
behaviorContent.addChild('bullet').setBoolValue(false);
|
||||
@@ -336,40 +360,41 @@ module.exports = {
|
||||
};
|
||||
|
||||
var sharedData = new gd.BehaviorSharedDataJsImplementation();
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
sharedData.updateProperty = function (
|
||||
sharedContent,
|
||||
propertyName,
|
||||
newValue
|
||||
) {
|
||||
if (propertyName === 'gravityX') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
sharedContent.getChild('gravityX').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
sharedContent.getChild('gravityX').setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'gravityY') {
|
||||
newValue = parseFloat(newValue);
|
||||
if (newValue !== newValue) return false;
|
||||
sharedContent.getChild('gravityY').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseFloat(newValue);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
sharedContent.getChild('gravityY').setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'scaleX') {
|
||||
newValue = parseInt(newValue, 10);
|
||||
if (newValue !== newValue) return false;
|
||||
sharedContent.getChild('scaleX').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseInt(newValue, 10);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
sharedContent.getChild('scaleX').setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'scaleY') {
|
||||
newValue = parseInt(newValue, 10);
|
||||
if (newValue !== newValue) return false;
|
||||
sharedContent.getChild('scaleY').setDoubleValue(newValue);
|
||||
const newValueAsNumber = parseInt(newValue, 10);
|
||||
if (newValueAsNumber !== newValueAsNumber) return false;
|
||||
sharedContent.getChild('scaleY').setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
sharedData.getProperties = function (sharedContent) {
|
||||
var sharedProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
@@ -402,7 +427,6 @@ module.exports = {
|
||||
|
||||
return sharedProperties;
|
||||
};
|
||||
// $FlowExpectedError - ignore Flow warning as we're creating a behavior
|
||||
sharedData.initializeContent = function (behaviorContent) {
|
||||
behaviorContent.addChild('gravityX').setDoubleValue(0);
|
||||
behaviorContent.addChild('gravityY').setDoubleValue(9.8);
|
||||
@@ -422,6 +446,7 @@ module.exports = {
|
||||
'',
|
||||
'res/physics32.png',
|
||||
'Physics2Behavior',
|
||||
//@ts-ignore The class hierarchy is incorrect leading to a type error, but this is valid.
|
||||
physics2Behavior,
|
||||
sharedData
|
||||
)
|
||||
@@ -561,7 +586,8 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Time scale (1 by default)'))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
|
||||
.setFunctionName('gdjs.physics2.setTimeScale');
|
||||
|
||||
aut
|
||||
@@ -775,10 +801,10 @@ module.exports = {
|
||||
.setDefaultValue('true')
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setSleepingAllowed');
|
||||
|
||||
|
||||
// Deprecated action (fixed typo):
|
||||
aut
|
||||
.addDuplicatedAction("SetSleepingaAllowed", "SetSleepingAllowed")
|
||||
.addDuplicatedAction('SetSleepingaAllowed', 'SetSleepingAllowed')
|
||||
.setHidden();
|
||||
|
||||
aut
|
||||
@@ -1390,6 +1416,26 @@ module.exports = {
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('getLinearVelocityAngle');
|
||||
|
||||
aut
|
||||
.addAction(
|
||||
'LinearVelocityAngle',
|
||||
_('Linear velocity towards an angle'),
|
||||
_('Set the linear velocity towards an angle.'),
|
||||
_(
|
||||
'Set the linear velocity of _PARAM0_ towards angle: _PARAM2_ degrees, speed: _PARAM3_ pixels per second'
|
||||
),
|
||||
_('Velocity'),
|
||||
'res/physics32.png',
|
||||
'res/physics32.png'
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Angle'))
|
||||
.addParameter('expression', _('Speed (in pixels per second)'))
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('setLinearVelocityAngle')
|
||||
.setGetter('getLinearVelocityAngle');
|
||||
|
||||
aut
|
||||
.addExpression(
|
||||
'LinearVelocityAngle',
|
||||
@@ -1476,10 +1522,16 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('X component (N)'))
|
||||
.addParameter('expression', _('Y component (N)'))
|
||||
.setParameterLongDescription(_('A force is like an acceleration but depends on the mass.'))
|
||||
.setParameterLongDescription(
|
||||
_('A force is like an acceleration but depends on the mass.')
|
||||
)
|
||||
.addParameter('expression', _('Application point on X axis'))
|
||||
.addParameter('expression', _('Application point on Y axis'))
|
||||
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyForce');
|
||||
|
||||
@@ -1499,10 +1551,16 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Angle'))
|
||||
.addParameter('expression', _('Length (N)'))
|
||||
.setParameterLongDescription(_('A force is like an acceleration but depends on the mass.'))
|
||||
.setParameterLongDescription(
|
||||
_('A force is like an acceleration but depends on the mass.')
|
||||
)
|
||||
.addParameter('expression', _('Application point on X axis'))
|
||||
.addParameter('expression', _('Application point on Y axis'))
|
||||
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyPolarForce');
|
||||
|
||||
@@ -1523,12 +1581,18 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Length (N)'))
|
||||
.setParameterLongDescription(_('A force is like an acceleration but depends on the mass.'))
|
||||
.setParameterLongDescription(
|
||||
_('A force is like an acceleration but depends on the mass.')
|
||||
)
|
||||
.addParameter('expression', _('X position'))
|
||||
.addParameter('expression', _('Y position'))
|
||||
.addParameter('expression', _('Application point on X axis'))
|
||||
.addParameter('expression', _('Application point on Y axis'))
|
||||
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyForceTowardPosition');
|
||||
|
||||
@@ -1546,18 +1610,18 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter(
|
||||
'expression',
|
||||
_('X component (N·s or kg·m·s⁻¹)')
|
||||
.addParameter('expression', _('X component (N·s or kg·m·s⁻¹)'))
|
||||
.addParameter('expression', _('Y component (N·s or kg·m·s⁻¹)'))
|
||||
.setParameterLongDescription(
|
||||
_('An impulse is like a speed addition but depends on the mass.')
|
||||
)
|
||||
.addParameter(
|
||||
'expression',
|
||||
_('Y component (N·s or kg·m·s⁻¹)')
|
||||
)
|
||||
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
|
||||
.addParameter('expression', _('Application point on X axis'))
|
||||
.addParameter('expression', _('Application point on Y axis'))
|
||||
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyImpulse');
|
||||
|
||||
@@ -1578,14 +1642,17 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Angle'))
|
||||
.addParameter(
|
||||
'expression',
|
||||
_('Length (N·s or kg·m·s⁻¹)')
|
||||
.addParameter('expression', _('Length (N·s or kg·m·s⁻¹)'))
|
||||
.setParameterLongDescription(
|
||||
_('An impulse is like a speed addition but depends on the mass.')
|
||||
)
|
||||
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
|
||||
.addParameter('expression', _('Application point on X axis'))
|
||||
.addParameter('expression', _('Application point on Y axis'))
|
||||
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyPolarImpulse');
|
||||
|
||||
@@ -1605,16 +1672,19 @@ module.exports = {
|
||||
)
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter(
|
||||
'expression',
|
||||
_('Length (N·s or kg·m·s⁻¹)')
|
||||
.addParameter('expression', _('Length (N·s or kg·m·s⁻¹)'))
|
||||
.setParameterLongDescription(
|
||||
_('An impulse is like a speed addition but depends on the mass.')
|
||||
)
|
||||
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
|
||||
.addParameter('expression', _('X position'))
|
||||
.addParameter('expression', _('Y position'))
|
||||
.addParameter('expression', _('Application point on X axis'))
|
||||
.addParameter('expression', _('Application point on Y axis'))
|
||||
.setParameterLongDescription(_('Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'Use `MassCenterX` and `MassCenterY` expressions to avoid any rotation.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyImpulseTowardPosition');
|
||||
|
||||
@@ -1633,7 +1703,9 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Torque (N·m)'))
|
||||
.setParameterLongDescription(_('A torque is like a rotation acceleration but depends on the mass.'))
|
||||
.setParameterLongDescription(
|
||||
_('A torque is like a rotation acceleration but depends on the mass.')
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyTorque');
|
||||
|
||||
@@ -1652,7 +1724,11 @@ module.exports = {
|
||||
.addParameter('object', _('Object'), '', false)
|
||||
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
|
||||
.addParameter('expression', _('Angular impulse (N·m·s'))
|
||||
.setParameterLongDescription(_('An impulse is like a rotation speed addition but depends on the mass.'))
|
||||
.setParameterLongDescription(
|
||||
_(
|
||||
'An impulse is like a rotation speed addition but depends on the mass.'
|
||||
)
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setFunctionName('applyAngularImpulse');
|
||||
|
||||
@@ -4019,7 +4095,8 @@ module.exports = {
|
||||
.addParameter('objectList', _('Object'), '', false)
|
||||
.addCodeOnlyParameter('conditionInverted', '')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
|
||||
.setFunctionName('gdjs.physics2.objectsCollide');
|
||||
|
||||
extension
|
||||
@@ -4037,7 +4114,8 @@ module.exports = {
|
||||
.addParameter('objectList', _('Object'), '', false)
|
||||
.addCodeOnlyParameter('conditionInverted', '')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
|
||||
.setFunctionName('gdjs.physics2.haveObjectsStartedColliding');
|
||||
|
||||
extension
|
||||
@@ -4055,16 +4133,14 @@ module.exports = {
|
||||
.addParameter('objectList', _('Object'), '', false)
|
||||
.addCodeOnlyParameter('conditionInverted', '')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2tools.js')
|
||||
.addIncludeFile('Extensions/Physics2Behavior/physics2runtimebehavior.js')
|
||||
.setFunctionName('gdjs.physics2.haveObjectsStoppedColliding');
|
||||
|
||||
return extension;
|
||||
},
|
||||
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
const dummyBehavior = extension
|
||||
.getBehaviorMetadata('Physics2::Physics2Behavior')
|
||||
.get();
|
||||
|
@@ -1429,6 +1429,23 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
setLinearVelocityAngle(angle: float, linearVelocity: float): void {
|
||||
// If there is no body, set a new one
|
||||
if (this._body === null) {
|
||||
if (!this.createBody()) return;
|
||||
}
|
||||
const body = this._body!;
|
||||
|
||||
// Set the linear velocity toward an angle
|
||||
angle = gdjs.toRad(angle);
|
||||
body.SetLinearVelocity(
|
||||
this.b2Vec2(
|
||||
linearVelocity * Math.cos(angle) * this._sharedData.invScaleX,
|
||||
linearVelocity * Math.sin(angle) * this._sharedData.invScaleY
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getAngularVelocity(): float {
|
||||
// If there is no body, set a new one
|
||||
if (this._body === null) {
|
||||
|
@@ -872,6 +872,5 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("objectList", _("Object"), "", false)
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("objectList", _("Platforms"), "", false)
|
||||
.AddCodeOnlyParameter("conditionInverted", "")
|
||||
.SetFunctionName("gdjs.evtTools.platform.isOnPlatform");
|
||||
.AddCodeOnlyParameter("conditionInverted", "");
|
||||
}
|
||||
|
@@ -41,6 +41,17 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
|
||||
.AddIncludeFile(
|
||||
"Extensions/PlatformBehavior/platformtools.js");
|
||||
|
||||
std::map<gd::String, gd::InstructionMetadata>& extConditions =
|
||||
GetAllConditions();
|
||||
extConditions["PlatformBehavior::IsObjectOnGivenFloor"].SetFunctionName(
|
||||
"gdjs.evtTools.platform.isOnPlatform")
|
||||
.AddIncludeFile(
|
||||
"Extensions/PlatformBehavior/platformruntimebehavior.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/PlatformBehavior/platformerobjectruntimebehavior.js")
|
||||
.AddIncludeFile(
|
||||
"Extensions/PlatformBehavior/platformtools.js");
|
||||
|
||||
{
|
||||
std::map<gd::String, gd::InstructionMetadata>& autActions =
|
||||
GetAllActionsForBehavior(
|
||||
|
@@ -1692,13 +1692,15 @@ namespace gdjs {
|
||||
|
||||
beforeUpdatingObstacles(timeDelta: float) {
|
||||
const object = this._behavior.owner;
|
||||
//Stick the object to the floor if its height has changed.
|
||||
// Stick the object to the floor if its height has changed.
|
||||
if (this._oldHeight !== object.getHeight()) {
|
||||
object.setY(
|
||||
this._floorLastY -
|
||||
object.getHeight() +
|
||||
(object.getY() - object.getDrawableY())
|
||||
);
|
||||
// TODO This should probably be done after the events because
|
||||
// the character stays at the wrong place during 1 frame.
|
||||
const deltaY =
|
||||
((this._oldHeight - object.getHeight()) *
|
||||
(object.getHeight() + object.getDrawableY() - object.getY())) /
|
||||
object.getHeight();
|
||||
object.setY(object.getY() + deltaY);
|
||||
}
|
||||
// Directly follow the floor movement on the Y axis by moving the character.
|
||||
// For the X axis, we follow the floor movement using `_requestedDeltaX`
|
||||
|
@@ -262,6 +262,9 @@ namespace gdjs {
|
||||
const behavior1 = object1.getBehavior(
|
||||
behaviorName
|
||||
) as PlatformerObjectRuntimeBehavior;
|
||||
if (!behavior1) {
|
||||
return false;
|
||||
}
|
||||
return behavior1.isOnFloorObject(object2);
|
||||
}
|
||||
}
|
||||
|
@@ -501,6 +501,22 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('can stay on a rotated platform when its height changes', function () {
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
platform.setAngle(-45);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
const oldY = object.getY();
|
||||
expect(object.getY()).to.be.within(-40, -39);
|
||||
|
||||
object.setHeight(object.getHeight() - 8);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be(oldY + 8);
|
||||
});
|
||||
});
|
||||
|
||||
[0, 25].forEach((slopeMaxAngle) => {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -72,9 +64,7 @@ module.exports = {
|
||||
.addAction(
|
||||
'HideAuthenticationBanner',
|
||||
_('Hide authentication banner'),
|
||||
_(
|
||||
'Hide the authentication banner from the top of the game screen.'
|
||||
),
|
||||
_('Hide the authentication banner from the top of the game screen.'),
|
||||
_('Hide the authentication banner'),
|
||||
'',
|
||||
'JsPlatform/Extensions/authentication.svg',
|
||||
@@ -110,7 +100,10 @@ module.exports = {
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
|
||||
)
|
||||
.setFunctionName('gdjs.playerAuthentication.openAuthenticationWindow');
|
||||
.setFunctionName('gdjs.playerAuthentication.openAuthenticationWindow')
|
||||
.setAsyncFunctionName(
|
||||
'gdjs.playerAuthentication.openAuthenticationWindow'
|
||||
);
|
||||
|
||||
extension
|
||||
.addCondition(
|
||||
@@ -226,10 +219,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -5,18 +5,25 @@ namespace gdjs {
|
||||
platform,
|
||||
isGameRegistered,
|
||||
}: {
|
||||
platform: 'cordova' | 'electron' | 'web';
|
||||
platform:
|
||||
| 'cordova'
|
||||
| 'cordova-websocket'
|
||||
| 'electron'
|
||||
| 'web-iframe'
|
||||
| 'web';
|
||||
isGameRegistered: boolean;
|
||||
}) =>
|
||||
isGameRegistered
|
||||
? {
|
||||
title: 'Logging in...',
|
||||
text1:
|
||||
platform === 'cordova'
|
||||
platform === 'cordova' || platform === 'cordova-websocket'
|
||||
? "One moment, we're opening a window for you to log in."
|
||||
: "One moment, we're opening a new page with your web browser for you to log in.",
|
||||
text2:
|
||||
'If the window did not open, please check your pop-up blocker and click the button below to try again.',
|
||||
platform === 'cordova' || platform === 'cordova-websocket'
|
||||
? ''
|
||||
: 'If the window did not open, please check your pop-up blocker and click the button below to try again.',
|
||||
}
|
||||
: {
|
||||
title: 'Publish your game!',
|
||||
@@ -166,9 +173,14 @@ namespace gdjs {
|
||||
*/
|
||||
export const addAuthenticationTextsToLoadingContainer = (
|
||||
loaderContainer: HTMLDivElement,
|
||||
platform,
|
||||
isGameRegistered,
|
||||
wikiOpenAction
|
||||
platform:
|
||||
| 'cordova'
|
||||
| 'cordova-websocket'
|
||||
| 'electron'
|
||||
| 'web-iframe'
|
||||
| 'web',
|
||||
isGameRegistered: boolean,
|
||||
wikiOpenAction: (() => void) | null
|
||||
) => {
|
||||
const textContainer: HTMLDivElement = document.createElement('div');
|
||||
textContainer.id = 'authentication-container-texts';
|
||||
|
@@ -15,7 +15,7 @@ namespace gdjs {
|
||||
|
||||
// Authentication display
|
||||
let _authenticationWindow: Window | null = null; // For Web.
|
||||
let _authenticationInAppWindow: Window | null = null; // For Cordova.
|
||||
let _authenticationInAppWindow: any | null = null; // For Cordova.
|
||||
let _authenticationRootContainer: HTMLDivElement | null = null;
|
||||
let _authenticationLoaderContainer: HTMLDivElement | null = null;
|
||||
let _authenticationIframeContainer: HTMLDivElement | null = null;
|
||||
@@ -28,11 +28,10 @@ namespace gdjs {
|
||||
let _authenticationMessageCallback:
|
||||
| ((event: MessageEvent) => void)
|
||||
| null = null;
|
||||
let _cordovaAuthenticationMessageCallback:
|
||||
| ((event: MessageEvent) => void)
|
||||
| null = null;
|
||||
let _websocket: WebSocket | null = null;
|
||||
|
||||
type AuthenticationWindowStatus = 'logged' | 'errored' | 'dismissed';
|
||||
|
||||
// Ensure that the condition "just logged in" is valid only for one frame.
|
||||
gdjs.registerRuntimeScenePostEventsCallback(() => {
|
||||
_justLoggedIn = false;
|
||||
@@ -43,13 +42,15 @@ namespace gdjs {
|
||||
// Then send a message to the parent iframe to say that the player auth is ready.
|
||||
gdjs.registerFirstRuntimeSceneLoadedCallback(
|
||||
(runtimeScene: RuntimeScene) => {
|
||||
if (getPlatform(runtimeScene) !== 'web') {
|
||||
if (getPlayerAuthPlatform(runtimeScene) !== 'web') {
|
||||
// Automatic authentication is only valid when the game is hosted on GDevelop games platform.
|
||||
return;
|
||||
}
|
||||
removeAuthenticationCallbacks(); // Remove any callback that could have been registered before.
|
||||
_authenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveAuthenticationMessage(runtimeScene, event, {
|
||||
receiveAuthenticationMessage({
|
||||
runtimeScene,
|
||||
event,
|
||||
checkOrigin: true,
|
||||
});
|
||||
};
|
||||
@@ -98,18 +99,40 @@ namespace gdjs {
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper returning the platform.
|
||||
* Get the platform running the game, which changes how the authentication
|
||||
* window is opened.
|
||||
*/
|
||||
const getPlatform = (
|
||||
const getPlayerAuthPlatform = (
|
||||
runtimeScene: RuntimeScene
|
||||
): 'electron' | 'cordova' | 'web' => {
|
||||
): 'electron' | 'cordova' | 'cordova-websocket' | 'web-iframe' | 'web' => {
|
||||
const runtimeGame = runtimeScene.getGame();
|
||||
const electron = runtimeGame.getRenderer().getElectron();
|
||||
if (electron) {
|
||||
// This can be a:
|
||||
// - Preview in GDevelop desktop app.
|
||||
// - Desktop game running on Electron.
|
||||
return 'electron';
|
||||
}
|
||||
if (typeof cordova !== 'undefined') return 'cordova';
|
||||
|
||||
// This can be a:
|
||||
// - Preview in GDevelop mobile app (iOS only)
|
||||
if (shouldAuthenticationUseIframe(runtimeScene)) return 'web-iframe';
|
||||
|
||||
if (typeof cordova !== 'undefined') {
|
||||
if (cordova.platformId === 'ios') {
|
||||
// The game is an iOS app.
|
||||
return 'cordova-websocket';
|
||||
}
|
||||
|
||||
// The game is an Android app.
|
||||
return 'cordova';
|
||||
}
|
||||
|
||||
// This can be a:
|
||||
// - Preview in GDevelop web-app
|
||||
// - Preview in Gdevelop mobile app (Android only)
|
||||
// - Web game (gd.games or any website/server) accessed via a desktop browser...
|
||||
// - Or a web game accessed via a mobile browser (Android/iOS).
|
||||
return 'web';
|
||||
};
|
||||
|
||||
@@ -117,7 +140,8 @@ namespace gdjs {
|
||||
* Check if, in some exceptional cases, we allow authentication
|
||||
* to be done through a iframe.
|
||||
* This is usually discouraged as the user can't verify that the authentication
|
||||
* window is a genuine one. It's only to be used in trusted contexts.
|
||||
* window is a genuine one. It's only to be used in trusted contexts (e.g:
|
||||
* preview in the GDevelop mobile app).
|
||||
*/
|
||||
const shouldAuthenticationUseIframe = (runtimeScene: RuntimeScene) => {
|
||||
const runtimeGameOptions = runtimeScene.getGame().getAdditionalOptions();
|
||||
@@ -279,7 +303,10 @@ namespace gdjs {
|
||||
const cleanUpAuthWindowAndCallbacks = (runtimeScene: RuntimeScene) => {
|
||||
removeAuthenticationContainer(runtimeScene);
|
||||
clearAuthenticationWindowTimeout();
|
||||
|
||||
// If there is a websocket communication (electron, cordova iOS), close it.
|
||||
if (_websocket) {
|
||||
logger.info('Closing authentication websocket connection.');
|
||||
_websocket.close();
|
||||
_websocket = null;
|
||||
}
|
||||
@@ -334,16 +361,17 @@ namespace gdjs {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When the game receives the authentication result, close all the
|
||||
* authentication windows, display the notification and focus on the game.
|
||||
*/
|
||||
const handleLoggedInEvent = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
userId: string,
|
||||
username: string | null,
|
||||
userToken: string
|
||||
) {
|
||||
export const login = ({
|
||||
runtimeScene,
|
||||
userId,
|
||||
username,
|
||||
userToken,
|
||||
}: {
|
||||
runtimeScene: gdjs.RuntimeScene;
|
||||
userId: string;
|
||||
username: string | null;
|
||||
userToken: string;
|
||||
}) => {
|
||||
saveAuthKeyToStorage({ userId, username, userToken });
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
removeAuthenticationBanner(runtimeScene);
|
||||
@@ -363,18 +391,23 @@ namespace gdjs {
|
||||
domElementContainer,
|
||||
_username || 'Anonymous'
|
||||
);
|
||||
focusOnGame(runtimeScene);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads the event sent by the authentication window and
|
||||
* display the appropriate banner.
|
||||
*/
|
||||
const receiveAuthenticationMessage = function (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
event: MessageEvent,
|
||||
{ checkOrigin }: { checkOrigin: boolean }
|
||||
) {
|
||||
const receiveAuthenticationMessage = function ({
|
||||
runtimeScene,
|
||||
event,
|
||||
checkOrigin,
|
||||
onDone,
|
||||
}: {
|
||||
runtimeScene: gdjs.RuntimeScene;
|
||||
event: MessageEvent;
|
||||
checkOrigin: boolean;
|
||||
onDone?: (status: 'logged' | 'errored' | 'dismissed') => void;
|
||||
}) {
|
||||
const allowedOrigins = ['https://liluo.io', 'https://gd.games'];
|
||||
|
||||
// Check origin of message.
|
||||
@@ -394,12 +427,14 @@ namespace gdjs {
|
||||
throw new Error('Malformed message.');
|
||||
}
|
||||
|
||||
handleLoggedInEvent(
|
||||
login({
|
||||
runtimeScene,
|
||||
event.data.body.userId,
|
||||
event.data.body.username,
|
||||
event.data.body.token
|
||||
);
|
||||
userId: event.data.body.userId,
|
||||
username: event.data.body.username,
|
||||
userToken: event.data.body.token,
|
||||
});
|
||||
focusOnGame(runtimeScene);
|
||||
if (onDone) onDone('logged');
|
||||
break;
|
||||
}
|
||||
case 'alreadyAuthenticated': {
|
||||
@@ -452,7 +487,7 @@ namespace gdjs {
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) => {
|
||||
clearAuthenticationWindowTimeout();
|
||||
const time = 12 * 60 * 1000; // 12 minutes, in case the user needs time to authenticate.
|
||||
const time = 15 * 60 * 1000; // 15 minutes, in case the user needs time to authenticate.
|
||||
_authenticationTimeoutId = setTimeout(() => {
|
||||
logger.info(
|
||||
'Authentication window did not send message in time. Closing it.'
|
||||
@@ -487,6 +522,7 @@ namespace gdjs {
|
||||
const onOpenAuthenticationWindow = () => {
|
||||
openAuthenticationWindow(runtimeScene);
|
||||
};
|
||||
|
||||
return _userToken
|
||||
? authComponents.computeAuthenticatedBanner(
|
||||
onOpenAuthenticationWindow,
|
||||
@@ -558,6 +594,84 @@ namespace gdjs {
|
||||
);
|
||||
};
|
||||
|
||||
const setupWebsocketForAuthenticationWindow = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
onOpenAuthenticationWindow: (options: {
|
||||
connectionId: string;
|
||||
resolve: (AuthenticationWindowStatus) => void;
|
||||
}) => void
|
||||
) =>
|
||||
new Promise<AuthenticationWindowStatus>((resolve) => {
|
||||
let hasFinishedAlready = false;
|
||||
const wsPlayApi = runtimeScene
|
||||
.getGame()
|
||||
.isUsingGDevelopDevelopmentEnvironment()
|
||||
? 'wss://api-ws-dev.gdevelop.io/play'
|
||||
: 'wss://api-ws.gdevelop.io/play';
|
||||
_websocket = new WebSocket(wsPlayApi);
|
||||
_websocket.onopen = () => {
|
||||
logger.info('Opened authentication websocket connection.');
|
||||
// When socket is open, ask for the connectionId, so that we can open the authentication window.
|
||||
if (_websocket) {
|
||||
_websocket.send(JSON.stringify({ action: 'getConnectionId' }));
|
||||
}
|
||||
};
|
||||
_websocket.onerror = () => {
|
||||
logger.info('Error in authentication websocket connection.');
|
||||
if (!hasFinishedAlready) {
|
||||
hasFinishedAlready = true;
|
||||
resolve('errored');
|
||||
}
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'Error while connecting to the authentication server.'
|
||||
);
|
||||
};
|
||||
_websocket.onclose = () => {
|
||||
logger.info('Closing authentication websocket connection.');
|
||||
if (!hasFinishedAlready) {
|
||||
hasFinishedAlready = true;
|
||||
resolve('dismissed');
|
||||
}
|
||||
};
|
||||
_websocket.onmessage = (event) => {
|
||||
if (event.data) {
|
||||
const messageContent = JSON.parse(event.data);
|
||||
switch (messageContent.type) {
|
||||
case 'authenticationResult': {
|
||||
const messageData = messageContent.data;
|
||||
|
||||
login({
|
||||
runtimeScene,
|
||||
userId: messageData.userId,
|
||||
username: messageData.username,
|
||||
userToken: messageData.token,
|
||||
});
|
||||
focusOnGame(runtimeScene);
|
||||
|
||||
hasFinishedAlready = true;
|
||||
resolve('logged');
|
||||
break;
|
||||
}
|
||||
case 'connectionId': {
|
||||
const messageData = messageContent.data;
|
||||
const connectionId = messageData.connectionId;
|
||||
if (!connectionId) {
|
||||
logger.error('No WebSocket connectionId received');
|
||||
hasFinishedAlready = true;
|
||||
resolve('errored');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('WebSocket connectionId received.');
|
||||
onOpenAuthenticationWindow({ connectionId, resolve });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on Electron.
|
||||
* We open a new window, and create a websocket to know when the user is logged in.
|
||||
@@ -565,75 +679,67 @@ namespace gdjs {
|
||||
const openAuthenticationWindowForElectron = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
const wsPlayApi = runtimeScene
|
||||
.getGame()
|
||||
.isUsingGDevelopDevelopmentEnvironment()
|
||||
? 'wss://api-ws-dev.gdevelop.io/play'
|
||||
: 'wss://api-ws.gdevelop.io/play';
|
||||
_websocket = new WebSocket(wsPlayApi);
|
||||
_websocket.onopen = () => {
|
||||
// When socket is open, ask for the connectionId, so that we can open the authentication window.
|
||||
if (_websocket) {
|
||||
_websocket.send(JSON.stringify({ action: 'getConnectionId' }));
|
||||
}
|
||||
};
|
||||
_websocket.onerror = () => {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'Error while connecting to the authentication server.'
|
||||
);
|
||||
};
|
||||
_websocket.onmessage = (event) => {
|
||||
if (event.data) {
|
||||
const messageContent = JSON.parse(event.data);
|
||||
switch (messageContent.type) {
|
||||
case 'authenticationResult': {
|
||||
const messageData = messageContent.data;
|
||||
handleLoggedInEvent(
|
||||
runtimeScene,
|
||||
messageData.userId,
|
||||
messageData.username,
|
||||
messageData.token
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'connectionId': {
|
||||
const messageData = messageContent.data;
|
||||
const connectionId = messageData.connectionId;
|
||||
if (!connectionId) {
|
||||
logger.error('No connectionId received');
|
||||
return;
|
||||
}
|
||||
) =>
|
||||
setupWebsocketForAuthenticationWindow(
|
||||
runtimeScene,
|
||||
({ connectionId }) => {
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
connectionId,
|
||||
});
|
||||
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
connectionId,
|
||||
});
|
||||
const electron = runtimeScene.getGame().getRenderer().getElectron();
|
||||
const openWindow = () => electron.shell.openExternal(targetUrl);
|
||||
|
||||
const electron = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectron();
|
||||
const openWindow = () => electron.shell.openExternal(targetUrl);
|
||||
openWindow();
|
||||
|
||||
openWindow();
|
||||
|
||||
// Add the link to the window in case a popup blocker is preventing the window from opening.
|
||||
if (_authenticationTextContainer) {
|
||||
authComponents.addAuthenticationUrlToTextsContainer(
|
||||
openWindow,
|
||||
_authenticationTextContainer
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// Add the link to the window in case a popup blocker is preventing the window from opening.
|
||||
if (_authenticationTextContainer) {
|
||||
authComponents.addAuthenticationUrlToTextsContainer(
|
||||
openWindow,
|
||||
_authenticationTextContainer
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
);
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on Cordova on iOS.
|
||||
* We open an InAppBrowser window, and listen to the websocket to know when the user is logged in.
|
||||
*/
|
||||
const openAuthenticationWindowForCordovaWithWebSocket = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) =>
|
||||
setupWebsocketForAuthenticationWindow(
|
||||
runtimeScene,
|
||||
({ connectionId, resolve }) => {
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
connectionId,
|
||||
});
|
||||
|
||||
_authenticationInAppWindow = cordova.InAppBrowser.open(
|
||||
targetUrl,
|
||||
'authentication',
|
||||
'location=yes,toolbarcolor=#000000,hidenavigationbuttons=yes,closebuttoncolor=#FFFFFF' // location=yes is important to show the URL bar to the user.
|
||||
);
|
||||
if (!_authenticationInAppWindow) {
|
||||
resolve('errored');
|
||||
return;
|
||||
}
|
||||
|
||||
_authenticationInAppWindow.addEventListener(
|
||||
'exit',
|
||||
() => {
|
||||
resolve('dismissed');
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on Cordova.
|
||||
@@ -642,32 +748,52 @@ namespace gdjs {
|
||||
const openAuthenticationWindowForCordova = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
) =>
|
||||
new Promise<AuthenticationWindowStatus>((resolve) => {
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
|
||||
_authenticationInAppWindow = cordova.InAppBrowser.open(
|
||||
targetUrl,
|
||||
'authentication',
|
||||
'location=yes' // location=yes is important to show the URL bar to the user.
|
||||
);
|
||||
// Listen to messages posted on the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
if (_authenticationInAppWindow) {
|
||||
_cordovaAuthenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveAuthenticationMessage(runtimeScene, event, {
|
||||
checkOrigin: false, // For Cordova we don't check the origin, as the message is read from the InAppBrowser directly.
|
||||
});
|
||||
};
|
||||
_authenticationInAppWindow = cordova.InAppBrowser.open(
|
||||
targetUrl,
|
||||
'authentication',
|
||||
'location=yes,toolbarcolor=#000000,hidenavigationbuttons=yes,closebuttoncolor=#FFFFFF' // location=yes is important to show the URL bar to the user.
|
||||
);
|
||||
if (!_authenticationInAppWindow) {
|
||||
resolve('errored');
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen to messages posted on the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
let isDoneAlready = false;
|
||||
_authenticationInAppWindow.addEventListener(
|
||||
'message',
|
||||
_cordovaAuthenticationMessageCallback,
|
||||
(event: MessageEvent) => {
|
||||
receiveAuthenticationMessage({
|
||||
runtimeScene,
|
||||
event,
|
||||
checkOrigin: false, // For Cordova we don't check the origin, as the message is read from the InAppBrowser directly.
|
||||
onDone: (status) => {
|
||||
if (isDoneAlready) return;
|
||||
isDoneAlready = true;
|
||||
resolve(status);
|
||||
},
|
||||
});
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
};
|
||||
_authenticationInAppWindow.addEventListener(
|
||||
'exit',
|
||||
() => {
|
||||
if (isDoneAlready) return;
|
||||
isDoneAlready = true;
|
||||
resolve('dismissed');
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to handle authentication window on web.
|
||||
@@ -676,37 +802,56 @@ namespace gdjs {
|
||||
const openAuthenticationWindowForWeb = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
// If we're on a browser, open a new window.
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
|
||||
// Listen to messages posted by the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
_authenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveAuthenticationMessage(runtimeScene, event, {
|
||||
checkOrigin: true,
|
||||
) =>
|
||||
new Promise<AuthenticationWindowStatus>((resolve) => {
|
||||
// If we're on a browser, open a new window.
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
};
|
||||
window.addEventListener('message', _authenticationMessageCallback, true);
|
||||
|
||||
const left = screen.width / 2 - 500 / 2;
|
||||
const top = screen.height / 2 - 600 / 2;
|
||||
const windowFeatures = `left=${left},top=${top},width=500,height=600`;
|
||||
const openWindow = () =>
|
||||
window.open(targetUrl, 'authentication', windowFeatures);
|
||||
_authenticationWindow = openWindow();
|
||||
|
||||
// Add the link to the window in case a popup blocker is preventing the window from opening.
|
||||
if (_authenticationTextContainer) {
|
||||
authComponents.addAuthenticationUrlToTextsContainer(
|
||||
openWindow,
|
||||
_authenticationTextContainer
|
||||
// Listen to messages posted by the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
let isDoneAlready = false;
|
||||
_authenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveAuthenticationMessage({
|
||||
runtimeScene,
|
||||
event,
|
||||
checkOrigin: true,
|
||||
onDone: (status) => {
|
||||
if (isDoneAlready) return;
|
||||
isDoneAlready = true;
|
||||
resolve(status);
|
||||
},
|
||||
});
|
||||
};
|
||||
window.addEventListener(
|
||||
'message',
|
||||
_authenticationMessageCallback,
|
||||
true
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const left = screen.width / 2 - 500 / 2;
|
||||
const top = screen.height / 2 - 600 / 2;
|
||||
const windowFeatures = `left=${left},top=${top},width=500,height=600`;
|
||||
const openWindow = () => {
|
||||
_authenticationWindow = window.open(
|
||||
targetUrl,
|
||||
'authentication',
|
||||
windowFeatures
|
||||
);
|
||||
};
|
||||
|
||||
openWindow();
|
||||
|
||||
// Add the link to the window in case a popup blocker is preventing the window from opening.
|
||||
if (_authenticationTextContainer) {
|
||||
authComponents.addAuthenticationUrlToTextsContainer(
|
||||
openWindow,
|
||||
_authenticationTextContainer
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to handle authentication iframe on web.
|
||||
@@ -715,143 +860,197 @@ namespace gdjs {
|
||||
const openAuthenticationIframeForWeb = (
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
gameId: string
|
||||
) => {
|
||||
if (
|
||||
!_authenticationIframeContainer ||
|
||||
!_authenticationLoaderContainer ||
|
||||
!_authenticationTextContainer
|
||||
) {
|
||||
console.error(
|
||||
"Can't open an authentication iframe - no iframe container, loader container or text container was opened for it."
|
||||
);
|
||||
return;
|
||||
}
|
||||
) =>
|
||||
new Promise<AuthenticationWindowStatus>((resolve) => {
|
||||
if (
|
||||
!_authenticationIframeContainer ||
|
||||
!_authenticationLoaderContainer ||
|
||||
!_authenticationTextContainer
|
||||
) {
|
||||
console.error(
|
||||
"Can't open an authentication iframe - no iframe container, loader container or text container was opened for it."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
|
||||
// Listen to messages posted by the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
_authenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveAuthenticationMessage(runtimeScene, event, {
|
||||
checkOrigin: true,
|
||||
const targetUrl = getAuthWindowUrl({
|
||||
runtimeGame: runtimeScene.getGame(),
|
||||
gameId,
|
||||
});
|
||||
};
|
||||
window.addEventListener('message', _authenticationMessageCallback, true);
|
||||
|
||||
authComponents.displayIframeInsideAuthenticationContainer(
|
||||
_authenticationIframeContainer,
|
||||
_authenticationLoaderContainer,
|
||||
_authenticationTextContainer,
|
||||
targetUrl
|
||||
);
|
||||
};
|
||||
// Listen to messages posted by the authentication window, so that we can
|
||||
// know when the user is authenticated.
|
||||
_authenticationMessageCallback = (event: MessageEvent) => {
|
||||
receiveAuthenticationMessage({
|
||||
runtimeScene,
|
||||
event,
|
||||
checkOrigin: true,
|
||||
onDone: resolve,
|
||||
});
|
||||
};
|
||||
window.addEventListener(
|
||||
'message',
|
||||
_authenticationMessageCallback,
|
||||
true
|
||||
);
|
||||
|
||||
authComponents.displayIframeInsideAuthenticationContainer(
|
||||
_authenticationIframeContainer,
|
||||
_authenticationLoaderContainer,
|
||||
_authenticationTextContainer,
|
||||
targetUrl
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Action to display the authentication window to the user.
|
||||
*/
|
||||
export const openAuthenticationWindow = function (
|
||||
export const openAuthenticationWindow = (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
) {
|
||||
// Create the authentication container for the player to wait.
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication window cannot be displayed."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const onAuthenticationContainerDismissed = () => {
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
displayAuthenticationBanner(runtimeScene);
|
||||
};
|
||||
|
||||
const _gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!_gameId) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'The game ID is missing, the authentication window cannot be opened.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the banner is displayed, hide it, so that it can be shown again if the user closes the window.
|
||||
if (_authenticationBanner) _authenticationBanner.style.opacity = '0';
|
||||
|
||||
const platform = getPlatform(runtimeScene);
|
||||
const {
|
||||
rootContainer,
|
||||
loaderContainer,
|
||||
iframeContainer,
|
||||
} = authComponents.computeAuthenticationContainer(
|
||||
onAuthenticationContainerDismissed
|
||||
);
|
||||
_authenticationRootContainer = rootContainer;
|
||||
_authenticationLoaderContainer = loaderContainer;
|
||||
_authenticationIframeContainer = iframeContainer;
|
||||
|
||||
// Display the authentication window right away, to show a loader
|
||||
// while the call for game registration is happening.
|
||||
domElementContainer.appendChild(_authenticationRootContainer);
|
||||
|
||||
// If the game is registered, open the authentication window.
|
||||
// Otherwise, open the window indicating that the game is not registered.
|
||||
checkIfGameIsRegistered(runtimeScene.getGame(), _gameId)
|
||||
.then((isGameRegistered) => {
|
||||
if (_authenticationLoaderContainer) {
|
||||
const electron = runtimeScene.getGame().getRenderer().getElectron();
|
||||
const wikiOpenAction = electron
|
||||
? () =>
|
||||
electron.shell.openExternal(
|
||||
'https://wiki.gdevelop.io/gdevelop5/publishing/web'
|
||||
)
|
||||
: null; // Only show a link if we're on electron.
|
||||
|
||||
_authenticationTextContainer = authComponents.addAuthenticationTextsToLoadingContainer(
|
||||
_authenticationLoaderContainer,
|
||||
platform,
|
||||
isGameRegistered,
|
||||
wikiOpenAction
|
||||
): gdjs.PromiseTask<{ status: 'logged' | 'errored' | 'dismissed' }> =>
|
||||
new gdjs.PromiseTask(
|
||||
new Promise((resolve) => {
|
||||
// Create the authentication container for the player to wait.
|
||||
const domElementContainer = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getDomElementContainer();
|
||||
if (!domElementContainer) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
"The div element covering the game couldn't be found, the authentication window cannot be displayed."
|
||||
);
|
||||
resolve({ status: 'errored' });
|
||||
return;
|
||||
}
|
||||
if (isGameRegistered) {
|
||||
|
||||
const _gameId = gdjs.projectData.properties.projectUuid;
|
||||
if (!_gameId) {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'The game ID is missing, the authentication window cannot be opened.'
|
||||
);
|
||||
resolve({ status: 'errored' });
|
||||
return;
|
||||
}
|
||||
|
||||
let isDimissedAlready = false;
|
||||
const onAuthenticationContainerDismissed = () => {
|
||||
cleanUpAuthWindowAndCallbacks(runtimeScene);
|
||||
displayAuthenticationBanner(runtimeScene);
|
||||
|
||||
isDimissedAlready = true;
|
||||
resolve({ status: 'dismissed' });
|
||||
};
|
||||
|
||||
// If the banner is displayed, hide it, so that it can be shown again if the user closes the window.
|
||||
if (_authenticationBanner) _authenticationBanner.style.opacity = '0';
|
||||
|
||||
const playerAuthPlatform = getPlayerAuthPlatform(runtimeScene);
|
||||
const {
|
||||
rootContainer,
|
||||
loaderContainer,
|
||||
iframeContainer,
|
||||
} = authComponents.computeAuthenticationContainer(
|
||||
onAuthenticationContainerDismissed
|
||||
);
|
||||
_authenticationRootContainer = rootContainer;
|
||||
_authenticationLoaderContainer = loaderContainer;
|
||||
_authenticationIframeContainer = iframeContainer;
|
||||
|
||||
// Display the authentication window right away, to show a loader
|
||||
// while the call for game registration is happening.
|
||||
domElementContainer.appendChild(_authenticationRootContainer);
|
||||
|
||||
// If the game is registered, open the authentication window.
|
||||
// Otherwise, open the window indicating that the game is not registered.
|
||||
(async () => {
|
||||
const isGameRegistered = await checkIfGameIsRegistered(
|
||||
runtimeScene.getGame(),
|
||||
_gameId
|
||||
);
|
||||
|
||||
if (_authenticationLoaderContainer) {
|
||||
const electron = runtimeScene
|
||||
.getGame()
|
||||
.getRenderer()
|
||||
.getElectron();
|
||||
const wikiOpenAction = electron
|
||||
? () =>
|
||||
electron.shell.openExternal(
|
||||
'https://wiki.gdevelop.io/gdevelop5/publishing/web'
|
||||
)
|
||||
: null; // Only show a link if we're on electron.
|
||||
|
||||
_authenticationTextContainer = authComponents.addAuthenticationTextsToLoadingContainer(
|
||||
_authenticationLoaderContainer,
|
||||
playerAuthPlatform,
|
||||
isGameRegistered,
|
||||
wikiOpenAction
|
||||
);
|
||||
}
|
||||
if (!isGameRegistered) return;
|
||||
|
||||
startAuthenticationWindowTimeout(runtimeScene);
|
||||
|
||||
// Based on which platform the game is running, we open the authentication window
|
||||
// with a different window, with or without a websocket.
|
||||
switch (platform) {
|
||||
let status: AuthenticationWindowStatus;
|
||||
switch (playerAuthPlatform) {
|
||||
case 'electron':
|
||||
openAuthenticationWindowForElectron(runtimeScene, _gameId);
|
||||
// This can be a:
|
||||
// - Preview in GDevelop desktop app.
|
||||
// - Desktop game running on Electron.
|
||||
status = await openAuthenticationWindowForElectron(
|
||||
runtimeScene,
|
||||
_gameId
|
||||
);
|
||||
break;
|
||||
case 'cordova':
|
||||
openAuthenticationWindowForCordova(runtimeScene, _gameId);
|
||||
// The game is an Android app.
|
||||
status = await openAuthenticationWindowForCordova(
|
||||
runtimeScene,
|
||||
_gameId
|
||||
);
|
||||
break;
|
||||
case 'cordova-websocket':
|
||||
// The game is an iOS app.
|
||||
status = await openAuthenticationWindowForCordovaWithWebSocket(
|
||||
runtimeScene,
|
||||
_gameId
|
||||
);
|
||||
break;
|
||||
case 'web-iframe':
|
||||
// This can be a:
|
||||
// - Preview in GDevelop mobile app (iOS only)
|
||||
status = await openAuthenticationIframeForWeb(
|
||||
runtimeScene,
|
||||
_gameId
|
||||
);
|
||||
break;
|
||||
case 'web':
|
||||
default:
|
||||
if (shouldAuthenticationUseIframe(runtimeScene)) {
|
||||
openAuthenticationIframeForWeb(runtimeScene, _gameId);
|
||||
} else {
|
||||
openAuthenticationWindowForWeb(runtimeScene, _gameId);
|
||||
}
|
||||
// This can be a:
|
||||
// - Preview in GDevelop web-app
|
||||
// - Preview in Gdevelop mobile app (Android only)
|
||||
// - Web game (gd.games or any website/server) accessed via a desktop browser...
|
||||
// - Or a web game accessed via a mobile browser (Android/iOS).
|
||||
status = await openAuthenticationWindowForWeb(
|
||||
runtimeScene,
|
||||
_gameId
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDimissedAlready) return;
|
||||
if (status === 'dismissed') {
|
||||
onAuthenticationContainerDismissed();
|
||||
}
|
||||
|
||||
resolve({ status });
|
||||
})();
|
||||
})
|
||||
.catch((error) => {
|
||||
handleAuthenticationError(
|
||||
runtimeScene,
|
||||
'Error while checking if the game is registered.'
|
||||
);
|
||||
logger.error(error);
|
||||
});
|
||||
};
|
||||
);
|
||||
|
||||
/**
|
||||
* Condition to check if the window is open, so that the game can be paused in the background.
|
||||
@@ -901,8 +1100,6 @@ namespace gdjs {
|
||||
true
|
||||
);
|
||||
_authenticationMessageCallback = null;
|
||||
// No need to detach the callback from the InAppBrowser, as it's destroyed when the window is closed.
|
||||
_cordovaAuthenticationMessageCallback = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -124,6 +124,25 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("expression", _("The height of the ellipse"))
|
||||
.SetFunctionName("DrawEllipse");
|
||||
|
||||
obj.AddAction("FilletRectangle",
|
||||
_("Fillet Rectangle"),
|
||||
_("Draw a fillet rectangle on screen"),
|
||||
_("Draw from _PARAM1_;_PARAM2_ to _PARAM3_;_PARAM4_ a fillet "
|
||||
"rectangle (fillet: _PARAM5_)"
|
||||
"with _PARAM0_"),
|
||||
_("Drawing"),
|
||||
"res/actions/filletRectangle24.png",
|
||||
"res/actions/filletRectangle.png")
|
||||
|
||||
.AddParameter("object", _("Shape Painter object"), "Drawer")
|
||||
.AddParameter("expression", _("Left X position"))
|
||||
.AddParameter("expression", _("Top Y position"))
|
||||
.AddParameter("expression", _("Right X position"))
|
||||
.AddParameter("expression", _("Bottom Y position"))
|
||||
.AddParameter("expression", _("Fillet (in pixels)"))
|
||||
.SetFunctionName("DrawFilletRectangle");
|
||||
|
||||
|
||||
obj.AddAction("RoundedRectangle",
|
||||
_("Rounded rectangle"),
|
||||
_("Draw a rounded rectangle on screen"),
|
||||
|
@@ -47,6 +47,11 @@ class PrimitiveDrawingJsExtension : public gd::PlatformExtension {
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Ellipse"]
|
||||
.SetFunctionName("drawEllipse");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::FilletRectangle"]
|
||||
.SetFunctionName("drawFilletRectangle");
|
||||
|
||||
GetAllActionsForObject(
|
||||
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::RoundedRectangle"]
|
||||
.SetFunctionName("drawRoundedRectangle");
|
||||
|
@@ -136,6 +136,25 @@ namespace gdjs {
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawFilletRectangle(
|
||||
x1: float,
|
||||
y1: float,
|
||||
x2: float,
|
||||
y2: float,
|
||||
fillet: float
|
||||
) {
|
||||
this.updateOutline();
|
||||
this._graphics.beginFill(
|
||||
this._object._fillColor,
|
||||
this._object._fillOpacity / 255
|
||||
);
|
||||
//@ts-ignore from @pixi/graphics-extras
|
||||
this._graphics.drawFilletRect(x1, y1, x2 - x1, y2 - y1, fillet);
|
||||
this._graphics.closePath();
|
||||
this._graphics.endFill();
|
||||
this.invalidateBounds();
|
||||
}
|
||||
|
||||
drawChamferRectangle(
|
||||
x1: float,
|
||||
y1: float,
|
||||
|
@@ -210,6 +210,22 @@ namespace gdjs {
|
||||
this._renderer.drawEllipse(centerX, centerY, width, height);
|
||||
}
|
||||
|
||||
drawFilletRectangle(
|
||||
startX1: float,
|
||||
startY1: float,
|
||||
endX2: float,
|
||||
endY2: float,
|
||||
fillet: float
|
||||
) {
|
||||
this._renderer.drawFilletRectangle(
|
||||
startX1,
|
||||
startY1,
|
||||
endX2,
|
||||
endY2,
|
||||
fillet
|
||||
);
|
||||
}
|
||||
|
||||
drawRoundedRectangle(
|
||||
startX1: float,
|
||||
startY1: float,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -59,10 +51,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
//@ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
extension
|
||||
.setExtensionInformation(
|
||||
@@ -83,10 +75,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
// @ts-check
|
||||
/// <reference path="../JsExtensionTypes.d.ts" />
|
||||
/**
|
||||
* This is a declaration of an extension for GDevelop 5.
|
||||
*
|
||||
@@ -12,18 +13,9 @@
|
||||
* 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'
|
||||
*/
|
||||
|
||||
/** @type {ExtensionModule} */
|
||||
module.exports = {
|
||||
createExtension: function (
|
||||
_ /*: (string) => string */,
|
||||
gd /*: libGDevelop */
|
||||
) {
|
||||
createExtension: function (_, gd) {
|
||||
const extension = new gd.PlatformExtension();
|
||||
|
||||
extension
|
||||
@@ -94,10 +86,7 @@ module.exports = {
|
||||
* But it is recommended to create tests for the behaviors/objects properties you created
|
||||
* to avoid mistakes.
|
||||
*/
|
||||
runExtensionSanityTests: function (
|
||||
gd /*: libGDevelop */,
|
||||
extension /*: gdPlatformExtension*/
|
||||
) {
|
||||
runExtensionSanityTests: function (gd, extension) {
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
@@ -105,17 +94,13 @@ module.exports = {
|
||||
*
|
||||
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
|
||||
*/
|
||||
registerEditorConfigurations: function (
|
||||
objectsEditorService /*: ObjectsEditorService */
|
||||
) {},
|
||||
registerEditorConfigurations: function (objectsEditorService) {},
|
||||
/**
|
||||
* 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 */
|
||||
) {
|
||||
registerInstanceRenderers: function (objectsRenderingService) {
|
||||
const { PIXI, RenderedInstance, gd } = objectsRenderingService;
|
||||
|
||||
class RenderedSpineInstance extends RenderedInstance {
|
||||
@@ -163,6 +148,9 @@ module.exports = {
|
||||
this._instance.getX(),
|
||||
this._instance.getY()
|
||||
);
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
this._instance.getAngle()
|
||||
);
|
||||
|
||||
this.setAnimation(this._instance.getRawDoubleProperty('animation'));
|
||||
|
||||
@@ -203,6 +191,14 @@ module.exports = {
|
||||
return -this._spineOriginOffsetY;
|
||||
}
|
||||
|
||||
getCenterX() {
|
||||
return this.getOriginX();
|
||||
}
|
||||
|
||||
getCenterY() {
|
||||
return this.getOriginY();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index - animation index
|
||||
*/
|
||||
@@ -228,7 +224,6 @@ module.exports = {
|
||||
const animation = configuration.getAnimation(index);
|
||||
const source = animation.getSource();
|
||||
const shouldLoop = animation.shouldLoop();
|
||||
const scale = this.getScale();
|
||||
|
||||
// reset scale to track new animation range
|
||||
// if custom size is set it will be reinitialized in update method
|
||||
|
@@ -138,16 +138,15 @@ namespace gdjs {
|
||||
this._loadedSpineAtlases.set(resource, atlas);
|
||||
callback(null, atlas);
|
||||
};
|
||||
const url = this._resourceLoader.getFullUrl(resource.file);
|
||||
|
||||
PIXI.Assets.setPreferences({
|
||||
preferWorkers: false,
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(
|
||||
resource.file
|
||||
)
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
|
||||
? 'use-credentials'
|
||||
: 'anonymous',
|
||||
});
|
||||
PIXI.Assets.add(resource.name, resource.file, { images });
|
||||
PIXI.Assets.add(resource.name, url, { images });
|
||||
PIXI.Assets.load<pixi_spine.TextureAtlas | string>(resource.name).then(
|
||||
(atlas) => {
|
||||
/**
|
||||
|
@@ -65,15 +65,14 @@ namespace gdjs {
|
||||
const spineAtlas = await this._spineAtlasManager.getOrLoad(
|
||||
atlasResourceName
|
||||
);
|
||||
const url = this._resourceLoader.getFullUrl(resource.file);
|
||||
PIXI.Assets.setPreferences({
|
||||
preferWorkers: false,
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(
|
||||
resource.file
|
||||
)
|
||||
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
|
||||
? 'use-credentials'
|
||||
: 'anonymous',
|
||||
});
|
||||
PIXI.Assets.add(resource.name, resource.file, { spineAtlas });
|
||||
PIXI.Assets.add(resource.name, url, { spineAtlas });
|
||||
const loadedJson = await PIXI.Assets.load(resource.name);
|
||||
|
||||
if (loadedJson.spineData) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user