[Wiki]Everything there is to know about TAS Mode

What is it?

TAS, or Tiles-And-Sprites, Mode is a screen mode optimized for rendering tilemaps with sprites on top. It uses very little memory compared to modes based on a framebuffer and supports up to 256 colors at either low or high resolution.

2

Specs

Resolution: 220x176 or 110x88
Colors: 256
RAM: 512 bytes palette, 220 bytes line buffer, 4 bytes per tile, 16 bytes per Sprite
Good for: Games with tiles in the background
Not good for: Games that need of drawing primitives

How do I get started?

MicroPython

Just go to https://pokittopython.herokuapp.com, click on the Flags tab, then change the resolution to either TAS Low or TAS High. That’s it.

C++

Download:

You’re ready! In FemtoIDE create a new project and select the 2-C++ Tiles+Sprites Game template. You now have a project that converts Tiled TMX files into C++ and draws them on the Pokitto. To convert all the TMX files in the maps folder, just click Scripts -> Convert TMX. Also included is a script that converts animated sprites into easy-to-use code.

If, instead of high-res you prefer 110x88, open My_settings.h and set the screenmode like this:

#define PROJ_SCREENMODE TASMODELOW

Optionally, you can tweak following parameters.

  • PROJ_MAX_SPRITES Default: 100. Defines the maximum amount of sprites you can draw per frame.
  • PROJ_TILE_W: Default: 16. Defines the width of each tile
  • PROJ_TILE_H: Default: 16. Defines the height of each tile.

For example, if you want a tilemap with 32x32 pixel tiles and only 10 sprites, you’d have this in your project’s My_settings.h:

#define PROJ_SCREENMODE TASMODE
#define PROJ_TILE_W 32
#define PROJ_TILE_H 32
#define PROJ_MAX_SPRITES 10

Tiles

The tilemap in TAS Mode is one tile larger than the screen, in each dimension. With each tile being 32x32 pixels, that means you get an 8x7 tilemap. This might seem limiting at first, but used together with the included Tilemap library, it is all that is necessary to draw all the scenery you can fit into your game.

To learn about how to use the Tilemap library, have a look at @filmote’s excellent tutorials.

The Tilemap in TAS Mode has a few minor differences, compared to other screen modes:

  • Every tile must be of the same size.
  • The tilemap is always drawn under the sprites, in the background
  • There can be only one tilemap
  • You can have either 1 color, 4-bit (16 colors), or 8-bit (256 color) tiles.

To save space you can limit your tiles to use only the first 16 colors of the palette. Your tiles must then be in the same format as in one of the 4-bit modes. For this to work, you just need to change the display’s color depth:
Pokitto::Display::setColorDepth(4);

If you want to have a tile that is a single color, there is no need to create a bitmap for it. Instead, you can use:
tilemap.setColorTile(tileIndex, colorIndex);
To draw a sea, for example, if the color blue is on index #2 in your palette, tilemap.setColorTile(0, 2) will create a blue tile wherever tile #0 is found in the map.

Sprites

In TAS mode, instead of drawing everything into a large framebuffer and sending that to the screen, a list is kept with all the drawing operations (sprites). This means that, if you don’t need to draw a lot of sprites at once, you can have a very small list and free up RAM for other things.

If you try to draw more sprites than you defined in PROJ_MAX_SPRITES, the list will clear itself and everything you drew before will be erased.

Since there is no framebuffer, not all drawing operations are supported.
These are the functions that work:

  • drawColumn
  • drawRow
  • drawRectangle
  • fillRectangle
  • bufferChar (and, consequently, print)
  • drawBitmapData2BPP
  • drawBitmapData4BPP
  • drawBitmapData8BPP
  • drawBitmapDataXFlipped4BPP
  • drawBitmapDataYFlipped (4BPP only)
  • drawBitmapDataXFlipped4BPP
  • drawBitmapDataXFlipped8BPP
  • drawSprite

