├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── addons └── ascii_grid │ ├── ascii_grid.gd │ ├── plugin.cfg │ ├── term_buffer.gd │ ├── term_cell.gd │ ├── term_cell_map.gd │ ├── term_container.gd │ ├── term_container_border_config.gd │ ├── term_container_hbox.gd │ ├── term_container_title.gd │ ├── term_container_vbox.gd │ ├── term_element.gd │ ├── term_label.gd │ ├── term_progress.gd │ ├── term_rect.gd │ ├── term_shader.gdshader │ └── term_single_cell.gd ├── examples └── UI_Mockup │ ├── UiMockup.gd │ ├── UiMockup.tscn │ ├── UiMockupScreenshot.png │ ├── UiMockupScreenshot.png.import │ ├── terminus_8x16.png │ └── terminus_8x16.png.import ├── icon.svg ├── icon.svg.import └── project.godot /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | export.cfg 4 | export_presets.cfg 5 | 6 | # Imported translations (automatically generated from CSV files) 7 | *.translation 8 | 9 | # Mono-specific ignores 10 | .mono/ 11 | data_*/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Selina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GODOT 4 ASCII GRID 2 | 3 | This is an addon for Godot 4 which allows drawing of grid of colored ASCII characters. 4 | 5 | ![Screenshot of an example for the ASCII grid. It depicts a mockup for a typical roguelike interface.](https://raw.githubusercontent.com/SelinaDev/Godot-4-ASCII-Grid/main/examples/UI_Mockup/UiMockupScreenshot.png) 6 | -------------------------------------------------------------------------------- /addons/ascii_grid/ascii_grid.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | 5 | func _enter_tree() -> void: 6 | # Initialization of the plugin goes here. 7 | pass 8 | 9 | 10 | func _exit_tree() -> void: 11 | # Clean-up of the plugin goes here. 12 | pass 13 | -------------------------------------------------------------------------------- /addons/ascii_grid/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="ASCII Grid" 4 | description="A set tools to handle rendering to a terminal-style ASCII grid." 5 | author="SelinaDev" 6 | version="" 7 | script="ascii_grid.gd" 8 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_buffer.gd: -------------------------------------------------------------------------------- 1 | class_name TermBuffer 2 | extends RefCounted 3 | 4 | ## A buffer for the terminal 5 | ## 6 | ## This is a buffer containing [TermCell]s corresponding to positions in the terminal's grid. 7 | 8 | var _grid := {} 9 | var _size: Vector2i 10 | var _draw_region: Rect2i 11 | 12 | 13 | func _init(buffer_size: Vector2i) -> void: 14 | _size = buffer_size 15 | _draw_region = Rect2i(Vector2i.ZERO, _size) 16 | _grid = {} 17 | 18 | ## Specifies the active draw region of the buffer. Calls to [method TermBuffer.put_cell] will be filtered by this region, so that trying to set a cell outside the draw region will have no effect. 19 | ## This is mainly used by [TermElement] nodes to restrict drawing to their own area. 20 | func set_draw_region(draw_region: Rect2i) -> void: 21 | _draw_region = draw_region 22 | 23 | ## Returns the buffer's current draw region. 24 | func get_draw_region() -> Rect2i: 25 | return _draw_region 26 | 27 | ## Resets the draw region to include the whole size of the buffer. See [method TermBuffer.set_draw_region]. 28 | func reset_draw_region() -> void: 29 | _draw_region = Rect2i(Vector2i.ZERO, _size) 30 | 31 | ## Sets an individual cell in the buffer. This method is filtered by the current draw region of the buffer (see [method TermBuffer.set_draw_region]. Placing a cell outside the current draw region has no effect. 32 | func put_cell(cell: TermCell, at: Vector2i) -> void: 33 | if _draw_region.has_point(at): 34 | _grid[at] = cell 35 | 36 | 37 | ## Returns the cell as the specified position from the buffer. Returns [code]null[/code] if no cell is present at that position. 38 | func get_cell(at: Vector2i) -> TermCell: 39 | return _grid.get(at, null) 40 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_cell.gd: -------------------------------------------------------------------------------- 1 | class_name TermCell 2 | extends Resource 3 | 4 | ## Terminal Cell representing a cell that can be rendered to a terminal 5 | ## 6 | 7 | const _CHAR_MAPPING := { "": 0, "☺": 1, "☻": 2, "♥": 3, "♦": 4, "♣": 5, "♠": 6, "•": 7, "◘": 8, "○": 9, "◙": 10, "♂": 11, "♀": 12, "♪": 13, "♫": 14, "☼": 15, "►": 16, "◄": 17, "↕": 18, "‼": 19, "¶": 20, "§": 21, "▬": 22, "↨": 23, "↑": 24, "↓": 25, "→": 26, "←": 27, "∟": 28, "↔": 29, "▲": 30, "▼": 31, " ": 32, "!": 33, "\"": 34, "#": 35, "$": 36, "%": 37, "&": 38, "\'": 39, "(": 40, ")": 41, "*": 42, "+": 43, ",": 44, "-": 45, ".": 46, "/": 47, "0": 48, "1": 49, "2": 50, "3": 51, "4": 52, "5": 53, "6": 54, "7": 55, "8": 56, "9": 57, ":": 58, ";": 59, "<": 60, "=": 61, ">": 62, "?": 63, "@": 64, "A": 65, "B": 66, "C": 67, "D": 68, "E": 69, "F": 70, "G": 71, "H": 72, "I": 73, "J": 74, "K": 75, "L": 76, "M": 77, "N": 78, "O": 79, "P": 80, "Q": 81, "R": 82, "S": 83, "T": 84, "U": 85, "V": 86, "W": 87, "X": 88, "Y": 89, "Z": 90, "[": 91, "\\": 92, "]": 93, "^": 94, "_": 95, "`": 96, "a": 97, "b": 98, "c": 99, "d": 100, "e": 101, "f": 102, "g": 103, "h": 104, "i": 105, "j": 106, "k": 107, "l": 108, "m": 109, "n": 110, "o": 111, "p": 112, "q": 113, "r": 114, "s": 115, "t": 116, "u": 117, "v": 118, "w": 119, "x": 120, "y": 121, "z": 122, "{": 123, "|": 124, "}": 125, "~": 126, "⌂": 127, "Ç": 128, "ü": 129, "é": 130, "â": 131, "ä": 132, "à": 133, "å": 134, "ç": 135, "ê": 136, "ë": 137, "è": 138, "ï": 139, "î": 140, "ì": 141, "Ä": 142, "Å": 143, "É": 144, "æ": 145, "Æ": 146, "ô": 147, "ö": 148, "ò": 149, "û": 150, "ù": 151, "ÿ": 152, "Ö": 153, "Ü": 154, "¢": 155, "£": 156, "¥": 157, "₧": 158, "ƒ": 159, "á": 160, "í": 161, "ó": 162, "ú": 163, "ñ": 164, "Ñ": 165, "ª": 166, "º": 167, "¿": 168, "⌐": 169, "¬": 170, "½": 171, "¼": 172, "¡": 173, "«": 174, "»": 175, "░": 176, "▒": 177, "▓": 178, "│": 179, "┤": 180, "╡": 181, "╢": 182, "╖": 183, "╕": 184, "╣": 185, "║": 186, "╗": 187, "╝": 188, "╜": 189, "╛": 190, "┐": 191, "└": 192, "┴": 193, "┬": 194, "├": 195, "─": 196, "┼": 197, "╞": 198, "╟": 199, "╚": 200, "╔": 201, "╩": 202, "╦": 203, "╠": 204, "═": 205, "╬": 206, "╧": 207, "╨": 208, "╤": 209, "╥": 210, "╙": 211, "╘": 212, "╒": 213, "╓": 214, "╫": 215, "╪": 216, "┘": 217, "┌": 218, "█": 219, "▄": 220, "▌": 221, "▐": 222, "▀": 223, "α": 224, "ß": 225, "Γ": 226, "π": 227, "Σ": 228, "σ": 229, "µ": 230, "τ": 231, "Φ": 232, "Θ": 233, "Ω": 234, "δ": 235, "∞": 236, "φ": 237, "ε": 238, "∩": 239, "≡": 240, "±": 241, "≥": 242, "≤": 243, "⌠": 244, "⌡": 245, "÷": 246, "≈": 247, "°": 248, "∙": 249, "·": 250, "√": 251, "ⁿ": 252, "²": 253, "■": 254, " ": 255 } 8 | 9 | 10 | ## The (unicode) character that should be rendered. This must be a single character from codepage 437. 11 | ## If this property is set to a longer string, only the first character of that string will be used. 12 | @export var character: String = " ": 13 | set(value): 14 | character = value.substr(0, 1) 15 | _set_character_id() 16 | ## Foreground color. This is the color the character itself will be rendered in. Does not support transparency. 17 | @export_color_no_alpha var fg_color: Color = Color.WHITE 18 | ## Background color. This is the color the background of the cell will be rendered in. Does not support transparency. 19 | @export_color_no_alpha var bg_color: Color = Color.BLACK 20 | 21 | var _character_id: int 22 | 23 | ## Constructor definition. This can be used to create a new [TermCell] in code. 24 | ## [codeblock] 25 | ## var cell = Cell.new("@", Color.RED, Color.BLUE) 26 | ## [/codeblock] 27 | ## This constructor uses default arguments. The following will create a [TermCell] with the character [i]S[/i], with white foreground (default) and black background (default). 28 | ## [codeblock] 29 | ## var cell_2 = Cell.new("S") 30 | ## [/codeblock] 31 | ## Similarly, just calling [code]Cell.new()[/code] will create a [TermCell] with a space character (technically in white, but not drawn in most fonts) and black background. 32 | func _init(c: String = " ", fg: Color = Color.WHITE, bg: Color = Color.BLACK) -> void: 33 | character = c 34 | fg_color = fg 35 | bg_color = bg 36 | 37 | ## Returns the id of this cells character. This id is the numerical position of the character within codepage 437. 38 | ## If the character is not part of codepage 437, this function will return the id of a space character (i.e., the cell will be rendered as an empty cell). 39 | func get_character_id() -> int: 40 | return _character_id 41 | 42 | 43 | func _set_character_id() -> void: 44 | _character_id = _CHAR_MAPPING.get(character, 32) + 1 45 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_cell_map.gd: -------------------------------------------------------------------------------- 1 | class_name TermCellMap 2 | extends TermContainer 3 | 4 | ## A node holding a grid of [TermCell]s that can be edited directly. 5 | ## 6 | ## This node holds a grid of [TermCell]s, that automatically get drawn to the buffer. 7 | ## Can be used either to directly manipulate the grid, or, e.g., as a background layer that does not change a lot. 8 | 9 | var _grid: Dictionary = {} 10 | var _draw_grid: Dictionary 11 | var _recalculate_draw_grid := false 12 | 13 | ## Offset to be applied to the grid when drawing. 14 | ## Per default drawing of the grid starts at [0, 0] in the top left corner. The [property TermCellMap.offset] can move this point. 15 | ## This could be used to implement, e.g., a camera system. 16 | @export var offset: Vector2i = Vector2i.ZERO: 17 | set(value): 18 | offset = value 19 | _redraw_required = true 20 | 21 | 22 | ## Clears the internal grid of the node. 23 | func clear() -> void: 24 | _grid = {} 25 | _redraw_required = true 26 | 27 | ## Places the provided [parameter cell] at the specified point in the grid. 28 | func put_cell(cell: TermCell, at: Vector2i) -> void: 29 | _grid[at] = cell 30 | _redraw_required = true 31 | _recalculate_draw_grid = true 32 | 33 | ## Returns the cell stored at the specified position. 34 | ## Returns [code]null[/code] if no cell is set at that position. 35 | func get_cell(at: Vector2i) -> TermCell: 36 | return _grid.get(at, null) 37 | 38 | 39 | func _blit_self_under(buffer: TermBuffer) -> void: 40 | if _recalculate_draw_grid: 41 | _set_draw_grid() 42 | _recalculate_draw_grid = false 43 | for position: Vector2i in _draw_grid: 44 | buffer.put_cell(_draw_grid[position], position) 45 | 46 | 47 | func _set_draw_grid() -> void: 48 | for x in _rect.size.x: 49 | for y in _rect.size.y: 50 | var position := Vector2i(x, y) + offset 51 | var draw_pos: Vector2i = _rect.position + position - offset 52 | var cell: TermCell = _grid.get(position, null) 53 | if cell: 54 | _draw_grid[draw_pos] = cell 55 | 56 | 57 | func _update_sizing() -> void: 58 | _set_draw_grid() 59 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_container.gd: -------------------------------------------------------------------------------- 1 | class_name TermContainer 2 | extends TermElement 3 | 4 | ## Base class for containing other [TermElement]s. 5 | ## 6 | ## This is the base container class of the ASCII terminal system. 7 | ## When determining sizes, this node will simply stretch all it's children's size rects to it's own size. 8 | 9 | ## If [property border] is set, the container will draw a border around its outer edge. 10 | ## Styling of this border can be determined within the provided [TermContainerBorderConfig]. 11 | @export var border: TermContainerBorderConfig: 12 | set(value): 13 | border = value 14 | _redraw_required = true 15 | if border: 16 | border.changed.connect(_set_dirty) 17 | ## If both [property border] and [property title] are set, a title will be drawn at the top edge of the border. 18 | ## The title string itself, as well as its styling can be determined with the provided [TermContainerTitle]. 19 | @export var title: TermContainerTitle: 20 | set(value): 21 | title = value 22 | _redraw_required = true 23 | if title: 24 | title.changed.connect(_set_dirty) 25 | 26 | 27 | ## The rectangle available for drawing content in this container. 28 | ## This region does not include the [property border], if set. 29 | func get_content_rect() -> Rect2i: 30 | if border: 31 | return _rect.grow(-1) 32 | return _rect 33 | 34 | ## Container version of [method TermElement.update_sizing]. Internally calls [method TermContainer._arrange_children]. 35 | func update_sizing() -> void: 36 | _arrange_children() 37 | super() 38 | 39 | ## Arranges child nodes accoriding to container rules. 40 | ## Custom containers inheriting [TermContainer] should override this method. 41 | func _arrange_children() -> void: 42 | var total_content_rect: Rect2i = get_content_rect() 43 | for child: TermElement in _children: 44 | child.set_rect(total_content_rect) 45 | 46 | 47 | func _blit_self_over(buffer: TermBuffer) -> void: 48 | if border: 49 | _draw_border(buffer) 50 | if title: 51 | _draw_title(buffer) 52 | 53 | 54 | func _draw_border(buffer: TermBuffer) -> void: 55 | if not border: return 56 | 57 | var cell_top := TermCell.new(border.top_border, border.fg_color, border.bg_color) 58 | var cell_bottom := TermCell.new(border.bottom_border, border.fg_color, border.bg_color) 59 | for x in range(_rect.position.x, _rect.end.x): 60 | buffer.put_cell(cell_top, Vector2i(x, _rect.position.y)) 61 | buffer.put_cell(cell_bottom, Vector2i(x, _rect.end.y - 1)) 62 | 63 | var cell_left := TermCell.new(border.left_border, border.fg_color, border.bg_color) 64 | var cell_right := TermCell.new(border.right_border, border.fg_color, border.bg_color) 65 | for y in range(_rect.position.y, _rect.end.y): 66 | buffer.put_cell(cell_left, Vector2i(_rect.position.x, y)) 67 | buffer.put_cell(cell_left, Vector2i(_rect.end.x - 1, y)) 68 | 69 | var corners: Array[Vector2i] = [ 70 | _rect.position, 71 | _rect.position + Vector2i(_rect.size.x - 1, 0), 72 | _rect.position + Vector2i(0, _rect.size.y - 1), 73 | _rect.end - Vector2i.ONE 74 | ] 75 | var corner_chars: Array[String] = [ 76 | border.top_left_corner, 77 | border.top_right_corner, 78 | border.bottom_left_corner, 79 | border.bottom_right_corner 80 | ] 81 | for i in 4: 82 | buffer.put_cell(TermCell.new(corner_chars[i], border.fg_color, border.bg_color), corners[i]) 83 | 84 | func _draw_title(buffer: TermBuffer) -> void: 85 | if not title: return 86 | 87 | var title_start_pos := Vector2i.ZERO 88 | title_start_pos.y = _rect.position.y 89 | match title.alignment: 90 | TermContainerTitle.Alignment.LEFT: 91 | title_start_pos.x = _rect.position.x + 1 92 | TermContainerTitle.Alignment.CENTER: 93 | title_start_pos.x = _rect.position.x + _rect.size.x / 2 - title.title.length() / 2 94 | TermContainerTitle.Alignment.RIGHT: 95 | title_start_pos.x = _rect.end.x - 1 - title.length() 96 | for i: int in title.title.length(): 97 | var draw_pos: Vector2i = title_start_pos + Vector2i.RIGHT * i 98 | buffer.put_cell(TermCell.new(title.title[i], title.fg_color, title.bg_color), draw_pos) 99 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_container_border_config.gd: -------------------------------------------------------------------------------- 1 | class_name TermContainerBorderConfig 2 | extends Resource 3 | 4 | ## Border configuration resource to be used by a [TermContainer]. 5 | 6 | @export_category("Border Characters") 7 | @export var top_border: String = "─" 8 | @export var bottom_border: String = "─" 9 | @export var left_border: String = "│" 10 | @export var right_border: String = "│" 11 | @export var top_left_corner: String = "┌" 12 | @export var top_right_corner: String = "┐" 13 | @export var bottom_left_corner: String = "└" 14 | @export var bottom_right_corner: String = "┘" 15 | 16 | @export_category("Border Colors") 17 | @export var fg_color: Color = Color.WHITE ## Foreground color of the corder characters. 18 | @export var bg_color: Color = Color.BLACK ## Background color of the border characters. 19 | 20 | 21 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_container_hbox.gd: -------------------------------------------------------------------------------- 1 | class_name TermContainerHBox 2 | extends TermContainer 3 | 4 | ## A ASCII terminal container arranging its children horizontally. 5 | ## 6 | ## This container works analogous to [HBoxContainer], and will try to distribute it's children horizontally. 7 | ## All children will be stretched to the height available to this container. 8 | ## Any children with a set fixed width (i.e., [code]fixed_size.x[/code] greater than [code]0[/code]) will be set to that width. 9 | ## The remaining width will be evenly distributed between all children without fixed width (i.e., [code]fixed_size.x[/code] set to [code]0[/code]). 10 | 11 | func _arrange_children() -> void: 12 | var total_content_rect: Rect2i = get_content_rect() 13 | var available_width: int = total_content_rect.size.x - _children.reduce(func(accum: int, child: TermElement) -> int: return accum + child.fixed_size.x, 0) 14 | var children_to_distribute: int = _children.filter(func(child: TermElement) -> bool: return child.fixed_size.x == 0).size() 15 | var width_per_child: int = available_width / children_to_distribute if children_to_distribute > 0 else 0 16 | var remainder: int = available_width % children_to_distribute if children_to_distribute > 0 else 0 # Add this to the first child 17 | 18 | var running_pos: int = 0 19 | for child: TermElement in _children: 20 | var child_pos: Vector2i = total_content_rect.position 21 | child_pos.x += running_pos 22 | var child_size := Vector2i.ZERO 23 | child_size.y = total_content_rect.size.y 24 | var child_width: int = child.get_fixed_width() 25 | if child_width == 0: 26 | child_width = width_per_child + remainder 27 | remainder = 0 28 | child_size.x = child_width 29 | child.set_rect( 30 | Rect2i( 31 | child_pos, 32 | child_size 33 | ) 34 | ) 35 | running_pos += child_width 36 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_container_title.gd: -------------------------------------------------------------------------------- 1 | class_name TermContainerTitle 2 | extends Resource 3 | 4 | ## Title configuration to be used by a [TermContainer] node. 5 | 6 | enum Alignment { LEFT, CENTER, RIGHT } 7 | 8 | ## The actual title string to be displayed. 9 | @export var title := "" 10 | ## The foreground color the title should be rendered in. 11 | @export var fg_color := Color.WHITE 12 | ## The background color the title should be rendered on. 13 | @export var bg_color := Color.BLACK 14 | ## Alignment of the title within the top border. 15 | @export var alignment := Alignment.LEFT 16 | 17 | 18 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_container_vbox.gd: -------------------------------------------------------------------------------- 1 | class_name TermContainerVBox 2 | extends TermContainer 3 | 4 | ## A ASCII terminal container arranging its children vertically. 5 | ## 6 | ## This container works analogous to [VBoxContainer], and will try to distribute it's children vertically. 7 | ## All children will be stretched to the width available to this container. 8 | ## Any children with a set fixed height (i.e., [code]fixed_size.y[/code] greater than [code]0[/code]) will be set to that height. 9 | ## The remaining height will be evenly distributed between all children without fixed height (i.e., [code]fixed_size.y[/code] set to [code]0[/code]). 10 | 11 | 12 | func _arrange_children() -> void: 13 | var total_content_rect: Rect2i = get_content_rect() 14 | var available_height: int = total_content_rect.size.y - _children.reduce(func(accum: int, child: TermElement) -> int: return accum + child.fixed_size.y, 0) 15 | var children_to_distribute: int = _children.filter(func(child: TermElement) -> bool: return child.fixed_size.y == 0).size() 16 | var height_per_child: int = available_height / children_to_distribute if children_to_distribute > 0 else 0 17 | var remainder: int = available_height % children_to_distribute if children_to_distribute > 0 else 0 # Add this to the first child 18 | 19 | var running_pos: int = 0 20 | for child: TermElement in _children: 21 | var child_pos: Vector2i = total_content_rect.position 22 | child_pos.y += running_pos 23 | var child_size := Vector2i.ZERO 24 | child_size.x = total_content_rect.size.x 25 | var child_height: int = child.get_fixed_height() 26 | if child_height == 0: 27 | child_height = height_per_child + remainder 28 | remainder = 0 29 | child_size.y = child_height 30 | child.set_rect( 31 | Rect2i( 32 | child_pos, 33 | child_size 34 | ) 35 | ) 36 | running_pos += child_height 37 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_element.gd: -------------------------------------------------------------------------------- 1 | class_name TermElement 2 | extends Node 3 | 4 | ## The base terminal element. 5 | ## 6 | ## This is the base class for ASCII terminal elements. 7 | ## The terminal system is intended to work with a tree of nodes derived from this class. 8 | ## This base class provides the basic functionality for recursively drawing [TermElement] nodes. 9 | ## It can be extended by overriding [method TermElement._blit_self_under] and [method TermElement._blit_self_over]. 10 | 11 | signal visual_representation_changed 12 | 13 | ## The fixed size of the element. This property is used by the [TermContainer] variants to determine sizing. 14 | @export var fixed_size := Vector2i.ZERO 15 | 16 | var _children: Array[TermElement] = [] 17 | var _rect: Rect2i 18 | var _redraw_required := true: 19 | set(value): 20 | if not _redraw_required and value: 21 | _redraw_required = true 22 | visual_representation_changed.emit() 23 | else: 24 | _redraw_required = value 25 | 26 | 27 | func _ready() -> void: 28 | child_entered_tree.connect(_on_child_entered_tree) 29 | child_exiting_tree.connect(_on_child_exiting_tree) 30 | for child: Node in get_children(): 31 | _on_child_entered_tree(child) 32 | 33 | 34 | func _on_child_entered_tree(child: Node) -> void: 35 | if child is TermElement: 36 | _children.append(child) 37 | child.visual_representation_changed.connect(_on_child_visual_representation_changed) 38 | update_sizing() 39 | 40 | 41 | func _on_child_exiting_tree(child:Node) -> void: 42 | _children.erase(child) 43 | update_sizing() 44 | 45 | ## This method initiates an update of the sizing of this node and it's children (recursively). 46 | func update_sizing() -> void: 47 | _redraw_required = true 48 | for child: TermElement in _children: 49 | child.update_sizing() 50 | _update_sizing() 51 | 52 | 53 | func _update_sizing() -> void: 54 | pass 55 | 56 | 57 | ## Blits the node and it's children to the provided [TermBuffer]. 58 | ## This works by first calling [method TermElement._blit_self_under], then recursively calling this function on all [TermElement] children, then calling [method TermElement._blit_self_over]. 59 | func blit_to_buffer(buffer: TermBuffer) -> void: 60 | buffer.set_draw_region(_rect) 61 | _blit_self_under(buffer) 62 | for child: TermElement in _children: 63 | child.blit_to_buffer(buffer) 64 | _blit_self_over(buffer) 65 | buffer.reset_draw_region() 66 | _redraw_required = false 67 | 68 | ## Draws the [TermElement]'s contents to the buffer. 69 | ## Contents are drawn before any children are drawn, and can be overdrawn by them. 70 | ## Does nothing on the base [TermElement], and is intended to be overridden by inheriting classes. 71 | ## This method is intended as a general draw function for [TermElement] 72 | func _blit_self_under(_buffer: TermBuffer) -> void: 73 | pass 74 | 75 | ## Draws the [TermElement]'s contents to the buffer. 76 | ## Contents are drawn after all children are done drawing, and can overdraw previously drawn cells. 77 | ## Does nothing on the base [TermElement], and is indented to be overriden by inheriting classes. 78 | ## This method is intended for decorations, e.g., borders, that are drawn over the content and children. 79 | func _blit_self_over(_buffer: TermBuffer) -> void: 80 | pass 81 | 82 | ## This returns the width component ([code]x[/code]) of [member TermElement.fixed_size]. 83 | ## May be overriden in special cases where a required width needs to be calculated dynamically, e.g., texts spanning multiple lines. 84 | func get_fixed_width() -> int: 85 | return fixed_size.x 86 | 87 | ## This returns the height component ([code]y[/code]) of [member TermElement.fixed_size]. 88 | ## May be overriden in special cases where a required height needs to be calculated dynamically, e.g., texts spanning multiple lines. 89 | func get_fixed_height() -> int: 90 | return fixed_size.y 91 | 92 | ## Sets the rectangle in which the [TermElement] is allowed to draw to a [TermBuffer] (in global grid coordinates). 93 | func set_rect(rect: Rect2i) -> void: 94 | _rect = rect 95 | 96 | ## Returns the rectangle in which the [TermElement] is allowed to draw to a [TermBuffer] (in global grid coordinates). 97 | func get_rect() -> Rect2i: 98 | return _rect 99 | 100 | ## Returns an array of all [TermElement] children of this node. 101 | func get_term_children() -> Array[TermElement]: 102 | return _children.duplicate() 103 | 104 | 105 | func _on_child_visual_representation_changed() -> void: 106 | _redraw_required = true 107 | 108 | ## Returns whether the visual representation of this node or any of its children has changed since it was last rendered. 109 | func is_redraw_required() -> bool: 110 | return _redraw_required 111 | 112 | 113 | func _set_dirty() -> void: 114 | _redraw_required = true 115 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_label.gd: -------------------------------------------------------------------------------- 1 | class_name TermLabel 2 | extends TermElement 3 | 4 | ## A string of text to display in a ASCII terminal 5 | 6 | enum TextWrap { 7 | NONE, 8 | ARBITRARY, 9 | WORD_BORDER 10 | } 11 | 12 | ## The text to be displayed. Can only contain characters from codepage 437 (any other characters will be rendered as a blank space). 13 | @export_multiline var text: String = "": 14 | set(value): 15 | text = value 16 | _set_text_grid() 17 | _redraw_required = true 18 | ## How to wrap text if the label is longer than the parent container. 19 | ## [br][br]None: Don't wrap text. Any text that doesn't fit will be clipped. 20 | ## [br]Arbitrary: Fills the available with and then continues in the next line, regardless of word borders. 21 | ## [br]Word Border: Will perform line breaks only between words. 22 | @export var text_wrap := TextWrap.WORD_BORDER: 23 | set(value): 24 | text_wrap = value 25 | _set_text_grid() 26 | _redraw_required = true 27 | ## Color to render the text in. 28 | @export var fg_color := Color.WHITE: 29 | set(value): 30 | fg_color = value 31 | _set_text_grid() 32 | _redraw_required = true 33 | ## Background color to render the text on. 34 | @export var bg_color := Color.BLACK: 35 | set(value): 36 | bg_color = value 37 | _set_text_grid() 38 | _redraw_required = true 39 | 40 | var _text_grid: Dictionary 41 | 42 | 43 | func _ready() -> void: 44 | super() 45 | _set_text_grid() 46 | 47 | 48 | func get_fixed_width() -> int: 49 | return max(fixed_size.x, text.length()) 50 | 51 | 52 | func get_fixed_height() -> int: 53 | match text_wrap: 54 | TextWrap.ARBITRARY: 55 | return _determine_text_height_arbitrary() 56 | TextWrap.WORD_BORDER: 57 | return _determine_text_height_wordborder() 58 | _: 59 | return _determine_text_height_none() 60 | 61 | 62 | func _determine_text_height_none() -> int: 63 | return 1 64 | 65 | 66 | func _determine_text_height_arbitrary() -> int: 67 | var text_height: int = text.length() / _rect.size.x 68 | return text_height 69 | 70 | 71 | func _determine_text_height_wordborder() -> int: 72 | var lines: PackedStringArray = text.split("\n") 73 | var text_height := 0 74 | var line_lenth := 0 75 | var max_line_length := _rect.size.x 76 | for line: String in lines: 77 | text_height += 1 78 | for text_element: String in line.split(" "): 79 | var element_length := text_element.length() 80 | if line_lenth + 1 + element_length > max_line_length: 81 | text_height += 1 82 | line_lenth = element_length 83 | else: 84 | line_lenth += 1 + element_length 85 | line_lenth = 0 86 | return text_height 87 | 88 | 89 | func _blit_self_under(buffer: TermBuffer) -> void: 90 | for position: Vector2i in _text_grid: 91 | buffer.put_cell(_text_grid[position], position) 92 | 93 | 94 | func _set_text_grid() -> void: 95 | match text_wrap: 96 | TextWrap.ARBITRARY: 97 | _set_text_grid_arbitrary() 98 | TextWrap.WORD_BORDER: 99 | _set_text_grid_word_border() 100 | _: 101 | _set_text_grid_no_text_wrap() 102 | 103 | 104 | func _set_text_grid_no_text_wrap() -> void: 105 | _text_grid = {} 106 | for x: int in min(_rect.size.x, text.length()): 107 | var draw_pos: Vector2i = _rect.position + Vector2i.RIGHT * x 108 | _text_grid[draw_pos] = TermCell.new(text[x], fg_color, bg_color) 109 | 110 | 111 | func _set_text_grid_arbitrary() -> void: 112 | _text_grid = {} 113 | var line_length := _rect.size.x 114 | for i: int in text.length(): 115 | var x := i % line_length 116 | var y := i / line_length 117 | var draw_pos: Vector2i = _rect.position + Vector2i(x, y) 118 | _text_grid[draw_pos] = TermCell.new(text[i], fg_color, bg_color) 119 | 120 | 121 | func _set_text_grid_word_border() -> void: 122 | _text_grid = {} 123 | var line_length := _rect.size.x 124 | var y := 0 125 | var x := 0 126 | for line: String in text.split("\n"): 127 | for text_element: String in line.split(" "): 128 | if x + 1 + text_element.length() > line_length: 129 | y += 1 130 | x = 0 131 | var start_pos: Vector2i = _rect.position + Vector2i(x, y) 132 | if x != 0: 133 | if not _rect.has_point(start_pos): continue 134 | _text_grid[start_pos] = TermCell.new(" ", fg_color, bg_color) 135 | x += 1 136 | start_pos += Vector2i.RIGHT 137 | for i: int in text_element.length(): 138 | var draw_pos: Vector2i = start_pos + Vector2i.RIGHT * i 139 | _text_grid[draw_pos] = TermCell.new(text_element[i], fg_color, bg_color) 140 | x += text_element.length() 141 | y += 1 142 | x = 0 143 | 144 | 145 | func _update_sizing() -> void: 146 | _set_text_grid() 147 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_progress.gd: -------------------------------------------------------------------------------- 1 | class_name TermProgress 2 | extends TermElement 3 | 4 | ## A node to draw a progress bar in an ASCII terminal. 5 | 6 | ## The maximum value of the progress bar. 7 | @export var max_value: int: 8 | set(value): 9 | max_value = value 10 | _set_bar() 11 | _redraw_required = true 12 | ## The current value of the progress bar 13 | @export var value: int: 14 | set(new_value): 15 | value = new_value 16 | _set_bar() 17 | _redraw_required = true 18 | 19 | ## The color the filled segments of the bar are drawn in. 20 | @export var fg_color_filled := Color.WHITE: 21 | set(value): 22 | fg_color_filled = value 23 | _set_bar() 24 | _redraw_required = true 25 | ## The color the empty segments of the bar are drawn in. Note: This affects the [i]foreground[/i] of the [property TermProgress.empty_character]. If the [property TermProgress.empty_character] does not print anything (e.g., it's a " "), this will have no effect. 26 | @export var fg_color_empty:= Color.WHITE: 27 | set(value): 28 | fg_color_empty = value 29 | _set_bar() 30 | _redraw_required = true 31 | ## The background color the bar is drawn on. 32 | @export var bg_color := Color.BLACK: 33 | set(value): 34 | bg_color = value 35 | _set_bar() 36 | _redraw_required = true 37 | ## The character to represent a filled segment of the progress bar. 38 | @export var full_character := "█": 39 | set(value): 40 | full_character = value 41 | _set_bar() 42 | _redraw_required = true 43 | ## The character used to represent an empty segment of the 44 | @export var empty_character := "░": 45 | set(value): 46 | empty_character = value 47 | _set_bar() 48 | _redraw_required = true 49 | 50 | 51 | var _bar: Dictionary 52 | 53 | 54 | func _ready() -> void: 55 | super() 56 | _set_bar() 57 | 58 | 59 | func _blit_self_under(buffer: TermBuffer) -> void: 60 | for position: Vector2i in _bar: 61 | buffer.put_cell(_bar[position], position) 62 | 63 | 64 | func _set_bar() -> void: 65 | _bar = {} 66 | var width := _rect.size.x 67 | if max_value <= 0: 68 | return 69 | var filled := roundi(float(width) * (float(value) / float(max_value))) 70 | var full_cell := TermCell.new(full_character, fg_color_filled, bg_color) 71 | var empty_cell := TermCell.new(empty_character, fg_color_empty, bg_color) 72 | for x: int in width: 73 | var draw_pos: Vector2i = _rect.position + Vector2i.RIGHT * x 74 | var used_cell: TermCell = full_cell if x <= filled else empty_cell 75 | _bar[draw_pos] = used_cell 76 | 77 | 78 | func get_fixed_height() -> int: 79 | return 1 80 | 81 | 82 | func _update_sizing() -> void: 83 | _set_bar() 84 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_rect.gd: -------------------------------------------------------------------------------- 1 | class_name TermRect 2 | extends ColorRect 3 | 4 | ## Control node for rendering an ASCII terminal. 5 | ## 6 | ## This node is responsible for the visual output of the ASCII terminal rendering system. 7 | 8 | const _SHADER = preload("res://addons/ascii_grid/term_shader.gdshader") 9 | 10 | @export_category("Font configuration") 11 | ## The source font file. This requires an image file with characters according to Code Page 437. 12 | @export var font: Texture 13 | 14 | ## The size of an individual tile. Needs to be set according to the source font file. 15 | @export var tile_size: Vector2i = Vector2i(8, 16): 16 | set(value): 17 | tile_size = value 18 | if not is_inside_tree(): 19 | await ready 20 | var shader_tile_size := Vector2(1.0 / tile_size.x, 1.0 / tile_size.y) 21 | (material as ShaderMaterial).set_shader_parameter("tile_size", shader_tile_size) 22 | 23 | ## Number of characters per line in the font image file. 24 | @export var characters_per_line := 16: 25 | set(value): 26 | characters_per_line = value 27 | if not is_inside_tree(): 28 | await ready 29 | (material as ShaderMaterial).set_shader_parameter("chars_per_line", characters_per_line) 30 | 31 | ## Root node of the rendered terminal nodes. 32 | @export var term_root: TermElement 33 | 34 | ## The color to draw for empty terminal cells. 35 | @export_color_no_alpha var clear_color: Color = Color.BLACK 36 | 37 | ## If a [property TermRect.term_root] is configured, the [TermRect] will automatically redraw it whenever its visual representation changes. 38 | @export var automatic_redraw: bool = true 39 | 40 | var _grid_size: Vector2i 41 | var _fg_image: Image 42 | var _bg_image: Image 43 | var _chars_image: Image 44 | var _resize_required: bool = true 45 | 46 | func _ready() -> void: 47 | material = ShaderMaterial.new() 48 | material.shader = _SHADER 49 | material.set_shader_parameter("glyphs", font) 50 | material.set_shader_parameter("chars_per_line", characters_per_line) 51 | anchors_preset = PRESET_FULL_RECT 52 | item_rect_changed.connect(_set_resize_required) 53 | _set_resize_required() 54 | 55 | 56 | func _process(_delta: float) -> void: 57 | if _resize_required: 58 | _resize() 59 | _resize_required = false 60 | if automatic_redraw and term_root and term_root.is_redraw_required(): 61 | render() 62 | 63 | 64 | ## Obtains a new, empty buffer that has the same size as this [TermRect]'s grid. 65 | func create_buffer() -> TermBuffer: 66 | return TermBuffer.new(_grid_size) 67 | 68 | ## Renders a [TermBuffer] to the [TermRect]. Uses [param empty_color] for positions not specified in the [param buffer]. 69 | ## This method may be called directly, by supplying a buffer (see [method TermRect.create_buffer]). Alternatively, use [method TermRect.render]. 70 | func render_buffer(buffer: TermBuffer, clear_color: Color = clear_color) -> void: 71 | var empty_cell := TermCell.new(" ", clear_color, clear_color) 72 | for y: int in _grid_size.y: 73 | for x: int in _grid_size.x: 74 | var grid_pos := Vector2i(x, y) 75 | var cell: TermCell = buffer._grid.get(grid_pos, null) 76 | if not cell: 77 | cell = empty_cell 78 | var char_value: float = cell.get_character_id() / 256.0 79 | _chars_image.set_pixelv(grid_pos, Color(char_value, 0.0, 0.0, 0.0)) 80 | _fg_image.set_pixelv(grid_pos, cell.fg_color) 81 | _bg_image.set_pixelv(grid_pos, cell.bg_color) 82 | (material as ShaderMaterial).set_shader_parameter("character_grid", ImageTexture.create_from_image(_chars_image)) 83 | (material as ShaderMaterial).set_shader_parameter("fg_color", ImageTexture.create_from_image(_fg_image)) 84 | (material as ShaderMaterial).set_shader_parameter("bg_color", ImageTexture.create_from_image(_bg_image)) 85 | 86 | ## Renders the tree of [TermElements] at [property TermRect.term_root]. 87 | ## [TermElement] nodes in the configured tree will automatically draw themselves to an internal buffer, which will then be rendered to the terminal. 88 | ## Will create an error if [property TermRect.term_root] is [code]null[/code] (i.e., unconfigured). 89 | func render() -> void: 90 | if not term_root: 91 | push_error("No terminal root node configured. render() can only be called whith a TermElement as root node.") 92 | return 93 | var buffer: TermBuffer = create_buffer() 94 | term_root.blit_to_buffer(buffer) 95 | render_buffer(buffer) 96 | 97 | 98 | func _set_resize_required() -> void: 99 | _resize_required = true 100 | 101 | 102 | func _resize() -> void: 103 | var parent_size: Vector2i = get_parent().size 104 | _grid_size = Vector2i(parent_size) / tile_size 105 | set_deferred("size", _grid_size * tile_size) 106 | (material as ShaderMaterial).set_shader_parameter("grid_size", _grid_size) 107 | _fg_image = Image.create(_grid_size.x, _grid_size.y, false, Image.FORMAT_RGBA8) 108 | _bg_image = Image.create(_grid_size.x, _grid_size.y, false, Image.FORMAT_RGBA8) 109 | _chars_image = Image.create(_grid_size.x, _grid_size.y, false, Image.FORMAT_RGBA8) 110 | if term_root: 111 | term_root.set_rect(Rect2i(Vector2i.ZERO, _grid_size)) 112 | term_root.update_sizing() 113 | -------------------------------------------------------------------------------- /addons/ascii_grid/term_shader.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform sampler2D glyphs; 4 | uniform sampler2D character_grid; 5 | uniform sampler2D fg_color; 6 | uniform sampler2D bg_color; 7 | 8 | const vec2 tile_size = vec2(1.0 / 8.0, 1.0 / 16.0); 9 | uniform vec2 grid_size; 10 | uniform int chars_per_line = 16; 11 | 12 | void fragment() { 13 | vec2 cell_coord = floor(UV * grid_size) + vec2(0.5); 14 | vec2 cell_uv = fract(UV * grid_size); 15 | int char_code = int((texture(character_grid, cell_coord / grid_size).r) * 256.0); 16 | vec2 glyph_pixel_pos = (vec2( 17 | float(char_code % chars_per_line), 18 | float(char_code / chars_per_line) 19 | ) + cell_uv) / vec2(float(chars_per_line), float(256 / chars_per_line)); 20 | vec3 fg_color_pixel = texture(fg_color, cell_coord / grid_size).rgb; 21 | vec3 bg_color_pixel = texture(bg_color, cell_coord / grid_size).rgb; 22 | vec3 char_pixel = texture(glyphs, glyph_pixel_pos, 0.0).rgb; 23 | COLOR.rgb = mix(bg_color_pixel, fg_color_pixel, char_pixel.r); 24 | } -------------------------------------------------------------------------------- /addons/ascii_grid/term_single_cell.gd: -------------------------------------------------------------------------------- 1 | class_name TermSingleCell 2 | extends TermElement 3 | 4 | ## A node to draw a single cell onto an ASCII Terminal 5 | ## 6 | ## This node allows to draw a single cell at a specified position. 7 | ## This position is a local position, i.e., relative to the parent on the screen. 8 | 9 | ## Local position of the cell, relative to the parent cell. 10 | @export var position: Vector2i: 11 | set(value): 12 | position = value 13 | _redraw_required = true 14 | ## Character to be drawn in the cell. 15 | @export var character: String = "": 16 | set(value): 17 | character = value 18 | _cell.character = character 19 | _redraw_required = true 20 | ## Foreground color the cell is drawn in. 21 | @export var fg_color: Color = Color.WHITE: 22 | set(value): 23 | fg_color = value 24 | _cell.fg_color = fg_color 25 | _redraw_required = true 26 | ## Background color the cell is drawn on. 27 | @export var bg_color: Color = Color.BLACK: 28 | set(value): 29 | bg_color = value 30 | _cell.bg_color = bg_color 31 | _redraw_required = true 32 | 33 | var _cell := TermCell.new() 34 | 35 | 36 | func _blit_self_under(buffer: TermBuffer) -> void: 37 | var draw_pos: Vector2i = position + _rect.position 38 | buffer.put_cell(_cell, draw_pos) 39 | -------------------------------------------------------------------------------- /examples/UI_Mockup/UiMockup.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | 4 | @onready var term_cell_map: TermCellMap = $TermRoot/MainColumn/TermCellMap 5 | 6 | 7 | func _ready() -> void: 8 | _fill_cell_map() 9 | 10 | 11 | func _fill_cell_map() -> void: 12 | var ground_cell := TermCell.new(".", Color.SADDLE_BROWN) 13 | var tree_cell := TermCell.new("♠", Color.WEB_GREEN) 14 | for x in 500: 15 | for y in 500: 16 | if randf() < 0.01: 17 | term_cell_map.put_cell(tree_cell, Vector2i(x, y)) 18 | else: 19 | term_cell_map.put_cell(ground_cell, Vector2i(x, y)) 20 | 21 | var wall_cell := TermCell.new("#", Color.GRAY) 22 | var empty_cell := TermCell.new(" ") 23 | for x in range(5, 10): 24 | for y in range(8, 19): 25 | if x == 5 or x == 9 or y == 8 or y == 18: 26 | term_cell_map.put_cell(wall_cell, Vector2i(x, y)) 27 | else: 28 | term_cell_map.put_cell(empty_cell, Vector2i(x, y)) 29 | var door_cell := TermCell.new("+", Color.YELLOW) 30 | term_cell_map.put_cell(door_cell, Vector2i(9, 13)) 31 | -------------------------------------------------------------------------------- /examples/UI_Mockup/UiMockup.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=16 format=3 uid="uid://bqfmink7nn8bq"] 2 | 3 | [ext_resource type="Script" path="res://examples/UI_Mockup/UiMockup.gd" id="1_fkuvn"] 4 | [ext_resource type="Script" path="res://addons/ascii_grid/term_rect.gd" id="1_mxp7t"] 5 | [ext_resource type="Script" path="res://addons/ascii_grid/term_container_hbox.gd" id="2_4kb2r"] 6 | [ext_resource type="Texture2D" uid="uid://cvih7i3m23qn3" path="res://examples/UI_Mockup/terminus_8x16.png" id="2_yxg0a"] 7 | [ext_resource type="Script" path="res://addons/ascii_grid/term_container_vbox.gd" id="3_kxddn"] 8 | [ext_resource type="Script" path="res://addons/ascii_grid/term_container_border_config.gd" id="4_tlnej"] 9 | [ext_resource type="Script" path="res://addons/ascii_grid/term_container_title.gd" id="5_1ns4l"] 10 | [ext_resource type="Script" path="res://addons/ascii_grid/term_cell_map.gd" id="6_fibfa"] 11 | [ext_resource type="Script" path="res://addons/ascii_grid/term_label.gd" id="7_7e13d"] 12 | [ext_resource type="Script" path="res://addons/ascii_grid/term_progress.gd" id="8_6xm3b"] 13 | [ext_resource type="Script" path="res://addons/ascii_grid/term_single_cell.gd" id="11_kp205"] 14 | 15 | [sub_resource type="Resource" id="Resource_je84p"] 16 | script = ExtResource("4_tlnej") 17 | top_border = "─" 18 | bottom_border = "─" 19 | left_border = "│" 20 | right_border = "│" 21 | top_left_corner = "┌" 22 | top_right_corner = "┐" 23 | bottom_left_corner = "└" 24 | bottom_right_corner = "┘" 25 | fg_color = Color(1, 1, 1, 1) 26 | bg_color = Color(0, 0, 0, 1) 27 | 28 | [sub_resource type="Resource" id="Resource_kv3h7"] 29 | script = ExtResource("5_1ns4l") 30 | title = "┤Player Info├" 31 | fg_color = Color(1, 1, 1, 1) 32 | bg_color = Color(0, 0, 0, 1) 33 | alignment = 1 34 | 35 | [sub_resource type="Resource" id="Resource_7hfsj"] 36 | script = ExtResource("4_tlnej") 37 | top_border = "─" 38 | bottom_border = "─" 39 | left_border = "│" 40 | right_border = "│" 41 | top_left_corner = "┌" 42 | top_right_corner = "┐" 43 | bottom_left_corner = "└" 44 | bottom_right_corner = "┘" 45 | fg_color = Color(1, 1, 1, 1) 46 | bg_color = Color(0, 0, 0, 1) 47 | 48 | [sub_resource type="Resource" id="Resource_h2xgo"] 49 | script = ExtResource("5_1ns4l") 50 | title = "─Messages" 51 | fg_color = Color(1, 1, 1, 1) 52 | bg_color = Color(0, 0, 0, 1) 53 | alignment = 0 54 | 55 | [node name="UiMockup" type="Control"] 56 | layout_mode = 3 57 | anchors_preset = 15 58 | anchor_right = 1.0 59 | anchor_bottom = 1.0 60 | grow_horizontal = 2 61 | grow_vertical = 2 62 | script = ExtResource("1_fkuvn") 63 | 64 | [node name="ColorRect" type="ColorRect" parent="."] 65 | layout_mode = 1 66 | anchors_preset = 15 67 | anchor_right = 1.0 68 | anchor_bottom = 1.0 69 | grow_horizontal = 2 70 | grow_vertical = 2 71 | color = Color(0, 0, 0, 1) 72 | 73 | [node name="TermRect" type="ColorRect" parent="." node_paths=PackedStringArray("term_root")] 74 | layout_mode = 1 75 | anchors_preset = 15 76 | anchor_right = 1.0 77 | anchor_bottom = 1.0 78 | grow_horizontal = 2 79 | grow_vertical = 2 80 | script = ExtResource("1_mxp7t") 81 | font = ExtResource("2_yxg0a") 82 | term_root = NodePath("../TermRoot") 83 | 84 | [node name="TermRoot" type="Node" parent="."] 85 | script = ExtResource("2_4kb2r") 86 | 87 | [node name="LeftColumn" type="Node" parent="TermRoot"] 88 | script = ExtResource("3_kxddn") 89 | border = SubResource("Resource_je84p") 90 | title = SubResource("Resource_kv3h7") 91 | fixed_size = Vector2i(21, 0) 92 | 93 | [node name="TermLabel" type="Node" parent="TermRoot/LeftColumn"] 94 | script = ExtResource("7_7e13d") 95 | text = " 96 | Godette 97 | Robot 98 | Level 4" 99 | 100 | [node name="TermLabel2" type="Node" parent="TermRoot/LeftColumn"] 101 | script = ExtResource("7_7e13d") 102 | text = " 103 | HP: 20/30" 104 | fg_color = Color(1, 0, 0, 1) 105 | 106 | [node name="TermProgress" type="Node" parent="TermRoot/LeftColumn"] 107 | script = ExtResource("8_6xm3b") 108 | max_value = 30 109 | value = 20 110 | fg_color_filled = Color(1, 0, 0, 1) 111 | fg_color_empty = Color(1, 0, 0, 1) 112 | 113 | [node name="TermLabel3" type="Node" parent="TermRoot/LeftColumn"] 114 | script = ExtResource("7_7e13d") 115 | text = " 116 | MP: 4/5" 117 | fg_color = Color(0, 0, 1, 1) 118 | 119 | [node name="TermProgress2" type="Node" parent="TermRoot/LeftColumn"] 120 | script = ExtResource("8_6xm3b") 121 | max_value = 5 122 | value = 4 123 | fg_color_filled = Color(0, 0, 1, 1) 124 | fg_color_empty = Color(0, 0, 1, 1) 125 | 126 | [node name="MainColumn" type="Node" parent="TermRoot"] 127 | script = ExtResource("3_kxddn") 128 | 129 | [node name="TermCellMap" type="Node" parent="TermRoot/MainColumn"] 130 | script = ExtResource("6_fibfa") 131 | 132 | [node name="TermSingleCell" type="Node" parent="TermRoot/MainColumn/TermCellMap"] 133 | script = ExtResource("11_kp205") 134 | position = Vector2i(20, 20) 135 | character = "@" 136 | 137 | [node name="MessageBox" type="Node" parent="TermRoot/MainColumn"] 138 | script = ExtResource("3_kxddn") 139 | border = SubResource("Resource_7hfsj") 140 | title = SubResource("Resource_h2xgo") 141 | fixed_size = Vector2i(0, 12) 142 | 143 | [node name="TermLabel" type="Node" parent="TermRoot/MainColumn/MessageBox"] 144 | script = ExtResource("7_7e13d") 145 | text = "Godette received 42 XP!" 146 | fg_color = Color(0, 1, 1, 1) 147 | 148 | [node name="TermLabel2" type="Node" parent="TermRoot/MainColumn/MessageBox"] 149 | script = ExtResource("7_7e13d") 150 | text = "Godette is pleased with being drawn on an ASCII terminal ☺" 151 | fg_color = Color(0, 1, 0, 1) 152 | 153 | [node name="TermLabel3" type="Node" parent="TermRoot/MainColumn/MessageBox"] 154 | script = ExtResource("7_7e13d") 155 | text = "Godette wonders if this very long message will wrap correctly once the window is resized ...?" 156 | fg_color = Color(1, 0, 1, 1) 157 | -------------------------------------------------------------------------------- /examples/UI_Mockup/UiMockupScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SelinaDev/Godot-4-ASCII-Grid/0ad3a145f3b296cd474e3619b43ca1128c6a309d/examples/UI_Mockup/UiMockupScreenshot.png -------------------------------------------------------------------------------- /examples/UI_Mockup/UiMockupScreenshot.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dxxe4f2hajg1c" 6 | path="res://.godot/imported/UiMockupScreenshot.png-b27a4749cf96a7544f0b1ef1ac29fb7f.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://examples/UI_Mockup/UiMockupScreenshot.png" 14 | dest_files=["res://.godot/imported/UiMockupScreenshot.png-b27a4749cf96a7544f0b1ef1ac29fb7f.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /examples/UI_Mockup/terminus_8x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SelinaDev/Godot-4-ASCII-Grid/0ad3a145f3b296cd474e3619b43ca1128c6a309d/examples/UI_Mockup/terminus_8x16.png -------------------------------------------------------------------------------- /examples/UI_Mockup/terminus_8x16.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cvih7i3m23qn3" 6 | path="res://.godot/imported/terminus_8x16.png-862a190e2300fb77c3c0adcd0b0dc225.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://examples/UI_Mockup/terminus_8x16.png" 14 | dest_files=["res://.godot/imported/terminus_8x16.png-862a190e2300fb77c3c0adcd0b0dc225.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bjyhgr4m4nc42" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Godot 4 ASCII Grid" 14 | config/features=PackedStringArray("4.2", "GL Compatibility") 15 | config/icon="res://icon.svg" 16 | 17 | [editor_plugins] 18 | 19 | enabled=PackedStringArray("res://addons/ascii_grid/plugin.cfg") 20 | 21 | [rendering] 22 | 23 | renderer/rendering_method="gl_compatibility" 24 | renderer/rendering_method.mobile="gl_compatibility" 25 | --------------------------------------------------------------------------------