What is Bracket-lib

bracket-lib started out life as RLTK - The RogueLike Toolkit. Initially, I needed a code-page 437 terminal library on which to build roguelikes. The Rust Roguelike Tutorial grew, and so did the library. Then Hands-on Rust (my book about learning Rust) was born, and bracket-lib became the primary library behind the book.

Bracket-lib is intended to fulfill the following needs:

  • A user-friendly teaching library. If I have to choose between performance and a new-user friendly interface, 99% of the time I'll choose the friendly approach.
  • Provide virtual consoles on a variety of platforms, from the web to major Operating Systems.
  • Provide a number of extensions to allow you to do layering, sprites, and more advanced rendering---without hurting the overall goal of the library.
  • Provide a good "on ramp" for moving onto high-performance libraries once you've mastered the basics.
  • Remain agnostic about how you write your game. ECS, tick methods, embedded scripting---take the path you want. Bracket-lib is meant to provide the basic tools you need and let you unleash your imagination.

This "getting started" guide is intended to help you get started with bracket-lib.

Linking to Bracket-lib

The quickest and easiest way to use bracket-lib in your program is to include the entire library in your project. Open your project's Cargo.toml and include:

[dependencies]
bracket-lib = "0.8"

You now have the whole bracket-lib project linked to your program.

Using the Github Version

If you'd like to live on the bleeding edge, you can link to the Github version. In your project's Cargo.toml file, add the following:

[dependencies]
bracket-lib = { git = "https://github.com/amethyst/bracket-lib.git" }

The main reason to do this is to try out new features. The Github version isn't always as stable as I'd like, so proceed with caution.

Hello Terminal

You can get to hello terminal pretty quickly:

Create a New Project

Find the directory in which you want to start developing, and type cargo init my_project to create a new project.

Link Bracket-Lib

Open Cargo.toml in the newly created project, and expand the [dependencies] section as follows:

[dependencies]
bracket-lib = "0.8"

Hello Minimal Terminal

The following code prints "Hello Bracket World" in a new simple console:

use bracket_lib::prelude::*;

struct State {}

impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        ctx.print(1, 1, "Hello Bracket World");
    }
}

fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Hello Minimal Bracket World")
        .build()?;

    let gs: State = State {};
    main_loop(context, gs)
}

This provides what you need for a minimal start:

  1. Importing the prelude from bracket_lib makes the various types and functions available.
  2. You have to create a State object. This is where your ongoing game state is stored.
  3. Implementing GameState provides bracket-lib with a tick function to call on every frame.
  4. Your main function constructs a terminal---in this case an 80x50 text console.
  5. You create a State object, even though it doesn't have much state to store.
  6. Launching main_loop hands control over to bracket-lib, and runs the tick function on every rendered frame.

Bouncy Hello World

Another example (hello_terminal in the bracket-lib source) provides a bouncing "Hello World". Let's use it to explore some of the features available in the library:

use bracket_lib::prelude::*;

struct State {
    y: i32,
    going_down: bool,
}

impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        let col1 = RGB::named(CYAN);
        let col2 = RGB::named(YELLOW);
        let percent: f32 = self.y as f32 / 50.0;
        let fg = col1.lerp(col2, percent);

        ctx.cls();
        ctx.printer(
            40,
            49,
            "#[blue]Hello #[pink]Bracket#[] world.",
            TextAlign::Center,
            Some(RGBA::from_u8(200, 200, 200, 255)),
        );

        ctx.print_color(
            1,
            self.y,
            fg,
            RGB::named(BLACK),
            "♫ ♪ Hello Bracket World ☺",
        );

        if self.going_down {
            self.y += 1;
            if self.y > 48 {
                self.going_down = false;
            }
        } else {
            self.y -= 1;
            if self.y < 2 {
                self.going_down = true;
            }
        }

        ctx.draw_box(39, 0, 20, 3, RGB::named(WHITE), RGB::named(BLACK));
        ctx.printer(
            58,
            1,
            &format!("#[pink]FPS: #[]{}", ctx.fps),
            TextAlign::Right,
            None,
        );
        ctx.printer(
            58,
            2,
            &format!("#[pink]Frame Time: #[]{} ms", ctx.frame_time_ms),
            TextAlign::Right,
            None,
        );
    }
}

fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Hello Bracket World")
        .build()?;

    let gs: State = State {
        y: 1,
        going_down: true,
    };

    register_palette_color("blue", RGB::named(BLUE));
    register_palette_color("pink", RGB::named(MAGENTA));

    main_loop(context, gs)
}