Each one of these puts a sprite in the list of drawing operations. print calls bufferChar for each letter in a string, so Display::print("hello world!") will take up 12 sprites!

TAS mode is optimized for 8-bit images, but to draw a 4-bit image, the following is supported:

Display::setColorDepth(4);
Display::drawBitmap(x, y, bitmap);

For 8-bit images, it is better to use the drawSprite function:

Display::drawSprite(int x, int y, const uint8_t *data, bool flipped=0, bool mirrored=0, uint8_t recolor=0)

Flip (mirror vertically) and mirror can be used together. Recolor will add the specified value to each non-transparent pixel. If you want to make a football game, you can have a single bitmap for the players, but add a different recolor offset for each team. Or in a fighting game, if both fighers choose the same character, you can recolor one of them so they can be told apart.

Masking

It is possible to disable TAS mode for certain parts of the screen. This allows you to draw freely in the disabled areas using direct mode.
Disabling is done in rows of 8 pixels using setTASRowMask:
Pokitto::Display::setTASRowMask(0b1111'00000000'11111111);
From right-to-left, each 1 enables a row, resulting in the following image:

MicroPython

PyInSky is being updated to support TAS Mode. Due to the flash space constraints, it will use only the 4-bit drawing functions, but it will have a lot more free RAM than the currently supported modes.

1

TL;DR - Show me the code, already!

// My_settings.h
#define PROJ_SCREENMODE TASMODE
#define PROJ_TILE_H 16
#define PROJ_TILE_W 16
#define MAX_TILE_COUNT 256
// main.cpp
#include <Pokitto.h>
#include <miloslav.h>
#include <Tilemap.hpp>
#include "gardenPath.h"
#include "smile.h"

int main(){
    using PC=Pokitto::Core;
    using PD=Pokitto::Display;
    using PB=Pokitto::Buttons;
    
    PC::begin();
    PD::invisiblecolor = 0;
    PD::loadRGBPalette(Miloslav);
    
    auto& map = gardenPath;
    Tilemap tilemap;
    tilemap.set(map[0], map[1], map+2);
    for(int i=0; i<sizeof(tiles)/(POK_TILE_W*POK_TILE_H); i++)
        tilemap.setTile(i, POK_TILE_W, POK_TILE_H, tiles+i*POK_TILE_W*POK_TILE_H);
    auto start = PC::getTime();
    while( PC::isRunning() ){
        if( !PC::update() ) continue;
        for(int i=0; i<8; ++i)
            PD::drawSprite(i*16, rand()%32+40, Smile);
        PD::setCursor(60, 60);
        PD::fontSize = 2;
        PD::print("Hello world!");
        tilemap.draw(x, y);
    }
}
10 Likes

TAS is looking excellent :slight_smile: Especially for MicroPython where ram is quite low in the hires mode. Recoloring sprites is very interesting too.

A question about text drawing. Could we first print text to a (ram) sprite buffer, which is e.g. 220x8 and then, draw the sprite on screen?

1 Like

Thanks!

I think printing into a sprite buffer would take up more RAM than the current implementation.

Would it not be better to use a second tile map layer for text? most of the fonts are fixed width anyway.

I guess, having a look at TASMode.cpp that the best would be to add a dedicated blitter that’d use the string as Sprite's data and supporting a single font - then have a call drawText() that’d add such a “sprite”.

You can alternatively use Sprites as single character in the meanwhile - it wouldn’t be that annoying for printing Numbers, but it’ll dry the pool quite quickly for longer texts

It depends. That would be good if you have a lot of text and you don’t need to position it freely. Like if you’re making a text-only game, in which case TAS mode wouldn’t be a good fit.
You would also be paying the RAM cost of having a fullscreen text buffer even if your game doesn’t have any text.
Also take into consideration that it might be necessary to have multiple colors, multiple fonts on-screen at once, and you can double the font size.

