![image](https://private-user-images.githubusercontent.com/32767250/350731733-c3075838-7f15-4679-862e-ce47780c9b39.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzMjI0OTIsIm5iZiI6MTcyNDMyMjE5MiwicGF0aCI6Ii8zMjc2NzI1MC8zNTA3MzE3MzMtYzMwNzU4MzgtN2YxNS00Njc5LTg2MmUtY2U0Nzc4MGM5YjM5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIyVDEwMjMxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFmMGUwOGMyNjAxMzg4ZmNlYzU4OGU4OTFjZGE3ZjdhZTU0ZDkwYTBhNzU2ZGFlMzNhMTY5MDY3MDc2MWI5NDAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.l00jH2yC9QicH_se52hnwIduDyKEn6ia7E-369RmsOc)
(Gif animation produced using terminalizer
and gifsicle
.)
DungGine
is a terminal based dungeon engine for RPGs and dungeon-crawlers (e.g. Rogue-likes) and that uses Termin8or
for rendering.
This is a header only library.
The engine works very well together with the GameEngine
class of lib Termin8or
.
There are two main classes for this dungeon generator: BSPTree
that is responsible for creating the rooms, corridors and doors in the "dungeon", and then there is the class DungGine
that is the dungeon game engine itself.
See the next section for a summary over these two classes.
BSPTree.h
BSPTree(int min_room_length)
: The constructor.generate(int world_size_rows, int world_size_cols, Orientation first_split_orientation)
: Generates the BSP regions recursively.pad_rooms(int min_rnd_wall_padding = 1, int max_rnd_wall_padding = 4)
: Pads the regions into rooms.create_corridors(int min_corridor_half_width = 1)
: Non-recursive method of creating corridors on leaf-level.create_doors(int max_num_locked_doors, bool allow_passageways)
: Creates doors between rooms and corridors. You need to first have calledgenerate()
,pad_rooms()
andcreate_corridors()
before calling this function.draw_regions(SpriteHandler<NR, NC>& sh, int r0 = 0, int c0 = 0, const styles::Style& border_style = { Color::Black, Color::Yellow })
: Draws the regions.draw_rooms(SpriteHandler<NR, NC>& sh, int r0 = 0, int c0 = 0, const styles::Style& room_style = { Color::White, Color::DarkRed })
: Draws the rooms.draw_corridors(SpriteHandler<NR, NC>& sh, int r0 = 0, int c0 = 0, const styles::Style& corridor_outline_style = { Color::Green, Color::DarkGreen }, const styles::Style& corridor_fill_style = { Color::Black, Color::Green })
: Draws the non-recursive corridors.print_tree()
: Debug printing of the tree.fetch_leaves()
: Fetches the leaves of the BSP tree where the rooms are stored.get_room_corridor_map()
: Function that retrieves the room and corridor relationship data structure.get_world_size()
: Gets the world size.fetch_doors()
: Gets a vector of pointers to all doors.
DungGine.h
DungGine(const std::string& exe_folder, bool use_fow, DungGineTextureParams texture_params = {})
: The constructor.load_dungeon(BSPTree* bsp_tree)
: Loads a generated BSP tree.style_dungeon()
: Performs automated styling of rooms in the dungeon / realm.set_player_character(char ch)
: Sets the character of the playable character (pun intended).set_player_style(const Style& style)
: Sets the style (fg/bg color) of the playable character.place_player(const RC& screen_size, std::optional<RC> world_pos = std::nullopt)
: Places the player near the middle of the realm in one of the corridors and centers the screen around the player.configure_sun(float sun_day_t_offs = 0.f, float minutes_per_day = 20.f, Season start_season = Season::Spring, float minutes_per_year = 120.f, Latitude latitude = Latitude::NorthernHemisphere, Longitude longitude = Longitude::F, bool use_per_room_lat_long_for_sun_dir = true)
: Configures the speed of the solar day, speed of the solar year, the starting direction of the sun and the starting season. Used for shadow movements for rooms over ground. Whenuse_per_room_lat_long_for_sun_dir
istrue
then uselatitude = Latitude::Equator
andlongitude = Longitude::F
to start with. Other values will shift the map over the globe so to speak, but with these starting settings the rooms at the top of the map will be the at the north pole and the rooms at the bottom of the map will be at the south pole. Whenuse_per_room_lat_long_for_sun_dir
isfalse
then the specified latitude and longitude will be used globally across the whole map and the the function default args is a good starting point.configure_sun_rand(float minutes_per_day = 20.f, float minutes_per_year = 120.f, Latitude latitude = Latitude::NorthernHemisphere, Longitude longitude = Longitude::F, bool use_per_room_lat_long_for_sun_dir = true)
: Same as above but randomizes the initial direction of the sun.place_keys(bool only_place_on_dry_land)
: Places the keys in rooms, randomly all over the world.place_lamps(int num_lamps, bool only_place_on_dry_land)
: Placesnum_lamps
lamps in rooms, randomly all over the world.place_weapons(int num_weapons, bool only_place_on_dry_land)
: Placesnum_weapons
weapons in rooms, randomly all over the world.place_potions(int num_potions, bool only_place_on_dry_land)
: Placesnum_potions
potions in rooms, randomly all over the world.place_armour(int num_armour, bool only_place_on_dry_land)
: Placesnum_armour
armour parts in rooms, randomly all over the world.place_npcs(int num_npcs, bool only_place_on_dry_land)
: Placesnum_npcs
NPCs in rooms, randomly all over the world.set_screen_scrolling_mode(ScreenScrollingMode mode, float t_page = 0.2f)
: Sets the screen scrolling mode to eitherAlwaysInCentre
,PageWise
orWhenOutsideScreen
.t_page
is used withPageWise
mode.update(double real_time_s, const keyboard::KeyPressData& kpd, bool* game_over)
: Updating the state of the dungeon engine. Manages things such as the change of direction of the sun for the shadows of rooms that are not under the ground and key-presses for control of the playable character.draw(SpriteHandler<NR, NC>& sh, double real_time_s, int anim_ctr, ui::VerticalAlignment mb_v_align = ui::VerticalAlignment::CENTER, ui::HorizontalAlignment mb_h_align = ui::HorizontalAlignment::CENTER, int mb_v_align_offs = 0, int mb_h_align_offs = 0, bool framed_mode = false)
: Draws the whole dungeon world with NPCs and the PC along with items strewn all over the place. Use mb_v_align and mb_h_align to place the messagebox along with mb_v_align_offs, mb_h_align_offs and framed_mode.
Texturing done using the editor TextUR
.
Use the TextUR
command line argument -c
to convert a normal/fill texture to a shadow texture.
DungGine
supports texture animations.
dung::BSPTree::draw_regions()
![image](https://private-user-images.githubusercontent.com/32767250/338175872-d3dba22a-f41d-482b-9a8f-0490e6f24835.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzMjI0OTIsIm5iZiI6MTcyNDMyMjE5MiwicGF0aCI6Ii8zMjc2NzI1MC8zMzgxNzU4NzItZDNkYmEyMmEtZjQxZC00ODJiLTlhOGYtMDQ5MGU2ZjI0ODM1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIyVDEwMjMxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWIyMDRjM2UzNDgwNDczMDEyMGEwZWYzMTE3ZDYyMjg3M2JmZDU5ZWJlMWJiZTE4ZGVhZmVjMTA2NzFkYWNhZDUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.iQqnoK892doYj3oMYT1r6LNr0nufj_1mXHoiW8X4QlU)
dung::BSPTree::draw_rooms()
![image](https://private-user-images.githubusercontent.com/32767250/338176529-b4b672a8-9ce5-4d74-8d71-c23b37ac1042.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzMjI0OTIsIm5iZiI6MTcyNDMyMjE5MiwicGF0aCI6Ii8zMjc2NzI1MC8zMzgxNzY1MjktYjRiNjcyYTgtOWNlNS00ZDc0LThkNzEtYzIzYjM3YWMxMDQyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIyVDEwMjMxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTNkNWQ4NDcwMjYxNWVhZjVmYmFmYjg5ZjFiMWYwNDdiMDVmYzIwNjk2ZDVlYTM0NzhjNDM5NTg3ZGZiNjI1ZTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.fHlVBJ8UjzwSOdEQIfLmr34VoDEIB_00i7IrQS2Ym54)
dung::BSPTree::draw_rooms()
dung::BSPTree::draw_corridors()
![image](https://private-user-images.githubusercontent.com/32767250/339173772-fd23bf8e-17b2-4055-84d6-d6bdd87538ef.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzMjI0OTIsIm5iZiI6MTcyNDMyMjE5MiwicGF0aCI6Ii8zMjc2NzI1MC8zMzkxNzM3NzItZmQyM2JmOGUtMTdiMi00MDU1LTg0ZDYtZDZiZGQ4NzUzOGVmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIyVDEwMjMxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdjMjk3MzA1OGUwZmU1Y2M4MDNjZTJkZWYxYzVlNGM0ZDAwNzI5NmY2ZWU3MjQ1NWEzNDM4NjhiZDIxNGNlMjEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.18LO0ZYhR0UNMhop4EWJnTxkuyvaX1r2EV0ck1DU3P0)
dung::BSPTree bsp_tree { 4 }; // argument: `min_room_length = 4`.
bsp_tree.generate(29, 79, dung::Orientation::Vertical); // arguments: world_size_rows, world_size_cols,
first_split_orientation.
bsp_tree.pad_rooms(4); // arguments: min_rnd_wall_padding = 4, [max_rnd_wall_padding = 4].
bsp_tree.create_corridors(1); // argument: min_corridor_half_width = 1, (1 means it will be three chars wide).
Text t;
SpriteHandler<NR, NC> sh;
Color bg_color = Color::Default;
dung::DungGine dungeon_engine;
dungeon_engine.load_dungeon(&bsp_tree);
dungeon_engine.style_dungeon();
dungeon_engine.draw(sh, get_real_time_s(), 0);
sh.print_screen_buffer(t, bg_color);
![image](https://private-user-images.githubusercontent.com/32767250/339774401-7417d29d-1d2a-47b6-b926-31a9ae0177b2.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzMjI0OTIsIm5iZiI6MTcyNDMyMjE5MiwicGF0aCI6Ii8zMjc2NzI1MC8zMzk3NzQ0MDEtNzQxN2QyOWQtMWQyYS00N2I2LWI5MjYtMzFhOWFlMDE3N2IyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIyVDEwMjMxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTc3MWYyYWNhOGY3YTRjYzk0NWU1MjFjMjZkYzQ4MTQ3YTUzYjhjMTRhY2RmODkwZjRiMjQ2N2FiZDRkMTE1MjkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.dg1_COW7HKdYj8IHl_urSuPFJalsIamdahs9aa_Q_08)
// Declarations
dung::BSPTree bsp_tree { 4 }; // argument: `min_room_length = 4`.
dung::DungGineTextureParams texture_params;
std::unique_ptr<dung::DungGine> dungeon_engine;
Text t;
SpriteHandler<NR, NC> sh;
Color bg_color = Color::Black;
int anim_ctr = 0;
// Initializations
bsp_tree.generate(200, 400, dung::Orientation::Vertical); // arguments: world_size_rows, world_size_cols,
first_split_orientation.
bsp_tree.pad_rooms(4); // arguments: min_rnd_wall_padding = 4, [max_rnd_wall_padding = 4].
bsp_tree.create_corridors(1); // argument: min_corridor_half_width = 1, (1 means it will be three chars wide).
bsp_tree.create_doors(100, true); // We also create doors here. Arguments: max_num_locked_doors, allow_passageways.
dungeon_engine = std::make_unique<dung::DungGine>(get_exe_folder(), true); // arguments: exe_folder, use_fow, texture_params.
dungeon_engine.load_dungeon(&bsp_tree);
dungeon_engine.configure_sun_rand(20.f, 120.f, dung::Latitude::NorthernHemisphere, dung::Longitude::FW, false); // 20 minutes per day and 120 minutes per year. Global shadow.
dungeon_engine.style_dungeon();
if (!dungeon_engine.place_player(sh.size()))
std::cerr << "ERROR : Unable to place the playable character!" << std::endl;
dungeon_engine->place_keys(true);
dungeon_engine->place_lamps(20, true);
dungeon_engine->place_weapons(100, true);
dungeon_engine->place_potions(100, true);
dungeon_engine.set_screen_scrolling_mode(ScreenScrollingMode::WhenOutsideScreen);
// In game loop:
sh.clear();
bool game_over = false;
dungeon_engine->update(get_real_time_s(), kpd, &game_over); // arg0 : time from game start, arg1 : keyboard::KeyPressData object, arg2 : retrieves game over state.
if (game_over)
set_state_game_over();
dungeon_engine->draw(sh, get_real_time_s(), anim_ctr);
sh.print_screen_buffer(t, bg_color);
anim_ctr++;
![image](https://private-user-images.githubusercontent.com/32767250/340404340-b24d58d7-6ad5-4063-881d-650dd7bc905a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQzMjI0OTIsIm5iZiI6MTcyNDMyMjE5MiwicGF0aCI6Ii8zMjc2NzI1MC8zNDA0MDQzNDAtYjI0ZDU4ZDctNmFkNS00MDYzLTg4MWQtNjUwZGQ3YmM5MDVhLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA4MjIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODIyVDEwMjMxMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWU0NTlhMDMyOTc1OWU5MTM2YTgxOTcyY2VjZWE5NGE5NDQ4OGExNDQ2NTkwNTI5NmQ5ZTRhOTZkNjFiMGMyMzgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.9W1BPD7LsRlK50mmy60kIBWnbjlIirWGrAf1vaja-b0)
Note the playable character marked as a "@" in the centre of the screen.
To move the character in a game loop, use function update()
to allow keystrokes to be registered. Then control the character by pressing the ASWD keys. Press space-bar to open and close doors that you are located next to.
// Declarations
dung::BSPTree bsp_tree { 4 }; // argument: `min_room_length = 4`.
dung::DungGineTextureParams texture_params;
std::unique_ptr<dung::DungGine> dungeon_engine;
Text t;
SpriteHandler<NR, NC> sh;
Color bg_color = Color::Black;
int anim_ctr = 0;
// Initializations
bsp_tree.generate(200, 400, dung::Orientation::Vertical); // arguments: world_size_rows, world_size_cols,
first_split_orientation.
bsp_tree.pad_rooms(4); // arguments: min_rnd_wall_padding = 4, [max_rnd_wall_padding = 4].
bsp_tree.create_corridors(1); // argument: min_corridor_half_width = 1, (1 means it will be three chars wide).
bsp_tree.create_doors(100, true); // We also create doors here. Arguments: max_num_locked_doors, allow_passageways.
texture_params.dt_anim_s = 0.5;
auto f_tex_path = [](const auto& filename)
{
return folder::join_path({ "textures", filename });
};
texture_params.texture_file_names_surface_level_fill.emplace_back(f_tex_path("texture_sl_fill_0.tex"));
texture_params.texture_file_names_surface_level_fill.emplace_back(f_tex_path("texture_sl_fill_1.tex"));
texture_params.texture_file_names_surface_level_shadow.emplace_back(f_tex_path("texture_sl_shadow_0.tex"));
texture_params.texture_file_names_surface_level_shadow.emplace_back(f_tex_path("texture_sl_shadow_1.tex"));
dungeon_engine = std::make_unique<dung::DungGine>(get_exe_folder(), true, texture_params); // arguments: exe_folder, use_fow, texture_params.
dungeon_engine.load_dungeon(&bsp_tree);
dungeon_engine.configure_sun_rand(20.f, 120.f, dung::Latitude::Equator, dung::Longitude::F, true); // 20 minutes per day and 120 minutes per year. Localized shadows across the map starting at Equator & Front.
dungeon_engine.style_dungeon();
if (!dungeon_engine.place_player(sh.size()))
std::cerr << "ERROR : Unable to place the playable character!" << std::endl;
dungeon_engine->place_keys(true);
dungeon_engine->place_lamps(20, true);
dungeon_engine->place_weapons(150, true);
dungeon_engine->place_potions(150, true);
dungeon_engine->place_npcs(100, false);
dungeon_engine.set_screen_scrolling_mode(ScreenScrollingMode::WhenOutsideScreen);
// In game loop:
sh.clear();
bool game_over = false;
dungeon_engine->update(get_real_time_s(), kpd, &game_over); // arg0 : time from game start, arg1 : keyboard::KeyPressData object, arg2 : retrieves game over state.
if (game_over)
set_state_game_over();
dungeon_engine->draw(sh, get_real_time_s(), anim_ctr, ui::VerticalAlignment::BOTTOM, ui::HorizontalAlignment::CENTER, -5, 0);
sh.print_screen_buffer(t, bg_color);
anim_ctr++;