Sujet n°13848
Posté par Pαlвσlѕку le 14 Juin - 16:42 (2014)
Titre : [Script] Puzzle
Bonjour à tous !

Aujourd'hui je viens vous présenter un script que j'ai réalisé pour elric54 mais que je donne à disposition pour tous.
elric54 a déjà mis le script en ligne ici mais j'ai décidé de faire un topic pour mieux le présenter.

Ce script permet donc de faire un puzzle de 16 pièces, comme c'était le cas dans Pokémon AOC (et HGSS ?) dans les ruines Alpha.
Il est possible de faire différents puzzles.

Voici un petit screen :
Screen


J'ai mis des nombres sur deux pièces sinon il y aurait eu 2 pièces identiques.


Tout d'abord voici le code :
Script
Code:
#============================================================================== 
# ■ Puzzle 
# By Palbolsky 
# 14/06/2014 
#----------------------------------------------------------------------------- 
 
class Puzzle 
  # Constantes 
  # Mettez le nom du background si vous en avez un (ex : image.png) 
  BACKGROUND = "" 
  # La dimension de vos pièces (par défaut 108x108) 
  DIMENSION_PIECE = 108 
  # Position de base de la grille des pièces 
  POSITION_GRILLE_X = 90 
  POSITION_GRILLE_Y = 15 
  # L'épaisseur du curseur (par défaut 3 pixels) 
  EPAISSEUR_CURSEUR = 3 
  $puzzleReussi = false 
   
  def initialize(id = 1)
    # Initialisation & déclaration de variables 
    @tableauPuzzle = Array.new(16, [0, 0])   
    @spPiecePuzzle = Array.new(16)     
    @index = 0 
    @mode = 0 
    @id = id
    # Fond d'écran 
    @background = Sprite.new 
    if (BACKGROUND != "") 
      @background.bitmap = RPG::Cache.picture("puzzle#{@id}/#{BACKGROUND}") 
    end     
    @background.z = 0 
    # Image du curseur 
    @curseur = Sprite.new 
    @curseur.bitmap = RPG::Cache.picture("puzzle#{@id}/curseur.png") 
    @curseur.z = 2 
    # Création des images des pièces 
    j, k = 0, 0 
    16.times do |i| 
      @spPiecePuzzle[i] = Sprite.new       
      @spPiecePuzzle[i].bitmap = Bitmap.new(DIMENSION_PIECE, DIMENSION_PIECE) 
      # Coordonnées de l'image 
      largeur = POSITION_GRILLE_X + 5 + @spPiecePuzzle[i].bitmap.width / 2 +   
        (@spPiecePuzzle[i].bitmap.width + 5 ) * j         
      @spPiecePuzzle[i].x = largeur     
      hauteur = POSITION_GRILLE_Y + 5 + @spPiecePuzzle[i].bitmap.height / 2 +   
        (@spPiecePuzzle[i].bitmap.height + 5) * k   
      @spPiecePuzzle[i].y = hauteur 
      # Origine du repère au centre de l'image 
      @spPiecePuzzle[i].ox = @spPiecePuzzle[i].bitmap.width / 2 
      @spPiecePuzzle[i].oy = @spPiecePuzzle[i].bitmap.height / 2 
      @spPiecePuzzle[i].z = 1       
      if (j == 3) 
        j = 0 
        k += 1 
      else 
        j += 1 
      end       
    end 
    creerPuzzle 
    afficherPuzzle 
    dessinerCurseur 
  end 
   
  def main 
    Graphics.transition 
    loop do 
      Graphics.update 
      Input.update 
      update 
      if ($scene != self) 
        break 
      end         
    end 
    Graphics.freeze 
    @background.dispose 
    @background = nil 
    16.times do |i| 
      @spPiecePuzzle[i].dispose 
      @spPiecePuzzle[i] = nil 
    end     
    @curseur.dispose 
    @curseur = nil 
  end 
   
  def update 
    if (@mode == 0) # Déplacement curseur 
      if (Input.trigger?(Input::B)) 
        $game_system.se_play($data_system.cancel_se) 
        $scene = Scene_Map.new 
        return 
      end 
      if (Input.trigger?(Input::C)) 
        $game_system.se_play($data_system.decision_se) 
        @mode = 1 
        return 
      end   
      if (Input.trigger?(Input::UP)) 
        return if (@index < 4) 
        $game_system.se_play($data_system.cursor_se)         
        @index -= 4         
        dessinerCurseur 
        return 
      end 
      if (Input.trigger?(Input::LEFT)) 
        return if (@index % 4 == 0) 
        $game_system.se_play($data_system.cursor_se) 
        @index -= 1         
        dessinerCurseur 
        return 
      end 
      if (Input.trigger?(Input::RIGHT)) 
        return if ((@index + 1) % 4 == 0) 
        $game_system.se_play($data_system.cursor_se) 
        @index += 1         
        dessinerCurseur 
        return 
      end 
      if (Input.trigger?(Input::DOWN)) 
        return if (@index > 11) 
        $game_system.se_play($data_system.cursor_se) 
        @index += 4       
        dessinerCurseur 
        return 
      end       
    elsif (@mode == 1) # Déplacement pièce et rotation 
      if (Input.trigger?(Input::B) or Input.trigger?(Input::C)) 
        $game_system.se_play($data_system.decision_se) 
        if (puzzleFini?) # Vérifie que le puzzle est fini 
          $puzzleReussi = true 
          $scene = Scene_Map.new 
          return 
        else           
          @mode = 0 
          return 
        end 
      end     
      if (Input.trigger?(Input::A)) # Rotation 
        $game_system.se_play($data_system.cursor_se) 
        rotationPiece 
        return 
      end 
      if (Input.trigger?(Input::UP)) 
        return if (@index < 4) 
        $game_system.se_play($data_system.cursor_se) 
        @index -= 4 
        deplacementPiece(4)         
        return 
      end 
      if (Input.trigger?(Input::LEFT)) 
        return if (@index % 4 == 0) 
        $game_system.se_play($data_system.cursor_se) 
        @index -= 1 
        deplacementPiece(1) 
        return 
      end 
      if (Input.trigger?(Input::RIGHT)) 
        return if ((@index + 1) % 4 == 0) 
        $game_system.se_play($data_system.cursor_se) 
        @index += 1 
        deplacementPiece(-1) 
        return 
      end 
      if (Input.trigger?(Input::DOWN)) 
        return if (@index > 11) 
        $game_system.se_play($data_system.cursor_se) 
        @index += 4 
        deplacementPiece(-4) 
        return 
      end     
    end     
  end 
   
  def creerPuzzle 
    # On crée une liste de pièces aléatoires 
    numPiece = Array.new(16) 
    16.times do |i| 
      numPiece[i] = i 
    end 
    numPiece.sort!{|x, y | rand <=> rand}     
    # Ajout des pièces et des rotations dans le tableau 
    16.times do |i| 
      @tableauPuzzle[i] = [numPiece[i], rand(4)] 
    end     
  end   
   
  def afficherPuzzle 
    # On associe l'apparence de la pièce au numéro de la pièce présent dans 
    # tableauPuzzle et on applique la rotation 
    16.times do |i| 
      pathImage = "puzzle#{@id}/" + sprintf("%02d", @tableauPuzzle[i][0] + 1) 
      @spPiecePuzzle[i].bitmap = RPG::Cache.picture("#{pathImage}.png") 
      @spPiecePuzzle[i].angle = 90 * @tableauPuzzle[i][1]       
    end 
  end   
   
  def dessinerCurseur 
    # Coordonnées du curseur 
    @curseur.x = @spPiecePuzzle[@index].x - EPAISSEUR_CURSEUR -   
                  @spPiecePuzzle[@index].bitmap.width / 2 
    @curseur.y = @spPiecePuzzle[@index].y - EPAISSEUR_CURSEUR -   
                  @spPiecePuzzle[@index].bitmap.height / 2 
  end 
                 
  def deplacementPiece(i) 
    # Déplacement de la pièce 
    @tableauPuzzle[@index + i],@tableauPuzzle[@index] = @tableauPuzzle[@index], 
        @tableauPuzzle[@index + i] 
    afficherPuzzle 
    dessinerCurseur     
  end   
   
  def rotationPiece 
    # Rotation de la pièce 
    if (@tableauPuzzle[@index][1] == 3) 
      @tableauPuzzle[@index][1] = 0 
    else 
      @tableauPuzzle[@index][1] += 1 
    end 
    afficherPuzzle 
  end                 
   
  def puzzleFini? 
    # Vérifie si le puzzle est terminé 
    16.times do |i| 
      if (@tableauPuzzle[i][0] != i or @tableauPuzzle[i][1] != 0)             
        return false 
      end       
    end 
    return true 
  end   