There's quite a lot to unwrap here, so let's go through a quick tour of using bracket-lib.

The State Structure

Everything your program needs to retain during execution lives in your state structure (well, you could use globals and lazy-statics). In this case, we're storing two variables in State:

  • y stores the current vertical location of the bouncing "Hello Bracket World".
  • going_down stores whether the words are going up or down at the moment. It changes direction at the top and bottom of the screen.

Setup

In the main function, we start by initializing bracket-terminal. We ask for a simple 80x50 console, and title the window. The build() function actually creates the window; the other chained functions return an object describing what you want. There's a lot of options you can choose, documented elsewhere in this guide.

We initialize State with some starting values for the bouncing text. Then we call register_palette_color---more on that in "pretty printing", below. Finally, we call main_loop to begin running the program---and calling tick every frame.

Printing to the Console

  1. The tick function starts by defining some colors. The RGB::named(xxx) functions return a color object. The library defines all of the W3C named colors; you can also use from_rgb to specify one yourself. These colors are then used in various printing functions.
  2. Then it does a little dance to figure out where to draw the bouncing hello world.
  3. ctx.cls() clears the console.
  4. ctx.printer is then used to "pretty print" the words "Hello Bracket World".
    1. The decorators #[blue], #[pink] specify that text following that marker should be in the color that was registered wih the register_palette_color label.
    2. The two numbers (40, 49) are screen coordinates.
    3. TextAlign::center will center the text horizontally around the specified coordinates.
    4. Some(RGBA::from_u8(200, 200, 200, 255)) describes a background color for the text.
  5. Then we use the print_color commands, which is a simpler way to put single-color text on the console.
  6. Some more printer calls, demonstrating different alignments and color settings.

Notice that in the text, we use unicode. ♫ ♪ Hello Bracket World ☺ is valid, because the characters are part of the codepage-437 set---and bracket-terminal automatically translates them.

You'll find more detailed usage instructions throughout this document.

Using Individual Bracket-Lib Components

bracket-lib is a collection of libraries. You can depend upon them individually, if you only want part of the bracket-lib functionality. The parts are:

  • bracket-algorithm-traits defines some traits that are used in other parts of the program.
  • bracket-color defines how the library handles color, and includes functions for grayscale, RGB/RGBA/HSV conversion, a lot of named colors, and general color management support.
  • bracket-geometry provides points, lines, rectangles and circle support.
  • bracket-noise provides Perlin, Simplex, White and other noise functions useful for randomly generating things.
  • bracket-pathfinding provides an A-Star and a Dijkstra mapping solution.
  • bracket-random provides an easy-to-use wrapper to a random number generator.
  • bracket-terminal provides console rendering and support.

When you link directly to a dependency, the namespace is on longer bracket_lib::prelude. Instead, it will be the crate's name, e.g. bracket_random::prelude.

This option is partly provided to help keep development efforts separated, and partly because sometimes you just want a small portion of what the library has to offer---and there's no point in wasting space (and mostly compile time) on the bits you don't need.

Using the Examples

Each of the bracket-lib crates comes with a number of examples. These are a great source of inspiration for how the parts of the library fit together, as well as serving as a demonstration of what can be done.

You can run examples locally by changing directory to the crate you wish to use (e.g. cd bracket-terminal), and then running cargo run --example (name). You can get a list of valid example names by omitting (name).

Many people find they learn better by reading examples, rather than big manuals. If so, this section is for you!

Bracket-Color Examples

All of the bracket-color examples require crossterm for console output. You can run them with:

cd bracket-color
cargo run --example (name) --no-default-features --features crossterm

colors

Source Code.

The colors example demonstrates printing to the console in a number of colors, demonstrating several of the ways in which colors can be generated (named, from floating point and byte RGB).

lerp

Source Code

The lerp example shows how to use the lerp function, providing smooth transitions between two colors. The transition occurs in the RGB space. You can use HSV colors to lerp in that space if you wish. ("Lerp" is a common shorthand for "Linear Interpolation")

lerpit

Source Code

Similar to the lerp example, but using an iterator instead of repeated calls to the lerp function. Bracket-color includes an RgbLerp iterator for this purpose.

lerpit_hsv

Source Code

This is just like the lerpit example, but using an HSV color space for interpolation via the HsvLerp iterator.

named_color

Source Code

A very simple demo printing colors obtained via RGB::named, including a quick test of the into conversion between colors.

shades_of_grey

Source Code

This demo shows off how to use to_greyscale() and desaturate() on colors, providing two different methods of converting colors to greyscale.

Bracket-Geometry Examples

