Compare commits

...

31 Commits

Author SHA1 Message Date
Davy Hélard
689676e058 Review changes: add a test with create and return object actions. 2023-11-18 17:28:29 +01:00
Davy Hélard
1ed6b4889a Move the action declaration next to other "return" actions. 2023-10-29 17:00:47 +01:00
Davy Hélard
ccec9e9b8b Wording 2023-10-29 17:00:06 +01:00
Davy Hélard
2a036368f5 Remove only. 2023-10-29 16:46:08 +01:00
Davy Hélard
3bc8b1839f Add tests. 2023-10-29 16:43:51 +01:00
Davy Hélard
d8f2ced5f5 Format 2023-10-28 16:27:25 +02:00
Davy Hélard
5373406177 Fix a crash when the pick action is used in a function called from another function. 2023-10-28 16:27:24 +02:00
Davy Hélard
3d1142f7c7 Add line breaks. 2023-10-28 16:27:24 +02:00
Davy Hélard
2d7e6248f7 Use the new way for context. 2023-10-28 16:27:24 +02:00
Arthur Pacaud
85dd04e430 Don't use Object.values" 2023-10-28 16:27:24 +02:00
Arthur Pacaud
c25252ce33 Fix objects groups inside of functions 2023-10-28 16:27:23 +02:00
Arthur Pacaud
685b2105e1 Fix object being added to wring lists when a group is passed 2023-10-28 16:27:23 +02:00
Arthur Pacaud
cd0bfa9281 Add action to return objects 2023-10-28 16:27:23 +02:00
D8H
297fade0bd Remove unused imports (#5844)
Don't show in changelog
2023-10-28 14:35:46 +02:00
D8H
84d7500eab Add the 3D particle emitter in the object list (#5840) 2023-10-27 11:51:05 +02:00
D8H
f99c4ab948 Move the Z-order action and condition to the "Layer" group (#5834) 2023-10-26 11:02:47 +02:00
D8H
5fe4f23c83 Improve the search to give as many results as possible (#5839) 2023-10-26 11:01:57 +02:00
Florian Rival
8820bd45e4 Fix typo 2023-10-26 09:09:55 +02:00
D8H
5e16968f37 Fix "Scale Z" group in actions/conditions editor (#5835) 2023-10-25 18:09:39 +02:00
TRP
6b6179ff22 Add new shape in Shape Painter: Chamfer rectangle (#5817) 2023-10-25 17:55:42 +02:00
AlexandreS
94bcd87a9f Add stop method on video and use it when object is destroyed (#5833)
Fixes bug where video are not restarted when switching scenes
2023-10-25 13:15:10 +02:00
D8H
356a1974ef Improve descriptions for impulse and force actions of the Physics behavior (#5832) 2023-10-25 12:38:09 +02:00
AlexandreS
345bad9876 Clean forgotten import (#5831)
Don't show in changelog
2023-10-25 11:17:54 +02:00
AlexandreS
0b8d843a73 Mobile version optimizations (#5830)
Fixes a bug where scene toolbar does not update on instance selection.
Don't show in changelog
2023-10-25 09:35:45 +02:00
D8H
9f0c987ec7 Fix scale actions group (#5825) 2023-10-23 17:05:11 +02:00
D8H
0e79209d2b Fix a regression on the object selection in the instruction editor (#5824)
- don't show in changelog
2023-10-23 12:26:57 +02:00
D8H
f991c09c39 Fix a crash when only the Ease expression is used from Tween extension (#5821) 2023-10-23 09:57:22 +02:00
D8H
6e24dfa9b8 Fix object selection for deprecated instructions (#5816) 2023-10-20 17:13:20 +02:00
D8H
a3cd00dc94 Fix a typo in the scale tween action (#5815) 2023-10-20 15:47:29 +02:00
Florian Rival
300c011151 Bump newIDE version (#5813) 2023-10-20 15:24:18 +02:00
D8H
230d410469 Fix missing exported files for scene tweens (#5812) 2023-10-20 15:20:16 +02:00
44 changed files with 667 additions and 155 deletions

View File

@@ -46,10 +46,6 @@ void EffectsCodeGenerator::GenerateEffectsIncludeFiles(
// TODO Add unit tests on this function.
// TODO Merge with UsedExtensionsFinder.
// Default lights rely on the fact that UsedExtensionsFinder doesn't find
// extension usages for effects. This has the happy side effect of not
// including Three.js when no 3D object are in the scenes.
// We need to make something explicit to avoid future bugs.
// See also gd::Project::ExposeResources for a method that traverse the whole
// project (this time for resources) and

View File

@@ -66,6 +66,19 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
.SetRelevantForFunctionEventsOnly()
.MarkAsAdvanced();
extension
.AddAction("SetReturnObject",
_("Set returned objects"),
_("Set currently picked instances as the ones returned for the object. These instances will be picked for actions, conditions and sub-events that follow this function call."),
_("Set currently picked instances of _PARAM0_ to be returned"),
_("Functions"),
"res/function24.png",
"res/function16.png")
.SetHelpPath("/events/functions/return")
.AddParameter("object", "Picked instances object")
.SetRelevantForFunctionEventsOnly()
.MarkAsAdvanced();
extension
.AddAction("CopyArgumentToVariable",
_("Copy function parameter to variable"),

View File

@@ -396,7 +396,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Z order"),
_("Modify the Z-order of an object"),
_("the z-order"),
_("Z order"),
_("Layers and cameras"),
"res/actions/planicon24.png",
"res/actions/planicon.png")
@@ -550,7 +550,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Z-order"),
_("Compare the Z-order of the specified object."),
_("the Z-order"),
_("Z-order"),
_("Layer"),
"res/conditions/planicon24.png",
"res/conditions/planicon.png")
@@ -1151,7 +1151,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
obj.AddExpression("ZOrder",
_("Z-order"),
_("Z-order of an object"),
_("Visibility"),
"",
"res/actions/planicon.png")
.AddParameter("object", _("Object"));

View File

@@ -46,7 +46,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("Scale"),
_("the scale of the object (default scale is 1)"),
_("the scale"),
_("Scale"),
_("Size"),
"res/actions/scale24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
@@ -63,7 +63,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("Scale on X axis"),
_("the scale on X axis of the object (default scale is 1)"),
_("the scale on X axis"),
_("Scale"),
_("Size"),
"res/actions/scaleWidth24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
@@ -80,7 +80,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("Scale on Y axis"),
_("the scale on Y axis of the object (default scale is 1)"),
_("the scale on Y axis"),
_("Scale"),
_("Size"),
"res/actions/scaleHeight24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")

View File

@@ -30,7 +30,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.AddObject<SpriteObject>("Sprite",
_("Sprite"),
_("Animated object which can be used for "
"most elements of a game"),
"most elements of a game."),
"CppPlatform/Extensions/spriteicon.png")
.SetCategoryFullName(_("General"))
.AddDefaultBehavior("EffectCapability::EffectBehavior")
@@ -408,6 +408,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
.SetHidden()
.MarkAsSimple();
// Deprecated
obj.AddCondition("ScaleWidth",
_("Scale on X axis"),
_("Compare the scale of the width of an object."),
@@ -415,7 +416,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Size"),
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Sprite")
.UseStandardRelationalOperatorParameters(
"number",
@@ -423,6 +424,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Scale (1 by default)")))
.MarkAsAdvanced();
// Deprecated
obj.AddCondition("ScaleHeight",
_("Scale on Y axis"),
_("Compare the scale of the height of an object."),
@@ -430,7 +432,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
_("Size"),
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
.SetHidden()
.AddParameter("object", _("Object"), "Sprite")
.UseStandardRelationalOperatorParameters(
"number",

View File

@@ -303,12 +303,24 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
return *this;
}
/**
* \brief Return true if the instruction must be hidden in the IDE.
* \brief Return true if the object must be hidden in the IDE.
*/
bool IsHidden() const { return hidden; }
/**
* \brief Declare a usage of the 3D renderer.
*/
ObjectMetadata &MarkAsRenderedIn3D() {
isRenderedIn3D = true;
return *this;
}
/**
* \brief Return true if the object uses the 3D renderer.
*/
bool IsRenderedIn3D() const { return isRenderedIn3D; }
std::map<gd::String, gd::InstructionMetadata> conditionsInfos;
std::map<gd::String, gd::InstructionMetadata> actionsInfos;
std::map<gd::String, gd::ExpressionMetadata> expressionsInfos;
@@ -329,6 +341,7 @@ class GD_CORE_API ObjectMetadata : public InstructionOrExpressionContainerMetada
gd::String categoryFullName;
std::set<gd::String> defaultBehaviorTypes;
bool hidden = false;
bool isRenderedIn3D = false;
std::shared_ptr<gd::ObjectConfiguration>
blueprintObject; ///< The "blueprint" object to be copied when a new

View File

@@ -25,6 +25,9 @@ const UsedExtensionsResult UsedExtensionsFinder::ScanProject(gd::Project& projec
void UsedExtensionsFinder::DoVisitObject(gd::Object &object) {
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), object.GetType());
if (metadata.GetMetadata().IsRenderedIn3D()) {
result.MarkAsHaving3DObjects();
}
result.GetUsedExtensions().insert(metadata.GetExtension().GetName());
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
result.GetUsedIncludeFiles().insert(includeFile);

View File

@@ -44,6 +44,13 @@ public:
return usedRequiredFiles;
}
/**
* \brief Return true when at least 1 object uses the 3D renderer.
*/
bool Has3DObjects() const {
return has3DObjects;
}
/**
* The extensions used by the project (or part of it).
*/
@@ -59,10 +66,15 @@ public:
*/
std::set<gd::String> &GetUsedRequiredFiles() { return usedRequiredFiles; }
void MarkAsHaving3DObjects() {
has3DObjects = true;
}
private:
std::set<gd::String> usedExtensions;
std::set<gd::String> usedIncludeFiles;
std::set<gd::String> usedRequiredFiles;
bool has3DObjects = false;
};
class GD_CORE_API UsedExtensionsFinder

View File

@@ -27,6 +27,9 @@ EventsBasedObject::EventsBasedObject(const gd::EventsBasedObject &_eventBasedObj
void EventsBasedObject::SerializeTo(SerializerElement& element) const {
element.SetAttribute("defaultName", defaultName);
if (isRenderedIn3D) {
element.SetBoolAttribute("is3D", true);
}
AbstractEventsBasedEntity::SerializeTo(element);
SerializeObjectsTo(element.AddChild("objects"));
@@ -36,6 +39,7 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
void EventsBasedObject::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
defaultName = element.GetStringAttribute("defaultName");
isRenderedIn3D = element.GetBoolAttribute("is3D", false);
AbstractEventsBasedEntity::UnserializeFrom(project, element);
UnserializeObjectsFrom(project, element.GetChild("objects"));

View File

@@ -72,6 +72,19 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
return *this;
}
/**
* \brief Declare a usage of the 3D renderer.
*/
EventsBasedObject& MarkAsRenderedIn3D(bool isRenderedIn3D_) {
isRenderedIn3D = isRenderedIn3D_;
return *this;
}
/**
* \brief Return true if the object uses the 3D renderer.
*/
bool IsRenderedIn3D() const { return isRenderedIn3D; }
void SerializeTo(SerializerElement& element) const override;
void UnserializeFrom(gd::Project& project,
@@ -79,6 +92,7 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity, public Ob
private:
gd::String defaultName;
bool isRenderedIn3D;
};
} // namespace gd

View File

@@ -107,7 +107,7 @@ module.exports = {
_('Scale on Z axis'),
_("the scale on Z axis of an object (default scale is 1)"),
_("the scale on Z axis scale"),
_('Scale'),
_('Size'),
'res/conditions/3d_box.svg'
)
.addParameter('object', _('3D object'))
@@ -244,17 +244,18 @@ module.exports = {
.addObject(
'Model3DObject',
_('3D Model'),
_('A 3D model.'),
_('An animated 3D model.'),
'JsPlatform/Extensions/3d_box.svg',
new gd.Model3DObjectConfiguration()
)
.setCategoryFullName(_('3D'))
.setCategoryFullName(_('General'))
// Effects are unsupported because the object is not rendered with PIXI.
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.addDefaultBehavior('FlippableCapability::FlippableBehavior')
.addDefaultBehavior('AnimatableCapability::AnimatableBehavior')
.addDefaultBehavior('Scene3D::Base3DBehavior')
.markAsRenderedIn3D()
.setIncludeFile('Extensions/3D/A_RuntimeObject3D.js')
.addIncludeFile('Extensions/3D/A_RuntimeObject3DRenderer.js')
.addIncludeFile('Extensions/3D/Model3DRuntimeObject.js')
@@ -1125,16 +1126,17 @@ module.exports = {
.addObject(
'Cube3DObject',
_('3D Box'),
_('A 3D box.'),
_('A box with images for each face'),
'JsPlatform/Extensions/3d_box.svg',
Cube3DObject
)
.setCategoryFullName(_('3D'))
.setCategoryFullName(_('General'))
// Effects are unsupported because the object is not rendered with PIXI.
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.addDefaultBehavior('FlippableCapability::FlippableBehavior')
.addDefaultBehavior('Scene3D::Base3DBehavior')
.markAsRenderedIn3D()
.setIncludeFile('Extensions/3D/A_RuntimeObject3D.js')
.addIncludeFile('Extensions/3D/A_RuntimeObject3DRenderer.js')
.addIncludeFile('Extensions/3D/Cube3DRuntimeObject.js')

View File

@@ -1465,7 +1465,7 @@ module.exports = {
'ApplyForce',
_('Apply force'),
_(
'Apply a force to the object. You need to specify the point of application (you can get the body mass center through expressions).'
'Apply a force to the object over time. It "accelerates" an object and must be used every frame during a time period.'
),
_('Apply to _PARAM0_ a force of _PARAM2_;_PARAM3_'),
_('Forces & impulses'),
@@ -1476,8 +1476,10 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('X component (N)'))
.addParameter('expression', _('Y component (N)'))
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying Y position'))
.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.'))
.getCodeExtraInformation()
.setFunctionName('applyForce');
@@ -1486,7 +1488,7 @@ module.exports = {
'ApplyPolarForce',
_('Apply force (angle)'),
_(
'Apply a force to the object using polar coordinates. You need to specify the point of application (you can get the body mass center through expressions).'
'Apply a force to the object over time using polar coordinates. It "accelerates" an object and must be used every frame during a time period.'
),
_('Apply to _PARAM0_ a force of angle _PARAM2_ and length _PARAM3_'),
_('Forces & impulses'),
@@ -1497,8 +1499,10 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angle'))
.addParameter('expression', _('Length (N)'))
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying Y position'))
.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.'))
.getCodeExtraInformation()
.setFunctionName('applyPolarForce');
@@ -1507,7 +1511,7 @@ module.exports = {
'ApplyForceTowardPosition',
_('Apply force toward position'),
_(
'Apply a force to the object to move it toward a position. You need to specify the point of application (you can get the body mass center through expressions).'
'Apply a force to the object over time to move it toward a position. It "accelerates" an object and must be used every frame during a time period.'
),
_(
'Apply to _PARAM0_ a force of length _PARAM2_ towards _PARAM3_;_PARAM4_'
@@ -1519,10 +1523,12 @@ 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.'))
.addParameter('expression', _('X position'))
.addParameter('expression', _('Y position'))
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying 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.'))
.getCodeExtraInformation()
.setFunctionName('applyForceTowardPosition');
@@ -1531,7 +1537,7 @@ module.exports = {
'ApplyImpulse',
_('Apply impulse'),
_(
'Apply an impulse to the object. You need to specify the point of application (you can get the body mass center through expressions).'
'Apply an impulse to the object. It instantly changes the speed, to give an initial speed for instance.'
),
_('Apply to _PARAM0_ an impulse of _PARAM2_;_PARAM3_'),
_('Forces & impulses'),
@@ -1542,14 +1548,16 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter(
'expression',
_('X component (in Newton * seconds or kilogram * meter per second)')
_('X component (N·s or kg·m·s⁻¹)')
)
.addParameter(
'expression',
_('Y component (in Newton * seconds or kilogram * meter per second)')
_('Y component (N·s or kg·m·s⁻¹)')
)
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying Y position'))
.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.'))
.getCodeExtraInformation()
.setFunctionName('applyImpulse');
@@ -1558,7 +1566,7 @@ module.exports = {
'ApplyPolarImpulse',
_('Apply impulse (angle)'),
_(
'Apply an impulse to the object using polar coordinates. You need to specify the point of application (you can get the body mass center through expressions).'
'Apply an impulse to the object using polar coordinates. It instantly changes the speed, to give an initial speed for instance.'
),
_(
'Apply to _PARAM0_ an impulse of angle _PARAM2_ and length _PARAM3_ (applied at _PARAM4_;_PARAM5_)'
@@ -1572,10 +1580,12 @@ module.exports = {
.addParameter('expression', _('Angle'))
.addParameter(
'expression',
_('Length (in Newton * seconds or kilogram * meter per second)')
_('Length (N·s or kg·m·s⁻¹)')
)
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying Y position'))
.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.'))
.getCodeExtraInformation()
.setFunctionName('applyPolarImpulse');
@@ -1584,7 +1594,7 @@ module.exports = {
'ApplyImpulseTowardPosition',
_('Apply impulse toward position'),
_(
'Apply an impulse to the object to move it toward a position. You need to specify the point of application (you can get the body mass center through expressions).'
'Apply an impulse to the object to move it toward a position. It instantly changes the speed, to give an initial speed for instance.'
),
_(
'Apply to _PARAM0_ an impulse of length _PARAM2_ towards _PARAM3_;_PARAM4_ (applied at _PARAM5_;_PARAM6_)'
@@ -1597,12 +1607,14 @@ module.exports = {
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter(
'expression',
_('Length (in Newton * seconds or kilogram * meter per second)')
_('Length (N·s or kg·m·s⁻¹)')
)
.setParameterLongDescription(_('An impulse is like a speed addition but depends on the mass.'))
.addParameter('expression', _('X position'))
.addParameter('expression', _('Y position'))
.addParameter('expression', _('Applying X position'))
.addParameter('expression', _('Applying 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.'))
.getCodeExtraInformation()
.setFunctionName('applyImpulseTowardPosition');
@@ -1611,7 +1623,7 @@ module.exports = {
'ApplyTorque',
_('Apply torque (rotational force)'),
_(
'Apply a torque (also called "rotational force") to the object. This will make the object rotate without moving it.'
'Apply a torque (also called "rotational force") to the object. It "accelerates" an object rotation and must be used every frame during a time period.'
),
_('Apply to _PARAM0_ a torque of _PARAM2_'),
_('Forces & impulses'),
@@ -1620,7 +1632,8 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Torque (N.m)'))
.addParameter('expression', _('Torque (N·m)'))
.setParameterLongDescription(_('A torque is like a rotation acceleration but depends on the mass.'))
.getCodeExtraInformation()
.setFunctionName('applyTorque');
@@ -1629,7 +1642,7 @@ module.exports = {
'ApplyAngularImpulse',
_('Apply angular impulse (rotational impulse)'),
_(
'Apply an angular impulse (also called a "rotational impulse") to the object. This will make the object rotate without moving it.'
'Apply an angular impulse (also called a "rotational impulse") to the object. It instantly changes the rotation speed, to give an initial speed for instance.'
),
_('Apply to _PARAM0_ an angular impulse of _PARAM2_'),
_('Forces & impulses'),
@@ -1638,7 +1651,8 @@ module.exports = {
)
.addParameter('object', _('Object'), '', false)
.addParameter('behavior', _('Behavior'), 'Physics2Behavior')
.addParameter('expression', _('Angular impulse (N.m.s'))
.addParameter('expression', _('Angular impulse (N·m·s'))
.setParameterLongDescription(_('An impulse is like a rotation speed addition but depends on the mass.'))
.getCodeExtraInformation()
.setFunctionName('applyAngularImpulse');

View File

@@ -142,6 +142,23 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
.AddParameter("expression", _("Radius (in pixels)"))
.SetFunctionName("DrawRoundedRectangle");
obj.AddAction("ChamferRectangle",
_("Chamfer Rectangle"),
_("Draw a chamfer rectangle on screen"),
_("Draw from _PARAM1_;_PARAM2_ to _PARAM3_;_PARAM4_ a chamfer "
"rectangle (chamfer: _PARAM5_) "
"with _PARAM0_"),
_("Drawing"),
"res/actions/chamferRectangle24.png",
"res/actions/chamferRectangle.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", _("Chamfer (in pixels)"))
.SetFunctionName("DrawChamferRectangle");
obj.AddAction(
"Star",
_("Star"),

View File

@@ -50,6 +50,8 @@ class PrimitiveDrawingJsExtension : public gd::PlatformExtension {
GetAllActionsForObject(
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::RoundedRectangle"]
.SetFunctionName("drawRoundedRectangle");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::ChamferRectangle"]
.SetFunctionName("drawChamferRectangle");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::Star"]
.SetFunctionName("drawStar");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::Arc"]
@@ -85,14 +87,7 @@ class PrimitiveDrawingJsExtension : public gd::PlatformExtension {
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::ClosePath"]
.SetFunctionName("closePath");
GetAllActionsForObject(
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::Ellipse"]
.SetFunctionName("drawEllipse");
GetAllActionsForObject(
"PrimitiveDrawing::Drawer")["PrimitiveDrawing::RoundedRectangle"]
.SetFunctionName("drawRoundedRectangle");
GetAllActionsForObject("PrimitiveDrawing::Drawer")["PrimitiveDrawing::Star"]
.SetFunctionName("drawStar");
// These actions are not exposed yet as the way they work is unsure. See
// https://github.com/4ian/GDevelop/pull/1256
/*GetAllActionsForObject(

View File

@@ -136,6 +136,25 @@ namespace gdjs {
this.invalidateBounds();
}
drawChamferRectangle(
x1: float,
y1: float,
x2: float,
y2: float,
chamfer: float
) {
this.updateOutline();
this._graphics.beginFill(
this._object._fillColor,
this._object._fillOpacity / 255
);
//@ts-ignore from @pixi/graphics-extras
this._graphics.drawChamferRect(x1, y1, x2 - x1, y2 - y1, chamfer);
this._graphics.closePath();
this._graphics.endFill();
this.invalidateBounds();
}
drawStar(
x1: float,
y1: float,

View File

@@ -221,6 +221,22 @@ namespace gdjs {
);
}
drawChamferRectangle(
startX1: float,
startY1: float,
endX2: float,
endY2: float,
chamfer: float
) {
this._renderer.drawChamferRectangle(
startX1,
startY1,
endX2,
endY2,
chamfer
);
}
drawStar(
centerX: float,
centerY: float,

View File

@@ -96,7 +96,6 @@ module.exports = {
.setParameterLongDescription(_('From 0 to 1.'))
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.ease');
// Deprecated
@@ -124,6 +123,7 @@ module.exports = {
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenVariableNumber');
@@ -151,6 +151,7 @@ module.exports = {
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenVariableNumber2');
@@ -177,6 +178,7 @@ module.exports = {
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenVariableNumber3');
@@ -206,6 +208,7 @@ module.exports = {
.markAsAdvanced()
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.addLayoutValueTween');
@@ -236,6 +239,7 @@ module.exports = {
.markAsAdvanced()
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.addLayerValueTween');
@@ -262,6 +266,7 @@ module.exports = {
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCamera');
@@ -287,6 +292,7 @@ module.exports = {
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCamera2');
@@ -312,6 +318,7 @@ module.exports = {
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraZoom');
@@ -336,6 +343,7 @@ module.exports = {
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraZoom2');
@@ -361,6 +369,7 @@ module.exports = {
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraRotation');
@@ -385,6 +394,7 @@ module.exports = {
.addParameter('expression', _('Duration (in seconds)'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.tweenCameraRotation2');
@@ -402,6 +412,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.sceneTweenExists');
@@ -419,6 +430,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.sceneTweenIsPlaying');
@@ -436,6 +448,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.sceneTweenHasFinished');
@@ -453,6 +466,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.pauseSceneTween');
@@ -471,6 +485,7 @@ module.exports = {
.addParameter('yesorno', _('Jump to the end'), '', false)
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.stopSceneTween');
@@ -488,6 +503,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.resumeSceneTween');
@@ -507,6 +523,7 @@ module.exports = {
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.removeSceneTween');
@@ -521,8 +538,11 @@ module.exports = {
'JsPlatform/Extensions/tween_behavior32.png'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.getProgress');
extension
@@ -536,8 +556,11 @@ module.exports = {
'JsPlatform/Extensions/tween_behavior32.png'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
.getCodeExtraInformation()
.setIncludeFile('Extensions/TweenBehavior/standard-easing-functions.js')
.addIncludeFile('Extensions/TweenBehavior/tweenruntimebehavior.js')
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
.setFunctionName('gdjs.evtTools.tween.getValue');
const tweenBehavior = new gd.BehaviorJsImplementation();
@@ -1189,7 +1212,7 @@ module.exports = {
'AddObjectScaleTween2',
_('Tween object scale'),
_(
'Tweens an object scale from its current scale to a new one (note: the scale can never be less than 0).'
'Tweens an object scale from its current scale to a new one (note: the scale can never be 0 or less).'
),
_(
'Tween the scale of _PARAM0_ to X-scale: _PARAM3_, Y-scale: _PARAM4_ (from center: _PARAM8_) with easing _PARAM5_ over _PARAM6_ seconds as _PARAM2_'

View File

@@ -18,6 +18,29 @@
namespace gdjs {
export namespace evtTools {
export namespace tween {
/**
* Tween between 2 values according to an easing function.
* @param fromValue Start value
* @param toValue End value
* @param easingValue Type of easing
* @param weighting from 0 to 1
*/
export const ease = (
easingValue: string,
fromValue: float,
toValue: float,
weighting: float
) => {
// This local declaration is needed because otherwise the transpiled
// code doesn't know it.
const easingFunctions = gdjs.evtTools.tween.easingFunctions;
const easingFunction = easingFunctions.hasOwnProperty(easingValue)
? easingFunctions[easingValue]
: easingFunctions.linear;
return fromValue + (toValue - fromValue) * easingFunction(weighting);
};
export type EasingFunction = (progress: float) => float;
export const easingFunctions: Record<string, EasingFunction> = {

View File

@@ -8,29 +8,6 @@ namespace gdjs {
}
export namespace evtTools {
export namespace tween {
/**
* Tween between 2 values according to an easing function.
* @param fromValue Start value
* @param toValue End value
* @param easingValue Type of easing
* @param weighting from 0 to 1
*/
export const ease = (
easingValue: string,
fromValue: float,
toValue: float,
weighting: float
) => {
// This local declaration is needed because otherwise the transpiled
// code doesn't know it.
const easingFunctions = gdjs.evtTools.tween.easingFunctions;
const easingFunction = easingFunctions.hasOwnProperty(easingValue)
? easingFunctions[easingValue]
: easingFunctions.linear;
return fromValue + (toValue - fromValue) * easingFunction(weighting);
};
export const getTweensMap = (runtimeScene: RuntimeScene) =>
runtimeScene._tweens ||
(runtimeScene._tweens = new gdjs.TweenRuntimeBehavior.TweenManager());

View File

@@ -49,10 +49,11 @@ namespace gdjs {
}
/**
* To be called when the object is removed from the scene: will pause the video.
* To be called when the object is removed from the scene: will stop the video
* (goes back to beginning).
*/
onDestroy() {
this.pause();
this.stop();
}
ensureUpToDate() {
@@ -172,6 +173,18 @@ namespace gdjs {
source.pause();
}
/**
* Stops the video and comes back to first frame.
*/
stop() {
const source = this._getHTMLVideoElementSource();
if (!source) {
return;
}
source.pause();
source.currentTime = 0;
}
// Autoplay was prevented.
/**
* Set the loop on video in renderer

View File

@@ -131,6 +131,9 @@ gd::ObjectMetadata &MetadataDeclarationHelper::DeclareObjectMetadata(
.AddDefaultBehavior("ScalableCapability::ScalableBehavior")
.AddDefaultBehavior("FlippableCapability::FlippableBehavior")
.AddDefaultBehavior("OpacityCapability::OpacityBehavior");
if (eventsBasedObject.IsRenderedIn3D()) {
objectMetadata.MarkAsRenderedIn3D();
}
// TODO EBO Use full type to identify object to avoid collision.
// Objects are identified by their name alone.

View File

@@ -99,6 +99,28 @@ AdvancedExtension::AdvancedExtension() {
"}\n";
});
GetAllActions()["SetReturnObject"]
.GetCodeExtraInformation()
.SetCustomCodeGenerator([](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context) {
gd::String objectsPickingCode;
for (const auto& objectName : codeGenerator.GetObjectsContainersList().ExpandObjectName(
instruction.GetParameter(0).GetPlainString(), context.GetCurrentObject())) {
const gd::String& objectNameString = codeGenerator.ConvertToStringExplicit(objectName);
const gd::String& objectList = codeGenerator.GetObjectListName(objectName, context);
objectsPickingCode += "gdjs.evtTools.object.pickObjects("
"eventsFunctionContext.getObjectsLists(" +
objectNameString + "), " + objectList + ");\n";
}
return "if (typeof eventsFunctionContext !== 'undefined') {\n"
" eventsFunctionContext.returnValue = true;\n"
+ objectsPickingCode +
"}\n";
});
GetAllConditions()["GetArgumentAsBoolean"]
.SetCustomCodeGenerator([](gd::Instruction& instruction,
gd::EventsCodeGenerator& codeGenerator,

View File

@@ -90,14 +90,10 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
fs, exportedProject.GetResourcesManager(), exportDir);
// end of compatibility code
bool isUsingScene3DExtension =
usedExtensionsResult.GetUsedExtensions().find("Scene3D") !=
usedExtensionsResult.GetUsedExtensions().end();
// Export engine libraries
helper.AddLibsInclude(
/*pixiRenderers=*/true,
/*pixiInThreeRenderers=*/isUsingScene3DExtension,
usedExtensionsResult.Has3DObjects(),
/*includeWebsocketDebuggerClient=*/false,
/*includeWindowMessageDebuggerClient=*/false,
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),

View File

@@ -146,13 +146,9 @@ bool ExporterHelper::ExportProjectForPixiPreview(
auto usedExtensionsResult =
gd::UsedExtensionsFinder::ScanProject(exportedProject);
bool isUsingScene3DExtension =
usedExtensionsResult.GetUsedExtensions().find("Scene3D") !=
usedExtensionsResult.GetUsedExtensions().end();
// Export engine libraries
AddLibsInclude(/*pixiRenderers=*/true,
/*pixiInThreeRenderers=*/isUsingScene3DExtension,
usedExtensionsResult.Has3DObjects(),
/*includeWebsocketDebuggerClient=*/
!options.websocketDebuggerServerAddress.empty(),
/*includeWindowMessageDebuggerClient=*/

View File

@@ -218,6 +218,37 @@ namespace gdjs {
arr.length = finalSize;
};
/**
* Pick the objects from a given `Array`.
* @param objectsLists the picking list to filter
* @param objects the objects to pick
*/
export const pickObjects = (
objectsLists: ObjectsLists,
objects: gdjs.RuntimeObject[]
) => {
// Clear the `pick` flag on every objects.
for (const objectsName in objectsLists.items) {
if (objectsLists.items.hasOwnProperty(objectsName)) {
for (const object of objectsLists.items[objectsName]) {
object.pick = false;
}
}
}
// Mark objects that need to be picked.
for (const object of objects) {
object.pick = true;
}
// Trim not picked objects from lists.
for (const objectsName in objectsLists.items) {
if (objectsLists.items.hasOwnProperty(objectsName)) {
gdjs.evtTools.object.filterPickedObjectsList(
objectsLists.items[objectsName]
);
}
}
};
export const hitBoxesCollisionTest = function (
objectsLists1: ObjectsLists,
objectsLists2: ObjectsLists,

View File

@@ -3489,4 +3489,4 @@
"dev": true
}
}
}
}

View File

@@ -86,6 +86,28 @@ describe('gdjs.evtTools.object.pickObjectsIf', function() {
});
});
describe('gdjs.evtTools.object.pickObjects', function() {
it('should properly pick objects', function(){
const runtimeScene = new gdjs.RuntimeScene(null);
const objectA1 = new gdjs.RuntimeObject(runtimeScene, {name: "ObjectA", type: "", behaviors: [], effects: []});
const objectA2 = new gdjs.RuntimeObject(runtimeScene, {name: "ObjectA", type: "", behaviors: [], effects: []});
const objectA3 = new gdjs.RuntimeObject(runtimeScene, {name: "ObjectA", type: "", behaviors: [], effects: []});
const objectB1 = new gdjs.RuntimeObject(runtimeScene, {name: "ObjectB", type: "", behaviors: [], effects: []});
const objectB2 = new gdjs.RuntimeObject(runtimeScene, {name: "ObjectB", type: "", behaviors: [], effects: []});
const pickedObjectMap = Hashtable.newFrom({
ObjectA: [objectA1, objectA2, objectA3],
ObjectB: [objectB1, objectB2]
});
gdjs.evtTools.object.pickObjects(pickedObjectMap, [objectA3, objectB2, objectA1]);
expect(pickedObjectMap.get("ObjectA")).to.have.length(2);
expect(pickedObjectMap.get("ObjectB")).to.have.length(1);
expect(pickedObjectMap.get("ObjectA")).to.eql([objectA1, objectA3]);
expect(pickedObjectMap.get("ObjectB")).to.eql([objectB2]);
});
});
describe('gdjs.evtTools.object.pickRandomObject', function() {
it('should pick only one object', function(){
var runtimeScene = new gdjs.RuntimeScene(null);

View File

@@ -1716,6 +1716,9 @@ interface ObjectMetadata {
[Ref] ObjectMetadata SetHidden();
boolean IsHidden();
[Ref] ObjectMetadata MarkAsRenderedIn3D();
boolean IsRenderedIn3D();
};
interface BehaviorMetadata {
@@ -2768,6 +2771,9 @@ interface EventsBasedObject {
[Ref] EventsBasedObject SetDefaultName([Const] DOMString defaultName);
[Const, Ref] DOMString GetDefaultName();
[Ref] EventsBasedObject MarkAsRenderedIn3D(boolean isRenderedIn3D);
boolean IsRenderedIn3D();
[Const, Value] DOMString STATIC_GetPropertyActionName([Const] DOMString propertyName);
[Const, Value] DOMString STATIC_GetPropertyConditionName([Const] DOMString propertyName);
[Const, Value] DOMString STATIC_GetPropertyExpressionName([Const] DOMString propertyName);

View File

@@ -290,6 +290,22 @@ class VariablesContainer {
has(variableName) {
return this._variables.containsKey(variableName);
}
/**
* @param {string} name
* @param {Variable} newVariable
*/
add(name, newVariable) {
const oldVariable = this._variables.get(name);
this._variables.put(name, newVariable);
if (oldVariable) {
const variableIndex = this._variablesArray.indexOf(oldVariable);
if (variableIndex !== -1) {
this._variablesArray[variableIndex] = newVariable;
}
}
}
}
class RuntimeObject {
@@ -565,6 +581,54 @@ const getPickedInstancesCount = (objectsLists) => {
return count;
};
/**
* @param {RuntimeObject[]} arr
*/
const filterPickedObjectsList = function (
arr
) {
let finalSize = 0;
for (let k = 0, lenk = arr.length; k < lenk; ++k) {
const obj = arr[k];
if (obj.pick) {
arr[finalSize] = obj;
finalSize++;
}
}
arr.length = finalSize;
};
/**
* @param {Hashtable<RuntimeObject[]>} objectsLists
* @param {RuntimeObject[]} objects
*/
const pickObjects = (
objectsLists,
objects
) => {
// Clear the `pick` flag on every objects.
for (const objectsName in objectsLists.items) {
if (objectsLists.items.hasOwnProperty(objectsName)) {
for (const object of objectsLists.items[objectsName]) {
object.pick = false;
}
}
}
// Mark objects that need to be picked.
for (const object of objects) {
object.pick = true;
}
// Trim not picked objects from lists.
for (const objectsName in objectsLists.items) {
if (objectsLists.items.hasOwnProperty(objectsName)) {
filterPickedObjectsList(
objectsLists.items[objectsName]
);
}
}
};
/** A minimal implementation of gdjs.RuntimeScene for testing. */
class RuntimeScene {
constructor(sceneData) {
@@ -729,6 +793,7 @@ function makeMinimalGDJSMock(options) {
createObjectOnScene,
getSceneInstancesCount,
getPickedInstancesCount,
pickObjects,
},
runtimeScene: {
wait: () => new FakeAsyncTask(),

View File

@@ -219,4 +219,121 @@ describe('libGD.js - GDJS Code Generation integration tests', function () {
.getAsNumber()
).toBe(123);
});
it('can generate function returning objects', function () {
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
const myObjectA1 = runtimeScene.createObject('MyObjectA');
const myObjectA2 = runtimeScene.createObject('MyObjectA');
const myObjectB1 = runtimeScene.createObject('MyObjectB');
const myObjectB2 = runtimeScene.createObject('MyObjectB');
const myObjectB3 = runtimeScene.createObject('MyObjectB');
const variableA1 = new gdjs.Variable();
variableA1.setNumber(1);
myObjectA1.getVariables().add('Pick', variableA1);
const variableB3 = new gdjs.Variable();
variableB3.setNumber(1);
myObjectB3.getVariables().add('Pick', variableB3);
// Run the function passing some objects as parameters.
const objectsLists = gdjs.Hashtable.newFrom({
MyObjectA: [myObjectA1],
MyObjectB: [myObjectB2, myObjectB3],
MyObjectC: [],
});
const serializerElement = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [
{
type: { value: 'VarObjet' },
parameters: ["Object","Pick","!=","0"],
},
],
actions: [
{
type: { value: 'SetReturnObject' },
parameters: ['Object'],
},
],
events: [],
},
]);
const runCompiledEvents = generateCompiledEventsFromSerializedEvents(
gd,
serializerElement,
{ parameterTypes: { Object: 'object' }, logCode: false }
);
runCompiledEvents(gdjs, runtimeScene, [objectsLists]);
expect(objectsLists.get('MyObjectA')).toEqual([myObjectA1]);
expect(objectsLists.get('MyObjectB')).toEqual([myObjectB3]);
expect(objectsLists.get('MyObjectC')).toEqual([]);
});
it('can generate function creating 2 instances and returning only one', function () {
const { gdjs, runtimeScene } = makeMinimalGDJSMock();
const myObjectA1 = runtimeScene.createObject('MyObjectA');
const myObjectA2 = runtimeScene.createObject('MyObjectA');
const myObjectB1 = runtimeScene.createObject('MyObjectB');
// According to the parameter type objectListOrEmptyIfJustDeclared,
// no instances is passed to the function because the ObjectsLists has not
// been filtered before.
const objectsLists = gdjs.Hashtable.newFrom({
MyObjectA: [],
MyObjectB: [],
MyObjectC: [],
});
const serializerElement = gd.Serializer.fromJSObject([
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [
],
actions: [
{
type: { value: 'Create' },
parameters: ['', 'Object', '0','0',''],
},
{
type: {'value':'ModVarObjet'},
parameters: ['Object','Pick','=','1']
},
{
type: { value: 'Create' },
parameters: ['', 'Object', '0','0',''],
},
],
events: [],
},
{
type: 'BuiltinCommonInstructions::Standard',
conditions: [
{
type: { value: 'VarObjet' },
parameters: ['Object','Pick','!=','0'],
},
],
actions: [
{
type: { value: 'SetReturnObject' },
parameters: ['Object'],
},
],
events: [],
},
]);
const runCompiledEvents = generateCompiledEventsFromSerializedEvents(
gd,
serializerElement,
{ parameterTypes: { Object: 'objectListOrEmptyIfJustDeclared' }, logCode: false }
);
runCompiledEvents(gdjs, runtimeScene, [objectsLists]);
expect(objectsLists.get('MyObjectA').length).toBe(1);
});
});

View File

@@ -9,6 +9,8 @@ declare class gdEventsBasedObject extends gdAbstractEventsBasedEntity {
getFullName(): string;
setDefaultName(defaultName: string): gdEventsBasedObject;
getDefaultName(): string;
markAsRenderedIn3D(isRenderedIn3D: boolean): gdEventsBasedObject;
isRenderedIn3D(): boolean;
static getPropertyActionName(propertyName: string): string;
static getPropertyConditionName(propertyName: string): string;
static getPropertyExpressionName(propertyName: string): string;

View File

@@ -26,6 +26,8 @@ declare class gdObjectMetadata {
addDefaultBehavior(behaviorType: string): gdObjectMetadata;
setHidden(): gdObjectMetadata;
isHidden(): boolean;
markAsRenderedIn3D(): gdObjectMetadata;
isRenderedIn3D(): boolean;
delete(): void;
ptr: number;
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

View File

@@ -139,6 +139,12 @@ const getMergedInstalledWithDefaultEnumeratedObjectMetadataByCategory = ({
{
name: 'PanelSpriteObject::PanelSprite',
},
{
name: 'Scene3D::Cube3DObject',
},
{
name: 'Scene3D::Model3DObject',
},
],
[translateExtensionCategory('Input', i18n)]: [
{
@@ -257,6 +263,24 @@ const getMergedInstalledWithDefaultEnumeratedObjectMetadataByCategory = ({
},
{
name: 'ParticleSystem::ParticleEmitter',
assetStorePackTag: 'particles emitter',
requiredExtensions: [],
},
{
name: 'ParticleEmitter3D::ParticleEmitter3D',
fullName: i18n._(t`3D particle emitter`),
description: i18n._(
t`Displays a large number of particles to create visual effects.`
),
iconFilename:
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWZpcmUiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTcuNjYgMTEuMkMxNy40MyAxMC45IDE3LjE1IDEwLjY0IDE2Ljg5IDEwLjM4QzE2LjIyIDkuNzggMTUuNDYgOS4zNSAxNC44MiA4LjcyQzEzLjMzIDcuMjYgMTMgNC44NSAxMy45NSAzQzEzIDMuMjMgMTIuMTcgMy43NSAxMS40NiA0LjMyQzguODcgNi40IDcuODUgMTAuMDcgOS4wNyAxMy4yMkM5LjExIDEzLjMyIDkuMTUgMTMuNDIgOS4xNSAxMy41NUM5LjE1IDEzLjc3IDkgMTMuOTcgOC44IDE0LjA1QzguNTcgMTQuMTUgOC4zMyAxNC4wOSA4LjE0IDEzLjkzQzguMDggMTMuODggOC4wNCAxMy44MyA4IDEzLjc2QzYuODcgMTIuMzMgNi42OSAxMC4yOCA3LjQ1IDguNjRDNS43OCAxMCA0Ljg3IDEyLjMgNSAxNC40N0M1LjA2IDE0Ljk3IDUuMTIgMTUuNDcgNS4yOSAxNS45N0M1LjQzIDE2LjU3IDUuNyAxNy4xNyA2IDE3LjdDNy4wOCAxOS40MyA4Ljk1IDIwLjY3IDEwLjk2IDIwLjkyQzEzLjEgMjEuMTkgMTUuMzkgMjAuOCAxNy4wMyAxOS4zMkMxOC44NiAxNy42NiAxOS41IDE1IDE4LjU2IDEyLjcyTDE4LjQzIDEyLjQ2QzE4LjIyIDEyIDE3LjY2IDExLjIgMTcuNjYgMTEuMk0xNC41IDE3LjVDMTQuMjIgMTcuNzQgMTMuNzYgMTggMTMuNCAxOC4xQzEyLjI4IDE4LjUgMTEuMTYgMTcuOTQgMTAuNSAxNy4yOEMxMS42OSAxNyAxMi40IDE2LjEyIDEyLjYxIDE1LjIzQzEyLjc4IDE0LjQzIDEyLjQ2IDEzLjc3IDEyLjMzIDEzQzEyLjIxIDEyLjI2IDEyLjIzIDExLjYzIDEyLjUgMTAuOTRDMTIuNjkgMTEuMzIgMTIuODkgMTEuNyAxMy4xMyAxMkMxMy45IDEzIDE1LjExIDEzLjQ0IDE1LjM3IDE0LjhDMTUuNDEgMTQuOTQgMTUuNDMgMTUuMDggMTUuNDMgMTUuMjNDMTUuNDYgMTYuMDUgMTUuMSAxNi45NSAxNC41IDE3LjVIMTQuNVoiIC8+PC9zdmc+',
assetStorePackTag: '3d particles',
requiredExtensions: [
{
extensionName: 'ParticleEmitter3D',
extensionVersion: '1.0.0',
},
],
},
],
[translateExtensionCategory('Advanced', i18n)]: [

View File

@@ -1,7 +1,6 @@
// @flow
import { Trans } from '@lingui/macro';
import { t } from '@lingui/macro';
import { I18n } from '@lingui/react';
import * as React from 'react';
import TextField from '../UI/TextField';
@@ -10,6 +9,7 @@ import DismissableAlertMessage from '../UI/DismissableAlertMessage';
import AlertMessage from '../UI/AlertMessage';
import { ColumnStackLayout } from '../UI/Layout';
import useForceUpdate from '../Utils/UseForceUpdate';
import Checkbox from '../UI/Checkbox';
const gd: libGDevelop = global.gd;
@@ -69,22 +69,26 @@ export default function EventsBasedObjectEditor({ eventsBasedObject }: Props) {
fullWidth
rows={3}
/>
<I18n>
{({ i18n }) => (
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Default name for created objects</Trans>}
value={
eventsBasedObject.getDefaultName() || eventsBasedObject.getName()
}
onChange={newName => {
eventsBasedObject.setDefaultName(gd.Project.getSafeName(newName));
forceUpdate();
}}
fullWidth
/>
)}
</I18n>
<SemiControlledTextField
commitOnBlur
floatingLabelText={<Trans>Default name for created objects</Trans>}
value={
eventsBasedObject.getDefaultName() || eventsBasedObject.getName()
}
onChange={newName => {
eventsBasedObject.setDefaultName(gd.Project.getSafeName(newName));
forceUpdate();
}}
fullWidth
/>
<Checkbox
label={<Trans>Use 3D rendering</Trans>}
checked={eventsBasedObject.isRenderedIn3D()}
onCheck={(e, checked) => {
eventsBasedObject.markAsRenderedIn3D(checked);
forceUpdate();
}}
/>
{eventsBasedObject.getEventsFunctions().getEventsFunctionsCount() ===
0 && (
<DismissableAlertMessage

View File

@@ -2,7 +2,7 @@
import * as React from 'react';
import {
enumerateObjectAndBehaviorsInstructions,
enumerateAllInstructions,
isObjectInstruction,
getObjectParameterIndex,
} from '../../InstructionOrExpression/EnumerateInstructions';
import {
@@ -129,19 +129,25 @@ export const useInstructionEditor = ({
if (!isNewInstruction) {
// Check if the instruction is an object/behavior instruction. If yes
// select the object, which is the first parameter of the instruction.
const allInstructions = enumerateAllInstructions(isCondition);
const instructionType: string = instruction.getType();
const enumeratedInstructionMetadata = findInstruction(
allInstructions,
instructionType
);
const instructionMetadata = isCondition
? gd.MetadataProvider.getConditionMetadata(
project.getCurrentPlatform(),
instructionType
)
: gd.MetadataProvider.getActionMetadata(
project.getCurrentPlatform(),
instructionType
);
if (
enumeratedInstructionMetadata &&
(enumeratedInstructionMetadata.scope.objectMetadata ||
enumeratedInstructionMetadata.scope.behaviorMetadata)
isObjectInstruction(
project.getCurrentPlatform(),
instruction,
isCondition
)
) {
const objectParameterIndex = getObjectParameterIndex(
enumeratedInstructionMetadata.metadata
instructionMetadata
);
if (objectParameterIndex !== -1) {
return getChosenObjectState(

View File

@@ -377,7 +377,7 @@ const InviteHome = ({ cloudProjectId }: Props) => {
<GetSubscriptionCard subscriptionDialogOpeningReason="Add collaborators on project">
<Text>
<Trans>
Get a startup subscription to invite collaborators on your
Get a startup subscription to invite collaborators into your
project.
</Trans>
</Text>

View File

@@ -68,6 +68,44 @@ const freeInstructionsToRemove = {
],
};
/**
* @returns `true` if the instruction in shown as an object instruction.
*/
export const isObjectInstruction = (
platform: gdPlatform,
instruction: gdInstruction,
isCondition: boolean
) => {
const instructionType: string = instruction.getType();
const extensionAndInstructionMetadata = isCondition
? gd.MetadataProvider.getExtensionAndConditionMetadata(
platform,
instructionType
)
: gd.MetadataProvider.getExtensionAndActionMetadata(
platform,
instructionType
);
const extension = extensionAndInstructionMetadata.getExtension();
const instructionMetadata = extensionAndInstructionMetadata.getMetadata();
for (const extensionName in freeInstructionsToRemove) {
if (extensionName !== extension.getName()) {
continue;
}
for (const instructionToRemoveName of freeInstructionsToRemove[
extensionName
]) {
if (instructionType === instructionToRemoveName) {
return true;
}
}
}
return (
instructionMetadata.getParametersCount() >= 1 &&
gd.ParameterMetadata.isObject(instructionMetadata.getParameter(0).getType())
);
};
export const getExtensionPrefix = (extension: gdPlatformExtension): string => {
return extension.getCategory() + GROUP_DELIMITER + extension.getFullName();
};

View File

@@ -49,6 +49,7 @@ import KeyboardShortcuts from '../UI/KeyboardShortcuts';
import Link from '../UI/Link';
import { getHelpLink } from '../Utils/HelpLink';
import useAlertDialog from '../UI/Alert/useAlertDialog';
import { useResponsiveWindowWidth } from '../UI/Reponsive/ResponsiveWindowMeasurer';
const gd: libGDevelop = global.gd;
const sceneObjectsRootFolderId = 'scene-objects';
@@ -264,6 +265,8 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
const { showDeleteConfirmation } = useAlertDialog();
const treeViewRef = React.useRef<?TreeViewInterface<TreeViewItem>>(null);
const forceUpdate = useForceUpdate();
const windowWidth = useResponsiveWindowWidth();
const isMobileScreen = windowWidth === 'small';
const forceUpdateList = React.useCallback(
() => {
@@ -653,10 +656,17 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
const editName = React.useCallback(
(objectFolderOrObjectWithContext: ?ObjectFolderOrObjectWithContext) => {
if (!objectFolderOrObjectWithContext) return;
if (treeViewRef.current)
treeViewRef.current.renameItem(objectFolderOrObjectWithContext);
const treeView = treeViewRef.current;
if (treeView) {
if (isMobileScreen) {
// Position item at top of the screen to make sure it will be visible
// once the keyboard is open.
treeView.scrollToItem(objectFolderOrObjectWithContext, 'start');
}
treeView.renameItem(objectFolderOrObjectWithContext);
}
},
[]
[isMobileScreen]
);
const duplicateObject = React.useCallback(
@@ -1533,7 +1543,6 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
canMoveSelectionToItem={canMoveSelectionTo}
reactDndType={objectWithContextReactDndType}
initiallyOpenedNodeIds={initiallyOpenedNodeIds}
renderHiddenElements={!!currentlyRunningInAppTutorial}
arrowKeyNavigationProps={arrowKeyNavigationProps}
/>
)}

View File

@@ -639,7 +639,10 @@ export default class SceneEditor extends React.Component<Props, State> {
_onInstancesSelected = (instances: Array<gdInitialInstance>) => {
if (instances.length === 0) {
this.setState({ selectedObjectFolderOrObjectsWithContext: [] });
this.setState(
{ selectedObjectFolderOrObjectsWithContext: [] },
this.updateToolbar
);
return;
}
const { project, layout } = this.props;
@@ -648,27 +651,33 @@ export default class SceneEditor extends React.Component<Props, State> {
const lastSelectedInstance = instances[instances.length - 1];
const objectName = lastSelectedInstance.getObjectName();
if (project.hasObjectNamed(objectName)) {
this.setState({
selectedObjectFolderOrObjectsWithContext: [
{
objectFolderOrObject: project
.getRootFolder()
.getObjectNamed(objectName),
global: true,
},
],
});
this.setState(
{
selectedObjectFolderOrObjectsWithContext: [
{
objectFolderOrObject: project
.getRootFolder()
.getObjectNamed(objectName),
global: true,
},
],
},
this.updateToolbar
);
} else if (layout.hasObjectNamed(objectName)) {
this.setState({
selectedObjectFolderOrObjectsWithContext: [
{
objectFolderOrObject: layout
.getRootFolder()
.getObjectNamed(objectName),
global: false,
},
],
});
this.setState(
{
selectedObjectFolderOrObjectsWithContext: [
{
objectFolderOrObject: layout
.getRootFolder()
.getObjectNamed(objectName),
global: false,
},
],
},
this.updateToolbar
);
}
};

View File

@@ -79,7 +79,7 @@ export const getFuseSearchQueryForMultipleKeys = (
});
return {
$and: searchQuery,
$or: searchQuery,
};
};

View File

@@ -103,7 +103,7 @@ const getItemProps = memoizeOne(
export type TreeViewInterface<Item> = {|
forceUpdateList: () => void,
scrollToItem: Item => void,
scrollToItem: (Item, placement?: 'smart' | 'start') => void,
renameItem: Item => void,
openItems: (string[]) => void,
closeItems: (string[]) => void,
@@ -136,7 +136,6 @@ type Props<Item> = {|
reactDndType: string,
forceAllOpened?: boolean,
initiallyOpenedNodeIds?: string[],
renderHiddenElements?: boolean,
arrowKeyNavigationProps?: {|
onGetItemInside: (item: Item) => ?Item,
onGetItemOutside: (item: Item) => ?Item,
@@ -166,7 +165,6 @@ const TreeView = <Item: ItemBaseAttributes>(
reactDndType,
forceAllOpened,
initiallyOpenedNodeIds,
renderHiddenElements,
arrowKeyNavigationProps,
}: Props<Item>,
ref: TreeViewInterface<Item>
@@ -365,7 +363,7 @@ const TreeView = <Item: ItemBaseAttributes>(
);
const scrollToItem = React.useCallback(
(item: Item) => {
(item: Item, placement?: 'smart' | 'start' = 'smart') => {
const list = listRef.current;
if (list) {
const itemId = getItemId(item);
@@ -374,7 +372,7 @@ const TreeView = <Item: ItemBaseAttributes>(
// $FlowFixMe - Method introduced in 2022.
const index = flattenedData.findLastIndex(node => node.id === itemId);
if (index >= 0) {
list.scrollToItem(index, 'smart');
list.scrollToItem(index, placement);
}
}
},
@@ -637,7 +635,13 @@ const TreeView = <Item: ItemBaseAttributes>(
// $FlowFixMe
itemData={itemData}
ref={listRef}
overscanCount={renderHiddenElements ? 20 : 2}
// Keep overscanCount relatively high so that:
// - during in-app tutorials we make sure the tooltip displayer finds
// the elements to highlight
// - on mobile it avoids jumping screens. This can happen when an item
// name is edited, the keyboard opens and reduces the window height
// making the item disappear (because or virtualization).
overscanCount={20}
>
{TreeViewRow}
</FixedSizeList>

View File

@@ -2,7 +2,7 @@
"name": "gdevelop",
"productName": "GDevelop 5",
"description": "GDevelop 5 IDE - the open-source, cross-platform game engine designed for everyone",
"version": "5.2.177",
"version": "5.2.178",
"author": "GDevelop Team <hello@gdevelop.io>",
"license": "MIT",
"homepage": "https://gdevelop.io",