A terminal-style text-only mode would be a nice addition, though.

For games that need to display a lot of text, say an RPG with lots of dialog, I’d prefer to implement letter boxing: TAS Mode would skip the first/last rows of the screen and you’d be able to write in that area using directChar. It could be useful for drawing HUDs as well.

Edit:
Implemented it here. The size of the top/bottom bars are configurable at run-time.
letterbox

6 Likes

Good idea! Why not make it so any rows on the screen can be left out, not just top/bottom? Then people can put text anywhere.
image

2 Likes

It probably means maintaining a dynamic list of start/stop and makes the whole a bit more complicated - and takes a various amount of memory.

However! it’d allows interesting tricks:

  • Pausing TASMode in specific ranges
    • You could print the TAS in a given range, “disable” TAS refresh for this area, then print text above it on the next frame.
    • Might provide a FPS boost when needed?
  • Making things looks glitched become a lot easier, especially if you’re disabling individual rows randomly each frame :stuck_out_tongue:

Memory probably wouldn’t be as much of a problem because there’s a fixed number of rows so you’d have a maximum upper bound for the size of the array needed to keep track of which rows to not draw.

For a 16x16 map there’s 12 rows ((176 / 16) + 1).
And since ‘draw or not draw’ is a ‘true or false’ decision,
that could be turned into a bit vector,
which means you’re looking at 12 bits rather than 12 bytes.

It’s more likely that speed or complexity would be an issue.
(The bit shifting and masking needed for the bit vector approach would probably make this a case where using a whole byte per row is fast enough to warrant the 10 extra bytes.)


If sprites have no size limit (the main post does not specify whether or not there is such a limit) then it would be easier to just have a ‘textbox’ sprite and write text to that sprite.

Now that I think about it, with a mere 22 bytes you can enable/disable each of the 176 individual screen rows, and it supports every cases

It was worth a shot, so I gave it a go. I think enabling/disabling for every row of pixels would be too slow, so I chose to disable rows of tiles instead.
Right now the API looks like this:

    PD::setTASRowMask(0b10101010'10101010'10101010'10101010);

From right-to-left, each digit enables/disables a row, top-to-bottom.

With this it is possible to implement parallax scrolling. Scroll a bit, draw all the far rows, scroll some more, draw all the near rows.

Edit: Here’s the commit, if anybody wants to experiment with it.

For my mixed screen mode I simply had an array representing every two rows of the screen. 88 bytes. Easily less if you only representing on or off. 11 bytes would probably do that.

You appear to have completely skipped over the three comments after the one you just quoted.

1 Like

We’ll blame me being on my phone for that :stuck_out_tongue:

2 Likes

So this mask implementation works very fine for scrolling horizontally, and indeed support h-parallax:

TilemapProject.bin.1
Imagine some ships, pew pew SFX and 'splosions

However it won’t support vertical scrolling - because it’s tied to a row of tiles and not the screen:
2020-02-16 - Vertical Parallax
Failed attempt at vertical parallax, with “non-rendering holes” left by the masks

But sidescrolling games don’t require vertical scrolling anyway :stuck_out_tongue:

1 Like

Not being able to move vertically is going to be a problem, so I’ve changed it as per the conversation we had on discord. Updated commit.
Dungeon.bin.1

1 Like

Gif looks corrupted for me.

you mean the black bars? It’s supposed to look like that, it’s drawing every other 8 rows.

1 Like

Not sure I undestrand what I’m seeing :relaxed:

1 Like

Maybe this will clear it up:
That gif above corresponds to this mask:
PD::setTASRowMask(0b1010'10101010'10101010);

This image here:
Dungeon.bin.1
Is what you get with this mask:
PD::setTASRowMask(0b1111'00000000'11111111);

And this image here:
Dungeon.bin.1
Is the result of this:
PD::setTASRowMask(0b0000'00000011'11111111);

3 Likes