end 

Au début du script il est possible de changer des constantes afin de mettre un background, changer la taille des pièces, positionner où on veut la grille et choisir l'épaisseur du curseur.
Pour des pièces qui font 108x108, si vous faites un curseur de 3 pixels dépaisseur, il devra faire la taille de 114x114 (108+2*3).
Si vous avez des questions sur l'utilisation de ces constantes n'hésitez pas à les poser.
Toutefois, les constantes seront le même peu importe le puzzle que vous souhaitez résoudre.

Il faut maintenant placer au bon endroit les ressources graphiques. Il va falloir créer un dossier par puzzle dans le dossier Pictures.
Pour le premier puzzle, le dossier devra s'appeler puzzle1, pour le deuxième puzzle2, etc. Dedans, vous devez mettre les pièces, nommé de cette façon : 01.png, 02.png, ..., 16.png.
Il vous faut aussi une image pour le curseur nommé curseur.png. Vous pouvez aussi ajouter une image pour le background.

Les pièces sont placées comme ceci :
01 02 03 04
05 06 07 08
09 10 11 12
13 14 15 16

Pour utiliser le script, c'est très simple.
Pour appeler le script, vous devez mettre en insérer de script :
Code:
$scene = Puzzle.new(id)

L'id correspond à l'id du puzzle que vous souhaitez faire que vous avez défini lorsque vous avez créé les dossiers des ressources graphiques.

