0 votes

I'm trying to create a kind of slide picture puzzle to put in order the pieces of a picture by swapping a tile with the next one. I'm using a tilemap in which I divide the picture in 24 pieces by creating a new atlas from the picture.
So far I have done this:

extends Node2D

var PICTURE = [0,1,2,3,4] # array with images to choose from

var touch_y_limit = 5
var first_touch = Vector2.ZERO
var final_touch = Vector2.ZERO
var swipe_limit = 10.0
var swiping = false
var first_cell
var sec_cell
var cell_one
var cell_two
func _ready():
    tileset_correct()

func tileset_correct():
for i in range(0,4):
    for j in range(0,6):
        $Background/Grid.set_cell(i, j, PICTURE[1], false, false, false, Vector2(i,j))

func _process(delta):
touch_input()

func touch_input():
if Input.is_action_just_pressed("touch"):
    if get_global_mouse_position().y > touch_y_limit:
        first_touch = get_global_mouse_position()
                    var first_with_offset = first_touch - $Background/Grid.position
        first_cell = $Background/Grid.world_to_map(first_with_offset)
        swiping = true

if Input.is_action_just_released("touch") and swiping:
    final_touch = get_global_mouse_position()
            var final_with_offset = final_touch - $Background/Grid.position
    print($Background/Grid.world_to_map(final_touch))
    var sec_cell = $Background/Grid.world_to_map(final_with_offset)
    $Background/Grid.set_cell(sec_cell.x, sec_cell.y , PICTURE[1], false, false, false, Vector2(first_cell))
    $Background/Grid.set_cell(first_cell.x, first_cell.y,PICTURE[1], false, false, false, Vector2(sec_cell))
    calculate_direction()
    swiping = false

func calculate_direction():
    var swipe_vector = first_touch - final_touch
if swipe_vector.length() > swipe_limit:
    var temp = rad2deg(swipe_vector.angle())
    if temp > -45 and temp <= 45:   
        move_to = "Left"        
    elif temp > 45 and temp <= 135: 
        move_to = "Up"
    elif temp > 135 and temp <= 225:
        move_to = "Right"
    elif temp > -135 and temp <= -45:
        move_to = "Down"
final_touch = Vector2.ZERO  
first_touch = Vector2.ZERO

With the above I can swap the tile with the next one by using the cell coordinations for the tiles. But then I cannot swap again these two tiles. This is because at the begining all tiles have the same coordinations with the cell positions. So in cell (0,0) is the tile (0,0), in (0,1), is the (0,1) etc. But when this change I don't know how to track which tile is in a cell.
I tried to use tileset.geticon_coordinates , but this gives every time (0,0), so I don't know how to access the tiles positions and keep track in which cell is each one.
Is this possible with tilemap?

EDIT:
I updated the code of gd script.
There is one scene so far for this puzzle(I have some others in this project with another mini game but for now aren't connected to each other)
The scene contains a TextureRect and the Tilemap as child of it.
--Node2D
---Background
----Grid

in Engine by (219 points)
edited by

Can you provide a more complete example so we can reproduce this puzzle/problem :-) And your code need some more space for a correct indentation.

I updated my question

Thanks for sharing your code. I learned something :-)

2 Answers

+2 votes
Best answer

The code below does not use Atlas tiles but just expect correct numberdimensions of tiles. It works for me.

I have reduced the variables needed and typed them so autocomplete functions :-)

extends Node2D

# define touch action for left mouse down

# This assumes a tilemap with same dimensions and tiles set in order
export var puzzle_dimensions:Vector2 = Vector2(3,4)
onready var puzzle:TileMap = $Background/Grid

var touch_y_limit:int = 5
var swipe_limit:float = 10.0
var swiping:bool = false
var first_cell:Vector2
var sec_cell:Vector2

func get_tile_index(x:int, y:int) -> int:
    return x + y * int(puzzle_dimensions.x)

func _ready():
    solved_puzzle()

func solved_puzzle():
    for i in range(puzzle_dimensions.x):
        for j in range(puzzle_dimensions.y):
            var index = get_tile_index(i,j)
            puzzle.set_cell(i, j, index)

func _process(delta):
    touch_input()

func touch_input():
    var touch_location:Vector2

    if Input.is_action_just_pressed("touch"):
        if get_global_mouse_position().y > touch_y_limit:
            touch_location = get_global_mouse_position()
            first_cell = puzzle.world_to_map(touch_location)
            swiping = true

    if Input.is_action_just_released("touch") and swiping:
        touch_location = get_global_mouse_position()
        sec_cell = puzzle.world_to_map(touch_location)

        var first_tile:int = puzzle.get_cellv(first_cell)
        var sec_tile:int = puzzle.get_cellv(sec_cell)
        puzzle.set_cellv(first_cell, sec_tile)
        puzzle.set_cellv(sec_cell, first_tile)

        swiping = false
by (636 points)
selected by

Thanks a lot.
I have some questions though.
the :
export var puzzle_dimensions:Vector2 = Vector2(3,4)
is the grid's number of cells(4 columns, 5 rows)? So in my case will be Vector2(3,5), because the image is divided to 24 pieces(4 columns and 6 rows), if the count starts from 0.
And also the tilemap's position doesn't start at position 0,0 but is a bit right and down.
I already tried to calculate it(I have updated my first post) like this:

first_touch = get_global_mouse_position()
var first_with_offset = first_touch - $Background/Grid.position
first_cell = $Background/Grid.world_to_map(first_with_offset)

and:

final_touch = get_global_mouse_position()
var final_with_offset = first_touch - $Background/Grid.position
first_cell = $Background/Grid.world_to_map(final_with_offset)

