Magnetic attraction and capturing mouse events to the canvas

61 views
Skip to first unread message

Sandro Magi

unread,
Mar 4, 2016, 10:32:35 AM3/4/16
to Crafty
I've managed to learn enough Crafty for some basic demos of what I want to do. For instance, here's a sample of some movable boxes with collision detection and Google maps-like zoom functionality: http://jsfiddle.net/b9ozcm91/

Feel free to include the zoom function in Crafty if you think it would be useful. Also, please let me know if there are better ways of doing what I've done.

So I have two challenges to tackle next:

 1. take the above example and make each side of the box "magnetic", so that if one box gets sufficiently close to another, they "snap" together, and dragging the mouse sufficiently far away pulls them apart.
 2. make the boxes selectable by clicking or dragging the mouse on the background to encompass a set of boxes, like how you select multiple files in a desktop file manager.

What components should I be looking at to do the above? My current thoughts:

For #1, I'm not sure what would be a Crafty approach. I could register a Move event handler on the box, then use Crafty.findClosestEntityByComponent for each side of the box and call .move() if it meets the criteria. Would that be the best approach? To pull them apart, perhaps add the MouseDrag component for the Dragging event, and if the force of the drag is above a threshold, move them apart past the closeness threshold?

For #2, I can capture the 'Click' event for the Mouse component and add a custom component name to "select" a box, but it still fires even if I was dragging the box (not sure if this is a bug). I would like to know how to avoid it a click even on a drag.

Then I also want a click on the background to "deselect" any selected entities. I don't see how to capture a mouse click that's not on an entity though. I could obviously capture the DOM click event on the canvas element, but I figured there must be a Crafty-way I'm not seeing. Once captured, I can just do something like:  Crafty(selectComponent).each(function(i) { this.removeComponent(selectComponent); });

Sandro

mucaho

unread,
Mar 4, 2016, 2:11:17 PM3/4/16
to Crafty
How does the zoom functionality work? Does it just zoom in / out when you scroll the mouse wheel or is it doing something behind the scenes? Is this functionality missing in Crafty or bugged at the moment?
(I'm not that familiar with all the viewport goodies in Crafty)

Your proposed solution for #1 sounds good.
You are investigating stuff for the currently moving object only, which is better than doing it for every (currently not moving) object.
Using a broadphase search combined with a precise click area investigation (all handled by Crafty.findClosestEntityByComponent) sounds good.
I noticed you are using Canvas display method. Judging from Crafty.findClosestEntityByComponent implementation, finding entities should be a lot faster if you used the DOM render method instead.
Yes, you should be able to calculate dragged distance using evt.realX & evt.realY obtained from StartDrag & Dragging events.

Ad #2:
I think the "Click" event is passed through from the original DOM event, the "Drag" events are synthetic (created by Crafty) and tacked on afterwards. You could maybe deselect the box again when it get starts to get dragged?
You can capture the original DOM events that are propagated through Crafty's main stage element, e.g.
Crafty.addEvent(this, Crafty.stage.elem, "click", myClickHandlerCallback);
Have a look at similar events in crafty's source code.

From a design point-of-view:
Will you allow users to shift click boxes to add them to a selection? Or only by encompassing them from the background?

HTH

Sandro Magi

unread,
Mar 4, 2016, 2:39:06 PM3/4/16
to Crafty
On Friday, 4 March 2016 14:11:17 UTC-5, mucaho wrote:
How does the zoom functionality work? Does it just zoom in / out when you scroll the mouse wheel or is it doing something behind the scenes? Is this functionality missing in Crafty or bugged at the moment?
(I'm not that familiar with all the viewport goodies in Crafty)

It's mouse wheel zoom, so it doesn't work on touch screens. I hope to get to that at some point too, just not there yet.

My zoom is built on Craft's zoom, so Craft's isn't broken, mine is just more suitable to my purposes and possibly others that may use maps in their games and want to be able to navigate and zoom like in Google maps. Craft's zoom requires you to specify the viewport's centre after the zoom, so my zoom computes the new centre based on the current centre, the mouse position and the scaling factor, ie. if the mouse is at the top-right, it zooms to the top-right.
 
Your proposed solution for #1 sounds good.
You are investigating stuff for the currently moving object only, which is better than doing it for every (currently not moving) object.
Using a broadphase search combined with a precise click area investigation (all handled by Crafty.findClosestEntityByComponent) sounds good.
I noticed you are using Canvas display method. Judging from Crafty.findClosestEntityByComponent implementation, finding entities should be a lot faster if you used the DOM render method instead.

Zooming is a big part of what I need, and that works much better with Canvas. I'll be dealing with 100 entities at most, so I don't think performance will be a problem.
 
Yes, you should be able to calculate dragged distance using evt.realX & evt.realY obtained from StartDrag & Dragging events.

Ad #2:
I think the "Click" event is passed through from the original DOM event, the "Drag" events are synthetic (created by Crafty) and tacked on afterwards. You could maybe deselect the box again when it get starts to get dragged?
You can capture the original DOM events that are propagated through Crafty's main stage element, e.g.
Crafty.addEvent(this, Crafty.stage.elem, "click", myClickHandlerCallback);
Have a look at similar events in crafty's source code.

From a design point-of-view:
Will you allow users to shift click boxes to add them to a selection? Or only by encompassing them from the background?

I think I've changed my mind on the background selection because it would interact poorly with mouselook (unless you hold down a key while you select). It will probably all be a shift-click-like selection process in the first version for simplicity.

Thanks for the feedback! I'll try out a few things and post back what code I can which might be useful for Crafty (like the zoom).

Sandro

Sandro Magi

unread,
Mar 6, 2016, 1:05:43 PM3/6/16
to Crafty
I refactored the code as a zoomTowards function on the viewport so it's more general [1], and created a pull request in case you guys would find it useful.

Sandro



On Friday, 4 March 2016 14:11:17 UTC-5, mucaho wrote:

starwed

unread,
Mar 6, 2016, 7:59:44 PM3/6/16
to Crafty
> 1. take the above example and make each side of the box "magnetic", so that if one box gets sufficiently close to another, they "snap" together, and dragging the mouse sufficiently far away pulls them apart.

I had to do something pretty similar -- I added an invisible entity, set it to follow the base entity, and set the hitbox to extend several pixels beyond the visible area.  When the visible entity moves, it calls the `hit()` method of the inivisble one, and if there's a suitable intersection, it allows them to latch together.  In my case I also did a check for the velocity the object was moving at -- at large speeds the objects recoiled instead of snapping together.

Sandro Magi

unread,
Mar 7, 2016, 6:21:38 PM3/7/16
to Crafty
It's a good idea, but after struggling with this for a while, I just went with making the objects completely solid on collision: https://jsfiddle.net/5adrugp3/5/

They can fit the blocks together by sliding along each other this way. I've documented various trials and tribulations below if anyone is interested in seeing how many utterly wrong ways there is to do this. :-)

Sandro

[1] catch move event, but mouse is "sticky" and objects stops dragging for some reason: https://jsfiddle.net/5adrugp3/
[2] try to have the visible element catch Dragging event and check for hits with the "magnetic" component, but this hits on its own child entity, so no go: https://jsfiddle.net/5adrugp3/1/
[3] I could register the HitOn event, but then that ends up moving *both* boxes when I only want to move the one being dragged: https://jsfiddle.net/5adrugp3/2/
[4] this works, but stops the dragging on snap: https://jsfiddle.net/5adrugp3/3/
Reply all
Reply to author
Forward
0 new messages