Compare commits
11 Commits
955e1b187b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
1fe75a871e
|
|||
| 191235da54 | |||
| 69c3e6f396 | |||
| f87cc28c57 | |||
| 283e3cbb5a | |||
| ea63279633 | |||
| 21a25c07a8 | |||
| 6d3652aa84 | |||
| bdaf2901db | |||
| 9dc7f22123 | |||
| b44ff2b09d |
70
addons/TileMapDual/AtlasWatcher.gd
Normal file
70
addons/TileMapDual/AtlasWatcher.gd
Normal file
@@ -0,0 +1,70 @@
|
||||
##[br] Watches a TileSetAtlasSource for changes.
|
||||
##[br] Causes its 'parent' TileSetWatcher to emit terrains_changed when the atlas changes.
|
||||
##[br] Also emits parent.atlas_autotiled when it thinks the user auto-generated atlas tiles.
|
||||
class_name AtlasWatcher
|
||||
|
||||
## Prevents the number of seen atlases from extending to infinity.
|
||||
const UNDO_LIMIT = 1024
|
||||
## Stores all of the atlas instance id's that have been seen before, to prevent autogen on redo.
|
||||
static var _registered_atlases := []
|
||||
|
||||
## The TileSetWatcher that created this AtlasWatcher. Used to send signals back.
|
||||
var parent: TileSetWatcher
|
||||
|
||||
## The Source ID of `self.atlas`.
|
||||
var sid: int
|
||||
|
||||
## The atlas to be watched for changes.
|
||||
var atlas: TileSetAtlasSource
|
||||
|
||||
func _init(parent: TileSetWatcher, sid: int, atlas: TileSetAtlasSource) -> void:
|
||||
self.parent = parent
|
||||
self.sid = sid
|
||||
self.atlas = atlas
|
||||
atlas.changed.connect(_atlas_changed, ConnectFlags.CONNECT_DEFERRED)
|
||||
var id := atlas.get_instance_id()
|
||||
# should not autogen if atlas was created through redo, i.e. its instance id already existed
|
||||
if _atlas_is_empty() and id not in _registered_atlases:
|
||||
_registered_atlases.push_back(id)
|
||||
if _registered_atlases.size() > UNDO_LIMIT:
|
||||
_registered_atlases.pop_front()
|
||||
atlas.changed.connect(_detect_autogen, ConnectFlags.CONNECT_DEFERRED | ConnectFlags.CONNECT_ONE_SHOT)
|
||||
|
||||
|
||||
func _atlas_is_empty() -> bool:
|
||||
return atlas.get_tiles_count() == 0
|
||||
|
||||
|
||||
## Returns true if the texture has any opaque pixels in the specified tile coordinates.
|
||||
func _is_opaque_tile(image: Image, tile: Vector2i, p_threshold: float = 0.1) -> bool:
|
||||
# We cannot use atlas.get_tile_texture_region(tile) as it fails on unregistered tiles.
|
||||
var region := Rect2i(tile * atlas.texture_region_size, atlas.texture_region_size)
|
||||
var sprite := image.get_region(region)
|
||||
if sprite.is_invisible():
|
||||
return false
|
||||
# We're still not sure if the tile is empty or not.
|
||||
# Godot's auto-gen considers 0.1 opacity as "transparent" but not "invisible".
|
||||
for y in range(region.position.y, region.end.y):
|
||||
for x in range(region.position.x, region.end.x):
|
||||
if image.get_pixel(x, y).a > p_threshold:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
##[br] HACK: literally just tries to guess which tiles the terrain autogen system would make
|
||||
##[br] Called once, and only once, at the end of the first frame that a texture is created.
|
||||
func _detect_autogen() -> void:
|
||||
var size := Vector2i(atlas.texture.get_size()) / atlas.texture_region_size
|
||||
var image := atlas.texture.get_image()
|
||||
var expected_tiles := []
|
||||
for y in size.y:
|
||||
for x in size.x:
|
||||
var tile := Vector2i(x, y)
|
||||
if atlas.has_tile(tile) != _is_opaque_tile(image, tile):
|
||||
return
|
||||
parent.atlas_autotiled.emit(sid, atlas)
|
||||
|
||||
|
||||
## Called every time the atlas changes. Simply flags that terrains have changed.
|
||||
func _atlas_changed() -> void:
|
||||
parent._flag_terrains_changed = true
|
||||
1
addons/TileMapDual/AtlasWatcher.gd.uid
Normal file
1
addons/TileMapDual/AtlasWatcher.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cfhiw77a85x8w
|
||||
41
addons/TileMapDual/CursorDual.gd
Normal file
41
addons/TileMapDual/CursorDual.gd
Normal file
@@ -0,0 +1,41 @@
|
||||
@icon('CursorDual.svg')
|
||||
class_name CursorDual
|
||||
extends Sprite2D
|
||||
|
||||
# TODO: instead of setting the target tilemap manually,
|
||||
# just add the CursorDual as a child of the target tilemap
|
||||
@export var tilemap_dual: TileMapDual = null
|
||||
|
||||
var cell: Vector2i
|
||||
var tile_size: Vector2
|
||||
var sprite_size: Vector2
|
||||
var terrain := 1
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if tilemap_dual != null:
|
||||
tile_size = tilemap_dual.tile_set.tile_size
|
||||
sprite_size = self.texture.get_size()
|
||||
scale = Vector2(tile_size.y, tile_size.y) / sprite_size
|
||||
self.set_scale(scale)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if tilemap_dual == null:
|
||||
return
|
||||
cell = tilemap_dual.local_to_map(tilemap_dual.get_local_mouse_position())
|
||||
global_position = tilemap_dual.to_global(tilemap_dual.map_to_local(cell))
|
||||
# Clicking the 1 key activates the first terrain
|
||||
if Input.is_action_pressed("quick_action_1"):
|
||||
terrain = 1
|
||||
# Clicking the 2 key activates the second terrain
|
||||
if Input.is_action_pressed("quick_action_2"):
|
||||
terrain = 2
|
||||
# Clicking the 0 key activates the background terrain
|
||||
if Input.is_action_pressed("quick_action_0"):
|
||||
terrain = 0
|
||||
|
||||
if Input.is_action_pressed("left_click"):
|
||||
tilemap_dual.draw_cell(cell, terrain)
|
||||
elif Input.is_action_pressed("right_click"):
|
||||
tilemap_dual.draw_cell(cell, 0)
|
||||
1
addons/TileMapDual/CursorDual.gd.uid
Normal file
1
addons/TileMapDual/CursorDual.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c6m630v880okx
|
||||
49
addons/TileMapDual/CursorDual.svg
Normal file
49
addons/TileMapDual/CursorDual.svg
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="CursorDual.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#999999"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="35.5"
|
||||
inkscape:cx="9.0845069"
|
||||
inkscape:cy="8.112676"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="736"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 8.9882813,2.0703125 V 4.046875 h 2.9648437 v 2.9648437 h 1.976563 V 2.0703125 Z"
|
||||
style="fill:#ffb273;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="path10" />
|
||||
<path
|
||||
d="m 8.9882813,11.953125 v 1.976563 H 13.929688 V 8.9882813 h -1.976563 v 2.9648437 z"
|
||||
style="fill:#ffb273;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="path9" />
|
||||
<path
|
||||
d="M 7.0117187,13.929688 V 11.953125 H 4.046875 V 8.9882813 H 2.0703125 v 4.9414067 z"
|
||||
style="fill:#ffb273;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="path8" />
|
||||
<path
|
||||
d="M 2.0703125,7.0117187 H 4.046875 V 4.046875 H 7.0117187 V 2.0703125 H 2.0703125 Z"
|
||||
style="fill:#ffb273;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="path7" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
43
addons/TileMapDual/CursorDual.svg.import
Normal file
43
addons/TileMapDual/CursorDual.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bth8306ui4dcx"
|
||||
path="res://.godot/imported/CursorDual.svg-af24e4bad2d22f0cc5627c83d35ae307.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TileMapDual/CursorDual.svg"
|
||||
dest_files=["res://.godot/imported/CursorDual.svg-af24e4bad2d22f0cc5627c83d35ae307.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
151
addons/TileMapDual/Display.gd
Normal file
151
addons/TileMapDual/Display.gd
Normal file
@@ -0,0 +1,151 @@
|
||||
##[br] A Node designed to hold and manage up to 2 DisplayLayer children.
|
||||
##[br] See DisplayLayer.gd for details.
|
||||
class_name Display
|
||||
extends Node2D
|
||||
|
||||
|
||||
## See TerrainDual.gd
|
||||
var terrain: TerrainDual
|
||||
## See TileSetWatcher.gd
|
||||
var _tileset_watcher: TileSetWatcher
|
||||
## The parent TileMapDual to base the terrains off of.
|
||||
@export var world: TileMapDual
|
||||
## Creates a new Display that updates when the TileSet updates.
|
||||
func _init(world: TileMapDual, tileset_watcher: TileSetWatcher) -> void:
|
||||
#print('initializing Display...')
|
||||
self.world = world
|
||||
_tileset_watcher = tileset_watcher
|
||||
terrain = TerrainDual.new(tileset_watcher)
|
||||
terrain.changed.connect(_terrain_changed, 1)
|
||||
world_tiles_changed.connect(_world_tiles_changed, 1)
|
||||
# let parent materal through to the displaylayers
|
||||
use_parent_material = true
|
||||
|
||||
|
||||
## Activates when the TerrainDual changes.
|
||||
func _terrain_changed() -> void:
|
||||
cached_cells.update(world)
|
||||
_delete_layers()
|
||||
if _tileset_watcher.tile_set != null:
|
||||
_create_layers()
|
||||
|
||||
|
||||
## Emitted when the tiles in the map have been edited.
|
||||
signal world_tiles_changed(changed: Array)
|
||||
func _world_tiles_changed(changed: Array) -> void:
|
||||
#print('SIGNAL EMITTED: world_tiles_changed(%s)' % {'changed': changed})
|
||||
for child in get_children(true):
|
||||
child.update_tiles(cached_cells, changed)
|
||||
|
||||
|
||||
## Initializes and configures new DisplayLayers according to the grid shape.
|
||||
func _create_layers() -> void:
|
||||
#print('GRID SHAPE: %s' % _tileset_watcher.grid_shape)
|
||||
var grid: Array = GRIDS[_tileset_watcher.grid_shape]
|
||||
for i in grid.size():
|
||||
var layer_config: Dictionary = grid[i]
|
||||
#print('layer_config: %s' % layer_config)
|
||||
var layer := DisplayLayer.new(world, _tileset_watcher, layer_config, terrain.layers[i])
|
||||
add_child(layer)
|
||||
layer.update_tiles_all(cached_cells)
|
||||
|
||||
|
||||
## Deletes all of the DisplayLayers.
|
||||
func _delete_layers() -> void:
|
||||
for child in get_children(true):
|
||||
child.queue_free()
|
||||
|
||||
|
||||
## The TileCache computed from the last time update() was called.
|
||||
var cached_cells := TileCache.new()
|
||||
## Updates the display based on the cells changed in the world TileMapDual.
|
||||
func update(updated: Array) -> void:
|
||||
if _tileset_watcher.tile_set == null:
|
||||
return
|
||||
_update_properties()
|
||||
if not updated.is_empty():
|
||||
cached_cells.update(world, updated)
|
||||
world_tiles_changed.emit(updated)
|
||||
|
||||
|
||||
## Activates when the properties of the parent TileMapDual have been edited.
|
||||
func _update_properties() -> void:
|
||||
for child in get_children(true):
|
||||
child.update_properties(world)
|
||||
|
||||
|
||||
# TODO: phase out GridShape and simply transpose everything when the offset axis is vertical
|
||||
##[br] Returns what kind of grid a TileSet is.
|
||||
##[br] Will default to SQUARE if Godot decides to add a new TileShape.
|
||||
static func tileset_gridshape(tile_set: TileSet) -> GridShape:
|
||||
var hori: bool = tile_set.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL
|
||||
match tile_set.tile_shape:
|
||||
TileSet.TileShape.TILE_SHAPE_SQUARE:
|
||||
return GridShape.SQUARE
|
||||
TileSet.TileShape.TILE_SHAPE_ISOMETRIC:
|
||||
return GridShape.ISO
|
||||
TileSet.TileShape.TILE_SHAPE_HALF_OFFSET_SQUARE:
|
||||
return GridShape.HALF_OFF_HORI if hori else GridShape.HALF_OFF_VERT
|
||||
TileSet.TileShape.TILE_SHAPE_HEXAGON:
|
||||
return GridShape.HEX_HORI if hori else GridShape.HEX_VERT
|
||||
_:
|
||||
return GridShape.SQUARE
|
||||
|
||||
|
||||
## Every meaningfully different TileSet.tile_shape * TileSet.tile_offset_axis combination.
|
||||
enum GridShape {
|
||||
SQUARE,
|
||||
ISO,
|
||||
HALF_OFF_HORI,
|
||||
HALF_OFF_VERT,
|
||||
HEX_HORI,
|
||||
HEX_VERT,
|
||||
}
|
||||
|
||||
|
||||
##[br] How to deal with every available GridShape.
|
||||
##[br] See DisplayLayer.gd for more information about these fields.
|
||||
const GRIDS: Dictionary = {
|
||||
GridShape.SQUARE: [
|
||||
{ # []
|
||||
'offset': Vector2(-0.5, -0.5),
|
||||
}
|
||||
],
|
||||
GridShape.ISO: [
|
||||
{ # <>
|
||||
'offset': Vector2(0, -0.5),
|
||||
}
|
||||
],
|
||||
GridShape.HALF_OFF_HORI: [
|
||||
{ # v
|
||||
'offset': Vector2(0.0, -0.5),
|
||||
},
|
||||
{ # ^
|
||||
'offset': Vector2(-0.5, -0.5),
|
||||
},
|
||||
],
|
||||
GridShape.HALF_OFF_VERT: [
|
||||
{ # >
|
||||
'offset': Vector2(-0.5, 0.0),
|
||||
},
|
||||
{ # <
|
||||
'offset': Vector2(-0.5, -0.5),
|
||||
},
|
||||
],
|
||||
GridShape.HEX_HORI: [
|
||||
{ # v
|
||||
'offset': Vector2(0.0, -3.0 / 8.0),
|
||||
},
|
||||
{ # ^
|
||||
'offset': Vector2(-0.5, -3.0 / 8.0),
|
||||
},
|
||||
],
|
||||
GridShape.HEX_VERT: [
|
||||
{ # >
|
||||
'offset': Vector2(-3.0 / 8.0, 0.0),
|
||||
},
|
||||
{ # <
|
||||
'offset': Vector2(-3.0 / 8.0, -0.5),
|
||||
},
|
||||
],
|
||||
}
|
||||
1
addons/TileMapDual/Display.gd.uid
Normal file
1
addons/TileMapDual/Display.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cacp8xe0jaxln
|
||||
102
addons/TileMapDual/DisplayLayer.gd
Normal file
102
addons/TileMapDual/DisplayLayer.gd
Normal file
@@ -0,0 +1,102 @@
|
||||
##[br] A single TileMapLayer whose purpose is to display tiles to maintain the Dual Grid illusion.
|
||||
##[br] Its contents are automatically computed and updated based on:
|
||||
##[br] - the contents of the parent TileMapDual
|
||||
##[br] - the rules set in its assigned TerrainLayer
|
||||
class_name DisplayLayer
|
||||
extends TileMapLayer
|
||||
|
||||
|
||||
##[br] How much to offset this DisplayLayer relative to the main TileMapDual grid.
|
||||
##[br] This is independent of tile size.
|
||||
var offset: Vector2
|
||||
|
||||
## See TileSetWatcher.gd
|
||||
var _tileset_watcher: TileSetWatcher
|
||||
|
||||
## See TerrainDual.gd
|
||||
var _terrain: TerrainLayer
|
||||
|
||||
func _init(
|
||||
world: TileMapDual,
|
||||
tileset_watcher: TileSetWatcher,
|
||||
fields: Dictionary,
|
||||
layer: TerrainLayer
|
||||
) -> void:
|
||||
#print('initializing Layer...')
|
||||
update_properties(world)
|
||||
offset = fields.offset
|
||||
_tileset_watcher = tileset_watcher
|
||||
_terrain = layer
|
||||
tile_set = tileset_watcher.tile_set
|
||||
tileset_watcher.tileset_resized.connect(reposition, 1)
|
||||
reposition()
|
||||
|
||||
|
||||
## Adjusts the position of this DisplayLayer based on the tile set's tile_size
|
||||
func reposition() -> void:
|
||||
position = offset * Vector2(_tileset_watcher.tile_size)
|
||||
|
||||
|
||||
## Copies properties from parent TileMapDual to child display tilemap
|
||||
func update_properties(parent: TileMapDual) -> void:
|
||||
# Both tilemaps must be the same, so we copy all relevant properties
|
||||
# Tilemap
|
||||
# already covered by parent._tileset_watcher
|
||||
# Rendering
|
||||
self.y_sort_origin = parent.y_sort_origin
|
||||
self.x_draw_order_reversed = parent.x_draw_order_reversed
|
||||
self.rendering_quadrant_size = parent.rendering_quadrant_size
|
||||
# Physics
|
||||
self.collision_enabled = parent.collision_enabled
|
||||
self.use_kinematic_bodies = parent.use_kinematic_bodies
|
||||
self.collision_visibility_mode = parent.collision_visibility_mode
|
||||
# Navigation
|
||||
self.navigation_enabled = parent.navigation_enabled
|
||||
self.navigation_visibility_mode = parent.navigation_visibility_mode
|
||||
# Canvas item properties
|
||||
self.show_behind_parent = parent.show_behind_parent
|
||||
self.top_level = parent.top_level
|
||||
self.light_mask = parent.light_mask
|
||||
self.visibility_layer = parent.visibility_layer
|
||||
self.y_sort_enabled = parent.y_sort_enabled
|
||||
self.modulate = parent.modulate
|
||||
self.self_modulate = parent.self_modulate
|
||||
# NOTE: parent material takes priority over the current shaders, causing the world tiles to show up
|
||||
self.use_parent_material = parent.use_parent_material
|
||||
|
||||
# Save any manually introduced Material change:
|
||||
self.material = parent.display_material
|
||||
|
||||
|
||||
## Updates all display tiles to reflect the current changes.
|
||||
func update_tiles_all(cache: TileCache) -> void:
|
||||
update_tiles(cache, cache.cells.keys())
|
||||
|
||||
|
||||
## Update all display tiles affected by the world cells
|
||||
func update_tiles(cache: TileCache, updated_world_cells: Array) -> void:
|
||||
#push_warning('updating tiles')
|
||||
var already_updated := Set.new()
|
||||
for path: Array in _terrain.display_to_world_neighborhood:
|
||||
path = path.map(Util.reverse_neighbor)
|
||||
for world_cell: Vector2i in updated_world_cells:
|
||||
var display_cell := follow_path(world_cell, path)
|
||||
if already_updated.insert(display_cell):
|
||||
update_tile(cache, display_cell)
|
||||
|
||||
|
||||
## Updates a specific world cell.
|
||||
func update_tile(cache: TileCache, cell: Vector2i) -> void:
|
||||
var get_cell_at_path := func(path): return cache.get_terrain_at(follow_path(cell, path))
|
||||
var terrain_neighbors := _terrain.display_to_world_neighborhood.map(get_cell_at_path)
|
||||
var mapping: Dictionary = _terrain.apply_rule(terrain_neighbors, cell)
|
||||
var sid: int = mapping.sid
|
||||
var tile: Vector2i = mapping.tile
|
||||
set_cell(cell, sid, tile)
|
||||
|
||||
|
||||
## Finds the neighbor of a given cell by following a path of CellNeighbors
|
||||
func follow_path(cell: Vector2i, path: Array) -> Vector2i:
|
||||
for neighbor: TileSet.CellNeighbor in path:
|
||||
cell = get_neighbor_cell(cell, neighbor)
|
||||
return cell
|
||||
1
addons/TileMapDual/DisplayLayer.gd.uid
Normal file
1
addons/TileMapDual/DisplayLayer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bon5inags0mqy
|
||||
76
addons/TileMapDual/Set.gd
Normal file
76
addons/TileMapDual/Set.gd
Normal file
@@ -0,0 +1,76 @@
|
||||
##[br] Real sets don't exist yet.
|
||||
##[br] https://github.com/godotengine/godot/pull/94399
|
||||
class_name Set
|
||||
extends Resource
|
||||
|
||||
|
||||
## The internal Dictionary that holds this Set's items as keys.
|
||||
var data: Dictionary = {}
|
||||
|
||||
func _init(initial_data: Variant = []) -> void:
|
||||
union_in_place(initial_data)
|
||||
|
||||
## Returns true if the item exists in this Set.
|
||||
func has(item: Variant) -> bool:
|
||||
return item in data
|
||||
|
||||
|
||||
## A dummy value to put in a slot.
|
||||
const DUMMY = null
|
||||
## Returns true if the item was not previously in the Set.
|
||||
func insert(item: Variant) -> bool:
|
||||
var out := not has(item)
|
||||
data[item] = DUMMY
|
||||
return out
|
||||
|
||||
|
||||
## Returns true if the item was previously in the Set.
|
||||
func remove(item: Variant) -> bool:
|
||||
return data.erase(item)
|
||||
|
||||
|
||||
## Deletes all items in this Set.
|
||||
func clear() -> void:
|
||||
data = {}
|
||||
|
||||
|
||||
## Merges an Array's items or Dict's keys into the Set.
|
||||
func union_in_place(other: Variant):
|
||||
for item in other:
|
||||
insert(item)
|
||||
|
||||
|
||||
## Returns a new Set with the items of both self and other.
|
||||
func union(other: Set) -> Set:
|
||||
var out = self.duplicate()
|
||||
out.union_in_place(other.data)
|
||||
return out
|
||||
|
||||
|
||||
## Removes an Array's items or Dict's keys from the Set.
|
||||
func diff_in_place(other: Variant):
|
||||
for item in other:
|
||||
remove(item)
|
||||
|
||||
|
||||
## Returns a new Set with all items in self that are not present in other.
|
||||
func diff(other: Set) -> Set:
|
||||
var out = self.duplicate()
|
||||
out.diff_in_place(other.data)
|
||||
return out
|
||||
|
||||
|
||||
## Inserts elements that are in other but not in self, and removes elements found in both.
|
||||
func xor_in_place(other: Variant):
|
||||
for item in other:
|
||||
if has(item):
|
||||
remove(item)
|
||||
else:
|
||||
insert(item)
|
||||
|
||||
|
||||
## Returns a new Set where each item is either in self or other, but not both.
|
||||
func xor(other: Set) -> Set:
|
||||
var out = self.duplicate()
|
||||
out.xor_in_place(other.data)
|
||||
return out
|
||||
1
addons/TileMapDual/Set.gd.uid
Normal file
1
addons/TileMapDual/Set.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ds8j47h4vi6gx
|
||||
194
addons/TileMapDual/TerrainDual.gd
Normal file
194
addons/TileMapDual/TerrainDual.gd
Normal file
@@ -0,0 +1,194 @@
|
||||
##[br] Reads a TileSet and dictates which tiles in the display map
|
||||
##[br] match up with its neighbors in the world map
|
||||
class_name TerrainDual
|
||||
extends Resource
|
||||
|
||||
|
||||
# Functions are ordered top to bottom in the transformation pipeline
|
||||
|
||||
## Maps a TileSet to a Neighborhood.
|
||||
static func tileset_neighborhood(tile_set: TileSet) -> Neighborhood:
|
||||
return GRID_NEIGHBORHOODS[Display.tileset_gridshape(tile_set)]
|
||||
|
||||
|
||||
## Maps a GridShape to a Neighborhood.
|
||||
const GRID_NEIGHBORHOODS = {
|
||||
Display.GridShape.SQUARE: Neighborhood.SQUARE,
|
||||
Display.GridShape.ISO: Neighborhood.ISOMETRIC,
|
||||
Display.GridShape.HALF_OFF_HORI: Neighborhood.TRIANGLE_HORIZONTAL,
|
||||
Display.GridShape.HALF_OFF_VERT: Neighborhood.TRIANGLE_VERTICAL,
|
||||
Display.GridShape.HEX_HORI: Neighborhood.TRIANGLE_HORIZONTAL,
|
||||
Display.GridShape.HEX_VERT: Neighborhood.TRIANGLE_VERTICAL,
|
||||
}
|
||||
|
||||
|
||||
## A specific neighborhood that the Display tiles will look at.
|
||||
enum Neighborhood {
|
||||
SQUARE,
|
||||
ISOMETRIC,
|
||||
TRIANGLE_HORIZONTAL,
|
||||
TRIANGLE_VERTICAL,
|
||||
}
|
||||
|
||||
|
||||
## Maps a Neighborhood to a set of atlas terrain neighbors.
|
||||
const NEIGHBORHOOD_LAYERS := {
|
||||
Neighborhood.SQUARE: [
|
||||
{ # []
|
||||
'terrain_neighborhood': [
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER,
|
||||
],
|
||||
'display_to_world_neighborhood': [
|
||||
[TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_SIDE],
|
||||
[TileSet.CELL_NEIGHBOR_LEFT_SIDE],
|
||||
[],
|
||||
],
|
||||
},
|
||||
],
|
||||
Neighborhood.ISOMETRIC: [
|
||||
{ # <>
|
||||
'terrain_neighborhood': [
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER,
|
||||
],
|
||||
'display_to_world_neighborhood': [
|
||||
[TileSet.CELL_NEIGHBOR_TOP_CORNER],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE],
|
||||
[],
|
||||
],
|
||||
},
|
||||
],
|
||||
Neighborhood.TRIANGLE_HORIZONTAL: [
|
||||
{ # v
|
||||
'terrain_neighborhood': [
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER,
|
||||
],
|
||||
'display_to_world_neighborhood': [
|
||||
[],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE],
|
||||
],
|
||||
},
|
||||
{ # ^
|
||||
'terrain_neighborhood': [
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER,
|
||||
],
|
||||
'display_to_world_neighborhood': [
|
||||
[TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE],
|
||||
[TileSet.CELL_NEIGHBOR_LEFT_SIDE],
|
||||
[],
|
||||
],
|
||||
},
|
||||
],
|
||||
# TODO: this is just TRIANGLE_HORIZONTAL but transposed. this can be refactored.
|
||||
Neighborhood.TRIANGLE_VERTICAL: [
|
||||
{ # >
|
||||
'terrain_neighborhood': [
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER,
|
||||
],
|
||||
'display_to_world_neighborhood': [
|
||||
[],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE],
|
||||
[TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE],
|
||||
],
|
||||
},
|
||||
{ # <
|
||||
'terrain_neighborhood': [
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER,
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER,
|
||||
],
|
||||
'display_to_world_neighborhood': [
|
||||
[TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE],
|
||||
[TileSet.CELL_NEIGHBOR_TOP_SIDE],
|
||||
[],
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
## The Neighborhood type of this TerrainDual.
|
||||
var neighborhood: Neighborhood
|
||||
|
||||
## Maps a terrain type to its sprite as registered in the TerrainDual.
|
||||
var terrains: Dictionary
|
||||
|
||||
## The TerrainLayers for this TerrainDual.
|
||||
var layers: Array
|
||||
var _tileset_watcher: TileSetWatcher
|
||||
func _init(tileset_watcher: TileSetWatcher) -> void:
|
||||
_tileset_watcher = tileset_watcher
|
||||
_tileset_watcher.terrains_changed.connect(_changed, 1)
|
||||
_changed()
|
||||
|
||||
|
||||
##[br] Emitted when any of the terrains change.
|
||||
##[br] NOTE: Prefer connecting to TerrainDual.changed instead of TileSetWatcher.terrains_changed.
|
||||
func _changed() -> void:
|
||||
#print('SIGNAL EMITTED: changed(%s)' % {})
|
||||
read_tileset(_tileset_watcher.tile_set)
|
||||
emit_changed()
|
||||
|
||||
|
||||
## Create rules for every atlas in a TileSet.
|
||||
func read_tileset(tile_set: TileSet) -> void:
|
||||
terrains = {}
|
||||
layers = []
|
||||
neighborhood = Neighborhood.SQUARE # default
|
||||
if tile_set == null:
|
||||
return
|
||||
neighborhood = tileset_neighborhood(tile_set)
|
||||
layers = NEIGHBORHOOD_LAYERS[neighborhood].map(TerrainLayer.new)
|
||||
for i in tile_set.get_source_count():
|
||||
var sid := tile_set.get_source_id(i)
|
||||
var src := tile_set.get_source(sid)
|
||||
if src is not TileSetAtlasSource:
|
||||
continue
|
||||
read_atlas(src, sid)
|
||||
|
||||
|
||||
## Create rules for every tile in an atlas.
|
||||
func read_atlas(atlas: TileSetAtlasSource, sid: int) -> void:
|
||||
var size = atlas.get_atlas_grid_size()
|
||||
for y in size.y:
|
||||
for x in size.x:
|
||||
var tile := Vector2i(x, y)
|
||||
# Take only existing tiles
|
||||
if not atlas.has_tile(tile):
|
||||
continue
|
||||
read_tile(atlas, sid, tile)
|
||||
|
||||
|
||||
## Add a new rule for a specific tile in an atlas.
|
||||
func read_tile(atlas: TileSetAtlasSource, sid: int, tile: Vector2i) -> void:
|
||||
var data := atlas.get_tile_data(tile, 0)
|
||||
var mapping := {'sid': sid, 'tile': tile}
|
||||
var terrain_set := data.terrain_set
|
||||
if terrain_set != 0:
|
||||
push_warning(
|
||||
"The tile at %s has a terrain set of %d. Only terrain set 0 is supported." % [mapping, terrain_set]
|
||||
)
|
||||
return
|
||||
var terrain := data.terrain
|
||||
if terrain != -1:
|
||||
if not terrain in terrains:
|
||||
terrains[terrain] = mapping
|
||||
|
||||
var filters = NEIGHBORHOOD_LAYERS[neighborhood]
|
||||
for i in layers.size():
|
||||
var layer: TerrainLayer = layers[i]
|
||||
layer._register_tile(data, mapping)
|
||||
1
addons/TileMapDual/TerrainDual.gd.uid
Normal file
1
addons/TileMapDual/TerrainDual.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://qa8xnqphuk68
|
||||
128
addons/TileMapDual/TerrainLayer.gd
Normal file
128
addons/TileMapDual/TerrainLayer.gd
Normal file
@@ -0,0 +1,128 @@
|
||||
## A set of _rules usable by a single DisplayLayer.
|
||||
class_name TerrainLayer
|
||||
extends Resource
|
||||
|
||||
|
||||
## A list of which CellNeighbors to care about during terrain checking.
|
||||
var terrain_neighborhood: Array = []
|
||||
|
||||
##[br] When a cell in a DisplayLayer needs to be recomputed,
|
||||
## the TerrainLayer needs to know which tiles surround it.
|
||||
##[br] This Array stores the paths from the affected cell to the neighboring world cells.
|
||||
var display_to_world_neighborhood: Array
|
||||
|
||||
# TODO: change Mapping to support https://github.com/pablogila/TileMapDual/issues/13
|
||||
##[br] The rules that dictate which tile matches a given set of neighbors.
|
||||
##[br] Used by register_rule() and apply_rule()
|
||||
##[codeblock]
|
||||
## _rules: TrieNode = # The actual type of _rules
|
||||
##
|
||||
## TrieNode: Dictionary{
|
||||
## key: int = # The terrain value of this neighbor
|
||||
## value: TrieNode | TrieLeaf = # The next branch of this trie
|
||||
## }
|
||||
##
|
||||
## TrieLeaf: Dictionary{
|
||||
## 'mapping': Mapping = # The tile that this should now become
|
||||
## }
|
||||
##
|
||||
## Mapping: Dictionary{
|
||||
## 'sid': int = # The source_id of this tile
|
||||
## 'tile': Vector2i = # The Atlas Coordinates of this tile
|
||||
## }
|
||||
##[/codeblock]
|
||||
##[br] Internally a decision "trie":
|
||||
##[br] - each node branch represents a terrain neighbor
|
||||
##[br] - each leaf node represents the terrain that a tile
|
||||
## with the given terrain neighborhood should become
|
||||
##[br]
|
||||
##[br] How the trie is searched:
|
||||
##[br] - check the next neighbor in terrain_neighbors
|
||||
##[br] - check if there is a branch corresponding to the terrain of that neighbor
|
||||
##[br] - if there is a branch, search again under that branch
|
||||
##[br] - else pretend that neighbor is empty and try again
|
||||
##[br] - if there really isn't a branch, no rules exist so just return empty
|
||||
##[br] - once at a leaf node, its mapping should tell us what terrain to become
|
||||
##[br]
|
||||
##[br] See apply_rule() for more details.
|
||||
var _rules: Dictionary = {}
|
||||
|
||||
## Generator for random placement of tiles
|
||||
var rand : RandomNumberGenerator
|
||||
## Random seed used to generate tiles deterministically
|
||||
var global_seed : int = 707
|
||||
|
||||
func _init(fields: Dictionary) -> void:
|
||||
self.terrain_neighborhood = fields.terrain_neighborhood
|
||||
self.display_to_world_neighborhood = fields.display_to_world_neighborhood
|
||||
self.rand = RandomNumberGenerator.new()
|
||||
|
||||
|
||||
## Register a new rule for a specific tile in an atlas.
|
||||
func _register_tile(data: TileData, mapping: Dictionary) -> void:
|
||||
if data.terrain_set != 0:
|
||||
# This was already handled as an error in the parent TerrainDual
|
||||
return
|
||||
var terrain_neighbors := terrain_neighborhood.map(data.get_terrain_peering_bit)
|
||||
# Skip tiles with no peering bits in this filter
|
||||
# They might be used for a different layer,
|
||||
# or may have no peering bits at all, which will just be ignored by all layers
|
||||
if terrain_neighbors.any(func(neighbor): return neighbor == -1):
|
||||
if terrain_neighbors.any(func(neighbor): return neighbor != -1):
|
||||
push_warning(
|
||||
"Invalid Tile Neighborhood at %s.\n" % [mapping] +
|
||||
"Expected neighborhood: %s" % [terrain_neighborhood.map(Util.neighbor_name)]
|
||||
)
|
||||
return
|
||||
mapping["prob"] = data.probability
|
||||
_register_rule(terrain_neighbors, mapping)
|
||||
|
||||
|
||||
## Register a new rule for a set of surrounding terrain neighbors
|
||||
func _register_rule(terrain_neighbors: Array, mapping: Dictionary) -> void:
|
||||
var node := _rules
|
||||
for terrain in terrain_neighbors:
|
||||
if terrain not in node:
|
||||
node[terrain] = {}
|
||||
node = node[terrain]
|
||||
if not 'mappings' in node:
|
||||
node.mappings = []
|
||||
node.mappings.append(mapping)
|
||||
|
||||
|
||||
const TILE_EMPTY: Dictionary = {'sid': - 1, 'tile': Vector2i(-1, -1)}
|
||||
## Returns the tile that should be used based on the surrounding terrain neighbors
|
||||
func apply_rule(terrain_neighbors: Array, cell: Vector2i) -> Dictionary:
|
||||
var is_empty := terrain_neighbors.all(func(terrain): return terrain == -1)
|
||||
if is_empty:
|
||||
return TILE_EMPTY
|
||||
var normalized_neighbors = terrain_neighbors.map(normalize_terrain)
|
||||
|
||||
var node := _rules
|
||||
for terrain in normalized_neighbors:
|
||||
if terrain not in node:
|
||||
terrain = 0
|
||||
if terrain not in node:
|
||||
return TILE_EMPTY
|
||||
node = node[terrain]
|
||||
if 'mappings' not in node:
|
||||
return TILE_EMPTY
|
||||
|
||||
#random tile selection
|
||||
var weights: Array= []
|
||||
var index: int = 0
|
||||
for mapping in node.mappings:
|
||||
weights.append(mapping.prob)
|
||||
rand.seed = hash(str(cell)+str(global_seed))
|
||||
index = rand.rand_weighted(weights)
|
||||
return node.mappings[index]
|
||||
|
||||
|
||||
## Coerces all empty tiles to have a terrain of 0.
|
||||
static func normalize_terrain(terrain):
|
||||
return terrain if terrain != -1 else 0
|
||||
|
||||
|
||||
## Utility function for easier printing
|
||||
func _neighbors_to_dict(terrain_neighbors: Array) -> Dictionary:
|
||||
return Util.arrays_to_dict(terrain_neighborhood.map(Util.neighbor_name), terrain_neighbors)
|
||||
1
addons/TileMapDual/TerrainLayer.gd.uid
Normal file
1
addons/TileMapDual/TerrainLayer.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ks3fxwahkm6j
|
||||
322
addons/TileMapDual/TerrainPreset.gd
Normal file
322
addons/TileMapDual/TerrainPreset.gd
Normal file
@@ -0,0 +1,322 @@
|
||||
## Functions for automatically generating terrains for an atlas.
|
||||
class_name TerrainPreset
|
||||
|
||||
|
||||
## Maps a Neighborhood to a Topology.
|
||||
const NEIGHBORHOOD_TOPOLOGIES := {
|
||||
TerrainDual.Neighborhood.SQUARE: Topology.SQUARE,
|
||||
TerrainDual.Neighborhood.ISOMETRIC: Topology.SQUARE,
|
||||
TerrainDual.Neighborhood.TRIANGLE_HORIZONTAL: Topology.TRIANGLE,
|
||||
TerrainDual.Neighborhood.TRIANGLE_VERTICAL: Topology.TRIANGLE,
|
||||
}
|
||||
|
||||
|
||||
## Determines the available Terrain presets for a certain Atlas.
|
||||
enum Topology {
|
||||
SQUARE,
|
||||
TRIANGLE,
|
||||
}
|
||||
|
||||
|
||||
## Maps a Neighborhood to a preset of the specified name.
|
||||
static func neighborhood_preset(
|
||||
neighborhood: TerrainDual.Neighborhood,
|
||||
preset_name: String = 'Standard'
|
||||
) -> Dictionary:
|
||||
var topology: Topology = NEIGHBORHOOD_TOPOLOGIES[neighborhood]
|
||||
# TODO: test when the preset doesn't exist
|
||||
var available_presets = PRESETS[topology]
|
||||
if preset_name not in available_presets:
|
||||
return {'size': Vector2i.ONE, 'layers': []}
|
||||
var out: Dictionary = available_presets[preset_name].duplicate(true)
|
||||
# All Horizontal neighborhoods can be transposed to Vertical
|
||||
if neighborhood == TerrainDual.Neighborhood.TRIANGLE_VERTICAL:
|
||||
out.size = Util.transpose_vec(out.size)
|
||||
out.fg = Util.transpose_vec(out.fg)
|
||||
out.bg = Util.transpose_vec(out.bg)
|
||||
for seq in out.layers:
|
||||
for i in seq.size():
|
||||
seq[i] = Util.transpose_vec(seq[i])
|
||||
return out
|
||||
|
||||
|
||||
## Contains all of the builtin Terrain presets for each topology
|
||||
const PRESETS := {
|
||||
Topology.SQUARE: {
|
||||
'Standard': {
|
||||
'size': Vector2i(4, 4),
|
||||
'bg': Vector2i(0, 3),
|
||||
'fg': Vector2i(2, 1),
|
||||
'layers': [
|
||||
[ # []
|
||||
Vector2i(0, 3),
|
||||
Vector2i(3, 3),
|
||||
Vector2i(0, 2),
|
||||
Vector2i(1, 2),
|
||||
Vector2i(0, 0),
|
||||
Vector2i(3, 2),
|
||||
Vector2i(2, 3),
|
||||
Vector2i(3, 1),
|
||||
Vector2i(1, 3),
|
||||
Vector2i(0, 1),
|
||||
Vector2i(1, 0),
|
||||
Vector2i(2, 2),
|
||||
Vector2i(3, 0),
|
||||
Vector2i(2, 0),
|
||||
Vector2i(1, 1),
|
||||
Vector2i(2, 1),
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
Topology.TRIANGLE: {
|
||||
'Standard': {
|
||||
'size': Vector2i(4, 4),
|
||||
'bg': Vector2i(0, 0),
|
||||
'fg': Vector2i(0, 2),
|
||||
'layers': [
|
||||
[ # v
|
||||
Vector2i(0, 1),
|
||||
Vector2i(2, 3),
|
||||
Vector2i(3, 1),
|
||||
Vector2i(1, 3),
|
||||
Vector2i(1, 1),
|
||||
Vector2i(3, 3),
|
||||
Vector2i(2, 1),
|
||||
Vector2i(0, 3),
|
||||
],
|
||||
[ # ^
|
||||
Vector2i(0, 0),
|
||||
Vector2i(2, 2),
|
||||
Vector2i(3, 0),
|
||||
Vector2i(1, 2),
|
||||
Vector2i(1, 0),
|
||||
Vector2i(3, 2),
|
||||
Vector2i(2, 0),
|
||||
Vector2i(0, 2),
|
||||
],
|
||||
]
|
||||
},
|
||||
# Old template.
|
||||
# a bit inconvenient to use for Brick (Half-Off Square) tilesets.
|
||||
'Winged': {
|
||||
'size': Vector2i(4, 4),
|
||||
'bg': Vector2i(0, 0),
|
||||
'fg': Vector2i(0, 2),
|
||||
'layers': [
|
||||
[ # v
|
||||
Vector2i(0, 1),
|
||||
Vector2i(2, 1),
|
||||
Vector2i(3, 1),
|
||||
Vector2i(1, 3),
|
||||
Vector2i(1, 1),
|
||||
Vector2i(3, 3),
|
||||
Vector2i(2, 3),
|
||||
Vector2i(0, 3),
|
||||
],
|
||||
[ # ^
|
||||
Vector2i(0, 0),
|
||||
Vector2i(2, 0),
|
||||
Vector2i(3, 0),
|
||||
Vector2i(1, 2),
|
||||
Vector2i(1, 0),
|
||||
Vector2i(3, 2),
|
||||
Vector2i(2, 2),
|
||||
Vector2i(0, 2),
|
||||
],
|
||||
]
|
||||
},
|
||||
# Old template.
|
||||
# The gaps between triangles made them harder to align.
|
||||
'Alternating': {
|
||||
'size': Vector2i(4, 4),
|
||||
'bg': Vector2i(0, 0),
|
||||
'fg': Vector2i(0, 2),
|
||||
'layers': [
|
||||
[ # v
|
||||
Vector2i(0, 0),
|
||||
Vector2i(2, 0),
|
||||
Vector2i(3, 1),
|
||||
Vector2i(1, 3),
|
||||
Vector2i(1, 1),
|
||||
Vector2i(3, 3),
|
||||
Vector2i(2, 2),
|
||||
Vector2i(0, 2),
|
||||
],
|
||||
[ # ^
|
||||
Vector2i(0, 1),
|
||||
Vector2i(2, 1),
|
||||
Vector2i(3, 0),
|
||||
Vector2i(1, 2),
|
||||
Vector2i(1, 0),
|
||||
Vector2i(3, 2),
|
||||
Vector2i(2, 3),
|
||||
Vector2i(0, 3),
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
##[br] Would you like to automatically create tiles in the atlas?
|
||||
##[br]
|
||||
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
||||
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
||||
static func write_default_preset(urm: EditorUndoRedoManager, tile_set: TileSet, atlas: TileSetAtlasSource) -> void:
|
||||
#print('writing default')
|
||||
var neighborhood := TerrainDual.tileset_neighborhood(tile_set)
|
||||
var terrain := new_terrain(
|
||||
urm,
|
||||
tile_set,
|
||||
atlas.texture.resource_path.get_file()
|
||||
)
|
||||
write_preset(
|
||||
urm,
|
||||
atlas,
|
||||
neighborhood,
|
||||
terrain,
|
||||
)
|
||||
|
||||
|
||||
##[br] Creates terrain set 0 (the primary terrain set) and terrain 0 (the 'any' terrain)
|
||||
##[br]
|
||||
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
||||
static func init_terrains(urm: EditorUndoRedoManager, tile_set: TileSet) -> void:
|
||||
urm.add_do_method(TerrainPreset, "_do_init_terrains", tile_set)
|
||||
urm.add_undo_method(TerrainPreset, "_undo_init_terrains", tile_set)
|
||||
|
||||
static func _do_init_terrains(tile_set: TileSet) -> void:
|
||||
tile_set.add_terrain_set()
|
||||
tile_set.set_terrain_set_mode(0, TileSet.TERRAIN_MODE_MATCH_CORNERS)
|
||||
tile_set.add_terrain(0)
|
||||
tile_set.set_terrain_name(0, 0, "<any>")
|
||||
tile_set.set_terrain_color(0, 0, Color.VIOLET)
|
||||
|
||||
static func _undo_init_terrains(tile_set: TileSet) -> void:
|
||||
tile_set.remove_terrain_set(0)
|
||||
|
||||
|
||||
##[br] Adds a new terrain type to terrain set 0 for the sprites to use.
|
||||
##[br]
|
||||
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
||||
static func new_terrain(urm: EditorUndoRedoManager, tile_set: TileSet, terrain_name: String) -> int:
|
||||
var terrain: int
|
||||
if tile_set.get_terrain_sets_count() == 0:
|
||||
init_terrains(urm, tile_set)
|
||||
terrain = 1
|
||||
else:
|
||||
terrain = tile_set.get_terrains_count(0)
|
||||
urm.add_do_method(TerrainPreset, "_do_new_terrain", tile_set, terrain_name)
|
||||
urm.add_undo_method(TerrainPreset, "_undo_new_terrain", tile_set)
|
||||
return terrain
|
||||
|
||||
static func _do_new_terrain(tile_set: TileSet, terrain_name: String) -> void:
|
||||
tile_set.add_terrain(0)
|
||||
var terrain := tile_set.get_terrains_count(0) - 1
|
||||
tile_set.set_terrain_name(0, terrain, "FG -%s" % terrain_name)
|
||||
|
||||
static func _undo_new_terrain(tile_set: TileSet) -> void:
|
||||
var terrain := tile_set.get_terrains_count(0) - 1
|
||||
tile_set.remove_terrain(0, terrain)
|
||||
|
||||
|
||||
##[br] Takes a preset and writes it onto the given atlas, replacing the previous configuration.
|
||||
##[br] ARGUMENTS:
|
||||
##[br] - atlas: the atlas source to apply the preset to.
|
||||
##[br] - filters: the neighborhood filter
|
||||
##[br]
|
||||
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
||||
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
||||
static func write_preset(
|
||||
urm: EditorUndoRedoManager,
|
||||
atlas: TileSetAtlasSource,
|
||||
neighborhood: TerrainDual.Neighborhood,
|
||||
terrain_foreground: int,
|
||||
terrain_background: int = 0,
|
||||
preset: Dictionary = neighborhood_preset(neighborhood),
|
||||
) -> void:
|
||||
clear_and_divide_atlas(urm, atlas, preset.size)
|
||||
urm.add_do_method(TerrainPreset, '_do_write_preset', atlas, neighborhood, terrain_foreground, terrain_background, preset)
|
||||
|
||||
static func _do_write_preset(
|
||||
atlas: TileSetAtlasSource,
|
||||
neighborhood: TerrainDual.Neighborhood,
|
||||
terrain_foreground: int,
|
||||
terrain_background: int,
|
||||
preset: Dictionary,
|
||||
) -> void:
|
||||
var layers: Array = TerrainDual.NEIGHBORHOOD_LAYERS[neighborhood]
|
||||
# Set peering bits
|
||||
var sequences: Array = preset.layers
|
||||
for j in layers.size():
|
||||
var terrain_neighborhood = layers[j].terrain_neighborhood
|
||||
var sequence: Array = sequences[j]
|
||||
for i in sequence.size():
|
||||
var tile: Vector2i = sequence[i]
|
||||
atlas.create_tile(tile)
|
||||
var data := atlas.get_tile_data(tile, 0)
|
||||
data.terrain_set = 0
|
||||
for neighbor in terrain_neighborhood:
|
||||
data.set_terrain_peering_bit(
|
||||
neighbor,
|
||||
[terrain_background, terrain_foreground][i & 1]
|
||||
)
|
||||
i >>= 1
|
||||
# Set terrains
|
||||
atlas.get_tile_data(preset.bg, 0).terrain = terrain_background
|
||||
atlas.get_tile_data(preset.fg, 0).terrain = terrain_foreground
|
||||
|
||||
##[br] Unregisters all the tiles in an atlas and changes the size of the individual sprites.
|
||||
##[br]
|
||||
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
||||
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
||||
static func clear_and_resize_atlas(urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, size: Vector2) -> void:
|
||||
var atlas_data := _save_atlas_data(atlas)
|
||||
urm.add_do_method(TerrainPreset, '_do_clear_and_resize_atlas', atlas, size)
|
||||
urm.add_undo_method(TerrainPreset, '_undo_clear_and_resize_atlas', atlas, atlas_data)
|
||||
|
||||
static func _do_clear_and_resize_atlas(atlas: TileSetAtlasSource, size: Vector2) -> void:
|
||||
# Clear all tiles
|
||||
atlas.texture_region_size = atlas.texture.get_size() + Vector2.ONE
|
||||
atlas.clear_tiles_outside_texture()
|
||||
# Resize the tiles
|
||||
atlas.texture_region_size = size
|
||||
|
||||
static func _undo_clear_and_resize_atlas(atlas: TileSetAtlasSource, atlas_data: Dictionary) -> void:
|
||||
_load_atlas_data(atlas, atlas_data)
|
||||
|
||||
## NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else.
|
||||
static func _save_atlas_data(atlas: TileSetAtlasSource) -> Dictionary:
|
||||
var size_img := atlas.texture.get_size()
|
||||
var size_sprite := atlas.texture_region_size
|
||||
var size_dims := Vector2i(size_img) / size_sprite
|
||||
var tiles := []
|
||||
for y in size_dims.y:
|
||||
var row := []
|
||||
for x in size_dims.x:
|
||||
var tile := Vector2i(x, y)
|
||||
var exists := atlas.has_tile(tile)
|
||||
row.push_back(exists)
|
||||
tiles.push_back(row)
|
||||
return {
|
||||
'size_sprite': size_sprite,
|
||||
'size_dims': size_dims,
|
||||
'tiles': tiles,
|
||||
}
|
||||
|
||||
static func _load_atlas_data(atlas: TileSetAtlasSource, atlas_data: Dictionary) -> void:
|
||||
_do_clear_and_resize_atlas(atlas, atlas_data.size_sprite)
|
||||
for y in atlas_data.size_dims.y:
|
||||
for x in atlas_data.size_dims.x:
|
||||
if atlas_data.tiles[y][x]:
|
||||
var tile := Vector2i(x, y)
|
||||
atlas.create_tile(tile)
|
||||
|
||||
|
||||
##[br] Unregisters all the tiles in an atlas and changes the size of the
|
||||
## individual sprites to accomodate a divisions.x by divisions.y grid of sprites.
|
||||
##[br]
|
||||
##[br] NOTE: Assumes urm.create_action() was called. Does not actually do anything until urm.commit_action() is called.
|
||||
##[br] NOTE: Assumes atlas only has auto-generated tiles. Does not save peering bit information or anything else for undo/redo.
|
||||
static func clear_and_divide_atlas(urm: EditorUndoRedoManager, atlas: TileSetAtlasSource, divisions: Vector2i) -> void:
|
||||
clear_and_resize_atlas(urm, atlas, atlas.texture.get_size() / Vector2(divisions))
|
||||
1
addons/TileMapDual/TerrainPreset.gd.uid
Normal file
1
addons/TileMapDual/TerrainPreset.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cdsxbtu57wews
|
||||
74
addons/TileMapDual/TileCache.gd
Normal file
74
addons/TileMapDual/TileCache.gd
Normal file
@@ -0,0 +1,74 @@
|
||||
## Caches the sprite location and terrains of each tile in the TileMapDual world grid.
|
||||
class_name TileCache
|
||||
extends Resource
|
||||
|
||||
|
||||
##[br] Maps a cell coordinate to the stored tile data.
|
||||
##[codeblock]
|
||||
## Dictionary{
|
||||
## key: Vector2i = # The coordinates of this tile in the world grid.
|
||||
## value: Dictionary{
|
||||
## 'sid': int = # The Source ID of this tile.
|
||||
## 'tile': Vector2i = # The coordinates of this tile in its Atlas.
|
||||
## 'terrain': int = # The terrain assigned to this tile.
|
||||
## } = # The data stored at this tile.
|
||||
## }
|
||||
##[/codeblock]
|
||||
var cells := {}
|
||||
func _init() -> void:
|
||||
pass
|
||||
|
||||
##[br] Updates specific cells of the TileCache based on the current layer data at those points.
|
||||
##[br] Makes corrections in case the user accidentally places invalid tiles.
|
||||
func update(world: TileMapLayer, edited: Array = cells.keys()) -> void:
|
||||
var tile_set := world.tile_set
|
||||
if tile_set == null:
|
||||
push_error('Attempted to update TileCache while tile set was null')
|
||||
return
|
||||
for cell in edited:
|
||||
# Invalid cells will be treated as empty and ignored
|
||||
var sid := world.get_cell_source_id(cell)
|
||||
if sid == -1:
|
||||
cells.erase(cell)
|
||||
continue
|
||||
if not tile_set.has_source(sid):
|
||||
continue
|
||||
var src = tile_set.get_source(sid)
|
||||
var tile := world.get_cell_atlas_coords(cell)
|
||||
if not src.has_tile(tile):
|
||||
continue
|
||||
var data := world.get_cell_tile_data(cell)
|
||||
if data == null:
|
||||
continue
|
||||
# Accidental cells should be reset to their previous value
|
||||
# They will be treated as unchanged
|
||||
if data.terrain == -1 or data.terrain_set != 0:
|
||||
if cell not in cells:
|
||||
world.erase_cell(cell)
|
||||
continue
|
||||
var cached: Dictionary = cells[cell]
|
||||
sid = cached.sid
|
||||
tile = cached.tile
|
||||
world.set_cell(cell, sid, tile)
|
||||
data = world.get_cell_tile_data(cell)
|
||||
cells[cell] = {'sid': sid, 'tile': tile, 'terrain': data.terrain}
|
||||
|
||||
|
||||
## Returns the symmetric difference (xor) of two tile caches.
|
||||
func xor(other: TileCache) -> Array[Vector2i]:
|
||||
var out: Array[Vector2i] = []
|
||||
for key in cells:
|
||||
if key not in other.cells or cells[key].terrain != other.cells[key].terrain:
|
||||
out.push_back(key)
|
||||
for key in other.cells:
|
||||
if key not in cells:
|
||||
out.push_back(key)
|
||||
return out
|
||||
|
||||
|
||||
##[br] Returns the terrain value of the tile at the given cell coordinates.
|
||||
##[br] Empty cells have a terrain of -1.
|
||||
func get_terrain_at(cell: Vector2i) -> int:
|
||||
if cell not in cells:
|
||||
return -1
|
||||
return cells[cell].terrain
|
||||
1
addons/TileMapDual/TileCache.gd.uid
Normal file
1
addons/TileMapDual/TileCache.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dpuc70ypq7wf8
|
||||
160
addons/TileMapDual/TileMapDual.gd
Normal file
160
addons/TileMapDual/TileMapDual.gd
Normal file
@@ -0,0 +1,160 @@
|
||||
@tool
|
||||
@icon('TileMapDual.svg')
|
||||
class_name TileMapDual
|
||||
extends TileMapLayer
|
||||
|
||||
|
||||
## An invisible material used to hide the world grid so only the display layers show up.
|
||||
## Currently implemented as a shader that sets all pixels to 0 alpha.
|
||||
var _ghost_material: Material = preload("res://addons/TileMapDual/ghost_material.tres")
|
||||
|
||||
|
||||
# === External functions that don't exist once exported ===
|
||||
# HACK: this uses some sort of "Dynamic Linking" technique because these features don't exist right now
|
||||
# - conditional compilation
|
||||
# - static signals
|
||||
static func _editor_only(name: String):
|
||||
push_error('Attempt to call Editor-Only function "' + name + '"')
|
||||
static var autotile: Callable = _editor_only.bind('autotile').unbind(3)
|
||||
static var popup: Callable = _editor_only.bind('popup').unbind(2)
|
||||
|
||||
|
||||
## Material for the display tilemap.
|
||||
@export_custom(PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial, CanvasItemMaterial")
|
||||
var display_material: Material:
|
||||
get:
|
||||
return display_material
|
||||
set(new_material): # Custom setter so that it gets copied
|
||||
display_material = new_material
|
||||
changed.emit()
|
||||
|
||||
var _tileset_watcher: TileSetWatcher
|
||||
var _display: Display
|
||||
func _ready() -> void:
|
||||
_tileset_watcher = TileSetWatcher.new(tile_set)
|
||||
_display = Display.new(self, _tileset_watcher)
|
||||
add_child(_display)
|
||||
_make_self_invisible(true)
|
||||
if Engine.is_editor_hint():
|
||||
_tileset_watcher.atlas_autotiled.connect(_atlas_autotiled)
|
||||
set_process(true)
|
||||
else: # Run in-game using signals for better performance
|
||||
changed.connect(_changed, 1)
|
||||
set_process(false)
|
||||
# Update full tileset on first instance
|
||||
await get_tree().process_frame
|
||||
_changed()
|
||||
|
||||
|
||||
## Automatically generate terrains when the atlas is initialized.
|
||||
func _atlas_autotiled(source_id: int, atlas: TileSetAtlasSource):
|
||||
autotile.call(source_id, atlas, tile_set)
|
||||
|
||||
|
||||
## Keeps track of use_parent_material to see when it turns on or off.
|
||||
var _cached_use_parent_material = null
|
||||
##[br] Makes the main world grid invisible.
|
||||
##[br] The main tiles don't need to be seen. Only the DisplayLayers should be visible.
|
||||
##[br] Called every frame, and functions a lot like TileSetWatcher.
|
||||
func _make_self_invisible(startup: bool = false) -> void:
|
||||
# If user has set a material in the original slot, inform the user
|
||||
if material != _ghost_material:
|
||||
if not startup and Engine.is_editor_hint():
|
||||
popup.call(
|
||||
"Warning! Direct material edit detected.",
|
||||
"Don't manually edit the real material in the editor! Instead edit the custom 'Display Material' property.\n" +
|
||||
"(Resetting the material to an invisible shader material... this is to keep the 'World Layer' invisible)\n" +
|
||||
"* This warning is only given when the material is set in the Godot editor.\n" +
|
||||
"* In-game scripts may set the material directly. It will be copied over to display_material automatically."
|
||||
)
|
||||
else:
|
||||
# copy over the material if it was edited by script
|
||||
display_material = material
|
||||
material = _ghost_material # Force TileMapDual's material to become invisible
|
||||
|
||||
# check if use_parent_material is set
|
||||
if (
|
||||
Engine.is_editor_hint()
|
||||
and use_parent_material != _cached_use_parent_material
|
||||
and _cached_use_parent_material == false # cache may be null
|
||||
):
|
||||
popup.call(
|
||||
"Warning: Using Parent Material.",
|
||||
"The parent material will override any other materials used by the TileMapDual,\n" +
|
||||
"including the 'ghost shader' that the world tiles use to hide themselves.\n" +
|
||||
"This will cause the world tiles to show themselves in-game.\n" +
|
||||
"\n" +
|
||||
"* Recommendation: Turn this setting off. Don't use parent material.\n" +
|
||||
"* Workaround: Set your world tiles to custom sprites that are entirely transparent.\n" +
|
||||
"(see 'res://addons/TileMapDual/docs/custom_drawing_sprites.mp4' for a non-transparent example)"
|
||||
)
|
||||
_cached_use_parent_material = use_parent_material
|
||||
|
||||
|
||||
## HACK: How long to wait before processing another "frame".
|
||||
## Mainly matters when [godot_4_3_compatibility] is active.
|
||||
@export_range(0.0, 0.1) var refresh_time: float = 0.02
|
||||
var _timer: float = 0.0
|
||||
func _process(delta: float) -> void: # Only used inside the editor
|
||||
if refresh_time < 0.0:
|
||||
return
|
||||
if _timer > 0:
|
||||
_timer -= delta
|
||||
return
|
||||
_timer = refresh_time
|
||||
call_deferred('_changed')
|
||||
|
||||
## When toggled on, double-checks ALL cells in the grid every change.
|
||||
## Only use this when running Godot 4.3 and below,
|
||||
## where TileMapLayer could not detect changes properly.
|
||||
@export var godot_4_3_compatibility: bool = _godot_is_below_4_4()
|
||||
|
||||
## Detects if godot is below v4.4.
|
||||
## Only used to detect whether the _update_cells() function is usable.
|
||||
func _godot_is_below_4_4():
|
||||
var version := Engine.get_version_info()
|
||||
return version.major < 4 or version.major == 4 and version.minor < 4
|
||||
|
||||
## Called by signals when the tileset changes,
|
||||
## or by _process inside the editor.
|
||||
func _changed() -> void:
|
||||
_tileset_watcher.update(tile_set)
|
||||
|
||||
var updated_cells := []
|
||||
# HACK: double check all tiles every refresh
|
||||
if godot_4_3_compatibility and tile_set != null:
|
||||
var current_cells := TileCache.new()
|
||||
current_cells.update(self, get_used_cells())
|
||||
updated_cells = current_cells.xor(_display.cached_cells)
|
||||
|
||||
_display.update(updated_cells)
|
||||
_make_self_invisible()
|
||||
|
||||
|
||||
## Called when the user draws on the map or presses undo/redo.
|
||||
func _update_cells(coords: Array[Vector2i], forced_cleanup: bool) -> void:
|
||||
if is_instance_valid(_display):
|
||||
_display.update(coords)
|
||||
|
||||
|
||||
##[br] Public method to add and remove tiles.
|
||||
##[br]
|
||||
##[br] - 'cell' is a vector with the cell position.
|
||||
##[br] - 'terrain' is which terrain type to draw.
|
||||
##[br] - terrain -1 completely removes the tile,
|
||||
##[br] - and by default, terrain 0 is the empty tile.
|
||||
func draw_cell(cell: Vector2i, terrain: int = 1) -> void:
|
||||
var terrains := _display.terrain.terrains
|
||||
if terrain not in terrains:
|
||||
erase_cell(cell)
|
||||
changed.emit()
|
||||
return
|
||||
var tile_to_use: Dictionary = terrains[terrain]
|
||||
var sid: int = tile_to_use.sid
|
||||
var tile: Vector2i = tile_to_use.tile
|
||||
set_cell(cell, sid, tile)
|
||||
changed.emit()
|
||||
|
||||
## Public method to get the terrain at a specific coordinate.
|
||||
func get_cell(cell: Vector2i) -> int:
|
||||
return get_cell_tile_data(cell).terrain
|
||||
1
addons/TileMapDual/TileMapDual.gd.uid
Normal file
1
addons/TileMapDual/TileMapDual.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cjk8nronimk5r
|
||||
38
addons/TileMapDual/TileMapDual.svg
Normal file
38
addons/TileMapDual/TileMapDual.svg
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="TileMapDual.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#999999"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="35.5"
|
||||
inkscape:cx="8"
|
||||
inkscape:cy="8.028169"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="736"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
fill="#8da5f3"
|
||||
d="M8 2 6.25 3.375 8 4.75l1.75-1.375zm2.624 2.062-1.75 1.375 1.75 1.375 1.75-1.375Zm2.626 2.063L11.5 7.5l1.75 1.375L15 7.5ZM5.376 4.062l-1.75 1.375 1.75 1.375 1.75-1.375zM8 6.124 6.25 7.499 8 8.874l1.75-1.375zm2.626 2.063-1.75 1.375 1.75 1.375 1.75-1.375ZM2.75 6.125 1 7.5l1.75 1.375L4.5 7.5Zm2.624 2.062-1.75 1.375 1.75 1.375 1.75-1.375ZM8 10.25l-1.75 1.375L8 13l1.75-1.375z"
|
||||
id="path1"
|
||||
style="fill:#ffb273;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
43
addons/TileMapDual/TileMapDual.svg.import
Normal file
43
addons/TileMapDual/TileMapDual.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bqu55yxko3ofm"
|
||||
path="res://.godot/imported/TileMapDual.svg-74ed91a3d196dbe51dce1133c9b2d6f7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/TileMapDual/TileMapDual.svg"
|
||||
dest_files=["res://.godot/imported/TileMapDual.svg-74ed91a3d196dbe51dce1133c9b2d6f7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
333
addons/TileMapDual/TileMapDualLegacy.gd
Normal file
333
addons/TileMapDual/TileMapDualLegacy.gd
Normal file
@@ -0,0 +1,333 @@
|
||||
@tool
|
||||
@icon('TileMapDual.svg')
|
||||
class_name TileMapDualLegacy
|
||||
extends TileMapLayer
|
||||
|
||||
## Material for the display tilemap.
|
||||
@export_custom(PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,CanvasItemMaterial")
|
||||
var display_material: Material:
|
||||
get:
|
||||
return display_material
|
||||
set(new_material): # Custom setter so that it gets copied
|
||||
display_material = new_material
|
||||
if display_tilemap: # Copy over, only if display tilemap is initiated
|
||||
display_tilemap.material = display_material
|
||||
|
||||
var display_tilemap: TileMapLayer = null
|
||||
var _filled_cells: Dictionary = {}
|
||||
var _emptied_cells: Dictionary = {}
|
||||
var _tile_shape: TileSet.TileShape = TileSet.TileShape.TILE_SHAPE_SQUARE
|
||||
var _tile_size: Vector2i = Vector2i(16, 16)
|
||||
## Coordinates for the fully-filled tile in the Atlas that
|
||||
## will be used to sketch in the World grid.
|
||||
## Only this tile will be considered for autotiling.
|
||||
var full_tile: Vector2i = Vector2i(2,1)
|
||||
## The opposed of full_tile. Used to erase sketched tiles.
|
||||
var empty_tile: Vector2i = Vector2i(0,3)
|
||||
var _should_check_cells: bool = false
|
||||
## Prevents checking the cells more than once when the entire tileset
|
||||
## is being updated, which is indicated by `_should_check_cells`.
|
||||
var _checked_cells: Dictionary = {}
|
||||
var is_isometric: bool = false
|
||||
var _atlas_id: int
|
||||
|
||||
## We will use a bit-wise logic, so that a summation over all sketched
|
||||
## neighbours provides a unique key, assigned to the corresponding
|
||||
## tile from the Atlas through the NEIGHBOURS_TO_ATLAS dictionary.
|
||||
enum _location {
|
||||
TOP_LEFT = 1,
|
||||
LOW_LEFT = 2,
|
||||
TOP_RIGHT = 4,
|
||||
LOW_RIGHT = 8
|
||||
}
|
||||
|
||||
enum _direction {
|
||||
TOP,
|
||||
LEFT,
|
||||
BOTTOM,
|
||||
RIGHT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_RIGHT,
|
||||
TOP_LEFT,
|
||||
TOP_RIGHT,
|
||||
}
|
||||
|
||||
## Overlapping tiles from the World grid
|
||||
## that a tile from the Dual grid has.
|
||||
const _NEIGHBORS := {
|
||||
_direction.TOP : TileSet.CellNeighbor.CELL_NEIGHBOR_TOP_SIDE,
|
||||
_direction.LEFT : TileSet.CellNeighbor.CELL_NEIGHBOR_LEFT_SIDE,
|
||||
_direction.RIGHT : TileSet.CellNeighbor.CELL_NEIGHBOR_RIGHT_SIDE,
|
||||
_direction.BOTTOM : TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_SIDE,
|
||||
_direction.TOP_LEFT : TileSet.CellNeighbor.CELL_NEIGHBOR_TOP_LEFT_CORNER,
|
||||
_direction.TOP_RIGHT : TileSet.CellNeighbor.CELL_NEIGHBOR_TOP_RIGHT_CORNER,
|
||||
_direction.BOTTOM_LEFT : TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER,
|
||||
_direction.BOTTOM_RIGHT : TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER
|
||||
}
|
||||
|
||||
## Overlapping tiles from the World grid
|
||||
## that a tile from the Dual grid has.
|
||||
## To be used ONLY with isometric tilesets.
|
||||
## CellNighbors are literal, even for Isometric
|
||||
const _NEIGHBORS_ISOMETRIC := {
|
||||
_direction.TOP : TileSet.CellNeighbor.CELL_NEIGHBOR_TOP_RIGHT_SIDE,
|
||||
_direction.LEFT : TileSet.CellNeighbor.CELL_NEIGHBOR_TOP_LEFT_SIDE,
|
||||
_direction.RIGHT : TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE,
|
||||
_direction.BOTTOM : TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE,
|
||||
_direction.TOP_LEFT : TileSet.CellNeighbor.CELL_NEIGHBOR_TOP_CORNER,
|
||||
_direction.TOP_RIGHT : TileSet.CellNeighbor.CELL_NEIGHBOR_RIGHT_CORNER,
|
||||
_direction.BOTTOM_LEFT : TileSet.CellNeighbor.CELL_NEIGHBOR_LEFT_CORNER,
|
||||
_direction.BOTTOM_RIGHT : TileSet.CellNeighbor.CELL_NEIGHBOR_BOTTOM_CORNER
|
||||
}
|
||||
|
||||
## Dict to assign the Atlas coordinates from the
|
||||
## summation over all sketched NEIGHBOURS.
|
||||
## Follows the official 2x2 template.
|
||||
## Works for isometric as well.
|
||||
const _NEIGHBORS_TO_ATLAS: Dictionary = {
|
||||
0: Vector2i(0,3),
|
||||
1: Vector2i(3,3),
|
||||
2: Vector2i(0,0),
|
||||
3: Vector2i(3,2),
|
||||
4: Vector2i(0,2),
|
||||
5: Vector2i(1,2),
|
||||
6: Vector2i(2,3),
|
||||
7: Vector2i(3,1),
|
||||
8: Vector2i(1,3),
|
||||
9: Vector2i(0,1),
|
||||
10: Vector2i(3,0),
|
||||
11: Vector2i(2,0),
|
||||
12: Vector2i(1,0),
|
||||
13: Vector2i(2,2),
|
||||
14: Vector2i(1,1),
|
||||
15: Vector2i(2,1)
|
||||
}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
set_process(true)
|
||||
else: # Run in-game using signals for better performance
|
||||
set_process(false)
|
||||
self.changed.connect(_update_tileset, 1)
|
||||
update_full_tileset()
|
||||
# Fire copy upon any property change. More specific than _update_tileset
|
||||
self.changed.connect(copy_properties, 1)
|
||||
|
||||
func _process(_delta): # Only used inside the editor
|
||||
if not self.tile_set:
|
||||
return
|
||||
call_deferred('_update_tileset')
|
||||
|
||||
## Copies properties from parent TileMapDual to child display tilemap
|
||||
func copy_properties() -> void:
|
||||
# Both tilemaps must be the same, so we copy all relevant properties
|
||||
# Tilemap
|
||||
display_tilemap.tile_set = self.tile_set
|
||||
# Rendering
|
||||
display_tilemap.y_sort_origin = self.y_sort_origin
|
||||
display_tilemap.x_draw_order_reversed = self.x_draw_order_reversed
|
||||
display_tilemap.rendering_quadrant_size = self.rendering_quadrant_size
|
||||
# Physics
|
||||
display_tilemap.collision_enabled = self.collision_enabled
|
||||
display_tilemap.use_kinematic_bodies = self.use_kinematic_bodies
|
||||
display_tilemap.collision_visibility_mode = self.collision_visibility_mode
|
||||
# Navigation
|
||||
display_tilemap.navigation_enabled = self.navigation_enabled
|
||||
display_tilemap.navigation_visibility_mode = self.navigation_visibility_mode
|
||||
# Canvas item properties
|
||||
display_tilemap.show_behind_parent = self.show_behind_parent
|
||||
display_tilemap.top_level = self.top_level
|
||||
display_tilemap.light_mask = self.light_mask
|
||||
display_tilemap.visibility_layer = self.visibility_layer
|
||||
display_tilemap.y_sort_enabled = self.y_sort_enabled
|
||||
display_tilemap.modulate = self.modulate
|
||||
|
||||
# If user has set a material in the original slot, copy it over for redundancy
|
||||
# Helps both migration to new version, and prevents user mistakes
|
||||
if self.material:
|
||||
display_material = self.material
|
||||
|
||||
# Set material for first time
|
||||
display_tilemap.material = display_material
|
||||
|
||||
# Save any manually introduced alpha modulation:
|
||||
if self.self_modulate.a != 0.0:
|
||||
display_tilemap.self_modulate = self.self_modulate
|
||||
self.self_modulate.a = 0.0 # Override modulation to prevent render bugs with certain shaders
|
||||
|
||||
self.material = null # Unset TileMapDual's material, to prevent render of it
|
||||
|
||||
## Set the dual grid as a child of TileMapDual.
|
||||
func _set_display_tilemap() -> void:
|
||||
if not self.tile_set:
|
||||
return
|
||||
|
||||
# Add the display TileMapLayer, if it doesn't already exist
|
||||
if not get_node_or_null("WorldTileMap"):
|
||||
display_tilemap = TileMapLayer.new()
|
||||
display_tilemap.name = "WorldTileMap"
|
||||
add_child(display_tilemap)
|
||||
|
||||
copy_properties() # Copy properties from TileMapDual to displayed tilemap
|
||||
|
||||
update_geometry()
|
||||
display_tilemap.clear()
|
||||
|
||||
## Update the size and shape of the tileset, displacing the display TileMapLayer accordingly.
|
||||
func update_geometry() -> void:
|
||||
is_isometric = self.tile_set.tile_shape == TileSet.TileShape.TILE_SHAPE_ISOMETRIC
|
||||
var offset := Vector2(self.tile_set.tile_size) * -0.5
|
||||
if is_isometric:
|
||||
offset.x = 0
|
||||
display_tilemap.position = offset
|
||||
_tile_size = self.tile_set.tile_size
|
||||
_tile_shape = self.tile_set.tile_shape
|
||||
|
||||
|
||||
## Update the entire tileset, processing all the cells in the map.
|
||||
func update_full_tileset() -> void:
|
||||
if display_tilemap == null:
|
||||
_set_display_tilemap()
|
||||
elif display_tilemap.tile_set != self.tile_set: # TO-DO: merge with the above
|
||||
_set_display_tilemap()
|
||||
_should_check_cells = true
|
||||
for _cell in self.get_used_cells():
|
||||
if _is_world_tile_sketched(_cell) == 1 or _is_world_tile_sketched(_cell) == 0:
|
||||
update_tile(_cell)
|
||||
_should_check_cells = false
|
||||
_checked_cells = {}
|
||||
# _checked_cells is only used when updating
|
||||
# the whole tilemap to avoid repeating checks.
|
||||
# This is skipped when updating tiles individually.
|
||||
|
||||
## Update only the very specific tiles that have changed.
|
||||
## Much more efficient than update_full_tileset.
|
||||
## Called by signals when the tileset changes,
|
||||
## or by _process inside the editor.
|
||||
func _update_tileset() -> void:
|
||||
if display_tilemap == null:
|
||||
update_full_tileset()
|
||||
return
|
||||
elif display_tilemap.tile_set != self.tile_set: # TO-DO: merge with the above
|
||||
update_full_tileset()
|
||||
return
|
||||
elif _tile_size != self.tile_set.tile_size or _tile_shape != self.tile_set.tile_shape:
|
||||
update_geometry()
|
||||
return
|
||||
|
||||
var _new_emptied_cells: Dictionary = array_to_dict(get_used_cells_by_id(-1, empty_tile))
|
||||
var _new_filled_cells: Dictionary = array_to_dict(get_used_cells_by_id(-1, full_tile))
|
||||
var _changed_cells: Dictionary = exclude_dicts(_emptied_cells, _new_emptied_cells).merged(exclude_dicts(_filled_cells, _new_filled_cells))
|
||||
|
||||
_emptied_cells = _new_emptied_cells
|
||||
_filled_cells = _new_filled_cells
|
||||
for _cell in _changed_cells:
|
||||
update_tile(_cell)
|
||||
|
||||
func array_to_dict(array: Array) -> Dictionary:
|
||||
var dict: Dictionary = {}
|
||||
for item in array:
|
||||
dict[item] = true
|
||||
return dict
|
||||
|
||||
## Return the values that are not shared between the arrays
|
||||
func exclude_dicts(a: Dictionary, b: Dictionary) -> Dictionary:
|
||||
var result = a.duplicate()
|
||||
for item in b:
|
||||
if result.has(item):
|
||||
result.erase(item)
|
||||
else:
|
||||
result[item] = true
|
||||
return result
|
||||
|
||||
## Takes a cell, and updates the overlapping tiles from the dual grid accordingly.
|
||||
func update_tile(world_cell: Vector2i, recurse: bool = true) -> void:
|
||||
_atlas_id = self.get_cell_source_id(world_cell)
|
||||
|
||||
# to not fall in a recursive loop because of a large space of emptiness in the map
|
||||
if (!recurse and _atlas_id == -1):
|
||||
return
|
||||
|
||||
var __NEIGHBORS = _NEIGHBORS_ISOMETRIC if is_isometric else _NEIGHBORS
|
||||
var _top_left = world_cell
|
||||
var _low_left = display_tilemap.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.BOTTOM])
|
||||
var _top_right = display_tilemap.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.RIGHT])
|
||||
var _low_right = display_tilemap.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.BOTTOM_RIGHT])
|
||||
_update_displayed_tile(_top_left)
|
||||
_update_displayed_tile(_low_left)
|
||||
_update_displayed_tile(_top_right)
|
||||
_update_displayed_tile(_low_right)
|
||||
|
||||
# if atlas id is -1 the tile is empty, so to have a good rendering we need to update surroundings
|
||||
if (_atlas_id == -1):
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.LEFT]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.TOP_LEFT]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.TOP]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.TOP_RIGHT]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.RIGHT]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.BOTTOM_RIGHT]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.BOTTOM]), false)
|
||||
update_tile(self.get_neighbor_cell(world_cell, __NEIGHBORS[_direction.BOTTOM_LEFT]), false)
|
||||
|
||||
|
||||
func _update_displayed_tile(_display_cell: Vector2i) -> void:
|
||||
# Avoid updating cells more than necessary
|
||||
if _should_check_cells:
|
||||
if _display_cell in _checked_cells:
|
||||
return
|
||||
_checked_cells[_display_cell] = true
|
||||
|
||||
var __NEIGHBORS = _NEIGHBORS_ISOMETRIC if is_isometric else _NEIGHBORS
|
||||
var _top_left = display_tilemap.get_neighbor_cell(_display_cell, __NEIGHBORS[_direction.TOP_LEFT])
|
||||
var _low_left = display_tilemap.get_neighbor_cell(_display_cell, __NEIGHBORS[_direction.LEFT])
|
||||
var _top_right = display_tilemap.get_neighbor_cell(_display_cell, __NEIGHBORS[_direction.TOP])
|
||||
var _low_right = _display_cell
|
||||
|
||||
# We perform a bitwise summation over the sketched neighbours
|
||||
var _tile_key: int = 0
|
||||
if _is_world_tile_sketched(_top_left) == 1:
|
||||
_tile_key += _location.TOP_LEFT
|
||||
if _is_world_tile_sketched(_low_left) == 1:
|
||||
_tile_key += _location.LOW_LEFT
|
||||
if _is_world_tile_sketched(_top_right) == 1:
|
||||
_tile_key += _location.TOP_RIGHT
|
||||
if _is_world_tile_sketched(_low_right) == 1:
|
||||
_tile_key += _location.LOW_RIGHT
|
||||
|
||||
var _coords_atlas: Vector2i = _NEIGHBORS_TO_ATLAS[_tile_key]
|
||||
display_tilemap.set_cell(_display_cell, _atlas_id, _coords_atlas)
|
||||
|
||||
|
||||
## Return -1 if the cell is empty, 0 if sketched with the empty tile,
|
||||
## and 1 if it is sketched with the fully-filled tile.
|
||||
func _is_world_tile_sketched(_world_cell: Vector2i):
|
||||
var _atlas_coords = get_cell_atlas_coords(_world_cell)
|
||||
if _atlas_coords == full_tile:
|
||||
return 1
|
||||
elif _atlas_coords == empty_tile:
|
||||
return 0
|
||||
return -1
|
||||
|
||||
|
||||
## Public method to add and remove tiles, as
|
||||
## TileMapDual.draw(cell, tile, atlas_id).
|
||||
## 'cell' is a vector with the cell position.
|
||||
## 'tile' is 1 to draw the full tile (default), 0 to draw the empty tile,
|
||||
## and -1 to completely remove the tile.
|
||||
## 'atlas_id' is the atlas id of the tileset to modify, 0 by default.
|
||||
## This method replaces the deprecated 'fill_tile' and 'erase_tile' methods.
|
||||
func draw(cell: Vector2i, tile: int = 1, atlas_id: int = 0) -> void:
|
||||
# Prevents a crash if this is called on the first frame
|
||||
if display_tilemap == null:
|
||||
update_full_tileset()
|
||||
var tile_to_use: Vector2i
|
||||
if tile == 1:
|
||||
tile_to_use = full_tile
|
||||
if tile == 0:
|
||||
tile_to_use = empty_tile
|
||||
if tile == -1:
|
||||
tile_to_use = Vector2i(-1, -1)
|
||||
atlas_id = -1
|
||||
set_cell(cell, atlas_id, tile_to_use)
|
||||
update_tile(cell)
|
||||
1
addons/TileMapDual/TileMapDualLegacy.gd.uid
Normal file
1
addons/TileMapDual/TileMapDualLegacy.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dlyqj0u8ckhb0
|
||||
181
addons/TileMapDual/TileSetWatcher.gd
Normal file
181
addons/TileMapDual/TileSetWatcher.gd
Normal file
@@ -0,0 +1,181 @@
|
||||
## Provides information about a TileSet and sends signals when it changes.
|
||||
class_name TileSetWatcher
|
||||
extends Resource
|
||||
|
||||
## Caches the previous tile_set to see when it changes.
|
||||
var tile_set: TileSet
|
||||
## Caches the previous tile_size to see when it changes.
|
||||
var tile_size: Vector2i
|
||||
## caches the previous result of display.tileset_grid_shape(tile_set) to see when it changes.
|
||||
var grid_shape: Display.GridShape
|
||||
|
||||
func _init(tile_set: TileSet) -> void:
|
||||
# tileset_deleted.connect(func(): print('tileset_deleted'), 1)
|
||||
# tileset_created.connect(func(): print('tileset_created'), 1)
|
||||
# tileset_resized.connect(func(): print('tileset_resized'), 1)
|
||||
# tileset_reshaped.connect(func(): print('tileset_reshaped'), 1)
|
||||
atlas_added.connect(_atlas_added, 1)
|
||||
update(tile_set)
|
||||
|
||||
|
||||
var _flag_tileset_deleted := false
|
||||
## Emitted when the parent TileMapDual's tile_set is cleared or replaced.
|
||||
signal tileset_deleted
|
||||
|
||||
var _flag_tileset_created := false
|
||||
## Emitted when the parent TileMapDual's tile_set is created or replaced.
|
||||
signal tileset_created
|
||||
|
||||
var _flag_tileset_resized := false
|
||||
## Emitted when tile_set.tile_size is changed.
|
||||
signal tileset_resized
|
||||
|
||||
var _flag_tileset_reshaped := false
|
||||
## Emitted when the GridShape of the TileSet would be different.
|
||||
signal tileset_reshaped
|
||||
|
||||
var _flag_atlas_added := false
|
||||
## Emitted when a new Atlas is added to this TileSet.
|
||||
## Does not react to Scenes being added to the TileSet.
|
||||
signal atlas_added(source_id: int, atlas: TileSetAtlasSource)
|
||||
func _atlas_added(source_id: int, atlas: TileSetAtlasSource) -> void:
|
||||
_flag_atlas_added = true
|
||||
#print('SIGNAL EMITTED: atlas_added(%s)' % {'source_id': source_id, 'atlas': atlas})
|
||||
|
||||
|
||||
## Emitted when the watcher thinks that "Yes" was clicked for:
|
||||
## 'Would you like to automatically create tiles in the atlas?'
|
||||
signal atlas_autotiled(source_id: int, atlas: TileSetAtlasSource)
|
||||
|
||||
|
||||
var _flag_terrains_changed := false
|
||||
## Emitted when an atlas is added or removed,
|
||||
## or when the terrains change in one of the Atlases.
|
||||
## NOTE: Prefer connecting to TerrainDual.changed instead of TileSetWatcher.terrains_changed.
|
||||
signal terrains_changed
|
||||
|
||||
|
||||
## Checks if anything about the concerned TileMapDual's tile_set changed.
|
||||
## Must be called by the TileMapDual every frame.
|
||||
func update(tile_set: TileSet) -> void:
|
||||
check_tile_set(tile_set)
|
||||
check_flags()
|
||||
|
||||
|
||||
## Emit update signals if the corresponding flags were set.
|
||||
## Must only be run once per frame.
|
||||
func check_flags() -> void:
|
||||
if _flag_tileset_changed:
|
||||
_flag_tileset_changed = false
|
||||
_update_tileset()
|
||||
if _flag_tileset_deleted:
|
||||
_flag_tileset_deleted = false
|
||||
_flag_tileset_reshaped = true
|
||||
tileset_deleted.emit()
|
||||
if _flag_tileset_created:
|
||||
_flag_tileset_created = false
|
||||
_flag_tileset_reshaped = true
|
||||
tileset_created.emit()
|
||||
if _flag_tileset_resized:
|
||||
_flag_tileset_resized = false
|
||||
tileset_resized.emit()
|
||||
if _flag_tileset_reshaped:
|
||||
_flag_tileset_reshaped = false
|
||||
_flag_terrains_changed = true
|
||||
tileset_reshaped.emit()
|
||||
if _flag_atlas_added:
|
||||
_flag_atlas_added = false
|
||||
_flag_terrains_changed = true
|
||||
if _flag_terrains_changed:
|
||||
_flag_terrains_changed = false
|
||||
terrains_changed.emit()
|
||||
|
||||
|
||||
## Check if tile_set has been added, replaced, or deleted.
|
||||
func check_tile_set(tile_set: TileSet) -> void:
|
||||
if tile_set == self.tile_set:
|
||||
return
|
||||
if self.tile_set != null:
|
||||
self.tile_set.changed.disconnect(_set_tileset_changed)
|
||||
_cached_source_count = 0
|
||||
_cached_sids.clear()
|
||||
_flag_tileset_deleted = true
|
||||
self.tile_set = tile_set
|
||||
if self.tile_set != null:
|
||||
self.tile_set.changed.connect(_set_tileset_changed, 1)
|
||||
self.tile_set.emit_changed()
|
||||
_flag_tileset_created = true
|
||||
emit_changed()
|
||||
|
||||
|
||||
var _flag_tileset_changed := false
|
||||
## Helper method to be called when the tile_set detects a change.
|
||||
## Must be disconnected when the tile_set is changed.
|
||||
func _set_tileset_changed() -> void:
|
||||
_flag_tileset_changed = true
|
||||
|
||||
|
||||
## Called when _flag_tileset_changed.
|
||||
## Provides more detail about what changed.
|
||||
func _update_tileset() -> void:
|
||||
var tile_size = tile_set.tile_size
|
||||
if self.tile_size != tile_size:
|
||||
self.tile_size = tile_size
|
||||
_flag_tileset_resized = true
|
||||
var grid_shape = Display.tileset_gridshape(tile_set)
|
||||
if self.grid_shape != grid_shape:
|
||||
self.grid_shape = grid_shape
|
||||
_flag_tileset_reshaped = true
|
||||
_update_tileset_atlases()
|
||||
|
||||
|
||||
# Cached variables from the previous frame
|
||||
# These are used to compare what changed between frames
|
||||
var _cached_source_count: int = 0
|
||||
var _cached_sids := {}
|
||||
# TODO: detect automatic tile creation
|
||||
## Checks if new atlases have been added.
|
||||
## Does not check which ones were deleted.
|
||||
func _update_tileset_atlases() -> void:
|
||||
# Update all tileset sources
|
||||
var source_count := tile_set.get_source_count()
|
||||
|
||||
# Only if an asset was added or removed
|
||||
# FIXME?: may break on add+remove in 1 frame
|
||||
if _cached_source_count == source_count:
|
||||
return
|
||||
_cached_source_count = source_count
|
||||
|
||||
# Process the new atlases in the TileSet
|
||||
var sids := {}
|
||||
for i in source_count:
|
||||
var sid: int = tile_set.get_source_id(i)
|
||||
if sid in _cached_sids:
|
||||
sids[sid] = _cached_sids[sid]
|
||||
continue
|
||||
var source: TileSetSource = tile_set.get_source(sid)
|
||||
if source is not TileSetAtlasSource:
|
||||
push_warning(
|
||||
"Non-Atlas TileSet found at index %i, source id %i.\n" % [i, source] +
|
||||
"Dual Grids only support Atlas TileSets."
|
||||
)
|
||||
sids[sid] = null
|
||||
continue
|
||||
var atlas: TileSetAtlasSource = source
|
||||
# FIXME?: check if this needs to be disconnected
|
||||
# SETUP:
|
||||
# - add logging to check which Watcher's flag was changed
|
||||
# - add a TileSet with an atlas to 2 TileMapDuals
|
||||
# - remove the TileSet
|
||||
# - modify the terrains on one of the atlases
|
||||
# - check how many watchers were flagged:
|
||||
# - if 2 watchers were flagged, this is bad.
|
||||
# try to repeatedly add and remove the tileset.
|
||||
# this could either cause the flag to happen multiple times,
|
||||
# or it could stay at 2 watchers.
|
||||
# - if 1 watcher was flagged, that is ok
|
||||
sids[sid] = AtlasWatcher.new(self, sid, atlas)
|
||||
atlas_added.emit(sid, atlas)
|
||||
_flag_terrains_changed = true
|
||||
# FIXME?: find which sid's were deleted
|
||||
_cached_sids = sids
|
||||
1
addons/TileMapDual/TileSetWatcher.gd.uid
Normal file
1
addons/TileMapDual/TileSetWatcher.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bjmo6oy8a4k2k
|
||||
62
addons/TileMapDual/Util.gd
Normal file
62
addons/TileMapDual/Util.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
## Utility functions.
|
||||
class_name Util
|
||||
|
||||
|
||||
## Merges an Array of keys and an Array of values into a Dictionary.
|
||||
static func arrays_to_dict(keys: Array, values: Array) -> Dictionary:
|
||||
var out := {}
|
||||
for i in keys.size():
|
||||
out[keys[i]] = values[i]
|
||||
return out
|
||||
|
||||
|
||||
## Swaps the X and Y axes of a Vector2i.
|
||||
static func transpose_vec(v: Vector2i) -> Vector2i:
|
||||
return Vector2i(v.y, v.x)
|
||||
|
||||
|
||||
# TODO: transposed(TileSet.CellNeighbor) -> Tileset.CellNeighbor
|
||||
|
||||
|
||||
## Reverses the direction of a CellNeighbor.
|
||||
static func reverse_neighbor(neighbor: TileSet.CellNeighbor) -> TileSet.CellNeighbor:
|
||||
return (neighbor + 8) % 16
|
||||
|
||||
|
||||
## Returns a shorthand name for a CellNeighbor.
|
||||
static func neighbor_name(neighbor: TileSet.CellNeighbor) -> String:
|
||||
const DIRECTIONS := ['E', 'SE', 'S', 'SW', 'W', 'NW', 'N', 'NE']
|
||||
return DIRECTIONS[neighbor >> 1]
|
||||
|
||||
|
||||
## Returns a pretty-printable neighborhood.
|
||||
static func neighborhood_str(neighborhood: Array) -> String:
|
||||
var neighbors := array_of(-1, 16)
|
||||
for i in neighborhood.size():
|
||||
neighbors[neighborhood[i]] = i
|
||||
|
||||
var get := func(neighbor: TileSet.CellNeighbor) -> String:
|
||||
var terrain = neighbors[neighbor]
|
||||
return '-' if terrain == -1 else str(terrain)
|
||||
|
||||
var nw = get.call(TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER)
|
||||
var n = get.call(TileSet.CELL_NEIGHBOR_TOP_CORNER)
|
||||
var ne = get.call(TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER)
|
||||
var w = get.call(TileSet.CELL_NEIGHBOR_LEFT_CORNER)
|
||||
var e = get.call(TileSet.CELL_NEIGHBOR_RIGHT_CORNER)
|
||||
var sw = get.call(TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)
|
||||
var s = get.call(TileSet.CELL_NEIGHBOR_BOTTOM_CORNER)
|
||||
var se = get.call(TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)
|
||||
|
||||
return (
|
||||
"%2s %2s %2s\n" % [nw, n, ne] +
|
||||
"%2s C %2s\n" % [w, e] +
|
||||
"%2s %2s %2s\n" % [sw, s, se]
|
||||
)
|
||||
|
||||
## Returns an Array of the given size, all filled with the given value.
|
||||
static func array_of(value: Variant, size: int) -> Array[Variant]:
|
||||
var out := []
|
||||
out.resize(size)
|
||||
out.fill(value)
|
||||
return out
|
||||
1
addons/TileMapDual/Util.gd.uid
Normal file
1
addons/TileMapDual/Util.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bfbksxcjuwdjt
|
||||
6
addons/TileMapDual/ghost.gdshader
Normal file
6
addons/TileMapDual/ghost.gdshader
Normal file
@@ -0,0 +1,6 @@
|
||||
// A shader that sets all pixels to 0 alpha, making the object invisible.
|
||||
|
||||
shader_type canvas_item;
|
||||
void fragment() {
|
||||
COLOR = vec4(0);
|
||||
}
|
||||
1
addons/TileMapDual/ghost.gdshader.uid
Normal file
1
addons/TileMapDual/ghost.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://44652e0wv1ve
|
||||
6
addons/TileMapDual/ghost_material.tres
Normal file
6
addons/TileMapDual/ghost_material.tres
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://cmbcfxlkxxnwq"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://44652e0wv1ve" path="res://addons/TileMapDual/ghost.gdshader" id="1_gvngp"]
|
||||
|
||||
[resource]
|
||||
shader = ExtResource("1_gvngp")
|
||||
7
addons/TileMapDual/plugin.cfg
Normal file
7
addons/TileMapDual/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="TileMapDual"
|
||||
description="An automatic, real-time dual-grid tileset system for Godot!"
|
||||
author="@GilaPixel"
|
||||
version="5.0.0rc3"
|
||||
script="plugin.gd"
|
||||
63
addons/TileMapDual/plugin.gd
Normal file
63
addons/TileMapDual/plugin.gd
Normal file
@@ -0,0 +1,63 @@
|
||||
@tool
|
||||
class_name TileMapDualEditorPlugin
|
||||
extends EditorPlugin
|
||||
|
||||
static var instance: TileMapDualEditorPlugin = null
|
||||
|
||||
|
||||
# TODO: create a message queue that groups warnings, errors, and messages into categories
|
||||
# so that we don't get 300 lines of the same warnings pushed to console every time we undo/redo
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# assign singleton instance
|
||||
instance = self
|
||||
# register custom nodes
|
||||
add_custom_type("TileMapDual", "TileMapLayer", preload("TileMapDual.gd"), preload("TileMapDual.svg"))
|
||||
add_custom_type("CursorDual", "Sprite2D", preload("CursorDual.gd"), preload("CursorDual.svg"))
|
||||
add_custom_type("TileMapDualLegacy", "TileMapLayer", preload("TileMapDualLegacy.gd"), preload("TileMapDual.svg"))
|
||||
# load editor-only functions
|
||||
TileMapDual.autotile = autotile
|
||||
TileMapDual.popup = popup
|
||||
# finish
|
||||
print("plugin TileMapDual loaded")
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
# disable editor-only functions
|
||||
TileMapDual.popup = TileMapDual._editor_only.bind('popup').unbind(2)
|
||||
TileMapDual.autotile = TileMapDual._editor_only.bind('autotile').unbind(3)
|
||||
# remove custom nodes
|
||||
remove_custom_type("TileMapDualLegacy")
|
||||
remove_custom_type("CursorDual")
|
||||
remove_custom_type("TileMapDual")
|
||||
# unassign singleton instance
|
||||
instance = null
|
||||
# finish
|
||||
print("plugin TileMapDual unloaded")
|
||||
|
||||
|
||||
# HACK: functions that reference EditorPlugin, directly or indirectly, cannot be in the publicly exported scripts
|
||||
# or else they simply won't work when exported
|
||||
|
||||
## Shows a popup with a title bar, a message, and an "Ok" button in the middle of the screen.
|
||||
func popup(title: String, message: String) -> void:
|
||||
var popup := AcceptDialog.new()
|
||||
get_editor_interface().get_base_control().add_child(popup)
|
||||
popup.name = 'TileMapDualPopup'
|
||||
popup.title = title
|
||||
popup.dialog_text = message
|
||||
popup.popup_centered()
|
||||
await popup.confirmed
|
||||
popup.queue_free()
|
||||
|
||||
|
||||
## Automatically generate terrains when the atlas is initialized.
|
||||
func autotile(source_id: int, atlas: TileSetAtlasSource, tile_set: TileSet):
|
||||
print_stack()
|
||||
var urm := get_undo_redo()
|
||||
urm.create_action("Create tiles in non-transparent texture regions", UndoRedo.MergeMode.MERGE_ALL, self, true)
|
||||
# NOTE: commit_action() is called immediately after.
|
||||
# NOTE: Atlas is guaranteed to have only been auto-generated with no extra peering bit information.
|
||||
TerrainPreset.write_default_preset(urm, tile_set, atlas)
|
||||
urm.commit_action()
|
||||
1
addons/TileMapDual/plugin.gd.uid
Normal file
1
addons/TileMapDual/plugin.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://irrn0ous8tet
|
||||
BIN
aseprite/grass_tilemap.aseprite
Normal file
BIN
aseprite/grass_tilemap.aseprite
Normal file
Binary file not shown.
Binary file not shown.
BIN
aseprite/houses.aseprite
Normal file
BIN
aseprite/houses.aseprite
Normal file
Binary file not shown.
BIN
aseprite/nature.aseprite
Normal file
BIN
aseprite/nature.aseprite
Normal file
Binary file not shown.
BIN
assets/fonts/monogram-extended.ttf
Normal file
BIN
assets/fonts/monogram-extended.ttf
Normal file
Binary file not shown.
36
assets/fonts/monogram-extended.ttf.import
Normal file
36
assets/fonts/monogram-extended.ttf.import
Normal file
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://s0mghd0bccm0"
|
||||
path="res://.godot/imported/monogram-extended.ttf-d0e25f0152286e72083f4de361724351.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/fonts/monogram-extended.ttf"
|
||||
dest_files=["res://.godot/imported/monogram-extended.ttf-d0e25f0152286e72083f4de361724351.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=1
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
BIN
assets/textures/1bit 16px icons part-2.png
Normal file
BIN
assets/textures/1bit 16px icons part-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
40
assets/textures/1bit 16px icons part-2.png.import
Normal file
40
assets/textures/1bit 16px icons part-2.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cfsvkp6w82tgh"
|
||||
path="res://.godot/imported/1bit 16px icons part-2.png-964581ea1d15a6f7999a5835abf4d89f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/1bit 16px icons part-2.png"
|
||||
dest_files=["res://.godot/imported/1bit 16px icons part-2.png-964581ea1d15a6f7999a5835abf4d89f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/textures/spritesheets/hood_player.png
Normal file
BIN
assets/textures/spritesheets/hood_player.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
40
assets/textures/spritesheets/hood_player.png.import
Normal file
40
assets/textures/spritesheets/hood_player.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c12v1hnrbfjr4"
|
||||
path="res://.godot/imported/hood_player.png-6c2f9e5710e5416c4bf1853ba3b50627.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/spritesheets/hood_player.png"
|
||||
dest_files=["res://.godot/imported/hood_player.png-6c2f9e5710e5416c4bf1853ba3b50627.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/textures/spritesheets/houses.png
Normal file
BIN
assets/textures/spritesheets/houses.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
40
assets/textures/spritesheets/houses.png.import
Normal file
40
assets/textures/spritesheets/houses.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://db4dctk13rgbm"
|
||||
path="res://.godot/imported/houses.png-beb9348ecf7ad3cb2e053ab2dbfaa8e1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/spritesheets/houses.png"
|
||||
dest_files=["res://.godot/imported/houses.png-beb9348ecf7ad3cb2e053ab2dbfaa8e1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
assets/textures/tilemaps/grass_tilemap.png
Normal file
BIN
assets/textures/tilemaps/grass_tilemap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 481 B |
40
assets/textures/tilemaps/grass_tilemap.png.import
Normal file
40
assets/textures/tilemaps/grass_tilemap.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dve2b2glwitsw"
|
||||
path="res://.godot/imported/grass_tilemap.png-de5493da83e3ee84719d7aa1394438db.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/textures/tilemaps/grass_tilemap.png"
|
||||
dest_files=["res://.godot/imported/grass_tilemap.png-de5493da83e3ee84719d7aa1394438db.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -11,7 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="hood"
|
||||
run/main_scene="uid://clnb1eshis30m"
|
||||
run/main_scene="uid://dgq21ggr3br2g"
|
||||
run/print_header=false
|
||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||
boot_splash/bg_color=Color(0, 0, 0, 1)
|
||||
@@ -22,19 +22,32 @@ config/icon="res://icon.svg"
|
||||
|
||||
[autoload]
|
||||
|
||||
Windowman="*res://scripts/autoloads/windowman.gd"
|
||||
WindowManager="*res://scripts/autoloads/windowman.gd"
|
||||
DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd"
|
||||
Save="*res://scripts/autoloads/save.gd"
|
||||
EventManager="*res://scenes/autoloads/event_manager.tscn"
|
||||
Pixelize="*res://scenes/filter/pixelize.tscn"
|
||||
|
||||
[display]
|
||||
|
||||
window/stretch/mode="canvas_items"
|
||||
window/stretch/scale=3.5
|
||||
window/size/viewport_width=360
|
||||
window/size/viewport_height=200
|
||||
window/size/window_width_override=1152
|
||||
window/size/window_height_override=640
|
||||
window/stretch/mode="viewport"
|
||||
display_server/driver.linuxbsd="wayland"
|
||||
window/size/mode.release=4
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/dialogue_manager/plugin.cfg")
|
||||
enabled=PackedStringArray("res://addons/TileMapDual/plugin.cfg", "res://addons/dialogue_manager/plugin.cfg")
|
||||
|
||||
[gui]
|
||||
|
||||
theme/default_font_antialiasing=0
|
||||
theme/default_font_subpixel_positioning=0
|
||||
theme/lcd_subpixel_layout=0
|
||||
theme/custom_font="uid://s0mghd0bccm0"
|
||||
|
||||
[input]
|
||||
|
||||
@@ -87,16 +100,28 @@ escape={
|
||||
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":true,"script":null)
|
||||
]
|
||||
}
|
||||
interact={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(495, 26),"global_position":Vector2(514, 122),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":89,"key_label":0,"unicode":122,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[layer_names]
|
||||
|
||||
2d_physics/layer_2="Interactable"
|
||||
|
||||
[physics]
|
||||
|
||||
2d/run_on_separate_thread=true
|
||||
common/physics_interpolation=true
|
||||
|
||||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=3
|
||||
anti_aliasing/quality/msaa_2d=3
|
||||
anti_aliasing/quality/screen_space_aa=2
|
||||
anti_aliasing/quality/use_taa=true
|
||||
anti_aliasing/quality/use_debanding=true
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
textures/default_filters/use_nearest_mipmap_filter=true
|
||||
2d/snap/snap_2d_transforms_to_pixel=true
|
||||
2d/snap/snap_2d_vertices_to_pixel=true
|
||||
environment/defaults/default_clear_color.release=Color(0, 0, 0, 1)
|
||||
|
||||
16
scenes/autoloads/event_manager.tscn
Normal file
16
scenes/autoloads/event_manager.tscn
Normal file
@@ -0,0 +1,16 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cjbbslcx6vjjy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b08q3lqbuoolb" path="res://scripts/autoloads/nodes/event_manager.gd" id="1_xplee"]
|
||||
|
||||
[node name="EventManager" type="CanvasLayer"]
|
||||
script = ExtResource("1_xplee")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
self_modulate = Color(1, 1, 1, 0)
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
color = Color(0, 0, 0, 1)
|
||||
23
scenes/filter/pixelize.tscn
Normal file
23
scenes/filter/pixelize.tscn
Normal file
@@ -0,0 +1,23 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dthcpgxk5b74y"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://dgstw8crggyti" path="res://shaders/pixelate.gdshader" id="1_tt4dp"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_83vy4"]
|
||||
shader = ExtResource("1_tt4dp")
|
||||
shader_parameter/texture_mode = 1
|
||||
shader_parameter/quantize_size = 1
|
||||
shader_parameter/snap_to_world = false
|
||||
shader_parameter/limit_subpixels = true
|
||||
|
||||
[node name="Pixelize" type="CanvasLayer"]
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="."]
|
||||
visible = false
|
||||
material = SubResource("ShaderMaterial_83vy4")
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
metadata/_edit_lock_ = true
|
||||
27
scenes/houses/home.tscn
Normal file
27
scenes/houses/home.tscn
Normal file
@@ -0,0 +1,27 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://cokphmh2g8wvs"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://db4dctk13rgbm" path="res://assets/textures/spritesheets/houses.png" id="1_fltv6"]
|
||||
[ext_resource type="PackedScene" uid="uid://h77hilgbces" path="res://scenes/interactables/interact_sceneswitch.tscn" id="2_0afuu"]
|
||||
[ext_resource type="PackedScene" uid="uid://ccfdsdgaon63m" path="res://scenes/levels/home.tscn" id="3_0afuu"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_0db70"]
|
||||
atlas = ExtResource("1_fltv6")
|
||||
region = Rect2(1, 4, 94, 89)
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_0afuu"]
|
||||
size = Vector2(90, 46)
|
||||
|
||||
[node name="Home" type="StaticBody2D"]
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
texture = SubResource("AtlasTexture_0db70")
|
||||
|
||||
[node name="InteractSceneswitch" parent="." instance=ExtResource("2_0afuu")]
|
||||
position = Vector2(0, 25)
|
||||
scene = ExtResource("3_0afuu")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
y_sort_enabled = true
|
||||
position = Vector2(0, 18)
|
||||
shape = SubResource("RectangleShape2D_0afuu")
|
||||
debug_color = Color(0.8768643, 0.34231007, 0.046793222, 0.41960785)
|
||||
14
scenes/interactable.tscn
Normal file
14
scenes/interactable.tscn
Normal file
@@ -0,0 +1,14 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://n24dhbpflcec"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ccthj5mtii0bw" path="res://scripts/interactable.gd" id="1_56v82"]
|
||||
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_ij5l6"]
|
||||
size = Vector2(32, 32)
|
||||
|
||||
[node name="Interactable" type="Area2D"]
|
||||
collision_layer = 2
|
||||
collision_mask = 0
|
||||
script = ExtResource("1_56v82")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_ij5l6")
|
||||
11
scenes/interactables/interact_sceneswitch.tscn
Normal file
11
scenes/interactables/interact_sceneswitch.tscn
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://h77hilgbces"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c4ejbchoh7yrh" path="res://scripts/interactables/interact_sceneswitch.gd" id="1_f3ssn"]
|
||||
[ext_resource type="PackedScene" uid="uid://n24dhbpflcec" path="res://scenes/interactable.tscn" id="2_43o6g"]
|
||||
|
||||
[node name="InteractSceneswitch" type="Node2D"]
|
||||
script = ExtResource("1_f3ssn")
|
||||
|
||||
[node name="Interactable" parent="." instance=ExtResource("2_43o6g")]
|
||||
|
||||
[connection signal="interacted" from="Interactable" to="." method="_on_interactable_interacted"]
|
||||
84
scenes/levels/home.tscn
Normal file
84
scenes/levels/home.tscn
Normal file
@@ -0,0 +1,84 @@
|
||||
[gd_scene load_steps=5 format=4 uid="uid://ccfdsdgaon63m"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://dfbomt0l6b1o4" path="res://scenes/player.tscn" id="1_ikf4c"]
|
||||
[ext_resource type="Texture2D" uid="uid://dve2b2glwitsw" path="res://assets/textures/tilemaps/grass_tilemap.png" id="1_q28r8"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_2vl8h"]
|
||||
texture = ExtResource("1_q28r8")
|
||||
0:0/0 = 0
|
||||
1:0/0 = 0
|
||||
2:0/0 = 0
|
||||
3:0/0 = 0
|
||||
4:0/0 = 0
|
||||
5:0/0 = 0
|
||||
6:0/0 = 0
|
||||
7:0/0 = 0
|
||||
0:1/0 = 0
|
||||
1:1/0 = 0
|
||||
2:1/0 = 0
|
||||
3:1/0 = 0
|
||||
4:1/0 = 0
|
||||
5:1/0 = 0
|
||||
6:1/0 = 0
|
||||
7:1/0 = 0
|
||||
0:2/0 = 0
|
||||
1:2/0 = 0
|
||||
2:2/0 = 0
|
||||
3:2/0 = 0
|
||||
4:2/0 = 0
|
||||
5:2/0 = 0
|
||||
6:2/0 = 0
|
||||
7:2/0 = 0
|
||||
0:3/0 = 0
|
||||
1:3/0 = 0
|
||||
2:3/0 = 0
|
||||
3:3/0 = 0
|
||||
4:3/0 = 0
|
||||
5:3/0 = 0
|
||||
6:3/0 = 0
|
||||
7:3/0 = 0
|
||||
0:4/0 = 0
|
||||
1:4/0 = 0
|
||||
2:4/0 = 0
|
||||
3:4/0 = 0
|
||||
4:4/0 = 0
|
||||
5:4/0 = 0
|
||||
6:4/0 = 0
|
||||
7:4/0 = 0
|
||||
0:5/0 = 0
|
||||
1:5/0 = 0
|
||||
2:5/0 = 0
|
||||
3:5/0 = 0
|
||||
4:5/0 = 0
|
||||
5:5/0 = 0
|
||||
6:5/0 = 0
|
||||
7:5/0 = 0
|
||||
0:6/0 = 0
|
||||
1:6/0 = 0
|
||||
2:6/0 = 0
|
||||
3:6/0 = 0
|
||||
4:6/0 = 0
|
||||
5:6/0 = 0
|
||||
6:6/0 = 0
|
||||
7:6/0 = 0
|
||||
0:7/0 = 0
|
||||
1:7/0 = 0
|
||||
2:7/0 = 0
|
||||
3:7/0 = 0
|
||||
4:7/0 = 0
|
||||
5:7/0 = 0
|
||||
6:7/0 = 0
|
||||
7:7/0 = 0
|
||||
|
||||
[sub_resource type="TileSet" id="TileSet_ikf4c"]
|
||||
sources/1 = SubResource("TileSetAtlasSource_2vl8h")
|
||||
|
||||
[node name="Home" type="Node2D"]
|
||||
|
||||
[node name="TileMapLayer" type="TileMapLayer" parent="."]
|
||||
tile_map_data = PackedByteArray("AAAGAAMAAQACAAMAAAAGAAQAAQACAAMAAAAGAAUAAQACAAMAAAAGAAYAAQACAAMAAAAGAAcAAQACAAMAAAAGAAgAAQACAAMAAAAGAAkAAQACAAMAAAAGAAoAAQACAAMAAAAGAAsAAQACAAMAAAAGAAwAAQACAAMAAAAHAAMAAQACAAMAAAAHAAQAAQACAAMAAAAHAAUAAQACAAMAAAAHAAYAAQACAAMAAAAHAAcAAQACAAMAAAAHAAgAAQACAAMAAAAHAAkAAQACAAMAAAAHAAoAAQACAAMAAAAHAAsAAQACAAMAAAAHAAwAAQACAAMAAAAIAAMAAQACAAMAAAAIAAQAAQACAAMAAAAIAAUAAQACAAMAAAAIAAYAAQACAAMAAAAIAAcAAQACAAMAAAAIAAgAAQACAAMAAAAIAAkAAQACAAMAAAAIAAoAAQACAAMAAAAIAAsAAQACAAMAAAAIAAwAAQACAAMAAAAJAAMAAQACAAMAAAAJAAQAAQACAAMAAAAJAAUAAQACAAMAAAAJAAYAAQACAAMAAAAJAAcAAQACAAMAAAAJAAgAAQACAAMAAAAJAAkAAQACAAMAAAAJAAoAAQACAAMAAAAJAAsAAQACAAMAAAAJAAwAAQACAAMAAAAKAAMAAQACAAMAAAAKAAQAAQACAAMAAAAKAAUAAQACAAMAAAAKAAYAAQACAAMAAAAKAAcAAQACAAMAAAAKAAgAAQACAAMAAAAKAAkAAQACAAMAAAAKAAoAAQACAAMAAAAKAAsAAQACAAMAAAAKAAwAAQACAAMAAAALAAMAAQACAAMAAAALAAQAAQACAAMAAAALAAUAAQACAAMAAAALAAYAAQACAAMAAAALAAcAAQACAAMAAAALAAgAAQACAAMAAAALAAkAAQACAAMAAAALAAoAAQACAAMAAAALAAsAAQACAAMAAAALAAwAAQACAAMAAAAMAAMAAQACAAMAAAAMAAQAAQACAAMAAAAMAAUAAQACAAMAAAAMAAYAAQACAAMAAAAMAAcAAQACAAMAAAAMAAgAAQACAAMAAAAMAAkAAQACAAMAAAAMAAoAAQACAAMAAAAMAAsAAQACAAMAAAAMAAwAAQACAAMAAAANAAMAAQACAAMAAAANAAQAAQACAAMAAAANAAUAAQACAAMAAAANAAYAAQACAAMAAAANAAcAAQACAAMAAAANAAgAAQACAAMAAAANAAkAAQACAAMAAAANAAoAAQACAAMAAAANAAsAAQACAAMAAAANAAwAAQACAAMAAAAOAAMAAQACAAMAAAAOAAQAAQACAAMAAAAOAAUAAQACAAMAAAAOAAYAAQACAAMAAAAOAAcAAQACAAMAAAAOAAgAAQACAAMAAAAOAAkAAQACAAMAAAAOAAoAAQACAAMAAAAOAAsAAQACAAMAAAAOAAwAAQACAAMAAAAPAAMAAQACAAMAAAAPAAQAAQACAAMAAAAPAAUAAQACAAMAAAAPAAYAAQACAAMAAAAPAAcAAQACAAMAAAAPAAgAAQACAAMAAAAPAAkAAQACAAMAAAAPAAoAAQACAAMAAAAPAAsAAQACAAMAAAAPAAwAAQACAAMAAAA=")
|
||||
tile_set = SubResource("TileSet_ikf4c")
|
||||
|
||||
[node name="Player" parent="." node_paths=PackedStringArray("tilemap") instance=ExtResource("1_ikf4c")]
|
||||
position = Vector2(180, 183)
|
||||
tilemap = NodePath("../TileMapLayer")
|
||||
@@ -1,10 +1,225 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://clnb1eshis30m"]
|
||||
[gd_scene load_steps=8 format=4 uid="uid://clnb1eshis30m"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://dve2b2glwitsw" path="res://assets/textures/tilemaps/grass_tilemap.png" id="1_74em3"]
|
||||
[ext_resource type="PackedScene" uid="uid://dfbomt0l6b1o4" path="res://scenes/player.tscn" id="1_ge1l5"]
|
||||
[ext_resource type="PackedScene" uid="uid://cokphmh2g8wvs" path="res://scenes/houses/home.tscn" id="3_5rqdi"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_1r5ec"]
|
||||
texture = ExtResource("1_74em3")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/terrain_set = 0
|
||||
0:0/0/terrain = 0
|
||||
0:0/0/terrains_peering_bit/right_side = 0
|
||||
0:0/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
0:0/0/terrains_peering_bit/bottom_side = 0
|
||||
0:0/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
0:0/0/terrains_peering_bit/left_side = 0
|
||||
0:0/0/terrains_peering_bit/top_left_corner = 0
|
||||
0:0/0/terrains_peering_bit/top_side = 0
|
||||
0:0/0/terrains_peering_bit/top_right_corner = 0
|
||||
1:0/0 = 0
|
||||
1:0/0/terrain_set = 0
|
||||
1:0/0/terrain = 0
|
||||
1:0/0/terrains_peering_bit/right_side = 1
|
||||
1:0/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
1:0/0/terrains_peering_bit/bottom_side = 0
|
||||
1:0/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
1:0/0/terrains_peering_bit/left_side = 0
|
||||
1:0/0/terrains_peering_bit/top_left_corner = 0
|
||||
1:0/0/terrains_peering_bit/top_side = 0
|
||||
1:0/0/terrains_peering_bit/top_right_corner = 1
|
||||
2:0/0 = 0
|
||||
2:0/0/terrain_set = 0
|
||||
2:0/0/terrain = 0
|
||||
2:0/0/terrains_peering_bit/right_side = 0
|
||||
2:0/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
2:0/0/terrains_peering_bit/bottom_side = 1
|
||||
2:0/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
2:0/0/terrains_peering_bit/left_side = 1
|
||||
2:0/0/terrains_peering_bit/top_left_corner = 1
|
||||
2:0/0/terrains_peering_bit/top_side = 0
|
||||
2:0/0/terrains_peering_bit/top_right_corner = 0
|
||||
3:0/0 = 0
|
||||
3:0/0/terrain_set = 0
|
||||
3:0/0/terrain = 0
|
||||
3:0/0/terrains_peering_bit/right_side = 0
|
||||
3:0/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
3:0/0/terrains_peering_bit/bottom_side = 1
|
||||
3:0/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
3:0/0/terrains_peering_bit/left_side = 0
|
||||
3:0/0/terrains_peering_bit/top_left_corner = 0
|
||||
3:0/0/terrains_peering_bit/top_side = 0
|
||||
3:0/0/terrains_peering_bit/top_right_corner = 0
|
||||
0:1/0 = 0
|
||||
0:1/0/terrain_set = 0
|
||||
0:1/0/terrain = 0
|
||||
0:1/0/terrains_peering_bit/right_side = 0
|
||||
0:1/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
0:1/0/terrains_peering_bit/bottom_side = 0
|
||||
0:1/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
0:1/0/terrains_peering_bit/left_side = 0
|
||||
0:1/0/terrains_peering_bit/top_left_corner = 1
|
||||
0:1/0/terrains_peering_bit/top_side = 0
|
||||
0:1/0/terrains_peering_bit/top_right_corner = 0
|
||||
1:1/0 = 0
|
||||
1:1/0/terrain_set = 0
|
||||
1:1/0/terrain = 0
|
||||
1:1/0/terrains_peering_bit/right_side = 1
|
||||
1:1/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
1:1/0/terrains_peering_bit/bottom_side = 1
|
||||
1:1/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
1:1/0/terrains_peering_bit/left_side = 0
|
||||
1:1/0/terrains_peering_bit/top_left_corner = 0
|
||||
1:1/0/terrains_peering_bit/top_side = 0
|
||||
1:1/0/terrains_peering_bit/top_right_corner = 1
|
||||
2:1/0 = 0
|
||||
2:1/0/terrain_set = 0
|
||||
2:1/0/terrain = 1
|
||||
2:1/0/terrains_peering_bit/right_side = 1
|
||||
2:1/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
2:1/0/terrains_peering_bit/bottom_side = 1
|
||||
2:1/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
2:1/0/terrains_peering_bit/left_side = 1
|
||||
2:1/0/terrains_peering_bit/top_left_corner = 1
|
||||
2:1/0/terrains_peering_bit/top_side = 1
|
||||
2:1/0/terrains_peering_bit/top_right_corner = 1
|
||||
3:1/0 = 0
|
||||
3:1/0/terrain_set = 0
|
||||
3:1/0/terrain = 0
|
||||
3:1/0/terrains_peering_bit/right_side = 0
|
||||
3:1/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
3:1/0/terrains_peering_bit/bottom_side = 0
|
||||
3:1/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
3:1/0/terrains_peering_bit/left_side = 1
|
||||
3:1/0/terrains_peering_bit/top_left_corner = 1
|
||||
3:1/0/terrains_peering_bit/top_side = 1
|
||||
3:1/0/terrains_peering_bit/top_right_corner = 1
|
||||
0:2/0 = 0
|
||||
0:2/0/terrain_set = 0
|
||||
0:2/0/terrain = 0
|
||||
0:2/0/terrains_peering_bit/right_side = 0
|
||||
0:2/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
0:2/0/terrains_peering_bit/bottom_side = 0
|
||||
0:2/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
0:2/0/terrains_peering_bit/left_side = 0
|
||||
0:2/0/terrains_peering_bit/top_left_corner = 0
|
||||
0:2/0/terrains_peering_bit/top_side = 0
|
||||
0:2/0/terrains_peering_bit/top_right_corner = 1
|
||||
1:2/0 = 0
|
||||
1:2/0/terrain_set = 0
|
||||
1:2/0/terrain = 0
|
||||
1:2/0/terrains_peering_bit/right_side = 0
|
||||
1:2/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
1:2/0/terrains_peering_bit/bottom_side = 0
|
||||
1:2/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
1:2/0/terrains_peering_bit/left_side = 0
|
||||
1:2/0/terrains_peering_bit/top_left_corner = 1
|
||||
1:2/0/terrains_peering_bit/top_side = 1
|
||||
1:2/0/terrains_peering_bit/top_right_corner = 1
|
||||
2:2/0 = 0
|
||||
2:2/0/terrain_set = 0
|
||||
2:2/0/terrain = 0
|
||||
2:2/0/terrains_peering_bit/right_side = 1
|
||||
2:2/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
2:2/0/terrains_peering_bit/bottom_side = 0
|
||||
2:2/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
2:2/0/terrains_peering_bit/left_side = 0
|
||||
2:2/0/terrains_peering_bit/top_left_corner = 1
|
||||
2:2/0/terrains_peering_bit/top_side = 1
|
||||
2:2/0/terrains_peering_bit/top_right_corner = 1
|
||||
3:2/0 = 0
|
||||
3:2/0/terrain_set = 0
|
||||
3:2/0/terrain = 0
|
||||
3:2/0/terrains_peering_bit/right_side = 0
|
||||
3:2/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
3:2/0/terrains_peering_bit/bottom_side = 0
|
||||
3:2/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
3:2/0/terrains_peering_bit/left_side = 1
|
||||
3:2/0/terrains_peering_bit/top_left_corner = 1
|
||||
3:2/0/terrains_peering_bit/top_side = 0
|
||||
3:2/0/terrains_peering_bit/top_right_corner = 0
|
||||
0:3/0 = 0
|
||||
0:3/0/terrain_set = 0
|
||||
0:3/0/terrain = 0
|
||||
0:3/0/terrains_peering_bit/right_side = 0
|
||||
0:3/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
0:3/0/terrains_peering_bit/bottom_side = 0
|
||||
0:3/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
0:3/0/terrains_peering_bit/left_side = 0
|
||||
0:3/0/terrains_peering_bit/top_left_corner = 0
|
||||
0:3/0/terrains_peering_bit/top_side = 0
|
||||
0:3/0/terrains_peering_bit/top_right_corner = 0
|
||||
1:3/0 = 0
|
||||
1:3/0/terrain_set = 0
|
||||
1:3/0/terrain = 0
|
||||
1:3/0/terrains_peering_bit/right_side = 0
|
||||
1:3/0/terrains_peering_bit/bottom_right_corner = 1
|
||||
1:3/0/terrains_peering_bit/bottom_side = 0
|
||||
1:3/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
1:3/0/terrains_peering_bit/left_side = 0
|
||||
1:3/0/terrains_peering_bit/top_left_corner = 0
|
||||
1:3/0/terrains_peering_bit/top_side = 0
|
||||
1:3/0/terrains_peering_bit/top_right_corner = 0
|
||||
2:3/0 = 0
|
||||
2:3/0/terrain_set = 0
|
||||
2:3/0/terrain = 0
|
||||
2:3/0/terrains_peering_bit/right_side = 0
|
||||
2:3/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
2:3/0/terrains_peering_bit/bottom_side = 0
|
||||
2:3/0/terrains_peering_bit/bottom_left_corner = 1
|
||||
2:3/0/terrains_peering_bit/left_side = 0
|
||||
2:3/0/terrains_peering_bit/top_left_corner = 0
|
||||
2:3/0/terrains_peering_bit/top_side = 0
|
||||
2:3/0/terrains_peering_bit/top_right_corner = 1
|
||||
3:3/0 = 0
|
||||
3:3/0/terrain_set = 0
|
||||
3:3/0/terrain = 0
|
||||
3:3/0/terrains_peering_bit/right_side = 0
|
||||
3:3/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
3:3/0/terrains_peering_bit/bottom_side = 0
|
||||
3:3/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
3:3/0/terrains_peering_bit/left_side = 0
|
||||
3:3/0/terrains_peering_bit/top_left_corner = 1
|
||||
3:3/0/terrains_peering_bit/top_side = 0
|
||||
3:3/0/terrains_peering_bit/top_right_corner = 0
|
||||
|
||||
[sub_resource type="TileSet" id="TileSet_ge1l5"]
|
||||
tile_size = Vector2i(32, 32)
|
||||
terrain_set_0/mode = 0
|
||||
terrain_set_0/terrain_0/name = "Grass"
|
||||
terrain_set_0/terrain_0/color = Color(0, 0.5365651, 0.20230255, 1)
|
||||
terrain_set_0/terrain_1/name = "Dirt"
|
||||
terrain_set_0/terrain_1/color = Color(0.5, 0.34375, 0.25, 1)
|
||||
sources/2 = SubResource("TileSetAtlasSource_1r5ec")
|
||||
|
||||
[sub_resource type="TileSetScenesCollectionSource" id="TileSetScenesCollectionSource_1r5ec"]
|
||||
resource_name = "Houses"
|
||||
scenes/1/scene = ExtResource("3_5rqdi")
|
||||
|
||||
[sub_resource type="TileSet" id="TileSet_5rqdi"]
|
||||
tile_size = Vector2i(32, 32)
|
||||
sources/0 = SubResource("TileSetScenesCollectionSource_1r5ec")
|
||||
|
||||
[node name="Hood" type="Node2D"]
|
||||
z_as_relative = false
|
||||
y_sort_enabled = true
|
||||
|
||||
[node name="TileMapLayer" type="TileMapLayer" parent="."]
|
||||
[node name="Ground" type="TileMapLayer" parent="."]
|
||||
z_index = -1
|
||||
tile_map_data = PackedByteArray("AAAQAAkAAgAAAAMAAAAQAAgAAgAAAAMAAAAQAAcAAgAAAAMAAAAQAAYAAgAAAAMAAAAQAAUAAgAAAAMAAAAQAAQAAgAAAAMAAAAQAAMAAgAAAAMAAAAQAAIAAgAAAAMAAAAQAAEAAgAAAAMAAAAQAAAAAgAAAAMAAAAQAP//AgAAAAMAAAAQAP7/AgAAAAMAAAAPAAkAAgAAAAMAAAAPAAgAAgAAAAMAAAAPAAcAAgAAAAMAAAAPAAYAAgADAAMAAAAPAAUAAgADAAIAAAAPAAQAAgAAAAAAAAAPAAMAAgAAAAMAAAAPAAIAAgAAAAMAAAAPAAEAAgAAAAMAAAAPAAAAAgAAAAMAAAAPAP//AgAAAAMAAAAPAP7/AgAAAAMAAAAOAAkAAgAAAAMAAAAOAAgAAgAAAAMAAAAOAAcAAgAAAAMAAAAOAAYAAgABAAIAAAAOAAUAAgACAAEAAAAOAAQAAgADAAAAAAAOAAMAAgAAAAMAAAAOAAIAAgAAAAMAAAAOAAEAAgAAAAMAAAAOAAAAAgAAAAMAAAAOAP//AgAAAAMAAAAOAP7/AgAAAAMAAAANAAkAAgAAAAMAAAANAAgAAgAAAAMAAAANAAcAAgAAAAMAAAANAAYAAgABAAIAAAANAAUAAgACAAEAAAANAAQAAgADAAAAAAANAAMAAgAAAAMAAAANAAIAAgAAAAMAAAANAAEAAgAAAAMAAAANAAAAAgAAAAMAAAANAP//AgAAAAMAAAANAP7/AgAAAAMAAAAMAAkAAgAAAAMAAAAMAAgAAgAAAAMAAAAMAAcAAgAAAAMAAAAMAAYAAgABAAIAAAAMAAUAAgACAAEAAAAMAAQAAgADAAAAAAAMAAMAAgAAAAMAAAAMAAIAAgAAAAMAAAAMAAEAAgAAAAMAAAAMAAAAAgAAAAMAAAAMAP//AgAAAAMAAAAMAP7/AgAAAAMAAAALAAkAAgAAAAMAAAALAAgAAgAAAAMAAAALAAcAAgAAAAMAAAALAAYAAgABAAIAAAALAAUAAgACAAEAAAALAAQAAgADAAAAAAALAAMAAgAAAAMAAAALAAIAAgAAAAMAAAALAAEAAgAAAAMAAAALAAAAAgAAAAMAAAALAP//AgAAAAMAAAALAP7/AgAAAAMAAAAKAAkAAgAAAAMAAAAKAAgAAgAAAAMAAAAKAAcAAgAAAAMAAAAKAAYAAgABAAIAAAAKAAUAAgACAAEAAAAKAAQAAgACAAAAAAAKAAMAAgADAAIAAAAKAAIAAgAAAAAAAAAKAAEAAgAAAAMAAAAKAAAAAgAAAAMAAAAKAP//AgAAAAMAAAAKAP7/AgAAAAMAAAAJAAkAAgAAAAMAAAAJAAgAAgAAAAMAAAAJAAcAAgAAAAMAAAAJAAYAAgABAAIAAAAJAAUAAgACAAEAAAAJAAQAAgACAAEAAAAJAAMAAgACAAEAAAAJAAIAAgADAAAAAAAJAAEAAgAAAAMAAAAJAAAAAgAAAAMAAAAJAP//AgAAAAMAAAAJAP7/AgAAAAMAAAAIAAkAAgAAAAMAAAAIAAgAAgAAAAMAAAAIAAcAAgAAAAMAAAAIAAYAAgABAAIAAAAIAAUAAgACAAEAAAAIAAQAAgABAAEAAAAIAAMAAgABAAAAAAAIAAIAAgABAAMAAAAIAAEAAgAAAAMAAAAIAAAAAgAAAAMAAAAIAP//AgAAAAMAAAAIAP7/AgAAAAMAAAAHAAkAAgAAAAMAAAAHAAgAAgAAAAMAAAAHAAcAAgAAAAMAAAAHAAYAAgABAAIAAAAHAAUAAgACAAEAAAAHAAQAAgADAAAAAAAHAAMAAgAAAAMAAAAHAAIAAgAAAAMAAAAHAAEAAgAAAAMAAAAHAAAAAgAAAAMAAAAHAP//AgAAAAMAAAAHAP7/AgAAAAMAAAAGAAkAAgAAAAMAAAAGAAgAAgAAAAMAAAAGAAcAAgAAAAMAAAAGAAYAAgABAAIAAAAGAAUAAgACAAEAAAAGAAQAAgADAAAAAAAGAAMAAgAAAAMAAAAGAAIAAgAAAAMAAAAGAAEAAgAAAAMAAAAGAAAAAgAAAAMAAAAGAP//AgAAAAMAAAAGAP7/AgAAAAMAAAAFAAkAAgAAAAMAAAAFAAgAAgAAAAMAAAAFAAcAAgAAAAMAAAAFAAYAAgABAAIAAAAFAAUAAgACAAEAAAAFAAQAAgADAAAAAAAFAAMAAgAAAAMAAAAFAAIAAgAAAAMAAAAFAAEAAgAAAAMAAAAFAAAAAgAAAAMAAAAFAP//AgAAAAMAAAAFAP7/AgAAAAMAAAAEAAkAAgAAAAMAAAAEAAgAAgAAAAMAAAAEAAcAAgAAAAMAAAAEAAYAAgABAAIAAAAEAAUAAgACAAEAAAAEAAQAAgACAAAAAAAEAAMAAgADAAIAAAAEAAIAAgADAAIAAAAEAAEAAgAAAAAAAAAEAAAAAgAAAAMAAAAEAP//AgAAAAMAAAAEAP7/AgAAAAMAAAADAAkAAgAAAAMAAAADAAgAAgAAAAMAAAADAAcAAgAAAAMAAAADAAYAAgABAAIAAAADAAUAAgACAAEAAAADAAQAAgACAAEAAAADAAMAAgACAAEAAAADAAIAAgACAAEAAAADAAEAAgADAAAAAAADAAAAAgAAAAMAAAADAP//AgAAAAMAAAADAP7/AgAAAAMAAAACAAkAAgAAAAMAAAACAAgAAgAAAAMAAAACAAcAAgAAAAMAAAACAAYAAgAAAAIAAAACAAUAAgABAAAAAAACAAQAAgACAAIAAAACAAMAAgACAAEAAAACAAIAAgABAAEAAAACAAEAAgABAAMAAAACAAAAAgAAAAMAAAACAP//AgAAAAMAAAACAP7/AgAAAAMAAAABAAkAAgAAAAMAAAABAAgAAgAAAAMAAAABAAcAAgAAAAMAAAABAAYAAgAAAAMAAAABAAUAAgAAAAMAAAABAAQAAgABAAIAAAABAAMAAgACAAEAAAABAAIAAgADAAAAAAABAAEAAgAAAAMAAAABAAAAAgAAAAMAAAABAP//AgAAAAMAAAABAP7/AgAAAAMAAAAAAAkAAgAAAAMAAAAAAAgAAgAAAAMAAAAAAAcAAgAAAAMAAAAAAAYAAgAAAAMAAAAAAAUAAgAAAAMAAAAAAAQAAgABAAIAAAAAAAMAAgACAAEAAAAAAAIAAgADAAAAAAAAAAEAAgAAAAMAAAAAAAAAAgAAAAMAAAAAAP//AgAAAAMAAAAAAP7/AgAAAAMAAAD//wkAAgAAAAMAAAD//wgAAgAAAAMAAAD//wcAAgAAAAMAAAD//wYAAgAAAAMAAAD//wUAAgAAAAMAAAD//wQAAgABAAIAAAD//wMAAgACAAEAAAD//wIAAgADAAAAAAD//wEAAgAAAAMAAAD//wAAAgAAAAMAAAD/////AgAAAAMAAAD///7/AgAAAAMAAAD+/wkAAgAAAAMAAAD+/wgAAgAAAAMAAAD+/wcAAgAAAAMAAAD+/wYAAgAAAAMAAAD+/wUAAgAAAAMAAAD+/wQAAgAAAAIAAAD+/wMAAgABAAAAAAD+/wIAAgABAAMAAAD+/wEAAgAAAAMAAAD+/wAAAgAAAAMAAAD+////AgAAAAMAAAD+//7/AgAAAAMAAAA=")
|
||||
tile_set = SubResource("TileSet_ge1l5")
|
||||
rendering_quadrant_size = 32
|
||||
physics_quadrant_size = 32
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("1_ge1l5")]
|
||||
position = Vector2(128, 76)
|
||||
[node name="Objects" type="TileMapLayer" parent="."]
|
||||
y_sort_enabled = true
|
||||
tile_map_data = PackedByteArray("AAADAAAAAAAAAAAAAQA=")
|
||||
tile_set = SubResource("TileSet_5rqdi")
|
||||
rendering_quadrant_size = 32
|
||||
physics_quadrant_size = 32
|
||||
|
||||
[node name="Player" parent="." node_paths=PackedStringArray("tilemap") instance=ExtResource("1_ge1l5")]
|
||||
position = Vector2(110, 38)
|
||||
tilemap = NodePath("../Ground")
|
||||
|
||||
54
scenes/menus/main_menu.tscn
Normal file
54
scenes/menus/main_menu.tscn
Normal file
@@ -0,0 +1,54 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dgq21ggr3br2g"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bfgqkpn60hhwj" path="res://scripts/menus/main_menu.gd" id="1_xqga4"]
|
||||
[ext_resource type="PackedScene" uid="uid://cd657k2lc3gyc" path="res://scenes/menus/profile_creator.tscn" id="2_ovrgc"]
|
||||
|
||||
[node name="MainMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_xqga4")
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
current_tab = 0
|
||||
tabs_visible = false
|
||||
drag_to_rearrange_enabled = true
|
||||
|
||||
[node name="Main" type="Control" parent="TabContainer"]
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/Main"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -20.0
|
||||
offset_top = -20.0
|
||||
offset_right = 20.0
|
||||
offset_bottom = 20.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Button" type="Button" parent="TabContainer/Main/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Start"
|
||||
|
||||
[node name="ProfileCreator" parent="TabContainer" instance=ExtResource("2_ovrgc")]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
[connection signal="pressed" from="TabContainer/Main/VBoxContainer/Button" to="." method="_on_start_button_pressed"]
|
||||
[connection signal="exit" from="TabContainer/ProfileCreator" to="." method="_on_profile_creator_exit"]
|
||||
58
scenes/menus/profile_creator.tscn
Normal file
58
scenes/menus/profile_creator.tscn
Normal file
@@ -0,0 +1,58 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cd657k2lc3gyc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cs1ylnivyrbc2" path="res://scripts/menus/profile_creator.gd" id="1_4xjd5"]
|
||||
[ext_resource type="FontFile" uid="uid://s0mghd0bccm0" path="res://assets/fonts/monogram-extended.ttf" id="2_yd267"]
|
||||
|
||||
[node name="ProfileCreator" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_4xjd5")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -74.0
|
||||
offset_top = -21.5
|
||||
offset_right = 74.0
|
||||
offset_bottom = 21.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_fonts/font = ExtResource("2_yd267")
|
||||
placeholder_text = "Name him"
|
||||
editable = false
|
||||
context_menu_enabled = false
|
||||
emoji_menu_enabled = false
|
||||
virtual_keyboard_enabled = false
|
||||
virtual_keyboard_show_on_focus = false
|
||||
clear_button_enabled = true
|
||||
selecting_enabled = false
|
||||
caret_blink = true
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="Back" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
text = "Back"
|
||||
|
||||
[node name="Next" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(50, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
text = "Next"
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Back" to="." method="_on_back_button_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Next" to="." method="_on_next_pressed"]
|
||||
191
scenes/menus/util/keyboard.tscn
Normal file
191
scenes/menus/util/keyboard.tscn
Normal file
@@ -0,0 +1,191 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dn0lr1sbir3d3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bxquk4wo56r22" path="res://scripts/menus/util/keyboard.gd" id="1_xrtsm"]
|
||||
[ext_resource type="Texture2D" uid="uid://cfsvkp6w82tgh" path="res://assets/textures/1bit 16px icons part-2.png" id="2_v4hro"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_l3m3g"]
|
||||
atlas = ExtResource("2_v4hro")
|
||||
region = Rect2(296.5, 82, 13, 13)
|
||||
|
||||
[node name="Keyboard" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 3.0
|
||||
offset_top = 1.0
|
||||
offset_right = 3.0
|
||||
offset_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
script = ExtResource("1_xrtsm")
|
||||
|
||||
[node name="TextLabel" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = -1
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.202
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.202
|
||||
offset_left = -125.0
|
||||
offset_top = -6.4000015
|
||||
offset_right = 126.0
|
||||
offset_bottom = 6.5999985
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="MainKeys" type="HFlowContainer" parent="."]
|
||||
custom_minimum_size = Vector2(230, 100)
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -115.0
|
||||
offset_top = -50.5
|
||||
offset_right = 154.0
|
||||
offset_bottom = 50.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Button" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "A"
|
||||
|
||||
[node name="Button2" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "B"
|
||||
|
||||
[node name="Button3" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "C"
|
||||
|
||||
[node name="Button4" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "D"
|
||||
|
||||
[node name="Button5" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "E"
|
||||
|
||||
[node name="Button6" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "F"
|
||||
|
||||
[node name="Button7" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "G"
|
||||
|
||||
[node name="Button8" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "H"
|
||||
|
||||
[node name="Button9" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "I"
|
||||
|
||||
[node name="UShift" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
icon = SubResource("AtlasTexture_l3m3g")
|
||||
|
||||
[node name="Button10" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "J"
|
||||
|
||||
[node name="Button11" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "K"
|
||||
|
||||
[node name="Button12" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "L"
|
||||
|
||||
[node name="Button13" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "M"
|
||||
|
||||
[node name="Button14" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "N"
|
||||
|
||||
[node name="Button15" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "O"
|
||||
|
||||
[node name="Button16" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "P"
|
||||
|
||||
[node name="Button17" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "Q"
|
||||
|
||||
[node name="Button18" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "R"
|
||||
|
||||
[node name="Button19" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "S"
|
||||
|
||||
[node name="Button20" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "T"
|
||||
|
||||
[node name="Button21" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "U"
|
||||
|
||||
[node name="Button22" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "V"
|
||||
|
||||
[node name="Button23" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "W"
|
||||
|
||||
[node name="USpace" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(74, 0)
|
||||
layout_mode = 2
|
||||
text = "Space"
|
||||
|
||||
[node name="Button24" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "X"
|
||||
|
||||
[node name="Button25" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "Y"
|
||||
|
||||
[node name="Button26" type="Button" parent="MainKeys"]
|
||||
custom_minimum_size = Vector2(22, 22)
|
||||
layout_mode = 2
|
||||
text = "Z"
|
||||
@@ -1,17 +1,96 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://dfbomt0l6b1o4"]
|
||||
[gd_scene load_steps=14 format=3 uid="uid://dfbomt0l6b1o4"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dxvslwwnnlosy" path="res://scripts/player.gd" id="1_3vyb7"]
|
||||
[ext_resource type="Texture2D" uid="uid://bivyvi585d2lk" path="res://assets/textures/hood_player.png" id="2_g2els"]
|
||||
[ext_resource type="Texture2D" uid="uid://c12v1hnrbfjr4" path="res://assets/textures/spritesheets/hood_player.png" id="2_g2els"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_g2els"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(0, 0, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qhqgy"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(34, 0, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dqkch"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(68, 0, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_qlg0r"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(0, 34, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tuyoq"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(34, 34, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_fjrip"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(68, 34, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_smehm"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(0, 68, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ur7pv"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(34, 68, 33, 33)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_y4r1p"]
|
||||
atlas = ExtResource("2_g2els")
|
||||
region = Rect2(68, 68, 33, 33)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_qhqgy"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": ExtResource("2_g2els")
|
||||
"texture": SubResource("AtlasTexture_g2els")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qhqgy")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_g2els")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_dqkch")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"default",
|
||||
"speed": 5.0
|
||||
"name": &"down",
|
||||
"speed": 6.5
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qlg0r")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_tuyoq")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_qlg0r")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_fjrip")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"side",
|
||||
"speed": 6.0
|
||||
}, {
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_smehm")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ur7pv")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_smehm")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_y4r1p")
|
||||
}],
|
||||
"loop": true,
|
||||
"name": &"up",
|
||||
"speed": 6.5
|
||||
}]
|
||||
|
||||
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_3vyb7"]
|
||||
@@ -19,12 +98,27 @@ radius = 3.0
|
||||
height = 14.0
|
||||
|
||||
[node name="Player" type="CharacterBody2D"]
|
||||
motion_mode = 1
|
||||
script = ExtResource("1_3vyb7")
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
||||
y_sort_enabled = true
|
||||
sprite_frames = SubResource("SpriteFrames_qhqgy")
|
||||
animation = &"down"
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0, 13)
|
||||
rotation = -1.5707964
|
||||
shape = SubResource("CapsuleShape2D_3vyb7")
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
process_callback = 0
|
||||
drag_horizontal_enabled = true
|
||||
drag_vertical_enabled = true
|
||||
|
||||
[node name="RayCast2D" type="RayCast2D" parent="."]
|
||||
target_position = Vector2(0, 32)
|
||||
collision_mask = 2
|
||||
hit_from_inside = true
|
||||
collide_with_areas = true
|
||||
collide_with_bodies = false
|
||||
|
||||
24
scripts/autoloads/nodes/event_manager.gd
Normal file
24
scripts/autoloads/nodes/event_manager.gd
Normal file
@@ -0,0 +1,24 @@
|
||||
extends Node
|
||||
|
||||
var player_free: bool = true
|
||||
var player: Player
|
||||
|
||||
enum Events { NONE, PUMPKIN_CARVE, OUTSIDE_NORMAL }
|
||||
var current_event: Events = Events.NONE
|
||||
|
||||
func transition_start() -> void:
|
||||
pass
|
||||
|
||||
func transition_end() -> void:
|
||||
pass
|
||||
|
||||
func run_event(event: Events, player_postion: Vector2 = Vector2.ZERO):
|
||||
current_event = event
|
||||
transition_start()
|
||||
if player != null && player_postion != Vector2.ZERO:
|
||||
player.position = player_postion
|
||||
match event:
|
||||
Events.PUMPKIN_CARVE:
|
||||
get_tree().change_scene_to_file("uid://ccfdsdgaon63m") # scenes/levels/home.tscn
|
||||
pass
|
||||
transition_end()
|
||||
1
scripts/autoloads/nodes/event_manager.gd.uid
Normal file
1
scripts/autoloads/nodes/event_manager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b08q3lqbuoolb
|
||||
44
scripts/autoloads/save.gd
Normal file
44
scripts/autoloads/save.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
class_name SaveManager
|
||||
extends Node
|
||||
|
||||
var file: ConfigFile = ConfigFile.new()
|
||||
var save_path: String = "user://save.binary"
|
||||
|
||||
func save(res: SaveFile) -> bool:
|
||||
if not is_instance_valid(res):
|
||||
push_error("cannot save: invalid resource")
|
||||
return false
|
||||
|
||||
if FileAccess.file_exists(save_path) and file.load(save_path) != OK:
|
||||
printerr("Save file corrupted!")
|
||||
return false
|
||||
|
||||
file.set_value("meta", "savedate", Time.get_datetime_string_from_system())
|
||||
|
||||
for property in res.get_property_list():
|
||||
if property.usage & PROPERTY_USAGE_STORAGE:
|
||||
var v_name: String = property.name
|
||||
file.set_value(res.save_name, v_name, res.get(v_name))
|
||||
|
||||
return file.save(save_path) == OK
|
||||
|
||||
func load(res: SaveFile) -> bool:
|
||||
if not FileAccess.file_exists(save_path):
|
||||
return true
|
||||
if file.load(save_path) != OK:
|
||||
printerr("Save file corrupted!")
|
||||
return false
|
||||
|
||||
var props := []
|
||||
for p in res.get_property_list():
|
||||
if p.usage & PROPERTY_USAGE_STORAGE:
|
||||
props.append(p.name)
|
||||
|
||||
for key in file.get_section_keys(res.save_name):
|
||||
if key in props:
|
||||
res.set(key, file.get_value(res.save_name, key))
|
||||
return true
|
||||
|
||||
func delete() -> void:
|
||||
file.clear()
|
||||
file.save(save_path)
|
||||
1
scripts/autoloads/save.gd.uid
Normal file
1
scripts/autoloads/save.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dfgqnmfb710g8
|
||||
@@ -1,10 +1,15 @@
|
||||
extends Node
|
||||
|
||||
@onready var fullscreen: bool = ProjectSettings.get_setting("display/window/size/mode") == Window.MODE_EXCLUSIVE_FULLSCREEN | Window.MODE_FULLSCREEN
|
||||
var config: Config = Config.new("config")
|
||||
|
||||
func _ready() -> void:
|
||||
config.load()
|
||||
toggle_fullscreen()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("fullscreen"):
|
||||
fullscreen = !fullscreen
|
||||
config.fullscreen = !config.fullscreen
|
||||
config.save()
|
||||
toggle_fullscreen()
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
@@ -18,7 +23,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_HIDDEN
|
||||
|
||||
func toggle_fullscreen() -> void:
|
||||
if fullscreen:
|
||||
if config.fullscreen:
|
||||
if OS.get_name() == "Windows":
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
|
||||
else:
|
||||
|
||||
6
scripts/interactable.gd
Normal file
6
scripts/interactable.gd
Normal file
@@ -0,0 +1,6 @@
|
||||
class_name Interactable
|
||||
extends Area2D
|
||||
|
||||
signal interacted
|
||||
|
||||
func interact() -> void: interacted.emit()
|
||||
1
scripts/interactable.gd.uid
Normal file
1
scripts/interactable.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccthj5mtii0bw
|
||||
6
scripts/interactables/interact_sceneswitch.gd
Normal file
6
scripts/interactables/interact_sceneswitch.gd
Normal file
@@ -0,0 +1,6 @@
|
||||
extends Node2D
|
||||
|
||||
@export var scene: PackedScene
|
||||
|
||||
func _on_interactable_interacted() -> void:
|
||||
get_tree().change_scene_to_packed(scene)
|
||||
1
scripts/interactables/interact_sceneswitch.gd.uid
Normal file
1
scripts/interactables/interact_sceneswitch.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c4ejbchoh7yrh
|
||||
9
scripts/menus/main_menu.gd
Normal file
9
scripts/menus/main_menu.gd
Normal file
@@ -0,0 +1,9 @@
|
||||
extends Control
|
||||
|
||||
@onready var tab_container: TabContainer = $TabContainer
|
||||
|
||||
func _on_start_button_pressed() -> void:
|
||||
tab_container.current_tab = 1
|
||||
|
||||
func _on_profile_creator_exit() -> void:
|
||||
tab_container.current_tab = 0
|
||||
1
scripts/menus/main_menu.gd.uid
Normal file
1
scripts/menus/main_menu.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bfgqkpn60hhwj
|
||||
45
scripts/menus/profile_creator.gd
Normal file
45
scripts/menus/profile_creator.gd
Normal file
@@ -0,0 +1,45 @@
|
||||
extends Control
|
||||
|
||||
signal exit
|
||||
|
||||
var profile: Profile = Profile.new("profile")
|
||||
@onready var next: Button = $VBoxContainer/HBoxContainer/Next
|
||||
@onready var line_edit: LineEdit = $VBoxContainer/LineEdit
|
||||
|
||||
@onready var max_index: int = profile.named_things.keys().size() - 1
|
||||
var index: int = 0
|
||||
|
||||
func _ready() -> void:
|
||||
next.disabled = index == max_index
|
||||
|
||||
func _on_back_button_pressed() -> void:
|
||||
if index == 0:
|
||||
exit.emit()
|
||||
else:
|
||||
index -= 1
|
||||
update_name()
|
||||
|
||||
func _on_next_pressed() -> void:
|
||||
index += 1
|
||||
update_name()
|
||||
|
||||
func update_name() -> void:
|
||||
if index == max_index:
|
||||
next.text = "Finish"
|
||||
elif index > max_index:
|
||||
profile.save()
|
||||
EventManager.run_event(EventManager.Events.PUMPKIN_CARVE)
|
||||
return
|
||||
else:
|
||||
next.text = "Next"
|
||||
|
||||
var labels: Dictionary = {
|
||||
"player_name": "Name him",
|
||||
"villain_friend": "Name him too",
|
||||
"stupid_friend": "And also name him",
|
||||
"favourite_candy": "Favourite candy?"
|
||||
}
|
||||
|
||||
var label: String = labels[profile.named_things.keys()[index]]
|
||||
line_edit.placeholder_text = label
|
||||
|
||||
1
scripts/menus/profile_creator.gd.uid
Normal file
1
scripts/menus/profile_creator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cs1ylnivyrbc2
|
||||
33
scripts/menus/util/keyboard.gd
Normal file
33
scripts/menus/util/keyboard.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
extends Control
|
||||
|
||||
signal text_changed
|
||||
signal key_pressed
|
||||
|
||||
@onready var main_keys: HFlowContainer = $MainKeys
|
||||
@onready var text_label: Label = $TextLabel
|
||||
|
||||
@export var text: String = "":
|
||||
set(value):
|
||||
text = value
|
||||
text_label.text = text
|
||||
text_changed.emit()
|
||||
|
||||
var shifting: bool = true
|
||||
|
||||
func _ready() -> void:
|
||||
for key: Button in main_keys.get_children():
|
||||
key.connect("pressed", func():
|
||||
key_pressed.emit()
|
||||
match key.name:
|
||||
"UShift":
|
||||
shifting = !shifting
|
||||
for i_key: Button in main_keys.get_children():
|
||||
if shifting:
|
||||
i_key.text = i_key.text.to_upper()
|
||||
else:
|
||||
i_key.text = i_key.text.to_lower()
|
||||
"USpace":
|
||||
text += " "
|
||||
_:
|
||||
text += key.text
|
||||
)
|
||||
1
scripts/menus/util/keyboard.gd.uid
Normal file
1
scripts/menus/util/keyboard.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bxquk4wo56r22
|
||||
@@ -1,7 +1,69 @@
|
||||
class_name Player
|
||||
extends CharacterBody2D
|
||||
|
||||
const SPEED: int = 500
|
||||
const SPEED: int = 800
|
||||
|
||||
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
|
||||
@onready var cam: Camera2D = $Camera2D
|
||||
@onready var raycast: RayCast2D = $RayCast2D
|
||||
const RAYCAST_RAGE: int = 32
|
||||
|
||||
|
||||
@export var tilemap: TileMapLayer
|
||||
var tilesize: int = 32
|
||||
var position_limit_rect: Rect2
|
||||
|
||||
func _ready() -> void:
|
||||
EventManager.player = self
|
||||
var used_tilemap_rect: Rect2i = tilemap.get_used_rect()
|
||||
tilesize = tilemap.tile_set.tile_size.x
|
||||
|
||||
cam.limit_left = used_tilemap_rect.position.x * tilesize
|
||||
cam.limit_top = used_tilemap_rect.position.y * tilesize
|
||||
cam.limit_right = used_tilemap_rect.end.x * tilesize
|
||||
cam.limit_bottom = used_tilemap_rect.end.y * tilesize
|
||||
|
||||
var margin: float = float(tilesize) / 4
|
||||
|
||||
position_limit_rect = Rect2(
|
||||
Vector2(cam.limit_left + margin, cam.limit_top - margin),
|
||||
Vector2(
|
||||
(cam.limit_right - margin) - (cam.limit_left + margin),
|
||||
(cam.limit_bottom - margin / 2) - (cam.limit_top + margin / 2)
|
||||
)
|
||||
)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("interact") && raycast.is_colliding() && raycast.get_collider() is Interactable:
|
||||
var interactable: Interactable = raycast.get_collider()
|
||||
interactable.interact()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
velocity = Input.get_vector("move_left","move_right","move_up","move_down") * delta * SPEED * 3
|
||||
if EventManager.player_free:
|
||||
var input := Input.get_vector("move_left", "move_right", "move_up", "move_down").normalized()
|
||||
|
||||
var raycast_input := Vector2.ZERO
|
||||
if abs(input.x) > abs(input.y):
|
||||
raycast_input.x = sign(input.x)
|
||||
elif abs(input.y) > 0:
|
||||
raycast_input.y = sign(input.y)
|
||||
if raycast_input != Vector2.ZERO:
|
||||
raycast.target_position = raycast_input * RAYCAST_RAGE
|
||||
|
||||
velocity = input * delta * SPEED * 3
|
||||
|
||||
move_and_slide()
|
||||
position = position.clamp(position_limit_rect.position, position_limit_rect.end)
|
||||
|
||||
if velocity.length() != 0:
|
||||
animated_sprite.play()
|
||||
animated_sprite.flip_h = false
|
||||
if abs(velocity.x) > abs(velocity.y):
|
||||
animated_sprite.animation = "side"
|
||||
animated_sprite.flip_h = velocity.x < 0
|
||||
elif velocity.y < 0:
|
||||
animated_sprite.animation = "up"
|
||||
else:
|
||||
animated_sprite.animation = "down"
|
||||
else:
|
||||
animated_sprite.stop()
|
||||
|
||||
4
scripts/resources/config.gd
Normal file
4
scripts/resources/config.gd
Normal file
@@ -0,0 +1,4 @@
|
||||
class_name Config
|
||||
extends SaveFile
|
||||
|
||||
@export var fullscreen: bool = ProjectSettings.get_setting("display/window/size/mode") == Window.MODE_EXCLUSIVE_FULLSCREEN | Window.MODE_FULLSCREEN
|
||||
1
scripts/resources/config.gd.uid
Normal file
1
scripts/resources/config.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b0xr7lumdnvci
|
||||
9
scripts/resources/profile.gd
Normal file
9
scripts/resources/profile.gd
Normal file
@@ -0,0 +1,9 @@
|
||||
class_name Profile
|
||||
extends SaveFile
|
||||
|
||||
@export var named_things: Dictionary[StringName, String] = {
|
||||
"player_name": "Joel",
|
||||
"villain_friend": "Josh",
|
||||
"stupid_friend": "Kevin",
|
||||
"favourite_candy": "Chocolate"
|
||||
}
|
||||
1
scripts/resources/profile.gd.uid
Normal file
1
scripts/resources/profile.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://sx081r57538w
|
||||
14
scripts/resources/save_file.gd
Normal file
14
scripts/resources/save_file.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
class_name SaveFile
|
||||
extends Resource
|
||||
|
||||
@export var save_name = "Save"
|
||||
|
||||
func _init(p_save_name = "Save") -> void:
|
||||
save_name = p_save_name
|
||||
|
||||
func save() -> void: Save.save(self)
|
||||
func load() -> void: Save.load(self)
|
||||
|
||||
func reset() -> void:
|
||||
Save.file.erase_section(save_name)
|
||||
Save.file.save(Save.save_path)
|
||||
1
scripts/resources/save_file.gd.uid
Normal file
1
scripts/resources/save_file.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dru7lnuaadb87
|
||||
51
shaders/pixelate.gdshader
Normal file
51
shaders/pixelate.gdshader
Normal file
@@ -0,0 +1,51 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
/**
|
||||
0 = Texture: For use with Sprite2Ds, TextureRects, and Meshes
|
||||
1 = Screen: For use with ColorRect
|
||||
*/
|
||||
uniform int texture_mode : hint_range(0,1) = 1;
|
||||
|
||||
// This file relies on a quantize shader include file, listed further down.
|
||||
// The paths must match your file's location.
|
||||
#include "res://shaders/quantize.gdshaderinc"
|
||||
|
||||
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, repeat_disable, filter_nearest;
|
||||
|
||||
void vertex() {
|
||||
float zoom = length(CANVAS_MATRIX[1].xyz);
|
||||
v_quant_size = getQuantizeSize(zoom);
|
||||
v_model_matrix = MODEL_MATRIX;
|
||||
v_vertex = VERTEX;
|
||||
|
||||
if (texture_mode == 0) {
|
||||
v_alt_matrix = inverse(MODEL_MATRIX);
|
||||
v_texture_data.xy = 1. / TEXTURE_PIXEL_SIZE;
|
||||
// Is texture flipped
|
||||
v_texture_data.zw = max(-sign((UV - 0.5) * VERTEX), 0.);
|
||||
} else {
|
||||
v_alt_matrix = SCREEN_MATRIX * CANVAS_MATRIX;
|
||||
|
||||
if (snap_to_world) {
|
||||
v_alt_matrix = inverse(v_alt_matrix);
|
||||
}
|
||||
|
||||
vec2 local_origin = (MODEL_MATRIX * vec4(0.0, 0.0, 0, 1)).xy;
|
||||
vec2 clip = (v_alt_matrix * vec4(local_origin, 0,1)).xy;
|
||||
vec2 screen_origin_uv = clip * 0.5 + 0.5;
|
||||
vec2 screen_pixel_size = 1. / vec2(textureSize(SCREEN_TEXTURE, 0));
|
||||
vec2 q = screen_pixel_size * float(v_quant_size) * zoom;
|
||||
v_texture_data.xy = screen_origin_uv;
|
||||
v_texture_data.zw = q;
|
||||
}
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
if (texture_mode == 0) {
|
||||
vec4 uvResult = getQuantizeTextureUV(UV);
|
||||
COLOR = texture(TEXTURE, uvResult.xy);
|
||||
} else {
|
||||
vec4 uvResult = getQuantizeScreenUV(SCREEN_UV);
|
||||
COLOR = texture(SCREEN_TEXTURE, uvResult.xy);
|
||||
}
|
||||
}
|
||||
1
shaders/pixelate.gdshader.uid
Normal file
1
shaders/pixelate.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dgstw8crggyti
|
||||
104
shaders/quantize.gdshaderinc
Normal file
104
shaders/quantize.gdshaderinc
Normal file
@@ -0,0 +1,104 @@
|
||||
group_uniforms Quantization;
|
||||
/**Pixel resolution scale (0 is bypass)*/
|
||||
uniform int quantize_size : hint_range(0,100) = 1;
|
||||
/**Pixels are snapped based on world coordinates vs local coordinates.*/
|
||||
uniform bool snap_to_world;
|
||||
/**Auto-scales the quantize size when zoom is < 1, preventing subpixel artifacts*/
|
||||
uniform bool limit_subpixels = true;
|
||||
group_uniforms;
|
||||
|
||||
varying mat4 v_model_matrix;
|
||||
varying mat4 v_alt_matrix;
|
||||
varying vec2 v_vertex;
|
||||
varying flat int v_quant_size;
|
||||
varying flat vec4 v_texture_data;
|
||||
|
||||
const float EPSILON = 0.0001;
|
||||
|
||||
int getQuantizeSize(float in_zoom) {
|
||||
int q_size = quantize_size;
|
||||
|
||||
if (limit_subpixels && in_zoom < 1.) {
|
||||
q_size = int(round(float(quantize_size) * (1. / in_zoom)));
|
||||
}
|
||||
|
||||
return q_size;
|
||||
}
|
||||
|
||||
vec2 _snap(vec2 in_uv, float in_q_size) {
|
||||
return (floor(in_uv / in_q_size) + 0.5) * in_q_size;
|
||||
}
|
||||
|
||||
vec4 getQuantizeScreenUV(vec2 in_screen_uv) {
|
||||
vec4 result;
|
||||
|
||||
if (v_quant_size == 0) {
|
||||
result.xy = in_screen_uv;
|
||||
result.zw = (v_model_matrix * vec4(v_vertex, 0, 1)).xy;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (snap_to_world) {
|
||||
vec2 uv = (v_model_matrix * vec4(v_vertex, 0, 1)).xy;
|
||||
result.zw = uv + EPSILON;
|
||||
uv = _snap(uv, float(v_quant_size));
|
||||
uv = (v_alt_matrix * vec4(uv, 0,1)).xy;
|
||||
uv = uv * 0.5 + 0.5;
|
||||
result.xy = uv;
|
||||
return result;
|
||||
} else {
|
||||
vec2 origin_uv = v_texture_data.xy;
|
||||
vec2 quant_pixel_size = v_texture_data.zw;
|
||||
vec2 uv = in_screen_uv - origin_uv;
|
||||
uv = (floor(uv / quant_pixel_size) + 0.5) * quant_pixel_size;
|
||||
|
||||
uv = uv + origin_uv;
|
||||
vec2 clipXY = uv * 2.0 - 1.;
|
||||
vec2 world_pos = (v_alt_matrix * vec4(clipXY, 0.0, 1.0)).xy;
|
||||
result.zw = world_pos + EPSILON;
|
||||
result.xy = uv;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
vec4 getQuantizeTextureUV(vec2 in_uv) {
|
||||
vec4 result;
|
||||
vec2 texture_size = v_texture_data.xy;
|
||||
vec2 flip = v_texture_data.zw;
|
||||
|
||||
if (v_quant_size == 0) {
|
||||
result.xy = in_uv;
|
||||
result.zw = (v_model_matrix * vec4(in_uv * texture_size, 0, 1)).xy;
|
||||
return result;
|
||||
}
|
||||
|
||||
vec2 offset;
|
||||
float q = float(v_quant_size);
|
||||
vec2 uv = in_uv;
|
||||
uv = mix(uv, 1.0 - uv, flip);
|
||||
|
||||
if (snap_to_world) {
|
||||
vec2 inv_texture_size = 1. / texture_size;
|
||||
offset = v_vertex * inv_texture_size;
|
||||
offset = uv - offset;
|
||||
uv -= offset;
|
||||
|
||||
uv *= texture_size;
|
||||
uv = (v_model_matrix * vec4(uv, 0, 1)).xy;
|
||||
result.zw = uv;
|
||||
uv = _snap(uv, q);
|
||||
uv = (v_alt_matrix * vec4(uv, 0, 1)).xy;
|
||||
uv *= inv_texture_size;
|
||||
|
||||
uv = offset + uv;
|
||||
} else {
|
||||
uv *= texture_size;
|
||||
result.zw = uv;
|
||||
uv = _snap(uv, q);
|
||||
uv /= texture_size;
|
||||
}
|
||||
|
||||
uv = mix(uv, 1.0 - uv, flip);
|
||||
result.xy = uv + EPSILON;
|
||||
return result;
|
||||
}
|
||||
1
shaders/quantize.gdshaderinc.uid
Normal file
1
shaders/quantize.gdshaderinc.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dp4ue4i3i5iaf
|
||||
Reference in New Issue
Block a user