Les tuiles avec love2d

Info
Assurez-vous de lire les commentaires dans le code, je mets beaucoup d’informations importantes là-dedans

Dans de nombreux jeux 2D, les niveaux sont constitués de tuiles. Nous allons créer notre propre niveau en mosaïque.

Commençons par créer une ligne. Créez un tableau et remplissez-le de uns et de zéros.

1
2
3
function love.load()
  tilemap = {1, 0, 0, 1, 1, 0, 1, 1, 1, 0}
end

C’est notre niveau. Un 1 est une tuile et un 0 est vide. Maintenant, nous devons le dessiner. Nous parcourons la table, et chaque fois que nous rencontrons un 1, nous dessinons un rectangle sur sa position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function love.draw()
  --ipairs recap
  --ipairs is a special function that allows you to loop through a table
  --Every iteration i becomes what iteration the loop is at, so 1, 2, 3, 4, etc)
  --Every iteration v becomes the value on position i, so in our case 1, 0, 0, 1, 1, 0, etc.
  for i,v in ipairs(tilemap) do
    if v == 1 then
      love.graphics.rectangle("line", i * 25, 100, 25, 25)
    end
  end
end
Représentation des tuiles dessinées
Représentation des tuiles dessinées

D’accord, cela fonctionne, mais maintenant nous voulons aller verticalement. Pour ce faire, nous mettons des tables à l’intérieur d’une table, également connue sous le nom de table 2D.

1
2
3
4
5
6
7
8
9
function love.load()
  tilemap = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 0, 0, 1, 1, 1, 1, 0, 0, 1},
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  }
end

Alors maintenant, nous avons un tableau rempli de tableaux. Voyez-le comme un tableau Excel.

Représentation du tableau sous excel
Représentation du tableau sous excel

1, 2, 3, etc. sont ce que nous appelons des lignes et A, B, C, etc. sont appelés des colonnes.

Une autre façon de voir les choses comme une petite ville

Une autre représentation mais avec des maisons
Une autre représentation mais avec des maisons

Chaque rangée de maisons est une table, et plusieurs rangées font toute la ville, ou dans notre cas, notre niveau.

La maison verte est au 2ème rang sur la 5ème colonne.

La maison rouge est au 3ème rang de la 2ème colonne.

Avec les tableaux 2D, nous accédons aux valeurs comme ceci :

1
tilemap[4][3]

Cela signifie : La 3ème valeur de la 4ème table ou la 3ème colonne de la 4ème ligne.

Comment le programme lit notre code
Comment le programme lit notre code

Dessinons notre niveau. Parce que nous avons une table 2D, nous devons utiliser une boucle for à l’intérieur d’une boucle for. Plus communément on appelle ceci une boucle for imbriquée.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function love.draw()
  --Let's do it without ipairs first.

  --For i=1 till the number of values in tilemap
  for i=1,#tilemap do
    --For j till the number of values in this row
    for j=1,#tilemap[i] do
      --If the value on row i, column j equals 1
      if tilemap[i][j] == 1 then
        --Draw the rectangle.
        --Use i and j to position the rectangle.
        -- j for x, i for y.
        love.graphics.rectangle("line", j * 25, i * 25, 25, 25)
      end 
    end
  end
end

Nous parcourons donc nos lignes et pour chaque ligne, nous parcourons nos colonnes.

Nous utilisons j, de notre boucle interne pour notre positionnement horizontal et i, de notre boucle externe pour le positionnement y. N’oubliez pas que ce ne sont que des noms de variables et peuvent être nommés, mais utiliser i et j comme ceci est courant.

Transformons les boucles for en une boucle ipairs.

1
2
3
4
5
6
7
8
9
function love.draw()
  for i,row in ipairs(tilemap) do
    for j,tile in ipairs(row) do
      if tile == 1 then
        love.graphics.rectangle("line", j * 25, i * 25, 25, 25)
      end
    end
  end
end

Nous utilisons deux variables, row et tile, pour rendre plus clair ce qui se passe. Nous parcourons le tilemap de la table et chaque valeur est une ligne. Nous parcourons la ligne et chaque valeur est une tuile.