bresenham_circle

Source Code

This demonstrates the use of BresenhamCircle to plot gridded circles, quickly and with compensation for grid locations.

bresenham_line

Source Code

This example draws a line and plots it on the console, using Bresenham's Line algorithm.

bresenham_sweep

Source Code

This example sweeps from 0 to 360 degrees, using bracket-geometry angle functions. It then projects a point along that angle, and draws a Bresenham Line to the new point.

distance

Source Code

This example calculates the distance between two points using Pythagoras, Pythagoras Squared, Manhattan and Chebyshev distance algorithms.

Given the two points:
Point {
    x: 0,
    y: 0,
}
Point {
    x: 10,
    y: 20,
}

Pythagoras Distance: 22.36068
Pythagoras Squared Distance: 500
Manhattan Distance: 30
Chebyshev Distance: 20

vector_line

Source Code

This example uses vector math to plot a line, rather than Bresenham. It can be slightly faster on some CPUs, if you are plotting enough lines (or long enough lines) for the floating-point math to make a difference. Lines aren't quite as nice as their Bresenham brethren.

..........
.*........
..*.......
..**......
...*......
....*.....
.....*....
.....**...
......*...
.......*..

Bracket-Noise Examples

cellular

Source Code

Note that this is not cellular automata! Generates cellular noise, basically a randomized Voronoi pattern.

perlin_fractal

Source Code

This example uses Perlin Fractal Noise to generate an ASCII height map.

simplex_billow_quintic

Source Code

This examples uses Simplex noise to make a heightmap, using the Billow and Quintic modes.

simplex_fractal

Source Code

This example uses Simplex noise in Fractal mode to make a heightmap.

simplex_rigid_hermite

Source Code

This example uses Simplex noise in Rigit Hermite mode to make a heightmap.

value

Source Code

This example uses value noise for simple interpolation.

value_fractal

Source Code

This example uses value noise in fractal interpolation mode.

whitenosie

Source Code

Generates a really random looking field of white noise. Perfect for snow, or for feeding random data to a shader.

Bracket-Pathfinding Examples

astar

Source Code

A quick demonstration of A-star pathfinding in action. Demonstrates the minimum required for accurate A-star pathing.

astar_manhattan

Source Code

Demonstrates using Manhattan distances in A-Star, rather than regular Pythagoras.

dijkstra

Source Code

Demonstrates the usage of the Dijkstra maps feature by brightening areas close to starting points, and darkening those further away.

fov

Source Code

Demonstrates the Field-of-View functionality.

Bracket-Random Examples

diceroll

Source Code

Demonstrates using the roll_dice functionality of the RNG to roll dice in a familiar, RPG-style. 3d6 is represented as roll_dice(3, 6).

Rolling 3d6, 10 times.
3d6 Roll: 10
3d6 Roll: 8
3d6 Roll: 11
3d6 Roll: 10
3d6 Roll: 14
3d6 Roll: 7
3d6 Roll: 13
3d6 Roll: 9
3d6 Roll: 13
3d6 Roll: 11
Total of rolls: 106

dicestring

Source Code

Demonstrates using the roll_str functionality to allow you to enter dice types as strings. For example, roll_str("3d6"). Note that this is a lot slower due to parsing requirements.

Rolling 3d6, 10 times.
3d6 Roll: 13
3d6 Roll: 3
3d6 Roll: 11
3d6 Roll: 6
3d6 Roll: 8
3d6 Roll: 12
3d6 Roll: 5
3d6 Roll: 10
3d6 Roll: 12
3d6 Roll: 13
Total of rolls: 93

die_iterator

Source Code

Creates a dice iterator, each rolling a 6-sided die. Takes 10 dice rolls and prints them.

Rolled 3
Rolled 1
Rolled 3
Rolled 6
Rolled 2
Rolled 4
Rolled 1
Rolled 2
Rolled 1
Rolled 1

distribution

Source Code

Rolls a lot of dice and plots their relative distribution. This is intended to validate that the random number generator is behaving appropriately.

Rolling 3d6, 200000 times and counting distribution.
03 : ##
04 : #######
05 : ###############
06 : #########################
07 : ######################################
08 : #####################################################
09 : ################################################################
10 : ####################################################################
11 : ######################################################################
12 : #################################################################
13 : #####################################################
14 : ######################################
15 : ##########################
16 : ###############
17 : #######
18 : ##

Source Code

Demonstrates the next_u64 function in the RNG, which passes straight through to the underlying RNG code.