EDIT:
I started to implement your suggestions, but seems that doesn't work for me.
With the function I had in "ready", I had the picture drawn on screen, but with the "solvedpuzzle" function I just get the first tile of every image I had in tileset. At this time I have 2 images on the tileset. I set them like this:
- Drag and drop the first image in tileset editor
- Select it and press the +New Atlas button
- Set the cell to 130x158
- In region tap I select all image to get the divided 24 cells for the image
Same procedure for the 2nd image.
-Save the tileset.

EDIT2:
I din't find a way to draw all image's tiles, I returned to my function to display the solved image, and I managed to make it work.
I created 2 more variables to store the autotile's coordinations and with this I can swap the tiles. I put all those in 2 new fucntions which I use in "touch" and "release" input if statements:

func swap_tile_start():
    var first_with_offset = first_touch - puzzle.position
    print(first_with_offset)
    print(first_touch)
    first_cell = puzzle.world_to_map(first_with_offset)
    print(first_cell)
    if is_in_grid(first_cell.x, first_cell.y):
        print(first_cell.x, first_cell.y)
        grid_controling = true
        cell_one = puzzle.get_cell(first_cell.x, first_cell.y)
        autotile_one = puzzle.get_cell_autotile_coord(first_cell.x, first_cell.y)
        #print("cell1 " + str(autotile_one))



func swap_tile_end():
    var final_with_offset = final_touch - puzzle.position
    sec_cell = puzzle.world_to_map(final_with_offset)
    print(sec_cell)
    if is_in_grid(sec_cell.x,sec_cell.y) and grid_controling:
        cell_two = puzzle.get_cell(sec_cell.x, sec_cell.y)
        autotile_two = puzzle.get_cell_autotile_coord(sec_cell.x, sec_cell.y)
        #print("cell2 " + str(puzzle.get_cell_autotile_coord(sec_cell.x,sec_cell.y)))
        puzzle.set_cell(sec_cell.x, sec_cell.y , cell_one, false, false, false, Vector2(autotile_one))
        puzzle.set_cell(first_cell.x, first_cell.y, cell_two, false, false, false, Vector2(autotile_two))

Like this works, but there are 2 more problems now:
1. Limit the draw and swap tile actions in the area of those 24 cells
2. Limit swipe to only the next tile up/down/left/right from the first.

Are we talking about tiles or real pictures/images :-)

My puzzle is one image.

My 2D scene has root Node2D with child Node2D named ** Background** with ChildTileMap` named Grid

I dragged a (32x3) x (32x4) image on the TileMap/TileSet then clicked New Single Tile 3x4 times and select each cell in order.

Then attached my script and it did it's job.

Hope this helps.

var dim:Vector2 = Vector2(3,4)

used in

for i in range(dim.x):
for j in range(dim.y):

makes i iterate over [0,1,2] and j over [0,1,2,3].

Restricting the swaps you can use

if sec_cell.distance_to(first_cell) == 1:

No, I'm talking about real images, I have 2(for now, but I added more) 525x948 pixels size. I used new atlas to divide each in 24 pieces, so every piece is a part(a region) of the image.
Thanks for the tip about restricting swaps, works fine.
The main problem now is the bounds of the tilemap. I tried to control so to swap only for a range of cells

func is_in_grid(col, row):
    if col >= 0 and col < 4:
        if row >= 0 and row < 6:
            grid_controling = true
    else:
        grid_controling = false
    return grid_controling

I call this function and I can see that called with print messages, but the swap action still applies on cells (-1,0), (-1,1)...., (0,6), (1,6).... which cause wired draws and messes up the image.

I guess you must check both first and second cells. From my head something like:

rect:Rect = Rect.new(dimensions)
rect.contains(cellFirst) and rect.contains(cellSeconds)

Thanks,
the above code didn't work because there's no rect, but I think I get the idea.
Probably you meant the Rect2 in which I get error about new() constant and contains method.
But after some trial and error I did it like this:

var rect = Rect2(Vector2.ZERO, puzzle_dimensions)
if rect.has_point(first_cell) and rect.has_point(sec_cell) and sec_cell.distance_to(first_cell) == 1:

and generally works. I have some problems though because on some cells(mostly on the sides) the swap action doesn't trigger in all cell' s area. For example on top 3 cells must to start the action by click(or touch) in the upper side of the cell.

I have some problems though because on some cells(mostly on the sides) the swap action doesn't trigger in all cell' s area.

You can always log the cell position on mouse move events to figure out what is wrong. My guess is the TileMap is not positioned on screen on Vector2.ZERO?

var mousePosition:Vector2 = get_global_mouse_position()
var local_position:Vector2 = puzzle.to_local(mousePosition)
var map_position:Vector2 = puzzle.world_to_map(local_position)
print(map_position)

If my answer is satisfying please mark it as such ;-)

Thanks again.
Yes, probalbly isn' t because I have a background picture with a frame. So I had move the tilemap position to right and down about 40pixels. But in some point I added another colorRect and put the tilemap inside it.
I also had commented out the variable below:
var firstwithoffset = first_touch - puzzle.position
Now I enable it and use it again and seems that is fine now.

+1 vote

So it looks like you're not referencing the cells at all but other than that everything's legit.

var cell_one = get_cell(first_cell.x, first_cell.y)
var cell_two = get_cell(sec_cell.x, sec_cell.y)

$Background/Grid.set_cell(sec_cell.x, sec_cell.y , cell_one, false, false, false, Vector2(first_cell))
$Background/Grid.set_cell(first_cell.x, first_cell.y, cell_two, false, false, false, Vector2(sec_cell))

Not that its any of my business but for future reference try using comments in your code for backtracking or assistance purposes and maintain as short of lines as possible.

by (4,518 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.