0. BEFORE USING THE LIBRARY, READ THE PCE OFFICIAL/UNOFFICIAL HARDWARE DOCUMNETATION TO UNDERSTAND HOW PCE HARDWARE WORKSThe tilemap library
History:v0.9Dynamic tilemaps:
~~~~~~~~~~~~
The information below applies mainly to multi-directional maps.
By default, all maps data are stored in ROM. Accessing them requires constant switching of memory banks.
This can be avoided by placing the map data in RAM. This greatly speeds up data access and gives new features such as dynamically changing map and tile properties.
Dynamic map changing can be used to change map topology, procedural generation of levels, also it's fastest way for storing collectable items etc...
There are three types of data that can be located in RAM:
- tilemap data
- tilemap LUT
- tile properties
This data can be initialized into RAM independently of each other. It all depends on the amount of free RAM available.
You can do this by placing the following declarations before the MPD library is included in your program:
#define MPD_RAM_MAP
#define MPD_RAM_MAP_TBL
#define MPD_RAM_TILE_PROPS <-- can be used with any type of maps(!)
All these defines speed up getting a tile property and slightly speed up static screens drawing and scrolling.
MPD_RAM_MAP - enables the following functions:
[macro]
u8 mpd_get_tile( u16 _x, u16 _y, bool _pixels ) / _pixels = TRUE - pixel coordinates, FALSE - tile coordinates
[macro]
void mpd_set_tile( u16 _x, u16 _y, bool _pixels, u8 _tile_ind ) / _pixels = TRUE - pixel coordinates, FALSE - tile coordinates, _tile_ind - 0..255
MPD_RAM_TILE_PROPS - enables the following functions:
#if FLAG_PROP_ID_PER_CHR
[macro]
u8 mpd_get_tile_property( u8 _tile_ind, u8 _CHR_ind ) / _tile_ind - 0..255, _CHR_ind - 0..3
[macro]
void mpd_set_tile_property( u8 _tile_ind, u8 _CHR_ind, u8 _new_prop ) / _tile_ind - 0..255, _CHR_ind - 0..3, _new_prop - a new property value
#else //FLAG_PROP_ID_PER_BLOCK
[macro]
u8 mpd_get_tile_property( u8 _tile_ind ) / _tile_ind - 0..255
[macro]
void mpd_set_tile_property( u8 _tile_ind, u8 _new_prop ) / _tile_ind - 0..255, _new_prop - a new property value
#endif //FLAG_PROP_ID_PER_CHR
By default, implementations of these functions are unsafe. You can write data outside the arrays by mistake.
Use
MPD_DEBUG when developing, to enable safe function implementations. If you write data outside of the allocated memory, you will get an error message, function name and its arguments.
The amount of memory that will be allocated for the data can be seen in
<my_exported_tilemap>.h#define MAX_MAP_SIZE ...
#define MAX_MAP_TBL_SIZE ...
#define MAX_TILE_PROPS_SIZE ...
NOTE: If several maps are exported, memory will be allocated for the largest map(!)
Transferring this data to RAM will have a positive effect on performance, even if you don't intend to use dynamic maps and tile properties features.
It all depends on the amount of free RAM in your project and the size of your maps. The size of a map can far exceed the amount of available RAM.
So plan and allocate memory in your project carefully to effectively use the MPD library.
There are two example projects that demonstrate dynamic maps functionality:
Simple map editor:
./samples/pce/tilemap_render/multidir_scroll_map_editor/
Procedural generation of a random maze (3x3 screens):
./samples/pce/tilemap_render/multidir_scroll_maze_generator/
NOTE: Dynamic maps support RLE-compression.
Getting a tile property with dynamic maps:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~From the available functionality of dynamic maps follows a more flexible way to obtain a tile properties for multi-directional maps.
For example, when both tiles and tile properties consist of
2x2* blocks, you can use:
mpd_get_tile_property( mpd_get_tile( x, y, TURE/FALSE ) ) **
This will work faster than
mpd_get_property( x, y ), due to its greater versatility.
But if you have a simple map consisting of 2x2 blocks or 4x4 tiles with unique properties per tile (when different tiles do not share the same property),
then the built-in tile property data can be ignored and replaced with block/tile indices.
In this case, getting the properties of a tile is reduced to a single call to
mpd_get_tile( x, y, TRUE/FALSE ), which is even faster.
------
* It is more complicated with 4x4 tiles, because they require additional data about the 2x2 blocks of which they are composed.
And this data is in ROM. In this case it is easier to use the existing function
mpd_get_property( x, y ).
** Since
mpd_get/set_tile_property(...) and
mpd_get/set_tile(...) are macros or inline functions, and if you have many identical calls to them in your code, it may be better to wrap these calls with function(s).
New read-only public variables are also available:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Map width/height in tiles:
R: u16 mpd_map_tiles_width
R: u16 mpd_map_tiles_height
Size of the tile properties array of a current map when declaring the MPD_RAM_TILE_PROPS:R: u16 mpd_tile_props_arr_size
ASM
MPD_DEBUG changed to HuC '
#define MPD_DEBUG'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
v0.8- reduced the number of HuC functions (.proc/.endp):
[#define] mpd_get_curr_screen_ind()
[#define] mpd_clear_update_flags()
[#define] mpd_copy_screen(...)
[#define] mpd_get_map_size(...)
[#define] mpd_get_start_screen(...)
[#define] mpd_init_base_ent_arr()
[#define] mpd_get_base_ent_cnt()
[#define] mpd_draw_screen_by_ind(...)
[#define] mpd_draw_screen_by_data(...)
[#define] mpd_draw_screen()
[macro] mpd_load_palette(...)
- optimized use of local variables and function arguments
v0.7- updated 'Working with screens/entities - General information' / item 2
- added 'mpd_get_curr_screen_ind()' for multidirectional maps
- added open variables to replace deprecated functions
- fixed missed tile 4x4 drawing for scrollable maps and also fixed BAT overflow in some cases in 'mpd_draw_screen(...)'
- optimized screen scrolling, re-written in assembler; 3x faster than before
- added 'mpd_get_scroll_step_x'/'mpd_get_scroll_step_y'
- renamed 'mpd_scroll_step_x'/'mpd_scroll_step_y' to 'mpd_set_scroll_step_x'/'mpd_set_scroll_step_y'
Open variables and deprecated functions:~~~~~~~~~~~~~~~~~~~~~~~~~~~#if
FLAG_MODE_BIDIR_SCROLL
R: mpd_SCR_DATA mpd_curr_scr
-> mpd_curr_screen()
- DEPRECATED#endif
//FLAG_MODE_BIDIR_SCROLL#if
FLAG_MODE_MULTIDIR_SCROLLMap width/height in screens:
R: u8 mpd_map_scr_width
R: u8 mpd_map_scr_height
Active map width/height in pixels = map width/height in pixels - screen width/height in pixels
R: u16 mpd_map_active_width -> mpd_active_map_width()
- DEPRECATEDR: u16 mpd_map_active_height -> mpd_active_map_height()
- DEPRECATED#endif
//FLAG_MODE_MULTIDIR_SCROLL#if
FLAG_MODE_MULTIDIR_SCROLL + FLAG_MODE_BIDIR_SCROLLMap scrolling step in the range 0..7
RW: u8 mpd_scroll_step_x [0..7] -> mpd_get_scroll_step_x()/mpd_set_scroll_step_x(...)
- DEPRECATEDRW: u8 mpd_scroll_step_y [0..7]
-> mpd_get_scroll_step_y()/mpd_set_scroll_step_y(...)
- DEPRECATEDMap scrolling values:
R: s16 mpd_scroll_x -> mpd_scroll_x() -
DEPRECATEDR: s16 mpd_scroll_y
-> mpd_scroll_y() -
DEPRECATED#endif
//FLAG_MODE_MULTIDIR_SCROLL + FLAG_MODE_BIDIR_SCROLL*R-read only; RW-read/write--------------------------------------------
Replace the deprecated functions with the global variables. And don't write to read only variables.
v0.6- screens/entities data moved from H file to ASM to save more memory for user data
- added HuC functions to get access to screens/entities data
- added description how to work with screens/entities
- added MPD_DEBUG flag that shows how long screen drawing/scrolling takes by border colors (use Mednafen); it is disabled by default in all samples, uncomment 'MPD_DEBUG' to enable the debug mode
Debug info (use Mednafen):- green border color - screen scrolling
- blue border color - static screen drawing
- yellow border color - getting a tile property
Place this before '
#include "mpd.h"':
#asm
MPD_DEBUG
#endasm
Working with screens/entities:
General information:
1. Regardless of a map type, all entities data is stored in screens.
2. The maximum number of allowed entities per screen is 255. But the maximum number of allowed entities per map is 256.
3. There are base entities and instances. The base entities you customize in MAPeD in the 'Entities' tab. The instances are base entities that have been placed on a map.
4. Entities can be sorted during the data export process in two ways: left to right or bottom to top. The sorting goes by pivot points.
5. So, for a multi-directional map, whichever screen you take, you will always have sorted entity data.
6. Accessing screen/entity data requires banks switching and data copying. So it is recommended to cache the data in optimal way for your project when initializing a map data.
Data structures:
typedef struct
{
u8 id;
u8 width;
u8 height;
u8 pivot_x;
u8 pivot_y;
u8 props_cnt;
} mpd_ENTITY_BASE;
typedef struct
{
u8 id;
u16 base_ent_addr; // for library use only
u16 targ_ent_addr; // for library use only
u16 x_pos;
u16 y_pos;
u8 props_cnt;
} mpd_ENTITY_INSTANCE;
typedef struct
{
mpd_ENTITY_INSTANCE inst;
u8 inst_props[ ENT_MAX_PROPS_CNT ];
mpd_ENTITY_BASE base;
u8 base_props[ ENT_MAX_PROPS_CNT ];
} mpd_ENTITY;
typedef struct
{
mpd_PTR24 scr_arr_ptr; // for library use only
u16 scr_offset; // for library use only
mpd_SCREEN scr; // depends on exported options, see exported .h file for details
#if FLAG_ENTITIES
mpd_ENTITY inst_ent;
mpd_ENTITY targ_ent;
#endif //FLAG_ENTITIES
} mpd_SCR_DATA;
The functions for accessing base entities:
void
mpd_init_base_ent_arr() - must be called once for all maps(!)
u16
mpd_get_base_ent_cnt()
void
mpd_get_base_entity( mpd_SCR_DATA* _scr_data, u16 _ent_ind ) -
RES: _scr_data->inst_ent.base
Examples of using the library for multi-dir maps:
void
mpd_init_screen_arr( mpd_SCR_DATA* _scr_data, u8 _map_ind ) - must be called once for each map(!)
Getting a map size in screens:
u16 map_size;
u8 scr_width;
u8 scr_height;
map_size = mpd_get_map_size( map_ind );
scr_width = map_size & 0x00ff;
scr_height = ( map_size & 0xff00 ) >> 8;
or for a current map use these global variables:
mpd_map_scr_width
mpd_map_scr_height
Accessing screen/entity data:
mpd_SCR_DATA scr_data;
u8 scr_cnt;
u8 scr_n;
u8 ent_n;
scr_cnt = mpd_map_scr_width * mpd_map_scr_height;
for( scr_n = 0; scr_n < scr_cnt; scr_n++ )
{
mpd_get_screen_data( &scr_data, scr_n );
for( ent_n = 0; ent_n < scr_data.scr.ents_cnt; ent_n++ )
{
mpd_get_entity( &scr_data, ent_n );
AND/OR
if( mpd_find_entity_by_base_id( &scr_data, base_ent_id ) )
{
...
}
AND/OR
if( mpd_find_entity_by_inst_id( &scr_data, inst_ent_id ) )
{
...
}
...
}
}
Examples of using the library for bi-dir maps:Use '
mpd_curr_scr' to access the current screen, but make a copy '
mpd_copy_screen( mpd_SCR_DATA* _src_scr, mpd_SCR_DATA* _dst_scr )' before using '
mpd_get_screen_data( mpd_SCR_DATA* _scr_data, u16 _scr_ind )'
Iterating a map screens:
u16 adj_scr;
mpd_SCR_DATA scr_data;
mpd_get_start_screen( &scr_data );
// getting adjacent screen by input direction
adj_scr = mpd_get_adj_screen( &scr_data, ADJ_SCR_LEFT/ADJ_SCR_RIGHT/ADJ_SCR_UP/ADJ_SCR_DOWN );
if( adj_scr != 0xffff )
{
mpd_get_screen_data( &scr_data, adj_scr );
...
...
Accessing entities:
u8 ent_n;
mpd_curr_scr <- read only!
OR
mpd_SCR_DATA scr_data;
mpd_get_start_screen( &scr_data );
for( ent_n = 0; ent_n < scr_data.scr.ents_cnt; ent_n++ )
{
mpd_get_entity( &scr_data, ent_n );
AND/OR
if( mpd_find_entity_by_base_id( &scr_data, base_ent_id ) )
{
...
}
AND/OR
if( mpd_find_entity_by_inst_id( &scr_data, inst_ent_id ) )
{
...
}
...
}
v0.5- removed 'mpd_draw_screen_by_pos_offs' as unsafe and useless
- 'mpd_draw_screen_by_ind' and 'mpd_draw_screen_by_ind_offs' - changed screen index from u16 to u8
- fixed scroll values overflow in the 'mpd_move_right/left/up/down' for multi-dir mode
Added tile drawing functions:IN: _x, _y - screen space coordinates in pixels
_block2x2_ind - 0..255
_CHR_ind - 0..3
void
mpd_draw_CHR( u16 _x, u16 _y, u8 _block2x2_ind, u8 _CHR_ind )
IN: _x, _y - screen space coordinates in pixels
_block2x2_ind - 0..255
void
mpd_draw_block2x2( u8 _x, u8 _y, u8 _block2x2_ind )
For maps with 4x4 tiles:IN: _x, _y - screen space coordinates in pixels
_tile4x4_ind - 0..255
void
mpd_draw_tile4x4( u8 _x, u8 _y, u8 _tile4x4_ind )
NOTE: For scrollable maps add '
mpd_scroll_x' to
_x and '
mpd_scroll_y' to
_yv0.41. Added support for free step scrolling (1-7 pixels per step). It can be used for inertial scrolling.
Removed 'mpd_scroll_step' enum, now
'mpd_init( u8 _map_ind, u8 _step )' as scroll step value receives numbers 1-7 pix.
DEPRECATED -> Added 'mpd_scroll_step_x( u8 _step_pix )/mpd_scroll_step_y( u8 _step_pix )' for scrollable maps.
DEPRECATED -> mpd_scroll_step_x( calced_positive_move_step ); use the 'mpd_scroll_step_x'/'mpd_scroll_step_y' variables
mpd_move_left();
2. 'mpd_draw_screen_by_scr_ind' renamed to 'mpd_draw_screen_by_ind'. 'mpd_draw_screen_by_scr_data' renamed to 'mpd_draw_screen_by_data'.3. Added 'mpd_draw_screen_by_ind_offs( u16 _scr_ind, u16 _BAT_offset, bool _reset_scroll )' for multi-directional mode.
Added 'mpd_draw_screen_by_data_offs( mpd_SCREEN* _scr_data, u16 _BAT_offset )' for bi-directional maps.
This allows to draw screens at any place in BAT and can be used for various purposes:
a. For double-buffered screen switching without black screen ( disp_off()/disp_on() ) within one map.
Added a new sample that shows double-buffered screen switching for multi-directional maps:
'./samples/pce/tilemap_render/multidir_stat_scr_multimap' b. For
custom scrolling between screens out of the tilemap library. The same technique as
(a), but you can make your custom scrolling between screens.
c. If your game map fits into any BAT size, you can fill the BAT with a map screens and make a map scrolling using the way described in point
4 (see below).
It's faster than using the tilemap library for scrolling!
So, the rule is as follows: if each of your game maps fits into BAT, DON'T USE the MPD library for scrolling! It doesn't make any sense! Use it for filling BAT with screens and getting a tile property!
4. Removed VDC's scroll registers update on 'mpd_init' and 'mpd_update_screen'. 'mpd_update_screen( bool _vsync )' changed to 'mpd_update_screen()'. Since
v0.4 the library doesn`t interact with VDC`s scroll registers in any way! It just provides scroll values X/Y:
mpd_scroll_x, mpd_scroll_y.
Thus, user must set scroll values in his program using these global variables. This is for scrollable maps only!
There are two ways: 1. Fullscreen scrolling. Write directly to the bgx1(0x220c)/bgy1(0x2210) system variables:
for(;;)
{
...your code here...
// update VDC's scroll registers on VBLANK via bgx1/bgy1 variables
pokew( 0x220c, mpd_scroll_x );
pokew( 0x2210, mpd_scroll_y );
vsync();
}
2. Area scrolling. If you need to combine a static HUD and the scrollable area for your map, you can do this using the HuC's scroll library - 'scroll(...)':
NOTE: This will work for linear horizontal bi-directional maps!
// init HUD out of the visible BAT area
scroll( 0, 0, 224, 0, 31, 0x80 );
for(;;)
{
...your code here...
// map scrolling
scroll( 1, mpd_scroll_x, 32, 32, 223, 0xC0 );
vsync();
}
Thus, the main option is
(1). But if you need to limit the scrollable area you need to use the
(2) option.
NOTE: To avoid conflicts, these options can not be used together. You must use either (1) or (2).
-------------------------------------------------------------------------------------------------------------------------
P.S.: See example projects for details: './samples/pce/tilemap_render'.
P.P.S.: All the changes and updated samples you can find in the dev build archive.