Generating the next 10 u64 numbers
Roll: 18130881974873676332
Roll: 3148465433356529749
Roll: 44531299326498369
Roll: 9665219486649819621
Roll: 10520437451657888625
Roll: 12316016225926642867
Roll: 2116667603649678054
Roll: 11573604930291377796
Roll: 2541210746452578386
Roll: 17238031251593215327

rand

Source Code

Demonstrates how to use rand to generate a random number of a specified type.

Generating the next 10 f64 numbers
Roll: 0.7195672608676137
Roll: 0.5348780904141426
Roll: 0.8706676996760022
Roll: 0.32794462603290664
Roll: 0.619775940285832
Roll: 0.4395722002981868
Roll: 0.37184757519241163
Roll: 0.9221657800105313
Roll: 0.35612926854806837
Roll: 0.17372920791278967

range

Source Code

Demonstrates the rng.range(min, max) function.

Generating the next 10 numbers in the range 100 - 200
Roll: 181
Roll: 179
Roll: 199
Roll: 180
Roll: 114
Roll: 117
Roll: 166
Roll: 162
Roll: 196
Roll: 113

slice

Source Code

Demonstrates using the crate to randomly select an entry from a slice (collection) of entries.

Randomly chose a: Cat
Randomly chose a: Dragon
Randomly chose a: Hamster
Randomly chose a: Dragon
Randomly chose a: Cat
Randomly chose a: Dragon
Randomly chose a: Cat
Randomly chose a: Hamster
Randomly chose a: Cat
Randomly chose a: Cat

slice_index

Source Code

Demonstrates using slice_index to randomly pull an index entry from a slice of data.

Randomly chose index: 2, which is a Gerbil
Randomly chose index: 0, which is a Cat
Randomly chose index: 0, which is a Cat
Randomly chose index: 1, which is a Dog
Randomly chose index: 0, which is a Cat
Randomly chose index: 2, which is a Gerbil
Randomly chose index: 4, which is a Dragon
Randomly chose index: 4, which is a Dragon
Randomly chose index: 4, which is a Dragon
Randomly chose index: 1, which is a Dog

Terminal Examples

alpha

Source Code

Demonstrates the RGBA capabilities of the library, when alpha blending is enabled on a sparse console overlaying a simple console. Also uses the VirtualConsole feature to create a large psuedo-console containing a chunk of A Tale of Two Cities, and rendering that as the base layer.

astar_mouse

Source Code

Generates a random blob map, and places a player on it. The player moves to wherever the mouse is clicked, using A star pathfinding. Also generates real-time field of view for the player as they zip around.

bench_scalable

Source Code

Generates a completely random console, as fast as possible. Uses the resizable console option to allow the window to be resized while it runs, and queries the console for dimensions---filling as much of it as possible.

No screenshot, sorry.

benchmark

Source Code

Draws a random console as fast as possible. No resizing.

colorfont

Source Code

Demonstrates that you can use color in your font graphic if you want to.

dwarfmap

Source Code

An homage to Dwarf Fortress. Generates a 3D map, and then renders it top-down with darkening for lower than the player z-levels. Implements a-star pathing in 3D, and mouse-based navigation.

flexible

Source Code

Demonstrates using a fancy/flexible console to make a spinning @ bounce around the screen.

fontswitch

Source Code

Demonstrates switching fonts at run-time.

hello_minimal

Source Code

Minimal hello world, see the quick start guide.

hello_terminal

Source Code

See the quick start guide---a bouncing hello world.

input_harness

Source Code

A handy tool to display the key code and mouse information about keys/mouse as they are utilized. Also demonstrates the advanced_input system, providing inputs as a stream of events rather than a simple set.

keyboard

Source Code

Mostly a debugging tool. Demonstrates keys and their various modifier combiantions.

native_gl

Source Code

Demonstrates how to access the underlying OpenGL system, if you should need it. Note that if you really want to write a GL application, this is a really inefficient way to do it---but it gets you started, and offers a way to perform quick OpenGL calls if you just need something fancy.

no_cls

Source Code

Mostly a debugging tool, this example shows that consoles accumulate data if you don't call cls. This was important for debugging the native console implementations and their dirty-render detection.

post_process

Source Code

Demonstrates reading a REX Paint file (.xp), and rendering it to the screen. Enables or disables the terminal's post-processing options, screen-burn and uses scan-lines.

return_error

Source Code

Demonstrates how to return and act on errors from initialization, should you wish to do so.

rex

Source Code

Loads Nyan Cat from an embedded REX Paint file, and renders it to the screen.

sparse

Source Code

Demonstrates layering a sparse console on top of a simple console, with different fonts on each.

