Let's run the layout. If our condition is well-formed, if we drag a piece, the pieces around its "hole" will flash, telling us we are looking for these pieces.
Are out target pieces in the right place?
Now we have to check if these pieces are close enough to be snapped around us. Add the following actions below Actualpiece.flash:
Set IdealX to (ActualPiece.PlaceX-CompareX)*PieceStepX+MyX
Set IdealY to (ActualPiece.PlaceY-CompareY)*PieceStepY+MyY
These two variables will store were we want the neighbour to be.
If our neighbour is on the left (ActualPiece.PlaceX-CompareX=-1), the piece will be PieceStepX pixels on the left of MyX, thus its coordinates will be (PieceStepx * -1) + MyX = -PieceStepX + MyX = MyX-PieceStepX.
If our neighbour is in the right, instead, the piece will be PiecestepX pixels on the right (ActualPiece.PlaceX-CompareX=+1, we will we adding instead of subtracting).
If our neighbour is on top or on bottom, similar calculations will be issued on IdealY.
Add another subevent (a sub-sub-subevent!) with the following condition:
Distance(ActualPiece.X, ActualPiece.Y, Ideal.X, Ideal.Y) = 5
So, we are checking if the pieces are, at least, 5 pixels away. You can change "5" for your preferred "error margin". The biggest, the easiest your game will be.
We use distance() as an easy way to calculate distance between pieces. I think using abs() and subtractions should be faster (distance() calculates a square root), but, after all the previous calculations I think you prefer an easy way.
Finally, move the "Flash" action to the last subevent and run the project. Pieces should flash now when "snapping" at the best position.
The previous point should be the more difficult. Now we just have to join pieces together, using the "Pin" behaviour. But, before we use "Pin", we should move the pieces slightly to their best match. What's their best match? --Guess it: (IdealX, IdealY)
So, below the "Flash" action, add:
ActualPiece=>Set position (IdealX, IdealY)
There is also another obstacle. Imagine you add the following code to your project:
ActualPiece=>Pin to another object =>...
We will find two difficulties.
First of all, we don't know which object to pin ActualPiece to. We need to store an UID to the ActualPiece instance we were dropping, but, if we do that, we will loose our reference of the matching ActualPiece. So pinning between instances of the same object is not easy!
Second, even if we pin two ActualPieces together, the first will move the second, but the second won't move the first (at least on the tests I did a long time ago). This is why we need "grouping".
Add the following to your code:
On the local variables block below "Actualpiece.On dragdrop drop", add a local variable called "MyGroup", and set it to 0.
At the blank event under the local variables, below "Set compareY to ActualPiece.PlaceY", add:
System=>Set Value MyGroup to ActualPiece.Group
Then, at the bottom subevent, after the "ActualPiece.SetPosition" action, add this one:
ActualPiece=>Set Group to ActualPiece.Group.
Now go to the layout view and insert an empty sprite (without image) called Pin. In the sample file, we have made it visible and blue for you to see it, but it should be invisible in a production environment.
Add the "Pin" behaviour to the "Pin" sprite.
Then, return to the "ActualPiece.On Drag" event and add:
System.Create Object Pin at (ActualPiece.X,ActualPiece.Y)
Pin Pin to ActualPiece (Position and angle)
We destroy the Pin before pinning it to the piece. This way, if we have something pinned to the original "Pin" object, it is "un-pinned" from it (this is faster than looking for objects pinned to it).
Below it, add a blank sub event. On top of the blank sub-event, add the following local variables:
MyUID (value:0). This will store current ActualPiece UID.
MyGroup (value:0). This will store current ActualPiece Group.
On the blank subevent, add:
Set MyUID to ActualPiece.UID
Set MyGroup to ActualPiece.Group
Add a sub-subevent to the blank subevent, with the following conditions:
System.Pick all Actualpiece
ActualPiece. Compare instance Variable Group with MyGroup
System. Pick ActualPiece by evaluating ActualPiece.UID<>MyUID.
Put the following action on it:
If you run the project now, you will see you can drag a piece and snap it to another one. But there are undesired behaviours, since pieces in the group will not "snap" if they are not the actual piece we are dragging. To fix it, we should use a loop to check all the pieces in the "dropped" group against pieces not in the group.
We will try to do it in a further tutorial.
Next in series
Making a Jigsaw Puzzle - Part four: Managing grouped pieces explains how to check the whole group for matching pieces and how to join groups together, along with minor details such as adding some audio, shuffling pieces or running a special event when the JigSaw is solved.