Ensuite, vous devez tester si la variable $puzzleReussi est vraie ou non. Si elle est vraie, elle indique que le joueur a terminé le puzzle, sinon il n'a pas réussi.
Voici un événement type pour l'utilisation du script :



Bonne journée à tous. Imbécile heureux

EDIT : Le script a été mise à jour le 14/04/2014 à 21h00.

Posté par Howrus le 14 Juin - 18:03 (2014)
Génial tout ça! Merci de ton travail, ça m'aidera Imbécile heureux

Posté par Aye le 14 Juin - 19:34 (2014)
Est-ce qu'on peut faire plusieurs puzzle et si oui comment ^^?

Posté par Pαlвσlѕку le 14 Juin - 20:04 (2014)
J'ai totalement oublié cette possibilité donc je vais modifier le code pour que ça soit possible. Imbécile heureux

EDIT VISIBLE : Le script et le tuto ont été mis à jour pour supporter cette possibilité.

Posté par FinalArt le 14 Juin - 20:54 (2014)
Pas plus pratique un switch au lieu des if à la suite ? (pour l'input)

Sinon, c'est fun !

Posté par Ku'rei le 14 Juin - 21:10 (2014)
Beau travail !

Mais, ce ne serait pas plus pratique (et économique) de n'utiliser que que l'image du puzzle complète et la blitter sur des bitmap de la bonne taille ? Ca éviterai au maker de devoir découper les pièces lui-même, ça permettrait de faire des pièces à taille variables pour augmenter/diminuer la difficulté et le nombre de pièces.

Si tu passe le paramètre taille_piece dans l'initialize tu peut imaginer quelque chose comme ça
Code:
img_puzzle = RPG::Cache.picture("puzzle#{id}") # Obtention de l'image complète

#Vérification de la validité de la taille de l'image
float_taille_piece = taille_piece.to_f #Conversion
float_w = img_puzzle.width.to_f/taille_piece.to_f
float_h = img_puzzle.height.to_f/taille_piece.to_f
int_w = float_w.to_i
int_h = float_h.to_i
if (int_w.to_f != float_w) or (int_h.to_f != float_h)
  print("ERREUR : le puzzle numero #{id} n'a pas une taille conforme pour être découpé en pièce de #{taille_piece} de côté.")
end

#Découpage et stockage
@spPiecePuzzle = []
for x in 0...int_w
  for y in 0...int_h
    #Création de la pièce
    p = Sprite.new
    p.bitmap = Bitmap.new(taille_piece, taille_piece)
    p.blt(0,0, img_puzzle, Rect.new(x*taille_piece, y*taille_piece, taille_piece, taille_piece)

    #Positionnement et positionnement de l'origine
    p.ox = p.bitmap.width/2
    p.oy = p.bitmap.height/2
    p.x = x*taille_piece + (x-1)*EPAISSEUR_CURSEUR + POSITION_GRILLE_X + p.bitmap.width/2
    p.y = y*taille_piece + (y-1)*EPAISSEUR_CURSEUR + POSITION_GRILLE_Y + p.bitmap.height/2

    @spPiecePuzzle.push p
  end
end


La vérification doit pouvoir être optimisée, mais dans l'idée. Ca permettrait de faire un puzzle de taille variable.

EDIT::J'ai pas mis de quoi mélanger les pièces, ... ^^'

Posté par Pαlвσlѕку le 14 Juin - 21:50 (2014)
J'y ai pensé tout à l'heure quand j'ai mis à jour le script mais j'ai un peu la flemme de regarder ça ce soir. Imbécile heureux
Je mettrais à jour le script dans quelques jours sauf si quelqu'un veut s'en charger, j'ai ma dernière épreuve de BTS la semaine prochaine et ça va me prendre un peu de temps pour la préparer.

Le code ci-dessus me semble assez correct, je sais pas s'il fonctionne mais on devrait pouvoir s'en inspirer.

Posté par Ku'rei le 14 Juin - 21:56 (2014)
Aaah la saison des exam ^^ Bon courage !

Il devrait fonctionner, mais les pièces ne sont pas mélangées.

Posté par Nuri Yuri le 14 Juin - 21:56 (2014)
Merh par pitié arrêtez d'utiliser blt quand vous pouvez utiliser src_rect.
Tout le script je te le fais fonctionner avec trois bitmaps, le fond, le bitmap contenant toutes les pièces, le layer de texte.

Posté par Ku'rei le 14 Juin - 22:12 (2014)
Je suis curieux de savoir comment ^^ avec strech_blt ?

Posté par Girakoth le 14 Juin - 22:18 (2014)
Ah super, je vais pouvoir rajouter des puzzles au Ruines Alpha dans mon jeu, merci Gros matou qui ronronne

Posté par Nuri Yuri le 14 Juin - 22:30 (2014)
src_rect, la propriété de la classe Sprite qui te permet de dessiner à l'écran la partie que tu veux de son bitmap.
Après les zoom et tout ça tu utilise les autres propriétés du sprite.

Si tu veux plus de détails, il y a le manuel d'RPG Maker et le classe Sprite_Character qui utilise cette méthode.

Posté par Ku'rei le 15 Juin - 15:46 (2014)
D'ac je vois. Effectivement, ça permettrai d'avoir qu'un seul objet Bitmap en mémoire.