sparse_cls

Source Code

Debugging tool, designed to help me notice when I break clearing sparse consoles again!

sprites

Source Code

Uses the sprite layer type (in its infancy) to animate some sprites as they run around the screen.

squishy

Source Code

A benchmark tool that also demonstrates the ability to "squish" terminals by dynamically resizing the size of each tile.

textblock

Source Code

Demonstrates the text-block tool, and how it can help with large amounts of text.

textsprites

Source Code

Demonstrates the "multi-tile text sprite" system. You can batch some characters together for quick rendering.

tiles

Source Code

Uses graphical fonts instead of ASCII/CP437 for a visual dungeon experience.

unicode

Source Code

Demonstrates that if you really want to (it's slow!), you can load a giant font containing lots of unicode and display multi-lingual terminals. Run this one in release mode. It takes far too long to start, otherwise.

virtual_console

Source Code

Demonstrates using a virtual console to hold a lot of data, and then displaying windows into the virtual console. This can be handy for logs, manuals and similar. This should have a seizure warning

walking

Source Code

Demonstrates walking around a map.

walking_with_is_pressed

Source Code

The same walking demo, but using the is_pressed function (for advanced input) rather than the simple key enum.

Working with Consoles

Bracket-terminal is based around consoles. At its base, a console represents a grid. Consoles have a grid dimension, and each cell/tile is addressed through x and y coordinates:

0,01,02,03,04,0
0,11,12,13,14,1
0,21,22,23,24,2
0,31,32,33,34,3
0,41,42,43,44,4

The coordinates extend out to the limits of the console's dimensions.

Types of Console

Bracket-terminal includes the following types of console:

  • Simple Consoles store an internal vector, representing each tile of the grid. Each tile/cell is rendered as part of an overall grid structure. Simple consoles are best suited to layers that address a large part of the screen.
    • Simple Consoles may or may not contain a background. If they don't include a background, they don't overwrite content behind them.
  • Sparse Consoles store a list of characters to render, and the address at which they are rendered. They are best used when you only want to draw a few things on the screen, typically with a simple console behind them.
  • Fancy Consoles also store a render list, but add niceties such as fractional coordinates and rotation to characters.
  • Sprite Consoles store a list of sprites from a sprite sheet, and render them in pixel coordinates.
  • Virtual Consoles aren't rendered, but can store huge amounts of data. Windowing functions make it easy to render parts of the console to another console type. These are great for large blobs of documentation, log files and similar.

Layers

Bracket-terminal supports layering. You can have multiple consoles, drawn on top of one another. Consoles can have their own tile sizes, and may be offset or scaled. They are rendered in the order in which they are initialized---the first console is always rendered first, subsequent consoles are rendered on top of them.

You can use layers to render your game with one tileset, characters with another, and HUD/game information in an attractive text font on top of them all.

Initializing Consoles and Layers

In your main function, when you initialize bracket-terminal you specify the consoles you require. The simplest possible initialization provides you with a single simple layer, containing 80 (width) by 50 (height) cells:

fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Hello Minimal Bracket World")
        .build()?;

Let's have a look at how this works.

Constructor

So what's really happening here? BTermBuilder is a builder structure that represents what you'd like to construct. You have to start with a constructor. You have some choices:

  • The default, new default to an 80x50 console, but don't specify any details.
  • The simple80x50 constructor provides everything you need for a default, 8x8 font 80x50 console.
  • The simple(width, height) constructor provides everything you need for a default, 8x8 font width by height console.
  • The vga80x50 constructor initializes an 80x50 console, with a VGA 8x16 font registered.
  • The vga(width, height) constructor provides a VGA 8x16 font console in the specified dimensions.

The constructor provides a starting-point for constructing the console. Chances are, you want to specify more information. The constructor does not return a usable context, yet---it provides a builder that lets you add detail to your request.

Global Builder Options

Some builder functions provide global (whole program) functionality. These include:

  • with_title(string) lets you specify a window title.
  • with_resource_path(string) lets you specify where your font files are saved.
  • with_font(filename, character width, character height) lets you register your own font file.
  • with_font_bg(filename, character width, character height, color) lets you register a font with an explicit background color that will be converted to transparent.
  • with_vsync(bool) lets you turn vertical refresh sync on or off. This uncaps your frame rate, but may lead to visual tearing on some systems.
  • with_fullscreen(bool) lets you specify that the game should try and run in full screen mode, as opposed to a window.
  • with_fps_cap(fps) lets you cap the maximum frames per second at which the game will render, explicitly sleeping to keep CPU load manageable.
  • with_platform_specific(hint) is used for those times you need to tell a renderer something, but that feature can't work in all render targets. These will be documented in the render targets.
  • with_advanced_input(bool) lets you change the input mechanism from populating the key/mouse variables to a stream of keyboard/mouse events.
  • with_automatic_console_resize(bool) lets you change the behavior of resizing the window. Instead of scaling the console up or down to fit the window, a resize event re-calculates the size of the consoles and expands/shrinks them to fit. You can query the current size of a layer from the context in tick.

These provide a fair amount of control over the general program. If you want to use layers, or get fancy with your consoles, you often need to specify per-layer information.

Building Layers

You can add as many layers as you want (I haven't tested the upper bound). There are several builder functions that add layers:

  • with_simple_console(width, height, font filename) creates a simple console of the specified dimensions, bound to the target font.
  • with_simple_console_no_bg(width, height, font filename) creates a simple console with no background - it won't overwrite underlying layers. This is useful when you want to layer printed data on top of other layers, but still want a simple console.
  • with_simple_8x8 creates a simple console bound to the internal 8x8 font, using dimensions that were specified with with_dimensions. This is mostly provided for compatibility with previous versions.
  • with_sparse_console(width, height, font filename) creates a sparse console with the specified dimensions and font.
  • with_sparse_console_no_bg(width, height, font filename) creates a sparse console with the specified dimensions and font. It also sets the "no background" flag, so backgrounds won't be rendered for characters. Instead of blanking the area around a rendered character, it will just render the character data.
  • with_fancy_console(width, height, font filename) creates a fancy console. These will have their own section in the manual.
  • with_sprite_console(width, height, sprite_sheet) creates a sprite console. These will also have their own section in the manual.

You can combine these with various with_tile_dimensions and with_dimensions calls. These calls apply to the most recently added layer, allowing you to customize how they are rendered.

For example, this creates an 80x50 layer (0) in 8x8 mode, and an 80x25 layer (1) in VGA font (8x16):


#![allow(unused)]
fn main() {
let context = BTermBuilder::simple80x50()
    .with_font("vga8x16.png", 8u32, 16u32)
    .with_sparse_console(80u32, 25u32, "vga8x16.png")
    .with_title("Bracket Terminal - Sparse Consoles")
    .build()?;
}

A more complicated example (from the Roguelike Tutorial):


#![allow(unused)]
fn main() {
let mut context = RltkBuilder::simple(80, 60)
    .unwrap()
    .with_title("Roguelike Tutorial")
    .with_font("vga8x16.png", 8, 16)
    .with_sparse_console(80, 30, "vga8x16.png")
    .with_vsync(false)
    .build()?;
}

Note that RltkBuilder is the same as BTermBuilder---it changes its name if you use the library as RLTK. This creates an 80x60 simple console, and then registers a VGA-mode 80x30 (half the height) sparse console.

Another example, this from from Hands-on Rust:


#![allow(unused)]
fn main() {
    let context = BTermBuilder::new()
        .with_title("Dungeon Crawler")
        .with_fps_cap(30.0)
        .with_dimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT)
        .with_tile_dimensions(32, 32)
        .with_resource_path("resources/")
        .with_font("dungeonfont.png", 32, 32)
        .with_font("terminal8x8.png", 8, 8)
        .with_simple_console(DISPLAY_WIDTH, DISPLAY_HEIGHT, "dungeonfont.png")
        .with_simple_console_no_bg(DISPLAY_WIDTH, DISPLAY_HEIGHT, "dungeonfont.png")
        .with_simple_console_no_bg(SCREEN_WIDTH*2, SCREEN_HEIGHT*2, "terminal8x8.png")
}

This sets a window title and an FPS cap. Then it sets the window size and tile dimensions, and a resource path. It loads the two fonts, and sets up three layers: a simple console for the map, a simple console for characters, and a text layer over the top.

Constructing Your Session

Once you're done with specifying details, you finish the builder chain with .build(). It returns a Result type, so it's handy to use the ? syntax---but you can handle errors however you wish. Sometimes, you asked for the impossible---or the underlying system just won't cooperate with your wishes. These generate errors. Otherwise, you receive a usable context---which you can run with the main_loop function and your game state (see below).

Handing Over Control

Bracket-terminal takes over control of the main game loop. It does this because it needs to continually interact with your operating system (or browser in WASM mode), marshall events and handle redrawing the screen. You need to create a State object that must implement GameState---and begin execution with main_loop(my_state).

For example, the following from "hello terminal" hands over control:

use bracket_lib::prelude::*;

struct State {}

impl GameState for State {
    fn tick(&mut self, ctx: &mut BTerm) {
        ctx.print(1, 1, "Hello Bracket World");
    }
}

fn main() -> BError {
    let context = BTermBuilder::simple80x50()
        .with_title("Hello Minimal Bracket World")
        .build()?;

    let gs: State = State {};
    main_loop(context, gs)
}

The tick function in your GameState implementing structure is called every frame.

Printing to Consoles

Probably the most common thing to do with a console is to print text to it. This can be actual text, or characters mapped to sprites in your font file---but it's a really common task. Bracket-terminal provides a lot of different ways to do this.

Interacting with Your Context

Your tick function receives a ctx: &mut BTerm parameter. This provides a link between your program and the terminals. Almost all of the direct control functions are called as methods on the ctx. You'll need to pass your context (or parts of it) to child functions in your program. This allows your program to control the console, while the library retains overall control of execution.

Passing a mutable reference around can be a pain. That's why there are other options, which will be covered later.

Working with Layers

If you have multiple layers, you need to make sure that you call ctx.set_active_console(index) to activate the one you wish to target. Most draw commands are passed through to the underlying console---the library needs to know where to send them. It's a good idea to remember to call set_active_console(0) at the end of your session, to ensure you don't forget to change back to the default on another pass.

Direct Drawing Commands

There are many different options for drawing to the console.

Clearing the Screen

You have two options for clearing a console: ctx.cls() clears the screen to a black background, ctx.cls_bg(color) clears the console to a specified background color (and has no effect on layers without background).

You often want to clear the console at the start of a tick, if you plan to draw the whole screen. Some optimizations may occur that make it not really clear and redraw everything, especially in Curses and Crossterm modes.

Specifying Characters

Characters need to be selected from the codepage-437 list. There's a great chart showing CP-437 here.

You can convert a Rust character into a codepage-437 character with to_cp437('X').

Printing a Single Character/Glyph

You can print a single CP-437 character with the ctx.set(x, y, foreground, background, glyph) command. This is mostly used by the print commands, but you can use it yourself if you wish.

For example:


#![allow(unused)]
fn main() {
ctx.set(1, 1, RED.into(), BLACK.into(), to_cp437('☺'));
}

This will set the character at 1,1 (near the top-left) to a red smiley face with a black background.

You can also use ctx.set_bg(x, y, color) to set just the background color a cell.

Printing Strings

You can print a string with the ctx.print(x, y, string) function. You don't need to convert the characters in the string---the library does it for you. No color is specified, it will just use black/white.

If you'd like to print in color, ctx.print_color(x, y, foreground, background, string) works the same way---but specifies the colors to use.

You can center your text (horizontally) around a given y coordinate with print_centered(y, string) and print_color_centered(y, fg, bg, string). These center relative to the whole panel.

You can also center your text around a specific point with print_centered_at(x, y, text) and its friend print_color_centered_at(x, y, fg, bg, string).

You can right-justify your text with print_color_right(x, y, fg, bg, string) and print_right(x, y, string).

I didn't want to tackle full-justification. Hyphenation systems are painful.

Printing with More Control

If you need to do fancy things like having different words in different colors, you can use the ctx.printer system:


#![allow(unused)]
fn main() {
printer(x, y, string, alignment, background)
}
  • The x and y coordinates behave as you'd expect.
  • alignment takes a TextAlign enum: TextAlign::Left, TextAlign::Center or TextAlign::Right.
  • background is an Option. So either specify None (for no background), or Some(color).

Text in the printer accepts control characters to specify colors. You have to register these with calls to register_palette_color (typically in your main function. It's a free function, not attached to your context). You can then use control characters inside your string. They are a stack---so you have to exit out once you are done with formatting.

For example:


#![allow(unused)]
fn main() {
register_palette_color("blue", RGB::named(BLUE));
register_palette_color("pink", RGB::named(MAGENTA));
}

This code assigns the name "blue" and "pink" to appropriate RGB colors. In your tick function, you can then use printer as follows:


#![allow(unused)]
fn main() {
ctx.printer(
    40,
    49,
    "#[blue]Hello #[pink]Bracket#[] world.",
    TextAlign::Center,
    Some(RGBA::from_u8(200, 200, 200, 255)),
);
}

Notice that #[] is used to end the color. This will print "Hello" and "world" in blue, and "Bracket" in pink. The #[] has popped the stack---reverting to the previously specified color.

Drawing Boxes

You can use the ctx.draw_box(x, y, width, height, foreground, background) function to draw a single-lined box at the specified coordinates.

Likewise, ctx.draw_box_double(x, y, w, h, fg, bg) does the same thing---but with double-lines.

Both of these functions zero out the contents of the box. If you don't want to do that, draw_hollow_box and draw_hollow_box_double omit this step, but otherwise operate in the same way.

Drawing Progress/Scroll/Health Bars

The function ctx.draw_bar_horizontal renders a bar, with a specified percentage in one color and a percentage in another. The full syntax is:

draw_bar_horizontal(x, y, width, number, max, foreground, background)

Specify number as the current number, bounded by max. For example, a health bar showing 6 out of 12 possible hit points remaining would use a number of 6 and max of 12.

If you'd like to do the same thing, but with a vertical bar you can use:

draw_bar_vertical(x, y, height, number, max, foreground, background)

Filling a Region

You can fill a rectangular region of the screen with the ctx.fill_region(target, symbol, foreground, background) function. target is a Rect from bracket-geometry.

Other Console Options

There are a bunch of other console-related functions.

Controlling String Translation

You can change the console's translation mode with set_translation_mode. By default, all strings are turned into cp437. If you are using a big unicode font, you can change to CharacterTranslationMode::Unicode to support it. See the unicode example for details.

Clipping Rendering

If you only want your rendering to apply to a given region of the screen, you can call ctx.set_clipping(Some(Rect)) (and remove it with ctx.set_clipping(None)). Rendered characters will only appear if they are within the clipping region. This can be handy for limiting window rendering.

Changing Font

You can change a console to use any registered font on the fly with ctx.set_active_font(font index, bool). If the bool is true, the console will resize to the dimensions implied by the font size. The "font index" is the order in which the font was inserted.

You can override the internal size of a font with ctx.set_char_size. Use this with caution. A better approach to resizing is to call ctx.set_char_size_and_resize_window(width, height). This doesn't work on all platforms.

Changing Alpha

You can change the alpha-level of ALL characters on a layer with set_all_fg_alpha and set_all_bg_alpha. The set_all_alpha applies your change to both foregrounds and backgrounds.

Scaling the Console

You can set a console scale with set_scale (and query it with get_scale). This allows you to "zoom in" on a layer. It's not commonly used.

Saving a Console

You can save the current state of a console to a REX Paint file by calling to_xp_layer. You can take a PNG screenshot with ctx.screenshot(filename).

Fancy Consoles

Fancy consoles serve as a bridge between a traditional gridded console, and sprite graphics. They were originally designed to make it easier to add smooth movement to a game: you can offset or rotate characters when they are rendered. Fancy consoles are like sparse consoles in that they store all of the characters to be rendered, and not a traditional grid. They even allow characters to be overlaid on top of one another.

Hands-on Rust includes bonus content that uses a fancy console to provide a smooth-moving version of Flappy Dragon.

Initializing a Fancy Console

Fancy consoles are another creation option in the BTermBuilder chain. The following example creates a single fancy console:


#![allow(unused)]
fn main() {
let mut context = BTermBuilder::simple80x50()
    .with_fancy_console(80, 50, "terminal8x8.png")
    .with_title("Bracket Terminal - Fancy Consoles")
    .with_vsync(false)
    .build()?;
}

Just like other consoles, you may use whatever font and size options you require.

Drawing to a Console

You clear a fancy console with the same ctx.cls() command as other consoles. You can also use the regular printing commands just like any other console. A new draw command is available to take advantage of the console's extended capabilities:


#![allow(unused)]
fn main() {
ctx.set_fancy(
    Location (PointF),
    z_order (i32),
    rotation (Angle),
    scale (PointF),
    foreground (RGBA),
    background (RGBA),
    glyph
);
}
  • Regular print commands take coordinates as integers. Fancy consoles accept floating point coordinates---represented by a PointF structure. The fractional part is used to offset the current location; 0.0 will render at the left or top, while 0.9 will render at the right or bottom. You can use this to implement smooth movement.
  • z_order determines the order in which the glyphs are rendered.
  • rotation takes an Angle type (either Radians or Degrees). If this is non-zero, the glyph will be rotated (around the center) by this amount.
  • scale controls how large the character is. 1.0 will render at normal size. You can shrink it by going smaller, or expand it by going larger. Scaling occurs separately on the x and y axes---you need to specify both in the PointF.
  • fg and bg are render colors.
  • glyph is a character type, like other set commands.

There's also batched versions of this. You can use batch.set_fancy in the same way to render as part of a batch.

The terminal source code includes an example called flexible to demonstrate this in action.