The core logic and functionality you need to build engaging match-3 games
·
Report Bug
·
Request Features
- 📦 Installation
- Getting started 🚀
- Editor preview 🪲
- Board Parameters
- Add spawn pieces to the board
- GridCellUI
- Download Latest Release
- Unpack the
ninetailsrabbit.match3_board
folder into your/addons
folder within the Godot project - Enable this addon within the Godot settings:
Project > Project Settings > Plugins
To better understand what branch to choose from for which Godot version, please refer to this table:
Godot Version | match3-board Branch | match3-board Version |
---|---|---|
main |
1.x |
To start creating your Match3 game you have available 5 pillars provided from this plugin to build it:
- 🔳 The
Match3Board
node handles the core game mechanics, including grid size, piece setup, and swap movement mode. - 💎 The
PieceUI
node provides the visual representation and interactive behavior of game pieces. You can customize it or create your own variations. - 📝 The
PieceConfiguration
defines the properties and behaviors of each piece type, such as its match logic and special abilities. - 🎲 The
PieceWeight
defines a weight value and a piece scene to be used for weight selection when drawing new pieces on the board. You can link any scene where the root node isPieceUI
. - ⛓ The
LineConnector
which allows parts to be connected on the basis of a Line2D whenLineConnected
swap mode is selected on the board configuration.
This feature provides a preview of your game board before it's fully initialized. This allows you to:
- Assess Layout: Visualize the size and layout of your board.
- Add Empty Spaces: Easily adjust the spacing between game pieces.
- Experiment with Textures: Test different textures for cells and pieces without affecting the final game.
You have available default textures on this plugin to visualize a preview in the editor, feel free to use your own ones. The nodes that are drawn are not the scenes that the board uses in the game, they are just sprites.
- Preview grid in editor: Enable or disable the preview
- Clean current preview: Removes the current preview from the editor
- Use placeholder textures: Use PlaceholderTexture2D as default texture to draw grids on the preview editor
- Placeholder textures colors: Define colors for the placeholder textures to have a better visualization.
- Preview pieces: The textures to preview in the grid randomly placed on each generation
- Odd cell texture: The texture to place in odd cell positions in the board
- Even cell texture: The texture to place in even cell positions in the board
- Empty cells: Set empty cells where a piece shall not be drawn in the shape of Vector2i(row, column)
- Grid width: The row width of the grid, a width of 7 means that 7 pieces will be drawed by row
- Grid height: The column height of the grid, a height o 8 means that 8 pieces will be drawed by column
- Cell size: The size of the cell in the world in the shape of Vector2(x, y). It's recommended to keep this values equals for better display
- Cell offset: The offset from the corner if you want to create more space.
ℹ️ With the preview enabled
, any changes you make to the board's parameters _(like grid size or piece definitions)_ will be instantly reflected in the preview window. This allows you to quickly iterate and experiment with different configurations ℹ️
When enabled, the board prepare itself automatically when it's ready on the scene tree. If you disable it, you need to manually call the function prepare_board()
on your script.
The collision layer where the pieces are set. Its values are from 1 to 32
and this allows you to not override existing layers in your Godot project
This is the swap_mode
to find matches on the pieces. They can be changed at runtime and the board emit the signal changed_swap_mode(previous, new)
when this value it's changed
You have the next ones availables:
An individual piece can swap with adjacent pieces but diagonal neighbours are not included.
Same as adjacent mode but this time diagonal neighbours are included
Same as adjacent mode but this time only diagonal neighbours are included
The piece can be swapped with any other
The piece can be swapped in the entire row it belongs
The piece can be swapped in the entire column it belongs
The piece can be swapped on a cross that is a mix of Row & Column
The piece can be swapped on cross but using diagonals instead
Connect the adjacent pieces from a line that originates from the selected cell until the maximum match of pieces or triggered manually.
This is the click_mode
to manipulate pieces in the board with the current swap mode. They can be changed at runtime and the board emit the signal changed_click_mode(previous, new)
when this value it's changed
You have available the following ones:
- Selection: You click on the piece to select and another click in the target piece to swap.
- Drag: You hold the mouse and drag the piece into the target piece to swap. When the
swap_mode
isConnectLine
the piece is not dragged but instead you need to hold the mouse to keep connecting lines.
When you're using the click_mode
Selection and the swap_mode
ConnectLine, there is two input actions you can define to consume
or cancel
the line connector matches.
consume
is triggered with MOUSE_BUTTON_LEFT
and the cancel
with MOUSE_BUTTON_RIGHT
This option defines the behaviour when the board is filling the empty gaps after consuming previous sequences of pieces.
You have available the following ones:
- Fall down: The pieces fall down into the deepest empty column cell as they are drawn into the board
- Side: Same behaviour as Fall down but if there is no empty space in the column and there is an empty space in the adjacent diagonal, the piece will move there.
- In place: The pieces spawn in the same cell without gravity effects
This is an array of PieceWeight
that will use the board to draw new pieces. A weight-based decision algorithm is used, i.e. the higher the value of the weight, the more likely it is to come out.
There is more extensive documentation in section How to create new pieces that explains in detail how to create the pieces scenes and add them to the board.
Note that the pieces you add here will be drawn on the board, i.e. the ones that can spawn when the board is in fill mode, this can be done as long as the is_disabled
property is set to false
Drawing special pieces is the responsibility of the SequenceConsumer
but nothing prevents you from placing them here and being part of the normal flow of the board.
class_name PieceWeight extends Resource
@export var weight: float = 1.0
@export var piece_scene: PackedScene
var is_disabled: bool = false
var current_weight: float = weight
var total_accum_weight: float = 0.0
func reset_accum_weight() -> void:
total_accum_weight = 0.0
func change_weight(new_value: float) -> void:
current_weight = new_value
func change_to_original_weight() -> void:
current_weight = weight
func enable() -> void:
is_disabled = false
func disable() -> void:
is_disabled = true
This parameter acts as information that can be accessed on the variable current_available_moves
about the moves this board can make. Each time the board returns to the initial state after consuming sequences, current_available_moves -= 1
it's triggered.
- When a move it's consumed the board emits the signal
movement_consumed
- When no remaining moves are available the board emits the signal
finished_available_movements
Each square in the preview represents a GridCellUI
node. This node serves as a visual representation of a cell within the game grid and is not designed for customization (although nothing prevents you from doing so)
The Swaps between pieces are done via this GridCellUI
node so the pieces are never directly interacted with, it is the cells that contain the checks and commands to see if the swap is possible or not.
To obtain the position of a cell you have access to variables row
, column
and method board_position()
. This method returns a Vector2i(row, column)
.
In addition, there are methods to check for more advanced cases with other cells to know the relative position in the board
func in_same_row_as(other_cell: GridCellUI) -> bool
func in_same_column_as(other_cell: GridCellUI) -> bool
func in_same_position_as(other_cell: GridCellUI) -> bool
func in_same_grid_position_as(grid_position: Vector2) -> bool
func is_row_neighbour_of(other_cell: GridCellUI) -> bool
func is_column_neighbour_of(other_cell: GridCellUI) -> bool
func is_adjacent_to(other_cell: GridCellUI) -> bool
func in_diagonal_with(other_cell: GridCellUI) -> bool:
There are more methods available to determine if it is a corner or a border
func is_top_left_corner() -> bool:
func is_top_right_corner() -> bool
func is_bottom_left_corner() -> bool
func is_bottom_right_corner() -> bool
func is_top_border() -> bool
func is_bottom_border() -> bool
func is_right_border() -> bool
func is_left_border() -> bool:
If the position of this cell match with any of the empty_cells
vectors from the Match3Board
, this cell will change the variable can_contain_piece
to false and you can choose if draw the background texture or not with the variable draw_background_texture
.
When the board is drawed and cells initialized, each one has access to the adjacent neighbours, you can access them with the variables:
neighbour_up,
neighbour_bottom,
neighbour_right,
neighbour_left,
diagonal_neighbour_top_right,
diagonal_neighbour_top_left,
diagonal_neighbour_bottom_right,
diagonal_neighbour_bottom_left
In addition you can access the neighbour cells that are available through the methods:
func available_neighbours(include_diagonals: bool = false) -> Array[GridCellUI]
func diagonal_neighbours() -> Array[GridCellUI]:
The cell has direct access to the related piece on variable current_piece
, this GridCellUI
class provides useful methods to manipulate them:
Available methods to know if the cell has a piece or not
func is_empty() -> bool
func has_piece() -> bool
Methods to assign or remove a piece. This methods does not remove the piece from the board UI, only from the cell.
// Assign a piece only if the cell is empty unless "overwrite" is true
func assign_piece(new_piece: PieceUI, overwrite: bool = false) -> void
// Replace the current piece to the new one even if the cell is empty
func replace_piece(new_piece: PieceUI) -> PieceUI
// Remove the current piece and return it if the cell was not empty.
func remove_piece() -> PieceUI?
All the swap are dones through the cells, although it is not recommended to use these methods directly. Rather, set up the pieces and their rules individually and consume them with a SequenceConsumer
.
func swap_piece_with(other_cell: GridCellUI) -> bool
func can_swap_piece_with(other_cell: GridCellUI) -> bool