mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Add possibility to use custom collision masks for draggable behavior (#3738)
* Added a toggle in draggable behavior parameters so that users can chose to use custom collision mask or not
This commit is contained in:
@@ -15,7 +15,7 @@ class SerializerElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Behavior that allows objects to be dragged with the mouse (or touch).
|
||||
* \brief Behavior that destroys object outside the screen.
|
||||
*/
|
||||
class GD_EXTENSION_API DestroyOutsideBehavior : public gd::Behavior {
|
||||
public:
|
||||
|
@@ -6,6 +6,42 @@ This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#include "DraggableBehavior.h"
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Project/PropertyDescriptor.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
|
||||
DraggableBehavior::DraggableBehavior() {}
|
||||
|
||||
void DraggableBehavior::InitializeContent(gd::SerializerElement& content) {
|
||||
content.SetAttribute("checkCollisionMask", true);
|
||||
}
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> DraggableBehavior::GetProperties(
|
||||
const gd::SerializerElement& behaviorContent) const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
properties["checkCollisionMask"]
|
||||
.SetValue(behaviorContent.GetBoolAttribute("checkCollisionMask")
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean")
|
||||
.SetLabel(_("Do a precision check against the object's collision mask"))
|
||||
.SetDescription(
|
||||
_("Use the object (custom) collision mask instead of the bounding "
|
||||
"box, making the behavior more precise at the cost of "
|
||||
"reduced performance"));
|
||||
;
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool DraggableBehavior::UpdateProperty(gd::SerializerElement& behaviorContent,
|
||||
const gd::String& name,
|
||||
const gd::String& value) {
|
||||
if (name == "checkCollisionMask") {
|
||||
behaviorContent.SetAttribute("checkCollisionMask", (value != "0"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
@@ -24,9 +24,20 @@ class GD_EXTENSION_API DraggableBehavior : public gd::Behavior {
|
||||
public:
|
||||
DraggableBehavior();
|
||||
virtual ~DraggableBehavior(){};
|
||||
virtual Behavior* Clone() const { return new DraggableBehavior(*this); }
|
||||
virtual Behavior* Clone() const override {
|
||||
return new DraggableBehavior(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
|
||||
const gd::SerializerElement& behaviorContent) const override;
|
||||
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
|
||||
const gd::String& name,
|
||||
const gd::String& value) override;
|
||||
#endif
|
||||
|
||||
virtual void InitializeContent(
|
||||
gd::SerializerElement& behaviorContent) override;
|
||||
};
|
||||
|
||||
#endif // DRAGGABLEBEHAVIOR_H
|
||||
|
@@ -14,9 +14,11 @@ namespace gdjs {
|
||||
* When the owner is being dragged, no other manager can start dragging it.
|
||||
*/
|
||||
_draggedByDraggableManager: DraggableManager | null = null;
|
||||
_checkCollisionMask: boolean;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
this._checkCollisionMask = behaviorData.checkCollisionMask ? true : false;
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
@@ -196,6 +198,14 @@ namespace gdjs {
|
||||
!draggableRuntimeBehavior.owner.insideObject(position[0], position[1])
|
||||
) {
|
||||
return false;
|
||||
} else if (
|
||||
draggableRuntimeBehavior._checkCollisionMask &&
|
||||
!draggableRuntimeBehavior.owner.isCollidingWithPoint(
|
||||
position[0],
|
||||
position[1]
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this._draggableBehavior) {
|
||||
// The previous best object to drag will not be dragged.
|
||||
|
@@ -34,20 +34,28 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
|
||||
instances: [],
|
||||
});
|
||||
|
||||
var object = new gdjs.RuntimeObject(runtimeScene, {
|
||||
var object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [{ name: 'Behavior1', type: 'DraggableBehavior::Draggable' }],
|
||||
variables: [],
|
||||
effects: [],
|
||||
});
|
||||
var object2 = new gdjs.RuntimeObject(runtimeScene, {
|
||||
object.setCustomWidthAndHeight(10, 10);
|
||||
var object2 = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [{ name: 'Behavior1', type: 'DraggableBehavior::Draggable' }],
|
||||
behaviors: [
|
||||
{
|
||||
name: 'Behavior1',
|
||||
type: 'DraggableBehavior::Draggable',
|
||||
checkCollisionMask: true,
|
||||
},
|
||||
],
|
||||
variables: [],
|
||||
effects: [],
|
||||
});
|
||||
object2.setCustomWidthAndHeight(10, 10);
|
||||
runtimeScene.addObject(object);
|
||||
runtimeScene.addObject(object2);
|
||||
|
||||
@@ -96,6 +104,70 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
|
||||
expect(object.getY()).to.be(700);
|
||||
});
|
||||
|
||||
it('can drag an object without collision mask check', function () {
|
||||
object.setPosition(450, 500);
|
||||
object.setAngle(45);
|
||||
|
||||
// Dragged point is in the bounding box but not in hitbox
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(450, 500);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
|
||||
object.setAngle(0);
|
||||
});
|
||||
|
||||
it('can drag an object with collision mask check', function () {
|
||||
object2.setPosition(450, 500);
|
||||
object2.setAngle(45);
|
||||
|
||||
// Dragged point is in the bounding box but not in hitbox
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(450, 500);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object2.getX()).to.be(450);
|
||||
expect(object2.getY()).to.be(500);
|
||||
|
||||
// Dragged point is in the bounding box and in hitbox
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(455, 505);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(855, 705);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object2.getX()).to.be(850);
|
||||
expect(object2.getY()).to.be(700);
|
||||
object2.setAngle(0);
|
||||
});
|
||||
|
||||
[false, true].forEach((firstInFront) => {
|
||||
it(`must drag the object in front (${
|
||||
firstInFront ? '1st object' : '2nd object'
|
||||
@@ -184,6 +256,65 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
|
||||
expect(object.getX()).to.be(850);
|
||||
expect(object.getY()).to.be(700);
|
||||
});
|
||||
|
||||
it('can drag an object without collision mask check', function () {
|
||||
object.setPosition(450, 500);
|
||||
object.setAngle(45);
|
||||
|
||||
// Dragged point is in the bounding box but not in hitbox
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
|
||||
object.setAngle(0);
|
||||
});
|
||||
|
||||
it('can drag an object with collision mask check', function () {
|
||||
object2.setPosition(450, 500);
|
||||
object2.setAngle(45);
|
||||
|
||||
// Dragged point is in the bounding box but not in hitbox
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
|
||||
expect(object2.getX()).to.be(450);
|
||||
expect(object2.getY()).to.be(500);
|
||||
|
||||
// Dragged point is in the bounding box but not in hitbox
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(0, 455, 505);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(0, 855, 705);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
|
||||
expect(object2.getX()).to.be(850);
|
||||
expect(object2.getY()).to.be(700);
|
||||
object2.setAngle(0);
|
||||
});
|
||||
|
||||
it('can drag 2 objects with multitouch', function () {
|
||||
runtimeGame.getInputManager().touchSimulateMouse(false);
|
||||
object.setPosition(450, 500);
|
||||
|
@@ -2339,6 +2339,7 @@ namespace gdjs {
|
||||
*
|
||||
* The position should be in "world" coordinates, i.e use gdjs.Layer.convertCoords
|
||||
* if you need to pass the mouse or a touch position that you get from gdjs.InputManager.
|
||||
* To check if a point is inside the object collision mask, you can use `isCollidingWithPoint` instead.
|
||||
*
|
||||
*/
|
||||
insideObject(x: float, y: float): boolean {
|
||||
|
@@ -6,7 +6,7 @@
|
||||
* an example to start a new object, take a look at gdjs.DummyRuntimeObject
|
||||
* in the Extensions folder.
|
||||
*/
|
||||
gdjs.TestRuntimeObject = class TestRuntimeObject extends gdjs.RuntimeObject {
|
||||
gdjs.TestRuntimeObject = class TestRuntimeObject extends gdjs.RuntimeObject {
|
||||
/** @type {float} */
|
||||
_customWidth = 0;
|
||||
/** @type {float} */
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return { visible: true };
|
||||
return null;
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* A test object doing nothing, with a fake getRendererObject method.
|
||||
*
|
||||
* It's only used for testing: if you want
|
||||
* an example to start a new object, take a look at gdjs.DummyRuntimeObject
|
||||
* in the Extensions folder.
|
||||
*/
|
||||
gdjs.TestRuntimeObjectWithFakeRenderer = class TestRuntimeObjectWithFakeRenderer extends gdjs.RuntimeObject {
|
||||
/**
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
* @param {ObjectData} objectData
|
||||
*/
|
||||
constructor(runtimeScene, objectData) {
|
||||
// *ALWAYS* call the base gdjs.RuntimeObject constructor.
|
||||
super(runtimeScene, objectData);
|
||||
|
||||
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
|
||||
this.onCreated();
|
||||
}
|
||||
|
||||
getRendererObject() {
|
||||
return { visible: true };
|
||||
}
|
||||
};
|
||||
|
||||
gdjs.registerObject(
|
||||
'TestObjectWithFakeRenderer::TestObjectWithFakeRenderer',
|
||||
gdjs.TestRuntimeObjectWithFakeRenderer
|
||||
);
|
2
GDJS/tests/tests/effects.js
vendored
2
GDJS/tests/tests/effects.js
vendored
@@ -10,7 +10,7 @@ describe('gdjs.EffectsManager', () => {
|
||||
|
||||
it('can add effects on a runtime object', () => {
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
const object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
const object = new gdjs.TestRuntimeObjectWithFakeRenderer(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
variables: [],
|
||||
|
Reference in New Issue
Block a user