├── icon.png ├── addons └── fabianlc_gpu_tilemap │ ├── shaders │ ├── tile_flip_map.png │ ├── icon.png.import │ ├── tile_flip_map.png.import │ └── tilemap_renderer.shader │ ├── autotile scripts │ ├── autotile_id_reference.png │ ├── autotile_id_reference.png.import │ ├── autotile_script.gd │ └── default_autotile.gd │ ├── plugin.cfg │ ├── scenes │ ├── clear_map_dialog.tscn │ ├── alignment_grid.gd │ ├── new_map_dialog.tscn │ ├── tlepicker_selected_cell.gd │ ├── tilepicker.tscn │ ├── import_tiled_map_dialog.tscn │ ├── import_tiled_map_dialog.gd │ ├── resize_map_dialog.tscn │ └── tilepicker.gd │ ├── gpu_tilemap.gd │ └── plugin.gd ├── LICENSE └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MightyPrinny/godot-gputilemap/HEAD/icon.png -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/shaders/tile_flip_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MightyPrinny/godot-gputilemap/HEAD/addons/fabianlc_gpu_tilemap/shaders/tile_flip_map.png -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/autotile scripts/autotile_id_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MightyPrinny/godot-gputilemap/HEAD/addons/fabianlc_gpu_tilemap/autotile scripts/autotile_id_reference.png -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="GPU Tilemap" 4 | description="Simpler tilemap that is drawn with a shader." 5 | author="Fabián L.C." 6 | version="0.9.6" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/clear_map_dialog.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="Control" type="ConfirmationDialog"] 4 | anchor_left = 0.5 5 | anchor_top = 0.5 6 | anchor_right = 0.5 7 | anchor_bottom = 0.5 8 | margin_left = -128.0 9 | margin_top = -55.5 10 | margin_right = 128.0 11 | margin_bottom = 55.5 12 | rect_min_size = Vector2( 170, 59.5 ) 13 | popup_exclusive = true 14 | window_title = "Clear Map" 15 | dialog_text = "This operation cannot be undone. 16 | Continue?" 17 | __meta__ = { 18 | "_edit_use_anchors_": false 19 | } 20 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/alignment_grid.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends GridContainer 3 | 4 | var buttons 5 | 6 | # Called when the node enters the scene tree for the first time. 7 | func _ready(): 8 | buttons = get_children() 9 | assert(buttons.size() == 9) 10 | for b in buttons: 11 | var a = b as Button 12 | b.connect("pressed",self,"button_pressed",[b]) 13 | 14 | func button_pressed(b): 15 | b.pressed = true 16 | for _b in buttons: 17 | if _b != b: 18 | _b.pressed = false 19 | 20 | func get_alignment(): 21 | var alinment = Vector2(0,0) 22 | var i = 0 23 | for b in buttons: 24 | if b.pressed: 25 | break 26 | i += 1 27 | alinment.x = i%3 28 | alinment.y = i/3 29 | 30 | return alinment 31 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/shaders/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-9888f8f078301d147a63684ee2917549.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/fabianlc_gpu_tilemap/shaders/icon.png" 13 | dest_files=[ "res://.import/icon.png-9888f8f078301d147a63684ee2917549.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/shaders/tile_flip_map.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/tile_flip_map.png-14c7e5b38fbbd3c0e30fcdb9cb6e5b01.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/fabianlc_gpu_tilemap/shaders/tile_flip_map.png" 13 | dest_files=[ "res://.import/tile_flip_map.png-14c7e5b38fbbd3c0e30fcdb9cb6e5b01.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=3 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/autotile scripts/autotile_id_reference.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/autotile_id_reference.png-53aae12f9ed322143bc39096fe31615b.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://addons/fabianlc_gpu_tilemap/autotile scripts/autotile_id_reference.png" 13 | dest_files=[ "res://.import/autotile_id_reference.png-53aae12f9ed322143bc39096fe31615b.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fabián L.C. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/shaders/tilemap_renderer.shader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | const highp vec2 flipMapSizeInv = vec2(0.25,0.5); 4 | const highp vec2 flipOffset = vec2(-1,-1); 5 | 6 | uniform highp vec2 viewportSize = vec2(256,240); 7 | uniform highp vec2 inverseTileTextureSize = vec2(0.0078125,0.0078125); 8 | uniform highp vec2 inverseTileSize = vec2(0.0625,0.0625); 9 | 10 | uniform highp sampler2D tileset; 11 | uniform highp sampler2D tilemap; 12 | uniform highp sampler2D flipMap; 13 | 14 | uniform highp vec2 inverseSpriteTextureSize = vec2(0.0232558139535,0.0294117647059); 15 | uniform highp vec2 tileSize = vec2(16,16); 16 | 17 | void fragment() 18 | { 19 | highp vec2 pixelCoord = (UV * viewportSize); 20 | highp vec2 texCoord = pixelCoord * inverseSpriteTextureSize * inverseTileSize; 21 | highp vec4 tile = texture(tilemap, texCoord); 22 | highp vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize; 23 | 24 | highp vec2 spriteCoord = mod(pixelCoord, tileSize); 25 | 26 | if(tile.b != 0.0) 27 | { 28 | highp float flip_id = tile.b*256.0; 29 | spriteCoord = spriteCoord*(texture(flipMap,vec2(flip_id,1.0)*flipMapSizeInv).rg*256.0 + flipOffset) + tileSize*(texture(flipMap,vec2(flip_id,0.0)*flipMapSizeInv).rg*256.0); 30 | } 31 | COLOR = texture(tileset, (spriteOffset + spriteCoord) * inverseTileTextureSize); 32 | COLOR.a = tile.a*COLOR.a; 33 | } -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/new_map_dialog.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="Control" type="ConfirmationDialog"] 4 | anchor_left = 0.5 5 | anchor_top = 0.5 6 | anchor_right = 0.5 7 | anchor_bottom = 0.5 8 | margin_left = -128.0 9 | margin_top = -120.0 10 | margin_right = 128.0 11 | margin_bottom = -9.0 12 | rect_min_size = Vector2( 170, 59.5 ) 13 | popup_exclusive = true 14 | window_title = "New Map" 15 | __meta__ = { 16 | "_edit_use_anchors_": false 17 | } 18 | 19 | [node name="V" type="VBoxContainer" parent="."] 20 | anchor_top = 0.5 21 | anchor_right = 1.0 22 | anchor_bottom = 0.5 23 | margin_left = 8.0 24 | margin_top = -47.5 25 | margin_right = -8.0 26 | margin_bottom = 19.5 27 | __meta__ = { 28 | "_edit_use_anchors_": false 29 | } 30 | 31 | [node name="Label2" type="Label" parent="V"] 32 | margin_right = 240.0 33 | margin_bottom = 14.0 34 | size_flags_vertical = 0 35 | text = "Map Size" 36 | align = 1 37 | valign = 1 38 | __meta__ = { 39 | "_edit_use_anchors_": false 40 | } 41 | 42 | [node name="H" type="HBoxContainer" parent="V"] 43 | margin_top = 18.0 44 | margin_right = 240.0 45 | margin_bottom = 42.0 46 | 47 | [node name="Label" type="Label" parent="V/H"] 48 | margin_top = 5.0 49 | margin_right = 38.0 50 | margin_bottom = 19.0 51 | text = "Width" 52 | __meta__ = { 53 | "_edit_use_anchors_": false 54 | } 55 | 56 | [node name="Width" type="SpinBox" parent="V/H"] 57 | margin_left = 42.0 58 | margin_right = 116.0 59 | margin_bottom = 24.0 60 | 61 | [node name="HT" type="Label" parent="V/H"] 62 | margin_left = 120.0 63 | margin_top = 5.0 64 | margin_right = 162.0 65 | margin_bottom = 19.0 66 | text = "Height" 67 | __meta__ = { 68 | "_edit_use_anchors_": false 69 | } 70 | 71 | [node name="Height" type="SpinBox" parent="V/H"] 72 | margin_left = 166.0 73 | margin_right = 240.0 74 | margin_bottom = 24.0 75 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/tlepicker_selected_cell.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Control 3 | 4 | var cell_size:Vector2 = Vector2(16,16) setget set_cell_size 5 | var cell_start = Vector2() 6 | var cell_end = Vector2(1,1) 7 | 8 | var tileset_size = Vector2(1,1) 9 | onready var spr = $Sprite 10 | var ready = false 11 | var texture = null 12 | var zoom_level = 1 13 | 14 | func _ready(): 15 | set_tex(texture) 16 | ready = true 17 | 18 | func _enter_tree(): 19 | set_tex(texture) 20 | 21 | func set_tex(_texture:Texture): 22 | texture = _texture 23 | if spr != null: 24 | spr.texture = texture 25 | update_tieset_size() 26 | get_parent().get_parent()._resized() 27 | set_zoom_level(zoom_level) 28 | 29 | elif !ready: 30 | call_deferred("set_tex",texture) 31 | 32 | 33 | 34 | func set_selection(start,end): 35 | cell_start = Vector2(min(start.x,end.x),min(start.y,end.y)) 36 | cell_end = Vector2(max(start.x,end.x),max(end.y,start.y)) 37 | cell_start.x = clamp(cell_start.x,0,tileset_size.x-1) 38 | cell_start.y = clamp(cell_start.y,0,tileset_size.y-1) 39 | cell_end.x = clamp(cell_end.x,0,tileset_size.x-1) 40 | cell_end.y = clamp(cell_end.y,0,tileset_size.y-1) 41 | update() 42 | 43 | func update_tieset_size(): 44 | if texture == null: 45 | return 46 | tileset_size = (texture.get_size()/cell_size).floor() 47 | set_selection(cell_start,cell_end) 48 | 49 | func set_cell_size(size:Vector2): 50 | cell_size = size 51 | update_tieset_size() 52 | update() 53 | 54 | func get_cell_poss_at(pos): 55 | if texture == null: 56 | return 57 | var global = get_global_transform().xform(pos) 58 | var scale = min(rect_size.y/float(texture.get_height()), rect_size.x/float(texture.get_width())) 59 | var local = spr.to_local(global) 60 | local = (local/cell_size).floor() 61 | return Vector2(clamp(local.x,0,tileset_size.x-1),clamp(local.y,0,tileset_size.y-1)) 62 | 63 | func set_zoom_level(value): 64 | zoom_level = max(floor(value), 1); 65 | spr.scale = Vector2(1,1)*zoom_level 66 | rect_min_size = spr.transform.basis_xform(spr.get_rect().size) 67 | rect_size = rect_min_size 68 | 69 | func _draw(): 70 | if spr == null || spr.texture == null: 71 | return 72 | var scale = spr.scale 73 | var rect = Rect2(cell_start*cell_size*scale,cell_size*scale).expand(cell_end*cell_size*scale+cell_size*scale) 74 | draw_rect(rect,Color.white,false) 75 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/tilepicker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://addons/fabianlc_gpu_tilemap/scenes/tilepicker.gd" type="Script" id=2] 4 | [ext_resource path="res://addons/fabianlc_gpu_tilemap/scenes/tlepicker_selected_cell.gd" type="Script" id=3] 5 | 6 | [node name="TilePicker" type="VBoxContainer"] 7 | anchor_bottom = 1.0 8 | margin_right = 123.0 9 | script = ExtResource( 2 ) 10 | __meta__ = { 11 | "_edit_use_anchors_": false 12 | } 13 | 14 | [node name="Label" type="Label" parent="."] 15 | margin_right = 123.0 16 | margin_bottom = 14.0 17 | size_flags_horizontal = 3 18 | text = "Tileset" 19 | align = 1 20 | valign = 1 21 | 22 | [node name="HSeparator" type="HSeparator" parent="."] 23 | margin_top = 18.0 24 | margin_right = 123.0 25 | margin_bottom = 22.0 26 | size_flags_horizontal = 3 27 | 28 | [node name="Options" type="HBoxContainer" parent="."] 29 | margin_top = 26.0 30 | margin_right = 123.0 31 | margin_bottom = 50.0 32 | size_flags_horizontal = 3 33 | alignment = 1 34 | 35 | [node name="Label2" type="Label" parent="Options"] 36 | margin_top = 5.0 37 | margin_right = 32.0 38 | margin_bottom = 19.0 39 | text = "h flip" 40 | __meta__ = { 41 | "_edit_use_anchors_": false 42 | } 43 | 44 | [node name="FlipH" type="CheckBox" parent="Options"] 45 | margin_left = 36.0 46 | margin_right = 60.0 47 | margin_bottom = 24.0 48 | align = 1 49 | 50 | [node name="Label3" type="Label" parent="Options"] 51 | margin_left = 64.0 52 | margin_top = 5.0 53 | margin_right = 95.0 54 | margin_bottom = 19.0 55 | text = "v flip" 56 | __meta__ = { 57 | "_edit_use_anchors_": false 58 | } 59 | 60 | [node name="FlipV" type="CheckBox" parent="Options"] 61 | margin_left = 99.0 62 | margin_right = 123.0 63 | margin_bottom = 24.0 64 | 65 | [node name="ScrollContainer" type="ScrollContainer" parent="."] 66 | margin_top = 54.0 67 | margin_right = 123.0 68 | margin_bottom = 600.0 69 | mouse_filter = 1 70 | size_flags_horizontal = 3 71 | size_flags_vertical = 3 72 | __meta__ = { 73 | "_edit_use_anchors_": false 74 | } 75 | 76 | [node name="Tileset" type="Control" parent="ScrollContainer"] 77 | margin_right = 123.0 78 | margin_bottom = 546.0 79 | rect_min_size = Vector2( 1, 1 ) 80 | focus_mode = 2 81 | size_flags_horizontal = 3 82 | size_flags_vertical = 3 83 | script = ExtResource( 3 ) 84 | 85 | [node name="Sprite" type="Sprite" parent="ScrollContainer/Tileset"] 86 | show_behind_parent = true 87 | centered = false 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a shader based tilemap alternative, maps are stored as image textures, for now the maps are saved in the scene, 2 | but be careful, the editor crashes if you expand the image resource on the map property of a GPUTilemap node 3 | see https://github.com/godotengine/godot/issues/34482. 4 | 5 | This does not work on old mobile GPUs with bad floating point accuracy such as the Mali 400 and Mali 450(https://www.youi.tv/mobile-gpu-floating-point-accuracy-variances/) 6 | 7 | To use it you need an ImageTexture as a base for your map, the image data from the original image won't be modified, it will only change on 8 | the image texture resource. 9 | 10 | The maximum map size should be 1024x1024 tiles, this is because 11 | older devices might not support bigger textures, however you can add another tilemap node if you need more. 12 | 13 | 14 | The maximum tileset size is 256x256(65536) tiles, this should be enough in most cases. 15 | 16 | Now the tilemap data is automatically locked and it's never unlocked so it can always be modified without any lock for better editing performance. 17 | 18 | Performance 19 | 20 | The main benefit you get with this addon is that because the zoom level doesn't affect performance(without changing the resolution of course) it doesn't lag the editor as much as the built in tilemap node and you can show a lot of tiles on screen in-game without getting a performance hit and removes the CPU overhead. 21 | 22 | Here's a comparison between two low end laptops, an Intel pentium 3540 and a BayTrail GPU vs an Intel i5 m540 and an Ironlake GPU (better gpu vs better cpu),the game is using the 2D stretch mode and is running at 768x720 without vsync and there are no empty tiles on the screen. 23 | 24 | Intel pentium 3540 with BayTrail GPU. 25 | 26 | | FPS | Map Type | N° of maps | 27 | | ---- | ---------- |------------ | 28 | | 415 | GPUTilemap | 1 | 29 | | 232 | Tilemap | 1 | 30 | | 300 | GPUTilemap | 2 | 31 | | 160 | Tilemap | 2 | 32 | | 245 | GPUTilemap | 3 | 33 | | 124 | Tilemap | 3 | 34 | 35 | Intel i5 m450 with Ironlake GPU 36 | 37 | | FPS | Map Type | N° of maps | 38 | | ---- | ---------- |------------ | 39 | | 260 | GPUTilemap | 1 | 40 | | 287 | Tilemap | 1 | 41 | | 198 | GPUTilemap | 2 | 42 | | 232 | Tilemap | 2 | 43 | | 160 | GPUTilemap | 3 | 44 | | 195 | Tilemap | 3 | 45 | 46 | 47 | As you can see on BayTrail GPUTilemap is faster but on Ironlake Tilemap is faster, however at a resolution of 256x240 GPUTilemap is faster on the Intel i5 m450 because the Ironlake can handle that very easily and the Intel pentium 3540 is a slower CPU. 48 | 49 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/autotile scripts/autotile_script.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name AutotileScript 3 | #Base script for autotile scripts 4 | #constants for setup_autotile 5 | const TilePos = 0 6 | const AutotileIds = 1 7 | 8 | const SelectionTile = 0 9 | 10 | #If true when the user right clicks the tileset view an option to setup autotile for this script 11 | #for the selected tiles will show up 12 | var enable_setup_option = false 13 | 14 | #Reference to the tilemap being modified 15 | var tilemap = null 16 | 17 | #Parameters 18 | #selection: array of arrays containing data about the selected tiles 19 | # each array has the following: 20 | # index 0(TilePos): position of the tile in grid coordinates relative to the top left tile of the selection 21 | # index 1(AutoTileIds): dictionary with all the autotile ids of the tile, modify this to setup the tiles for your script 22 | #Return value 23 | #the function must return the same array, only the changes to autitile_ids will be applied 24 | func setup_autotile(selection,group_id): 25 | pass 26 | 27 | #Sumary 28 | #Called when a tile is placed on the map, use map maniputation functions in this class to modify the map 29 | #Parameters 30 | #autotile_ids: array containing autotile ids of the tile being placed 31 | #tile_pos: position in grid coordinates of the tile being placed on the map 32 | #tile: a Vector2 representing the tile position in the tileset 33 | func autotile(tile_pos:Vector2,group_id:int): 34 | pass 35 | 36 | #Puts a tile in the map, only modify the map using this function 37 | func put_tile(pos:Vector2,tile:Vector2): 38 | tilemap.autotile_put_tile(pos,tile) 39 | 40 | #Returns a Vector2 representing the tile position in he tileset at position pos in grid coordinates 41 | #if the tile doesn't exist return (-1,-1) 42 | func get_tile(pos:Vector2): 43 | return tilemap.autotile_get_tile(pos) 44 | 45 | func get_tile_pixel(pos:Vector2): 46 | return tilemap.autotile_get_tile_pixel(pos) 47 | 48 | #Returns an array of autotile_ids 49 | func tile_get_data(tile:Vector2): 50 | return tilemap.tile_get_autotile_data(tile) 51 | 52 | #Sumary 53 | #Get the nearby tiles with the same group id, set group_id to -1 to ignore group_ids and just get the tiles 54 | #Returns 55 | #an array of arrays with the following 56 | # index 0 tile data, an array that looks like this [autotile_ids,cell_position] 57 | func get_nearby_tiles(pos:Vector2,group_id:int): 58 | var dist = 3 59 | var x = 0 60 | var y = 0 61 | var top_left = pos -Vector2(1,1) 62 | var tiles = [] 63 | while(x= fgid && gtid <= fgid+tcount: 132 | var tid = gtid - fgid 133 | var tx = int(tid)%columns 134 | var ty = int(tid)/columns 135 | mdata.set_pixel(x,y,Color8(tx,ty,0,255)) 136 | 137 | x += 1 138 | if x >= mw: 139 | x = 0 140 | y += 1 141 | if y >= mh: 142 | break 143 | 144 | #mdata.unlock() 145 | if mdata.is_empty(): 146 | printerr("data is empty") 147 | return 148 | 149 | var tex = ImageTexture.new() 150 | tex.create_from_image(mdata,0) 151 | if plugin != null: 152 | plugin.tilemap.set_tile_size(ts) 153 | plugin.tilemap.set_map_texture(tex) 154 | 155 | print("Import done") 156 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/resize_map_dialog.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://addons/fabianlc_gpu_tilemap/scenes/alignment_grid.gd" type="Script" id=1] 4 | 5 | [sub_resource type="Theme" id=1] 6 | 7 | [sub_resource type="Theme" id=2] 8 | 9 | [node name="Control" type="ConfirmationDialog"] 10 | visible = true 11 | anchor_left = 0.5 12 | anchor_top = 0.5 13 | anchor_right = 0.5 14 | anchor_bottom = 0.5 15 | margin_left = -128.0 16 | margin_top = -107.0 17 | margin_right = 128.0 18 | margin_bottom = 152.0 19 | rect_min_size = Vector2( 170, 59.5 ) 20 | size_flags_horizontal = 11 21 | size_flags_vertical = 3 22 | popup_exclusive = true 23 | window_title = "Resize Map" 24 | dialog_text = "This cannot be undone. 25 | " 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="V" type="VBoxContainer" parent="."] 31 | anchor_left = 0.5 32 | anchor_top = 0.5 33 | anchor_right = 0.5 34 | anchor_bottom = 0.5 35 | margin_left = -120.0 36 | margin_top = -90.5 37 | margin_right = 120.0 38 | margin_bottom = 93.5 39 | size_flags_horizontal = 6 40 | size_flags_vertical = 6 41 | __meta__ = { 42 | "_edit_use_anchors_": false 43 | } 44 | 45 | [node name="Alignment" type="Label" parent="V"] 46 | margin_right = 240.0 47 | margin_bottom = 14.0 48 | size_flags_vertical = 0 49 | text = "Alignment" 50 | align = 1 51 | valign = 1 52 | __meta__ = { 53 | "_edit_use_anchors_": false 54 | } 55 | 56 | [node name="GridContainer" type="GridContainer" parent="V"] 57 | margin_left = 81.0 58 | margin_top = 18.0 59 | margin_right = 158.0 60 | margin_bottom = 86.0 61 | size_flags_horizontal = 6 62 | theme = SubResource( 1 ) 63 | columns = 3 64 | script = ExtResource( 1 ) 65 | 66 | [node name="Button" type="Button" parent="V/GridContainer"] 67 | margin_right = 23.0 68 | margin_bottom = 20.0 69 | theme = SubResource( 2 ) 70 | toggle_mode = true 71 | pressed = true 72 | text = "O" 73 | 74 | [node name="Button2" type="Button" parent="V/GridContainer"] 75 | margin_left = 27.0 76 | margin_right = 50.0 77 | margin_bottom = 20.0 78 | theme = SubResource( 2 ) 79 | toggle_mode = true 80 | text = "O" 81 | 82 | [node name="Button3" type="Button" parent="V/GridContainer"] 83 | margin_left = 54.0 84 | margin_right = 77.0 85 | margin_bottom = 20.0 86 | theme = SubResource( 2 ) 87 | toggle_mode = true 88 | text = "O" 89 | 90 | [node name="Button4" type="Button" parent="V/GridContainer"] 91 | margin_top = 24.0 92 | margin_right = 23.0 93 | margin_bottom = 44.0 94 | theme = SubResource( 2 ) 95 | toggle_mode = true 96 | text = "O" 97 | 98 | [node name="Button5" type="Button" parent="V/GridContainer"] 99 | margin_left = 27.0 100 | margin_top = 24.0 101 | margin_right = 50.0 102 | margin_bottom = 44.0 103 | theme = SubResource( 2 ) 104 | toggle_mode = true 105 | text = "O" 106 | 107 | [node name="Button6" type="Button" parent="V/GridContainer"] 108 | margin_left = 54.0 109 | margin_top = 24.0 110 | margin_right = 77.0 111 | margin_bottom = 44.0 112 | theme = SubResource( 2 ) 113 | toggle_mode = true 114 | text = "O" 115 | 116 | [node name="Button7" type="Button" parent="V/GridContainer"] 117 | margin_top = 48.0 118 | margin_right = 23.0 119 | margin_bottom = 68.0 120 | theme = SubResource( 2 ) 121 | toggle_mode = true 122 | text = "O" 123 | 124 | [node name="Button8" type="Button" parent="V/GridContainer"] 125 | margin_left = 27.0 126 | margin_top = 48.0 127 | margin_right = 50.0 128 | margin_bottom = 68.0 129 | theme = SubResource( 2 ) 130 | toggle_mode = true 131 | text = "O" 132 | 133 | [node name="Button9" type="Button" parent="V/GridContainer"] 134 | margin_left = 54.0 135 | margin_top = 48.0 136 | margin_right = 77.0 137 | margin_bottom = 68.0 138 | theme = SubResource( 2 ) 139 | toggle_mode = true 140 | text = "O" 141 | 142 | [node name="Label2" type="Label" parent="V"] 143 | margin_top = 90.0 144 | margin_right = 240.0 145 | margin_bottom = 104.0 146 | size_flags_vertical = 0 147 | text = "Map Size" 148 | align = 1 149 | valign = 1 150 | __meta__ = { 151 | "_edit_use_anchors_": false 152 | } 153 | 154 | [node name="H" type="HBoxContainer" parent="V"] 155 | margin_top = 108.0 156 | margin_right = 240.0 157 | margin_bottom = 132.0 158 | 159 | [node name="Label" type="Label" parent="V/H"] 160 | margin_top = 5.0 161 | margin_right = 38.0 162 | margin_bottom = 19.0 163 | text = "Width" 164 | __meta__ = { 165 | "_edit_use_anchors_": false 166 | } 167 | 168 | [node name="Width" type="SpinBox" parent="V/H"] 169 | margin_left = 42.0 170 | margin_right = 116.0 171 | margin_bottom = 24.0 172 | 173 | [node name="HT" type="Label" parent="V/H"] 174 | margin_left = 120.0 175 | margin_top = 5.0 176 | margin_right = 162.0 177 | margin_bottom = 19.0 178 | text = "Height" 179 | __meta__ = { 180 | "_edit_use_anchors_": false 181 | } 182 | 183 | [node name="Height" type="SpinBox" parent="V/H"] 184 | margin_left = 166.0 185 | margin_right = 240.0 186 | margin_bottom = 24.0 187 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/autotile scripts/default_autotile.gd: -------------------------------------------------------------------------------- 1 | extends AutotileScript 2 | 3 | #Defautl autotile script, the full setup has 48 tiles 4 | #based on https://gamedevelopment.tutsplus.com/tutorials/how-to-use-tile-bitmasking-to-auto-tile-your-level-layouts--cms-25673 5 | 6 | #Direction masks 7 | const Tl = 8 #Top left 8 | const T = 16 #Top 9 | const Tr = 1 #Top right 10 | const L = 128 #Left 11 | const R = 32 #Right 12 | const Bl = 4 #Bottom left 13 | const B = 64 #Bottom 14 | const Br = 2 #Bottom right 15 | 16 | #Masks to get bits of a number 17 | const xoox = 6 #0110 18 | const oxox = 10 #1010 19 | const xoxx = 4 #0100 20 | const xoxo = 5 #0101 21 | const oxxo = 9 #1001 22 | const oxxx = 8 #1000 23 | const xxxo = 1 #0001 24 | const xxoo = 3 #0011 25 | const ooxx = 12 #1100 26 | const oooo = 15 #1111 27 | const xxox = 2 #0010 28 | 29 | var low_bit_masks = [oooo,xoox,ooxx,xoxx,oxxo,0,oxxx,0,xxoo,xxoo,0,0,xxxo,0,0,0] 30 | 31 | #Maps the bitmask of a tile to an autotile id which can be used to get a tile in the tileset 32 | const mask_to_id ={ 33 | 0:0,2:0,3:0,4:0,5:0,6:0,8:0,9:0,1:0,10:0,11:0,12:0,13:0,14:0,15:0,#Single tile 34 | 98:1,99:1,102:1,103:1,107:1,110:1,111:1,239:2,106:1,#Top left 35 | 230:2,238:2,231:2,#Top 36 | 206:3,197:3,198:3,196:3,199:3,204:3,207:3,#TopRight 37 | 115:4,123:4,127:4,119:4,#Left 38 | 255:5,#Center 39 | 220:6,223:6,222:6,221:6,#Right 40 | 49:7,51:7,53:7,55:7,59:7,61:7,57:7,63:7,#Bottom left 41 | 185:8,189:8,187:8,191:8,#Bottom 42 | 152:9,153:9,154:9,155:9,156:9,157:9,159:9,#Bottom right 43 | 32:10,33:10,34:10,35:10,36:10,37:10,38:10,40:10,44:10,45:10,46:10,47:10,#HLine left 44 | 160:11,163:11,166:11,167:11,171:11,172:11,175:11,168:11,161:11,162:11,164:11,169:11,#HLine center 45 | 128:12,129:12,130:12,131:12,140:12,141:12,143:12,132:12,136:12,#HLine Right 46 | 64:13,70:13,65:13,66:13,69:13,71:13,72:13,#VLine top 47 | 73:13,78:13,79:13,80:14,81:14,82:14,85:14,86:14,89:14,93:14,94:14,84:14,87:14,91:14,95:14,#VLine center 48 | 16:15,22:15,24:15,18:15,20:15,26:15,25:15,30:15,31:15,27:15,29:15,#Vline bottom 49 | 253:16, 50 | 251:17, 51 | 254:18, 52 | 247:19, 53 | 97:20, 54 | 101:20, 55 | 105:20, 56 | 108:20, 57 | 109:20, 58 | 224:21, 59 | 225:21, 60 | 233:21, 61 | 194:22, 62 | 195:22, 63 | 200:22, 64 | 202:22, 65 | 203:22, 66 | 112:23, 67 | 116:23, 68 | 120:23, 69 | 124:23, 70 | 240:24, 71 | 208:25, 72 | 211:25, 73 | 48:26, 74 | 50:26, 75 | 52:26, 76 | 54:26, 77 | 56:26, 78 | 58:26, 79 | 60:26, 80 | 62:26, 81 | 176:27, 82 | 178:27, 83 | 180:27, 84 | 182:27, 85 | 149:28, 86 | 151:28, 87 | 113:29, 88 | 117:29, 89 | 121:29, 90 | 125:29, 91 | 218:30, 92 | 219:30, 93 | 114:31, 94 | 118:31, 95 | 122:31, 96 | 126:31, 97 | 213:32, 98 | 215:32, 99 | 243:33, 100 | 252:34, 101 | 229:35, 102 | 237:35, 103 | 226:36, 104 | 227:36, 105 | 234:36, 106 | 235:36, 107 | 184:37, 108 | 186:37, 109 | 188:37, 110 | 190:37, 111 | 177:38, 112 | 179:38, 113 | 181:38, 114 | 183:38, 115 | 246:39, 116 | 249:40, 117 | 242:41, 118 | 244:42, 119 | 241:43, 120 | 248:44, 121 | 250:45, 122 | 245:46, 123 | } 124 | 125 | func _init(): 126 | enable_setup_option = true 127 | 128 | #Group ids must be set when the autosetup happens 129 | func setup_autotile(selection:Array,group_id:int): 130 | for tile in selection: 131 | tilemap.autotile_tile_set_group(tile,group_id) 132 | #Quick 9 patch setup 133 | if selection.size() == 9: 134 | print("setting up 9 patch") 135 | var id_to_selection = [4,0,1,2,3,4,5,6,7,8,4,4,4,4,4,4,4,4,4,4,0,1,2,3,4,5,6,7,8] 136 | for i in range(47): 137 | if i < 29: 138 | tilemap.autotile_add_id(selection[id_to_selection[i]],i) 139 | else: 140 | tilemap.autotile_add_id(selection[4],i) 141 | return 142 | #9 patch + corners 143 | if selection.size() == 15: 144 | print("setting up 9 patch + corners") 145 | var id_to_selection = [7,0,1,2,5,6,7,10,11,12,6,6,6,6,6,6,3,4,8,9,0,1,2,5,6,7,10,11,12] 146 | for i in range(47): 147 | if i < 29: 148 | tilemap.autotile_add_id(selection[id_to_selection[i]],i) 149 | else: 150 | tilemap.autotile_add_id(selection[6],i) 151 | return 152 | #9 patch + single tile and tile lines 153 | if selection.size() == 16: 154 | print("setting up 9 patch + pilars + single tile") 155 | var id_to_selection = [12,1,2,3,5,6,7,9,10,11,13,14,15,0,4,8,6,6,6,6,1,2,3,5,6,7,9,10,11] 156 | for i in range(47): 157 | if i < 29: 158 | tilemap.autotile_add_id(selection[id_to_selection[i]],i) 159 | else: 160 | tilemap.autotile_add_id(selection[6],i) 161 | #Medium setup with basic corners 162 | if selection.size() == 24: 163 | print("setting up 9 patch + pilars + single tile + corners") 164 | var id_to_selection = [18,1,2,3,7,8,9,13,14,15,19,20,21,0,6,12,4,5,10,11,1,2,3,7,8,9,13,14,15] 165 | for i in range(47): 166 | if i < 29: 167 | tilemap.autotile_add_id(selection[id_to_selection[i]],i) 168 | else: 169 | tilemap.autotile_add_id(selection[8],i) 170 | #full setup 171 | if selection.size() == 48: 172 | print("full setup") 173 | var id_to_selection = [18,1,2,3,7,8,9,13,14,15,19,20,21,0,6,12,4,5,10,11,24,25,26,30,31,32,36,37,38,16,17,22,23,28,29,34,35,40,41,33,39,42,43,44,45,46,47] 174 | for i in range(47): 175 | tilemap.autotile_add_id(selection[id_to_selection[i]],i) 176 | 177 | func get_closest_id(mask): 178 | var msk = mask 179 | var search_range = 10 180 | var i = 0 181 | var id = null 182 | while i> 4 220 | 221 | var autotile_id = mask_to_id.get(full_mask,-1) 222 | if autotile_id == -1: 223 | #print("unknown combination " + str(full_mask) + "(" + str(bitmask) + ")," + str(autotile_id) + ", "+ str(low_bits) + ", "+ str(high_bits)) 224 | autotile_id = get_closest_id(full_mask) 225 | 226 | 227 | if autotile_id == -1: 228 | bitmask = high_bits | (low_bits & low_bit_masks[mask_id]) 229 | autotile_id = mask_to_id.get(bitmask,-1) 230 | if autotile_id != -1: 231 | #if prev_init: 232 | # print(str(low_bits|high_bits) + "(" + str(bitmask) + ")," + ", " + str(autotile_id) + ", "+ str(low_bits) + ", "+ str(high_bits)) 233 | var new_tile = tilemap.autotile_id_get_tile(autotile_id,group_id) 234 | if new_tile != Vector2(-1,-1) : 235 | put_tile(tile_pos,new_tile) 236 | #When painting autotile will be called for neigbor tiles automatically 237 | else:#when we erase we just have to update the neighbor tiles 238 | for t_pos in next_autotile: 239 | update_tile(t_pos,group_id) 240 | 241 | func update_tile(tile_pos,group_id): 242 | var tiles:Array = get_nearby_tiles(tile_pos,group_id) 243 | 244 | var bitmask = 0 245 | var rel_pos 246 | var low_bits = 0 247 | var high_bits = 0 248 | for tile in tiles: 249 | rel_pos = tile[1] - tile_pos 250 | var bits = mask_from_relative_pos(rel_pos) 251 | if abs(rel_pos.x) != abs(rel_pos.y): 252 | high_bits = high_bits | bits 253 | else: 254 | low_bits = low_bits | bits 255 | 256 | var current_color = tilemap.map_data.get_pixelv(tile_pos) 257 | current_color.b = 0 258 | 259 | var current_tile = Vector2(int(current_color.r*255.0),int(current_color.g*255.0)) 260 | 261 | if int(current_color.a) == 1: 262 | #Make sure the tile isn't flipped 263 | put_tile(tile_pos,current_tile) 264 | var full_mask = (high_bits|low_bits) 265 | var mask_id = high_bits >> 4 266 | 267 | var autotile_id = mask_to_id.get(full_mask,-1) 268 | if autotile_id == -1: 269 | autotile_id = get_closest_id(full_mask) 270 | if autotile_id == -1: 271 | bitmask = high_bits | (low_bits & low_bit_masks[mask_id]) 272 | autotile_id = mask_to_id.get(bitmask,-1) 273 | if autotile_id != -1: 274 | var new_tile = tilemap.autotile_id_get_tile(autotile_id,group_id) 275 | if new_tile != Vector2(-1,-1) : 276 | put_tile(tile_pos,new_tile) 277 | 278 | 279 | func mask_from_relative_pos(pos): 280 | match pos: 281 | Vector2(-1,0): 282 | return L 283 | Vector2(-1,-1): 284 | return Tl 285 | Vector2(0,-1): 286 | return T 287 | Vector2(1,-1): 288 | return Tr 289 | Vector2(1,0): 290 | return R 291 | Vector2(1,1): 292 | return Br 293 | Vector2(0,1): 294 | return B 295 | Vector2(-1,1): 296 | return Bl 297 | _: 298 | return 0 299 | 300 | 301 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/scenes/tilepicker.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends VBoxContainer 3 | 4 | const SetTypeId = 0 5 | const SetupAutotile = 1 6 | const AddAutotileId = 2 7 | const RemoveAutotileId = 3 8 | const SetGroupId = 4 9 | const ClearAutotileData = 5 10 | const SaveTileData = 6 11 | const LoadTileData = 7 12 | 13 | var selected_tile = Vector2() 14 | 15 | onready var tileset = $ScrollContainer/Tileset 16 | var selecting = false 17 | var selection_start_cell = Vector2() 18 | var plugin = null 19 | onready var scroll_container:ScrollContainer 20 | var scroll_h:HScrollBar 21 | var scroll_v:VScrollBar 22 | var hscroll = 0 23 | var vscroll = 0 24 | var scroll = false 25 | var scrolling = false 26 | var right_click_menu:PopupMenu 27 | var tile_id_dialog:AcceptDialog 28 | var tile_id_spinbox:SpinBox 29 | var last_option = 0 30 | var spin_value = 0 31 | 32 | #Options 33 | onready var flip_h:CheckBox = get_node("Options/FlipH") 34 | onready var flip_v:CheckBox = get_node("Options/FlipV") 35 | 36 | const magic = "H3po@xd23s94h7f42v5wp29" 37 | 38 | # Called when the node enters the scene tree for the first time. 39 | func _ready(): 40 | scroll_container = get_node("ScrollContainer") 41 | tileset.connect("gui_input",self,"tileset_input") 42 | connect("resized",self,"_resized") 43 | tileset.connect("mouse_exited",self,"tileset_mouse_exited") 44 | scroll_h = scroll_container.get_h_scrollbar() 45 | scroll_v = scroll_container.get_v_scrollbar() 46 | scroll_h.connect("changed",self,"scrollingh") 47 | scroll_v.connect("changed",self,"scrollingv") 48 | right_click_menu = PopupMenu.new() 49 | right_click_menu.add_item("Set type id",SetTypeId) 50 | right_click_menu.add_item("Setup autotile",SetupAutotile) 51 | right_click_menu.add_separator("Manual autotile setup") 52 | right_click_menu.add_item("Add autotile id",AddAutotileId) 53 | right_click_menu.add_item("Remove autotile id",RemoveAutotileId) 54 | right_click_menu.add_item("Set autotile group id",SetGroupId) 55 | right_click_menu.add_separator("Tile data") 56 | right_click_menu.add_item("Clear autotile data",ClearAutotileData) 57 | right_click_menu.add_item("Export tile data",SaveTileData) 58 | right_click_menu.add_item("Import tile data",LoadTileData) 59 | right_click_menu.connect("id_pressed",self,"menu_id_pressed") 60 | add_child(right_click_menu) 61 | 62 | tile_id_dialog = AcceptDialog.new() 63 | tile_id_dialog.window_title = "Set tile type id" 64 | 65 | tile_id_dialog.set_anchors_and_margins_preset(Control.PRESET_CENTER) 66 | tile_id_dialog.size_flags_horizontal = SIZE_EXPAND_FILL 67 | add_child(tile_id_dialog) 68 | var container = VBoxContainer.new() 69 | container.size_flags_horizontal = SIZE_EXPAND_FILL 70 | container.set_anchors_and_margins_preset(Control.PRESET_CENTER) 71 | tile_id_dialog.add_child(container) 72 | var lbl = Label.new() 73 | lbl.text = """The type id can be used by the instancing script. 74 | set to -1 to clear 75 | 76 | """ 77 | lbl.align = Label.ALIGN_CENTER 78 | lbl.valign = Label.VALIGN_CENTER 79 | lbl.set_anchors_and_margins_preset(Control.PRESET_CENTER) 80 | container.add_child(lbl) 81 | lbl = Label.new() 82 | lbl.text = "Type id" 83 | lbl.align = Label.ALIGN_CENTER 84 | lbl.valign = Label.VALIGN_CENTER 85 | lbl.set_anchors_and_margins_preset(Control.PRESET_CENTER) 86 | container.add_child(lbl) 87 | var spin_box = SpinBox.new() 88 | tile_id_spinbox = spin_box 89 | spin_box.min_value = -1 90 | spin_box.max_value = 256*256 91 | spin_box.set_anchors_and_margins_preset(Control.PRESET_CENTER) 92 | container.add_child(spin_box) 93 | spin_box.name = "TileId" 94 | tile_id_dialog.connect("confirmed",self,"type_id_confirmed") 95 | 96 | flip_h.connect("toggled",self,"update_plugin_brush") 97 | flip_v.connect("toggled",self,"update_plugin_brush") 98 | func menu_id_pressed(id): 99 | last_option = id 100 | if plugin == null || plugin.tilemap == null || plugin.tilemap.tileset == null || plugin.tilemap.map == null: 101 | printerr("Error: map not ready to be modified") 102 | return 103 | match(id): 104 | SetTypeId: 105 | set_type_id() 106 | SetupAutotile: 107 | var tilemap:GPUTileMap = plugin.tilemap 108 | if tilemap.has_autotile_script && tilemap.autotile_script_instance != null: 109 | open_spin_dialog("Autotile group id","Value") 110 | else: 111 | printerr("Missing autotile script") 112 | AddAutotileId: 113 | open_spin_dialog("Add autotile id","Value","") 114 | RemoveAutotileId: 115 | open_spin_dialog("Remove autotile id","Value","") 116 | SetGroupId: 117 | open_spin_dialog("Autotile group id","Value","Set to -1 to clear") 118 | SaveTileData: 119 | save_tile_data_dialog() 120 | LoadTileData: 121 | load_tile_data_dialog() 122 | ClearAutotileData: 123 | clear_autotile_data() 124 | 125 | func save_tile_data_dialog(): 126 | var dialog = FileDialog.new() 127 | dialog.access = FileDialog.ACCESS_FILESYSTEM 128 | dialog.mode = FileDialog.MODE_SAVE_FILE 129 | dialog.popup_exclusive = true 130 | dialog.add_filter("*.tiledata") 131 | 132 | dialog.connect("file_selected",self,"save_tile_data_confirmed",[dialog],CONNECT_DEFERRED) 133 | 134 | plugin.get_editor_interface().get_base_control().add_child(dialog) 135 | dialog.popup_centered_ratio() 136 | 137 | func save_tile_data_confirmed(selected_file, dialog:FileDialog): 138 | print("Save confimed") 139 | if !dialog.current_path.ends_with(".tiledata"): 140 | dialog.current_path += ".tiledata" 141 | var tilemap = plugin.tilemap 142 | if tilemap == null: 143 | printerr("tilemap is null") 144 | return 145 | var file = File.new() 146 | var err = file.open(dialog.current_path,File.WRITE) 147 | if err != OK: 148 | print(dialog.current_path) 149 | printerr("can't open file") 150 | return 151 | var data = {} 152 | data["magic"] = magic 153 | data["tile_data"] = tilemap.tile_data 154 | data["autotile_data"] = tilemap.autotile_data 155 | data["autotile_data_val2key"] = tilemap.autotile_data_val2key 156 | data["autotile_tile_groups"] = tilemap.autotile_tile_groups 157 | file.store_var(data) 158 | file.close() 159 | 160 | 161 | 162 | 163 | func load_tile_data_dialog(): 164 | var dialog = FileDialog.new() 165 | dialog.access = FileDialog.ACCESS_FILESYSTEM 166 | dialog.mode = FileDialog.MODE_OPEN_FILE 167 | dialog.popup_exclusive = true 168 | dialog.add_filter("*.tiledata") 169 | dialog.connect("file_selected",self,"load_tile_data_confirmed",[dialog],CONNECT_DEFERRED) 170 | 171 | plugin.get_editor_interface().get_base_control().add_child(dialog) 172 | dialog.popup_centered_ratio() 173 | 174 | func load_tile_data_confirmed(selected_file, dialog:FileDialog): 175 | var tilemap:GPUTileMap = plugin.tilemap 176 | if tilemap == null: 177 | printerr("tilemap is null") 178 | return 179 | var file = File.new() 180 | var err = file.open(dialog.current_path,File.READ) 181 | if err != OK: 182 | printerr("can't open file") 183 | return 184 | var obj = file.get_var() 185 | file.close() 186 | if !(obj is Dictionary) || obj["magic"] != magic: 187 | printerr("invalid data") 188 | return 189 | 190 | tilemap.tile_data = obj["tile_data"] 191 | tilemap.autotile_data = obj["autotile_data"] 192 | tilemap.autotile_data_val2key = obj["autotile_data_val2key"] 193 | tilemap.autotile_tile_groups = obj["autotile_tile_groups"] 194 | 195 | func clear_autotile_data(): 196 | plugin.tilemap.clear_autitle_data() 197 | 198 | func open_spin_dialog(dialog_name,spin_label_text,dialog_text = ""): 199 | var dialog = AcceptDialog.new() 200 | dialog.popup_exclusive = true 201 | dialog.window_title = dialog_name 202 | dialog.dialog_text = "" 203 | dialog.set_anchors_preset(Control.PRESET_CENTER) 204 | dialog.resizable = true 205 | dialog.dialog_autowrap = true 206 | var vbox = VBoxContainer.new() 207 | vbox.alignment = BoxContainer.ALIGN_BEGIN 208 | vbox.size_flags_horizontal = SIZE_EXPAND_FILL 209 | vbox.size_flags_vertical = SIZE_EXPAND_FILL 210 | dialog.size_flags_horizontal = SIZE_EXPAND_FILL 211 | 212 | vbox.set_anchors_and_margins_preset(Control.PRESET_CENTER) 213 | var label = Label.new() 214 | label.set_anchors_preset(Control.PRESET_CENTER) 215 | label.align = Label.ALIGN_CENTER 216 | label.valign = Label.VALIGN_CENTER 217 | label.text = dialog_text 218 | vbox.add_child(label) 219 | 220 | label = Label.new() 221 | label.set_anchors_preset(Control.PRESET_CENTER) 222 | label.align = Label.ALIGN_CENTER 223 | label.valign = Label.VALIGN_CENTER 224 | label.text = spin_label_text 225 | vbox.add_child(label) 226 | var spin = SpinBox.new() 227 | spin.max_value = 256 228 | spin.min_value = -1 229 | vbox.name = "V" 230 | vbox.add_child(spin) 231 | spin.name = "Spin" 232 | 233 | dialog.add_child(vbox) 234 | 235 | plugin.get_editor_interface().get_base_control().add_child(dialog) 236 | dialog.popup_centered() 237 | dialog.connect("popup_hide",self,"dialog_hide",[dialog]) 238 | dialog.connect("confirmed",self,"spin_dialog_confirmed",[dialog]) 239 | 240 | func dialog_hide(dialog): 241 | dialog.queue_free() 242 | 243 | func spin_dialog_confirmed(dialog:AcceptDialog): 244 | var spin = dialog.get_node("V/Spin") 245 | 246 | spin_value = spin.value 247 | 248 | match(last_option): 249 | SetupAutotile: 250 | setup_autotile_confirmed() 251 | SetGroupId: 252 | set_group_id_confirmed() 253 | AddAutotileId: 254 | add_autotile_id_confirmed() 255 | RemoveAutotileId: 256 | remove_autotile_id_confirmed() 257 | func add_autotile_id_confirmed(): 258 | var tilemap = plugin.tilemap 259 | var selection = Rect2(tileset.cell_start,Vector2(1,1)).expand(tileset.cell_end+Vector2(1,1)) 260 | var selection_size = selection.size 261 | if(selection_size.x <= 0 || selection_size.y <= 0): 262 | printerr("tileset selection is invalid") 263 | return 264 | var tstw = int(tileset.spr.texture.get_width()/tileset.cell_size.x) 265 | var tile_data = plugin.tilemap.tile_data 266 | var x = tileset.cell_start.x 267 | var y = tileset.cell_start.y 268 | var mx = x+selection_size.x 269 | var my = y+selection_size.y 270 | 271 | while x < mx && y < my: 272 | tilemap.autotile_add_id(Vector2(x,y),spin_value) 273 | x = x + 1 274 | if x >= mx: 275 | x = tileset.cell_start.x 276 | y += 1 277 | 278 | 279 | func remove_autotile_id_confirmed(): 280 | var tilemap = plugin.tilemap 281 | var selection = Rect2(tileset.cell_start,Vector2(1,1)).expand(tileset.cell_end+Vector2(1,1)) 282 | var selection_size = selection.size 283 | if(selection_size.x <= 0 || selection_size.y <= 0): 284 | printerr("tileset selection is invalid") 285 | return 286 | var tstw = int(tileset.spr.texture.get_width()/tileset.cell_size.x) 287 | var tile_data = plugin.tilemap.tile_data 288 | var x = tileset.cell_start.x 289 | var y = tileset.cell_start.y 290 | var mx = x+selection_size.x 291 | var my = y+selection_size.y 292 | 293 | while x < mx && y < my: 294 | tilemap.autotile_remove_id(Vector2(x,y),spin_value) 295 | x = x + 1 296 | if x >= mx: 297 | x = tileset.cell_start.x 298 | y += 1 299 | 300 | func setup_autotile_confirmed(): 301 | if spin_value == -1: 302 | printerr("group id is -1") 303 | return 304 | var selection_tiles = [] 305 | var tilemap:GPUTileMap = plugin.tilemap 306 | var selection = Rect2(tileset.cell_start,Vector2(1,1)).expand(tileset.cell_end+Vector2(1,1)) 307 | var selection_size = selection.size 308 | if(selection_size.x <= 0 || selection_size.y <= 0): 309 | print("tileset selection is invalid") 310 | return 311 | else: 312 | print(selection_size) 313 | var tstw = int(tileset.spr.texture.get_width()/tileset.cell_size.x) 314 | var tile_data = plugin.tilemap.tile_data 315 | var x = tileset.cell_start.x 316 | var y = tileset.cell_start.y 317 | var mx = x+selection_size.x 318 | var my = y+selection_size.y 319 | while x < mx && y < my: 320 | selection_tiles.append(Vector2(x,y)) 321 | x = x + 1 322 | if x >= mx: 323 | x = tileset.cell_start.x 324 | y += 1 325 | if !selection_tiles.empty(): 326 | tilemap.autotile_script_instance.setup_autotile(selection_tiles,spin_value) 327 | else: 328 | printerr("Selection is empty") 329 | 330 | func set_group_id_confirmed(): 331 | var tilemap:GPUTileMap = plugin.tilemap 332 | var selection = Rect2(tileset.cell_start,Vector2(1,1)).expand(tileset.cell_end+Vector2(1,1)) 333 | var selection_size = selection.size 334 | if(selection_size.x <= 0 || selection_size.y <= 0): 335 | printerr("tileset selection is invalid") 336 | return 337 | 338 | var tstw = int(tileset.spr.texture.get_width()/tileset.cell_size.x) 339 | var tile_data = plugin.tilemap.tile_data 340 | var x = tileset.cell_start.x 341 | var y = tileset.cell_start.y 342 | var mx = x+selection_size.x 343 | var my = y+selection_size.y 344 | 345 | while x < mx && y < my: 346 | tilemap.autotile_tile_set_group(Vector2(x,y),spin_value) 347 | x = x + 1 348 | if x >= mx: 349 | x = tileset.cell_start.x 350 | y += 1 351 | 352 | func set_type_id(): 353 | if tileset.spr.texture == null || plugin == null: 354 | return 355 | tile_id_dialog.popup_centered() 356 | 357 | func type_id_confirmed(): 358 | var type = tile_id_spinbox.value 359 | 360 | var selection = Rect2(tileset.cell_start,Vector2(1,1)).expand(tileset.cell_end+Vector2(1,1)) 361 | var selection_size = selection.size 362 | if(selection_size.x <= 0 || selection_size.y <= 0): 363 | print("tileset selection is invalid") 364 | return 365 | else: 366 | print(selection_size) 367 | var tstw = int(tileset.spr.texture.get_width()/tileset.cell_size.x) 368 | var tile_data = plugin.tilemap.tile_data 369 | var x = tileset.cell_start.x 370 | var y = tileset.cell_start.y 371 | var mx = x+selection_size.x 372 | var my = y+selection_size.y 373 | while x < mx: 374 | y = tileset.cell_start.y 375 | while y < my: 376 | if type != -1: 377 | tile_data[int(y*tstw+x)] = type 378 | else: 379 | tile_data.erase(int(y*tstw+x)) 380 | y = y + 1 381 | x = x + 1 382 | 383 | #Workaround for scroll bugs 384 | func scrollingh(b=false): 385 | if !scrolling: 386 | scrolling = scroll_h.get_global_rect().has_point(scroll_h.get_global_mouse_position()) 387 | if scrolling: 388 | hscroll = scroll_container.scroll_horizontal 389 | 390 | func scrollingv(b=false): 391 | if !scrolling: 392 | scrolling = scroll_v.get_global_rect().has_point(scroll_v.get_global_mouse_position()) 393 | if scrolling: 394 | vscroll = scroll_container.scroll_vertical 395 | 396 | func _process(delta): 397 | if !Input.is_mouse_button_pressed(BUTTON_LEFT): 398 | scrolling = false 399 | scroll_container.scroll_vertical = vscroll 400 | scroll_container.scroll_horizontal = hscroll 401 | 402 | func _resized(): 403 | pass 404 | 405 | 406 | func tileset_mouse_exited(): 407 | if selecting: 408 | selecting = false 409 | update_plugin_brush() 410 | 411 | 412 | func update_plugin_brush(a=null): 413 | if !is_instance_valid(plugin) || !is_instance_valid(tileset): 414 | return 415 | print("update plugin brush") 416 | var selection = Rect2(tileset.cell_start,Vector2(1,1)).expand(tileset.cell_end+Vector2(1,1)) 417 | var selection_size = selection.size 418 | if(selection_size.x <= 0 || selection_size.y <= 0): 419 | print("tileset selection is invalid") 420 | return 421 | else: 422 | print(selection_size) 423 | 424 | var brush = Image.new() 425 | brush.create(selection_size.x,selection_size.y,false,Image.FORMAT_RGBA8) 426 | brush.lock() 427 | 428 | var flip = int(flip_h.pressed) + int(flip_v.pressed)*2 429 | print(flip) 430 | var x = 0 431 | var y = 0 432 | var mx = selection_size.x 433 | var my = selection_size.y 434 | while x < mx: 435 | while y < my: 436 | brush.set_pixel(x,y,Color8(x+tileset.cell_start.x,y+tileset.cell_start.y,flip,255)) 437 | y = y + 1 438 | y = 0 439 | x = x + 1 440 | if flip_h.pressed: 441 | brush.flip_x() 442 | if flip_v.pressed: 443 | brush.flip_y() 444 | brush.unlock() 445 | plugin.brush = brush 446 | plugin.brush.lock() 447 | 448 | func tileset_input(event): 449 | if event is InputEventMouseButton: 450 | if event.pressed: 451 | if event.button_index == BUTTON_WHEEL_UP: 452 | tileset.set_zoom_level(tileset.zoom_level + 1) 453 | elif event.button_index == BUTTON_WHEEL_DOWN: 454 | tileset.set_zoom_level(tileset.zoom_level - 1) 455 | accept_event() 456 | 457 | if event is InputEventMouse: 458 | var cell = tileset.get_cell_poss_at(event.position) 459 | if event is InputEventMouseButton: 460 | if event.pressed && event.button_index == BUTTON_LEFT: 461 | tileset.set_selection(cell,cell) 462 | selecting = true 463 | selection_start_cell = cell 464 | accept_event() 465 | elif event.button_index == BUTTON_LEFT && !event.pressed: 466 | selecting = false 467 | update_plugin_brush() 468 | accept_event() 469 | elif event.pressed && event.button_index == BUTTON_RIGHT: 470 | right_click_menu.popup() 471 | right_click_menu.set_global_position(get_global_mouse_position()) 472 | accept_event() 473 | if event is InputEventMouseMotion: 474 | if selecting: 475 | tileset.set_selection(selection_start_cell,cell) 476 | accept_event() 477 | 478 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/gpu_tilemap.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends ColorRect 3 | class_name GPUTileMap 4 | 5 | export var tileset:Texture setget set_tileset_texture 6 | export var map:ImageTexture setget set_map_texture 7 | export var tile_size:Vector2 = Vector2(16,16) setget set_tile_size 8 | export var instancing_script:Script = null#used for instancing objects on the map eg:collision objects 9 | export var instances_node_name:String = "Collision" 10 | export var auto_free_instances_node = true 11 | export var autotile_script:Script = load("res://addons/fabianlc_gpu_tilemap/autotile scripts/default_autotile.gd") 12 | var autotile_script_instance = null setget set_autotile_script 13 | var has_autotile_script = false 14 | var do_autotile = false 15 | export(Dictionary) var tile_data = {}#passed to the instancing script both the key and value are ints, the key is the tile id and the value is a type id 16 | export(Dictionary) var autotile_data = {}#Used for autotiling, maps the group_id to a dictionay of auto_tile ids which maps an autotile_id to a tile_id, eg: {group_id:{auto_tile_id:tile_id}} 17 | export(Dictionary) var autotile_data_val2key = {}#same as above but with the keys and values swaped, eg: {tile_id:{autotile_ids}} 18 | export(Dictionary) var autotile_tile_groups = {}#maps a tile_id to a group 19 | export var persistent_map_changes = false#if true map changes persist at runtime even after switching scenes 20 | 21 | enum FlipTile{NotFlipped=0,FlipH = 1,FlipV = 2, FlipBoth = 3} 22 | 23 | var map_data:Image 24 | var tileset_data:Image 25 | 26 | var shader_mat:ShaderMaterial 27 | var draw_editor_selection = false 28 | 29 | var rect_list = [] 30 | var draw_rect_list = false 31 | var cell_start = Vector2() 32 | var cell_end = Vector2() 33 | var map_size = Vector2() 34 | 35 | var mouse_pos = Vector2() 36 | var drawer 37 | var tile_selector = null 38 | var plugin = null 39 | 40 | var single_tile_brush:Image 41 | 42 | # Called when the node enters the scene tree for the first time. 43 | func _ready(): 44 | if !Engine.editor_hint: 45 | if map == null: 46 | queue_free() 47 | if !persistent_map_changes: 48 | set_process_input(false) 49 | map = map.duplicate(false) 50 | #map_data = map.get_data(); 51 | if map_data != null: 52 | map_data.lock() 53 | update_shader() 54 | else: 55 | drawer = Node2D.new() 56 | add_child(drawer) 57 | drawer.connect("draw",self,"draw_stuff") 58 | 59 | if has_autotile_script: 60 | single_tile_brush = Image.new() 61 | single_tile_brush.create(1,1,false,Image.FORMAT_RGBA8) 62 | single_tile_brush.lock() 63 | 64 | 65 | func _enter_tree(): 66 | material = ShaderMaterial.new(); 67 | shader_mat = material 68 | shader_mat.shader = load("res://addons/fabianlc_gpu_tilemap/shaders/tilemap_renderer.shader") 69 | shader_mat.set_shader_param("flipMap",preload("res://addons/fabianlc_gpu_tilemap/shaders/tile_flip_map.png")) 70 | update_shader() 71 | 72 | var size 73 | #I thought that it would be faster if I adjusted the view offset and size but performance is litterally the same 74 | if map != null: 75 | size = map.get_size()*tile_size 76 | shader_mat.set_shader_param("viewportSize",size) 77 | rect_size = size 78 | set_autotile_script(autotile_script) 79 | 80 | set_process(false) 81 | 82 | func update_shader(): 83 | shader_mat.set_shader_param("tileSize",tile_size); 84 | shader_mat.set_shader_param("inverseTileSize",Vector2(1.0,1.0)/tile_size) 85 | shader_mat.set_shader_param("tilemap",map) 86 | #shader_mat.set_shader_param("viewportSize",get_viewport_rect().size) 87 | shader_mat.set_shader_param("tileset",tileset) 88 | if tileset != null: 89 | shader_mat.set_shader_param("inverseTileTextureSize",Vector2(1.0,1.0)/tileset.get_size()) 90 | if map != null: 91 | shader_mat.set_shader_param("inverseSpriteTextureSize",Vector2(1.0,1.0)/map.get_size()) 92 | 93 | func set_tile_size(sz): 94 | tile_size = Vector2(max(1,sz.x),max(1,sz.y)) 95 | if !is_inside_tree(): 96 | return 97 | if plugin != null: 98 | if is_instance_valid(plugin.tile_picker): 99 | var ts = plugin.tile_picker.tileset 100 | ts.cell_size = sz 101 | ts.update() 102 | update_shader() 103 | 104 | func set_autotile_script(script): 105 | autotile_script = script 106 | var inst = script.new() 107 | if !(inst is AutotileScript): 108 | printerr("autotile script must inherit from AutotileScript") 109 | autotile_script = null 110 | autotile_script_instance = null 111 | has_autotile_script = false 112 | return 113 | autotile_script_instance = inst 114 | inst.tilemap = self 115 | has_autotile_script = true 116 | 117 | func set_tileset_texture(tex): 118 | tileset = tex 119 | if tex != null: 120 | tileset_data = tex.get_data() 121 | if !is_inside_tree(): 122 | return 123 | 124 | if is_instance_valid(tile_selector) && tile_selector.visible: 125 | tile_selector.tileset.set_tex(tileset) 126 | tile_selector.tileset.set_selection(Vector2(0,0),Vector2(0,0)) 127 | update_shader() 128 | 129 | 130 | func set_map_texture(tex:ImageTexture): 131 | map = tex 132 | if map != null: 133 | map_size = map.get_size() 134 | map_data = map.get_data() 135 | map_data.lock() 136 | if !is_inside_tree(): 137 | return 138 | update_shader() 139 | if map != null: 140 | var size = map.get_size()*tile_size 141 | shader_mat.set_shader_param("viewportSize",size) 142 | rect_size = size 143 | 144 | func set_cached_map_data(b): 145 | map_data = b 146 | map_data.lock() 147 | set_map_texture(map) 148 | 149 | func set_selection(start,end): 150 | if map == null: 151 | return 152 | cell_start = Vector2(min(start.x,end.x),min(start.y,end.y)) 153 | cell_end = Vector2(max(start.x,end.x),max(end.y,start.y)) 154 | cell_start.x = clamp(cell_start.x,0,map_size.x-1) 155 | cell_start.y = clamp(cell_start.y,0,map_size.y-1) 156 | cell_end.x = clamp(cell_end.x,0,map_size.x-1) 157 | cell_end.y = clamp(cell_end.y,0,map_size.y-1) 158 | update() 159 | 160 | func draw_editor_selection(): 161 | draw_editor_selection = true 162 | drawer.update() 163 | 164 | func draw_rect_list(): 165 | draw_rect_list = true 166 | drawer.update() 167 | 168 | func draw_clear(): 169 | if draw_editor_selection: 170 | draw_editor_selection = false 171 | if draw_rect_list: 172 | draw_rect_list = false 173 | drawer.update() 174 | 175 | func update_map_tex(): 176 | if map == null || map_data == null: 177 | return 178 | map.set_data(map_data) 179 | 180 | func get_tileset_tile_size() -> Vector2: 181 | return (tileset.get_size()/tile_size).floor() 182 | 183 | func erase_tile(cell,update_map = true): 184 | put_tile_pixel(cell,Color(0,0,0,0),update_map) 185 | 186 | func put_tile_with_autoile(cell,tilepos,flip=FlipTile.NotFlipped,update_map = true): 187 | if has_autotile_script && single_tile_brush != null: 188 | single_tile_brush.set_pixel(0,0,Color8(tilepos.x,tilepos.y,flip,255)) 189 | var prev_do_autotile = do_autotile 190 | do_autotile = true 191 | blend_brush(cell,single_tile_brush,update_map) 192 | do_autotile = prev_do_autotile 193 | 194 | func erase_tile_with_autotile(cell,update_map=true): 195 | if has_autotile_script && single_tile_brush != null: 196 | single_tile_brush.set_pixel(0,0,Color(0,0,0,1)) 197 | var prev_do_autotile = do_autotile 198 | do_autotile = true 199 | erase_with_brush(cell,single_tile_brush,update_map) 200 | do_autotile = prev_do_autotile 201 | 202 | func put_tile(cell,tilepos,flip=FlipTile.NotFlipped,update_map = true): 203 | if cell.x >= 0 && cell.x < map_size.x && cell.y >= 0 && cell.y < map_size.y: 204 | if plugin != null && plugin.making_action: 205 | plugin.add_do_tile_action(cell,map_data.get_pixelv(cell),Color8(tilepos.x,tilepos.y,flip,255)) 206 | 207 | map_data.set_pixelv(cell,Color8(tilepos.x,tilepos.y,flip,255)) 208 | if update_map: 209 | map.set_data(map_data) 210 | 211 | func put_tile_pixel(cell,color,update_map = true): 212 | if cell.x >= 0 && cell.x < map_size.x && cell.y >= 0 && cell.y < map_size.y: 213 | #if do_locks: 214 | #map_data.lock() 215 | if plugin != null && plugin.making_action: 216 | plugin.add_do_tile_action(cell,map_data.get_pixelv(cell),color) 217 | 218 | map_data.set_pixelv(cell,color) 219 | #if do_locks: 220 | #map_data.unlock() 221 | if update_map: 222 | map.set_data(map_data) 223 | 224 | func autotile_put_tile(cell,tilepos): 225 | put_tile(cell,tilepos,FlipTile.NotFlipped,false) 226 | 227 | func get_tile_at_cell(cell): 228 | if map == null: 229 | return Vector2(0,0) 230 | #map_data.lock() 231 | var t = Vector2() 232 | if cell.x >= 0 && cell.x < map_data.get_width() && cell.y >= 0 && cell.y < map_data.get_height(): 233 | var c = map_data.get_pixelv(cell) 234 | t = Vector2(int(c.r*255),int(c.g*255)) 235 | #map_data.unlock() 236 | return t 237 | 238 | func get_map_region_as_texture(start,end): 239 | if map == null || tileset == null: 240 | return null 241 | 242 | #map_data.lock() 243 | 244 | var cs = Vector2(min(start.x,end.x),min(start.y,end.y)) 245 | var ce = Vector2(max(start.x,end.x),max(start.y,end.y)) 246 | cs.x = clamp(cs.x,0,map_size.x-1) 247 | cs.y = clamp(cs.y,0,map_size.y-1) 248 | ce.x = clamp(ce.x,0,map_size.x-1) 249 | ce.y = clamp(ce.y,0,map_size.y-1) 250 | var rect = Rect2(cs,Vector2(1,1)).expand(ce+Vector2(1,1)) 251 | var w = rect.size.x 252 | var h = rect.size.y 253 | var mw = map_data.get_width() 254 | var mh = map_data.get_height() 255 | var c 256 | var p 257 | 258 | var tex = ImageTexture.new() 259 | var img = Image.new() 260 | img.create(w*tile_size.x,h*tile_size.y,false,Image.FORMAT_RGBA8) 261 | img.lock() 262 | 263 | var tdata:Image = tileset_data 264 | 265 | tdata.lock() 266 | 267 | var x = 0 268 | var y = 0 269 | while(x= 0 && p.x < mw && p.y >= 0 && p.y < mh: 274 | var col = map_data.get_pixelv(p) 275 | if col.a != 0: 276 | img.blit_rect(tdata,Rect2(int(col.r*255)*tile_size.x,int(col.g*255)*tile_size.y,tile_size.x,tile_size.y),Vector2(x*tile_size.x,y*tile_size.y)) 277 | 278 | y += 1 279 | x += 1 280 | 281 | #img.unlock() 282 | #map_data.unlock() 283 | #tdata.unlock() 284 | tex.create_from_image(img,0) 285 | return tex 286 | 287 | #Brush must be locked 288 | func erase_with_brush(cell,brush:Image,update_map = true,do_locks = true): 289 | #if do_locks: 290 | #map_data.lock() 291 | 292 | var x = 0 293 | var y = 0 294 | var w = brush.get_width() 295 | var h = brush.get_height() 296 | var mw = map_data.get_width() 297 | var mh = map_data.get_height() 298 | var c 299 | var p 300 | 301 | var store = plugin != null && plugin.making_action 302 | var ec = Color(0,0,0,0) 303 | var tile 304 | var col 305 | while(x < w): 306 | y = 0 307 | while(y < h): 308 | p = cell+Vector2(x,y) 309 | if p.x >= 0 && p.x < mw && p.y >= 0 && p.y < mh: 310 | c = brush.get_pixel(x,y) 311 | if c.a != 0: 312 | col = map_data.get_pixelv(p) 313 | if store: 314 | plugin.add_do_tile_action(p,col,ec) 315 | 316 | tile = Vector2(int(col.r*255),int(col.g*255)) 317 | map_data.set_pixelv(p,ec) 318 | 319 | if do_autotile && has_autotile_script && col.a != 0: 320 | var gid = autotile_tile_groups.get(tile_get_id(tile),null) 321 | if gid != null: 322 | autotile_script_instance.autotile(p,gid) 323 | y += 1 324 | x+= 1 325 | #if do_locks: 326 | #map_data.unlock() 327 | if(update_map): 328 | map.set_data(map_data) 329 | 330 | func brush_from_selection(): 331 | var brush = Image.new() 332 | 333 | var cell_rect = Rect2(cell_start,Vector2(1,1)).expand(cell_end+Vector2(1,1)) 334 | var cell = cell_rect.position 335 | 336 | #map_data.lock() 337 | 338 | 339 | var x = 0 340 | var y = 0 341 | var w = cell_rect.size.x 342 | var h = cell_rect.size.y 343 | var mw = map_data.get_width() 344 | var mh = map_data.get_height() 345 | var c = Color(0,0,0,0) 346 | var p 347 | 348 | brush.create(w,h,false,Image.FORMAT_RGBA8) 349 | brush.lock() 350 | 351 | while(x < w): 352 | y = 0 353 | while(y < h): 354 | p = cell+Vector2(x,y) 355 | if p.x >= 0 && p.x < mw && p.y >= 0 && p.y < mh: 356 | brush.set_pixel(x,y,map_data.get_pixelv(p)) 357 | y += 1 358 | x+= 1 359 | 360 | #brush.unlock() 361 | #map_data.unlock() 362 | return brush 363 | 364 | func erase_selection(): 365 | var cell_rect = Rect2(cell_start,Vector2(1,1)).expand(cell_end+Vector2(1,1)) 366 | #map_data.lock() 367 | 368 | var x = 0 369 | var y = 0 370 | var w = cell_rect.size.x 371 | var h = cell_rect.size.y 372 | var mw = map_data.get_width() 373 | var mh = map_data.get_height() 374 | var c = Color(0,0,0,0) 375 | var p 376 | var col 377 | var tile 378 | var store = plugin != null && plugin.making_action 379 | 380 | while(x < w): 381 | y = 0 382 | while(y < h): 383 | p = cell_start+Vector2(x,y) 384 | if p.x >= 0 && p.x < mw && p.y >= 0 && p.y < mh: 385 | col = map_data.get_pixelv(p) 386 | if store: 387 | plugin.add_do_tile_action(p,col,c) 388 | 389 | tile = Vector2(int(col.r*255),int(col.g*255)) 390 | map_data.set_pixelv(p,c) 391 | if do_autotile && has_autotile_script && col.a != 0: 392 | var gid = autotile_tile_groups.get(tile_get_id(tile),null) 393 | if gid != null: 394 | autotile_script_instance.autotile(p,gid) 395 | 396 | y += 1 397 | x+= 1 398 | 399 | #map_data.unlock() 400 | map.set_data(map_data) 401 | 402 | func blend_brush(cell,brush:Image,update_map = true): 403 | var x = 0 404 | var y = 0 405 | var w = brush.get_width() 406 | var h = brush.get_height() 407 | var mw = map_data.get_width() 408 | var mh = map_data.get_height() 409 | var c 410 | var p 411 | var store = plugin != null && plugin.making_action 412 | var col 413 | var tile 414 | var autotile_update_pending 415 | var autotiling = do_autotile && has_autotile_script 416 | if autotiling: 417 | autotile_update_pending = {} 418 | while(x < w): 419 | y = 0 420 | while(y < h): 421 | p = cell+Vector2(x,y) 422 | if p.x >= 0 && p.x < mw && p.y >= 0 && p.y < mh: 423 | c = brush.get_pixel(x,y) 424 | if c.a != 0: 425 | col = map_data.get_pixelv(p) 426 | if store: 427 | plugin.add_do_tile_action(p,map_data.get_pixelv(p),c) 428 | 429 | map_data.set_pixelv(p,c) 430 | tile = Vector2(int(c.r*255),int(c.g*255)) 431 | if autotiling && c.a != 0: 432 | var gid = autotile_tile_groups.get(tile_get_id(tile),null) 433 | if gid != null: 434 | autotile_script_instance.autotile(p,gid) 435 | var neighbors = autotile_script_instance.get_nearby_tiles(p,gid) 436 | for _tile in neighbors: 437 | autotile_update_pending[_tile[1]] = gid 438 | y += 1 439 | x += 1 440 | if autotiling: 441 | var keys = autotile_update_pending.keys() 442 | for _tile in keys: 443 | var gid = autotile_update_pending[_tile] 444 | if gid != null: 445 | autotile_script_instance.autotile(_tile,gid) 446 | 447 | if update_map: 448 | map.set_data(map_data) 449 | 450 | func clear_map(): 451 | if !is_instance_valid(map): 452 | return 453 | var data = Image.new() 454 | data.create(map.get_width(),map.get_height(),false,map.get_data().get_format()) 455 | map_data = data 456 | map_data.lock() 457 | map.set_data(data) 458 | 459 | func local_to_cell(global_pos): 460 | if map == null: 461 | return 462 | var pos = (global_pos/tile_size).floor() 463 | pos = Vector2(clamp(pos.x,0,map.get_width()-1),clamp(pos.y,0,map.get_height()-1)) 464 | 465 | return pos 466 | 467 | func generate_instances(parent): 468 | var ownr = parent.get_parent() 469 | var factory = instancing_script.new() as Reference 470 | 471 | #map_data.lock() 472 | 473 | var x = 0 474 | var y = 0 475 | var mw = map_data.get_width() 476 | var mh = map_data.get_height() 477 | var tile 478 | var c 479 | var node 480 | var tid 481 | var tst_w = int(tileset.get_data().get_width()/tile_size.x) 482 | var visited = {} 483 | var type = -1 484 | var _type = -1 485 | var yo = 0 486 | var xo = 0 487 | var gid = 0 488 | while(y 0 || yo > 0: 564 | node.scale.x += xo 565 | node.scale.y += yo 566 | parent.add_child(node) 567 | 568 | var childs = node.get_children() 569 | node.owner = ownr 570 | for child in childs: 571 | if child.owner == null: 572 | child.owner = ownr 573 | 574 | x += 1 575 | y += 1 576 | 577 | #map_data.unlock() 578 | 579 | func tile_get_id(tile:Vector2): 580 | var tst_w = int(tileset_data.get_width()/tile_size.x) 581 | return int(tile.y*tst_w + tile.x); 582 | 583 | #Autotile methods 584 | func clear_autitle_data(): 585 | autotile_data = {} 586 | autotile_data_val2key = {} 587 | autotile_tile_groups = {} 588 | 589 | func autotile_add_id(tile,autotile_id): 590 | var tid = tile_get_id(tile) 591 | var group_id = autotile_tile_groups.get(tid,null) 592 | if group_id == null: 593 | printerr("the tile doesn't belong to an autotile group add") 594 | return 595 | 596 | var gdata = autotile_data.get(group_id,null) 597 | if gdata == null: 598 | gdata = {} 599 | autotile_data[group_id] = gdata 600 | 601 | gdata[autotile_id] = tile 602 | 603 | var ids = autotile_data_val2key.get(tid,null) 604 | if ids == null: 605 | ids = [] 606 | autotile_data_val2key[tid] = ids 607 | if !ids.has(autotile_id): 608 | ids.append(autotile_id) 609 | 610 | func autotile_remove_id(tile,autotile_id): 611 | var tid = tile_get_id(tile) 612 | var group_id = autotile_tile_groups.get(tid,null) 613 | if group_id == null: 614 | printerr("the tile doesn't belong to an autotile group remove") 615 | var gdata = autotile_data.get(group_id,null) 616 | if gdata != null: 617 | gdata.erase(autotile_id) 618 | 619 | 620 | gdata = autotile_data_val2key.get(tid,null) 621 | if gdata == null: 622 | return 623 | var ids = gdata.get(group_id,null) 624 | if ids == null: 625 | return 626 | var pos = ids.find(autotile_id) 627 | if pos != -1: 628 | ids.remove(ids.find(autotile_id)) 629 | 630 | func autotile_tile_set_group(tile,group_id): 631 | var tid = tile_get_id(tile) 632 | #Remove tile from everything 633 | 634 | var ids = autotile_data_val2key.get(tid) 635 | if ids == null: 636 | ids = [] 637 | var groups = autotile_tile_groups.keys() 638 | for g in groups: 639 | var gdata = autotile_data.get(g,null) 640 | if gdata != null: 641 | for id in ids: 642 | gdata.erase(id) 643 | autotile_data_val2key.erase(tid) 644 | if group_id != -1: 645 | autotile_tile_groups[tid] = group_id 646 | else: 647 | autotile_tile_groups.erase(tid) 648 | 649 | func autotile_get_tile(pos): 650 | if pos.x >= 0 && pos.y >= 0 && pos.x < map_size.x && pos.y < map_size.y: 651 | var tile = map_data.get_pixelv(pos) 652 | if tile.a != 0: 653 | return Vector2(int(tile.r*255),int(tile.g*255)) 654 | 655 | return Vector2(-1,-1) 656 | 657 | func autotile_get_tile_pixel(pos): 658 | if pos.x >= 0 && pos.y >= 0 && pos.x < map_size.x && pos.y < map_size.y: 659 | var tile = map_data.get_pixelv(pos) 660 | return tile 661 | return Color(0,0,0,0) 662 | 663 | 664 | func tile_get_autotile_data(tile:Vector2): 665 | return autotile_data_val2key.get(tile_get_id(tile),null) 666 | 667 | 668 | func autotile_id_get_tile(autotile_id:int,group_id:int): 669 | var ids = autotile_data.get(group_id,null) 670 | if ids != null: 671 | return ids.get(autotile_id,Vector2(-1,-1)) 672 | return Vector2(-1,-1) 673 | 674 | #Editor cell drawing 675 | func draw_stuff(): 676 | if draw_editor_selection: 677 | var rect = Rect2(cell_start*tile_size,tile_size).expand(cell_end*tile_size+tile_size) 678 | drawer.draw_rect(rect,Color(0,0.35,0.7,0.45),true) 679 | if draw_rect_list: 680 | var rect 681 | for c in rect_list: 682 | rect = Rect2(c.position*tile_size,c.size*tile_size) 683 | drawer.draw_rect(rect,Color(0,0.35,0.7,0.45),true) 684 | -------------------------------------------------------------------------------- /addons/fabianlc_gpu_tilemap/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | 5 | const EditModePaint = 0 6 | const EditModeLine = 1 7 | const EditModeErase = 2 8 | const EditModeSelect = 3 9 | 10 | const ResizeMap = 0 11 | const ClearMap = 1 12 | const NewMap = 2 13 | const NewMapCR = 3 14 | const InstanceGen = 4 15 | 16 | const SaveBrush = 0 17 | const LoadBrush = 1 18 | const SaveMap = 2 19 | const LoadMap = 3 20 | const ExportMap = 4 21 | const ImportTiled = 5 22 | 23 | const FlipBrushH = 0 24 | const FlipBrushV = 1 25 | 26 | const MaxMapSize = 1024 #1024x1024 textures should be safe to use on old devices 27 | 28 | var tile_picker_scene = load("res://addons/fabianlc_gpu_tilemap/scenes/tilepicker.tscn") 29 | var tile_picker 30 | var resize_dialog_scene = load("res://addons/fabianlc_gpu_tilemap/scenes/resize_map_dialog.tscn") 31 | var resize_dialog 32 | var new_map_dialog_scene = load("res://addons/fabianlc_gpu_tilemap/scenes/new_map_dialog.tscn") 33 | var new_map_dialog 34 | var clear_map_dialog_scene = load("res://addons/fabianlc_gpu_tilemap/scenes/clear_map_dialog.tscn") 35 | var clear_map_dialog 36 | var import_tiled_map_scene = load("res://addons/fabianlc_gpu_tilemap/scenes/import_tiled_map_dialog.tscn") 37 | 38 | var paint_mode = EditModePaint 39 | 40 | var toolbar = null 41 | var paint_mode_option = null 42 | var tilemap:GPUTileMap = null 43 | var mouse_over:bool = false 44 | var mouse_pos = Vector2() 45 | var mouse_pressed = false 46 | var prev_mouse_cell_pos = Vector2() 47 | var options_popup:PopupMenu 48 | var selection_popup:PopupMenu 49 | var brush_popup:PopupMenu 50 | var file_popup:PopupMenu 51 | var autotile_checkbox:CheckBox 52 | var brush:Image 53 | 54 | const NoSelection = 0 55 | const Selecting = 1 56 | const Selected = 2 57 | 58 | var selection_state = 0; 59 | var selection_start_cell = Vector2() 60 | 61 | class TileAction: 62 | var cell:Vector2 63 | var prevc:Color 64 | var newc:Color 65 | func _init(tile_pos,prev_color,new_color): 66 | cell = tile_pos 67 | prevc = prev_color 68 | newc = new_color 69 | 70 | var undoredo:UndoRedo 71 | var tile_action_list = {} 72 | var making_action = false 73 | 74 | var delete_shortcut:ShortCut 75 | var copy_shortcut:ShortCut 76 | var paint_shortcut:ShortCut 77 | var erase_shortcut:ShortCut 78 | var select_shortcut:ShortCut 79 | var line_shortcut:ShortCut 80 | 81 | #var ignore_next_click = false 82 | 83 | # Called when the node enters the scene tree for the first time. 84 | func _init(): 85 | delete_shortcut = ShortCut.new() 86 | var del_key = InputEventKey.new() 87 | del_key.scancode = KEY_DELETE 88 | delete_shortcut.shortcut = del_key 89 | copy_shortcut = ShortCut.new() 90 | var copy_key = InputEventKey.new() 91 | copy_key.scancode = KEY_C 92 | copy_key.control = true 93 | copy_shortcut.shortcut = copy_key 94 | var paint_key = InputEventKey.new() 95 | paint_key.scancode = KEY_A 96 | paint_shortcut = ShortCut.new() 97 | paint_shortcut.shortcut = paint_key 98 | var erase_key = InputEventKey.new() 99 | erase_key.scancode = KEY_S 100 | erase_shortcut = ShortCut.new() 101 | erase_shortcut.shortcut = erase_key 102 | var select_key = InputEventKey.new() 103 | select_key.scancode = KEY_D 104 | select_shortcut = ShortCut.new() 105 | select_shortcut.shortcut = select_key 106 | 107 | var line_key = InputEventKey.new() 108 | line_key.scancode = KEY_F 109 | line_shortcut = ShortCut.new() 110 | line_shortcut.shortcut = line_key 111 | 112 | print("gputilemap plugin") 113 | 114 | func _enter_tree(): 115 | undoredo = get_undo_redo() 116 | toolbar = HBoxContainer.new() 117 | 118 | add_control_to_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, toolbar) 119 | toolbar.hide() 120 | 121 | new_map_dialog = new_map_dialog_scene.instance() 122 | get_editor_interface().get_base_control().add_child(new_map_dialog) 123 | new_map_dialog.connect("confirmed",self,"new_map") 124 | clear_map_dialog = clear_map_dialog_scene.instance() 125 | get_editor_interface().get_base_control().add_child(clear_map_dialog) 126 | clear_map_dialog.connect("confirmed",self,"clear_map") 127 | resize_dialog = resize_dialog_scene.instance() 128 | get_editor_interface().get_base_control().add_child(resize_dialog) 129 | resize_dialog.connect("confirmed",self,"resize_map") 130 | 131 | var lbl = Label.new() 132 | 133 | lbl.text = "autotile" 134 | toolbar.add_child(lbl) 135 | autotile_checkbox = CheckBox.new() 136 | autotile_checkbox.connect("pressed",self,"autotile_checkbox_pressed",[autotile_checkbox]) 137 | toolbar.add_child(autotile_checkbox) 138 | 139 | paint_mode_option = OptionButton.new() 140 | paint_mode_option.add_item("Paint",EditModePaint) 141 | paint_mode_option.get_popup().set_item_shortcut(paint_mode_option.get_item_index(EditModePaint),paint_shortcut) 142 | paint_mode_option.add_item("Line",EditModeLine) 143 | paint_mode_option.get_popup().set_item_shortcut(paint_mode_option.get_item_index(EditModeLine),line_shortcut) 144 | paint_mode_option.add_item("Erase",EditModeErase) 145 | paint_mode_option.get_popup().set_item_shortcut(paint_mode_option.get_item_index(EditModeErase),erase_shortcut) 146 | paint_mode_option.add_item("Select",EditModeSelect) 147 | paint_mode_option.get_popup().set_item_shortcut(paint_mode_option.get_item_index(EditModeSelect),select_shortcut) 148 | paint_mode_option.connect("item_selected",self,"paint_mode_selected") 149 | toolbar.add_child(paint_mode_option) 150 | 151 | 152 | 153 | var popup_menu = PopupMenu.new() 154 | options_popup = popup_menu 155 | popup_menu.add_item("generate instances", InstanceGen) 156 | popup_menu.add_item("resize map", ResizeMap) 157 | popup_menu.add_item("clear map",ClearMap) 158 | popup_menu.add_item("new map",NewMap) 159 | popup_menu.add_item("new map from current rect",NewMapCR) 160 | popup_menu.connect("id_pressed",self,"popup_option_selected") 161 | 162 | var tool_button = ToolButton.new() 163 | tool_button.text = "Options" 164 | tool_button.connect("pressed",self,"show_option_popup") 165 | 166 | toolbar.add_child(tool_button) 167 | 168 | tool_button.add_child(popup_menu) 169 | 170 | brush_popup = PopupMenu.new() 171 | brush_popup.add_item("Flip horizontally",FlipBrushH) 172 | brush_popup.add_item("Flip vertically", FlipBrushV) 173 | brush_popup.connect("id_pressed",self,"brush_item_selected") 174 | 175 | tool_button = ToolButton.new() 176 | tool_button.text = "Brush" 177 | tool_button.add_child(brush_popup) 178 | tool_button.connect("pressed",self,"show_brush_popup") 179 | toolbar.add_child(tool_button) 180 | 181 | popup_menu = PopupMenu.new() 182 | tool_button = ToolButton.new() 183 | file_popup = popup_menu 184 | tool_button.text = "File" 185 | tool_button.add_child(popup_menu) 186 | tool_button.connect("pressed",self,"show_file_popup") 187 | toolbar.add_child(tool_button) 188 | popup_menu.add_item("save brush",SaveBrush) 189 | popup_menu.add_item("load brush",LoadBrush) 190 | popup_menu.add_item("save map to file",SaveMap) 191 | popup_menu.add_item("load map from file",LoadMap) 192 | popup_menu.add_item("export map to image",ExportMap) 193 | popup_menu.add_item("import tiled json",ImportTiled) 194 | popup_menu.connect("id_pressed",self,"file_option_selected") 195 | 196 | 197 | popup_menu = PopupMenu.new() 198 | selection_popup = popup_menu 199 | tool_button = ToolButton.new() 200 | tool_button.text = "Selection" 201 | popup_menu.add_item("copy to brush",0) 202 | popup_menu.add_item("delete",1) 203 | popup_menu.set_item_shortcut(1,delete_shortcut) 204 | popup_menu.set_item_shortcut(0,copy_shortcut) 205 | popup_menu.connect("id_pressed",self,"selection_item_selected") 206 | tool_button.add_child(popup_menu) 207 | tool_button.connect("pressed",self,"show_selection_popup") 208 | toolbar.add_child(tool_button) 209 | 210 | tile_picker = tile_picker_scene.instance() 211 | tile_picker.plugin = self 212 | add_control_to_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_SIDE_LEFT,tile_picker) 213 | tile_picker.hide() 214 | 215 | func can_access_map(): 216 | return tilemap != null && tilemap.map != null && tilemap.tileset != null 217 | 218 | func autotile_checkbox_pressed(cb:CheckBox): 219 | if can_access_map(): 220 | tilemap.do_autotile = cb.pressed 221 | 222 | func show_brush_popup(): 223 | brush_popup.popup() 224 | brush_popup.set_global_position( brush_popup.get_parent().get_global_rect().position + Vector2(8,8)) 225 | 226 | 227 | func show_option_popup(): 228 | options_popup.popup() 229 | options_popup.set_global_position( options_popup.get_parent().get_global_rect().position + Vector2(8,8)) 230 | 231 | func show_selection_popup(): 232 | selection_popup.popup() 233 | selection_popup.set_global_position( selection_popup.get_parent().get_global_rect().position + Vector2(8,8)) 234 | 235 | func show_file_popup(): 236 | file_popup.popup() 237 | file_popup.set_global_position( file_popup.get_parent().get_global_rect().position + Vector2(8,8)) 238 | 239 | 240 | func selection_item_selected(id): 241 | if id == 0:#Copy to brush 242 | brush_from_selection() 243 | elif id == 1:#Delete 244 | delete_selection() 245 | 246 | func brush_item_selected(id): 247 | match id: 248 | FlipBrushH: 249 | flip_brush_h() 250 | FlipBrushV: 251 | flip_brush_v() 252 | 253 | func flip_brush_h(): 254 | #brush.lock() 255 | 256 | var x = 0 257 | var y = 0 258 | var w = brush.get_width() 259 | var h = brush.get_height() 260 | while(y= w: 275 | x = 0 276 | y += 1 277 | 278 | #brush.unlock() 279 | brush.flip_x() 280 | 281 | 282 | 283 | func flip_brush_v(): 284 | #brush.lock() 285 | 286 | var x = 0 287 | var y = 0 288 | var w = brush.get_width() 289 | var h = brush.get_height() 290 | while(y= w: 305 | x = 0 306 | y += 1 307 | 308 | #brush.unlock() 309 | brush.flip_y() 310 | 311 | func brush_from_selection(): 312 | if can_access_map(): 313 | brush = tilemap.brush_from_selection() 314 | brush.lock() 315 | print("Copy to brush") 316 | 317 | func _exit_tree(): 318 | clear_map_dialog.queue_free() 319 | new_map_dialog.queue_free() 320 | resize_dialog.queue_free() 321 | toolbar.queue_free() 322 | toolbar = null 323 | 324 | paint_mode_option.queue_free() 325 | paint_mode_option = null 326 | 327 | remove_control_from_container(EditorPlugin.CONTAINER_CANVAS_EDITOR_SIDE_LEFT,tile_picker) 328 | tile_picker.queue_free() 329 | tile_picker = null 330 | 331 | 332 | func edit(object): 333 | print("Edit ", object) 334 | if object != null: 335 | if tile_picker.tileset != null: 336 | tile_picker.tileset.set_tex(object.tileset) 337 | tile_picker.tileset.cell_size = object.tile_size 338 | tile_picker.tileset.set_selection(Vector2(0,0),Vector2(0,0)) 339 | tile_picker.update_plugin_brush() 340 | 341 | tilemap = object 342 | tilemap.plugin = self 343 | tilemap.tile_selector = tile_picker 344 | autotile_checkbox.pressed = tilemap.do_autotile 345 | set_process(true) 346 | print("tilemap selected") 347 | 348 | 349 | func handles(object): 350 | return object is GPUTileMap && object.map != null && object.tileset != null && object.tile_size != Vector2(0,0) 351 | 352 | func _process(delta): 353 | if can_access_map(): 354 | if tilemap.get_global_rect().has_point(tilemap.get_global_mouse_position()): 355 | mouse_over = true 356 | 357 | else: 358 | mouse_over = false 359 | 360 | if selection_state == Selecting: 361 | selection_state = Selected 362 | if paint_mode != EditModeSelect || selection_state != Selected: 363 | tilemap.draw_clear() 364 | 365 | if mouse_pressed: 366 | release_mouse() 367 | 368 | #Input handling 369 | func forward_canvas_gui_input(event): 370 | if !can_access_map(): 371 | return 372 | if !mouse_over : 373 | return false 374 | 375 | if event is InputEventMouse: 376 | var draw = false 377 | var mouse_cell_pos = tilemap.local_to_cell(tilemap.get_local_mouse_position()) 378 | if event is InputEventMouseMotion: 379 | 380 | mouse_pos = event.global_position 381 | if paint_mode == EditModeSelect: 382 | if selection_state == NoSelection && mouse_pressed: 383 | selection_state = Selecting 384 | selection_start_cell = mouse_cell_pos 385 | if selection_state == NoSelection: 386 | selection_start_cell = mouse_cell_pos 387 | if selection_state != Selected: 388 | tilemap.set_selection(selection_start_cell,mouse_cell_pos) 389 | elif paint_mode != EditModeLine || selection_state == NoSelection: 390 | selection_start_cell = mouse_cell_pos 391 | tilemap.set_selection(selection_start_cell,mouse_cell_pos) 392 | elif paint_mode == EditModeLine: 393 | tilemap.set_selection(mouse_cell_pos,mouse_cell_pos) 394 | 395 | tilemap.draw_editor_selection() 396 | elif event is InputEventMouseButton: 397 | if event.button_index == BUTTON_LEFT: 398 | mouse_pressed = event.pressed 399 | if !mouse_pressed: 400 | if making_action: 401 | if paint_mode == EditModePaint: 402 | end_undoredo("Paint tiles") 403 | elif paint_mode == EditModeErase: 404 | end_undoredo("Erase tiles") 405 | if paint_mode == EditModeSelect: 406 | if selection_state == Selecting: 407 | selection_state = Selected 408 | elif paint_mode == EditModeLine: 409 | if selection_state == Selecting: 410 | selection_state = NoSelection 411 | begin_undoredo() 412 | paint_line_no_overlap(selection_start_cell,mouse_cell_pos,false) 413 | end_undoredo("Paint line") 414 | 415 | else: 416 | if paint_mode == EditModeLine: 417 | if selection_state == NoSelection: 418 | selection_state = Selecting 419 | selection_start_cell = mouse_cell_pos 420 | tilemap.set_selection(selection_start_cell,selection_start_cell) 421 | tilemap.rect_list = [Rect2(selection_start_cell,Vector2(1,1))] 422 | tilemap.draw_rect_list() 423 | elif paint_mode == EditModeSelect: 424 | if selection_state == Selected: 425 | selection_state = NoSelection 426 | tilemap.set_selection(mouse_cell_pos,mouse_cell_pos) 427 | tilemap.draw_editor_selection() 428 | elif selection_state == NoSelection: 429 | selection_state = Selecting 430 | selection_start_cell = mouse_cell_pos 431 | if mouse_pressed: 432 | if paint_mode == EditModeErase: 433 | if !making_action: 434 | begin_undoredo() 435 | if mouse_cell_pos != prev_mouse_cell_pos: 436 | paint_line(prev_mouse_cell_pos,mouse_cell_pos,true) 437 | else: 438 | #brush.lock() 439 | tilemap.erase_with_brush(mouse_cell_pos,brush) 440 | #brush.unlock() 441 | 442 | elif paint_mode == EditModePaint: 443 | if !making_action: 444 | begin_undoredo() 445 | if mouse_cell_pos != prev_mouse_cell_pos: 446 | paint_line(prev_mouse_cell_pos,mouse_cell_pos,false) 447 | else: 448 | #brush.lock() 449 | tilemap.blend_brush(mouse_cell_pos,brush) 450 | #brush.unlock() 451 | 452 | prev_mouse_cell_pos = tilemap.local_to_cell(tilemap.get_local_mouse_position()) 453 | return true 454 | prev_mouse_cell_pos = tilemap.local_to_cell(tilemap.get_local_mouse_position()) 455 | return false 456 | 457 | #Keyboard shortcuts 458 | if event is InputEventKey: 459 | if !mouse_over || !event.pressed: 460 | return 461 | 462 | if delete_shortcut.is_shortcut(event): 463 | delete_selection() 464 | return true 465 | elif copy_shortcut.is_shortcut(event): 466 | brush_from_selection() 467 | return true 468 | elif select_shortcut.is_shortcut(event): 469 | paint_mode_option.select(paint_mode_option.get_item_index(EditModeSelect)) 470 | paint_mode_selected(EditModeSelect) 471 | return true 472 | elif line_shortcut.is_shortcut(event): 473 | paint_mode_option.select(paint_mode_option.get_item_index(EditModeLine)) 474 | paint_mode_selected(EditModeLine) 475 | return true 476 | elif paint_shortcut.is_shortcut(event): 477 | paint_mode_option.select(paint_mode_option.get_item_index(EditModePaint)) 478 | paint_mode_selected(EditModePaint) 479 | return true 480 | elif erase_shortcut.is_shortcut(event): 481 | paint_mode_option.select(paint_mode_option.get_item_index(EditModeErase)) 482 | paint_mode_selected(EditModeErase) 483 | return true 484 | func delete_selection(): 485 | if !can_access_map(): 486 | return 487 | if paint_mode != EditModeSelect || selection_state != Selected: 488 | return 489 | begin_undoredo() 490 | tilemap.erase_selection() 491 | end_undoredo("Erase selection") 492 | 493 | func do_tile_action(tile_actions): 494 | if making_action: 495 | return 496 | if tilemap.map == null: 497 | return 498 | print("do") 499 | var vals = tile_actions.values() 500 | #tilemap.map_data.lock() 501 | for action in vals: 502 | tilemap.put_tile_pixel(action.cell,action.newc,false) 503 | #tilemap.map_data.unlock() 504 | tilemap.map.set_data(tilemap.map_data) 505 | 506 | func undo_tile_action(tile_actions): 507 | if making_action: 508 | return 509 | print("undo") 510 | var vals = tile_actions.values() 511 | #tilemap.map_data.lock() 512 | for action in vals: 513 | tilemap.put_tile_pixel(action.cell,action.prevc,false) 514 | #tilemap.map_data.unlock() 515 | tilemap.map.set_data(tilemap.map_data) 516 | 517 | func begin_undoredo(): 518 | making_action = true 519 | tile_action_list = {} 520 | 521 | func end_undoredo(action): 522 | if tile_action_list.empty(): 523 | return 524 | undoredo.create_action(action,UndoRedo.MERGE_DISABLE)#Batch undoing is handled manually to make sure things work as I want 525 | undoredo.add_do_method(self,"do_tile_action",tile_action_list) 526 | undoredo.add_undo_method(self,"undo_tile_action",tile_action_list) 527 | undoredo.commit_action() 528 | making_action = false 529 | 530 | #Used to undo actions 531 | func add_do_tile_action(cell,prev_color,new_color): 532 | var key = cell.y*tilemap.map.get_width() + cell.x 533 | if tile_action_list.has(key): 534 | var act = tile_action_list[key] 535 | if act.newc != new_color: 536 | act.newc = new_color 537 | else: 538 | tile_action_list[key] = TileAction.new(cell,prev_color,new_color) 539 | 540 | func file_option_selected(id): 541 | match id: 542 | SaveMap: 543 | save_map() 544 | ExportMap: 545 | export_map() 546 | SaveBrush: 547 | save_brush() 548 | LoadBrush: 549 | load_brush() 550 | LoadMap: 551 | load_map() 552 | ImportTiled: 553 | import_tiled_map() 554 | 555 | func popup_option_selected(id): 556 | if mouse_pressed: 557 | release_mouse() 558 | match id: 559 | ResizeMap: 560 | resize_map_dialog() 561 | ClearMap: 562 | clear_map_dialog.popup_centered() 563 | NewMap: 564 | new_map_dialog() 565 | NewMapCR: 566 | new_map_cr() 567 | InstanceGen: 568 | generate_instances() 569 | 570 | func generate_instances(): 571 | if tilemap.instancing_script == null: 572 | var alert = WindowDialog.new() 573 | alert.window_title = "Please set the instancing script" 574 | get_editor_interface().get_base_control().add_child(alert) 575 | alert.popup_exclusive = true 576 | alert.rect_min_size = Vector2(160,100) 577 | alert.popup_centered() 578 | yield(alert,"popup_hide") 579 | alert.queue_free() 580 | return 581 | 582 | var scene_root = get_editor_interface().get_edited_scene_root() 583 | 584 | var node_visible = true 585 | var node_folded = true 586 | 587 | if tilemap.auto_free_instances_node: 588 | var old_node = scene_root.get_node_or_null(tilemap.instances_node_name) 589 | if is_instance_valid(old_node): 590 | if old_node is CanvasItem: 591 | node_visible = old_node.visible 592 | node_folded = old_node.is_displayed_folded() 593 | old_node.free() 594 | 595 | var new_node = Node2D.new() 596 | new_node.name = tilemap.instances_node_name 597 | scene_root.add_child(new_node) 598 | new_node.owner = scene_root 599 | new_node.set_display_folded(node_folded) 600 | new_node.visible = node_visible 601 | 602 | tilemap.generate_instances(new_node) 603 | 604 | func export_map(): 605 | if !can_access_map(): 606 | printerr("can't access tilemap'") 607 | return 608 | var dialog = FileDialog.new() 609 | dialog.add_filter("*.png") 610 | dialog.access = FileDialog.ACCESS_RESOURCES 611 | dialog.mode = FileDialog.MODE_SAVE_FILE 612 | get_editor_interface().add_child(dialog) 613 | dialog.popup_exclusive = true 614 | dialog.popup_centered_ratio() 615 | dialog.connect("file_selected",self,"save_dialog_confirmed",[dialog]) 616 | yield(dialog,"popup_hide") 617 | if dialog.has_meta("confirmed"): 618 | var region_start = Vector2(0,0) 619 | var region_end = Vector2(tilemap.map.get_width()-1,tilemap.map.get_height()-1) 620 | var img:ImageTexture = tilemap.get_map_region_as_texture(region_start,region_end) 621 | if img == null: 622 | print("couldn't make image fro map") 623 | else: 624 | var data = img.get_data() 625 | if data != null && !data.is_empty(): 626 | var err = data.save_png(dialog.current_path) 627 | if err != OK: 628 | printerr(err) 629 | else: 630 | print("image is empty") 631 | 632 | 633 | dialog.queue_free() 634 | 635 | 636 | 637 | 638 | func save_map(): 639 | var dialog = FileDialog.new() 640 | dialog.add_filter("*.png") 641 | dialog.access = FileDialog.ACCESS_RESOURCES 642 | dialog.mode = FileDialog.MODE_SAVE_FILE 643 | get_editor_interface().add_child(dialog) 644 | dialog.popup_exclusive = true 645 | dialog.popup_centered_ratio() 646 | dialog.connect("file_selected",self,"save_dialog_confirmed",[dialog]) 647 | yield(dialog,"popup_hide") 648 | if dialog.has_meta("confirmed"): 649 | var img = tilemap.map.get_data() 650 | var err = img.save_png(dialog.current_path) 651 | if err != OK: 652 | printerr(err) 653 | dialog.queue_free() 654 | 655 | func save_brush(): 656 | if brush == null: 657 | return 658 | var dialog = FileDialog.new() 659 | dialog.add_filter("*.png") 660 | dialog.access = FileDialog.ACCESS_RESOURCES 661 | dialog.mode = FileDialog.MODE_SAVE_FILE 662 | get_editor_interface().add_child(dialog) 663 | dialog.popup_exclusive = true 664 | dialog.popup_centered_ratio() 665 | dialog.connect("file_selected",self,"save_dialog_confirmed",[dialog]) 666 | yield(dialog,"popup_hide") 667 | if dialog.has_meta("confirmed"): 668 | var img = brush 669 | var err = img.save_png(dialog.current_path) 670 | if err != OK: 671 | printerr(err) 672 | dialog.queue_free() 673 | 674 | func load_brush(): 675 | var dialog = FileDialog.new() 676 | dialog.add_filter("*.png") 677 | dialog.access = FileDialog.ACCESS_RESOURCES 678 | dialog.mode = FileDialog.MODE_OPEN_FILE 679 | get_editor_interface().add_child(dialog) 680 | dialog.popup_exclusive = true 681 | dialog.popup_centered_ratio() 682 | dialog.connect("file_selected",self,"save_dialog_confirmed",[dialog]) 683 | yield(dialog,"popup_hide") 684 | if dialog.has_meta("confirmed"): 685 | var img = Image.new() 686 | var err = img.load(dialog.current_path) 687 | if err != OK: 688 | printerr(err) 689 | else: 690 | brush = img 691 | brush.lock() 692 | dialog.queue_free() 693 | 694 | func load_map(): 695 | var dialog = FileDialog.new() 696 | dialog.add_filter("*.png") 697 | dialog.access = FileDialog.ACCESS_RESOURCES 698 | dialog.mode = FileDialog.MODE_OPEN_FILE 699 | get_editor_interface().add_child(dialog) 700 | dialog.popup_exclusive = true 701 | dialog.popup_centered_ratio() 702 | dialog.connect("file_selected",self,"save_dialog_confirmed",[dialog]) 703 | yield(dialog,"popup_hide") 704 | if dialog.has_meta("confirmed"): 705 | var img = Image.new() 706 | var err = img.load(dialog.current_path) 707 | if err != OK: 708 | printerr(err) 709 | else: 710 | var tex = ImageTexture.new() 711 | tex.create_from_image(img,0) 712 | tilemap.set_map_texture(tex) 713 | dialog.queue_free() 714 | 715 | func import_tiled_map(): 716 | var diag = import_tiled_map_scene.instance() 717 | 718 | get_editor_interface().get_base_control().add_child(diag) 719 | diag.popup_centered() 720 | diag.plugin = self 721 | yield(diag,"popup_hide") 722 | diag.queue_free() 723 | print("dialog closed") 724 | 725 | func save_dialog_confirmed(path,dialog): 726 | dialog.set_meta("confirmed",true) 727 | 728 | func release_mouse(): 729 | mouse_pressed = false 730 | if making_action: 731 | commit_action() 732 | if paint_mode == EditModePaint: 733 | selection_state = Selected 734 | elif paint_mode == EditModeLine: 735 | selection_state = NoSelection 736 | 737 | func commit_action(): 738 | match(paint_mode): 739 | EditModePaint: 740 | end_undoredo("Paint tiles") 741 | EditModeLine: 742 | end_undoredo("Paint line") 743 | EditModeErase: 744 | end_undoredo("Erase tiles") 745 | 746 | func clear_map(): 747 | tilemap.clear_map() 748 | 749 | func new_map_dialog(): 750 | if !is_instance_valid(tilemap): 751 | return 752 | var spin_w:SpinBox = new_map_dialog.get_node("V/H/Width") 753 | var spin_h:SpinBox = new_map_dialog.get_node("V/H/Height") 754 | spin_w.max_value = MaxMapSize 755 | spin_h.max_value = MaxMapSize 756 | spin_w.min_value = 0 757 | spin_h.min_value = 0 758 | spin_h.value = tilemap.map.get_height() 759 | spin_w.value = tilemap.map.get_width() 760 | new_map_dialog.popup_centered() 761 | 762 | func new_map(): 763 | var spin_w:SpinBox = new_map_dialog.get_node("V/H/Width") 764 | var spin_h:SpinBox = new_map_dialog.get_node("V/H/Height") 765 | var w = spin_w.value 766 | var h = spin_h.value 767 | 768 | var img = Image.new() 769 | img.create(w,h,false,Image.FORMAT_RGBA8) 770 | var tex = ImageTexture.new() 771 | tex.create_from_image(img,0) 772 | tilemap.set_map_texture(tex) 773 | 774 | func new_map_cr(): 775 | var img = Image.new() 776 | img.create(int(tilemap.rect_size.x/tilemap.tile_size.x),int(tilemap.rect_size.y/tilemap.tile_size.y),false,Image.FORMAT_RGBA8) 777 | var tex = ImageTexture.new() 778 | tex.create_from_image(img,0) 779 | tilemap.set_map_texture(tex) 780 | 781 | func resize_map_dialog(): 782 | if !is_instance_valid(tilemap): 783 | return 784 | var spin_w:SpinBox = resize_dialog.get_node("V/H/Width") 785 | var spin_h:SpinBox = resize_dialog.get_node("V/H/Height") 786 | spin_w.max_value = MaxMapSize 787 | spin_h.max_value = MaxMapSize 788 | spin_w.min_value = 0 789 | spin_h.min_value = 0 790 | spin_h.value = tilemap.map.get_height() 791 | spin_w.value = tilemap.map.get_width() 792 | resize_dialog.popup_centered() 793 | 794 | func resize_map(): 795 | var spin_w:SpinBox = resize_dialog.get_node("V/H/Width") 796 | var spin_h:SpinBox = resize_dialog.get_node("V/H/Height") 797 | var w = spin_w.value 798 | var h = spin_h.value 799 | var prev_img = tilemap.map.get_data() 800 | var img = Image.new() 801 | var alignment = resize_dialog.get_node("V/GridContainer").get_alignment() as Vector2 802 | var prev_rect = tilemap.get_rect() 803 | img.create(w,h,false,Image.FORMAT_RGBA8) 804 | img.lock() 805 | #prev_img.lock() 806 | var x = 0 807 | var y = 0 808 | var prev_tex_size = tilemap.map.get_size() 809 | var src_rect = Rect2(Vector2(0,0),prev_tex_size) 810 | var dest_pos = Vector2(0,0) 811 | var new_size = Vector2(w,h)*tilemap.tile_size 812 | 813 | match int(alignment.x): 814 | 0: 815 | pass 816 | 1: 817 | dest_pos.x -= (prev_tex_size.x-w)*0.5 818 | 2: 819 | dest_pos.x -= prev_tex_size.x- w 820 | 821 | match int(alignment.y): 822 | 0: 823 | pass 824 | 1: 825 | dest_pos.y -= (prev_tex_size.y - h)*0.5 826 | 2: 827 | dest_pos.y -= prev_tex_size.y-h 828 | 829 | img.blit_rect(prev_img,src_rect,dest_pos) 830 | 831 | #prev_img.unlock() 832 | #img.unlock() 833 | 834 | var tex = ImageTexture.new() 835 | tex.create_from_image(img,0) 836 | #tilemap.set_map_texture(null) 837 | #tilemap.call_deferred("set_map_texture",tex) 838 | tilemap.set_map_texture(tex) 839 | print(alignment) 840 | match int(alignment.x): 841 | 0: 842 | pass 843 | 1: 844 | tilemap.rect_position.x += (tilemap.rect_position.x+prev_rect.size.x*0.5)-(tilemap.rect_position.x+tilemap.rect_size.x*0.5) 845 | 2: 846 | tilemap.rect_position.x += (tilemap.rect_position.x+prev_rect.size.x)-(tilemap.rect_position.x+tilemap.rect_size.x) 847 | 848 | match int(alignment.y): 849 | 0: 850 | pass 851 | 1: 852 | tilemap.rect_position.y += (prev_rect.size.y - tilemap.rect_size.y)*0.5 853 | 2: 854 | tilemap.rect_position.y += (tilemap.rect_position.y+prev_rect.size.y)-(tilemap.rect_position.y+tilemap.rect_size.y) 855 | 856 | 857 | 858 | 859 | func paint_line(start,end,erase = false): 860 | var x0 = start.x 861 | var y0 = start.y 862 | var x1 = end.x 863 | var y1 = end.y 864 | var dx = abs(x1 - x0) 865 | var dy = abs(y1 - y0) 866 | var sx 867 | if (x0 < x1): 868 | sx = 1 869 | else: 870 | sx = -1 871 | var sy 872 | if (y0 < y1): 873 | sy = 1 874 | else: 875 | sy = -1 876 | var err = dx - dy; 877 | 878 | #brush.lock() 879 | #tilemap.map_data.lock() 880 | if !erase: 881 | while(true): 882 | tilemap.blend_brush(Vector2(x0,y0),brush,false) 883 | if ((x0 == x1) && (y0 == y1)): 884 | break; 885 | var e2 = 2*err; 886 | if (e2 > -dy): 887 | err -= dy; 888 | x0 += sx 889 | if (e2 < dx): 890 | err += dx 891 | y0 += sy 892 | else: 893 | while(true): 894 | tilemap.erase_with_brush(Vector2(x0,y0),brush,false) 895 | if ((x0 == x1) && (y0 == y1)): 896 | break; 897 | var e2 = 2*err; 898 | if (e2 > -dy): 899 | err -= dy; 900 | x0 += sx 901 | if (e2 < dx): 902 | err += dx 903 | y0 += sy 904 | 905 | #brush.unlock() 906 | #tilemap.map_data.unlock() 907 | tilemap.map.set_data(tilemap.map_data) 908 | 909 | func paint_line_no_overlap(start,end,erase = false): 910 | var brw = brush.get_width() 911 | var brh = brush.get_height() 912 | #end = end-start 913 | #start = start-start 914 | end = Vector2(floor(end.x/brw),floor(end.y/brh)) 915 | start = Vector2(floor(start.x/brw),floor(start.y/brh)) 916 | 917 | var x0 = start.x 918 | var y0 = start.y 919 | var x1 = end.x 920 | var y1 = end.y 921 | var dx = abs(x1 - x0) 922 | var dy = abs(y1 - y0) 923 | var sx 924 | if (x0 < x1): 925 | sx = 1 926 | else: 927 | sx = -1 928 | var sy 929 | if (y0 < y1): 930 | sy = 1 931 | else: 932 | sy = -1 933 | var err = dx - dy; 934 | 935 | #brush.lock() 936 | #tilemap.map_data.lock() 937 | if !erase: 938 | while(true): 939 | tilemap.blend_brush(Vector2(x0*brw,y0*brh),brush,false) 940 | if ((x0 == x1) && (y0 == y1)): 941 | break; 942 | var e2 = 2*err; 943 | if (e2 > -dy): 944 | err -= dy; 945 | x0 += sx 946 | if (e2 < dx): 947 | err += dx 948 | y0 += sy 949 | else: 950 | while(true): 951 | tilemap.erase_with_brush(Vector2(x0,y0),brush,false) 952 | if ((x0 == x1) && (y0 == y1)): 953 | break; 954 | var e2 = 2*err; 955 | if (e2 > -dy): 956 | err -= dy; 957 | x0 += sx 958 | if (e2 < dx): 959 | err += dx 960 | y0 += sy 961 | 962 | #brush.unlock() 963 | #tilemap.map_data.unlock() 964 | tilemap.map.set_data(tilemap.map_data) 965 | 966 | 967 | func make_visible(v): 968 | if is_instance_valid(toolbar): 969 | if v: 970 | toolbar.show() 971 | tile_picker.set_process(true) 972 | tile_picker.show() 973 | else: 974 | if is_instance_valid(tilemap): 975 | tilemap.draw_clear() 976 | tilemap.tile_selector = null 977 | tilemap.plugin = null 978 | tilemap = null 979 | set_process(false) 980 | toolbar.hide() 981 | tile_picker.hide() 982 | tile_picker.set_process(false) 983 | 984 | func paint_mode_selected(id): 985 | release_mouse() 986 | selection_state = NoSelection 987 | tilemap.draw_clear() 988 | paint_mode = clamp(id,0,3) 989 | --------------------------------------------------------------------------------