Nous pouvons également utiliser des numéros différents pour nos carreaux, et utiliser ces numéros pour donner aux carreaux des couleurs différentes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function love.load()
    tilemap = {
        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
        {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
        {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
        {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
    }
end

function love.draw()
  for i,row in ipairs(tilemap) do
    for j,tile in ipairs(row) do
        --First check if the tile is not zero
      if tile ~= 0 then

        --Set the color based on the tile number
        if tile == 1 then
          --setColor uses RGB, A is optional
          --Red, Green, Blue, Alpha
          love.graphics.setColor(1, 1, 1)
        elseif tile == 2 then
          love.graphics.setColor(1, 0, 0)
        elseif tile == 3 then
          love.graphics.setColor(1, 0, 1)
        elseif tile == 4 then
          love.graphics.setColor(0, 0, 1)
        elseif tile == 5 then
          love.graphics.setColor(0, 1, 1)
        end

        --Draw the tile
        love.graphics.rectangle("fill", j * 25, i * 25, 25, 25)
      end 
    end
  end
end

Ou une meilleure façon de procéder :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function love.load()
  tilemap = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
    {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
    {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  }

  --Create a table named colors
  colors = {
    --Fill it with tables filled with RGB numbers
    {1, 1, 1},
    {1, 0, 0},
    {1, 0, 1},
    {0, 0, 1},
    {0, 1, 1}
  }
end

function love.draw()
  for i,row in ipairs(tilemap) do
    for j,tile in ipairs(row) do
        --First check if the tile is not zero
      if tile ~= 0 then
        --Set the color. .setColor() also accepts a table with 3 numbers.
        --We pass the table with as position the value of tile.
        --So if tile equals 3 then we pass colors[3] which is {1, 0, 1}
        love.graphics.setColor(colors[tile])
        --Draw the tile
        love.graphics.rectangle("fill", j * 25, i * 25, 25, 25)
      end
    end
  end
end

Images

Nous pouvons donc créer un niveau coloré, mais maintenant nous voulons utiliser des images. Eh bien, c’est facile, il suffit d’ajouter une image, d’obtenir la largeur et la hauteur et de dessiner l’image au lieu d’un rectangle.

Je vais utiliser cette image :

Image qui servira de tuile pour les murs
Image qui servira de tuile pour les murs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function love.load()
  --Load the image
  image = love.graphics.newImage("tile.png")

  --Get the width and height
  width = image:getWidth()
  height = image:getHeight()

  tilemap = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
    {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
    {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  }

  colors = {
    --Fill it with tables filled with RGB numbers
    {1, 1, 1},
    {1, 0, 0},
    {1, 0, 1},
    {0, 0, 1},
    {0, 1, 1}
  }
end

function love.draw()
  for i,row in ipairs(tilemap) do
    for j,tile in ipairs(row) do
      if tile ~= 0 then
        love.graphics.setColor(colors[tile])
        --Draw the image
        love.graphics.draw(image, j * width, i * height)
      end
    end
  end
end

C’est donc simple. Mais que se passe-t-il si nous voulons dessiner des images différentes ? Eh bien, nous pourrions utiliser plusieurs images, mais dans le chapitre précédent, nous avons appris comment dessiner une partie d’une image avec des quads. Nous pouvons également l’utiliser pour les carreaux.

Utilisons ce jeu de tuiles :

Jeu de tuiles
Jeu de tuiles

Nous devons d’abord créer les quads.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function love.load()
  --Load the image
  image = love.graphics.newImage("tileset.png")

  --We need the full image width and height for creating the quads
  local image_width = image:getWidth()
  local image_height = image:getHeight()

  --The width and height of each tile is 32, 32
  --So we could do:
  width = 32
  height = 32
  --But let's say we don't know the width and height of a tile
  --We can also use the number of rows and columns in the tileset
  --Our tileset has 2 rows and 3 columns
  --But we need to subtract 2 to make up for the empty pixels we included to prevent bleeding
  width = (image_width / 3) - 2
  height = (image_height / 2) - 2

  --Create the quads
  quads = {}

  for i=0,1 do
    for j=0,2 do
      --The only reason this code is split up in multiple lines
      --is so that it fits the page
      table.insert(quads,
          love.graphics.newQuad(
              1 + j * (width + 2),
              1 + i * (height + 2),
              width, height,
              image_width, image_height))
    end
  end

  tilemap = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
    {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
    {1, 2, 3, 4, 5, 5, 4, 3, 2, 1},
    {1, 2, 2, 2, 2, 2, 2, 2, 2, 1},
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
  }
end

Maintenant que nous avons une table avec des quads, nous mettons le nombre correspondant dans notre tilemap en fonction du quad que nous voulons. En fonction de l’ordre dans lequel nous avons créé nos quads, ils se trouvent sur cette position dans notre tableau :

Jeu de tuiles numéroté pour se faciliter la vie
Jeu de tuiles numéroté pour se faciliter la vie

Donc si nous voulons créer ceci :

Ce a quoi devra ressembler notre niveau
Ce a quoi devra ressembler notre niveau

Nous ferions ressembler notre tilemap à ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
tilemap = {
  {1, 6, 6, 2, 1, 6, 6, 2},
  {3, 0, 0, 4, 5, 0, 0, 3},
  {3, 0, 0, 0, 0, 0, 0, 3},
  {4, 2, 0, 0, 0, 0, 1, 5},
  {1, 5, 0, 0, 0, 0, 4, 2},
  {3, 0, 0, 0, 0, 0, 0, 3},
  {3, 0, 0, 1, 2, 0, 0, 3},
  {4, 6, 6, 5, 4, 6, 6, 5}
}

Si vous comparez le tilemap avec l’image et les nombres, vous pouvez voir comment chaque tuile est utilisée.

Maintenant, nous devons dessiner le bon quad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function love.draw()
  for i,row in ipairs(tilemap) do
    for j,tile in ipairs(row) do
      if tile ~= 0 then
        --Draw the image with the correct quad
        love.graphics.draw(image, quads[tile], j * width, i * height)
      end
    end
  end
end

Donc sur (1,1) nous dessinons le quad en position 1. Sur (1,2) nous dessinons le quad sur la position 6, etc.

Si vous lancez le jeu, vous verrez que notre niveau ressemble maintenant à l’image ci-dessus.

Joueur

Maintenant que nous avons un niveau, créons un joueur qui peut se promener, mais pas traverser les murs.

Je vais utiliser cette image pour le joueur :

Image représentant le joueur
Image représentant le joueur
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function love.load()
  image = love.graphics.newImage("tileset.png")

  local image_width = image:getWidth()
  local image_height = image:getHeight()
  width = (image_width / 3) - 2
  height = (image_height / 2) - 2

  quads = {}

  for i=0,1 do
    for j=0,2 do
      table.insert(quads,
        love.graphics.newQuad(
          1 + j * (width + 2),
          1 + i * (height + 2),
          width, height,
          image_width, image_height))
    end
  end

  tilemap = {
    {1, 6, 6, 2, 1, 6, 6, 2},
    {3, 0, 0, 4, 5, 0, 0, 3},
    {3, 0, 0, 0, 0, 0, 0, 3},
    {4, 2, 0, 0, 0, 0, 1, 5},
    {1, 5, 0, 0, 0, 0, 4, 2},
    {3, 0, 0, 0, 0, 0, 0, 3},
    {3, 0, 0, 1, 2, 0, 0, 3},
    {4, 6, 6, 5, 4, 6, 6, 5}
  }

  --Create our player
  player = {
    image = love.graphics.newImage("player.png"),
    tile_x = 2,
    tile_y = 2
  }
end

Le tile_x et tile_y est la position du joueur sur notre tilemap. Ce nombre sera multiplié par la largeur et la hauteur des carreaux lorsqu’ils seront dessinés. Mais d’abord, faisons bouger les choses. Au lieu d’un mouvement en douceur, nous le ferons sauter à sa position suivante, nous n’aurons donc pas besoin de dt pour le mouvement. Cela signifie également que nous ne voulons pas savoir si les touches de mouvement sont enfoncées, mais si elles sont enfoncées. Pour cela, nous utilisons l’événement love.keypressed.

Nous créons d’abord la variable locale x et y. Ensuite, nous ajoutons ou soustrayons 1 à cette variable en fonction de la touche qui a été enfoncée, et finalement nous attribuons cette valeur à la position du joueur.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function love.keypressed(key)
  local x = player.tile_x
  local y = player.tile_y

  if key == "left" then
      x = x - 1
  elseif key == "right" then
      x = x + 1
  elseif key == "up" then
      y = y - 1
  elseif key == "down" then
      y = y + 1
  end

  player.tile_x = x
  player.tile_y = y
end

Maintenant qu’il peut bouger, dessinons-le.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function love.draw()
  for i,row in ipairs(tilemap) do
    for j,tile in ipairs(row) do
      if tile ~= 0 then
        --Draw the image with the correct quad
        love.graphics.draw(image, quads[tile], j * width, i * height)
      end
    end
  end

  --Draw the player and multiple its tile position with the tile width and height
  love.graphics.draw(player.image, player.tile_x * width, player.tile_y * height)
end
Ce que permet de faire le code actuel
Ce que permet de faire le code actuel

Lorsque vous exécutez le jeu, vous devriez pouvoir vous promener avec votre joueur. Mais le problème est qu’il peut traverser les murs. Corrigeons cela en vérifiant si la position où il veut aller est un mur.

Créez d’abord une fonction appelée isEmpty. À l’intérieur, nous retournons si la valeur sur les coordonnées est égale à 0.

1
2
3
function isEmpty(x, y)
  return tilemap[y][x] == 0
end

Il peut sembler étrange que x et y soient inversés, mais c’est correct. Parce que la position y est la ligne et la position x est la colonne.

Maintenant que nous avons notre fonction, nous pouvons vérifier si l’endroit où nous voulons aller est un endroit vide, et si c’est le cas, cela signifie que nous pouvons y aller.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function love.keypressed(key)
  local x = player.tile_x
  local y = player.tile_y

  if key == "left" then
    x = x - 1
  elseif key == "right" then
    x = x + 1
  elseif key == "up" then
    y = y - 1
  elseif key == "down" then
    y = y + 1
  end

  if isEmpty(x, y) then
    player.tile_x = x
    player.tile_y = y
  end
end
Ce que permet de faire le code définitif
Ce que permet de faire le code définitif

Super, maintenant notre joueur est piégé à l’intérieur de nos murs. Essayez de voir si vous pouvez lui faire ramasser des objets ou lui faire ouvrir une porte lorsque vous touchez une touche. Chercher des idées et essayez de les appliquer. C’est comme ça que vous apprendrez.

Résumé

Nous pouvons utiliser des tuiles pour faire des niveaux. Un tilemap est constitué de lignes et de colonnes. Chaque ligne contient un certain nombre de colonnes. Les lignes sont alignées verticalement et les colonnes horizontalement. Nous pouvons utiliser un jeu de tuiles et des quads pour dessiner notre niveau.