Hi, I’m porting my game to Android on Godot 4.3. I have a problem with the drawing section regarding exporting the drawing made on TileMapLayer as a png file on Android in the Drawing folder. I tested on Android and I press the cloudy button (the icon with the cloud to export the drawing in .png) but when I go to the Drawing folder in the Picture section, no .png image is created. I heard that you have to check the WRITE EXTERNAL STORAGE permission, which I checked, but it doesn’t export the drawing as a .png image. I only have this problem on Android. The drawings export perfectly as .png on Windows, MacOS and Linux.
Here is the long GDScript script of the scene to make drawings.
extends Node2D
# Références aux nœuds
@onready var tilemap_layer = $TileMapLayer
@onready var petite_brosse_button = $petitebrossebuttton
@onready var moyenne_brosse_button = $moyennebrossebutton
@onready var grosse_brosse_button = $grosbrossebutton
@onready var filly_button = $fillybutton
@onready var gummy_button = $gummybutton
@onready var bomby_button = $bombybutton
@onready var alazar_button = $alazarbutton # Bouton Undo
@onready var yakiti_button = $yakitibutton # Bouton Redo
@onready var floppy_button = $floppybutton # Bouton Sauvegarde
@onready var loady_button = $loadybutton # Bouton Chargement
@onready var cloudy_button = $cloudybutton # Bouton Export PNG
@onready var pinco_button = $pincobutton # Bouton couleur rouge vin
@onready var black_color_button = $blackcolorbutton # Bouton couleur noire
@onready var winered_color_button = $wineredcolorbutton
@onready var white_color_button = $whitecolorbutton
@onready var yellow_color_button = $yellowcolorbutton
@onready var lilas_color_button = $lilascolorbutton
@onready var orangeyellow_color_button = $orangeyellowcolorbutton
@onready var beige_color_button = $beigecolorbutton
@onready var darkblue_color_button = $darkbluecolorbutton
@onready var greyblue_color_button = $bluegreycolorbutton
@onready var riverblue_color_button = $riverbluecolorbutton
@onready var brown_color_button = $browncolorbutton
@onready var cyan_color_button = $cyancolorbutton
@onready var lightgrey_color_button = $lightgreycolorbutton
@onready var darkgrey_color_button = $darkgreycolorbutton
@onready var red_color_button = $redcolorbutton
@onready var green_color_button = $greencolorbutton
@onready var greenswamp_color_button = $greenswampcolorbutton
@onready var purple_color_button = $purplecolorbutton
@onready var pink_color_button = $pinkcolorbutton
@onready var floridapink_color_button = $floridapinkcolorbutton
@onready var orange_color_button = $orangecolorbutton
# Audio
@onready var petite_brosse_audio = $petitebrossebuttton/AudioStreamPlayer
@onready var moyenne_brosse_audio = $moyennebrossebutton/AudioStreamPlayer
@onready var grosse_brosse_audio = $grosbrossebutton/AudioStreamPlayer
@onready var filly_audio = $fillybutton/AudioStreamPlayer
@onready var gummy_audio = $gummybutton/AudioStreamPlayer
@onready var bomby_audio = $bombybutton/AudioStreamPlayer
@onready var alazar_audio = $alazarbutton/AudioStreamPlayer
@onready var yakiti_audio = $yakitibutton/AudioStreamPlayer
@onready var zemmourtousse_audio = $AudioStreamPlayerZemmourtousse
@onready var floppy_audio = $floppybutton/AudioStreamPlayer # Audio pour Sauvegarde
@onready var loady_audio = $loadybutton/AudioStreamPlayer # Audio pour Chargement
@onready var cloudy_audio = $cloudybutton/AudioStreamPlayer # Audio pour Export PNG
@onready var pinco_audio = $pincobutton/AudioStreamPlayer # Audio pour bouton rouge vin
# Icônes pour le bouton de remplissage
@onready var filly_icon = preload("res://icone/fillyicon.png")
@onready var filly_icon_deux = preload("res://icone/fillyicondeux.png")
# Variables pour le dessin
var current_brush_size = 1 # 1 = petite, 2 = moyenne, 3 = grosse
var last_tile: Vector2i = Vector2i(-1, -1)
var is_drawing = false
var is_filling = false # Mode remplissage activé/désactivé
var is_erasing = false # Mode gomme activé/désactivé
var loading_in_progress = false # Pour suivre si un chargement est en cours
# Variables pour la gestion des couleurs
var current_color_source_id = 0 # ID de la source (0 pour rouge vin par défaut)
var current_color_name = "default" # Nom dans l'atlas (default pour rouge vin)
# Historique pour Undo/Redo
var action_history = [] # Pour stocker l'historique des actions
var redo_history = [] # Pour stocker les actions annulées
var current_action = {} # Pour stocker l'action en cours
var is_action_recording = false # Pour savoir si on enregistre une action
# État sauvegardé
var saved_state = {} # Pour stocker l'état sauvegardé du dessin
# Limites de la zone de dessin
var min_bounds: Vector2i = Vector2i(-25, -16)
var max_bounds: Vector2i = Vector2i(24, 14)
func _ready():
# Connecter les boutons s'ils ne sont pas encore connectés
if not petite_brosse_button.pressed.is_connected(_on_petite_brosse_pressed):
petite_brosse_button.pressed.connect(_on_petite_brosse_pressed)
if not moyenne_brosse_button.pressed.is_connected(_on_moyenne_brosse_pressed):
moyenne_brosse_button.pressed.connect(_on_moyenne_brosse_pressed)
if not grosse_brosse_button.pressed.is_connected(_on_grosse_brosse_pressed):
grosse_brosse_button.pressed.connect(_on_grosse_brosse_pressed)
if not filly_button.pressed.is_connected(_on_filly_button_pressed):
filly_button.pressed.connect(_on_filly_button_pressed)
if not gummy_button.pressed.is_connected(_on_gummy_button_pressed):
gummy_button.pressed.connect(_on_gummy_button_pressed)
if not bomby_button.pressed.is_connected(_on_bomby_button_pressed):
bomby_button.pressed.connect(_on_bomby_button_pressed)
if not alazar_button.pressed.is_connected(_on_alazar_button_pressed):
alazar_button.pressed.connect(_on_alazar_button_pressed)
if not yakiti_button.pressed.is_connected(_on_yakiti_button_pressed):
yakiti_button.pressed.connect(_on_yakiti_button_pressed)
if not floppy_button.pressed.is_connected(_on_floppy_button_pressed):
floppy_button.pressed.connect(_on_floppy_button_pressed)
if not loady_button.pressed.is_connected(_on_loady_button_pressed):
loady_button.pressed.connect(_on_loady_button_pressed)
if not cloudy_button.pressed.is_connected(_on_cloudy_button_pressed):
cloudy_button.pressed.connect(_on_cloudy_button_pressed)
if not pinco_button.pressed.is_connected(_on_pinco_button_pressed):
pinco_button.pressed.connect(_on_pinco_button_pressed)
if not black_color_button.pressed.is_connected(_on_black_color_button_pressed):
black_color_button.pressed.connect(_on_black_color_button_pressed)
if not winered_color_button.pressed.is_connected(_on_winered_colorbutton_pressed):
winered_color_button.pressed.connect(_on_winered_colorbutton_pressed)
if not white_color_button.pressed.is_connected(_on_white_color_button_pressed):
white_color_button.pressed.connect(_on_white_color_button_pressed)
if not yellow_color_button.pressed.is_connected(_on_yellow_color_button_pressed):
yellow_color_button.pressed.connect(_on_yellow_color_button_pressed)
if not lilas_color_button.pressed.is_connected(_on_lilas_color_button_pressed):
lilas_color_button.pressed.connect(_on_lilas_color_button_pressed)
if not orangeyellow_color_button.pressed.is_connected(_on_orangeyellow_color_button_pressed):
orangeyellow_color_button.pressed.connect(_on_orangeyellow_color_button_pressed)
if not beige_color_button.pressed.is_connected(_on_beige_color_button_pressed):
beige_color_button.pressed.connect(_on_beige_color_button_pressed)
if not darkblue_color_button.pressed.is_connected(_on_darkblue_color_button_pressed):
darkblue_color_button.pressed.connect(_on_darkblue_color_button_pressed)
if not greyblue_color_button.pressed.is_connected(_on_greyblue_color_button_pressed):
greyblue_color_button.pressed.connect(_on_greyblue_color_button_pressed)
if not riverblue_color_button.pressed.is_connected(_on_riverblue_color_button_pressed):
riverblue_color_button.pressed.connect(_on_riverblue_color_button_pressed)
if not brown_color_button.pressed.is_connected(_on_brown_color_button_pressed):
brown_color_button.pressed.connect(_on_brown_color_button_pressed)
if not cyan_color_button.pressed.is_connected(_on_cyan_color_button_pressed):
cyan_color_button.pressed.connect(_on_cyan_color_button_pressed)
if not lightgrey_color_button.pressed.is_connected(_on_lightgrey_color_button_pressed):
lightgrey_color_button.pressed.connect(_on_lightgrey_color_button_pressed)
if not darkgrey_color_button.pressed.is_connected(_on_darkgrey_color_button_pressed):
darkgrey_color_button.pressed.connect(_on_darkgrey_color_button_pressed)
if not red_color_button.pressed.is_connected(_on_red_color_button_pressed):
red_color_button.pressed.connect(_on_red_color_button_pressed)
if not green_color_button.pressed.is_connected(_on_green_color_button_pressed):
green_color_button.pressed.connect(_on_green_color_button_pressed)
if not greenswamp_color_button.pressed.is_connected(_on_greenswamp_color_button_pressed):
greenswamp_color_button.pressed.connect(_on_greenswamp_color_button_pressed)
if not purple_color_button.pressed.is_connected(_on_purple_color_button_pressed):
purple_color_button.pressed.connect(_on_purple_color_button_pressed)
if not pink_color_button.pressed.is_connected(_on_pink_color_button_pressed):
pink_color_button.pressed.connect(_on_pink_color_button_pressed)
if not floridapink_color_button.pressed.is_connected(_on_floridapink_color_button_pressed):
floridapink_color_button.pressed.connect(_on_floridapink_color_button_pressed)
if not orange_color_button.pressed.is_connected(_on_orange_color_button_pressed):
orange_color_button.pressed.connect(_on_orange_color_button_pressed)
# Connecter le signal de fin d'audio pour le chargement
if not loady_audio.finished.is_connected(_on_loady_audio_finished):
loady_audio.finished.connect(_on_loady_audio_finished)
func _input(event):
# Gestion du raccourci Ctrl+Z pour Undo
if event is InputEventKey:
if event.keycode == KEY_Z and event.pressed and event.ctrl_pressed:
undo_last_action_with_zemmourtousse()
return
if event is InputEventMouse:
var mouse_pos = tilemap_layer.to_local(event.position)
var tile_pos = tilemap_layer.local_to_map(mouse_pos)
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
if is_within_bounds(tile_pos):
if is_filling:
# Capture l'état avant le remplissage
start_action("fill")
capture_tilemap_state()
flood_fill(tile_pos)
end_action()
else:
is_drawing = true
last_tile = tile_pos
# Débuter une nouvelle action de dessin
start_action("draw")
draw_brush(tile_pos)
else:
if is_drawing:
is_drawing = false
last_tile = Vector2i(-1, -1)
# Terminer l'action de dessin
end_action()
elif event is InputEventMouseMotion and is_drawing and not is_filling:
if tile_pos != last_tile and is_within_bounds(tile_pos):
if last_tile != Vector2i(-1, -1):
draw_line_on_tilemap(last_tile, tile_pos)
last_tile = tile_pos
# Système d'historique
func start_action(action_type: String):
is_action_recording = true
current_action = {
"type": action_type,
"before": {},
"after": {}
}
capture_tilemap_state()
func capture_tilemap_state():
# Capture l'état actuel de la tilemap
current_action["before"] = get_tilemap_state()
func end_action():
if is_action_recording:
is_action_recording = false
current_action["after"] = get_tilemap_state()
action_history.append(current_action.duplicate())
# Vider l'historique redo quand une nouvelle action est effectuée
redo_history.clear()
current_action = {}
func get_tilemap_state() -> Dictionary:
var state = {}
for x in range(min_bounds.x, max_bounds.x + 1):
for y in range(min_bounds.y, max_bounds.y + 1):
var pos = Vector2i(x, y)
var atlas_coords = tilemap_layer.get_cell_atlas_coords(pos)
var source_id = tilemap_layer.get_cell_source_id(pos)
if source_id != -1: # Si la cellule n'est pas vide
state[str(pos.x) + "," + str(pos.y)] = {
"source_id": source_id,
"atlas_coords": atlas_coords
}
return state
func apply_tilemap_state(state: Dictionary):
# Effacer la tilemap d'abord
for x in range(min_bounds.x, max_bounds.x + 1):
for y in range(min_bounds.y, max_bounds.y + 1):
tilemap_layer.erase_cell(Vector2i(x, y))
# Appliquer l'état sauvegardé
for pos_key in state:
var pos_parts = pos_key.split(",")
var pos = Vector2i(int(pos_parts[0]), int(pos_parts[1]))
var cell_data = state[pos_key]
tilemap_layer.set_cell(pos, cell_data["source_id"], cell_data["atlas_coords"])
# Boutons Undo/Redo
func _on_alazar_button_pressed():
cancel_loading_if_in_progress()
undo_last_action()
stop_all_audio()
alazar_audio.play()
func _on_yakiti_button_pressed():
cancel_loading_if_in_progress()
redo_last_action()
stop_all_audio()
yakiti_audio.play()
func undo_last_action():
if action_history.size() > 0:
var last_action = action_history.pop_back()
redo_history.append(last_action)
apply_tilemap_state(last_action["before"])
# Fonction spécifique pour Ctrl+Z qui joue un son différent
func undo_last_action_with_zemmourtousse():
cancel_loading_if_in_progress()
stop_all_audio()
zemmourtousse_audio.play()
# Effectuer l'annulation seulement s'il y a des actions à annuler
if action_history.size() > 0:
undo_last_action()
func redo_last_action():
if redo_history.size() > 0:
var next_action = redo_history.pop_back()
action_history.append(next_action)
apply_tilemap_state(next_action["after"])
# Fonctions de sauvegarde et chargement
func _on_floppy_button_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
floppy_audio.play()
save_drawing()
func _on_loady_button_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
loady_audio.play()
loading_in_progress = true
func _on_loady_audio_finished():
if loading_in_progress:
load_drawing()
loading_in_progress = false
func save_drawing():
var save_data = {
"tilemap_state": get_tilemap_state(),
"action_history": export_action_history(),
"redo_history": export_redo_history(),
"version": "1.0"
}
var save_file = FileAccess.open("user://drawing_save.dat", FileAccess.WRITE)
if save_file:
save_file.store_var(save_data)
print("Dessin + historique complet sauvegardé")
else:
print("Erreur lors de la sauvegarde.")
func load_drawing():
var save_file = FileAccess.open("user://drawing_save.dat", FileAccess.READ)
if save_file:
var save_data = save_file.get_var()
if save_data is Dictionary and save_data.has("tilemap_state"):
apply_tilemap_state(save_data["tilemap_state"])
saved_state = save_data["tilemap_state"]
# Restaurer les historiques s’ils existent
if save_data.has("action_history"):
action_history = save_data["action_history"]
else:
action_history.clear()
if save_data.has("redo_history"):
redo_history = save_data["redo_history"]
else:
redo_history.clear()
print("Dessin + historique Undo/Redo chargé")
func export_action_history() -> Array:
var exported = []
for action in action_history:
exported.append({
"type": action["type"],
"before": action["before"],
"after": action["after"]
})
return exported
func export_redo_history() -> Array:
var exported = []
for action in redo_history:
exported.append({
"type": action["type"],
"before": action["before"],
"after": action["after"]
})
return exported
func cancel_loading_if_in_progress():
if loading_in_progress:
loading_in_progress = false
# Le son sera arrêté par stop_all_audio() dans la fonction qui appelle celle-ci
# Boutons brosse
func _on_petite_brosse_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
petite_brosse_audio.play()
current_brush_size = 1
is_filling = false
is_erasing = false
filly_button.icon = filly_icon
update_button_visuals()
func _on_moyenne_brosse_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
moyenne_brosse_audio.play()
current_brush_size = 2
is_filling = false
is_erasing = false
filly_button.icon = filly_icon
update_button_visuals()
func _on_grosse_brosse_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
grosse_brosse_audio.play()
current_brush_size = 3
is_filling = false
is_erasing = false
filly_button.icon = filly_icon
update_button_visuals()
# Bouton remplissage
func _on_filly_button_pressed():
cancel_loading_if_in_progress()
is_filling = not is_filling
is_erasing = false
if is_filling:
filly_button.icon = filly_icon_deux
stop_all_audio()
filly_audio.play()
else:
filly_button.icon = filly_icon
filly_audio.stop()
# Bouton gomme
func _on_gummy_button_pressed():
cancel_loading_if_in_progress()
is_erasing = true
is_filling = false
stop_all_audio()
gummy_audio.play()
filly_button.icon = filly_icon
update_button_visuals()
# Bouton d'effacement complet
func _on_bomby_button_pressed():
cancel_loading_if_in_progress()
# Capture l'état avant l'effacement
start_action("clear")
clear_entire_drawing()
end_action()
action_history.clear()
redo_history.clear()
stop_all_audio()
bomby_audio.play()
# Boutons de couleur
func _on_pinco_button_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
pinco_audio.play()
current_color_source_id = 0
current_color_name = "default"
func _on_black_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 1
current_color_name = "black"
func _on_winered_colorbutton_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 0
current_color_name = "default"
func _on_white_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 2
current_color_name = "white"
func _on_yellow_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 3
current_color_name = "yellow"
func _on_lilas_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 4
current_color_name = "lilas"
func _on_orangeyellow_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 5
current_color_name = "orangeyellow"
func _on_beige_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 6
current_color_name = "beige"
func _on_darkblue_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 7
current_color_name = "darkblue"
func _on_greyblue_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 8
current_color_name = "greyblue"
func _on_riverblue_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 9
current_color_name = "riverblue"
func _on_brown_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 10
current_color_name = "brown"
func _on_cyan_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 11
current_color_name = "cyan"
func _on_lightgrey_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 12
current_color_name = "lightgrey"
func _on_darkgrey_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 13
current_color_name = "darkgrey"
func _on_red_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 14
current_color_name = "red"
func _on_green_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 15
current_color_name = "green"
func _on_greenswamp_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 16
current_color_name = "swampgreen"
func _on_purple_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 17
current_color_name = "purple"
func _on_pink_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 18
current_color_name = "pink"
func _on_floridapink_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 19
current_color_name = "floridapink"
func _on_orange_color_button_pressed():
cancel_loading_if_in_progress()
current_color_source_id = 20
current_color_name = "orange"
# Bouton d'export PNG
func _on_cloudy_button_pressed():
cancel_loading_if_in_progress()
stop_all_audio()
cloudy_audio.play()
export_drawing_to_png()
func export_drawing_to_png():
var tile_size = tilemap_layer.tile_set.tile_size
var tilemap_size = (max_bounds - min_bounds + Vector2i.ONE) * tile_size
# Créer le viewport pour rendre le TileMap
var viewport := SubViewport.new()
viewport.size = tilemap_size
viewport.render_target_clear_mode = SubViewport.CLEAR_MODE_ALWAYS
viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
viewport.transparent_bg = false
viewport.disable_3d = true
# Copier le TileMap dans un conteneur temporaire
var container := Node2D.new()
var tilemap_copy := tilemap_layer.duplicate()
tilemap_copy.position = -min_bounds * tile_size # Centrer à l'origine
container.add_child(tilemap_copy)
viewport.add_child(container)
# Ajouter le viewport à la scène pour permettre le rendu
add_child(viewport)
# Attendre quelques frames pour s'assurer que le rendu est complet
await get_tree().process_frame
await get_tree().idle_frame()
# Récupérer l'image
var image = viewport.get_texture().get_image()
# Nettoyage
remove_child(viewport)
viewport.queue_free()
# Construire le chemin d'enregistrement dans le dossier Pictures
var pictures_dir := OS.get_system_dir(OS.SYSTEM_DIR_PICTURES)
var drawing_dir := pictures_dir.path_join("Drawing")
if not DirAccess.dir_exists_absolute(drawing_dir):
DirAccess.make_dir_recursive_absolute(drawing_dir)
# Créer un nom de fichier sans caractères invalides
var timestamp := Time.get_datetime_string_from_system().replace(":", "-")
var filename := "export_tilemap_" + timestamp + ".png"
var file_path := drawing_dir.path_join(filename)
# Sauvegarder l'image au format PNG
var err = image.save_png(file_path)
if err == OK:
print("✅ Export PNG réussi :", file_path)
else:
print("❌ Erreur pendant l'export PNG :", err)
func stop_all_audio():
petite_brosse_audio.stop()
moyenne_brosse_audio.stop()
grosse_brosse_audio.stop()
filly_audio.stop()
gummy_audio.stop()
bomby_audio.stop()
alazar_audio.stop()
yakiti_audio.stop()
zemmourtousse_audio.stop()
floppy_audio.stop()
loady_audio.stop()
cloudy_audio.stop()
pinco_audio.stop()
func update_button_visuals():
# Exemple visuel : teinte jaune sur le bouton sélectionné
petite_brosse_button.modulate = Color(1, 1, 1)
moyenne_brosse_button.modulate = Color(1, 1, 1)
grosse_brosse_button.modulate = Color(1, 1, 1)
gummy_button.modulate = Color(1, 1, 1)
pinco_button.modulate = Color(1, 1, 1)
black_color_button.modulate = Color(1, 1, 1)
# Mettre en surbrillance le bouton de couleur actuel
if current_color_source_id == 0:
pinco_button.modulate = Color(1, 0.8, 0.8) # Teinte rouge clair
elif current_color_source_id == 1:
black_color_button.modulate = Color(0.8, 0.8, 1) # Teinte bleue claire
if is_erasing:
match current_brush_size:
1: petite_brosse_button.modulate = Color(1, 1, 0)
2: moyenne_brosse_button.modulate = Color(1, 1, 0)
3: grosse_brosse_button.modulate = Color(1, 1, 0)
# Efface entièrement le dessin
func clear_entire_drawing():
for x in range(min_bounds.x, max_bounds.x + 1):
for y in range(min_bounds.y, max_bounds.y + 1):
tilemap_layer.erase_cell(Vector2i(x, y))
# Dessin
func draw_brush(center_pos: Vector2i):
var tile_source = current_color_source_id
var atlas_coords = Vector2i(0, 0)
# Si en mode gomme, on efface les cellules
if is_erasing:
match current_brush_size:
1:
tilemap_layer.erase_cell(center_pos)
2:
for y in range(-1, 2):
for x in range(-1, 2):
var pos = center_pos + Vector2i(x, y)
if is_within_bounds(pos):
tilemap_layer.erase_cell(pos)
3:
for y in range(-2, 3):
for x in range(-2, 3):
var pos = center_pos + Vector2i(x, y)
if is_within_bounds(pos):
tilemap_layer.erase_cell(pos)
else:
# Mode dessin normal
match current_brush_size:
1:
tilemap_layer.set_cell(center_pos, tile_source, atlas_coords)
2:
for y in range(-1, 2):
for x in range(-1, 2):
var pos = center_pos + Vector2i(x, y)
if is_within_bounds(pos):
tilemap_layer.set_cell(pos, tile_source, atlas_coords)
3:
for y in range(-2, 3):
for x in range(-2, 3):
var pos = center_pos + Vector2i(x, y)
if is_within_bounds(pos):
tilemap_layer.set_cell(pos, tile_source, atlas_coords)
func draw_line_on_tilemap(start: Vector2i, end: Vector2i):
var points = get_line_points(start, end)
for point in points:
draw_brush(point)
func get_line_points(start: Vector2i, end: Vector2i) -> Array:
var points = []
var dx = abs(end.x - start.x)
var dy = abs(end.y - start.y)
var sx = 1 if start.x < end.x else -1
var sy = 1 if start.y < end.y else -1
var err = dx - dy
var x = start.x
var y = start.y
while true:
points.append(Vector2i(x, y))
if x == end.x and y == end.y:
break
var e2 = 2 * err
if e2 > -dy:
err -= dy
x += sx
if e2 < dx:
err += dx
y += sy
return points
# Flood fill
func flood_fill(start_pos: Vector2i):
var tile_source = current_color_source_id
var atlas_coords = Vector2i(0, 0)
var original_source_id = tilemap_layer.get_cell_source_id(start_pos)
var original_atlas_coords = tilemap_layer.get_cell_atlas_coords(start_pos)
# Ne pas remplir si déjà la même couleur
if original_source_id == tile_source && original_atlas_coords == atlas_coords:
return
var queue = [start_pos]
var visited = {}
while queue.size() > 0:
var pos = queue.pop_front()
if not is_within_bounds(pos):
continue
if visited.has(pos):
continue
visited[pos] = true
var cell_source_id = tilemap_layer.get_cell_source_id(pos)
var cell_atlas_coords = tilemap_layer.get_cell_atlas_coords(pos)
if cell_source_id != original_source_id || cell_atlas_coords != original_atlas_coords:
continue
tilemap_layer.set_cell(pos, tile_source, atlas_coords)
queue.append(pos + Vector2i(1, 0))
queue.append(pos + Vector2i(-1, 0))
queue.append(pos + Vector2i(0, 1))
queue.append(pos + Vector2i(0, -1))
# Limites
func is_within_bounds(pos: Vector2i) -> bool:
return pos.x >= min_bounds.x and pos.x <= max_bounds.x and pos.y >= min_bounds.y and pos.y <= max_bounds.y
func set_bounds(min_pos: Vector2i, max_pos: Vector2i):
min_bounds = min_pos
max_bounds = max_pos