Improve Draggable behavior to drag the frontmost object under the cursor/touch (#3066)

* In previous versions, the order was not guaranteed, which could result in a non intuitive result for the player.
This commit is contained in:
D8H
2021-09-19 02:36:49 +02:00
committed by GitHub
parent 8e6ba3abce
commit b40e2d3fdf
2 changed files with 223 additions and 95 deletions

View File

@@ -39,6 +39,10 @@ namespace gdjs {
this._draggedByDraggableManager = null;
}
_dismissDrag() {
this._draggedByDraggableManager = null;
}
_tryBeginDrag(runtimeScene) {
if (this._draggedByDraggableManager) {
return false;
@@ -119,7 +123,15 @@ namespace gdjs {
* Handle the dragging
*/
abstract class DraggableManager {
/**
* The object has left its original position.
* When true, the search for the best object to drag has ended.
*/
protected _draggingSomething = false;
/**
* The behavior of the object that is being dragged and that is the best one (i.e: highest Z order) found.
*/
protected _draggableBehavior: gdjs.DraggableRuntimeBehavior | null = null;
protected _xOffset: number = 0;
protected _yOffset: number = 0;
@@ -172,15 +184,26 @@ namespace gdjs {
runtimeScene: gdjs.RuntimeScene,
draggableRuntimeBehavior: DraggableRuntimeBehavior
) {
if (
this._draggableBehavior &&
draggableRuntimeBehavior.owner.getZOrder() <=
this._draggableBehavior.owner.getZOrder()
) {
return false;
}
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
if (
!draggableRuntimeBehavior.owner.insideObject(position[0], position[1])
) {
return false;
}
if (this._draggableBehavior) {
// The previous best object to drag will not be dragged.
this._draggableBehavior._dismissDrag();
}
this._draggableBehavior = draggableRuntimeBehavior;
this._xOffset = position[0] - draggableRuntimeBehavior.owner.getX();
this._yOffset = position[1] - draggableRuntimeBehavior.owner.getY();
this._draggingSomething = true;
return true;
}
@@ -189,12 +212,19 @@ namespace gdjs {
draggableRuntimeBehavior: DraggableRuntimeBehavior
) {
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
draggableRuntimeBehavior.owner.setX(position[0] - this._xOffset);
draggableRuntimeBehavior.owner.setY(position[1] - this._yOffset);
if (
draggableRuntimeBehavior.owner.getX() != position[0] - this._xOffset &&
draggableRuntimeBehavior.owner.getY() != position[1] - this._yOffset
) {
draggableRuntimeBehavior.owner.setX(position[0] - this._xOffset);
draggableRuntimeBehavior.owner.setY(position[1] - this._yOffset);
this._draggingSomething = true;
}
}
endDrag() {
this._draggingSomething = false;
this._draggableBehavior = null;
}
abstract isDragging(

View File

@@ -51,112 +51,210 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
runtimeScene.addObject(object);
runtimeScene.addObject(object2);
it('should handle mouse', function () {
object.setPosition(450, 500);
describe('(mouse)', function () {
it('can drag an object', function () {
object.setPosition(450, 500);
//Drag'n'drop
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);
// Drag'n'drop
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);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Mouse move with dragging
runtimeGame.getInputManager().onMouseMove(600, 600);
runtimeScene.renderAndStep(1000 / 60);
// Mouse move without dragging
runtimeGame.getInputManager().onMouseMove(600, 600);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Start dragging again
runtimeGame.getInputManager().onMouseMove(750, 600);
runtimeGame
.getInputManager()
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onMouseMove(850, 700);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame
.getInputManager()
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep(1000 / 60);
// Start dragging again
runtimeGame.getInputManager().onMouseMove(750, 600);
runtimeGame
.getInputManager()
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onMouseMove(850, 700);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame
.getInputManager()
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
});
[false, true].forEach((firstInFront) => {
it(`must drag the object in front (${
firstInFront ? '1st object' : '2nd object'
} in front)`, function () {
object.setPosition(450, 500);
object2.setPosition(450, 500);
if (firstInFront) {
object.setZOrder(2);
object2.setZOrder(1);
} else {
object.setZOrder(1);
object2.setZOrder(2);
}
// Drag'n'drop
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);
if (firstInFront) {
// The 1st object moved
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object2.getX()).to.be(450);
expect(object2.getY()).to.be(500);
} else {
// The 2nd object moved
expect(object.getX()).to.be(450);
expect(object.getY()).to.be(500);
expect(object2.getX()).to.be(750);
expect(object2.getY()).to.be(600);
}
});
});
});
it('should handle touches', function () {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
describe('(touch)', function () {
it('can drag an object', function () {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
//Drag'n'drop
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
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();
// Drag'n'drop
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
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);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Move another unrelated touch
runtimeGame.getInputManager().onTouchMove(1, 750, 600);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep(1000 / 60);
// Move another unrelated touch
runtimeGame.getInputManager().onTouchMove(1, 750, 600);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep(1000 / 60);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
//Start drag'n'drop with another touch
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(1, 750, 600);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
// Start drag'n'drop with another touch
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(1, 750, 600);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onFrameEnded();
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
});
it('should handle multitouch', function () {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
object2.setPosition(650, 600);
expect(object.getX()).to.be(850);
expect(object.getY()).to.be(700);
});
it('can drag 2 objects with multitouch', function () {
runtimeGame.getInputManager().touchSimulateMouse(false);
object.setPosition(450, 500);
object2.setPosition(650, 600);
//Drag'n'drop
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(2, 450, 500);
runtimeGame.getInputManager().onTouchStart(1, 650, 600);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchMove(2, 750, 700);
runtimeGame.getInputManager().onTouchMove(1, 100, 200);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchEnd(2);
// Drag'n'drop
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(2, 450, 500);
runtimeGame.getInputManager().onTouchStart(1, 650, 600);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchMove(2, 750, 700);
runtimeGame.getInputManager().onTouchMove(1, 100, 200);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onFrameEnded();
runtimeGame.getInputManager().onTouchEnd(1);
runtimeGame.getInputManager().onTouchEnd(2);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(700);
expect(object2.getX()).to.be(100);
expect(object2.getY()).to.be(200);
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(700);
expect(object2.getX()).to.be(100);
expect(object2.getY()).to.be(200);
// Avoid side effects on the following test cases
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onFrameEnded();
});
[false, true].forEach((firstInFront) => {
it(`must drag the object in front (${
firstInFront ? '1st object' : '2nd object'
} in front)`, function () {
object.setPosition(450, 500);
object2.setPosition(450, 500);
if (firstInFront) {
object.setZOrder(2);
object2.setZOrder(1);
} else {
object.setZOrder(1);
object2.setZOrder(2);
}
// Drag'n'drop
runtimeGame.getInputManager().touchSimulateMouse(false);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
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);
runtimeGame.getInputManager().onTouchEnd(1);
runtimeScene.renderAndStep(1000 / 60);
runtimeGame.getInputManager().onFrameEnded();
if (firstInFront) {
// The 1st object moved
expect(object.getX()).to.be(750);
expect(object.getY()).to.be(600);
expect(object2.getX()).to.be(450);
expect(object2.getY()).to.be(500);
} else {
// The 2nd object moved
expect(object.getX()).to.be(450);
expect(object.getY()).to.be(500);
expect(object2.getX()).to.be(750);
expect(object2.getY()).to.be(600);
}
});
});
});
});